前言
之前寫(xiě)過(guò)兩篇有關(guān)于ANCS的文章,最近一段時(shí)間老是有人問(wèn)關(guān)于得到ANCS服務(wù)的問(wèn)題,因?yàn)镮OS ANCS不同于其他的Peripheral一樣對(duì)周邊所有的藍(lán)牙設(shè)備廣播自己,而是僅有連接上配對(duì)并連接上IOS設(shè)備的可見(jiàn),我想這對(duì)于Android、IOS、嵌入式等的開(kāi)發(fā)都是一樣的。
現(xiàn)在將以前寫(xiě)的Android ble操作ANCS的demo修改了一下,并集中對(duì)于ANCS的相關(guān)問(wèn)題進(jìn)行說(shuō)明。
發(fā)現(xiàn)ANCS
熟悉IOS的都知道,IOS設(shè)備上的藍(lán)牙是有很大限制的,只能連接手表、耳機(jī)等周邊設(shè)備,甚至同樣是IOS平臺(tái)的設(shè)備都不能進(jìn)行互聯(lián)。但是BLE的出現(xiàn)給了我們使用藍(lán)牙技術(shù)進(jìn)行通信的可能。
這里我們用到的是IOS系統(tǒng)提供的ANCS服務(wù)獲取IOS分發(fā)的通知,包括消息、來(lái)電、計(jì)劃等,但是這個(gè)服務(wù)對(duì)于我們是不可見(jiàn)的,他并不主動(dòng)進(jìn)行廣播,我們使用BLE scan 并不能掃描到ANCS這個(gè)服務(wù)。
** 那么是不是意味著我們就無(wú)法找到這個(gè)ANCS服務(wù)了呢? **
答案是否定的,經(jīng)過(guò)調(diào)查我們發(fā)現(xiàn)ANCS是基于GATT做的封裝,也就是他是一個(gè)BLE的gatt server,只是對(duì)通信過(guò)程加入了自定義的協(xié)議,他跟其他的Ble service是同等的,比如常見(jiàn)的Heart Rate。因此我們考慮通過(guò)其他的service連接上這個(gè)GATT server,然后在獲取ANCS服務(wù)的思路。
發(fā)現(xiàn)設(shè)備
想要連接藍(lán)牙設(shè)備我們首先要知道他的設(shè)備地址,但是IOS設(shè)備的藍(lán)牙是不主動(dòng)廣播的,但是我們知道IOS是支持BLE廣播的。
這里我們就可以借由這個(gè)功能讓我們得到IOS設(shè)備的目標(biāo)地址,當(dāng)然你可以自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的APP去startLeAdvertisment,因?yàn)槲也⒉粫?huì)IOS開(kāi)發(fā)所以這里借助了一個(gè)第三方APP(LightBlue)來(lái)虛擬一個(gè)peripherial,例如Heart Rate.
然后在我們的Android APP中進(jìn)行掃描,就能掃描到這個(gè)名稱為Heart Rate的周邊設(shè)備。
@Override
public void onScanResult(int callbackType, ScanResult result) {
Log.d(TAG, "onScanResult Device Address :" + result.getDevice());
BluetoothDevice device = result.getDevice();
if (device.getName() != null && device.getName().equals("Heart Rate")) {
mTargetDevice = device;
}
}
連接設(shè)備
使用我們上一步得到的BluetoothDevice對(duì)象或者設(shè)備地址鏈接到Gatt操作。
安卓代碼示例如下:
device.connectGatt(getApplicationContext(), false, mGattCallback);
并在連接回調(diào)中,獲得連接的狀態(tài):
private class LocalBluetoothGattCallback extends BluetoothGattCallback {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d(TAG, "connected");
mConnectedGatt = gatt;
}
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG, "disconnected");
mConnectedGatt = null;
}
}
}
發(fā)現(xiàn)ANCS
當(dāng)連接上GATT后,我們就可以調(diào)用discover函數(shù)去發(fā)現(xiàn)所有的服務(wù)。
gatt.discoverServices();
然后在發(fā)現(xiàn)服務(wù)的回調(diào)中就可以根據(jù)UUID獲得ANCS服務(wù)。
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService ancsService = gatt.getService(UUID.fromString(Constants.service_ancs));
if (ancsService == null) {
Log.d(TAG, "ANCS cannot find");
} else {
Log.d(TAG, "ANCS find");
mANCSService = ancsService;
mDataSourceChar = ancsService.getCharacteristic(UUID.fromString(Constants.characteristics_data_source));
mPointControlChar = ancsService.getCharacteristic(UUID.fromString(Constants.characteristics_control_point));
mNotificationSourceChar = ancsService.getCharacteristic(UUID.fromString(Constants.characteristics_notification_source));
}
}
}
獲得ANCS后,我們也可以通過(guò)UUID獲得ANCS的三個(gè)characteristic.
綁定設(shè)備
當(dāng)我們進(jìn)行ANCS操作的時(shí)候,就會(huì)彈出配對(duì)的請(qǐng)求的對(duì)話框要求我們來(lái)完成配對(duì),如果我們不進(jìn)行配對(duì)的話,就無(wú)法對(duì)ANCS進(jìn)行操作,同時(shí)GATT連接也會(huì)經(jīng)常自動(dòng)斷開(kāi)連接。
因此我們一般是在掃描到設(shè)備后就與設(shè)備進(jìn)行配對(duì),完成配對(duì)后再與設(shè)備進(jìn)行連接。
先判斷是否已經(jīng)在配對(duì)列表中,是,則進(jìn)行連接,不是,則進(jìn)行配對(duì):
//已經(jīng)綁定,該設(shè)備在綁定的設(shè)備名單里面
if (mBluetoothAdapter.getBondedDevices().contains(device)) {
device.connectGatt(getApplicationContext(), false, mGattCallback);
mBluetoothLeScanner.stopScan(mScanCallback);
} else {//未綁定的設(shè)備
device.createBond();
}
通過(guò)接受系統(tǒng)的BondStateChanged廣播接受綁定成功的消息:
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
if (intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1) == BluetoothDevice.BOND_BONDED) {
showMessage("Bluetooth bond success!");
}
}
操作ANCS
操作ANCS就是操作ANCS服務(wù)下的三個(gè)Characteristic,其操作也無(wú)非是BLE characteristic的三種操作:
- 讀取(read)
- 寫(xiě)入(write)
- 通知(setNotification)
詳細(xì)可見(jiàn)最后一章參考文章《Android BLE開(kāi)發(fā)之玩轉(zhuǎn)小米手環(huán)》。
因?yàn)樾枰ㄟ^(guò)對(duì)Notification Source、Data Source、Control Point進(jìn)行讀寫(xiě)通知操作完成所有的功能,因此對(duì)操作的流程、通知數(shù)據(jù)包的格式、命令的格式進(jìn)行了規(guī)定,相當(dāng)于應(yīng)用層的協(xié)議,具體的可以參考ANCS分析的兩篇文章。
- Notification Source(setNotification):獲取通知基本信息
- Data Source(setNotification):獲取通知的詳細(xì)信息
- Control Point (write): 寫(xiě)入通知控制命令
接下來(lái)我們主要從代碼上來(lái)說(shuō)明如何操作。
獲取通知
- Data Source通知開(kāi)啟
private void setNotificationEnabled(BluetoothGattCharacteristic characteristic) {
mConnectedGatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(Constants.descriptor_config));
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mConnectedGatt.writeDescriptor(descriptor);
}
}
- Data Source開(kāi)啟后,Notification Source通知開(kāi)啟。
- Notification Source的回調(diào)中獲取通知基本信息。
System.out.println(
"EventId:" + String.format("%d", nsData[0]) + "\n" +
"EventFlags:" + String.format("%02x", nsData[1]) + "\n" +
"Category id:" + String.format("%d", nsData[2]) + "\n" +
"Category Count:" + String.format("%d", nsData[3]) + "\n" +
"NotificationUId:" + String.format("%02X", nsData[4]) + String.format("%02X", nsData[5])+ String.format("%02X", nsData[6]) + String.format("%02X", nsData[7]) + "\n"
);
- 往Control Point中寫(xiě)入獲取更多通知信息的命令。
private void getMoreAboutNotification(byte[] nsData) {
byte[] getNotificationAttribute = {
(byte) 0x00,
//UID
nsData[4], nsData[5], nsData[6], nsData[7],
//app id
(byte) 0x00,
//title
(byte) 0x01, (byte) 0xff, (byte) 0xff,
//message
(byte) 0x03, (byte) 0xff, (byte) 0xff
};
if (mConnectedGatt != null) {
mPointControlChar.setValue(getNotificationAttribute);
mConnectedGatt.writeCharacteristic(mPointControlChar);
}
}
- Data Source的回調(diào)中獲取更多信息。
具體解析參見(jiàn)相關(guān)閱讀中ANCS分析的兩篇文章。
執(zhí)行相應(yīng)動(dòng)作
- 解析eventFlags中的通知?jiǎng)幼鳌?/li>
public int getAction() {
action = 0;
//positive標(biāo)志位為1
if ((eventFlags & 0x08) > 0) {
action = action + 1;
}
//negative標(biāo)志位為1
if ((eventFlags & 0x10) > 0) {
action = action + 2;
}
return action;
}
- 寫(xiě)入動(dòng)作命令到Control Point中。
byte[] action = {
(byte) 0x02,
//UID
nid[0], nid[1], nid[2], nid[3],
//positive action id(二選一)
(byte) 0x00,
//negative action id(二選一)
(byte)0x01,
};
Demo
Github地址:ANCSReader
- 接收系統(tǒng)通知基本的信息,包括標(biāo)題、類型(消息、來(lái)電等)、狀態(tài)(產(chǎn)生、修改、刪除)
- 接收通知的詳細(xì)信息,包括內(nèi)容、應(yīng)用、時(shí)間等各種信息。
- 對(duì)通知采取相應(yīng)的操作
相關(guān)閱讀
** Android BLE開(kāi)發(fā)相關(guān)知識(shí) **
Android BLE開(kāi)發(fā)之初識(shí)GATT
Android BLE開(kāi)發(fā)之玩轉(zhuǎn)小米手環(huán)
** ANCS相關(guān)知識(shí) **
蘋果通知中心服務(wù)ANCS協(xié)議分析
蘋果通知中心服務(wù)ANCS協(xié)議分析二