昨天咱們已將警示通知的基本型設計好了,接下來就是利用它製作需要輔助欄位的警示通知。
以下是 IDS 定義的 Temperature 通知的輔助欄位:
AuxInfo2 Temperature Flags
AuxInfo2 Context
咱們先直接根據以上規格來定義它的建構函數:
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
value、temperature_flags 和 context 沒有預設值。value、temperature_flags 和 context 還是可以被設定為 None,表示欄位不存在。lower_bound 和 upper_bound 的預設值為 None。flags 預設包含 AUXINFO1_PRESENT 和 AUXINFO2_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"],
        )
IDS server 在運作期間會產生許多警示通知,而雖然可以用 IDD Annunciation Status 來讀取,但它只會傳回優先權最高的項目。不過 IDS 規格裡並沒有明文定義何謂優先權最高,而是交由廠商自行決定,所以咱們訂定幾個簡單規則:
嚴重性分為不同層級Status 為 Pending 和 Snoozed 的警示通知會被納入考量,也就是 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 有哪些定義:
現在可以來設計如何由已觸發的警示通知中,取出最高優先權的項目了。為了方便管理警示通知,咱們設計一個類別來處理:
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
嚴重性 > Status > fired_timestamp,所以將它們組成 tuple 來比較。但這方式會為 tuple 配置記憶體,且因這方法會在 IDD Annunciation Status 的 Read IRQ 中被呼叫,所以若想避免記憶體配置,就自行一個一個比較,如此也不須將特定值取負了。