要给 Android 平板蓝牙语音对讲 APP 上增加一个报警推送功能,即监听蓝牙控制板上的一个信号,来显示报警信息。实际上就是监听一个自定义的特性即可。
STM32 芯片的 Android SDK 封装的太猛了,捋一遍代码脑子爆炸。 所以记录一下看 SDK 源码的过程。
特性扫描
BlueSTSDK/BlueSTSDK/src/main/java/com/st/BlueSTSDK/Node.java
增加了调试日志,方便查看新增特性的 UUID
mCharFeatureMap.clear();
for(BluetoothGattService service : nodeServices){
//check if it is a specific service
if(service.getUuid().equals(BLENodeDefines.Services.Debug.DEBUG_SERVICE_UUID))
mDebugConsole = buildDebugService(service);
else if(service.getUuid().equals(BLENodeDefines.Services.Config.CONFIG_CONTROL_SERVICE_UUID)) {
List<BluetoothGattCharacteristic> controlChar = service.getCharacteristics();
//check for the initialization characteristics
for(BluetoothGattCharacteristic characteristic : controlChar) {
if (characteristic.getUuid().equals(BLENodeDefines.Services.Config.FEATURE_COMMAND_UUID))
mFeatureCommand = characteristic;
if (characteristic.getUuid().equals(BLENodeDefines.Services.Config.REGISTERS_ACCESS_UUID))
mConfigControl = new ConfigControl(Node.this, characteristic,mConnection);
}//for
}else {//otherwise will contains feature characteristics
Log.d(TAG, "found service with uuid: " + service.getUuid().toString());
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
UUID uuid = characteristic.getUuid();
Log.d(TAG, "found char with uuid: " + uuid.toString());
if (BLENodeDefines.FeatureCharacteristics.isBaseFeatureCharacteristics(uuid)) {
buildFeatures(characteristic);
Log.d(TAG, "found 1");
}else if (BLENodeDefines.FeatureCharacteristics.isExtendedFeatureCharacteristics(uuid)) {
buildFeaturesKnownUUID(characteristic,
BLENodeDefines.FeatureCharacteristics.getExtendedFeatureFor(uuid));
Log.d(TAG, "found 2");
}else if (BLENodeDefines.FeatureCharacteristics
.isGeneralPurposeCharacteristics(uuid)) {
buildGenericFeature(characteristic);
Log.d(TAG, "found 3");
}else if(mExternalCharFeatures!=null &&
mExternalCharFeatures.containsKey(uuid)) {
buildFeaturesKnownUUID(characteristic, mExternalCharFeatures.get(uuid));
Log.d(TAG, "found 4");
}
Log.d(TAG, "found 5");
}//for
}//if-else-if-else
}//for each service
打印出的日志:
found service with uuid: 00001801-0000-1000-8000-00805f9b34fb
found char with uuid: 00002a05-0000-1000-8000-00805f9b34fb
found 5
found service with uuid: 00001800-0000-1000-8000-00805f9b34fb
found char with uuid: 00002a00-0000-1000-8000-00805f9b34fb
found 5
found char with uuid: 00002a01-0000-1000-8000-00805f9b34fb
found 5
found char with uuid: 00002a04-0000-1000-8000-00805f9b34fb
found 5
found service with uuid: 00000000-0001-11e1-9ab4-0002a5d5c51b
found char with uuid: 00000001-0002-11e1-ac36-0002a5d5c51b
found 2
found 5
found char with uuid: 00000002-0002-11e1-ac36-0002a5d5c51b
found 2
found 5
found service with uuid: d973f2e0-b19e-11e2-9e96-0800200c9a66
found char with uuid: d973f2e1-b19e-11e2-9e96-0800200c9a66
found 5
其中 d9 开头的 UUID 即是自定义特性。
Node.java 中与 Notify 特性相关的函数
/**
*
* test if a characteristics can be notify
* @param characteristic characteristic to notify
* @return true if we can receive notification from it
*/
private static boolean charCanBeNotify(BluetoothGattCharacteristic characteristic)
/**
* send a request for enable/disable the notification update on a specific characteristics
* @param characteristic characteristics to notify
* @param enable true if you want enable the notification, false if you want disable it
* @return true if the request is correctly send, false otherwise
*/
boolean changeNotificationStatus(BluetoothGattCharacteristic characteristic, boolean enable)
/**
* unsubscribe the notification for the node update of the feature
* @param feature feature that you want stop to be notify
* @return false if the feature is not handle by this node or disabled
*/
public boolean disableNotification(Feature feature)
/**
* ask to the node to notify when the feature change its value
* @param feature feature to look
* @return false if the feature is not handle by this node or disabled
*/
public boolean enableNotification(Feature feature){
if(!feature.isEnabled() || feature.getParentNode()!=this)
return false;
if(isEnableNotification(feature))
return true;
BluetoothGattCharacteristic featureChar = getCorrespondingChar(feature);
if(charCanBeNotify(featureChar)) {
mNotifyFeature.add(feature);
//other things are send using that characteristic, so we don't have to
//enable it
if(characteristicsHasOtherEnabledFeatures(featureChar,feature))
return true;
return changeNotificationStatus(featureChar, true);
}
return false;
}//enableNotification
这个 enable 的写法值得参考一下。
enableNotification 在我改造后的基于前台服务的 NodeConnectionService 中会被调用。
注意 enableNotification 的参数是个 Feature 类型。
适合报警推送的 Feature
找到 SDK 中内置的一个符合报警推送的 Feature:
BlueSTSDK/BlueSTSDK/src/main/java/com/st/BlueSTSDK/Features/FeatureSwitch.java
NodeConnectionService 中与 Notify 相关的函数
/**
* listener for the audioSync feature, it will update the synchronize values
*/
private final Feature.FeatureListener mAudioConfListener = (f, sample) -> {
...
}
protected void enableNeededNotification(@NonNull Node node) {
NodeServer server = node.getNodeServer();
if(server == null)
return;
mAudioTransmitter = server.getExportedFeature(ExportedFeatureAudioOpusVoice.class);
mAudioConf = node.getFeature(FeatureAudioConf.class);
mAudioConfServer = server.getExportedFeature(ExportedAudioOpusConf.class);
// restoreGuiStatus(); // TODO: startRecSwitch.setChecked(getIsSendingStatus());
if(mAudioConf!=null && mAudioTransmitter!=null ) {
mAudioCodecManager = mAudioConf.instantiateManager(true,false);
audioSamplingFreq = mAudioCodecManager.getSamplingFreq();
audioChannels = mAudioCodecManager.getChannels();
mAudioConf.addFeatureListener(mAudioConfListener);
node.enableNotification(mAudioConf);
addFeatureListener 就是用于监听 notification 的,嘎嘎,终于找到了。
剩下的就是 enableNotification 的时机问题了。
实际上在 enableNeededNotification 里最后缀上即可。但是需要提前定义一个自定义的 Feature.FeatureListener 用来监听 notify 过来的报警信息。
Feature 与 characteristic 关联
最后一个问题是,如何知道需要 enable 的 Feature 对应的特性值。
enableNotification 中将 feature 转换为 characteristic 使用了 getCorrespondingChar 这个函数。
BluetoothGattCharacteristic featureChar = getCorrespondingChar(feature);
在 Node.java 中找到了
/**
* find the the gattCharacteristics corresponding to a feature
* @param feature feature to search
* @return null if the feature is not handle by the node, the characteristics otherwise
*/
private BluetoothGattCharacteristic getCorrespondingChar(Feature feature){
ArrayList<BluetoothGattCharacteristic> candidateChar = new ArrayList<>();
for (Map.Entry<BluetoothGattCharacteristic,List<Feature>> e: mCharFeatureMap.entrySet()){
List<Feature> featureList = e.getValue();
if(featureList.contains(feature)){
candidateChar.add(e.getKey());
}
}//for entry
if(candidateChar.isEmpty())
return null;
else if(candidateChar.size()==1){
return candidateChar.get(0);
}else{ //we have to select the feature that permit us to have more data
int maxNFeature=0;
BluetoothGattCharacteristic bestChar=null;
for(BluetoothGattCharacteristic characteristic: candidateChar){
int nFeature = mCharFeatureMap.get(characteristic).size();
if(nFeature>maxNFeature){
maxNFeature=nFeature;
bestChar=characteristic;
}//if
}//for
return bestChar;
}//if-else
}
mCharFeatureMap 的类型定义
private Map<BluetoothGattCharacteristic,List<Feature>> mCharFeatureMap= new HashMap<>();
即,characteristic 对应的是个 Feature list.
哪些地方填充了 mCharFeatureMap
- buildFeaturesKnownUUID。最终还是在服务发现回调那里调用了这个函数
- buildFeatures(BluetoothGattCharacteristic characteristic)
- buildGenericFeature(BluetoothGattCharacteristic characteristic)
/**
* describe as manage some specific UUID using a feature class, the uuid will be manage by
* the node class only if is know before the connection
* if a uuid is already know it will be overwrite with the new list of feature
* @param userDefineFeature map that link the uuid with the features that contains.
*/
public void addExternalCharacteristics(@Nullable Map<UUID,List<Class< ? extends Feature>>>
userDefineFeature){
if(userDefineFeature==null)
return;
mExternalCharFeatures.putAll(userDefineFeature);
}//addExternalCharacteristics
看来是需要调用这个 addExternalCharacteristics
}else if(mExternalCharFeatures!=null &&
mExternalCharFeatures.containsKey(uuid)) {
buildFeaturesKnownUUID(characteristic, mExternalCharFeatures.get(uuid));
Log.d(TAG, "found 4");
}
何时调用 addExternalCharacteristics
Node 建立连接时有个 options
public void connect(Context c, ConnectionOption options){
//if we are already connected or we are connecting avoid do to send again the command
if(mState == State.Connected || mState==State.Connecting){
return;
}
updateNodeStatus(State.Connecting);
if(options == null)
options = ConnectionOption.buildDefault();
//we start the connection so we will stop to receive advertise, so we delete the timeout
if(mBackGroundHandler !=null) mBackGroundHandler.removeCallbacks(mSetNodeLost);
mUserAskToDisconnect=false;
/*
HandlerThread thread = new HandlerThread("NodeConnection");
thread.start();
mBleThread = new Handler(thread.getLooper());
*/
mBleThread = new Handler(Looper.getMainLooper());
mContext=c;
setBoundListener(c.getApplicationContext());
mConnectionOption = options;
addExternalCharacteristics(options.getUserDefineFeature());
mBleThread.post(mConnectionTask);
}
在 NodeConnectionService 中搜索 node.connect
private void connect(int startId, Intent intent) {
String tag = intent.getStringExtra(NODE_TAG_ARG);
Log.d("NodeConnectionService","connect " + tag);
ConnectionOption options = intent.getParcelableExtra(CONNECTION_PARAM_ARG);
Node n = Manager.getSharedInstance().getNodeWithTag(tag);
if(n!=null)
if(!mConnectedNodes.contains(n)) {
mConnectedNodes.add(n);
mNode = n;
n.addNodeStateListener(mStateListener);
n.connect(this,options);
startForeground(startId, buildConnectionNotification(n));
}
}
看到 ConnectionOption.java 时,我绝望了。。。
private final
UUIDToFeatureMap userDefineFeature;
private ConnectionOption(Parcel in) {
resetCache = in.readByte() != 0;
enableAutoConnect = in.readByte() != 0;
if(in.readByte()!=0){
userDefineFeature = (UUIDToFeatureMap) in.readSerializable();
}else{
userDefineFeature = null;
}
}
这是什么鬼玩意,我要直接改 SDK 源代码了,封装这么多层,真是浪费时间。
最终
直接修改 sdk 中的服务发现逻辑,增加了一个 uuid 的判断逻辑
}else if(mExternalCharFeatures!=null &&
mExternalCharFeatures.containsKey(uuid)) {
buildFeaturesKnownUUID(characteristic, mExternalCharFeatures.get(uuid));
Log.d(TAG, "found 4");
} else if (uuid.toString().equals("d973f2e1-b19e-11e2-9e96-0800200c9a66")) {
// 报警通知特性
//List<Feature> tempFeatures = new ArrayList<>(FeatureSwitch.class);
List<Class<? extends Feature>> featureClasses = new ArrayList<>();
featureClasses.add(FeatureSwitch.class);
buildFeaturesKnownUUID(characteristic, featureClasses);
}
java 字符串比对的坑
开始时使用双等于号判定,蓝牙特性 uuid 是否等于固定的字符串,发现怎么也无法匹配。
原来是必须使用 equals 来判定是否相等。。。
微信关注我哦 👍
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式