【uniapp】举例解决uniapp无法注销回调函数,导致多次调用 + 举例EPS32-arduino 与uniapp间蓝牙JSON通信 BLE链接与通信过程

主要经验

1、ESP32 作为BLE服务器,将BLE客户端发来数据返回,作为调试手机APP的工具

2、编写一个公共文件ble_api.js,实现蓝牙功能。编写一个类,类中存储BLE通信数据和方法,定义此类唯一一个实例,供网页.vue调用。

3、为了解决uniapp蓝牙无法注销回调函数,在ble_api.js就BLE uni.notify编写唯一一个回调函数,保证唯一回调函数注册一次。否者会出现多次调用问题。

4、在回调函数中,使用uni.$emit公共函数,将BLE数据发送至具体网页.vue中。

5、经实验具体做法,在每个网页script内, onshow()中调用BLE类实例注册一次BLE uni.notify的回调函数。否者,要么BLE不接收数据,要么回调函数重复多次执行。

      BLE uni.notify的回调函数中使用uni.$emit()转发BLE数据,在具体网页.vue的 onshow()使用uni.$on()监听接收与处理数据,在此具体网页.vue的onhide()中使用uni.$off()注销此监听。

 6、推测:其他uniapp设备中,回调函数是否可以按照类似方法处理, 要么app生命周期内注册一次,要么在网页显示周期内注册一次?

ESP32程序

ESP32作为BLE服务器,将BLE客户端发来数据返回,作为调试手机APP的工具。

复制代码
/*
  将接收的数据,返送回去。
   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

    将接收的数据,返送回去。
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include "String.h"
#include <ArduinoJson.h>  // 需安装 ArduinoJson库

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
String Receive_Char_Read = "";
String Receive_char;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"  // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
StaticJsonDocument<200> jsonBuffer;  //声明一个JsonDocument对象,长度200
StaticJsonDocument<200> doc;

void str2json(String jsonString) {
  // 反序列化JSON
  DeserializationError error = deserializeJson(jsonBuffer, jsonString);
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }
  // 解析JSON
  const char *str = jsonBuffer["name"];  // 读取字符串
  int data1 = jsonBuffer["age"];     // 读取整形数据
  Serial.println(str);
  Serial.println(data1);
  return;
};
// 定义发送数据函数
void sendData(String jsonString)
{
    // json数据及其字符串化
    // doc["sensor"] = "sensor";
    // doc["value"] = 1234;
    // String jsonString;
    // serializeJson(doc, jsonString);
    // 发送Json字符串
    pTxCharacteristic->setValue(jsonString);  // 设定特性值
    //Serial.println(jsonString);
    //pTxCharacteristic->setValue(txString);
    pTxCharacteristic->notify();  //发送特性(描述符、值等)
    delay(100);  // bluetooth stack will go into congestion, if too many packets are sent
}
// 定义处理连接等回调函数
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
  };

  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
  }
};

// 定义回调函数,处理接收数据。
class MyCallbacks : public BLECharacteristicCallbacks {

  void onWrite(BLECharacteristic *pCharacteristic) {
    String rxValue = pCharacteristic->getValue();
    if (rxValue.length() > 0) {
      Serial.println("*********");
      Serial.print("Received: ");
      Serial.println(rxValue);
      // for (int i = 0; i < rxValue.length(); i++) {
      //   Serial.print(rxValue[i]);
      // }
      // str2json(rxValue);
      // Serial.println();
      // Serial.println("*********");
      sendData(rxValue);//蓝牙发送
    }
  }
};

void setup() {
  Serial.begin(115200);

  // Create the BLE Device 初始化蓝牙外设
  BLEDevice::init("UART Service");

  // Create the BLE Server 创建一个服务器
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service 创建一个BLE服务
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic 创建一个BLE特性 通知属性
  pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);

  pTxCharacteristic->addDescriptor(new BLE2902());  // 绑定描述符
  // Create a BLE Characteristic 创建一个BLE特性  读属性
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);

  pRxCharacteristic->setCallbacks(new MyCallbacks());  // 绑定回调函数

  // Start the service 启动服务器
  pService->start();

  // Start advertising  开始广播
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {

  if (deviceConnected) {

  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500);                   // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising();  // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}
arduino代码
复制代码

 

 uniAPP程序

手机作为BLE客户端,包括功能有 BLE设备搜索、连接、接收和发送数据。

公共文件 BLEl类

复制代码
  1 // ble_api.js 
  2  
  3 // 文档地址 https://uniapp.dcloud.net.cn/api/system/ble.html
  4 // 同ESP32内定义的UUID
  5 const SERVICE_UUID_ESP32 = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; // UART service UUID
  6 const CHARACTERISTIC_UUID_ESP32_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
  7 const CHARACTERISTIC_UUID_ESP32_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
  8 // globalClass.js  
  9 class BLE_DeviceClass {
 10     constructor() {
 11         // ... 初始化代码 
 12         this.name = "BLE_APP";
 13         // 是否已连接
 14         this.connected = false;
 15         this.inited = false;
 16 
 17         // 当前连接的设备id
 18         this.connectId = "";
 19         this.previousConnectId = ""; //有链接时,为空
 20 
 21         this.devices = []; //蓝牙周围设备列表
 22         // 接收到的JSON字符串
 23         this.rxJSONString = "";
 24         // 用于存储 接收到的JSON object数据
 25         this.rxJSONobject = {}; // 用于存储BLE发来的JSON object数据 在defaultCallback(jsonString)方法中完成
 26 
 27         // 避免以下重复注册回调函数,内部使用
 28         this.BLEAdapterStateChangeCallback = false;
 29         this.BLEConnectionStateChangeCallback = false;
 30         //周期性函数
 31         this.intervalId = null; // 用于存储setInterval返回的ID
 32 
 33         // 创建一个数组来存储BLE接收事件监听器的引用(但实际上这里只是模拟,防止重复注册相同监听函数)  
 34         this.eventListeners = [];
 35     }
 36     /*-----------------------------------------------------------------------*/
 37     // 内部函数,防止重复添加 蓝牙接收数据回调函数
 38     addeventListeners(callback) { // 参数为 funcction
 39 
 40         // this.eventListeners.forEach(listener => {// 遍历
 41         //     // 判别是否已经注册  
 42         const index = this.eventListeners.indexOf(callback);
 43         if (index !== -1) {
 44             return false; // 已有,不重复添加
 45         };
 46         // });
 47         this.eventListeners.push(callback); //添加
 48         return true;
 49     }
 50     /*-----------------------------------------------------------------------*/
 51     // 内部函数,防止重复添加 蓝牙接收数据回调函数    
 52     isIneventListeners(callback) {
 53         const index = this.eventListeners.indexOf(callback);
 54         if (index !== -1) {
 55             return true;
 56         } else {
 57             return false;
 58         };
 59     }
 60     /*-----------------------------------------------------------------------*/
 61     // 测试
 62     myMethod() {
 63 
 64 
 65         // ... 方法实现  
 66         console.log(this.name);
 67 
 68         /*
 69         const person = {
 70           firstName: "Bill",
 71           lastName : "Gates",
 72           language : "EN"
 73         };
 74         
 75         
 76         console.log( Object.keys(person));
 77         console.log( Object.values(person));
 78         let keys = Object.keys(person);// 获取JSON objet的键
 79         let text = "";
 80         for (let key in person) {
 81           text += person[key];
 82           person[key] = key+"_value";
 83         }
 84         console.log( text);
 85         console.log( person);
 86         if(keys.includes("language"))
 87         {
 88             console.log(person["language"]);
 89         }*/
 90     }
 91 
 92     /*-----------------------------------------------------------------------*/
 93     // API函数,开启蓝牙外设
 94     initBlue() {
 95         console.log('初始化蓝牙开始...')
 96         const _this = this;
 97         return new Promise((resolve) => {
 98             uni.openBluetoothAdapter({
 99                 success(res) {
100                     console.log('初始化蓝牙成功')
101                     _this.inited = true;
102                     resolve(true);
103                 },
104                 fail(err) {
105                     console.log('初始化蓝牙失败', err)
106                     uni.showModal({
107                         title: '失败提示',
108                         content: "初始化蓝牙失败",
109                         showCancel: false
110                     });
111                 }
112             })
113         });
114     }
115     /*-----------------------------------------------------------------------*/
116     //API函数,扫描外围设备,包括ESP32
117     async scan_start() {
118 
119         const _this = this;
120         if (!_this.inited) {
121             await _this.initBlue(); //注意this的作用域
122         };
123         // 设定扫描回调函数
124         this.scan_callback();
125         // 开始扫描外围设备
126         await uni.startBluetoothDevicesDiscovery({
127             success(res) {
128                 console.log('启动搜索');
129             },
130             fail(err) {
131                 console.log('启动搜索失败', err)
132                 uni.showModal({
133                     title: '失败提示',
134                     content: "启动搜索失败",
135                     showCancel: false
136                 });
137             }
138         });
139         //console.log('读取搜索结果');
140         // 扫描到外围蓝牙设备后的回调
141     }
142     //  内部函数,在扫描scan_start()前设定回调函数。
143     async scan_callback() {
144         const _this = this;
145         //console.log('搜索结果');
146         await uni.onBluetoothDeviceFound(
147             ({
148                 devices
149             }) => {
150                 _this.devices.push(...devices);
151                 // 去重
152                 _this.devices = [..._this.devices].filter((cur, index, self) => {
153                     return self.findIndex(item => item.deviceId == cur.deviceId) ===
154                         index;
155                 });
156                 //console.log(_this.devices);
157             });
158     }
159     /*-----------------------------------------------------------------------*/
160     //API函数,扫描外围设备,包括ESP32
161     async scan_stop() {
162         // 停止扫描设备, 否则会浪费性能
163         await uni.stopBluetoothDevicesDiscovery({
164             success(res) {
165                 console.log("停止蓝牙设备搜索")
166                 //console.log(res)
167             },
168             fail(err) {
169                 console.error(err);
170             }
171         });
172     }
173 
174     /*-----------------------------------------------------------------------*/
175     // 内部函数,字符串转为ArrayBuffer对象,参数为字符串
176     str2ab(str) {
177         var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
178         var bufView = new Uint16Array(buf);
179         for (var i = 0, strLen = str.length; i < strLen; i++) {
180             bufView[i] = str.charCodeAt(i);
181         }
182         return buf;
183     }
184 
185     /*-----------------------------------------------------------------------*/
186     // 内部函数,监听蓝牙模块状态
187     setCheckAdapterStateChange() {
188         // 避免以下重复注册回调函数
189         let _this = this;
190         if (_this.BLEAdapterStateChangeCallback) {
191             return;
192         }
193         //this.BLEConnectionStateChangeCallback = false;
194         uni.onBluetoothAdapterStateChange(function(res) {
195             console.log('BLE adapterState changed, now is', res)
196             _this.BLEAdapterStateChangeCallback = true;
197         });
198     }
199     /*-----------------------------------------------------------------------*/
200     //监听低功耗蓝牙连接状态的改变事件。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
201     setCheckConnectionStateChange() {
202         // 避免以下重复注册回调函数
203         let _this = this;
204         if (_this.BLEConnectionStateChangeCallback) {
205             return;
206         };
207 
208         uni.onBLEConnectionStateChange(function(res) {
209             //console.log('BLEConnectionState changed, now is', res);
210             // 该方法回调中可以用于处理连接意外断开等异常情况
211             //console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`);
212             
213             if (!res.connected) {
214                 // 是否已连接
215                 _this.connected = false;
216                 // 当前连接的设备id
217                 console.log(`BLE device ${res.deviceId} state has disconnected`);
218                 this.connectId = "";
219             };
220 
221             _this.BLEConnectionStateChangeCallback = true;
222 
223         });
224     }
225     /*-----------------------------------------------------------------------*/
226     // 内部函数
227     setMTU() {
228         const _this = this;
229         //3)设置MTU,否则传输报文不全,默认是23bytes,但是蓝牙本身是需要3bytes,故而只能传输20bytes
230         setTimeout(() => {
231             console.log('deviceId>>>', _this.connectId);
232             uni.setBLEMTU({
233                 deviceId: _this.connectId,
234                 mtu: 512,
235                 success: (res) => {
236                     console.log('设置MTU最大值512成功', res);
237                     //启动蓝牙接收数据
238                 },
239                 fail: (res) => {
240                     console.log('设置MTU最大值512失败', res);
241                 }
242             });
243         }, 1000);
244 
245     }
246     //API函数,
247     connect() {
248         const _this = this;
249         console.log('设备id', _this.connectId);
250 
251         const promise = new Promise((resolve) => {
252             uni.showLoading({
253                 title: '连接蓝牙设备中...'
254             })
255             uni.createBLEConnection({
256                 deviceId: _this.connectId,
257                 success(res) {
258                     console.log('蓝牙设备连接成功');
259                     _this.previousConnectId = null; // 有连接时置为空
260                     console.log(res);
261                     // 监听蓝牙模块状态
262                     _this.setCheckAdapterStateChange();
263                     // 设置MTU,传输报文255字节
264                     _this.setMTU();
265                     // 在需要关闭加载提示的时候,调用 uni.hideLoading 方法
266                     uni.hideLoading();
267                 },
268                 fail(err) {
269                     console.log('蓝牙设备连接失败');
270                     console.error(err);
271                     // 在需要关闭加载提示的时候,调用 uni.hideLoading 方法
272                     uni.hideLoading();
273                 }
274             });
275         });
276 
277     }
278     /*-----------------------------------------------------------------------*/
279     //API函数,断开链接
280     disConnect() {
281         const _this = this;
282         _this.previousConnectId = _this.connectId;
283         if (!_this.connectId) return;
284         uni.showLoading({
285             title: '断开蓝牙连接中...'
286         });
287         return new Promise((resolve) => {
288             uni.closeBLEConnection({
289                 deviceId: _this.connectId,
290                 success(res) {
291                     console.log("断开连接成功:", res)
292 
293                     // 断开后必须释放蓝牙资源,不然再点击连接就连不上了
294                     // https://uniapp.dcloud.net.cn/api/system/bluetooth.html
295                     uni.closeBluetoothAdapter({
296                         success() {
297                             console.log('释放蓝牙资源成功')
298                             _this.connected = false;
299                             _this.connectId = null
300                             _this.SERVICE_UUID = null
301                             _this.CHARACTERISTIC_UUID = null;
302                             _this.inited = false;
303                             _this.received = false;
304 
305                             // 延时下保险点
306                             setTimeout(() => {
307                                 uni.hideLoading();
308                                 resolve();
309                             }, 600)
310                         },
311                         fail() {
312                             uni.hideLoading();
313                         }
314                     })
315 
316                 },
317                 fail(err) {
318                     uni.hideLoading();
319                     console.log('断开连接失败:', err)
320                     uni.showModal({
321                         title: '失败提示',
322                         content: "断开连接失败 " + err.errMsg,
323                         showCancel: false
324                     });
325                 }
326             })
327         })
328     }
329     /*-----------------------------------------------------------------------*/
330     // API函数,获取蓝牙服务UUID等信息
331     getServices() {
332         uni.getBLEDeviceServices({
333             deviceId: this.connectId, // 设备ID
334             success(res) {
335                 console.log(res);
336             },
337             fail(err) {
338                 console.error(err);
339             }
340         });
341     }
342     /*-----------------------------------------------------------------------*/
343     // API函数,获取指定服务下的特性
344     getCharacteristics() {
345         uni.getBLEDeviceCharacteristics({
346             deviceId: this.connectId, // 设备ID
347             serviceId: SERVICE_UUID_ESP32, // 服务UUID
348             success(res) {
349                 console.log(res);
350             },
351             fail(err) {
352                 console.error(err);
353             }
354         });
355     }
356     /*-----------------------------------------------------------------------*/
357     /*接收外设设备发来的具有通知属性服务及其特性*/
358     // 内部函数,ArrayBuffer转16进度字符串示例
359     ab2hex(buffer) {
360         const hexArr = Array.prototype.map.call(
361             new Uint8Array(buffer),
362             function(bit) {
363                 return ('00' + bit.toString(16)).slice(-2);
364             }
365         );
366         return hexArr.join('');
367     }
368 
369     // 内部函数,将16进制的内容转成我们看得懂的字符串内容
370     hexCharCodeToStr(hexCharCodeStr) {
371         var trimedStr = hexCharCodeStr.trim();
372         var rawStr =
373             trimedStr.substr(0, 2).toLowerCase() === '0x' ?
374             trimedStr.substr(2) :
375             trimedStr;
376         var len = rawStr.length;
377         if (len % 2 !== 0) {
378             alert('存在非法字符!');
379             return '';
380         }
381         var curCharCode;
382         var resultStr = [];
383         for (var i = 0; i < len; i = i + 2) {
384             curCharCode = parseInt(rawStr.substr(i, 2), 16);
385             resultStr.push(String.fromCharCode(curCharCode));
386         }
387         return resultStr.join('');
388     }
389     // 接收回调函数,将数据进行处理,保存为JSON object
390     // 在网页中,编写回调函数处理业务,并且要调用此函数。
391     defaultCallback(jsonString) {
392         const _this = this;
393         console.log('Callback function called, raised by onBLECharacteristicValueChange');
394         if (!jsonString) {
395             return;
396         }
397         //将监听到的值转发其他网页
398         uni.$emit('BLE_RX_Value', {
399             msg: jsonString
400         }) //触发全局的自定义事件。附加参数都会传给监听器回调。
401 
402         //
403         console.log(jsonString);
404         _this.rxJSONString = jsonString; // 收到json字符串 
405         // 使用JSON.parse()将字符串转换回对象
406         let rxjsonObj = JSON.parse(_this.rxJSONString);
407 
408         // 遍历rxjsonObj的所有键,组成数组
409         let keys = [];
410         for (let key in rxjsonObj) {
411             // 键在obj中不存在或值不相同  
412             if (rxjsonObj.hasOwnProperty(key)) {
413                 keys.push(key); // 获取JSON objet的键
414             }
415         };
416         //console.log(keys);
417 
418         // 保存键值对  放在 this.rxJSONobject
419         for (let key in rxjsonObj) {
420             let element = key;
421             let value = rxjsonObj[element];
422             _this.rxJSONobject[element] = value;
423         }
424         console.log(_this.rxJSONobject);
425     }
426     // API函数,接收,将数据进行处理在回调函数中callback处理
427     // 仅在App.vue 中调用一次。
428     async notify(callback = this.defaultCallback()) {
429         const _this = this;
430         try {
431             // 已从BLE接收的数据,设置回调调函数处理。
432             let resultFromFunB = await uni.onBLECharacteristicValueChange(res => {
433                 // 结果
434                 //console.log(res);
435                 // 结果里有个value值,该值为 ArrayBuffer 类型,所以在控制台无法用肉眼观察到,必须将该值转换为16进制
436                 let resHex = this.ab2hex(res.value);
437                 //console.log(resHex);
438                 // 最后将16进制转换为ascii码,就能看到对应的结果
439                 let jsonString = this.hexCharCodeToStr(resHex);
440 
441                 callback(jsonString); // 回调函数实现功能
442             });
443             console.log('funB的结果:', resultFromFunB); // 打印funB的结果  
444             // 启动BLE接收功能
445             const resultFromFunA = await uni.notifyBLECharacteristicValueChange({
446                 deviceId: this.connectId, // 设备ID
447                 serviceId: SERVICE_UUID_ESP32, // 服务UUID
448                 characteristicId: CHARACTERISTIC_UUID_ESP32_TX, // 特性UUID
449                 success(res) {
450                     console.log(res);
451                 },
452                 fail(err) {
453                     console.error(err);
454                 }
455             });
456 
457         } catch (error) {
458             // 处理可能出现的错误  
459             console.error('执行函数时出错:', error);
460         } finally {
461             console.log('notify() finish'); // 打印结果  
462         };
463     }
464     /*-----------------------------------------------------------------------*/
465     //API函数, 向外围蓝牙设备发送数据,指定服务及其特性的UUID
466     send(jsonObject) {
467         // 向蓝牙设备发送一个0x00的16进制数据
468         // let jsonObject = {
469         //     name: "John",
470         //     age: -30,
471         //     city: "New York"
472         // };
473         return new Promise((resolve, reject) => {
474             let jsonString = JSON.stringify(jsonObject);
475             //字符串转为arraybuffer
476             const buffer = new ArrayBuffer(jsonString.length);
477             const dataView = new DataView(buffer);
478             // dataView.setUint8(0, 0)
479             for (var i = 0; i < jsonString.length; i++) {
480                 dataView.setUint8(i, jsonString.charAt(i).charCodeAt());
481             }
482 
483             uni.writeBLECharacteristicValue({
484                 deviceId: this.connectId, // 设备ID
485                 serviceId: SERVICE_UUID_ESP32, // 服务UUID
486                 characteristicId: CHARACTERISTIC_UUID_ESP32_RX, // 特性UUID
487                 value: buffer,
488                 success(res) {
489 
490                     //console.log(res);
491                     resolve(res);
492                 },
493                 fail(err) {
494                     //console.error(err);
495                     reject(err);
496                 }
497             });
498         });
499     }
500     /*-----------------------------------------------------------------------*/
501     // 接口函数 点击某个设备的连接
502     async connectClick(item) {
503         let _this = this;
504         console.log('即将连接蓝牙:', item);
505         // 如果当前有连接的设备,需要先断开
506         if (this.connected) {
507             await this.disConnect();
508         }
509         if (!this.inited) {
510             await this.initBlue();
511         }
512         // 停止扫描设备, 否则会浪费性能
513         await uni.stopBluetoothDevicesDiscovery({
514             success(res) {
515                 console.log("停止蓝牙设备搜索")
516                 //console.log(res)                
517             },
518             fail(err) {
519                 console.error(err);
520 
521             }
522         });
523         // 连接指定蓝牙设备
524         //console.log('开始连接蓝牙设备');
525         this.connectId = item.deviceId; // MAC地址
526         this.connect();
527     }
528     /*-----------------------------------------------------------------------*/
529 
530     /*-----------------------------------------------------------------------*/
531     //API函数, 周期性调用的函数
532     periodicFunction() {
533         console.log('这个函数被周期性调用了!');
534         // 在这里执行你的周期性任务  
535     }
536 
537     /*-----------------------------------------------------------------------*/
538     //API函数,开始周期性调用  
539     startPeriodicCall(callback, intervalms = 1000) {
540         if (!this.intervalId) { // 确保没有重复的定时器  
541             this.intervalId = setInterval(callback, intervalms); // 每秒调用一次periodicFunction  
542         }
543     }
544     /*-----------------------------------------------------------------------*/
545     //API函数, 停止周期性调用  
546     stopPeriodicCall() {
547         if (this.intervalId) { // 确保有定时器在运行  
548             clearInterval(this.intervalId); // 停止周期性调用  
549             this.intervalId = null; // 清空ID,以便下次可以重新开始
550             console.log('这个周期性函数被停止了!');
551         }
552     }
553     /*-----------------------------------------------------------------------*/
554     //API函数,  重新开始周期性调用 
555     restartPeriodicCall(callback, intervalms = 1000) {
556         if (this.intervalId) { // 确保有定时器在运行  
557             clearInterval(this.intervalId); // 停止周期性调用  
558             this.intervalId = null; // 清空ID,以便下次可以重新开始
559             console.log('这个周期性函数被停止了!');
560         };
561         if (!this.intervalId) { // 确保没有重复的定时器  
562             this.intervalId = setInterval(callback, intervalms); // 每秒调用一次periodicFunction  
563         };
564     }
565     /*-----------------------------------------------------------------------*/
566 
567 }
568 
569 // 创建一个单例实例  
570 const BLE_DeviceInstance = new BLE_DeviceClass();
571 
572 /*-----------------------------------------------------------------------*/
573 // module.exports = {
574 //     BLE_DeviceInstance, // 导出实例 ,作为全局实例
575 // }
576 export {
577     BLE_DeviceClass,
578     BLE_DeviceInstance
579 }
580 // import * as api from '@/util/api.js';// 导入公用变量与函数方法
581 // 在函数中引用  api.BLE_DeviceInstance.initBlue();
ble_api.js
复制代码

BLE搜索与连接   网页.vue

复制代码
  1 <template>
  2     <view>
  3         <!--功能按钮-->
  4         <label class="flex-container">
  5             <button class="button_Green"
  6                 style="background-color: white;color: black;border-style: solid;border: 2px solid #4CAF50;">注意:开启手机蓝牙与定位功能
  7             </button>
  8         </label>
  9 
 10         <!--设备列表-->
 11         <view>
 12             <div v-for="item in deviceArray" :key="item.deviceId">
 13                 <view class="flex-container"
 14                     style="flex-direction: column; border: 2px solid #4CAF50; margin_left:2%; margin_top:2%;padding: 0rpm;">
 15                     <label class="flex-container">
 16                         <text class="flex-item" style="text-align: center;"> 蓝牙名称:{{item.name}}</text>
 17                     </label>
 18                     <label class="flex-container" style="align-items: left;">
 19                         <view style="display:flex; text-align: left;flex:5">MAC:{{item.deviceId}}</view>
 20                         <view style="display:flex; text-align: left;flex:5">信号:{{item.RSSI}}</view>
 21 
 22                         <view class="button_Green" style="flex:5;">
 23                             <a @click="selectlick(item)" v-if="connectId !== item.deviceId">连接</a>
 24                             <a @click="unselectlick(item)" v-else style="color: red;">断开连接</a>
 25                         </view>
 26 
 27                     </label>
 28                 </view>
 29             </div>
 30         </view>
 31 
 32         <label class="flex-container">
 33             <button @click="startScanBLE" :disabled="!btn_select" class="button_Green"
 34                 style="background-color: #007aff;">搜索蓝牙外设 </button>
 35         </label>
 36         <label class="flex-container">
 37             <button @click="stopScanBLE" :disabled="btn_select" class="button_Green"
 38                 style="background-color: #4CAF50;">停止搜索蓝牙</button>
 39         </label>
 40 
 41     </view>
 42 </template>
 43 
 44 <script>
 45     import * as ble_api from '@/utilities/ble_api.js'; // 导入公用变量与函数方法
 46 
 47     export default {
 48         data() {
 49             return {
 50                 onscanBLE: true,
 51                 deviceArray: [], //蓝牙周围设备列表
 52                 connectId: "", // 连接的MAC
 53                 btn_select: true, // 控制按钮有效性
 54             }
 55         },
 56         onLoad() {
 57             //console.log("test.vue");
 58             ble_api.BLE_DeviceInstance.setCheckAdapterStateChange();
 59             ble_api.BLE_DeviceInstance.setCheckConnectionStateChange();
 60         },
 61         onShow() {
 62             // 监听页面显示  
 63             // if(ble_api.BLE_DeviceInstance.previousConnectId){
 64             // this.connectId = "";    
 65             // }
 66             console.log('页面显示');
 67             console.log(this.connectId);
 68         },
 69         onHide() {
 70             // 监听页面隐藏  
 71             console.log('页面隐藏');
 72         },
 73         methods: {
 74             //启动手机蓝牙功能
 75             openBLE() {
 76                 ble_api.BLE_DeviceInstance.initBlue();
 77             }, // end of openBLE() 
 78             //搜索蓝牙外设
 79             startScanBLE() {
 80                 this.btn_select = false;
 81                 let _this = this;
 82                 _this.onscanBLE = true;
 83                 ble_api.BLE_DeviceInstance.scan_start();
 84                 _this.getScanBLE();
 85             }, // end of startScanBLE()
 86             //搜索蓝牙外设
 87             stopScanBLE() {
 88                 this.btn_select = true;
 89                 let _this = this;
 90                 _this.onscanBLE = false;
 91                 ble_api.BLE_DeviceInstance.scan_stop();
 92             }, // end of stopScanBLE()
 93             //定时获取 搜索蓝牙外设 的结果
 94             async getScanBLE() {
 95                 let _this = this;
 96                 // 使用Promise包装setTimeout
 97                 while (_this.onscanBLE) {
 98                     //console.log('发一个数据包,开始延时...');
 99                     await new Promise(resolve => {
100                         setTimeout(() => {
101                             // 检测已发现的蓝牙外设列表,并根据两次搜索数量判断是否停止
102                             let len_previous = _this.deviceArray.length;
103                             _this.deviceArray.length = 0;
104                             let len = ble_api.BLE_DeviceInstance.devices.length;
105                             for (var i = 0; i < len; i++) {
106                                 if (ble_api.BLE_DeviceInstance.devices[i].name) {
107                                     _this.deviceArray.push(ble_api.BLE_DeviceInstance.devices[i]);
108                                 }
109                             }
110                             console.log("设备列表:");
111                             console.log(_this.deviceArray);
112                             // 比较相邻两次搜索结果,判断是否停止搜索蓝牙。
113                             len = _this.deviceArray.length;
114                             if (len_previous == len) //没有收到新外设
115                             {
116 
117                                 _this.stopScanBLE(); // 含有 _this.onscanBLE = false;
118                             }
119                             //console.log('延时100ms后执行(使用await)!');
120                             resolve(); // 调用resolve来继续执行async函数  
121                         }, 2000);
122                     });
123                     // 这段代码将在延时后执行  
124                 };
125                 console.log('关闭定时器(搜索蓝牙外设)');
126             }, // end of async getScanBLE()
127             //选择与连接蓝牙设备            
128             selectlick(item) {
129                 this.connectId = "";
130                 console.log(item.deviceId);
131                 this.connectId = item.deviceId;
132 
133                 //断开蓝牙连接,连接新蓝牙
134                 ble_api.BLE_DeviceInstance.connectClick(item);
135             }, // end of selectlick(item)
136             //断开蓝牙设备
137             unselectlick(item) {
138                 this.connectId = "";
139 
140                 //断开蓝牙连接
141                 ble_api.BLE_DeviceInstance.disConnect();
142             }, // end of unselectlick(item)
143             // 
144         }
145     }
146 </script>
147 
148 <style>
149     .flex-container {
150         display: flex;
151         flex-direction: row;
152         /* 主轴方向,默认为 row */
153         flex-wrap: nowrap;
154         /* 是否换行,默认为 nowrap */
155 
156         justify-content: center;
157         /* 如果需要的话,可以水平居中 */
158         align-items: center;
159         /* 如果需要的话,可以垂直居中  */
160         box-sizing: border-box;
161         width: 100%;
162         /* 或其他你需要的宽度 width: 96%;*/
163 
164         /* 或其他你需要的高度 height: 100%;*/
165         /* 其他样式... */
166     }
167 
168     .flex-item {
169         flex: 1;
170         /* 使按钮占据全部可用空间 */
171         /* 如果需要的话,可以添加边距或填充 */
172         /* margin: ...; */
173         /* padding: ...; */
174         /* 其他样式... */
175         text-align: center;
176         text-decoration: none;
177         display: inline-block;
178         font-size: 50rpx;
179     }
180 
181     .button_Green {
182         flex: 1;
183         /* Green */
184         background-color: #4CAF50;
185         /* Green */
186         border: none;
187         color: white;
188         /*padding: 15px 32px; */
189         text-align: center;
190         text-decoration: none;
191         /*display: inline-block;*/
192         font-size: 40rpx;
193 
194         border-radius:20rpx;
195         /* 或者使用 50% 来确保它总是圆形的 */
196     }
197 </style>
ble_link.vue
复制代码

 

BLE数据发送与接收举例     网页.vue

复制代码
  1 <template>
  2     <view>
  3         <!--设备列表-->
  4         <div class="flex-container" style="flex-direction: column;" v-for="item in deviceArray" :key="item.deviceId">
  5             <view class="flex-container"
  6                 style="flex-direction: column; border: 2px solid #4CAF50; margin_left:2%; margin_top:2%;padding: 0rpm;">
  7                 <label class="flex-container">
  8                     <text class="flex-item" style="text-align: center;">设备编号:{{item.deviceId+1}}</text>
  9                 </label>
 10                 <label class="flex-container">
 11                     <text class="flex-item" style="text-align: left;">安装位置:{{item.locate}}</text>
 12                     <text class="flex-item" style="text-align: left;">运动状态:{{item.state}}</text>
 13 
 14                 </label>
 15                 <label class="flex-container">
 16                     <button @click="btnForwardClick(item)" class="button_Green" style="background-color: #4CAF50;"
 17                         :disabled="!item.btn_forward">前进</button>
 18                     <button @click="btnBackwardClick(item)" class="button_Green" style="background-color: #008CBA;"
 19                         :disabled="!item.btn_backward">返回</button>
 20                     <button @click="btnStopClick(item)" class="button_Green" style="background-color: #f44336;"
 21                         :disabled="!item.btn_stop">停止</button>
 22                 </label>
 23             </view>
 24         </div>
 25         <!--设备列表-->
 26     </view>
 27 </template>
 28 
 29 <script>
 30     import * as ble_api from '@/utilities/ble_api.js'; // 导入公用变量与函数方法
 31     export default {
 32         data() {
 33             return {
 34 
 35                 deviceRegs: { // 编号:0bit,device0;bit1,device1;...bit31,device31;
 36                     existence: 0,
 37                     forward_state: 0,
 38                     backward_state: 0,
 39                     origin_pos: 0,   //驻泊位状态 有,或无
 40                     end_pos: 0,      //暂停位状态 有,或无
 41                     deviceId_cmd: 0, //接收命令的RS485设备
 42                     forward_cmd: 0,
 43                     backward_cmd: 0,
 44                 },
 45                 deviceArray: [],
 46                 // item: {
 47                 //     deviceId: 0,
 48                 //     locate: "设备NO.",
 49                 //     state: "",
 50                 //     btn_forward: true,
 51                 //     btn_backward: true,
 52                 //     btn_stop: false,
 53                 // },
 54             } // end of return
 55         },
 56         onLoad() {
 57             // BLE 读取 RS485设备状态
 58             this.button_disable = true;
 59             this.deviceRegs.existence = 15 + 15 * 16;
 60             this.deviceRegs.backward_state = 5;
 61             this.deviceRegs.forward_state = 10;
 62             this.deviceRegs.origion_pos = 5 * 16;
 63             this.deviceRegs.end_pos = 10 * 16;
 64             console.log(this.deviceRegs);
 65 
 66             this.setDeviceArray();
 67         }, // end of onLoad()
 68         onShow() {
 69             // 监听页面显示  
 70             // 必须在此添加一次。否者不能接收数据。回调函数只有一个。
 71             ble_api.BLE_DeviceInstance.notify(ble_api.BLE_DeviceInstance.defaultCallback);
 72             //**别的页面  监听全局的自定义事件。事件可以由 uni.$emit 触发,回调函数会接收所有传入事件触发函数的额外参数。
 73             // uni.$on('BLE_RX_Value', function(data) {
 74             //     console.log('监听到事件来自 update ,携带参数 msg 为:' + data.msg);
 75             // });
 76             uni.$on('BLE_RX_Value', this.BLE_RX_ValueFun);
 77 
 78             //此处应延时读取设备状态
 79         },
 80         onHide() {
 81             // 监听页面隐藏  
 82             uni.$off('BLE_RX_Value', this.BLE_RX_ValueFun); //移除全局自定义事件监听器。
 83 
 84             console.log('页面隐藏');
 85         },
 86         methods: {
 87             //回调函数 。uni.$on()监听全局的自定义事件。事件可以由 uni.$emit 触发,回调函数会接收所有传入事件触发函数的额外参数。
 88             BLE_RX_ValueFun(data) {
 89                 console.log('监听到事件来自 BLE_RX_Value ,携带参数 msg 为:' + data.msg);
 90                 let jsonString = data.msg;
 91                 this.BLE_RX_DataRead(jsonString); //定义函数,完成对ESP32发来的数据处理。
 92             },
 93             //定义函数,完成对ESP32发来的数据处理。
 94             BLE_RX_DataRead(jsonString) {
 95                 let _this = this;
 96                 let Keys = [];
 97                 // 解析 JSON string
 98                 let JsonObj = JSON.parse(jsonString);
 99                 //取得键值 
100                 let obj1 = _this.deviceRegs;
101                 for (let key in obj1) { // 遍历obj1={}的所有键  
102                     Keys.push(key);
103                 };
104                 //根据蓝牙发来的JsonObj,更新this.deviceRegs
105                 for (let key in JsonObj) { // 遍历JsonObj ={}的所有键 
106                     if (Keys.includes(key)) { // 更新 JsonObj{}内的值
107                         obj1[key] = JsonObj[key];
108                         //console.log(key);
109                     }
110                 };
111             },
112 
113             // 机APP通过蓝牙通信发送数据给外围蓝牙设备ESP32
114             //  报文功能是向设备写数据。
115             BLE_write_device(jsonObject) {
116                 let _this = this;
117                 //let ble_all_object = ble_api.BLE_DeviceInstance.rxJSONobject; // 用于存储JSON object数据
118                 jsonObject["command"] = "write_device";
119                 //API函数, 向外围蓝牙设备发送数据,指定服务及其特性的UUID
120                 ble_api.BLE_DeviceInstance.send(jsonObject)
121                 console.log("BLE_write_device(jsonObject)");
122             }, // end of BLE_write_device(jsonObject)
123             //  报文功能是读取设备状态。
124             BLE_read_device(jsonObject) {
125                 let _this = this;
126                 //let ble_all_object = ble_api.BLE_DeviceInstance.rxJSONobject; // 用于存储JSON object数据
127                 jsonObject["command"] = "read_device";
128                 //API函数, 向外围蓝牙设备发送数据,指定服务及其特性的UUID
129                 ble_api.BLE_DeviceInstance.send(jsonObject)
130                 console.log("BLE_write_device(jsonObject)");
131             }, // end of BLE_read_device(jsonObject)
132             /*-----------------------------------------------------------------------*/
133             /*<!-------------------------------------------------------------------------------------------->*/
134             /*根据this.deviceRegs 设置 this.deviceArray*/
135             setDeviceArray() {
136                 let _this = this;
137                 let existflag = _this.deviceRegs.existence;
138 
139                 this.deviceArray.length = 0; //清空数组
140                 //arr.splice(0, arr.length);
141 
142                 for (var i = 0; i < 16; i++) { //最多16个RS85设备
143                     let bit = 0x0001;
144                     let bit_flag = bit << i;
145                     if ((bit_flag & existflag) != bit_flag) {
146                         continue; // 无RS85设备
147                     };
148                     //有编号i的RS485设备
149                     let item = {
150                         deviceId: i,
151                         locate: "RS485设备安装位置",
152                         state: "",
153                         btn_forward: false,
154                         btn_backward: false,
155                         btn_stop: false,
156                     };
157                     // RS485运转时,只能停
158                     let _forward = ((_this.deviceRegs.forward_state & bit_flag) == bit_flag);
159                     let _backward = ((_this.deviceRegs.backward_state & bit_flag) == bit_flag);
160 
161                     // 使能 停止按钮
162                     if (_forward || _backward) {
163                         item.btn_forward = false;
164                         item.btn_backward = false;
165                         item.btn_stop = true;
166                         if (_forward) {
167                             item.state = "前进中";
168                         } else {
169                             item.state = "返回中";
170                         }
171                         _this.deviceArray.push(item);
172                         continue;
173                     }
174                     // RS485在端部时,只能向另外一端运动
175                     // 使能 前进按钮
176                     let at_origion = ((_this.deviceRegs.origion_pos & bit_flag) == bit_flag);
177                     if (at_origion) {
178                         item.btn_forward = true;
179                         item.btn_backward = false;
180                         item.btn_stop = false;
181 
182                         item.state = "驻停端";
183                         _this.deviceArray.push(item);
184                         continue;
185                     }
186                     // 使能 返回按钮
187                     let at_end = ((_this.deviceRegs.end_pos & bit_flag) == bit_flag);
188                     if (at_end) {
189                         item.btn_forward = false;
190                         item.btn_backward = true;
191                         item.btn_stop = false;
192 
193                         item.state = "暂停端";
194                         _this.deviceArray.push(item);
195                         continue;
196                     }
197                     // 处于中部,且停止,使能 前进 和 返回
198                     item.btn_forward = true;
199                     item.btn_backward = true;
200                     item.btn_stop = false;
201                     item.state = "停在中间段";
202                     _this.deviceArray.push(item);
203                 }
204                 console.log(_this.deviceArray);
205             }, // end of setDeviceArray()
206             // 点击某个设备的连接
207             //
208             btnForwardClick(item) {
209                 console.log(item.deviceId);
210                 let pos = item.deviceId;
211                 let bit = 0x0001 << pos;
212                 let flag = this.deviceRegs.existence;
213                 if ((flag & bit) != bit) { //无设备
214                     return;
215                 };
216                 this.deviceRegs.deviceId_cmd = bit;
217                 this.deviceRegs.backward_cmd = 0;
218                 this.deviceRegs.forward_cmd = bit;
219 
220                 // BLE 发送命令给ESP32
221                 let jsonObject = {
222                     deviceId_cmd: this.deviceRegs.deviceId_cmd, //接收命令的RS485设备
223                     forward_cmd: this.deviceRegs.forward_cmd,
224                     backward_cmd: this.deviceRegs.backward_cmd,
225                 };
226                 jsonObject["command"] = "write_device";
227 
228                 this.BLE_write_device(jsonObject);
229             }, // end of btnForwardClick(item)
230 
231             btnBackwardClick(item) {
232                 console.log(item.deviceId);
233                 let pos = item.deviceId;
234                 let bit = 0x0001 << pos;
235                 let flag = this.deviceRegs.existence;
236                 if ((flag & bit) != bit) { //无设备
237                     return;
238                 };
239                 this.deviceRegs.deviceId_cmd = bit;
240                 this.deviceRegs.backward_cmd = bit;
241                 this.deviceRegs.forward_cmd = 0;
242 
243                 // BLE 发送命令给ESP32
244                 let jsonObject = {
245                     deviceId_cmd: this.deviceRegs.deviceId_cmd, //接收命令的RS485设备
246                     forward_cmd: this.deviceRegs.forward_cmd,
247                     backward_cmd: this.deviceRegs.backward_cmd,
248                 };
249                 jsonObject["command"] = "write_device";
250 
251                 this.BLE_write_device(jsonObject);
252             }, // end of btnBackwardClick(item)
253 
254             btnStopClick(item) {
255                 console.log(item.deviceId);
256                 let pos = item.deviceId;
257                 let bit = 0x0001 << pos;
258                 let flag = this.deviceRegs.existence;
259                 if ((flag & bit) != bit) { //无设备
260                     return;
261                 };
262                 this.deviceRegs.deviceId_cmd = bit;
263                 this.deviceRegs.backward_cmd = 0;
264                 this.deviceRegs.forward_cmd = 0;
265 
266                 // BLE 发送命令给ESP32
267                 let jsonObject = {
268                     deviceId_cmd: this.deviceRegs.deviceId_cmd, //接收命令的RS485设备
269                     forward_cmd: this.deviceRegs.forward_cmd,
270                     backward_cmd: this.deviceRegs.backward_cmd,
271                 };
272                 jsonObject["command"] = "write_device";
273 
274                 this.BLE_write_device(jsonObject);
275             }, // end of btnStopClick(item)
276         }
277     }
278 </script>
279 
280 
281 <style>
282     .flex-container {
283         display: flex;
284         flex-direction: row;
285         /* 主轴方向,默认为 row */
286         flex-wrap: nowrap;
287         /* 是否换行,默认为 nowrap */
288 
289         justify-content: center;
290         /* 如果需要的话,可以水平居中 */
291         align-items: center;
292         /* 如果需要的话,可以垂直居中  */
293         width: 100%;
294         box-sizing: border-box;
295 
296         /* 或其他你需要的宽度 width: 96%;*/
297 
298         /* 或其他你需要的高度 height: 100%;*/
299         /* 其他样式... */
300     }
301 
302     .flex-item {
303         flex: 1;
304         /* 使按钮占据全部可用空间 */
305         /* 如果需要的话,可以添加边距或填充 */
306         /* margin: ...; */
307         /* padding: ...; */
308         /* 其他样式... */
309         text-align: center;
310         text-decoration: none;
311         display: inline-block;
312         font-size: 16px;
313     }
314 
315     .button_Green {
316         flex: 1;
317         /* Green */
318         background-color: #4CAF50;
319         /* Green */
320         border: none;
321         color: white;
322         /*padding: 15px 32px; */
323         text-align: center;
324         text-decoration: none;
325         /*display: inline-block;*/
326         font-size: 16px;
327     }
328 </style>
devicectrl.vue
复制代码

 

posted @   辛河  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2023-07-02 Tkinter Menu菜单 (disable 菜单命令)
点击右上角即可分享
微信分享提示