在先前 IDD Status Changed 的說明和測試中,一直沒有談到對於 Indicate 和 Notify 來說很重要的一件事,那就是 CCCD(Client Characteristic Configuration Descriptor)。所以在繼續開發 IDS 前,咱們先釐清 CCCD 是做什麼用?目前的 IDS server 又有什麼問題吧!
藍牙規格書規定,GATT Client 在接收某個 characteristic 的 Indicate 或 Notify 前,必須先正確設定此 characteristic 的 CCCD:
在昨天咱們用 nRF Connect app 與 IDS server 進行測試時,遇到一些奇怪的現象,整個測試步驟如下:
問題:
這行為和藍牙規格書並不一致,但這似乎是 MicroPython 在 ESP32 上使用的藍牙堆疊 NimBLE 的設計。
另一個問題是,若希望能在 app 寫入 CCCD 時收到相關通知,那目前最新的 MicroPython 1.26.0 還不支援。這個問題比前述問題還嚴重,因為若希望 GATT Server 只有在 CCCD 被正確設定時才送出 Indicate/Notify,那 GATT Server 勢必須要知道 CCCD 何時被設定。對於 IDS 來說,這功能是必須的,否則 E2E-Counter 無法被正確遞增。因為即使 GATT Client 沒有正確設定 CCCD,呼叫 MicroPython 的 bluetooth.BLE.gatts_indicate()
和 bluetooth.BLE.gatts_notify()
也不會有失敗通知。
所以為了至少能收到 CCCD 的寫入通知,咱們會修改 MicroPython,重新建置 ESP32 的 firmware。
以下指令都是在 Ubuntu 所執行。
sudo apt update
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
git clone https://github.com/micropython/micropython.git
由 micropython/ports/esp32/README.md
可以查到 MicroPython 支援的 ESP-IDF 版本。如本喵所下載的 MicroPython 支援到 ESP-IDF v5.4.2:
git clone -b v5.4.2 --recursive https://github.com/espressif/esp-idf.git
善意提醒,在使用 ESP-IDF v5.4.1 時發現,藍牙斷線中斷有時會收不到,所以若使用上有什麼問題,建議可以使用 ESP-IDF v5.4。
cd esp-idf
只須執行一次,除非要安裝其他平台
./install.sh esp32
每個 shell 啟用後執行一次
source export.sh
只須執行一次,除非有修改過此工具
cd ~/micropython/
make -C mpy-cross
檔案:micropython/extmod/modbluetooth.h
找到字串 MP_BLUETOOTH_IRQ_PASSKEY_ACTION
,在其下方新增:
#define MP_BLUETOOTH_IRQ_SUBSCRIBE (32)
這是 CCCD 被寫入的事件代碼,當然,完全可以使用不重複的任意數。
檔案:micropython/extmod/modbluetooth.h
找到字串 mp_bluetooth_gatts_on_write
,在其上方新增:
void mp_bluetooth_gap_on_subscribe(uint16_t conn_handle, uint16_t value_handle, uint8_t reason, uint8_t cur_notify, uint8_t cur_indicate);
這是要傳給 MicroPython 程式的 CCCD 寫入事件的內容。
檔案:micropython/extmod/modbluetooth.c
找到字串 mp_bluetooth_gap_on_passkey_action
,在其下方,與 mp_bluetooth_gatts_on_write
字串上方間新增:
void mp_bluetooth_gap_on_subscribe(uint16_t conn_handle, uint16_t value_handle, uint8_t reason, uint8_t cur_notify, uint8_t cur_indicate) {
mp_int_t args[] = { conn_handle, value_handle, reason, cur_notify, cur_indicate };
invoke_irq_handler(MP_BLUETOOTH_IRQ_SUBSCRIBE, args, 5, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0);
}
如以下位置:
看官應該會疑惑,為什麼要用那麼繞口的說法,直接說在 mp_bluetooth_gatts_on_write
上方新增不就好了嗎?其實這不是故意要讓人迷惑,它的原因和「為什麼 ESP32 平台上,在中斷函數裡配置記憶體不會有任何錯誤訊息」可能有關。
在 modbluetooth.c
裡,其實有 2 個 mp_bluetooth_gatts_on_write()
的實作,除了先前展示的部分外,還有一個如下:
注意到函數上方的註解了嗎?
Callbacks are called in interrupt context (i.e. can't allocate)
因為 ESP32 有啟用 MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
,所以必須把 mp_bluetooth_gap_on_subscribe()
放在對的位置。
而這也可能是為什麼在 ESP32 上,即使於中斷函數裡配置記憶體,也不會有異常的原因。但目前咱們還沒有 100% 把握,所以還是暫時儘量照著文件來做。雖然有句俗語:
程式的註解(文件),騙人的鬼 Σ(*゚д゚ノ)ノ(大誤)~
檔案:micropython/extmod/nimble/modbluetooth_nimble.c
找到字串 case BLE_GAP_EVENT_SUBSCRIBE
,在其下方新增:
mp_bluetooth_gap_on_subscribe(event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason, event->subscribe.cur_notify, event->subscribe.cur_indicate);
cd ports/esp32
只須執行一次
make submodules
make
編譯完成後,以 Thonny 將 micropython/ports/esp32/build-ESP32_GENERIC/firmware.bin
燒錄到板子上,重啟裝置後即可使用新 firmware:
如本喵目前編譯的版本為 MicroPython v1.27.0-preview,看官當然可以在下載 MicroPython 時,指定 1.26.0 或其他想要的版本。
雖然是想一氣呵成,但 ... 內容好像有點太多了 ...
還是明天再繼續好了 ∑( ̄□ ̄;)