iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Software Development

以MicroPython在ESP32上實作Insulin Delivery Service系列 第 23

Day 23 - 命令 IDS Server 做事 - Write Control Point

  • 分享至 

  • xImage
  •  

直到目前為止,咱們都只是單方面接收 IDS Server 的資訊,若不靠外部檔案這一外掛,便完全干涉不了 IDS Server 的運作。今天,咱們就來說明怎麼命令 IDS Server 做事。

1. Write Property

IDS 裡具備 Write 屬性的有:IDD Status Reader Control PointIDD Command Control PointIDD 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 的寫入資料後,剩下的事情就只是單純地解析資料、執行命令而已,這有什麼好說的啊!」
看官一定這麼想吧!
呃 ... 可惜,好事多磨,沒那麼順利 ...

2. Attribute Protocol Error Codes

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 的原始碼了。

3. 修改 MicroPython

檔案: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

4. WriteMixin

就像 Read 和 Indicate 一樣,咱們也可以將 Write 的共同部分抽取出來,分為 4 個部分:

  1. 讀取 GATT Client 寫入的資料
  2. 檢查 Attribute Protocol Error
  3. 執行寫入動作的後置動作
  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
  • 當 att_err 不為 0,表示有錯誤,所以不再執行收到的命令。
  • 方法在 _parse_write_data() 結束後並沒有 return 語句。
    因為 Python 在沒有回傳語句時,會回傳 None,且在 mp_bluetooth_gatts_on_write() 裡會將 None 當作 0 來處理。
  • _check_att_error()_parse_write_data() 應由 Control Point 自行處理,因其有各自的行為,所以沒有預設值。

5. 與 IdsServer 整合

接下來將 WriteMixinIdsServer 整合:

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)

6. IDD Status Reader Control Point

咱們來製作 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:
    1. 是否有其他 Control Point 在執行
    2. CCCD 是否有正確設定
    3. GATT Client 寫入的資料是否小於 2(因 IDD Status Reader Control Point 最短的資料長度必須大於等於 2)
  • _parse_write_data() 簡單將指令印出

7. 執行

咱們使用 nRF Connect app 進行測試:

  1. GATT Client 未設定 CCCD,徑直寫入指令
    https://ithelp.ithome.com.tw/upload/images/20250823/20177799ZrtLdEYqLB.jpg

  2. IDS Server 回應 CCCD Improperly Configured
    https://ithelp.ithome.com.tw/upload/images/20250823/20177799i88YMoU7PY.jpg

  3. GATT Client 設定 CCCD 後,寫入指令
    https://ithelp.ithome.com.tw/upload/images/20250823/20177799SOijEJbSiM.jpg

  4. GATT Client 收到 IDS Server 的回應
    https://ithelp.ithome.com.tw/upload/images/20250823/201777991CUTeKN0e5.jpg

IDS Server 的紀錄:
https://ithelp.ithome.com.tw/upload/images/20250823/20177799BV42na1Eu5.png


如此,基本的 Control Point 寫入架構雛形便完成了。


上一篇
Day 22 - 怒吼吧!IDD Annunciation Status!
下一篇
Day 24 - 建立 Control Point 基礎類別
系列文
以MicroPython在ESP32上實作Insulin Delivery Service31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言