直到目前為止,咱們都只是單方面接收 IDS Server 的資訊,若不靠外部檔案這一外掛,便完全干涉不了 IDS Server 的運作。今天,咱們就來說明怎麼命令 IDS Server 做事。
IDS 裡具備 Write 屬性的有:IDD Status Reader Control Point
、IDD Command Control Point
和 IDD Record Access Control Point
,而這些 Control Point,就是讓 GATT Client 指示(寫入)IDS Server 去行使既有命令的窗口。在 MicroPython bluetooth 模組中,GATT Server 要接收 GATT Client 的命令必須藉由監控 _IRQ_GATTS_WRITE (3)
這寫入中斷:
def bt_irq(event, data):
if event == _IRQ_GATTS_WRITE:
# A client has written to this characteristic or descriptor.
conn_handle, value_handle = data
req = bluetooth.BLE().gatts_read(value_handle)
這裡要注意的是,若要得到 GATT Client 寫入的資料,須以 BLE.gatts_read()
來取得。
「有了 GATT Client 的寫入資料後,剩下的事情就只是單純地解析資料、執行命令而已,這有什麼好說的啊!」
看官一定這麼想吧!
呃 ... 可惜,好事多磨,沒那麼順利 ...
IDS 規定,GATT Client 寫入 Control Point 後,對於某些情況,必須回應相應的 Error Code:
Name | Error Code | Description |
---|---|---|
Invalid CRC | 0x81 | 錯誤的 E2E-CRC |
Invalid Counter | 0x82 | 錯誤的 E2E-Counter |
CCCD Improperly Configured | 0xFD | CCCD 沒有正確設定 |
Procedure Already In Progress | 0xFE | 已經有 Control Point 在運作 |
Success | 0x00 | n/a |
但很可惜,MicroPython bluetooth 在 Write IRQ 裡,總是回覆 0,使用者無法干涉。於是,為了滿足 IDS 的規定,咱們又要動到 MicroPyhton 的原始碼了。
檔案:micropython/extmod/modbluetooth.h
找到 mp_bluetooth_gatts_on_write
,將其回傳值改為 mp_int_t
:
mp_int_t mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle);
檔案:micropython/extmod/modbluetooth.c
找到 mp_bluetooth_gap_on_subscribe
,修改其下的 mp_bluetooth_gatts_on_write
:
mp_int_t mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) {
mp_int_t args[] = {conn_handle, value_handle};
mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0);
mp_int_t ret = 0;
mp_obj_get_int_maybe(result, &ret);
return ret;
}
檔案:micropython/extmod/nimble/modbluetooth_nimble.c
找到 case BLE_GATT_ACCESS_OP_WRITE_DSC
,讓其回傳 mp_bluetooth_gatts_on_write()
的回傳值:
case BLE_GATT_ACCESS_OP_WRITE_DSC:
...
return mp_bluetooth_gatts_on_write(conn_handle, value_handle);
接下來就是編譯 MicroPython,編譯方法請直接參考第 16 天的 CCCD Monitor。
就像 Read 和 Indicate 一樣,咱們也可以將 Write 的共同部分抽取出來,分為 4 個部分:
class WriteMixin:
def on_write(self, value_handle: int):
data = ble.stack.gatts_read(value_handle)
att_err = self._check_att_error(data)
self._after_write()
# 非 0 的 ATT Error 值表示錯誤
if att_err:
return att_err
self._parse_write_data(data)
def _check_att_error(self, data: bytes) -> int:
raise NotImplementedError
def _after_write(self):
pass
def _parse_write_data(self, data: bytes):
raise NotImplementedError
_parse_write_data()
結束後並沒有 return
語句。None
,且在 mp_bluetooth_gatts_on_write()
裡會將 None
當作 0 來處理。_check_att_error()
和 _parse_write_data()
應由 Control Point 自行處理,因其有各自的行為,所以沒有預設值。接下來將 WriteMixin
與 IdsServer
整合:
class IdsServer(ble.stack.Server):
def _ble_isr(self, event, data):
if event == _IRQ_GATTS_WRITE:
value_handle = data[1]
for c in self._ids.chars:
if value_handle == c.value_handle:
return c.on_write(value_handle)
咱們來製作 IDD Status Reader Control Point 類別的雛形:
class IddStatusReaderCP(
ble.mixin.WriteMixin, ble.mixin.IndicateMixin, ble.stack.Characteristic
):
def __init__(self):
ble.stack.Characteristic.__init__(self, 0x2B24, write=True, indicate=True)
ble.mixin.IndicateMixin.__init__(self)
def _check_att_error(self, data: bytes) -> int:
if ble.global_var.is_cp_in_progress:
return ble.consts.ATT_ERR_PROCEDURE_ALREADY_IN_PROGRESS
elif not self._ind_enabled:
return ble.consts.ATT_ERR_CCCD_IMPROPERLY_CONFIGURED
elif len(data) < 2:
return ble.consts.ATT_ERR_VALUE_NOT_ALLOWED
else:
return 0
def _parse_write_data(self, data: bytes):
common.logger.write(
f"IDD Status Reader CP: {common.utils.array_to_hex_str(data)}"
)
_check_att_error()
判斷 3 種 ATT Error:
_parse_write_data()
簡單將指令印出咱們使用 nRF Connect app 進行測試:
GATT Client 未設定 CCCD,徑直寫入指令
IDS Server 回應 CCCD Improperly Configured
GATT Client 設定 CCCD 後,寫入指令
GATT Client 收到 IDS Server 的回應
IDS Server 的紀錄:
如此,基本的 Control Point 寫入架構雛形便完成了。