前言
之前寫過兩篇有關于ANCS的文章,最近一段時間老是有人問關于得到ANCS服務的問題,因為IOS ANCS不同于其他的Peripheral一樣對周邊所有的藍牙設備廣播自己,而是僅有連接上配對并連接上IOS設備的可見,我想這對于Android、IOS、嵌入式等的開發都是一樣的。
現在將以前寫的Android ble操作ANCS的demo修改了一下,并集中對于ANCS的相關問題進行說明。
發現ANCS
熟悉IOS的都知道,IOS設備上的藍牙是有很大限制的,只能連接手表、耳機等周邊設備,甚至同樣是IOS平臺的設備都不能進行互聯。但是BLE的出現給了我們使用藍牙技術進行通信的可能。
這里我們用到的是IOS系統提供的ANCS服務獲取IOS分發的通知,包括消息、來電、計劃等,但是這個服務對于我們是不可見的,他并不主動進行廣播,我們使用BLE scan 并不能掃描到ANCS這個服務。
** 那么是不是意味著我們就無法找到這個ANCS服務了呢? **
答案是否定的,經過調查我們發現ANCS是基于GATT做的封裝,也就是他是一個BLE的gatt server,只是對通信過程加入了自定義的協議,他跟其他的Ble service是同等的,比如常見的Heart Rate。因此我們考慮通過其他的service連接上這個GATT server,然后在獲取ANCS服務的思路。
發現設備
想要連接藍牙設備我們首先要知道他的設備地址,但是IOS設備的藍牙是不主動廣播的,但是我們知道IOS是支持BLE廣播的。
這里我們就可以借由這個功能讓我們得到IOS設備的目標地址,當然你可以自己實現一個簡單的APP去startLeAdvertisment,因為我并不會IOS開發所以這里借助了一個第三方APP(LightBlue)來虛擬一個peripherial,例如Heart Rate.
然后在我們的Android APP中進行掃描,就能掃描到這個名稱為Heart Rate的周邊設備。
@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;
}
}
連接設備
使用我們上一步得到的BluetoothDevice對象或者設備地址鏈接到Gatt操作。
安卓代碼示例如下:
device.connectGatt(getApplicationContext(), false, mGattCallback);
并在連接回調中,獲得連接的狀態:
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;
}
}
}
發現ANCS
當連接上GATT后,我們就可以調用discover函數去發現所有的服務。
gatt.discoverServices();
然后在發現服務的回調中就可以根據UUID獲得ANCS服務。
@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后,我們也可以通過UUID獲得ANCS的三個characteristic.
綁定設備
當我們進行ANCS操作的時候,就會彈出配對的請求的對話框要求我們來完成配對,如果我們不進行配對的話,就無法對ANCS進行操作,同時GATT連接也會經常自動斷開連接。
因此我們一般是在掃描到設備后就與設備進行配對,完成配對后再與設備進行連接。
先判斷是否已經在配對列表中,是,則進行連接,不是,則進行配對:
//已經綁定,該設備在綁定的設備名單里面
if (mBluetoothAdapter.getBondedDevices().contains(device)) {
device.connectGatt(getApplicationContext(), false, mGattCallback);
mBluetoothLeScanner.stopScan(mScanCallback);
} else {//未綁定的設備
device.createBond();
}
通過接受系統的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服務下的三個Characteristic,其操作也無非是BLE characteristic的三種操作:
- 讀取(read)
- 寫入(write)
- 通知(setNotification)
詳細可見最后一章參考文章《Android BLE開發之玩轉小米手環》。
因為需要通過對Notification Source、Data Source、Control Point進行讀寫通知操作完成所有的功能,因此對操作的流程、通知數據包的格式、命令的格式進行了規定,相當于應用層的協議,具體的可以參考ANCS分析的兩篇文章。
- Notification Source(setNotification):獲取通知基本信息
- Data Source(setNotification):獲取通知的詳細信息
- Control Point (write): 寫入通知控制命令
接下來我們主要從代碼上來說明如何操作。
獲取通知
- Data Source通知開啟
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開啟后,Notification Source通知開啟。
- Notification Source的回調中獲取通知基本信息。
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中寫入獲取更多通知信息的命令。
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的回調中獲取更多信息。
具體解析參見相關閱讀中ANCS分析的兩篇文章。
執行相應動作
- 解析eventFlags中的通知動作。
public int getAction() {
action = 0;
//positive標志位為1
if ((eventFlags & 0x08) > 0) {
action = action + 1;
}
//negative標志位為1
if ((eventFlags & 0x10) > 0) {
action = action + 2;
}
return action;
}
- 寫入動作命令到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
- 接收系統通知基本的信息,包括標題、類型(消息、來電等)、狀態(產生、修改、刪除)
- 接收通知的詳細信息,包括內容、應用、時間等各種信息。
- 對通知采取相應的操作
相關閱讀
** Android BLE開發相關知識 **
Android BLE開發之初識GATT
Android BLE開發之玩轉小米手環
** ANCS相關知識 **
蘋果通知中心服務ANCS協議分析
蘋果通知中心服務ANCS協議分析二