React Native BLE蓝牙通信

来源:互联网 发布:linux git clone 路径 编辑:程序博客网 时间:2024/05/21 12:31

转载请标明出处:http://blog.csdn.net/withings/article/details/71378562

由于项目需要,基于React Native 开发的App要跟BLE蓝牙设备通信。
在js.coach上搜索React Native BLE蓝牙组件,只找到三个组件:

  1. react-native-ble-manager:文档清晰明了,简单,基本每个月都有更新,遇到问题提交issue作者也能及时回复,但缺点就是可能会不太稳定,不一定适用所有BLE蓝牙设备。

  2. react-native-ble-plx:文档阅读起来有点难度,但API很丰富,相比react-native-ble-manager显的比较专业。(PS:到目前为止,该组件的文档改善了一些,但还是有点难懂,我试用了这个组件,并写了一个demo,代码已上传到github : demo源码地址)

  3. react-native-ble:由Node.js BLE改写而成,个人感觉不是很适用于React Native。

综上分析,我当初选择的是react-native-ble-manager,组件的安装、配置看官方文档即可。
PS:由于react-native-ble-manager更新比较频繁,本教程最初是基于3.2.1版本编写,由于版本跨度比较大,导致demo出现了一些问题,现已将代码和文章教程全部更新到6.2.0版本,如后面的版本没有及时更新适配,自己也可以根据官方文档作出相对应的更改,但通信原理和步骤是不变的。
若有疑问,欢迎加我QQ:190525238 咨询讨论。

数据通信

实现蓝牙数据通信需要初始化、搜索、连接、获取Service和Characteristic、通知监听、读数据、写数据

初始化

import BleManager from 'react-native-ble-manager';const BleManagerModule = NativeModules.BleManager;const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);BleManager.start({showAlert: false})  .then( ()=>{       //检查蓝牙打开状态,初始化蓝牙后检查当前蓝牙有没有打开       BleManager.checkState();       console.log('Init the module success.');                   }).catch(error=>{       console.log('Init the module fail.');   });

添加相应的监听器

//蓝牙状态改变监听BluetoothManager.addListener('BleManagerDidUpdateState', (args) => {    console.log('BleManagerDidUpdateStatea:', args);    if(args.state == 'on' ){  //蓝牙已打开    }});

搜索

第一次使用react-native-ble-manager这个组件时,发现搜索不到附近的蓝牙设备(手机,电脑),于是就向作者提交了一个issue,问题是:Can’t discovery to some devices,然后才明白该组件只能搜索到标准的BLE蓝牙设备。

蓝牙4.0标准包含两个蓝牙标准,准确的说,是一个双模的标准,它包含传统蓝牙部分(也有称之为经典蓝牙Classic Bluetooth)和低功耗蓝牙部分(Bluetooth Low Energy)。
经典蓝牙可以用数据量比较大的传输,如:图像、视频、音乐等。低功耗蓝牙的数据传输用于实时性要求比较高但数据速率比较低的产品,如智能穿戴设备、遥控类的,鼠标,键盘,遥控鼠标(Air Mouse),还有传感设备的数据发送,如心跳带,血压计,温度传感器等等、其应用的行业和方向也比较广泛。
详情可查看:什么是蓝牙双模标准

所以,即使是蓝牙4.0的设备,例如手机蓝牙、电脑蓝牙,使用该组件也搜索不到蓝牙,要看它是不是低功耗标准的蓝牙。
在android平台下,可以直接搜索到蓝牙设备的Mac地址,而ios需要通过广播0x18才能获取得到蓝牙的Mac地址(需要修改蓝牙固件将Mac地址加入到广播中,普通蓝牙设备一般没有)。

//扫描可用设备,5秒后结束 BleManager.scan([], 5, true)    .then(() => {        console.log('Scan started');    })    .catch( (err)=>{        console.log('Scan started fail');    });//停止扫描BleManager.stopScan()    .then(() => {        console.log('Scan stopped');    })    .catch( (err)=>{        console.log('Scan stopped fail',err);    });

添加相应的监听器

//搜索到一个新设备监听bleManagerEmitter.addListener('BleManagerDiscoverPeripheral', (data) => {    console.log('BleManagerDiscoverPeripheral:', data);    let id;  //蓝牙连接id    let macAddress;  //蓝牙Mac地址                if(Platform.OS == 'android'){        macAddress = data.id;        id = macAddress;    }else{          //ios连接时不需要用到Mac地址,但跨平台识别是否是同一设备时需要Mac地址        //如果广播携带有Mac地址,ios可通过广播0x18获取蓝牙Mac地址,        macAddress = getMacAddressFromIOS(data);        id = data.id;    }            });//搜索结束监听bleManagerEmitter.addListener('BleManagerStopScan', () => {     console.log('BleManagerStopScan:','Scanning is stopped');          //搜索结束后,获取搜索到的蓝牙设备列表,如监听了BleManagerDiscoverPeripheral,可省去这个步骤    BleManager.getDiscoveredPeripherals([])       .then((peripheralsArray) => {           console.log('Discovered peripherals: ', peripheralsArray);       });});/** ios系统从蓝牙广播信息中获取蓝牙MAC地址 */getMacAddressFromIOS(data){    let macAddressInAdvertising = data.advertising.kCBAdvDataManufacturerMacAddress;    //为undefined代表此蓝牙广播信息里不包括Mac地址    if(!macAddressInAdvertising){          return;    }    macAddressInAdvertising = macAddressInAdvertising.replace("<","").replace(">","").replace(" ","");    if(macAddressInAdvertising != undefined && macAddressInAdvertising != null && macAddressInAdvertising != '') {    macAddressInAdvertising = swapEndianWithColon(macAddressInAdvertising);    }    return macAddressInAdvertising;}/*** ios从广播中获取的mac地址进行大小端格式互换,并加上冒号:* @param str         010000CAEA80* @returns string    80:EA:CA:00:00:01*/swapEndianWithColon(str){    let format = '';    let len = str.length;    for(let j = 2; j <= len; j = j + 2){        format += str.substring(len-j, len-(j-2));        if(j != len) {            format += ":";        }    }    return format.toUpperCase();}

连接

android使用Mac地址与蓝牙连接,ios使用UUID与蓝牙连接。

//连接蓝牙BleManager.connect(id)   .then(() => {       console.log('Connected');   })   .catch((error) => {       console.log('Connected error:',error);   });//断开蓝牙连接BleManager.disconnect(id)    .then( () => {        console.log('Disconnected');    })    .catch( (error) => {        console.log('Disconnected error:',error);    });

添加相应的监听器

//蓝牙设备已连接监听bleManagerEmitter.addListener('BleManagerConnectPeripheral', (args) => {    log('BleManagerConnectPeripheral:', args);});//蓝牙设备已断开连接监听bleManagerEmitter.addListener('BleManagerDisconnectPeripheral', (args) => {    console.log('BleManagerDisconnectPeripheral:', args);});

蓝牙连接后会显示该设备的具体信息,android平台下连接成功后返回的数据如下:

{ characteristics:  [ { properties: { Read: 'Read' },       characteristic: '2a00',       service: '1800' },     { properties: { Read: 'Read' },       characteristic: '2a01',       service: '1800' },     { properties: { Write: 'Write', Read: 'Read' },       characteristic: '2a02',       service: '1800' },     { properties: { Read: 'Read' },       characteristic: '2a04',       service: '1800' },     { descriptors: [ { value: null, uuid: '2902' } ],       properties: { Indicate: 'Indicate', Read: 'Read' },       characteristic: '2a05',       service: '1801' },     { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],       properties: { Notify: 'Notify' },       characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb8',       service: '0783b03e-8535-b5a0-7140-a304d2495cb7' },     { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],       properties: { WriteWithoutResponse: 'WriteWithoutResponse' },       characteristic: '0783b03e-8535-b5a0-7140-a304d2495cba',       service: '0783b03e-8535-b5a0-7140-a304d2495cb7' },      { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],        properties:        { Notify: 'Notify',           WriteWithoutResponse: 'WriteWithoutResponse',           Read: 'Read' },        characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb9',        service: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],  services:  [ { uuid: '1800' },    { uuid: '1801' },    { uuid: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],  rssi: -46,  advertising:{ data: 'AgEGEQe3XEnSBKNAcaC1NYU+sIMHCQlQRVAtUEVOLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',CDVType: 'ArrayBuffer' },  id: '00:CD:FF:00:22:2D',  name: 'PEP-HC001' }

ios平台下连接成功后返回的数据如下:

{ name: 'PEP-HC001',  id: '64319987-E97B-46C0-91AE-261E93EADBFD',  advertising:    { kCBAdvDataLocalName: 'PEP-HC001',     kCBAdvDataIsConnectable: true,     kCBAdvDataServiceUUIDs: [ '0783' ],     kCBAdvDataManufacturerMacAddress: '<472200ff cd00>',     kCBAdvDataManufacturerData: { CDVType: 'ArrayBuffer', data: 'RyIA/80A' } },  services: [ '0783B03E-8535-B5A0-7140-A304D2495CB7' ],  characteristics:    [ { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',       isNotifying: false,       characteristic: '0783B03E-8535-B5A0-7140-A304D2495CB8',       properties: [ 'Notify' ] },     { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',       isNotifying: false,       characteristic: '0783B03E-8535-B5A0-7140-A304D2495CBA',       properties: [ 'WriteWithoutResponse' ] },     { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',       isNotifying: false,       characteristic: '0783B03E-8535-B5A0-7140-A304D2495CB9',       properties: [ 'Read', 'WriteWithoutResponse', 'Notify' ] } ],  rssi: -35 }

获取Service和Characteristic

BLE分为三部分Service(服务)、Characteristic(特征)、Descriptor(描述符),这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有跟权限相关的字段,如Property,Property有读写等各种属性,如Notify、Read、Write、WriteWithoutResponse。(引自:Android BLE开发之Android手机与BLE终端通信)

Service

一个低功耗蓝牙设备可以定义多个Service, Service可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
0x0000xxxx-0000-1000-8000-00805F9B34FB
为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的”x”部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
0x00002A37-0000-1000-8000-00805F9B34FB(引自:Android BLE 蓝牙开发入门)

Characteristic

在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。(引自:Android BLE 蓝牙开发入门)

蓝牙连接成功后,需要调用retrieveServices方法获取NotifyReadWriteserviceUUIDcharacteristicUUID作为参数来跟蓝牙进一步通信

//获取蓝牙Service和CharacteristicsBleManager.retrieveServices(peripheralId)    .then((peripheralInfo) => {        this.getUUID();        console.log('Peripheral info:', peripheralInfo);    });  

peripheralInfo下的characteristics字段值是一个特征数组,每一项代表一个特征通道,找到properties中包含有NotifyReadWriteWriteWithoutResponse属性的那一项,其servicecharacteristic即是我们需要的参数。
PS:serviceUUIDcharacteristicUUID标准格式为XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX的128bit的UUID。所以需要将获到的’XXXX’格式的UUID转换为标准的128bit的UUID格式才可能进行通信。
不同的蓝牙设备,可能有多个特征通道包含NotifyReadWriteWriteWithoutResponse属性值,那每个通道属性的功能可能会不一样,应根据具体的蓝牙设备选择符合我们要求的特征通道。有些可能不包含NotifyReadWriteWriteWithoutResponse中的一个或多个属性,具体跟蓝牙硬件有关系,一般有NotifyWrite两个属性就可以满足通信的要求了。

//获取Notify、Read、Write、WriteWithoutResponse的serviceUUID和characteristicUUIDgetUUID(peripheralInfo){           this.readServiceUUID = [];    this.readCharacteristicUUID = [];       this.writeWithResponseServiceUUID = [];    this.writeWithResponseCharacteristicUUID = [];    this.writeWithoutResponseServiceUUID = [];    this.writeWithoutResponseCharacteristicUUID = [];    this.nofityServiceUUID = [];    this.nofityCharacteristicUUID = [];      for(let item of peripheralInfo.characteristics){          item.service = this.fullUUID(item.service);        item.characteristic = this.fullUUID(item.characteristic);         if(Platform.OS == 'android'){               if(item.properties.Notify == 'Notify'){                     this.nofityServiceUUID.push(item.service);                      this.nofityCharacteristicUUID.push(item.characteristic);             }             if(item.properties.Read == 'Read'){                 this.readServiceUUID.push(item.service);                 this.readCharacteristicUUID.push(item.characteristic);             }             if(item.properties.Write == 'Write'){                 this.writeWithResponseServiceUUID.push(item.service);                 this.writeWithResponseCharacteristicUUID.push(item.characteristic);             }             if(item.properties.Write == 'WriteWithoutResponse'){                 this.writeWithoutResponseServiceUUID.push(item.service);                 this.writeWithoutResponseCharacteristicUUID.push(item.characteristic);             }                        }else{  //ios             for(let property of item.properties){                 if(property == 'Notify'){                     this.nofityServiceUUID.push(item.service);                     this.nofityCharacteristicUUID.push(item.characteristic);                 }                 if(property == 'Read'){                     this.readServiceUUID.push(item.service);                     this.readCharacteristicUUID.push(item.characteristic);                 }                 if(property == 'Write'){                     this.writeWithResponseServiceUUID.push(item.service);                     this.writeWithResponseCharacteristicUUID.push(item.characteristic);                 }                 if(property == 'WriteWithoutResponse'){                     this.writeWithoutResponseServiceUUID.push(item.service);                     this.writeWithoutResponseCharacteristicUUID.push(item.characteristic);                 }                               }         }     }}/** * Converts UUID to full 128bit. *  * @param {UUID} uuid 16bit, 32bit or 128bit UUID. * @returns {UUID} 128bit UUID. */ fullUUID(uuid) {     if (uuid.length === 4){         return '0000' + uuid.toUpperCase() + '-0000-1000-8000-00805F9B34FB'      }                  if (uuid.length === 8) {         return uuid.toUpperCase() + '-0000-1000-8000-00805F9B34FB'      }                  return uuid.toUpperCase()  }  

通知监听

蓝牙连接成功后,当我们发送数据给蓝牙后,如需要获取蓝牙反馈给手机的数据时,还需要开启通知服务。

//打开通知BleManager.startNotification(peripheralId, this.nofityServiceUUID[0], this.nofityCharacteristicUUID[0])    .then(() => {        console.log('Notification started');    })    .catch((error) => {        console.log('Notification error:',error);    });//关闭通知BleManager.stopNotification(peripheralId, this.nofityServiceUUID[0], this.nofityCharacteristicUUID[0])   .then(() => {        console.log('stopNotification success!');    })    .catch((error) => {        console.log('stopNotification error:',error);    });

添加相应的监听器

//接收到新数据监听,开启通知成功后,该监听才可接收到数据bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', (data) => {    //ios接收到的是小写的16进制,android接收的是大写的16进制,统一转化为大写16进制    let value = data.value.toUpperCase();                   console.log('BluetoothUpdateValue', value);});

读数据

//读取蓝牙数据BleManager.read(peripheralId, this.readServiceUUID[0], this.readCharacteristicUUID[0])     .then((data) => {         console.log('Read: ',data);                         })     .catch((error) => {         console.log(error);     });

写数据

写数据有两个方法,writewriteWithoutResponse,大部分蓝牙都有write属性,而writeWithoutResponse属性比较少。

import { stringToBytes } from 'convert-string';let data = stringToBytes(data);  //将字符串转换成字节数组传送//写数据到蓝牙BleManager.write(peripheralId, this.writeWithResponseServiceUUID[0], this.writeWithResponseCharacteristicUUID[0], data)    .then(() => {        console.log('write success: ',data);    })    .catch((error) => {        console.log('write  failed: ',data);    });//写数据到蓝牙,没有响应   BleManager.writeWithoutResponse(peripheralId, this.writeWithoutResponseServiceUUID[0], this.writeWithoutResponseCharacteristicUUID[0], data)    .then(() => {        console.log('writeWithoutResponse success: ',data);    })    .catch((error) => {        console.log('writeWithoutResponse  failed: ',data);    });

发送16进制数据(可选,适用5.0.1之前版本)

5.0.1之前的版本写数据是需要经过base64编码转换后发送的,5.0.1之后的版本直接发送Byte array数据就可以通信了。所以,本修改源码方法只适用于5.0.1之前的版本,高于5.0.1版本的就不需要修改源码了。

通信的基础是发送正确的数据格式,react-native-ble-manager组件默认发送到蓝牙的数据格式是10进制的string,而我们的蓝牙设备一般接收的是16进制的string。带着这个疑问,我提了一个issue给作者,问题是:Can I send hexadecimal data to BLE instead of base64 format?然而作者却没有给我一个满意的解决办法。所以,我尝试着修改组件源码来适配实际的项目需求。
本源码修改方法基于react-native-ble-manager组件3.2.1版本更改,高于或者低于这个版本的修改方法也都一样。

android源码修改

修改的源文件只有两个:BleManager.javaPeripheral.java文件,这两个文件都在react-native-ble-manager\android\src\main\java\it\innove目录下

BleManager.java文件

  • 增加的方法:
/** 16进制字符串转换为2进制byte字节数组 */public static byte[] hexStringToBytes(String hexString) {    if (hexString == null || hexString.equals("")) {        return null;    }    hexString = hexString.toUpperCase();    int length = hexString.length() / 2;    char[] hexChars = hexString.toCharArray();    byte[] d = new byte[length];    for (int i = 0; i < length; i++) {        int pos = i * 2;        d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));    }    return d;}public static byte charToByte(char c) {    return (byte) "0123456789ABCDEF".indexOf(c);}
  • 修改writeWithoutResponse方法内的代码:
修改前:    byte[] decoded = Base64.decode(message.getBytes(), Base64.DEFAULT);修改后:byte[] decoded = hexStringToBytes(message);

Peripheral.java 文件

  • 增加的方法:
/** 2进制byte数组转换成16进制字符串 */public static String bytes2HexString(byte[] b) {    String ret = "";    for (int i = 0; i < b.length; i++) {        String hex = Integer.toHexString(b[ i ] & 0xFF);        if (hex.length() == 1) {            hex = '0' + hex;        }        ret += hex.toUpperCase();    }    return ret;}/** 16进制字符串转换成16进制byte数组 */public static byte[] strToHexByteArray(String str){    byte[] hexByte = new byte[str.length()/2];    for(int i = 0,j = 0; i < str.length(); i = i + 2,j++){        hexByte[j] = (byte)Integer.parseInt(str.substring(i,i+2), 16);    }    return hexByte;}
  • 修改doWrite方法内的代码:
修改前:characteristic.setValue(data);修改后:byte [] value = strToHexByteArray(bytes2HexString(data));characteristic.setValue(value);
  • 修改write方法内的代码:
修改前:characteristic.setValue(data);修改后:byte [] value = strToHexByteArray(bytes2HexString(data));characteristic.setValue(value);

ios源码修改

已经有了修改后的ios源码了,暂未整理,最新版本也已不需要修改源码了。

demo

源码地址:适配android和ios平台

PS:由于海马玩模拟器的android版本是4.2.2,而react-native-ble-manager支持的android最低版本为API 19,即4.4.2,所以用海马玩模拟器打开使用这个组件的App会闪退,如果需要使用模拟器调试,建议使用Android官方模拟器或者夜神模拟器,不过这个组件涉及到蓝牙硬件,测试蓝牙功能只能使用真机。

截图(ios)

搜索

已连接

5 0