iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
Software Development

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

Day 21 - Temperature 通知 & 優先權管理

  • 分享至 

  • xImage
  •  

昨天咱們已將警示通知的基本型設計好了,接下來就是利用它製作需要輔助欄位的警示通知。

1. Temperature 通知

以下是 IDS 定義的 Temperature 通知的輔助欄位:
https://ithelp.ithome.com.tw/upload/images/20250821/20177799UlKTdWNshM.png

AuxInfo2 Temperature Flags
https://ithelp.ithome.com.tw/upload/images/20250821/20177799cbkAyY5sxY.png

AuxInfo2 Context
https://ithelp.ithome.com.tw/upload/images/20250821/20177799uo3fC8X0KI.png

咱們先直接根據以上規格來定義它的建構函數:

class TemperatureAnnunciation(core.annunc.base.BaseAnnunciation):
    def __init__(
        self,
        value: float | None,
        temperature_flags: int | None,
        context: int | None,
        lower_bound: float | None = None,
        upper_bound: float | None = None,
        *,
        flags: int | None = core.annunc.consts.ANNUNCIATION_PRESENT
        | core.annunc.consts.AUXINFO1_PRESENT
        | core.annunc.consts.AUXINFO2_PRESENT,
        id: int | None = 0,
        status: int | None = core.annunc.consts.STATUS_PENDING,
        fired_timestamp: int = -1,
        remaining_snoozed_time: int = 0,
    ):
        super().__init__(
            core.annunc.consts.TEMPERATURE,
            flags=flags,
            id=id,
            status=status,
            fired_timestamp=fired_timestamp,
            remaining_snoozed_time=remaining_snoozed_time,
        )

        self.value = value
        self.temperature_flags = temperature_flags
        self.context = context
        self.lower_bound = lower_bound
        self.upper_bound = upper_bound
  • 因 AuxInfo1 和 AuxInfo2 在 Requirement 欄位標示為 M,表示必須存在,所以 valuetemperature_flagscontext 沒有預設值。
  • 為了測試 GATT Client 的容錯能力,valuetemperature_flagscontext 還是可以被設定為 None,表示欄位不存在。
  • AuxInfo3 和 AuxInfo4 在 Requirement 欄位標示為 O,表示為可選,所以讓 lower_boundupper_bound 的預設值為 None
  • 因 AuxInfo1 和 AuxInfo2 為必選,所以讓 flags 預設包含 AUXINFO1_PRESENTAUXINFO2_PRESENT

因 Temperature 通知最多需要 4 個輔助欄位,所以要覆寫以下方法:

class TemperatureAnnunciation(core.annunc.base.BaseAnnunciation):
    def get_aux_info_1(self) -> int | None:
        if self.value is not None:
            return common.sfloat.float_to_sfloat(self.value)

        return None

    def get_aux_info_2(self) -> int | None:
        n = 0
        is_set = False

        if self.temperature_flags is not None:
            n = self.temperature_flags
            is_set = True

        if self.context is not None:
            n |= self.context << 8
            is_set = True

        return n if is_set else None

    def get_aux_info_3(self) -> int | None:
        if self.lower_bound is not None:
            return common.sfloat.float_to_sfloat(self.lower_bound)

        return None

    def get_aux_info_4(self) -> int | None:
        if self.upper_bound is not None:
            return common.sfloat.float_to_sfloat(self.upper_bound)

        return None

這幾個方法的思路都很直觀,若欄位的值不是 None,便將其轉成規格定義的格式,並回傳;否則便回傳 None,表示欄位不存在。

最後因為 Temperature 通知有自己的輔助欄位,所以必須覆寫 to_dict()from_dict()

class TemperatureAnnunciation(core.annunc.base.BaseAnnunciation):
    def to_dict(self):
        result = super().to_dict()
        result["value"] = self.value
        result["temperature_flags"] = self.temperature_flags
        result["context"] = self.context
        result["lower_bound"] = self.lower_bound
        result["upper_bound"] = self.upper_bound
        return result

    @classmethod
    def from_dict(cls, d):
        return cls(
            d["value"],
            d["temperature_flags"],
            d["context"],
            d["lower_bound"],
            d["upper_bound"],
            flags=d["flags"],
            id=d["id"],
            status=d["status"],
            fired_timestamp=d["fired_timestamp"],
            remaining_snoozed_time=d["remaining_snoozed_time"],
        )

2. 管理警示通知的優先權

IDS server 在運作期間會產生許多警示通知,而雖然可以用 IDD Annunciation Status 來讀取,但它只會傳回優先權最高的項目。不過 IDS 規格裡並沒有明文定義何謂優先權最高,而是交由廠商自行決定,所以咱們訂定幾個簡單規則:

  • 警示通知依嚴重性分為不同層級
  • 只有 StatusPendingSnoozed 的警示通知會被納入考量,也就是 Confirmed 的項目會視為不存在
  • Pending 的優先權高於 Snoozed
  • fired_timestamp 值愈大,優先權愈高
  • 比較順序是 嚴重性 > Status > fired_timestamp

根據上述規則,咱們先定義 4 種嚴重性:

# 值越小,優先權越高
_PRIORITY_ERROR = micropython.const(0)
_PRIORITY_MAINTENANCE = micropython.const(1)
_PRIORITY_WARNING = micropython.const(2)
_PRIORITY_REMINDER = micropython.const(3)

先前咱們也有用過 micropython.const(),只是都沒說明,所以這裡稍微解釋一下。根據 MicroPython 文件說明,當用到以 micropython.const() 定義的變數時,它不是以變數名來引用,而是直接使用其常數值,類似於 C/C++ 的 const#define 的作用,只是效果是否有必較好就不清楚了。

好!回來!接下來,本喵非常武斷地指派嚴重性給各警告通知:

# annunciation 的緊急程度
_ANNUNC_PRIORITY_MAP = {
    core.annunc.consts.SYSTEM_ISSUE: _PRIORITY_ERROR,
    core.annunc.consts.MECHANICAL_ISSUE: _PRIORITY_ERROR,
    core.annunc.consts.OCCLUSION_DETECTED: _PRIORITY_MAINTENANCE,
    core.annunc.consts.RESERVOIR_ISSUE: _PRIORITY_MAINTENANCE,
    core.annunc.consts.RESERVOIR_EMPTY: _PRIORITY_MAINTENANCE,
    core.annunc.consts.RESERVOIR_LOW: _PRIORITY_WARNING,
    core.annunc.consts.PRIMING_ISSUE: _PRIORITY_MAINTENANCE,
    core.annunc.consts.INFUSION_SET_INCOMPLETE: _PRIORITY_MAINTENANCE,
    core.annunc.consts.INFUSION_SET_DETACHED: _PRIORITY_MAINTENANCE,
    core.annunc.consts.POWER_SOURCE_INSUFFICIENT: _PRIORITY_MAINTENANCE,
    core.annunc.consts.BATTERY_EMPTY: _PRIORITY_MAINTENANCE,
    core.annunc.consts.BATTERY_LOW: _PRIORITY_WARNING,
    core.annunc.consts.BATTERY_MEDIUM: _PRIORITY_REMINDER,
    core.annunc.consts.BATTERY_FULL: _PRIORITY_REMINDER,
    core.annunc.consts.TEMPERATURE_OUT_OF_RANGE: _PRIORITY_MAINTENANCE,
    core.annunc.consts.AIR_PRESSURE_OUT_OF_RANGE: _PRIORITY_MAINTENANCE,
    core.annunc.consts.BOLUS_CANCELED: _PRIORITY_WARNING,
    core.annunc.consts.TBR_OVER: _PRIORITY_REMINDER,
    core.annunc.consts.TBR_CANCELED: _PRIORITY_WARNING,
    core.annunc.consts.MAX_DELIVERY: _PRIORITY_WARNING,
    core.annunc.consts.DATE_TIME_ISSUE: _PRIORITY_WARNING,
    core.annunc.consts.TEMPERATURE: _PRIORITY_REMINDER,
}

因為咱們規定「Pending 的優先權高於 Snoozed」,所以來看一下 Annunciation Status 有哪些定義:
https://ithelp.ithome.com.tw/upload/images/20250821/20177799wts7a7reyW.png

現在可以來設計如何由已觸發的警示通知中,取出最高優先權的項目了。為了方便管理警示通知,咱們設計一個類別來處理:

class AnnunciationManager:
    def get_top_priority_annunc(self) -> core.annunc.base.BaseAnnunciation | None:
        # 先比較 priority,再比較 status,若都一樣,則取最新的項目
        # 加負號是為讓愈小的 priority 和 status 愈大
        best_key = (-0xFF, -0xFF, -1)  # (priority, status, fired_timestamp)
        best_item = None

        for a in self._config.annunc_fired:
            if (
                a.status != core.annunc.consts.STATUS_PENDING
                and a.status != core.annunc.consts.STATUS_SNOOZED
            ):
                continue

            key = (
                -_ANNUNC_PRIORITY_MAP.get(a.type, _PRIORITY_REMINDER),
                -a.status,
                a.fired_timestamp,
            )
            if key > best_key:
                best_key = key
                best_item = a

        return best_item
  • 因為嚴重層級是值愈小,優先權愈高,所以會將得到的優先權取負值。
  • 因 Pending 的值是 0x33,而 Snoozed 是 0x3C,且 Pending 優先權高於 Snoozed,所以也要將 status 取負值。
  • 因為需要比較三個欄位,且比較順序是 嚴重性 > Status > fired_timestamp,所以將它們組成 tuple 來比較。但這方式會為 tuple 配置記憶體,且因這方法會在 IDD Annunciation Status 的 Read IRQ 中被呼叫,所以若想避免記憶體配置,就自行一個一個比較,如此也不須將特定值取負了。

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

尚未有邦友留言

立即登入留言