截至目前為止,IddStatusChanged 類別還遠算不上真正意義的完成,因為它的職責是將有變化的狀態主動傳送給 GATT Client,但還沒有任何程式碼對保存 IDS 狀態的 Config.idd_status_changed_flags 有任何干涉,所以第一步是先提供方法來改變 Config.idd_status_changed_flags。
IDD Status Changed 的 Flags 欄位的每個位元所代表的涵義如下:
| Flags fit Bit | Definition | Description | 
|---|---|---|
| 0 | Therapy Control State Changed | If this bit is set, the therapy control state of the Insulin Delivery Device changed. | 
| 1 | Operational State Changed | If this bit is set, the operational state of the Insulin Delivery Device changed. | 
| 2 | Reservoir Status Changed | If this bit is set, the status of the insulin reservoir changed (caused by a reservoir change or the delivery of insulin). | 
| 3 | Annunciation Status Changed | If this bit is set, a new annunciation was created by the Server application. | 
| 4 | Total Daily Insulin Status Changed | If this bit is set, the total daily insulin amount changed due to a bolus or basal delivery. The bit shall be set at the end of an effective delivery. | 
| 5 | Active Basal Rate Status Changed | If this bit is set, the current basal rate changed due to a new basal rate value (e.g., caused by a changed basal rate profile, reaching of a time block with another basal rate value or by a TBR). | 
| 6 | Active Bolus Status Changed | If this bit is set, a new bolus was initiated or the status of current Active Bolus changed. | 
| 7 | History Event Recorded | If this bit is set, a new event has been recorded in the history. | 
| All other bits | RFU | None | 
當 IDS server 的狀態改變時,Flags 相應的位元便會被設定。而無論這個位元是由 0 轉 1,或由 1 轉 0,系統都會送出一個 IDD Status Changed 的 Indicate 給 GATT Client。根據這個特性,程式可以這樣設計:
class IddStatusChanged:
    def _add_flags(self, flags: int) -> bool:
        """設定 flags 指定的欄位,若有欄位被改變,則回傳 True;否則 False。"""
        old = self._config.idd_status_changed_flags
        self._config.idd_status_changed_flags |= flags
        return old != self._config.idd_status_changed_flags
有了設定功能後,當然會有對應的重設功能:
def _reset_flags(self, flags: int) -> bool:
    """重設 flags 指定的欄位,若有欄位被改變,則回傳 True;否則 False。"""
    old = self._config.idd_status_changed_flags
    self._config.idd_status_changed_flags &= ~flags
    return old != self._config.idd_status_changed_flags
_add_flags() 和 _reset_flags() 已經完成了,但問題是其它部件怎麼使用它們呢?會改變狀態的時機是散佈在 IDS 系統的各處,如:
要讓其它組件直接呼叫 IddStatusChanged 類別的成員方法嗎?如若目前運作的是支援 E2E-Protection 的 E2EIddStatusChanged 呢?
有許多方法可以解決此問題,比如簡單地,定義兩個全域變數,讓它們儲存相關函數,以後需要改變狀態時,都藉由這些全域變數來呼叫相應函數:
# server.py
class IdsServer:
    def __init__(self):
        ids_var.set_add_status_changed_fn(self._add_status_changed)
        ids_var.set_reset_status_changed_fn(self._reset_status_changed)
# ids_var.py
def set_add_status_changed_fn(fn):
    global fn_add_status_changed
    fn_add_status_changed = fn
def set_reset_status_changed_fn(fn):
    global fn_reset_status_changed
    fn_reset_status_changed = fn
# some_module.py
def some_fn():
    ids_var.fn_add_status_changed(ACTIVE_BOLUS_STATUS_CHANGED)
這樣姑且是不與實際的函數有直接相依,若以後想要使用新的函數,也可以輕易替換。不過,為了更好解耦合與擴充,咱們不使用此模式,而是使用 Event Bus。
Event Bus 的基本概念如下:
_subscribers: dict[int, list] = None
def subscribe(event: int, handler):
    global _subscribers
    if not _subscribers:
        _subscribers = {}
    _subscribers.setdefault(event, []).append(handler)
def publish(event: int, *args):
    for handler in _subscribers.get(event, ()):
        handler(*args)
_subscribers 是一個字典,每個項目都是由事件和處理事件的函數的串列組成:
subscribe() 會由對特定事件感興趣的組件呼叫,將處裡事件的函數做為參數傳入。
publish() 則由觸發事件的組件呼叫,將事件發生時的相關資訊傳入此函數。
首先,咱們先定義 IDD Status Changed 會使用到的 Event Bus 事件:
# args:
# flags: int,欲增加的位元組合,由 IDD Status Changed 的 Flags 所規定。
EVENT_IDD_STATUS_ADDING = 0
# args:
# flags: int,欲重設的位元組合,由 IDD Status Changed 的 Flags 所規定。
EVENT_IDD_STATUS_RESETTING = 1
然後定義 IDD Status Changed 所需的 Flags 位元:
# IDD Status Changed
THERAPY_CONTROL_STATE_CHANGED = 0x0001
OPERATIONAL_STATE_CHANGED = 0x0002
RESERVOIR_STATUS_CHANGED = 0x0004
ANNUNCIATION_STATUS_CHANGED = 0x0008
TOTAL_DAILY_INSULIN_STATUS_CHANGED = 0x0010
ACTIVE_BASAL_RATE_STATUS_CHANGED = 0x0020
ACTIVE_BOLUS_STATUS_CHANGED = 0x0040
HISTORY_EVENT_RECORDED = 0x0080
有了 Event Bus 事件後,就是讓 IddStatusChanged 類別訂閱這些事件:
class IddStatusChanged:
    def __init__(self, config: config.Config):
        common.eventbus.subscribe(
            core.events.EVENT_IDD_STATUS_ADDING, self._on_status_adding
        )
        common.eventbus.subscribe(
            core.events.EVENT_IDD_STATUS_RESETTING, self._on_status_resetting
        )
    def _on_status_adding(self, flags: int):
        if self._add_flags(flags):
            ble.stack.BleTxScheduler().add(
                ble.stack.ACT_INDICATE, self.send_data, self.value_handle, None
            )
    def _on_status_resetting(self, flags: int):
        if self._reset_flags(flags):
            ble.stack.BleTxScheduler().add(
                ble.stack.ACT_INDICATE, self.send_data, self.value_handle, None
            )
咱們讓 IddStatusChanged 類別訂閱事件 EVENT_IDD_STATUS_ADDING 和 EVENT_IDD_STATUS_RESETTING,並且分別指派 _on_status_adding() 和 _on_status_resetting() 來處理事件。
這兩個方法裡只是直接呼叫先前已做好的 _add_flags() 和 _reset_flags(),然後利用 第 14 天 所做的排程器 將資料排進傳送的佇列。
咱們可以稍微改一下 第 15 天 所做的計時器 來試驗:
_timestamp = 0
def _on_timer(timer):
    # 遞增系統計數器,然後由系統安排主執行緒執行。
    global _timestamp
    _timestamp += 1
    micropython.schedule(_on_timestamp_changed, _timestamp)
def _on_timestamp_changed(timestamp: int):
    """此函數只能在主執行緒被呼叫,否則將有同步問題"""
    if timestamp % 10 == 0:
        common.logger.write("Timestamp: " + str(timestamp))
    if timestamp == 10:
        common.eventbus.publish(
            core.events.EVENT_IDD_STATUS_ADDING, core.events.ACTIVE_BOLUS_STATUS_CHANGED
        )
    elif timestamp == 15:
        common.eventbus.publish(
            core.events.EVENT_IDD_STATUS_RESETTING,
            core.events.ACTIVE_BOLUS_STATUS_CHANGED,
        )
當 IDS server 與 nRF Connect app 建立連線,並且正確設定 IDD Status Changed 的 CCCD 後,便可以在系統計數器為 10 和 15 秒時,收到相應的 Indicate。
至此,IDD Status Changed 就算是完成了,但還有一個問題:
若同一時間段,多次送出
EVENT_IDD_STATUS_ADDING或EVENT_IDD_STATUS_RESETTING,將會送出多個具相同值的 Indicate。
比如:
if timestamp == 10:
    common.eventbus.publish(
        core.events.EVENT_IDD_STATUS_ADDING, core.events.ACTIVE_BOLUS_STATUS_CHANGED
    )
    common.eventbus.publish(
        core.events.EVENT_IDD_STATUS_ADDING,
        core.events.ACTIVE_BASAL_RATE_STATUS_CHANGED,
    )
那麼將會收到 2 個 Flags 欄位為 0x0060 的 Indicate,這是因為 common.eventbus.publish() 並不是馬上將 Indicate 送出。要解決這問題,可以簡單地設立一個旗標 _scheduled:
class IddStatusChanged:
    def __init__(self, config: config.Config):
        # 因 Status Changed 被送出前可能會被多次設定,以此來避免重複送出相同值。
        self._scheduled = False
    def send_data(self, conn_handle: int | None, value_handle: int, arg):
        successful = super().send_data(conn_handle, value_handle, arg)
        self._scheduled = False
        return successful
    def _on_status_adding(self, flags: int):
        if self._add_flags(flags) and not self._scheduled:
            self._scheduled = True
            ble.stack.BleTxScheduler().add(
                ble.stack.ACT_INDICATE, self.send_data, self.value_handle, None
            )
    def _on_status_resetting(self, flags: int):
        if self._reset_flags(flags) and not self._scheduled:
            self._scheduled = True
            ble.stack.BleTxScheduler().add(
                ble.stack.ACT_INDICATE, self.send_data, self.value_handle, None
            )
不過這設計是否符合需求就不一定了。比如上面例子,也許需求是在第 10 秒時,先送出 ACTIVE_BOLUS_STATUS_CHANGED,然後再送出 ACTIVE_BASAL_RATE_STATUS_CHANGED,共 2 個 Indicate,那麼這設計就不符合要求了。
IDS 規格書並沒有說不能這樣,只是一般為了省電,會將多個 EVENT_IDD_STATUS_ADDING 和 EVENT_IDD_STATUS_RESETTING 合併,但一切還是要依需求為主。