React Native BLE蓝牙通信

由于业务需要,基于React Native 开发的App要跟BLE蓝牙设备通信。 http://blog.csdn.net/withings/article/details/71378562
js.coach上搜索React Native BLE蓝牙组件,只找到三个组件:

  1. react-native-ble-manager:文档清晰明了,简单,基本每个月都有更新,遇到问题提交issue作者也能及时回复。

  2. react-native-ble-plx文档阅读起来有点难度,更新频率较慢。

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

综上分析,我选择react-native-ble-manager组件,组件的安装、配置看官方文档即可。

修改源码发送16进制数据给蓝牙

通信的基础是发送正确的数据格式,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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 修改writeWithoutResponse方法内的代码:
修改前:    
byte[] decoded = Base64.decode(message.getBytes(), Base64.DEFAULT);

修改后:
byte[] decoded = hexStringToBytes(message);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

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;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 修改doWrite方法内的代码:
修改前:
characteristic.setValue(data);

修改后:
byte [] value = strToHexByteArray(bytes2HexString(data));
characteristic.setValue(value);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 修改write方法内的代码:
修改前:
characteristic.setValue(data);

修改后:
byte [] value = strToHexByteArray(bytes2HexString(data));
characteristic.setValue(value);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

数据通信

实现蓝牙数据通信需要初始化、搜索、连接、通知、写数据

初始化

BleManager.start({showAlert: false})
  .then(() => {
    console.log('Module initialized');
  });

//检查蓝牙打开状态,初始化蓝牙后检查当前蓝牙有没有打开
BleManager.checkState();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

添加相应的监听器

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

    }
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

搜索

第一次使用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地址加入到广播中,普通蓝牙设备一般没有)。

//扫描可用设备
BleManager.scan([], 5, true)
    .then(() => {
        console.log('Scan started');
    });

//停止扫描
BleManager.stopScan()
    .then(() => {
        console.log('Scan stopped');
    })
    .catch((err)=>{
        console.log('Scan stopped fail',err);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

添加相应的监听器

//搜索到一个新设备监听
NativeAppEventEmitter.addListener('BleManagerDiscoverPeripheral', (data) => {
    console.log('BleManagerDiscoverPeripheral:', data);
    let id;  //蓝牙连接id
    let macAddress;  //蓝牙Mac地址            
    if(Platform.OS == 'android'){
        macAddress = data.id;
        id = macAddress;
    }else{  //ios通过广播0x18获取蓝牙Mac地址,如果广播携带有Mac地址
        macAddress = getMacAddressFromIOS(data);
        id = data.id;
    }            
});

/** ios系统从蓝牙广播信息中获取蓝牙MAC地址 */
getMacAddressFromIOS(data){
    let macAddressInAdvertising = data.advertising.kCBAdvDataManufacturerMacAddress;
    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();
}

//搜索结束监听
NativeAppEventEmitter.addListener('BleManagerStopScan', () => {
     console.log('BleManagerStopScan:','Scanning is stopped');      
    //搜索结束后,获取搜索到的蓝牙设备
    BleManager.getDiscoveredPeripherals([])
       .then((peripheralsArray) => {
           console.log('Discovered peripherals: ', peripheralsArray);
       });
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

连接

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

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

//断开蓝牙连接
BleManager.disconnect(id)
    .then( () => {
        console.log('Disconnected');
    })
    .catch( (error) => {
        console.log('Disconnected error:',error);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

添加相应的监听器

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

//蓝牙设备已断开连接监听
NativeAppEventEmitter.addListener('BleManagerDisconnectPeripheral', (args) => {
    console.log('BleManagerDisconnectPeripheral:', args);
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

蓝牙连接后会显示该设备的具体信息,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-PEN-01' 
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

通知

蓝牙连接后还不能进行通信,要使蓝牙和手机互相通信,需要开启通知服务。

//打开通知
BleManager.startNotification(peripheralId, serviceUUID, characteristicUUID)
  .then(() => {
      console.log('Notification started');
  })
  .catch((error) => {
      console.log('Notification error:',error);
  });

//关闭通知
BleManager.stopNotification(peripheralId, serviceUUID, characteristicUUID)
   .then(() => {
        console.log('stopNotification success!');
    })
    .catch((error) => {
        console.log('stopNotification error:',error);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

peripheralId,配对的设备id,android下peripheralId为Mac地址,ios下peripheralId为UUID; 
假设蓝牙连接后返回的数据为bluetoothInfobluetoothInfo下的characteristics字段值是一个特征数组,每一项代表一个特征通道,找到properties属性为{ Notify: 'Notify' }的那一项

{ descriptors: [ { value: null, uuid: '2902' }, { value:    null, uuid: '2901' } ],
 properties: { Notify: 'Notify' },
 characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb8',
 service: '0783b03e-8535-b5a0-7140-a304d2495cb7' }
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

该项代表通知的特征通道,service字段值即为serviceUUID 服务UUID,characteristic字段值即为characteristicUUID 特征值UUID

let peripheralId, serviceUUID, characteristicUUID;
peripheralId = bluetoothInfo.id;
for(let item of bluetoothInfo.characteristics){
    if(JSON.stringify(item.properties) == JSON.stringify({ Notify: 'Notify' })){
        serviceUUID = item.service;
        characteristicUUID = item.characteristic;
        break;
    }       
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

写数据

写数据有两个方法,writewriteWithoutResponse,由于写数据使用的properties属性为writeWithoutResponse的特征通道,所以这里使用writeWithoutResponse方法来发送数据到蓝牙

BleManager.writeWithoutResponse(peripheralId, serviceUUID, writeCharacteristicUUID, data)
    .then(() => {
        console.log('Write success: ',data);
    })
    .catch((error) => {
        console.log('Write  failed: ',data);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

peripheralId,配对的设备id,android下peripheralId为Mac地址,ios下peripheralId为UUID;data为发送给蓝牙的数据; 
假设蓝牙连接后返回的数据为bluetoothInfobluetoothInfo下的characteristics字段值是一个特征数组,每一项代表一个特征通道,找到properties属性为{ WriteWithoutResponse: 'WriteWithoutResponse' }的那一项

{ descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],
  properties: { WriteWithoutResponse: 'WriteWithoutResponse' },
  characteristic: '0783b03e-8535-b5a0-7140-a304d2495cba',
  service: '0783b03e-8535-b5a0-7140-a304d2495cb7' }
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

该项代表写数据到蓝牙的特征通道,service字段值即为serviceUUID 服务UUID,characteristic字段值即为writeCharacteristicUUID 写数据特征值UUID;

let peripheralId, serviceUUID, writeCharacteristicUUID;
peripheralId = bluetoothInfo.id;
for(let item of bluetoothInfo.characteristics){
    if(JSON.stringify(item.properties) == JSON.stringify({ WriteWithoutResponse: 'WriteWithoutResponse' })){
        serviceUUID = item.service;
        writeCharacteristicUUID = item.characteristic;
        break;
    }       
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

添加相应的监听器

//通知接收到蓝牙发送过来的新数据
NativeAppEventEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', (data) => {
    //ios接收到的是小写的16进制,android接收的是大写的16进制,统一转化为大写16进制
    let value = data.value.toUpperCase();               
    console.log('BluetoothUpdateValue', value);
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

demo

源码地址

截图

搜索 

连接中 

发送数据

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值