蓝牙无线技术是一种全球通用的短距离无线技术,具有耗电量低、成本低、安全性、稳定性、易用性等优点,尤其在物联网设备上的占有率非常高,因此我们有必要对蓝牙做深入的了解。
蓝牙通信一般可以分为3步:
1)寻找设备
2)连接
3)通信
比如A设备要想通过蓝牙向B设备发送消息:
寻找设备
A要怎么找到B呢,一般是由设备B按照一定的周期广播数据包,然后A和B指定好协议,看广播数据里有没有协议约定的数据,有的话则说明找到了B。
广播的数据包分为四种
ADV_IND
ADV_DIRECT_IND
ADV_NONCONN_IND
ADV_SCAN_IND
一般发送的是ADV_IND包(可参考蓝牙协议分析(5)_BLE广播通信相关的技术分析。
Android设备通过系统提供的API开始接受广播数据,
//要先停止上一次的scan,不然无法启动新的scanbluetoothAdapter.getBluetoothLeScanner().stopScan(bleCallback);bluetoothAdapter.getBluetoothLeScanner().startScan(getFilters(),getSettings(),bleCallback);
然后在callback里获取广播数据
privateScanCallbackbleCallback=newScanCallback(){@OverridepublicvoidonScanResult(intcallbackType,finalScanResultresult){}}
ScanResult就是当前接收到的广播里数据,在5.0以及5.0以上的api,会帮我们解析广播的数据,但是5.0以下的话,只是会把整个广播数据传回来。下面大致讲解下广播协议数据。
广播的数据,按 len 1字节,TYPE1字节, data (len -1)字节的顺序组依次组织,key的含义在上面的表格中已经给出。
另外,设备的广播频率是可以自己定义的,从几ms到几百ms不等,广播的频率决定了手机发现设备的快慢,
有个软件叫"nRF Connect",可以很方便的观看蓝牙设备的广播速度
连接过程
蓝牙分为5种工作状态,
准备(standby):就绪状态,准备转变为其他状态
广播(advertising):向外发送数据的状态
监听扫描(Scanning): 扫描状态的时候,在接受到ADV_IND包是,会发送SCAN_REQ包,可以获得更多的信息。
发起连接(Initiating):发起连接状态,在ADV_IND或者ADV_DIRECT_IND之后,会发送CONNECT_REQ包,从而建立连接。
已连接(Connected):根据连接时约定的参数,发送CONNECT_EVENT,保持连接不断开。
具体工作流程如下:
可被连接的设备(Advertiser),按照一定的周期广播ADV_IND或者ADV_DIRECT_IND包(可参考“蓝牙协议分析(5)_BLE广播通信相关的技术分析”)。
主动连接的设备(Initiator),在收到广播包之后,会回应一个CONNECT_REQ请求,该请求携带了可决定后续“通信时序”的参数,例如双方在哪一个时间点、哪一个Physical Channel收发数据,等等。
Initiator在发出CONNECT_REQ数据包之后,自动转变为Connection状态,成为Master角色(注意:这是“自动”的,不需要等待另一方的回应)。同样,Advertiser在收到CONNECT_REQ请求之后,也自动转变为Connection状态,成为Slave角色。
此后,双方按照CONNECT_REQ参数所给出的约定,定时到切换到某一个Physical Channel上,按照Master->Slave然后Slave->Master的顺序,收发数据,直至连接断开。
在Android手机中,连接的代码非常简单,只有一个api可以调用,如下:
privateBluetoothGattconnectGattCompat(BluetoothGattCallbackbluetoothGattCallback,BluetoothDevicedevice,booleanautoConnect){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){returndevice.connectGatt(context,autoConnect,bluetoothGattCallback,TRANSPORT_LE);}else{returndevice.connectGatt(context,autoConnect,bluetoothGattCallback);}}
之后在bluetoothGattCallback的onConnectionChange的回调里,就可以知道连接的结果。
通信
蓝牙设备,都会注册service和characteristic,并且用uuid标识。
service 可以理解为一个服务,在BLE从机中有多个服务,电量信息,系统服务信息等,每一个service中包含了多个characteristic特征值,每一个具体的characteristic特征值才是BLE通信的主题。
characteristic特征值:BLE主机从机通信均是通过characteristic进行,可以将其理解为一个标签,通过该标签可以读取或写入相关信息。
大家可以理解为service就是java里的class,characteristic是class里的public方法。所以我们应该先找到class,再调用其方法。
再往下想,写和读肯定是两个方法,所以也会是两个characteristic。
service和characteristic的uuid都是通信之前双方约定好的。android端在上文说的discoverService之后,查看有没有对应的uuid,如果有,就可以发起通信了。
@OverridepublicvoidonServicesDiscovered(BluetoothGattgatt,intstatus){super.onServicesDiscovered(gatt,status);listener.onServicesDiscovered(status);}@OverridepublicvoidonServicesDiscovered(finalintstatus){if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.JELLY_BEAN_MR2){service=getGatt().getService(ServerUUID);if(null!=service){BluetoothGattCharacteristicread_characteristic=service.getCharacteristic(readDataUUID);if(null!=read_characteristic){intproperties=read_characteristic.getProperties();if((properties|BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0){getGatt().setCharacteristicNotification(read_characteristic,true);}}}}}
setCharacteristicNotification 成功后,就可以在onCharacteristicChanged方法收到回调了。
蓝牙通信的过程,就是往writeCharacteristic里写数据,然后监听readCharacteristic的返回。这两个操作保持有序进行,就可以通信了。
过程总结
针对上文说的例子, 画个图来总结一下
最后还有就是通信问题,通信相对于连接来说,是稳定很多的,但是仍然会有一定的几率,收不到消息。所以硬件设备和手机的蓝牙代码,最好有一方有重试机制,一般来说手机重试就足够了。