YiBi Dev Docs
架構決策 (ADR)

臨時內容即時下發機制(on-demand content push)— offline-first 下的低延遲下發選項

ACCEPTED(2026-06-23,deciders: howie,經 /spectra-discuss:採方案 C(adaptive pull) 為現行方向(change 0034-on-demand-adaptive-pull);B(BLE 旁路)列為後續唯一 push 候選, 啟動前須與晶片廠商討論並完成電池 spike。見 Decision 段。本 ADR 同時更新了 LANGUAGE.md 「雲端不主動 push」立場的適用邊界(見 Context vocabulary note 與下方 Consequences)。

Context

問題:offline-first 下臨時內容的下發延遲

ADR-0010 將裝置通訊改為 HTTPS event-driven + offline-first:裝置長時間閒置時停留於 淺睡眠(Light Sleep,感應模式)——NFC 低工作週期輪詢、WiFi off、主 CPU light-sleep; 這是 NFC 可觸發的待機狀態,其基底電流為待機壽命的最大項(見 docs/odm/06-power-state-machine.html 狀態2)。Deep Sleep(<20 μA)僅由手動長按進入、 NFC 全關、只有電源鍵與 RTC 鬧鐘可喚醒,並非日常閒置狀態。裝置只在 wake cycle (NFC scan / 充電 / RTC safety-net)接觸雲端。雲端→裝置的唯一指令通道是 POST /events response 帶回的 pending_actions(純 pull,無 push;見 docs/odm/01-device-communication-spec.md §3.1)。

這帶來一個固有延遲缺口:

  • 小孩用 NFC 觸發、正在聽已快取的故事時,裝置不會中途回頭 poll。
  • 此時爸媽臨時從 App 產生一則新故事,後端只能把它排成一個 content_refresh / content_preload pending action 靜靜等著
  • 由於後端無任何 push 能力,該 action 要到裝置「下一次接觸雲端」才會被取走。

下一次接觸雲端的時間,取下列最早發生者:

觸發路徑何時延遲
每日 safety-net wake(每天固定 UTC slot,±15 分鐘)每天 1 次保證上限 ~24h(最差約 24h15m)
下一次符合 wake decision tree 條件的 NFC scan不確定通常遠快於 24h,但無保證

若小孩接下來放的都是已快取內容、且 last_checkin_at 未滿 24h、config 未過期、 NVS 無 pending action,則每次 NFC scan 都直接放快取、完全不連雲端,新故事 要到隔天 safety-net slot 才會被發現。保證最壞延遲 ≈ 24 小時。

限制:為何「真 push」對這台裝置特別難

裝置是 WiFi-only,且在任一睡眠狀態(淺睡眠 / Deep Sleep / Locked-Sleep)WiFi 射頻 皆完全關閉(見 power-state spec),封包進不來。 手機能收 APNS/FCM 是因為蜂巢式 modem 永遠在低功耗 paging 模式聽基地台; WiFi-only 沉睡裝置沒有 FCM 的等價物。任何 server push 都必須先解決 「如何叫醒一個射頻關閉的裝置」,而這正是 ADR-0010 為了電池壽命刻意排除的方向 (MQTT 常連線 modem-sleep ~20 mA → 2000 mAh 約 4 天,達不到「數週至月」目標)。

這是在重啟一個已固化的立場(vocabulary drift)

「雲端不主動 push」目前已寫進專案共享詞彙,本 ADR 等於回頭挑戰它,必須誠實標註:

  • docs/openspec/LANGUAGE.md"config push" 列為 avoid:「裝置為 Device-initiated,雲端不主動 push」。
  • 同檔 Scheduled wake 條目亦 avoid 雲端 push:「雲端 push 網路 worst case 無法保證精度;Scheduled wake 完全相容 offline-first 模型」。

Vocabulary note:若本 ADR 最終採納任何形式的 push,須同步更新 LANGUAGE.md 的 "config push" / 雲端 push 條目(從「一律 avoid」改為「限定情境下允許」), 否則文件與行為會再次漂移。

Decision

裁決(2026-06-23,deciders: howie,經 /spectra-discuss):採方案 C(adaptive pull)為現行方向; B(BLE 旁路)列為後續唯一 push 候選;A 不採為後續路徑;D 否決。

  1. 現在做 C(adaptive pull):純 pull、零架構風險、相容 offline-first,把保證最壞延遲 ~24h 壓到 ~1–2h。已形式化為 change 0034-on-demand-adaptive-pull(proposal/design/specs/ tasks/testplan 齊備、spectra validate 通過)。後續以 /spectra-apply 實作。

  2. B(BLE 旁路)為後續唯一 push 候選:當產品確認需要「battery 待機也能近即時下發」時走 B (尤其 B2 裝置常駐 BLE advertising),因為只有 B2 能在不充電、不常開 WiFi 下被觸達——A 的 WSS 僅充電時可開,做不到。A 不作為後續 push 路徑保留。

  3. B 啟動的前置條件(go/no-go gate,全部達成才進 /spectra-propose):

    • 須與晶片廠商討論:RTL8730EAH(或最終選定 SoC)維持 BLE advertising 的實際平均電流、 advertiser-only 角色可行性、BLE stack / 配對的韌體成本——這些須由 ODM / 晶片廠商確認,不能只憑估算。
    • 完成電池 spike 實測(淺睡眠基底 + BLE advertising + WiFi 偶發喚醒的合成待機電流)。
    • 產品定出可接受的最壞下發延遲與「分層體驗(充電即時 / 待機延遲)」是否可接受。
    • B2 要擺脫「手機在場」假設需專用家用閘道——須先決定是否納入硬體路線。
  4. D(維持現狀)否決:~24h 體驗對「爸媽臨時做故事」不可接受。

詞彙影響:C 仍是 pull,config push / 雲端 pushavoid 立場不變;但 C 重新定義了 Safety-net wake(LANGUAGE.md 現為「02:00–04:00 本地單次」→ C 改「日間多 slot」), 0034 apply 落地時須同步更新 LANGUAGE.md 該條目。未來若實際採 B,再翻 config push 條目。

關鍵守則:不要為了 push 把整台機器拉回常連線——那會直接推翻 ADR-0010 的 電池決策。任何 push 都應綁定充電狀態,或走 deep-sleep 仍醒著的旁路。

Alternatives Considered

方案 C:Adaptive pull(不做 push,把 pull 變聰明)— 建議優先

零架構風險、最快落地,把最壞延遲從 ~24h 壓到分鐘~小時級:

  • adaptive safety-net:每天 1 次 → 清醒時段(如本地 7–21 時)每 1–2h 一次, 半夜不醒。延遲降到 ~1–2h,電池僅多花少量。
  • 調小 content_expiry_days:讓 NFC wake decision tree 條件 3 更易命中, 放卡時順便連雲端(0 = 每次 NFC 都連,最即時但最耗電)。
  • 產品流程對齊:讓「臨時故事」對應一張未快取的新內容 → 命中 decision tree 條件 1,放卡即拉,接近即時。
  • 代償:仍非真即時;半夜產生的內容隔天才到;adaptive 排程增加平均喚醒次數需量測電池影響。

方案 A:持久連線(WSS)—「真 push」,僅建議充電時開

裝置與後端維持長連線,後端有東西直接寫下來。

  • 省電技巧:WiFi modem-sleep + DTIM(每 ~100ms–1s 醒來看 AP buffer), push 延遲 < 1s,平均電流 ~1–5 mA → 電池由「月」掉到「天~週」。 注意:1–5 mA 是樂觀的實驗室基準;家用網路常見 broadcast / multicast 風暴會把 平均電流推到 ~10–20 mA,須在實際家庭網路環境量測驗證。
  • 僅在充電/插電時開:晚上放上充電座 → 開一條 WSS → 後端把累積的 content_refresh 全部推下來 → 早上即有新故事。
  • 部分複用既有基礎設施:裝置端已有 WSS client(live chat 用)可複用,但既有通道是 暫態、使用者觸發;方案 A 需常駐長連線,等於把通道性質從暫態改為常駐,後端的 presence registry / pub-sub 路由 / connection gateway 屬重大升級(見下表),非單純複用。 離線佇列語意已由現有 pending_actions 提供。
  • 代償:電池上不可在 battery + idle 常開;需後端長連線基礎設施(見下表)。

若採方案 A,後端需補:

元件作用常見實作
Presence / connection registry記錄 device X 連在哪個 gateway 節點Redis
Pub/Sub 路由「device X 有新內容」送到持有連線的節點Redis Pub/Sub / NATS
Connection gateway撐住 N 條長連線、idle ping/pong自寫 WS gateway
離線佇列 + QoS裝置離線時存著、重連補送既有 pending_actions

方案 B:BLE 旁路喚醒 — battery + idle 也能 push,電池代償低–中、主成本在韌體

用「比 WiFi 省電、deep sleep 仍醒著」的 BLE 副通道收「快醒來」訊號。

兩個變體:

  • B1(手機中繼):家長 App 永遠可被 FCM/APNS 觸達 → 後端 push 到手機 → 手機用 BLE 叫醒同房間裝置 → 裝置才開 WiFi 拉 pending_actions功耗角色須分清:裝置端應僅作 BLE advertiser(被動廣播,低功耗); BLE scanning 是高收信電流操作,不可放在電池裝置常駐——scanning / initiator 角色 交給手機(或閘道)。需手機在家、在附近。
  • B2(裝置常駐 BLE advertising):裝置自身常駐低功耗 BLE advertising 作為 「可被觸達通道」,由鄰近手機 / 家用閘道經 BLE 推喚醒提示。相對方案 A 的優勢是 battery + idle 也能近即時 push(A 的 WSS 只能充電時開)。 注意:B2 仍需鄰近有一個聯網中繼(手機或家用閘道)來偵測廣播並送出喚醒—— 裝置睡眠時 WiFi 全關,無法自行收網路 push。目前 MVP 硬體生態不含家用閘道, 故 B2 與 B1 同樣依賴「手機在家、在附近」;唯有導入專用固定閘道後,B2 才能擺脫手機在場假設。

電池代償重估(先前評為「中」,依韌體側估算下修為「低–中」)

殺死 ADR-0010「常連線」的根本原因是電池;但 BLE keep-alive 比 WiFi keep-alive 省約一個數量級。以 RTL8730EAH(候選晶片;ADR-0010 編註晶片型號待定,本估算以此 Realtek AmebaSmart 系列為據,最終以選定型號實測為準)維持 BLE advertising、經優化(大核 A32 沉睡、 廣播間隔設為 1 秒、降低 Tx Power)後,平均電流通常可壓在**數百 µA(甚至更低)**級別。 代入 2000 mAh 電池估算:

常駐通道平均電流2000 mAh 估算續航與「數週至月」目標
Deep sleep(地板參考;非日常 idle,日常 idle 為淺睡眠,電流更高)~10 µA以年計(受自放電上限)
BLE advertising(B2 增量,僅 BLE)~300 µA~9 個月(僅 BLE 上限)達標(須加基底)
B2 實際待機 = 淺睡眠基底(NFC 輪詢 + CPU light-sleep)+ BLE淺睡眠基底 + ~300 µA低於 9 個月(待 spike 量測合成電流)須實測確認
WiFi modem-sleep keep-alive(方案 A on battery)1–5 mA天~週不達標
MQTT 常連線(ADR-0010 已否決)~20 mA~4 天不達標

數值為韌體側估算,go/no-go 前須 spike 實測 RTL8730EAH 於目標廣播間隔 / Tx Power 下的合成待機電流 =(淺睡眠基底,含 NFC 輪詢)+ BLE advertising +(WiFi 偶發喚醒), 不可只憑此表的 BLE 增量逕行採納;BLE-only 的 ~9 個月為上限,實際續航更低。

  • 代償(重估後):
    • 電池:低–中(月級續航落在目標內,不再是否決理由)。
    • 主成本在工程而非電池:新增韌體 BLE stack、配對 / 安全(避免任意鄰近裝置觸發 喚醒)。B1 與 B2 皆需「手機在家、在附近」假設(MVP 無家用閘道;B2 僅在導入專用閘道後 才能擺脫此依賴)。對「家用兒童故事機」情境手機在場尚合理,但仍屬重投資。

註:本段下修了先前「電池代償=中、成本最高」的評估。此為記錄性重估,非裁決; 若團隊最終採 B,仍須完成上述 spike 實測並於 /spectra-discuss 收斂。

方案 D:維持現狀(不改)— 基準

接受 ~24h 最壞延遲,依賴 NFC scan 自然觸發。

  • 好:完全不動架構,電池最佳。
  • 壞:爸媽臨時內容體驗差(「我做了故事為什麼機器沒有」),客訴風險。

Consequences

好(採 C,現行方向)

  • 大幅縮短臨時內容下發延遲(~24h → ~1–2h),提升「爸媽即時產生故事」的產品體驗。
  • 幾乎零架構風險、純 pull、相容 offline-first,不動 config push 詞彙立場。

壞 / 代償

  • C 的 adaptive 多喚醒以電池為代價,0034 上線前須 spike 量測;不可推翻 ADR-0010。
  • C 重定義 Safety-net wake,0034 apply 時須同步更新 LANGUAGE.md(見 Decision)。
  • 後續方案 B 將引入韌體(BLE stack / 配對)複雜度與晶片廠商協作成本;採 B 時須翻 LANGUAGE.md「雲端不主動 push」詞彙。

中性

  • 現有 pending_actions 離線佇列在所有方案中都可複用,不需重做下發語意。
  • 決策需跨韌體(ODM)、後端、產品三方,屬跨團隊決策,適合先 discuss 收斂再 propose。

待決問題(方案 B go/no-go 前置;C 已裁決採用)

方案 C 已裁決採用(見 Decision),下列為未來啟動方案 B 前必須回答的前置條件:

  1. 須與晶片廠商 / ODM 討論:RTL8730EAH(或最終選定 SoC)維持 BLE advertising 的實際平均電流、 advertiser-only(裝置不做高耗電 scanning)角色可行性、BLE stack + 配對的韌體開發成本與時程。 此為 B 的硬體可行性根本,須由晶片廠商確認,不得只憑本 ADR 估算。
  2. RTL8730EAH BLE advertising 的電池 spike 實測(淺睡眠基底 + BLE + WiFi 偶發喚醒的合成待機電流)。
  3. 產品可接受的最壞下發延遲?以及「充電時即時 / 待機延遲」分層體驗能否被產品接受?
  4. B2 擺脫「手機在場」假設需專用家用閘道——是否納入硬體路線?
  5. 採 B 時須同步把 LANGUAGE.md config push / 雲端 push 從「一律 avoid」改為「限定情境允許」。

On this page