iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Software Development

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

Day 20 - 警告!警告!警告!基礎 Annunciation!

  • 分享至 

  • xImage
  •  

對於安全性層級較高的裝置來說,通常配有警示通知系統,比如電池快耗盡、異常、或需使用者注意、介入時,裝置便會發出訊號來通知使用者,而 IDD Annunciation Status 就是負責通知 GATT Client 警示資訊,好讓 GATT Client 可以通報使用者。

1. 資料欄位

IDD Annunciation Status 的資料欄位是根據警示類型而變動的,最短為 1 byte,表示沒有任何警示通知存在;最長可達 19 bytes。
https://ithelp.ithome.com.tw/upload/images/20250820/20177799YpWzhVDNw3.png

欄位 說明
Flags 指示哪些欄位存在:Annunciation Present (對應 ID、Type、Status 欄位是否存在),AuxInfo1 Present~AuxInfo5 Present(對應 AuxInfo1~AuxInfo5 欄位是否存在)
Annunciation Instance ID 警示通知獨一無二的編號
Annunciation Type 警示通知種類,如裝置須維修、藥劑耗盡等
Annunciation Status 警示通知狀態,如待處理、睡眠中、已確認等
AuxInfo1~AuxInfo5 警示通知所需的輔助欄位

IDS 雖然定義了許多警示通知類型,但只有對 Temperature 通知給出了定義,也就是說,其它警示通知需要傳輸什麼資訊是由廠商自行定義,因此咱們只會設計基本的警示通知和 Temperature 通知。

1-1. 基礎警示通知

首先來看看基本的警示通知需要哪些欄位。雖然一個最小的警示通知就是 Flags 為 0,表示沒有其他欄位存在,但這情況只出現在系統中沒有任何警示通知時,所以咱們會假設基礎警示通知中至少有 Annunciation 相關欄位,也就是 ID、Type 和 Status 這三個欄位都存在:

class BaseAnnunciation:
    def __init__(
        self,
        type: int | None,
        *,
        flags: int | None = core.annunc.consts.ANNUNCIATION_PRESENT,
        id: int | None = 0,
        status: int | None = core.annunc.consts.STATUS_PENDING,
        fired_timestamp: int = -1,
        remaining_snoozed_time: int = 0,
    ):
        self.flags = flags
        self.id = id
        self.type = type
        self.status = status

        # 被觸發時的系統計數器值(從系統啟動開始經過的秒數),
        # 當系統計數器為此值時觸發,
        # 小於 0 不觸發。
        self.fired_timestamp = fired_timestamp

        self.remaining_snoozed_time = remaining_snoozed_time
  • flagsidtypestatus 都是 IDS 所明定,一般情況下,只須傳入 type 就好,因為 id 欄位會由系統給予,即使指定了 id,也會被重設。
  • fired_timestamp 表示當系統計數器為指定的數值時會觸發警示通知。這是為了測試而設,讓咱們可以藉由外部檔案來觸發警示通知。
  • 因為警示通知可以被設為睡眠狀態,所以 remaining_snoozed_time 表示警示通知還可以睡眠多少時間。

雖然基礎警示通知不需要輔助欄位,但 Temperature 通知和廠商自訂的警示通知會需要,所以在 BaseAnnunciation 增加以下方法,然後需要輔助欄位的子類別可以覆寫這些方法:

class BaseAnnunciation:
    def get_aux_info_1(self) -> int | None:
        return None

    def get_aux_info_2(self) -> int | None:
        return None

    def get_aux_info_3(self) -> int | None:
        return None

    def get_aux_info_4(self) -> int | None:
        return None

    def get_aux_info_5(self) -> int | None:
        return None

而因為警示通知有已確認、待辦中和已睡眠等狀態,所以提供以下輔助方法:

class BaseAnnunciation:
    def confirm(self):
        self.status = core.annunc.consts.STATUS_CONFIRMED

    def pend(self):
        self.status = core.annunc.consts.STATUS_PENDING

    def snooze(self, snoozing_time: int):
        self.status = core.annunc.consts.STATUS_SNOOZED
        self.remaining_snoozed_time = snoozing_time

然後為了能與 JSON 互相轉換,定義相關函數:

class BaseAnnunciation:
    def to_dict(self):
        return {
            "flags": self.flags,
            "id": self.id,
            "type": self.type,
            "status": self.status,
            "fired_timestamp": self.fired_timestamp,
            "remaining_snoozed_time": self.remaining_snoozed_time,
        }

    @classmethod
    def from_dict(cls, d):
        return cls(
            d["type"],
            flags=d["flags"],
            id=d["id"],
            status=d["status"],
            fired_timestamp=d["fired_timestamp"],
            remaining_snoozed_time=d["remaining_snoozed_time"],
        )

要注意,這兩個方法 to_dict()from_dict() 是屬於 BaseAnnunciation 類別,不像先前的 IDD Status 和 IDD Status Changed 等是寫在 Config 類別裡,所以咱們必須在 Config 類別增加相關成員變數:

class Config:
    def __init__(self):
        # Unit: seconds
        self.annunc_snoozing_time = 180

        self.annunc_ready: list[core.annunc.base.BaseAnnunciation] = []
        self.annunc_fired: list[core.annunc.base.BaseAnnunciation] = []
  • annunc_snoozing_time 表示當使用者下達 Snooze 指令時,要讓警示通知睡眠多少秒。不過 IDS 在下達這指令時是以分計,會以秒來儲存只是為了方便程式計時。
  • annunc_ready 表示還未觸發的新警示通知。
  • annunc_fired 表示已觸發的警示通知。

為了儲存成 JSON,咱們將資料轉成字典形式:

class Config:
    def to_dict(self):
        return {
            "annunc_snoozing_time": self.annunc_snoozing_time,
            "annunc_ready": [x.to_dict() for x in self.annunc_ready],
            "annunc_fired": [x.to_dict() for x in self.annunc_fired],
        }

接著提供方法將 JSON 還原為物件:

class Config:
    @classmethod
    def from_dict(cls, d: dict):
        obj = cls()

        obj.annunc_snoozing_time = d["annunc_snoozing_time"]

        cls._restore_annunc(obj.annunc_ready.append, d["annunc_ready"])
        obj.annunc_ready.sort(
            key=lambda x: x.fired_timestamp if x.fired_timestamp is not None else -1
        )

        cls._restore_annunc(obj.annunc_fired.append, d["annunc_fired"])

        return obj

    @staticmethod
    def _restore_annunc(fn, d):
        for x in d:
            if x["type"] == core.annunc.consts.TEMPERATURE:
                annunc = core.annunc.temperature.TemperatureAnnunciation.from_dict(x)
            else:
                annunc = core.annunc.base.BaseAnnunciation.from_dict(x)

            fn(annunc)
  • 實際將 JSON 還原為物件的是 _restore_annunc()
  • _restore_annunc(),會根據 type 來建置警示通知物件
  • from_dict()annunc_ready 建置完後,必須依 fired_timestamp 來排序,以方便日後取用。需要自行執行排序是因為設定檔是由人所撰寫,難保不會出錯。

基礎警示通知完成了,但咱們的任務還有 Temperature 通知和管理警示通知的設計,這些就待下回分解吧!


上一篇
Day 19 - 傳輸 IDD Status & 避免浮點數誤差
下一篇
Day 21 - Temperature 通知 & 優先權管理
系列文
以MicroPython在ESP32上實作Insulin Delivery Service31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言