微信小程序蓝牙功能开发与问题记录

news2024/9/24 17:19:01

一、蓝牙支持情况

1. 微信小程序对蓝牙的支持情况

目前普遍使用的蓝牙规格:经典蓝牙和蓝牙低功耗。

经典蓝牙(蓝牙基础率/增强数据率):常用在对数据传输带宽有一定要求的大数据量传输场景上,比如需要传输音频数据的蓝牙音箱、蓝牙耳机等;

蓝牙低功耗 (Bluetooth Low Energy, BLE): 从蓝牙 4.0 起支持的协议,特点就是功耗极低、传输速度更快,常用在对续航要求较高且只需小数据量传输的各种智能电子产品中,比如智能穿戴设备、智能家电、传感器等。

IOS安卓

基础库

(当前开发基础库2.23+)

经典蓝牙不支持不支持,规划中/
蓝牙低功耗主机模式(手机作为客户端,主动连接)微信客户端6.5.6及以上微信客户端6.5.7及以上1.1.0及以上
从机模式(手机作为服务端,被动连接)支持支持2.10.3及以上
蓝牙信标(持续广播,但不建立连接)支持支持1.2.0及以上

2. IOS和安卓设备对蓝牙低功耗的支持情况

由于项目所使用的设备是低功耗蓝牙,故对此做调研:

IOS

安卓

连接设备数量20个6-8个
连接速度正常部分安卓机经常出现连接速度慢、连接超时的现象
传输数据量(MTU) 20字节 20字节
设备搜索支持6.0及以上版本需要打开定位权限

注意点:1)数据量超过 MTU  (20 字节)会导致错误,需要根据蓝牙设备协议进行分片传输。其中安卓分片之间的传输需要做延迟 250ms。

2)由于Android 从微信 6.5.7 开始支持,iOS 从微信 6.5.6 开始支持,因此小程序中需要做好版本检测(wx.getSystemInfoSync 获取系统信息)。

二、基本需求

1. 添加页面:开启蓝牙搜索,选择设备并输入识别码(蓝牙连接后发送识别码,匹配成功则与设备正式建立了连接)。

2. 首页:可进行蓝牙连接、切换、断开、消息监听与数据发送(识别码匹配)。

三、蓝牙API的基本使用

整理上述涉及蓝牙API的使用:

1. 添加页面(搜索蓝牙逻辑)

// 添加页
// 检查蓝牙是否开启
checkBluetoothOn(){
    let sucCallback = this.startDiscovering
    let errCallback = () => {
        // 蓝牙未开启逻辑处理
    } 
    // 如果蓝牙开启状态,就去搜索设备
    this.getBlueState(errCallback, sucCallback)
},
// 判断手机蓝牙是否打开
getBlueState (errCallback, sucCallback) {
    this.$getBlueState(errCallback).then(res=>{
        if(res.errno === 0){
            sucCallback ? sucCallback() : ''
        } else {}
    });
},
// 蓝牙搜索逻辑 15s关闭
startDiscovering(){
    // 开始搜寻附近设备
    this.$discoveryBlue(this.found)
    setTimeout(()=>{
        this.handleStopDiscovery()
    }, 15000)
},
// 找到新设备就触发该方法 处理数据逻辑
found(res) {
    var devices = res.devices;
    devices.map(async item => {
        // 对设备信息处理
    })
},
handleStopDiscovery(){
    let stopLoading = () => {
        this.loading = false
        // 其它关闭逻辑
        
    }
    this.$stopDiscoveryBlue(stopLoading, stopLoading)
},

2. 首页(连接逻辑)

onShow() {
    // 第一次进来如果没有连接 去自动连接
    let sucCallback = (this.blueDeviceList?.length && this.isAutoConnect) ? 
           this.autoConnect : this.isConnnected
    let errCallback = () => {
        // 未开启逻辑
    }
    // 如果蓝牙开启状态 就去连接
    this.getBlueState(errCallback, sucCallback)
    this.isAutoConnect = false
},
methods: {
    async autoConnect(){
        this.isFound = false
        this.$discoveryBlue(this.found)
        .catch((err) => {
            // 查找失败逻辑处理
        })
         // 15s后未找到数据
        setTimeout(()=>{
            if(!this.isFound){
                this.notFound()
            }
        }, 15000)
    },
    // 找到新设备就触发该方法 处理数据逻辑
    found(res) {
        var devices = res.devices;
        devices.map(async item => {
            // 以 deviceId 为唯一标识,过滤重复设备
            if(item.deviceId == this.connectingDevice.deviceId) {
                this.isFound = true
                this.handleConnect(this.connectingDevice)
                wx.offBluetoothDeviceFound(this.found) // 防止回调函数重复执行导致重复连接
            }
        })
    },
    notFound(){
        let stopLoading = () => {
            // 其它逻辑
        }
        this.$stopDiscoveryBlue(stopLoading, stopLoading)
    },
    async handleConnect(deviceInfo){
        let { deviceId, idCode } = deviceInfo
        this.$connectBlue(deviceId).then(async (res)=>{
            const {notifyServiceId, writeServiceId, notifyCharacteristicId, writeCharacteristicId} = await this.getBasicIds(deviceId)
            let listenValueChange = () => {
                this.$listenCharacteristicValueChange(this.getInfoFromBluetooth)
            }
            this.$notifyBlue(deviceId, listenValueChange, notifyServiceId, notifyCharacteristicId)
            .then(()=>{
                let { buffer } = this.$getBuffer({
                  body: idCode, 
                  length: 11, // 帧长度
                  command, // 绑定命令字
                })
                this.$sendBlue(deviceId, buffer, writeServiceId, writeCharacteristicId )
                .catch(async(err) => {
                    await this.stopConnect(deviceInfo.deviceId)
                    // 其它逻辑
                })
            }).catch(async()=>{
                await this.stopConnect(deviceInfo.deviceId)
                // 其它逻辑
            })

        }).catch(()=>{
            // 逻辑处理
        })
    },
    // 获取蓝牙传输过来的数据处理
    getInfoFromBluetooth(res){
        
    },
    stopConnect(deviceId){
        return new Promise((resolve) => {
            let connectedDevice = this.getStorageInfo('connectedDevice', {})
            let connectedDeviceId = deviceId || connectedDevice?.deviceId 
            if(!connectedDeviceId) return resolve()
            this.$closeConnection(connectedDeviceId).then((res) => {
                // 逻辑处理
                resolve()
            }).catch((err)=>{
                // 断连失败逻辑处理
            })
        })
    },
    isConnnected(){
        const { connected } = this.connectStatus
        let connectedDevice = this.blueDeviceList.find(item => item.status == connected)
        if(connectedDevice){
            this.$getServicesBlue(connectedDevice.deviceId)
            .then(async()=>{
                // 如果连接状态,需要重新建立消息通道
                const {notifyServiceId, notifyCharacteristicId} = await this.getBasicIds(connectedDevice.deviceId)
                let listenValueChange = () => {
                    this.$listenCharacteristicValueChange(this.getInfoFromBluetooth)
                }
                this.$notifyBlue(connectedDevice.deviceId, listenValueChange, notifyServiceId, notifyCharacteristicId)
                .catch(async()=>{
                    await this.stopConnect(deviceInfo.deviceId)
                    // 其它逻辑
                })
            })
            .catch(err => {
                // 连接已断开
                if(err.errCode == 10006){
                    // 其它逻辑
                }
            })
        }
    },
    getBasicIds(deviceId){
        return new Promise(async(resolve, reject) => {
            let {notifyServiceId, writeServiceId} = await this.$getServicesBlue(deviceId)
            let notifyCharacteristicId = await this.$getCharacteristicsBlue(deviceId, notifyServiceId)
            let writeCharacteristicId = await this.$getCharacteristicsBlue(deviceId, writeServiceId)
            resolve({notifyServiceId, writeServiceId, notifyCharacteristicId, writeCharacteristicId})
        })
    },
}

3. 公共方法

// 公共方法封装小程序蓝牙api
function $getBlueState(errCallback) {
    return new Promise((resolve, reject) => {
        $initBlue().then(res=>{
            resolve(res)
        }).catch(err=> {
            if(err.errCode === 10001){
                return errCallback ? errCallback() :
            }
        })
    })
}
function $initBlue() {
    return new Promise((resolve, reject) => {
        uni.openBluetoothAdapter({
            success(res) {
                resolve(res)
            },
            fail(err) {
                reject(err)
            }
        })
    })
}
function $discoveryBlue(callback) {
    return new Promise((resolve, reject) => {
        uni.startBluetoothDevicesDiscovery({
            services: mainServiceIds,
            allowDuplicatesKey: true,
            success(res) {
                uni.onBluetoothDeviceFound(callback)
            },
            fail(err) {
                console.error(err)
                reject(err)
            }
        })
    })
}
function $stopDiscoveryBlue(sucCallback, errCallback) {
    uni.stopBluetoothDevicesDiscovery({
        success(res) {
            console.log('停止设备搜索')
            sucCallback ? sucCallback() : ''
        },
        fail(err) {
            console.log('停止搜索设备失败')
            console.error(err)
            errCallback ? errCallback() : ''
        }
    })
}
function $getServicesBlue(deviceId) {
    return new Promise((resolve, reject) => {
        uni.getBLEDeviceServices({
            deviceId,
            success(res) {
                // 逻辑(根据硬件给的协议取对应服务ID)
                resolve({
                    notifyServiceId,
                    writeServiceId
                })
            },
            fail(err) {
                console.error(err)
                reject(err)
            }
        })
    })
}
function $getCharacteristicsBlue(deviceId, serviceId) {
    return new Promise((resolve, reject) => {
        uni.getBLEDeviceCharacteristics({
            deviceId,
            serviceId,
            success(res) {
                const characteristicId = res.characteristics[0].uuid
                resolve(characteristicId)
            },
            fail(err) {
                console.error(err)
            }
        })
    })
}
function $notifyBlue(deviceId, callback, serviceId, characteristicId) {
    return new Promise((resolve, reject) => {
        uni.notifyBLECharacteristicValueChange({
            state: true, // 启用 notify 功能
            deviceId, // 设备id
            serviceId: serviceId, // 监听指定的服务
            characteristicId: characteristicId, // 监听对应的特征值
            success(res) {
                callback()
                resolve()
            },
            fail(err) {
                console.log(serialDataChannel.serviceId, serialDataChannel.characteristicId)
                console.error(err)
                reject()
            }
        })
    })
}
function $sendBlue(deviceId, buffer, serviceId, characteristicId) {
    return new Promise((resolve, reject) => {
        uni.writeBLECharacteristicValue({
            deviceId,
            serviceId: serviceId,
            characteristicId: characteristicId,
            value: buffer,
            success(res) {
                resolve(res)
            },
            fail(err) {
                console.error(err)
                reject()
            }
        })
    })
}
function $listenCharacteristicValueChange(callback) {
    uni.onBLECharacteristicValueChange(res => {
        callback(res)
    })
}

四、问题记录

1. 手机开启了蓝牙,但是api openBluetoothAdapter 仍然调用失败?

检查是否给微信授权了蓝牙功能。

2. onBluetoothDeviceFound 搜索到设备以后需要建立连接。接口持续搜索会导致重复连接。

防止回调函数重复执行导致重复连接,需要调用 wx.offBluetoothDeviceFound(this.found) 。 

3. 设备Id、特征值Id、服务Id是否是唯一?
设备Id:唯一。


特征值Id和服务Id不唯一:不同蓝牙的服务Id和特征值Id可能是一样的;同一蓝牙设备的服务Id和特征值Id是固定的。

4. 既然蓝牙的特征值Id和服务Id是固定的,那是否可以写死,直接调用读写api( notifyBLECharacteristicValueChange 和 writeBLECharacteristicValue)?

不能。api会调用失败。需要先调用 getBLEDeviceServices 和 getBLEDeviceCharacteristics 。

5. 蓝牙读写通信的数据如何转化?

通信过程涉及到的转化包括:10进制和16进制互相转化、16进制和 ArrayBuffer 互相转化。

1)10进制转16进制:

let deci = 172;
console.log(deci.toString(16))

2)16进制转10进制:

let hex = '0xAC'
console.log(parseInt(hex, 16))

3)16进制转字符串:

let hexToString = (hex) => {
  let str = '';
  for (let i = 0; i < hex.length; i += 2) {
    let v = parseInt(hex.substr(i, 2), 16);
    if (v) str += String.fromCharCode(v);
  }
  return str;
}
hexToString('68656c6c6f')

4)字符串转16进制:

let stringToHex = (str) => {
  let val= "";
  for(let i = 0; i < str.length; i++){
    if(val == "")
      val = str.charCodeAt(i).toString(16);
    else
      val += "," + str.charCodeAt(i).toString(16);
  }
  return val;
}
stringToHex('hello')

5)字符串转16进制转 ArrayBuffer:

let info = 'hello'
const buffer = new ArrayBuffer(info.length)
const dataView = new DataView(buffer)
for (var i = 0; i < info.length; i++) {
  dataView.setUint8(i, info.charAt(i).charCodeAt())
}
wx.writeBLECharacteristicValue({
  deviceId,
  serviceId,
  characteristicId,
  value: buffer,
  success (res) {
    console.log('writeBLECharacteristicValue success', res)
  }
})

6)ArrayBuffer 转 16进制:

function ab2hex(buffer) {
  let hexArr = Array.prototype.map.call(
    new Uint8Array(buffer),
    function(bit) {
      return ('00' + bit.toString(16)).slice(-2)
    }
  )
  return hexArr.join('');
}
wx.onBLECharacteristicValueChange((res) => {
  console.log(ab2hex(res.value))
})


6. 蓝牙帧发送出现分包的情况?

有时蓝牙设备传来的帧会有不完整的情况,需要做拼接处理。此处逻辑为:在监听函数中,获取到不完整的帧时,如果帧头正确,保存并等下一帧,否则舍弃。帧头正确并获取到下一帧后进行拼接。当获取到的帧符合我们期待的长度时,进行之后的帧校验与业务逻辑处理。如果指定时间没有接受到有效帧数据,则断连。

7. 超时间没有获取到接收有效帧数据的断连逻辑?

本项目的协议上规定,状态帧每30s上传一次。如果接收到有效状态帧,则更新状态,并对关闭连接进行延迟;如果没有获取到有效状态帧,则2min后会断开连接。具体如下:

// 有效帧获取到后执行
this.delayStopConnect(this.connectedDevice.deviceId)

// 对 delayStopConnect 进行 debounce,状态帧超过2分钟没有回复则断连
delayStopConnect: utils.debounce(async function(deviceId){
    await this.stopConnect(deviceId)
    uni.hideLoading();
    this.showModalTips('设备连接异常')
}, 120000),

// debounce 函数
function debounce(fn, delay, isImmediate) {
    var timer = null;  //初始化timer,作为计时清除依据
    return function() {
      var context = this;  //获取函数所在作用域this
      var args = arguments;  //取得传入参数
      clearTimeout(timer);
      if(isImmediate && timer === null) {
          //时间间隔外立即执行
          fn.apply(context,args);
        timer = 0;
        return;
      }
      timer = setTimeout(function() {
        fn.apply(context,args);
        timer = null;
      }, delay);
    }
}

8. 如何保证帧的有效性(帧校验)?

一般来说,硬件会在协议上说明。一般是拿到回复帧后,取其中某几段进行和校验。如果校验得到的值和回复帧的值相同,则校验成功。例如:一个回复帧包含帧头+帧长度+命令字+识别码+校验码+帧尾。校验码等于命令字与识别码的和。假设命令字是 0xAB,识别码是 0x01,则正确的校验码是 0xAC 。把它跟蓝牙传来的回复帧的校验码进行比较即可。

9. 保证一次连接一个设备的逻辑处理?

由于当前项目的需求是,一个手机只能连接一个蓝牙设备(小程序做处理),但是实际手机是支持连接多个蓝牙设备的,所以如果用户一次性点了很多个设备,需要做相关处理。我的思路是创建一个数组堆,记录连接的设备。如果连接上则 push(),如果发现数组长度大于1,则 shift() 掉最先连接的。

10. 小程序能否主动监听到蓝牙开启与关闭?
可以通过 onBLEConnectionStateChange 监听到蓝牙断开;蓝牙开启监听不到,除非手动定时请求api判断(如果官方有相关监听api,欢迎指正)。

11. 切换页面,能否持续收到监听数据,是否要重新建立监听?
在页面A建立消息通道(notifyBLECharacteristicValueChange)以后,跳转到页面B,仍然可以监听到数据(不论是 navigateTo 还是 navigateBack)。但如果其它页面此时再次调用此api,会覆盖掉之前的消息通道。

12. 设备A连接后,断开连接。设备B立刻连接,连接不上,需要过1分钟左右(安卓机尤其明显)?
因为程序做了连接超时则请求失败的处理。经排查发现,连接超时的api是设备搜索(startBluetoothDevicesDiscovery)和设备连接(getConnectedBluetoothDevices)。
一个不完美,但管用的解决方法:这两个 api 不设置 services 参数。

13. 关闭再次打开小程序,蓝牙连接是否会中断?
理论上是,实际上有时不会。
首先,2023.23.3 官方回复目前暂时不支持后台下的蓝牙功能(https://developers.weixin.qq.com/community/develop/doc/0008a409c889c0f8d76fd1ec356400?highLine=%25E8%2593%259D%25E7%2589%2599%25E5%2590%258E%25E5%258F%25B0%25E5%259C%25BA%25E6%2599%25AF)
其次,小程序的运行机制是(https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html):
小程序切换后台(包括息屏)5s,微信会停止小程序js线程执行,在小程序再次进入前台时事件和接口回调会触发;小程序切换后台30分钟,小程序销毁。
二者结合,理论上蓝牙应该在30分钟后被断开。但事实上,发现有时候ios和安卓都没有断开。因为其它手机搜索不到相应设备。

14. 一些 ios 和安卓的 API 兼容问题

1)关于断开连接(closeBLEConnection):ios 设备在没有连接蓝牙设备时,调用断开接口,显示调用成功;但是安卓机会得到错误码 10006 (当前设备已断开连接)。

2)关于搜索设备(startBluetoothDevicesDiscovery):安卓机搜索一次以后,再次调用该接口,刚才已经搜索出来的设备搜索不到了,除非加上参数:allowDuplicatesKey。ios 加不加这个参数都可以正常搜索到设备。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488279.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI工具和用法汇总—集合的集合

AI 工具和用法汇总 汇集整理 by Staok/瞰百&#xff0c;源于相关资料在我这慢慢越积累越多&#xff0c;到了不得不梳理的程度。 文中有许多内容作者还没有亲自尝试&#xff0c;所以很多内容只是罗列&#xff0c;但信息大源都已给出&#xff0c;授人以渔&#xff0c;欢迎 PR 补…

hadoop3.2.1+hive3.1.2-docker安装

Hadoop 1.拉取镜像 docker pull hadoop_hive:32.运行容器 建立hadoop用的内部网络(此步出错&#xff0c;若与其它网段冲突&#xff0c;可省略) #指定固定ip号段 docker network create --driverbridge --subnet172.17.0.1/16 hadoop建立Master容器&#xff0c;映射端口 10…

滚动加载数据

效果图: 综合使用后,觉得还是以下绑定 div监听滚动条的方式好用,有的可以监听滚轮滚动,但是监听不到鼠标拖动滚动条,下面这种方式两者都可以监测到↓ <template><div class"scrollTest-container" id"scrollTestContainer"><div class&quo…

简单分享微信小程序上的招聘链接怎么做

招聘小程序的主要用户就是企业招聘端和找工作人员的用户端,下面从这两个端来对招聘小程序开发的功能进行介绍。 企业端功能 1、岗位发布:企业根据自身岗位需求,在招聘app上发布招聘岗位及所需技能。 2.简历筛选:根据求职者提交的简历选择合适的简历,并对公开发布的简历进行筛…

105.(cesium篇)cesium指南针

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en">

【软考高级】2019年系统分析师综合知识

第 1 题 面向对象分析中&#xff0c;一个事物发生变化会影响另一个事物&#xff0c;两个事物之间属于&#xff08;1&#xff09;。 (1) A .关联关系 B .依赖关系 C .实现关系 D .泛化关系 参考答案&#xff1a;(1)B 试题解析&#xff1a; 本题考查的是 UML 图中类的关系…

业务零中断,数据零丢失|庚顿新一代双活高可用架构实时数据库为流程工业核心业务保驾护航

新一代双活架构高可用架构实时数据库管理系统可实现流程工业数据平台“零中断”、“零丢数”的超高可用性要求&#xff0c;在满足实时性要求的同时&#xff0c;实现断网/掉电时业务不中断、不丢数&#xff0c;突破传统主备架构。 随着生产生活自动化、数字化、信息化水平不断升…

二叉树或者多叉树直径问题

原题链接&#xff1a;543. 二叉树的直径 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一棵二叉树&#xff0c;你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 什么是任意两点路径…

ChatGPT来了不用慌,广告人还有这个神器在手

#ChatGPT能取代广告人吗&#xff0c;#ChatGPT会抢走你的工作吗&#xff1f;#ChatGPT火了&#xff0c;会让营销人失业吗&#xff1f;自ChatGPT爆火以来&#xff0c;各种专业or非专业文章不停给广告人强加焦虑&#xff0c;但工具出现的意义&#xff0c;更多在于提效而非替代&…

BetaFlight统一硬件配置文件研读之timer命令

BetaFlight统一硬件配置文件研读之timer命令 1. 源由2. 代码分析3. 实例分析4. 配置情况4.1 AFn配置查表4.2 timer4.3 timer show4.4 timer pin list 5. 参考资料 统一硬件配置文件的设计是一种非常好的设计模式&#xff0c;可以将硬件和软件的工作进行解耦。 1. 源由 cli命令…

某程序员哀叹:二本计算机,4年开发,年包才40多万。二本真的不如985/211吗?

前段时间&#xff0c;某职场论坛上有人发了一个帖子&#xff0c;发帖人问&#xff1a;为什么大家工资那么高&#xff0c;三五年都六七十万了&#xff1f;我二本计算机专业&#xff0c;四年前端开发&#xff0c;找个年包40万多点就顶头了。 原贴如下&#xff1a; 有网友表示楼主…

【Docker】Docker上篇

文章目录 一、认识Docker1、Docker出现的背景2、Docker的历史3、虚拟机技术与容器技术4、容器比虚拟机快的原因5、对Devops层面的影响 二、Docker的安装的原理1、核心名词2、安装Docker&#xff08;for Linux&#xff09;3、配置阿里云镜像加速4、Run的流程和Docker原理 三、Do…

phoenix使用(一)之全局索引的使用

索引使用 1.1. 全局索引&#xff08;Global Indexing&#xff09; 名词解释&#xff1a; 全局索引&#xff0c;适用于读多写少的业务场景。使用Global indexing在写数据的时候开销很大&#xff0c;因为所有对数据表的更新操作&#xff08;DELETE, UPSERT VALUES and UPSERT SEL…

chatgpt软件 - chatbox

文章目录 打开github 进入chatgpt官方要记得登录&#xff01;&#xff01;点击头像将key命名&#xff1a;安装chatbox下面就可以开始使用啦&#xff01;&#xff01; 打开github https://github.com/Bin-Huang/chatbox 特性&#xff1a; 更自由、更强大的 Prompt 能力数据存储…

看了这13个案例,我总算又get到了企业级无代码

企业级无代码为何面向软件公司的研发团队&#xff1f; 哪些人能在企业级无代码旅程中获益&#xff1f; 如何找到实践的切入点&#xff1f; 如何开展规模化的交付实践&#xff1f; 如何快速组建企业级无代码开发团队&#xff1f; ...... 近期smardaten从组织创新、技术创新…

OCC的拓扑基础数据结构

在OpenCASCADE中,提供了一系列的拓扑基础数据结构,用于表示几何实体的拓扑结构,其中最基本的是TopoDS_Shape。下面是一些其他常用的拓扑数据结构: TopoDS_TCompound:代表了复合实体,即由多个几何实体组合而成的实体,可以包含任意数量和类型的其他几何实体。 TopoDS_TCom…

【消费战略方法论】认识消费者的恒常原理(三):消费者刺激反馈原理

人类是一种高度智能的生物&#xff0c;而所谓智能的核心在于其理解世界的能力&#xff0c;而理解世界的过程中必然伴随着感知和反应。人的刺激反馈机制就是在这个过程中发挥着重要的作用。 刺激反馈机制是一种生物学的反应现象&#xff0c;它指的是人体对外界刺激的感知与反应…

vue使用富文本和打印 egg使用ctx.getFileStream进行文件上传

vue2使用富文本 安装 npm install vue-quill-editor --save全局引入(main) import VueQuillEditor from vue-quill-editor//调用编辑器 // 样式 import quill/dist/quill.core.css import quill/dist/quill.snow.css import quill/dist/quill.bubble.css Vue.use(VueQuillE…

【汽车电子】浅谈LIN总线

目录 1.为何使用LIN总线 2.什么是LIN总线&#xff1f; 3.LIN总线的主从关系 4.LIN的特点 5.LIN报文帧结构 6.LIN总线波形 7.帧类型 8.进度表 9.状态机的实现 10.总结 11.声明 1.为何使用LIN总线 在这里你可能要问“不都有CAN总线了吗&#xff1f;这个LIN总线又是从哪…

【JS笔记】JS操作字符串、对象、数组、时间对象、数值操作、定时器

这篇文章&#xff0c;主要介绍JS操作字符串、对象、数组、时间对象、数值操作、定时器。 目录 一、字符串 1.1、定义字符串 1.2、字符串方法 1.3、模板字符串 1.4、JSON字符串 二、对象操作 2.1、定义对象 2.2、对象方法 三、数组操作 3.1、定义数组 3.2、数组方法 …