咱們已經將 BLE 相關函數都封裝好了,終於可以正式開始實作 IDS 了。
第一個目標當然是最簡單的 IDD Features,本喵會以 Insulin Delivery Service 1.0.2 為實作標準。
先來看看 IDD Features 需要那些欄位:
先前在 config.py 的 Config 類別裡,咱們用 idd_features 做為臨時的 IDD Features 的回應,現在咱們用正式的 Insulin Concentration 和 Flags 來取代它:
class Config:
def __init__(self):
self.idd_features_insulin_conc = 100
self.idd_features_flags = 0b_0000_0000_0000_0001_1110_0001
self._refresh(self.idd_features_flags)
def _refresh(self, flags):
self.is_e2e_protection_supported = bool(flags & 0x0001)
def to_dict(self) -> dict[str, str | int | tuple]:
return {
"idd_features_insulin_conc": self.idd_features_insulin_conc,
"idd_features_flags": self.idd_features_flags,
}
@classmethod
def from_dict(cls, d: dict[str, str | int | tuple]):
obj.idd_features_insulin_conc = d["idd_features_insulin_conc"]
obj.idd_features_flags = d["idd_features_flags"]
obj._refresh(obj.idd_features_flags)
咱們使用 idd_features_insulin_conc 和 idd_features_flags 分別控制 Insulin Concentration 和 Flags,而 E2E-CRC 和 E2E-Counter 皆由系統來設定。
這裡須要特別提的是 _refresh()。Flags 是一個由 24 bit 組成的結構,在程式裡,咱們是用整數來表示。以下是一部分位元所表示的意義:
因咱們以整數來儲存此結構,所以若要得到特定欄位是否被設定,須以「邏輯與」來達成。如對應以上三個欄位可以這樣判別:
self.is_e2e_protection_supported = bool(flags & 0x0001)
self.is_basal_rate_supported = bool(flags & 0x0002)
self.is_tbr_absolute_supported = bool(flags & 0x0004)
當然不一定須要將其以成員變數來儲存,完全可以將每一項都以函數形式呈現,視看官喜好、需求而定。
但請注意,idd_features_insulin_conc 的型態是 float,而 Insulin Concentration 則是 SFLOAT,所以之後在回應 GATT Client 的讀取要求時,須要將其由 float 轉為 SFLOAT。
在開始寫程式之前,咱們先來想想讀取的流程包含什麼:
次序 | 動作 | 特定 | 必要 |
---|---|---|---|
1 | 取得模型的最新資料 | Y | Y |
2 | 資料取得後,模型的後置處理 | Y | N |
3 | 更新 characteristic 的值 | N | Y |
4 | 以藍牙回應 | N | Y |
特定:characteristic 是否有其特定的行為
必要:是否 characteristic 都需要
為什麼要特別考慮「資料取得後,模型的後置處理」呢?這其實是為了 E2E-Protection 而設,目前可先無視它。
然後咱們根據上表來設計一個支援讀取的類別:
_rbuf = bytearray(20)
_rbuf_mv = memoryview(_rbuf)
class ReadMixin:
def on_read(self, value_handle: int):
n = self._build_read_rsp(_rbuf_mv)
self._after_build_tx_data()
# 將回覆資料寫入 characteristic 裡
ble.stack.gatts_write(value_handle, _rbuf_mv[:n])
def _build_read_rsp(self, buf: memoryview) -> int:
"""返回資料長度"""
raise NotImplementedError
def _after_build_tx_data(self):
pass
它的對應關係如下:
次序 | 動作 | 函數 |
---|---|---|
1 | 取得模型的最新資料 | _build_read_rsp() |
2 | 資料取得後,模型的後置處理 | _after_build_tx_data() |
3 | 更新 characteristic 的值 | gatts_write() |
4 | 以藍牙回應 | N/A |
也許看官會疑惑,為什麼不在 ble.stack.Characteristic 裡新增這些函數,而要另外創建一個類別呢?這是因為並非每個 characteristic 都必須支援讀取屬性,若將讀取相關函數放在 Characteristic 類別裡,那是否其它屬性的相關函數也應放在同一類別呢?這樣的類別是否過於龐大?是否適合管理?
接著咱們將先前已建好的類別組合成 IddFeatures:
class IddFeatures(ble.mixin.ReadMixin, ble.stack.Characteristic):
def __init__(self, config: config.Config):
ble.stack.Characteristic.__init__(self, 0x2B23, read=True)
self._config = config
def _build_read_rsp(self, buf: memoryview) -> int:
buf[0] = 0xFF # Low byte of E2E-CRC
buf[1] = 0xFF # High byte of E2E-CRC
buf[2] = 0x00 # E2E-Counter
t = common.sfloat.float_to_sfloat(self._config.idd_features_insulin_conc)
common.utils.write_uint16(buf, 3, t)
common.utils.write_uint24(buf, 5, self._config.idd_features_flags)
return 8
在 __init__()
,咱們明確呼叫 ble.stack.Characteristic.__init__()
,而不是 super().__init__()
,這是因為 MicroPython 的多重繼承與 super() 機制並不完整,在標準 Python 可正常運行的情境,在 MicroPython 卻有問題,這些留待以後討論。
在 _build_read_rsp(),咱們根據 IDS 規格將資訊填入 buf 這個 memoryview 參數,然後回傳使用的長度,讓 ReadMixin.on_read() 可以將正確的資料片段傳給 ble.stack.gatts_write()。
因為 IddFeatures 還沒支援 E2E-Protection,所以不須覆寫 _after_build_tx_data()。
接下來讓 IdsServer 使用 IddFeatures 類別:
def _build_ids(self) -> ble.stack.Service:
self._ids = ble.stack.Service(_IDS_UUID)
features = ble.ids.features.IddFeatures(_config)
self._ids.add_char(features)
return self._ids
def _ble_isr(self, event, data):
if event == _IRQ_GATTS_READ_REQUEST:
conn_handle, value_handle = data
for c in self._ids.chars:
if value_handle == c.value_handle:
c.on_read(value_handle)
return
在 _build_ids(),features 直接使用 IddFeatures 類別建立實例,而不是使用基礎類別 Characteristic 來建構。
在 _ble_isr(),咱們由註冊的 characteristics 中找到第一個符合條件的 Characteristic 物件,然後呼叫其 on_read()。若這個物件沒有此方法,直接讓程式死掉,因為若真發生這情況,完全是實作此 Characteristic 的人的鍋,咱們寶貴的記憶體空間不應該浪費在這種防護上。
當完成以上修改後,就可以來驗證。一樣用 nRF Connect app 與咱們的 IDS 伺服器相連,然後讀取 IDD Features,它應該類似如下:
在這一篇裡,本喵並沒有解釋 float 怎麼轉成 SFLAOT,欲知後事如何,且聽下回分解~