韌體變更紀錄機制
#v1.1.15(雲端發布,現場 OTA 待驗)— 2026-11-02 — 首次設定狀態與離線直連
- 新設備及恢復原廠保持首次設定狀態,不自動連線 MQTT、雲端或外部 NTP。單純儲存與重啟不啟用服務。
- 設定頁「完成設定並啟用」經管理員授權並持久化成功後由韌體安排重啟;USB
net activate同樣保存並確認網路試用設定。 - 舊有效設定缺少新欄位時遷移為正常運行。192.168.50.50 可作正式現場 IP,不以 IP 判定是否首次設定。
- 靜態無閘道時允許同網段數字 IP 的 MQTT/TCP;外部主機與需 DNS 的主機先拒絕,避免直連時 DNS 等候。使用本地 Broker 請填同網段 IP;跨網段或名稱解析請先配置可用路由。
- TCP 連線在 connect 前套用設定的 socket timeout;保留 watchdog 門檻,未以延長看門狗掩蓋阻塞。
- USER 恢復原廠除主設定/備份外清除網路試用及 Wi-Fi 救援副本,保留網頁資產與獨立授權檔。
- 依業主指示發布供
.46網頁 OTA 驗證。包含下方 1.1.14 密碼操作修正;1.1.14 不另作正式發布。 - Native131項、首次設定直連15分鐘、USB啟用與設定保存通過。
.248LAN MESB 上傳兩次接收階段失敗;無閘道48組HTTP取樣1次連線失敗、無重啟。窄螢幕實際視覺測試未完成。發布不代表上述項目全面驗收。 - 安裝包 BIN779848 bytes、8資產 MESB964248 bytes,Build ID
4F1CB95D3714432892C26B70E2F5D38B。新增 OTA 接收逾時/暫存短寫的區分診斷,尚未宣稱修復 OTA 根因。
#v1.1.14 候選(尚未取代正式版)— 2026-11-02 — 密碼操作與出廠直連網路
- Wi-Fi 與 MQTT 密碼欄位提供相同的眼睛按鈕;點擊切換新輸入內容的顯示/隱藏,並更新按鈕狀態、提示與輔助標籤。
- 欄位即時顯示 UTF-8 byte 數。MQTT 最多31 bytes;Wi-Fi 最多63 bytes。非ASCII字元可能占用多個bytes,不以字元數代替實際儲存長度。
- 貼上前檢查完整候選內容,超長時拒絕整次貼上;測試/儲存前再次檢查。Wi-Fi一般儲存、網路設定儲存與AP設定入口均有守門。
- 已儲存密碼不回傳網頁;「已儲存,留空保留」是提示,不是可揭露的密碼。一般設定頁留空保留原值;AP配置新連線仍沿用原本空密碼語意。
- 出廠有線預設為
192.168.50.50/24,閘道0.0.0.0;首次開機直接初始化靜態網路,已儲存 DHCP/靜態設定不覆蓋。Serial 支援無閘道設定;直連無閘道不觸發閘道自救。 - 保留1.1.13 Converter、輪詢間隔與空值回報功能;包含出廠網路初始化與QSPI網頁資產修正。使用完整MESB安裝。
#v1.1.13 — 2026-11-02 — 遠端 DI Converter、空值回報與輪詢間隔
8310/8320 共用完整 MESB(韌體+八項網頁資產),雲端發布與下載核對以本輪驗證記錄為準。
- MQTT Converter 新增遠端 OPTA 本體 DI1~DI8 與已確認的擴充 DI 引用,依 Slave ID 綁定;變數參考可複製數值與量測時間。
- 無有效樣本、離線、過期或缺席擴充時,各欄位輸出 JSON
null,不再因一個來源失敗而取消整則一般遙測。定時模式全部無效仍發送;變動才送模式在有效/null 轉換時發送,持續同值不重複發送。 - 新增可保存的設備輪詢間最小間隔:20~1000 ms,舊設定與預設50 ms。只控制一台設備輪詢完成到下一台開始的空檔,同設備內部協定時序不逐筆累加此延遲。
- 暫時失敗依正常週期重試;第三次連續失敗起逐步退避,成功後復原。畫面提供容量估算;資料 API 提供有效性、資料年齡與連續失敗次數。
- 合入 WI-267(來源提交4d6c9f7):MQTT 密碼最多31 UTF-8 bytes,測試連線及儲存前拒絕超長內容;貼上時先檢查完整值,避免 maxlength 靜默截斷。三語提示同步。
- 使用手冊、MQTT 契約、RS485 設定說明、延遲說明、現場驗收與出廠測試同步更新。
驗證與限制
- Native 131項及 Master/Slave 回歸11組通過;涵蓋校正守門、時間戳、失效/恢復、16台邏輯容量、設定編解碼。16台邏輯測試不等於16台實機驗收。
.46與 Slave1/41/43/44 完成 MQTT 實收、空值/恢復及實體41-DI2 OFF→ON三邊核對;變動才送穩定後不重複發送。實際網頁完成變數參考、儲存/重載、間隔邊界與密碼防呆測試。- 間隔與 Master 角色已通過重啟保存。仍不把短期四站測試當成長時間壓力測試或硬體故障根因證明;瞬時按鍵若短於輪詢週期可能漏讀。
- 密碼韌體欄位未加大;直接 API 呼叫端仍須遵守31 bytes限制。帳號欄位23 bytes限制本輪未擴大。校正路由/有效樣本與原有閘門、授權、MQTT連線條件不因空值回報而取消。
#v1.1.12 — 2026-10-27 — 1.1 功能整合發布
本版依業主指示發布今天整合的功能;發布不代表 Master/Slave 穩定性已驗收。8310 與 8320 共用完整 MESB,包含韌體及八項網頁資產。
新增與擴充
- RS485 設備上限 32 台,共用自訂暫存器 64 個;輸入群組、輸出動作、規則各 32 組。TCP I/O 上限維持 64;MQTT cmd/signal/0~31 保留。工作流程頁同步支援 32 個 MQTT 命令通道。
- 設定容量提高至 96 KB,DeviceConfig 使用 D2 SRAM;大型設定備份改串流,降低組包/存檔重疊的記憶體尖峰。遙測緩衝擴至 16 KB,支援較多設備的遠端即時資訊。
- USB 115200 網路指令支援 net show、net static、net dhcp、net keep;未確認網路試用設定支援回復,試用途中再次重啟會先還原。閘道探測移至背景,避免主迴圈同步等待。
- 整合 RS485 Master/Slave:設備頁選擇角色、本機 Slave ID;MQTT 可查詢及切換。角色與通訊設定保存,切回 Master 保留原外接設備設定。Slave 以 FC02 提供本體及擴充 DI 唯讀資料;Master 可掃描、加入並顯示 OPTA DI。
修正與介面
- MQTT 儲存/清除後重連維持每台唯一 Client ID,避免設備互踢;畫面顯示實際唯讀 Client ID。
- Broker 留空明確表示官方連線;本地 Broker 沿用指定設定,不自動退回官方。測試以實際主機、埠、帳密與 TLS 設定判定,過期測試結果不套用;測試成功後提供儲存操作。
- 補齊 Broker 設定翻譯與提示、加寬輸入群組編輯視窗、通知固定於可見位置,修正資產快取識別與 Probe 成功判定。
- Slave 模式總覽收起暫停輪詢的外接設備;Master 設定保留。移除角色面板重複新增設備入口。
- Slave 服務改採事件喚醒,保留 AboveNormal 優先權與 RTU 接收時間判定。加入交易時間診斷,供持續調校。
- 修正 OPTA DI 讀取失敗清空時間戳卻被計入成功統計,避免逾時耗時拉長自動輪詢窗口。不同設備交易間仍保留最小 20 ms 間隔。
驗證與已知限制
- 本輪主機端 RS485 相關 11 項測試通過,涵蓋 FC02、資料無效化/恢復、角色 API/MQTT/UI、設定編解碼,以及成功統計與雙設備逾時間隔。主機測試不等於實體線路驗收。
- 1.1.0 基線已有容量、Broker 與網路設定的實機證據;本次整合版未重新完成所有實機驗收。
- Master/Slave 的網路與輪詢穩定性仍待調校;先前觀察仍有 HTTP 連線重設。Finder 站號 1 無回覆原因尚未確認,正以 USB–RS485 隔離測試。不得把本版描述為已修復全部 RS485/網路問題。
- 保留既有多段校正、UTC 毫秒時間戳與設定相容性;本次未重做實秤校正驗收。不自動改寫現場角色、設備或 Broker 設定。
- 本次只發布雲端下載版本,不主動部署現場設備;需使用完整 MESB 同步更新網頁。
#v1.1.8 — 2026-10-27 — RS485 Slave 網路回歸修復候選(未發布)
- RS485 Slave worker 改為 Normal 優先權,維持既有 1 ms 服務週期、2 KB 堆疊、IRQ 時戳、FC02 與角色持久化。
- 8310 同機對照:AboveNormal 切入 Slave 後 HTTP/ARP 失效;Normal 的診斷版可保持 Slave、載入完整網頁並重開恢復。正式映像與雙機驗收結果依 本輪證據,不能用診斷版或主機測試代替。
- 正式映像不包含 USB 角色診斷入口。WI-259 靜態 IP 跨重開還原與背景閘道探測保持不變。
#v1.1.7 — 2026-10-27 — RS485 整合候選(禁止放行)
- 合併 1.0.5 的 Master/Slave 到 1.1.x。現場發現 Slave 影響網路,詳見 重現證據。不得因 Wi-Fi Master 正常而視為整版通過。
#v1.1.6 — 2026-10-27 — WI-259 背景探測候選
- SDK first-connect 在非阻塞設定下仍耗26255ms;撤回未奏效的 SDK TCP 修改。
- 閘道探測改由既有 csThread 執行,主迴圈只排程/收結果,不新增執行緒或延長 watchdog;OTA 守門沿用。
- 未確認設定再次重啟時仍在網路初始化前還原。
#v1.1.5 — 2026-10-27 — WI-259 SDK 內部診斷候選
- 1.1.4 總期限迴圈仍未消除約26秒等待;新增 SDK open/首次 connect 分段證據。跨重啟還原繼續有效。
#v1.1.4 — 2026-10-27 — WI-259 TCP 總期限修復候選
- 實測閘道 connect 26750ms、close 1ms;改用非阻塞 connect 與整次嘗試的總期限,避免 socket 事件反覆延長等待。
- 保留未確認設定的跨重啟還原,尚待相同故障條件實機對照。
#v1.1.3 — 2026-10-27 — WI-259 網路分段診斷候選
- 1.1.2 跨重啟還原已實機通過,但約163秒的重啟仍重現;不可宣稱根因修復。
- 新增 NTP、閘道連線/關閉分段時間,持續定位阻塞。不變更 watchdog 期限。
#v1.1.2 — 2026-10-27 — WI-259 修復候選(未發布)
- 在建置副本修正 SDK TCP connect 未套用 setSocketTimeout 的缺口,不修改共用 SDK。
- 未確認的 USB 網路設定於再次重啟時,在網路初始化前還原原設定;原定 netcfg 重啟仍可進入五分鐘試用。
- 保留診斷檢查點,實機對照測試中。
#v1.1.1 — 2026-10-27 — WI-259 診斷候選(非正式發布)
- 以 1.1.0 與交接修正 1a0e31f 為基礎,細分 ADC/統計/虛擬通道檢查點;不改看門狗期限。
- 用於錯誤靜態 IP 重開原因定位。跨重啟還原與根因修復尚未驗證,不宣稱修復完成。
#v1.0.0 — 2026-10-06 — 正式版
- KC 確認現場驗收完成並指示停止重測、正式發布;既有未執行與差異紀錄保留,不改寫為測試通過。
- 以6.0.135功能為基礎,正式opta建置預設啟用線上多段校正,variant為production;包含v2穩定憑證、三/五段累計重量、原重量payload中的ACK、校正期間持續傳送及原checkbox設定恢復。
- 包含UTC毫秒訊息/量測時間、控制器設定同步、MQTT signal 0~31。版號由開發期6.0.x統一為1.0.0;雲端指定部署可跨版下發,離線設備須上線才可安裝。
- 完整MESB:韌體778552 bytes、8資產CRC通過;Build ID
693105DF70A440A0AF63612869A0C3BE。
#v6.0.135 — 2026-10-06 — 手動校正穩定憑證(測試候選)
calibration.v=2:sample改為穩定區段編號,reading保留每次採樣流水號。連續至少5秒有效、控制器stable、全区段最大最小差≤0.1kg才ready;begin選填toleranceKg可調,並回報toleranceRaw/stableMs。- 同一區段可保留sample手動填JSON;重量跨範圍、失穩、讀取缺口或步驟改變後舊憑證失效。最後量測仍須≤2500ms;写入前及commit前重讀並納入原區段範圍。原checkbox與時間戳規則保留。
- Edge以reading去重並相容v1。60秒租期與10秒ping未改;134的MQTT重連/保活失敗尚未結案。135實秤手動驗收尚待執行,不提升stable或1.0。
#v6.0.134 — 2026-10-06 — 線上多段校正現場驗證版(beta)
- 雲端 beta/
calibration-candidate,Build ID35B4EEB5DA9C49EC9B363566A7EFE4B7;與.46實測為同一個完整 MESB,含8項網頁資產。 - TDA08B 狀態暫存器200的 bit 4 採實測語意:1正常、0感測器故障,不限制特定控制器韌體版本;保留穩定、新樣本與交易檢查。
- 短暫週期唯讀失敗先使當筆無效、重新取得三筆穩定新樣本;10秒有效量測缺口、設定/感測器異常或寫入結果不明仍停止,不自動重播寫入。
- 搬放砝碼期間工具持續 ping,人工確認後穩定等待預設300秒,可調整;134 新增
lease_renewed/lease_not_active設備回覆及診斷。 - 校正中相同重量持續發送新 sample,不改動「數值變動才送」checkbox;正常收尾後按原設定、間隔與門控發送。保留128的 UTC ISO 8601 毫秒訊息/量測時間及130的
cmd/signal/0~31。 - 本輪100 native/30 Python通過;
.46三段5→25→45kg與收尾完成,降載25/5/0kg時面板和系統一致為25.01/5.01/0.01kg。精度允收、五段實秤、校正後斷電保存、業主Edge與長時仍待驗;MQTT重連與缺樣仍記錄,非1.0放行。 - 操作與欄位見磅秤校正操作指南;驗收依現場驗收流程。
#v6.0.124 — 2026-10-05 — 線上多段校正測試候選
- 新增獨立 MQTT 校正命令與狀態/樣本串流,支援 TDA08B 通道 1、kg、三段或五段;Edge 輸入新增砝碼,OPTA 以累積總量校正並自動換算小數位。
- 校正中相同重量仍提供新樣本;以匹配 boot/sid/seq 的 ACK 確認,不以重量到達當作完成。失聯、過期樣本、重複/亂序請求及未知写入結果有守門。
- 此包為
opta-calibration/calibration-candidate,不是一般opta建置。BIN 773440 bytes,保留780000安全門檻;只對候選應用程式關閉C++例外,相依函式庫維持例外處理。 - 已完成
.46受控 OTA 的版本、Build ID、資產與配置讀回;尚未正式發布。實秤精度/斷電、業主Edge、長時及例外故障路徑未驗收,不能作為1.0放行。 - 操作見線上多段校正。
#v6.0.127 — 2026-10-06 — 通用訊息時間(測試候選)
- Converter 新增
${sys.timestamp}:每筆訊息組合時固定一次 UTC 時間,未對時為 JSON null;既有 RS485 量測時間語意保留。 - 訊息時間單獨變動不觸發 on-change,既有範本不自動改寫。
- 可用變數參考新增常用「訊息時間」,逐欄位量測時間收在進階區。候選實機核對與完整出廠/1.0 放行分開記錄。
#v6.0.123 — 2026-10-02 — 磅秤設定同步與設備啟停修正
- 新增或編輯 TDA08B,每次儲存先讀取控制器的小數位與分度值。倍率相同直接儲存;不同時先更新畫面並提醒,確認後再按儲存。讀取失敗不使用舊倍率直接存檔。
- 選擇 TDA08B 樣板自動填入預設設備名稱,保留已手動命名的內容。
- 修復 RS485 設備啟用/停用未生效;改用專用 API,讀回確認狀態後才顯示成功。
- 此同步只讀控制器設定,不執行重量校正,也不自動推斷重量單位。操作見 磅秤設定與多段校正。
.46先前 6.0.122 網頁更新已由現場回報數值與面板一致;本版將修正納入完整安裝包。新版本安裝驗收與 1.0 整體放行另行追蹤。
#v6.0.122 — 2026-10-01 — 出發前候選:預設量測時間與校正寫入失敗保護
整組斷電、路由器延後啟動:雙機自動恢復,無需二次人工重開;真 MQTT 新收發、設定保留及 186 秒觀察通過。恢復耗時未量測,詳見 備援與韌性。
一鍵磅秤/電表量測 JSON 預設增加
timestamp,不需使用者手動加入 token。既有標準量測範本可用遷移工具保留原欄位與精度後新增時間。格式與遷移。校正規則任一 RS485 寫入失敗,立即停止本條規則後續輸出,不再繼續提交或亮確認燈。有效回覆只代表協定寫入確認,準確度仍須現場用砝碼核對。
3/4/5 段與零重量收尾、每筆寫入失敗及無效目標經 production 執行迴圈故障注入驗證。Native 77/77;最終設備驗證結果另記於內部 QA。raw BIN 779920 bytes,Build ID
774ee85181bc4e18a22b8b30a33550d7。6.0.121 為預設 payload 中間測試版,出發候選由 122 取代。舊版「無磅秤、確認燈照亮」的 16/16 僅為歷史閘道器流程結果,不列為校正成功證據。
#v6.0.120 — 2026-10-01 — WI-250 原始量測 UTC timestamp(雙機測試候選)
- Converter 新增可選
${rs485.<slave>.<field>.timestamp},輸出 UTC ISO 8601 JSON 字串或null;須自行加到範本。時間與成功取得的讀值一起保存,延後發送不重新標時。 - 自訂暫存器各自保存時間;Finder 同批讀值共用時間。未校時不使用開機還原時間冒充量測時間。「值變更才發送」忽略 timestamp 變動;超出輸出容量時不發送截斷 JSON。
- 失敗情境測試找到並修正:Modbus 回應必須完整才可解析,不能把 CRC-valid 截短封包的 CRC 當成量測值並更新時間。
- 原生 76/76、ASan/UBSan 與五個負向變異驗證通過;修正版 .46 真 RS485 → MQTT 延遲接收 7/7。raw BIN 779528 bytes(餘量 472 bytes),Build ID
9bc7d0c4b4cb4d7781b48e176fa6836c。 - 雙機逐項最終結果(含明列重測):.46 72 通過,.250 70 通過/2 硬體不適用;五段校正閘道器邏輯 16/16。完整回歸與歷次失敗記於內部測試報告。尚未完成業主接收端整合,未新增持久化補送佇列,也未上傳雲端。設定與驗收方式。
#v6.0.119 — 2026-09-28 — WI-249 存檔失敗看得見+重試退避
- 存檔失敗時設定 API 仍回成功、畫面毫無跡象(116 候選版就是這樣所有變更靜默遺失)。現在
/api/system、/api/diag回報cfgSave(是否成功、累計失敗、原因代碼、是否有未存變更),網頁在失敗時於底部顯示紅色橫幅並說明原因,恢復後自動消失。 - 失敗存檔原本每一輪主迴圈立刻重試(數秒內數萬次),改為 5 秒起倍增、上限 5 分鐘退避。
- 驗證:故障注入測試版(正式版不含)在
.250以真 Chrome 7/7 PASS;持續失敗 30 秒只重試 2 次。raw BIN 779404 bytes(距上限 596 bytes)。
#v6.0.118 — 2026-09-28 — WI-248 多段校正:寫入值可取自 MQTT 命令
- 規則動作輸出新增
outputMode 6:寫出的值取自cmd/signal/<pulseMs>最近收到的數值(負值擋成 0)。通用能力;磅秤多段校正用它讓段號與砝碼重量在現場輸入,不受信號數量限制(1~50 段)。 - 不改設定結構與存檔格式(沿用
pulseMs欄位存通道號)。raw BIN 778292 bytes。 - 驗證:離線 14 項(1/3/5/10 段、收尾、未標零不寫、重寫單段、負值);
.250實機 16/16。操作手冊。
#v6.0.117 — 2026-09-26 — WI-156 code review 修正(兩機驗證)
- 網頁:信號的虛擬通道來源已刪除時,開啟群組存檔不再被改成 DI I1(改顯示「⚠ 已刪除的虛擬通道」占位並原樣存回);磅秤加入信號來源清單;存計數器不再固定送
tolerance:0覆蓋已保存的容差。 - 韌體:虛擬通道總數達設定檔上限 20 時拒絕新增(原本第 21 個以後存檔靜默丟失),
/api/virtual的capacity.total回報;未經新增就直接啟用的通道在存檔時補發識別。raw BIN 778260 bytes。 - 限制:設定過虛擬通道引用後不支援降版到 ≤6.0.115(降版存檔再升回會重複遷移)。
- 驗證:
test:persist擴充 PS.4(config/signals/actions/rules/tcpio/virtual 跨重開逐項比對)與 PS.5(WI-156 情境)。.46在 6.0.115 上紅(B 位置 1→0、threshold 12.5→0、計時器/延遲器變無名空殼),6.0.117 上.46/.250皆綠。
#v6.0.116 — 2026-09-26 — WI-156 虛擬通道穩定識別與參數持久化(8310 驗證)
- 刪除不再錯位:虛擬通道存檔保留位置、載入放回原位;每個通道有跨型別唯一、刪除後不重用的穩定
id。信號CH_VAR來源改存id(原為陣列位置,刪 A 後指向 B 的信號重開變成讀 C);計數器 sensor-fusion 來源改存signalId。刪除或找不到一律讀 0,不會悄悄指到別的通道。 - 補齊重開就消失的參數:計時器與延遲器整組、計數器
expIndex/threshold/unitWeight/tolerance、類比邏輯expIndex與死區/延遲/視窗。/api/virtual也開始讀取網頁一直有送的計數器unitWeight/tolerance。 - 舊設定自動遷移:以檔案有無
nextVChannelId判斷舊檔,一次性把舊編碼換成穩定識別;不升 CONFIG_VERSION(存檔與雲端驗證只接受 1..5,候選版曾因升版導致所有存檔失敗,已加編譯期檢查)。另修正舊檔載入後版本號不升、可能讓新引用下次開機被重複遷移的潛在問題。 - 網頁:信號來源選虛擬通道原本送出 NaN(從未能用)、計時器存成韌體不認得的 type 9,改存穩定
id;編輯計數器時依來源型別回填,不再把 Modbus/信號來源悄悄改成 DI。 - 瘦身(出貨尺寸上限 780000):移除休眠的
cmd/scale/calibrate/cmd/scale/active與 WI-125 診斷端點/api/ota/copytest*。raw BIN 778092 bytes。 - 驗證:主機端 8 組測試(ASan/UBSan),6 個故意改壞版本皆 FAIL;
.250實機 115→116 升級遷移+刪除重開 21/21 PASS、不重複遷移 PASS。證據(內部 QA 紀錄)。
#v6.0.115 — 2026-09-25 — WI-220 WiFi HTTPS 不再把 WiFi 韌體檔當憑證解析(8320 驗證中)
- WiFi 對外 HTTPS 原本走框架的
setRootCA,把 QSPI 分區1 的/wlan/整個目錄交給 mbedtls 當憑證路徑,其中 421098 bytes 的 WiFi 韌體檔每次都觸發calloc(1,421099)失敗,並把堆積尖峰推到上限前約 7 KB。改用與有線分支(v5.9.254)相同的 no-FS CA hook,只載入內建 ISRG Root X1。 .46實機:堆積尖峰 267384→214086(28 分鐘後 219000)、配置失敗 3→0;雲端心跳正常。raw BIN 778372 bytes。- 限制:WiFi HTTPS 現在只信任 ISRG Root X1(與有線相同)。隔夜穩定性未驗,WI-220 仍開啟、1.0 未放行。上機紀錄(內部 QA 紀錄)。
#v6.0.114 — 2026-09-21 — WI-220 配置失敗診斷(工程候選,未部署)
- 沿用已編入 Mbed 的 memory-trace callback,固定 RAM 記錄第一筆/最近一筆非零配置失敗及次數,包含大小、caller提示、執行緒與heap統計;不改 fault record,不改配置策略。
- 新增管理員唯讀
/api/diag/alloc;SDK hash變動拒絕建置。故障注入、權限/長度檢查、ASan/UBSan、完整編譯及MESB CRC通過。raw BIN778420 bytes。 - RAM資料會隨重開消失、caller不是完整回溯;實機時間干擾尚待驗證。
.46仍跑113。工程證據與上機計畫(內部 QA 紀錄)。
#v6.0.113 — 2026-09-21 — WI-220 擴充啟動配置(雲端 dev/8320 驗證中)
- 移除 Blueprint 0.2.8 數位擴充 startup callback 的暫存器 map 複製,保留型別篩選、輸出與 timeout 命令。以 hash 守門的 build-local middleware 套用,不修改依賴快取。
- 配置拒絕反例、命令一致性、ASan/UBSan、完整 opta 編譯與 8 資產 MESB CRC 通過;raw firmware 777348 bytes。首次物件建立仍需配置,不能稱為整個 startup 零配置。
- 完整證據(內部 QA 紀錄)。實機 I/O 與長時 OOM 驗證待做;WI-220 仍開啟、1.0 未放行。
- 後續已上雲端 dev 並 OTA 至
.46,boot473→474/SOFTWARE/ota、Build ID 相符、完整備份不變。首輪頁面/API/穩定性套件 35 PASS/0 FAIL/0 SKIP,收尾輸出一致;60 分鐘觀察已開始,尚未判定通過。部署與測試(內部 QA 紀錄)。
#v6.0.112 — 2026-09-21 — WI-247 堆積遙測
GET /api/diag新增heap物件(currentSize/maxSize/reservedSize/freeSize/allocCount/allocFailCount/overheadSize),取自mbed_stats_heap_get()。GET /api/stats的freeHeap原本寫死 0、從未量過任何東西,現已接上真值。- 新增
src/HeapDiag.{h,cpp}。只讀取,不改動任何配置或釋放路徑,未碰 fault record 的 v1 版面。 - 實機量到:穩態
currentSize162748 / arena 274592,基線平穩無洩漏;每次雲端 TLS 心跳推到 247365(+85 KB、近 300 次配置),尖峰只剩約 27 KB 且固定 2 次配置失敗,結束後完全回到基線。 - 這是量測,不是修正。WI-220 穩定性仍未解決、仍擋 1.0。
#v6.0.111 — 2026-09-12 — WI-220 故障暫存器診斷
- 沿用 128B v1 雙槽,以保留 word 26–29 與 flag mask 0x08 補記例外當下的 R0/R1/R4/R6;舊 record 回 null,舊 validator 仍接受新 record。
- 受控實機四次排定重開,四個已知暫存器值正確。診斷可用不等於記憶體缺陷已修。
#v6.0.110 — 2026-09-12 — WI-244 網頁資產傳送與啟動失敗恢復
- 補齊完整傳送檢查、header/body/queue 期限、可取消 gate、失敗語系快取移除、版本錯誤與重載畫面;語系切換只讓最新操作在成功後提交。
- 2A–2F 與 ST.8 實機 PASS;同輪另出現 UNEXPECTED/rules 並留下相符的 fault record,轉 WI-220 解析。
#v6.0.109 — 2026-09-12 — WI-242 備份預配置與序列化失敗分類
- 完整 JSON 輸出一次預留空間,使用不清掉預留容量的 String sink;附加失敗保持失敗,仍核對文件溢位、預期/回報/實際長度。
- 備份解析成功後先釋放原始文字再加入模板,降低同時保留的配置量。409 保留 BACKUP_SERIALIZE_FAILED,另附 serialization 階段/位元組/溢位/reserve/append 結果;不能將107的舊錯誤反推為已證明 OOM。
- native 故障注入、多模板/浮點/CRC 回歸通過;實機結果見 QA 記錄。診斷候選不表示 1.0 已通過出廠。
#v6.0.108 — 2026-09-12 — WI-220 請求 body 時限與不完整資料拒絕
- 非串流 HTTP body 使用不可續延的四秒總預算;斷線後的 50 ms idle 等候仍可讀完已緩衝資料,但不能繞過總預算。read 返回負值不再當作有效字元或消耗 Content-Length;String 附加失敗、逾時或不足長度均在路由前拒絕。
- 新增 webbody/wbrd/webbstp 分段,區分 body 等候、SDK read 與拒絕後 stop。時間檢查不能中斷 SDK 內部阻塞;不宣稱已查明先前 UNEXPECTED 根因。OTA/網頁資產串流仍由原 handler 接收。
#v6.0.107 — 2026-09-12 — WI-243 訊號回應完整性
- GET signals 使用與 GET config 相同的緩衝傳送器,依實際接受量補送;零進度或逾時後停止後續 JSON,記錄 endpoint/sent/pending。兩條路由共用既有 4 KB static buffer。
- 實機曾捕捉 HTTP 200 少一個
{;實際 handler 的零寫入反例可形成相同結構,尚未記錄該次 SDK 返回值,故不宣稱已證明原事件根因或解決意外重開。
#v6.0.106 — 2026-09-12 — WI-242 設定字串完整性與檔案讀取診斷
- 存檔及備份同時核對預期 JSON 長度、序列化回傳量及實際 String 長度。新設定先驗證再替換主檔,失敗保留原設定;檔案讀回比較 bytes。lastSave/configCrc 在成功提交後更新。
- 檔案讀取只保留一份預配置 String,改用固定小塊讀入;409 stage 區分開檔、定位、大小、配置記憶體、短讀及附加失敗,並包含 fileSize/bytesRead/readErrno。這些診斷不等於已查明先前 EMPTY_CONFIG 的根因。
#v6.0.105 — 2026-09-12 — WI-241 修正合法浮點設定的 CRC 誤拒絕
- 校驗原始 JSON bytes,僅將唯一根層 crc32 值置換為 0,避免浮點重新序列化改變末位後被誤判為檔案損毀。載入與備份/還原共用驗證;保留既有格式化檔的校驗相容途徑。
- 備份 HTTP 409 與推送錯誤會附帶拒絕階段。完整性檢查仍拒絕真正損毀;此 native 反例不代表已找到 8320 先前 HTTP 409 或重開的實機根因。105 的保存/重開子項通過,但整輪曾有 EMPTY_CONFIG 與 signals 回應缺字元,後續分別以 WI-242/WI-243 處理。
#v6.0.104 — 2026-09-12 — WI-240 修正設定回應的部分寫入遺失
- GET config 的緩衝層依底層實際接受數量補送,避免 TCP 只送部分內容時清掉剩餘 JSON。零進度、無效寫入量與整輪回應逾時會停止後續寫入並留下失敗紀錄。
- 五秒預算檢查位於 SDK 呼叫之間及回傳後,不能中斷卡住的單一 SDK 呼叫;不宣稱修復 WI-220 非預期重開。原始 header 反例與十一組 native 回歸通過;8320 OTA 保留設定,40 次完整 API 讀回及短輪 bootCount 守門通過。完整驗收與出廠門檻另計。
#v6.0.103 — 2026-09-12 — WI-220 還原套用階段診斷
- 正式雲端下載可收到完整內容,但 8320 在套用時返回失敗;增加格式、模板暫存、寫入讀回、備份與 rename 的拒絕階段,供 API/串口定位。errno 只作輔助線索,不代表精確 syscall 根因。
- 保留原本提交與失敗復原流程。8320 已完成 OTA,設定前後一致;仍為診斷候選,正式還原與穩定性門檻尚未通過。
#v6.0.102 — 2026-09-12 — WI-220 跨重開故障與還原下載診斷
- 在 Mbed fatal 與 CPU exception 入口保存首筆數值記錄,使用獨立 Backup SRAM 雙槽;保留原有 BootDiag、設定區及 SDK 重開行為。每次建置產生唯一 ID,故障位址需對應該 ID 的 ELF。
/api/configserver.download區分 HTTP、收到/寫入長度、檔案錯誤、逾時與設定套用拒絕;保留既有下載/還原流程與時限。APPLIED 仍需重開後完整讀回與雲端 ACK 驗證。- 這是桌上診斷候選,未修復非預期重開。專用
opta_fault_test僅供注入測試,不得作為一般出貨包;一般opta不含注入端點。覆蓋限制及逐輪結果見docs/qa/qa-wi-220-diagnostics-2026-09-12.md。
#v6.0.101 — 2026-09-11 — WI-220 雲端執行緒診斷候選
- 新增 CS worker 階段、階段序號與經過時間,區分 DNS、TLS、傳送、讀取、連線清理與重試;重開日誌不再固定推定 connect 未返回。
- busy 與起始時間一致發布;網路函式庫只加入 build-local 診斷,不變更 socket 控制流程,SDK 本體不修改。
- 8320 桌上機 OTA 與 45 分鐘備份觀察完成;仍有觀察窗口外 UNEXPECTED 重開待查。這是診斷候選,WI-220 未結案、1.0 尚未放行。
#v6.0.100 — 2026-09-09 — WI-239 修正 HTML 打包使導覽保護失效
- HTML 壓縮保留 inline script 及其他 raw-text 區塊的換行;避免 // 吃掉 WI-177 早期導覽 stub,解決載入途中點擊出現 nav undefined。
- 真正 gzip 產物執行測試先紅後綠;備份/模板/身份與 fetch gate 邏輯延續 6.0.99。
#v6.0.99 — 2026-09-09 — 1.0.0 備份/還原鏈候選修正
- WI-235:完整備份 API 與自動推送包含模板;還原先驗證 JSON、長度及模板,再寫入未使用的模板目錄,最後切換設定。下載中斷或模板寫入失敗保留現有設定。模板以 channelId 保存,避免刪除通道後錯位。
- WI-230:缺少/空白模板有 API 狀態、列表警告及 30 秒節流日誌;網頁阻擋啟用空白模板。
- WI-224:網頁備份使用與設備相同的有效憑證(雲端 token 空白時使用設備 license);韌體上傳腳本改讀 CONFIG_SERVER_TOKEN,不再硬編雲端管理 token。
- WI-228:推送按鈕顯示「已排程」;任一已設定目的地失敗仍保留重試,保留推送期間完成的新設定。既有 30 秒嘗試間隔/連續失敗後 5 分鐘冷卻仍適用。
- 延續 6.0.95 的 WI-195 工作確認;需搭配 Config Server 2.5.8。舊快照缺少完整設定或模板時回 422,不會拼接其他日期的模板。
- WI-238:設備 API 完整收完 response body 才放行下一筆請求,避免只有標頭完成就解除排隊;後台載入失敗有明確提示並保留上次資料。
- Config Server 工作執行緒堆疊由 4096 增至 8192 bytes,GET configserver 可觀察 size/peak。真實 TLS 測到峰值 4232 bytes,超過原配置;6.0.97 曾在恢復 TLS 環境後非預期重啟,因此由 6.0.98 診斷版修正後進入 6.0.99 回歸。
- 6.0.96~6.0.98 為中間測試版本;6.0.99 為此輪整合候選,後續 6.0.100 修正 HTML 打包。正式雲端部署與現場 token 遷移尚未完成,尚非 1.0.0 出貨宣告。
#v6.0.95 — 2026-09-09 — WI-195 還原確認
- 從發出還原命令的設定伺服器下載,避免雲端指令誤取地端設定。
- 成功載入還原設定後保留工作 ID,經心跳確認套用;未確認不視為還原完成。
- 狀態:本地 8310 Ethernet/8320 Wi-Fi 已 OTA 與還原競態測試通過;正式雲端尚未部署。
#v6.0.90 — 2026-09-02 — WI-216/WI-217 拿掉一個會說謊的診斷欄位,刪掉一段死碼
兩件都是 2026-09-01 那次「首次在無線環境完整重跑三套」順手挖出來的,沒有功能變更。
#6.0.128(毫秒時間測試候選,2026-10-06)
已發布雲端 beta,可手動下載/部署;同
.46驗證包,未提升 stable。Converter 通用訊息時間與 RS485/線上校正量測時間改為 UTC ISO 8601 三位小數毫秒;變數名稱不變,既有時間欄位格式由
...06Z變為...06.237Z,Edge 應支援小數秒解析。NTP 保留小數秒,網頁校時附帶毫秒,以 64-bit 單調時鐘推進;未對時仍為 null,舊樣本/補送不重寫,時間變化不觸發 onChange。
毫秒解析度不代表 UTC 誤差小於 1 ms。候選驗證與安裝原始證據在儲存庫
docs/qa/qa-wi250-millisecond-128-2026-10-06.md,不代替完整出廠、長時或實秤驗收。
#6.0.126 交付自檢候選(2026-10-05)
修正停用前置 RS485 設備後取錯 endianness 設定的問題,重量解碼改用原設定索引;多段校正支援標準中文樣板「實時重量」,不再要求固定英文名稱。保留原重量 payload 的 calibration 狀態/ACK。.46 受控安裝並重新驗時間戳,仍非 1.0 正式放行;實秤精度、業主 Edge 及實機故障驗證待完成。
#6.0.125 原重量 payload 整合候選(2026-10-05,未安裝)
WI-252:原重量 MQTT payload 增加 calibration 狀態與匹配命令 ACK;校正中不變重量仍送新樣本,Edge 不需另訂閱診斷 topic。保留既有流程設定,異常採樣不冒充舊重量;開始前檢查可用重量流程。124 仍是已安裝候選,其獨立 topic 行為不能代表本項通過。主機與本機 broker 驗證見 125 報告(儲存庫內部證據:docs/qa/qa-wi252-weight-payload-125-2026-10-05.md);實秤、業主 Edge、故障與長時仍未驗收。
#WI-216:/api/net/diag 移除 netifIsDefault
那一欄本來要回答「WiFi 這張 netif 是不是目前的預設介面」——它從結構上就答不了。
ni == NetworkInterface::get_default_instance() // ← 比較的對象是錯的mbed 自己的標頭寫著,get_default_instance() 的預設定義是 weak、會轉呼叫
get_target_default_instance() —— 回的是 target 編譯期預設,量不到執行期
set_as_default() 做了什麼。所以它恆為 false。
實測(2026-09-01,設備在 WiFi、韌體 6.0.89、靜置無併發):
"dnsErr": 0, "connGateway53_ms": 3, "connCloudIP443_ms": 9,
"netifIsDefault": false ← 跨網段明明 9ms 就通,欄位卻說「不是預設介面」跨網段連得上就是繞送正確的直接證據;欄位與地面事實矛盾,錯的是欄位。
危險之處在於 WI-212 的症狀正好是「多出來的 netif 搶走預設介面」——
一個恆為 false 的 isDefault,長得跟 WI-212 復發一模一樣,現場驗收的人看到它
會停下來追一個不存在的問題。
改法是直接拿掉,不是改名:能證明繞送正確的判準本來就在同一支端點裡 ——
connCloudIP443_ms > 0(跨網段純 IP 連得上)。RG.9 的失敗訊息早在 WI-215 就
不再印它了,這次是把韌體端的來源一併拔掉。
#WI-217:app.js 的 loadTcpIo() 死碼
它每次都完整組好一份「每通道一大段 template literal」的 HTML 陣列,然後寫進
document.getElementById("tcpIoList") —— 而那個 id 早已改名為
tcpChannelsList,el 恆為 null,整段被 if (el) 擋掉。畫面一直都是
函式尾端呼叫的 renderTcpIoList() 畫的。純浪費,無功能影響。
#實機驗證(8320,WiFi 192.168.72.46)
| 結果 | |
|---|---|
/api/net/diag |
netifIsDefault 已消失,JSON 仍能解析;connCloudIP443_ms=7 |
出廠閘門 test:gate |
35/35 通過(含 2B TCP 通道區 = WI-217 的驗證點) |
穩定性 factory-stability |
16/16,ST.1 40/40,ST.9 bootCount 全程未變 |
回歸 test:regression |
RG.9(守 WI-212 的那一項)通過 |
#v6.0.89 — 2026-09-01 — WI-212/WI-213 有線↔無線切換:切過去送不出資料、插回去不會切回來
業主人工驗收拔網路線時發現:設備確實切到 WiFi、網頁打得開、bootDetail 也正確標成
net-failover,但送不出任何資料 —— MQTT 連不上、雲端連不上、NTP 也對不到時間。
藍燈持續快閃超過三分鐘。
#真兇:為了偵測插線而建的那張 netif
failover 開機、WiFi 起來之後:
IPAddress dummyIp(169, 254, 1, 1);
Ethernet.begin(mac, dummyIp, dummyIp, dummyIp, dummySub, 1500UL);
// ↑IP ↑DNS ↑閘道 —— 全都指向自己它同時建出第二張 netif(沒有 link、閘道指向自己)並成為 mbed 的預設介面, 跨網段的封包從此走上死路。
為什麼難查:同網段一直是通的 —— 那不需要閘道,直接 ARP 就到。所以查 WiFi netif 的 IP / 閘道 / 遮罩全部正常,問題其實在「封包實際走哪一張」。
決定性對照(同一台、同一次 failover,只差這一行):
| 有這行 | 停掉它 | |
|---|---|---|
mqttConnected |
false |
true |
dnsErr |
-3009 |
0 |
| 跨網段純 TCP | timeout | 6ms |
#修法
拿掉那行會連帶弄壞插線偵測(那正是 WI-213 的機制)。正解是初始化完之後把預設介面 明確指回 WiFi:
NetworkInterface* wifiNi = (NetworkInterface*)WiFi.getNetwork();
if (wifiNi) wifiNi->set_as_default();#WI-213:同一個修法補到另一條路徑
「AUTO 開機時沒插線 → 落到 WiFi」這條路從來沒有初始化 Ethernet PHY,
所以 linkStatus() 永遠 LinkOFF,WI-193 武裝的回復監看等的訊號不會來 ——
實測插線後 2.5 分鐘零反應(更早一次 11.8 小時)。
failover 路徑之所以偵測得到,正是因為它有那個 dummy begin。
現在兩條路都做「初始化 PHY + set_as_default」。
#四條路徑實機驗證
| 路徑 | 結果 |
|---|---|
| 拔線 → failover 到 WiFi | ✅ MQTT 通、dnsErr=0、跨網段 8ms |
| failover 狀態 → 插線 | ✅ 5 秒自己切回,net-recovery |
| AUTO 開機落到 WiFi | ✅ MQTT 通(修法未破壞既有正常路徑) |
| AUTO-on-WiFi → 插線 | ✅ 5 秒自己切回 —— 這條原本完全不動 |
#順帶
/api/net/diag新增 mbed netif 層的netifIP/netifGateway/netifNetmask/netifIsDefault。就是這個儀器讓「參數全對卻不通」變成可觀測的,把懷疑從參數層 推到繞送層 —— 保留。- 修掉
dnsIp在 DNS 解析失敗時讀未初始化記憶體(實測印出"POST /ap8")。
#一個要記住的教訓
程式碼裡原本寫著:「WIFI_ONLY 路徑從不卸載 QSPI 且長期正常運作,即為『QSPI 與 WiFi 可並存』的實證」。它拿來當實證的那條路徑,正是本次證明壞掉的那條。 用「某條路徑長期在跑」證明「那條路徑上的設計是對的」,前提是那條路徑真的被驗過 —— 而 failover「切過去之後送不送得出資料」,在此之前從來沒有人驗過。
#v6.0.83 — 2026-08-28 — WI-211b 通知改 QoS 0 + 週期重發兜底 + 壓制窗 1 秒
v6.0.82 上機後業主回報「進步很多,只卡了一次」。追那一次:
t=49817 ⛔ 卡 1009ms 最大段 mqtt=1007ms ← 先卡
t=49818 🔌 MQTT 重新連線 ← 才斷線1007ms 這個數字在先前多份 log 裡反覆出現(1006/1007/1008)—— 又是固定逾時的指紋。
翻函式庫:MQTTClient.h:75 uint32_t timeout = 1000;。
真因是網路延遲,不是爆發:QoS 1 每則發送都要等 broker 回 PUBACK,broker 在
網際網路上,只要那一秒剛好塞車、ack 沒回來 → 函式庫判定失敗 → 連線被當成死的 → 重連。
佇列沒滿、合併正常運作 —— 這一次純粹是外部延遲。
#業主的提案與最終決定
業主問:「採取 QoS 0,然後發送的時候發兩次呢?」
發兩次幫助有限 —— 兩份相隔幾毫秒的複本會一起死在同一條斷掉的連線上, 而連線斷掉正是 QoS 0 唯一會發生的遺失情境。它擋不到唯一會發生的那種遺失, 卻讓通知量與主迴圈阻塞加倍。
正解是週期重發現況:涵蓋所有遺失原因,成本每分鐘一則,而且給下游一個 有界的過時上限 —— 那才是能寫進合約的東西。業主採納。
#三個改動
rules/*/trigger改 QoS 0 —— 沒有 ack 要等,1 秒逾時的故障模式直接消失。 對一個設計上就可丟的通知(設備已刻意合併中間值),付 ack 的代價換保證本身就不一致。- 週期重發:每 60 秒把每個狀態類 topic 的現況原樣重送一次(期間有更新則不重送)。 已被週期推送的資料(RS485 值)會不斷刷新時間戳,自然不會觸發 —— 機制自我調節。
- 壓制窗 500ms → 1000ms:發送次數再減半。
另修:訂閱的日誌誤印成發送 QoS(Subscribed to ... (QoS 1))—— 實際訂閱用的一直是
subQos(=2,正確),只是 v6.0.82 做變數改名時把那行的變數換錯了。行為無誤,日誌誤導。
#實測(同一台 .77,30 個事件 5 秒內)
| 版本 | 飛出訊息 | 重連 | 佇列滿 | mqtt 段最大卡頓 |
|---|---|---|---|---|
| v6.0.79 | 20 則 | 4 次 | 有 | 4398ms |
| v6.0.82 | 16 則 | 0 次 | 無 | 501ms |
| v6.0.83 | 10 則 | 0 次 | 無 | 無 >150ms |
週期重發實測:最後一次發送的 +66 秒,兩個 topic 的現況都自動重送。
#v6.0.82 — 2026-08-27 — WI-210/211 快速切換打掉 MQTT:QoS 配合語意 + 同 topic 合併 + payload 夾帶歷史
業主人工驗收時快速切換 Toggle 開關十幾次,呼吸燈進入快閃(=MQTT 斷線中,不是重開)。
追下去是一條完整的故障鏈:每條規則觸發都發一則通知 → 邊緣模式不節流 → 灌爆 16 格佇列 →
每則 TLS 發送阻塞主迴圈 265400ms → 連線撐不住 → 重連風暴(握手 1.64.4 秒且逐次變長)
→ 若持續約 90 秒,self-ping 連兩次無 echo → 設備自我重開。
同一個故障鏈 main.cpp:2620 的註解已描述並修過一半 —— 但只節流位準模式,
明確寫著「邊緣觸發不節流,每個事件都有意義不可丟」。那句是破口:對慢速事件成立,
但人手快切或現場抖動的接點的邊緣密度足以複製同一個故障(本站 debounceMs=0)。
業主定下的原則:「控制是現場的問題,訊息可以晚到。」 依此重做發送路徑。
#一、QoS 配合資料語意(最大的槓桿)
defaultQos 寫死 2,而 setDefaultQos() 全庫從來沒有人呼叫過 ——
config.mqtt.qos 早就有欄位、有存檔、Converter 也在用,就是沒接到管理器上。
發出去的絕大多數是「最後狀態」:重複送達會被下一筆覆蓋,無害 —— 需要的是「一定會到」,不是「恰好一次」。用 QoS 2 等於付兩趟往返買一個用不到的保證。
→ 發送預設 QoS 1、訂閱維持 QoS 2(命令不可重複執行),兩者皆可在網頁設定。
#二、同 topic 合併 + 每 topic 最小發送間隔 500ms
同一個 topic 還在佇列裡就直接取代,並移到隊尾 —— 移到隊尾是關鍵: 狀態由「哪個 topic 最後更新」表達,原地取代會讓先進佇列的舊 topic 晚於後更新的送出, 下游停在錯的最終狀態。
最小間隔同樣必要:沒有它,drainQueue() 每 50ms 排空,佇列永遠只有 0~1 筆,
「同 topic 取代」等於不存在 —— 實測 20 則全數飛出,零合併。
單次變化不受影響(該 topic 若 500ms 內沒送過就立刻送),只有連續變化才被壓。
#三、payload 夾帶歷史(opt-in,預設關)
{"v":"A2-MQTT滅","t":1787839126,"h":[[354,"A2-MQTT滅"]]}v=最後狀態、t=本批起點 epoch 秒(0=時鐘未同步)、h=[相對毫秒, 當時狀態]。
每筆帶時間與狀態。用相對毫秒 + 單一絕對錨點,是因為 payload 硬上限 128 bytes,
且時鐘未同步時仍要可用。超過 8 筆標 trunc:true,誠實告知不完整。
合併因此從有損變無損 —— 中間過程不是丟掉,是換個位置帶走。 預設關閉:開啟會把 payload 從裸值變 JSON,既有解析端會爆。升級不會自動開啟。
#實測(同一台 .77,30 個事件 5 秒內)
| 重連 | 佇列滿 | mqtt 段最大卡頓 | |
|---|---|---|---|
| v6.0.79(改前) | 4 次 | 有 | 4398ms |
| v6.0.82(改後) | 0 次 | 無 | 501ms |
#量測方法踩到的兩個坑
- 用
mosquitto_pub逐次呼叫做「爆發」是假的:每次都是新行程 + 完整 TLS 握手, 實際只有 2~3 則/秒,比壓制窗還慢,合併根本不會被觸發。要用-l單一連線連續送。 - 先前用 QoS 2 進向做壓力測試,測到的是進向握手成本,不是出向通知成本 —— 業主的情境是純出向(撥開關),兩者不可混為一談。
另:MQTT 設定改為每 3 秒收斂到管理器。原本只在建立連線時套用, 使用者在網頁改了 QoS 或歷史夾帶卻沒重連,設定永遠不生效(實測踩到)。
#v6.0.79 — 2026-08-27 — WI-208b 雲端每次心跳重送舊授權,設備每次白驗簽兩次
業主在 v6.0.78 上回報「呼吸燈大概 115 次會卡一次」。心跳燈每 500ms 翻一次 (一次呼吸 = 1 秒),所以那是約 115 秒一次 —— 對得上雲端心跳的 127 秒週期。
實測把每次卡頓與 log 事件對起來,關聯性零例外:
t=3019 ☁️ CS-Cloud Connecting → t=3029 📜 license/state rejected → t=3029 ⛔ 卡 1334ms
t=3146 ☁️ → t=3153 📜 → t=3153 ⛔ 卡 1340ms
t=3341 ☁️ → t=3350 📜 → t=3350 ⛔ 卡 1341ms
t=3407 ☁️ → t=3414 📜 → t=3414 ⛔ 卡 1338ms因果鏈:設備心跳連雲端 → 雲端回頭發一則 license/state → 該 token 序號固定
(tokenId=1786621672)且永遠被判 stale 丟掉 → 但丟掉之前付了兩次驗簽:
licenseTokenAcceptAndStore() 驗一次(663ms),它內部為了讀「既有序號」呼叫
licenseTokenStoredTokenId() 又完整驗簽一次(663ms)—— 只為了取 4 個 byte。
663 × 2 = 1326ms,與實測的 1330ms 完全吻合。
三項修正:
- 收訊前先看序號,不新就不驗。新增
licenseTokenPeekTokenId(),只解析標頭、 零密碼學運算。序號不比手上的新 → 驗了也是拒,直接丟。 - 既有序號改用快取(
licenseTokenStoredTokenIdCached())。序號只在存入新 token 時改變,沒有理由每次重算。 - 只有真的存了新 token 才重評門控。原本無條件 invalidate + refresh, 等於每則被拒的訊息再多付一次驗簽。
⚠️ 同時修掉一個 v6.0.78 引入的正確性 bug:WI-208 的三個快取變數被寫成 header 裡的
static—— 那是「每個 .cpp 各一份」,而licenseGateInvalidate()/licenseGateRefresh()是inline(外部連結),連結器只留一份定義並綁到其中一個 .cpp 的變數。結果兩者可能在動不同的變數:實測 v6.0.78 收到 license/state 後 明明呼叫了 invalidate,卻完全沒有重驗。 那不只是效能問題 —— 存入新 token 後門控不會重新評估,撤銷授權會延遲到重開才生效。 改為extern+ 在 main.cpp 定義(比照同檔既有的g_licenseEnforce)。 教訓:header 裡的static配inline函式,是 ODR 陷阱。
實測(v6.0.79,544 秒觀測窗、涵蓋 8 次雲端心跳):
net 卡頓 0 次 → 0.00%
[WI-145] license/state 略過(序號不新,未驗簽) tokenId=1786621672 已存=1786621672主迴圈凍結的完整歷程:6.8%(v6.0.74)→ 1.7%(v6.0.78)→ 0.00%(v6.0.79,net 段)。
/api/license 全程 status=ACTIVE, licensed=true。
根因仍在雲端:publishLicenseState 每次心跳都重送一則設備早就拒絕過的舊 token。
設備端現在擋得很便宜,但雲端不該一直發。已記在 WI-208 卡上,未修。
#v6.0.78 — 2026-08-27 — WI-208 授權門控每 30 秒凍結主迴圈 663ms(+WI-207 語系鍵)
業主回報「藍色呼吸燈會卡住,卡住的時候按鈕就延遲、甚至沒反應」。用 WI-163 的段別儀器
量了 210 秒:net 段卡頓 15 次、合計 14.4 秒 —— 主迴圈有 6.8% 的時間完全凍結,
期間呼吸燈不更新、DI 不被輪詢,實體按鈕的動作就這樣被吃掉。卡頓時長只有兩種且極度規律
(661~671ms / 約 2050ms),是固定成本的指紋而非負載。
net 段裡有兩個 30 秒任務且在同一輪迭代,靠時間軸分不開,故先上臨時儀器各自量測:
lic=659/665/663/662ms ← licenseGateRefresh()
wifiStat=0ms ← 診斷用 WiFi.status()
failoverStatMax=0ms ← 沒有時間閘門、每 loop 都戳的那個兩處 WiFi.status() 都是 0ms —— 原本最被懷疑的它是無罪的(程式碼裡那段註解描述的是
已經修好的舊病)。再拆一層:load=1ms verify=663ms,成本全在驗簽。
修正:門控 verdict 是 (token 內容, hwUid, nowSec) 的純函式。前兩者只在存 token 時改變
(那些路徑改為呼叫 licenseGateInvalidate() 強制重驗),nowSec 只能讓「未到期」翻成
「到期」。因此改為到期驅動 —— 只在「還沒驗過」或「先前有效且已跨過 expiresAt」時
才真的重驗;到期/簽錯/UID 錯都是終局,標記後不再重算。NTP 首次同步成功時一併失效快取,
避免「校時前誤判到期並鎖死」。
⚠️ 第一版(未出版的 6.0.76)完全沒生效:本站是永久授權(
expiresAt=0), 而判斷式寫成nowSec < expiresAt→nowSec < 0永不成立 → 照樣每 30 秒重驗。expiresAt == 0的語意是「永不到期」,韌體原本的算式 (out.expiresAt != 0 && ...)早就寫明了。終局判定補上這條才真正生效。
實測:主迴圈凍結 6.8% → 約 0.8%(net 段),665ms/30s 的卡頓完全消失,
/api/license 仍為 status=ACTIVE, licensed=true。
同版順帶修掉 WI-207:io.saving 這個 i18n 鍵三個語系檔全缺,而 t() 查不到時回傳
鍵本身(truthy),讓 || "儲存中" 這個 fallback 變成死碼 —— 存檔佇列徽章因此顯示
「💾 bH 1」而不是「💾 儲存中 1」。三份語系檔補齊,實機確認已對到「儲存中」。
尚未解決:net 段仍有約 1.3 秒的偶發卡頓,與對外 TLS([CS-Cloud] Connecting)
同時發生,屬「對外連線在主迴圈裡做」的老家族,要動架構才能根治。
#v6.0.74 — 2026-08-26 — WI-197 回復重開的標記寫錯方向
intentReset() 在有線回復路徑上標的是 net-failover —— 但那次重開是「WiFi → 有線」,
與 failover 的「有線 → WiFi」方向相反。同一台設備連續兩次相鄰重開因此標記相同,
事後看 bootDetail 分不出它是掉線還是恢復。v6.0.1 建 BKPSRAM intent marker 的初衷,
正是因為 Opta(MCUboot)開機前會清掉 RCC->RSR、硬體 reset 旗標救不回來,只能靠自己
留字條 —— 字條寫錯方向,這個機制在這條路徑上就等於白做。回復路徑改標 net-recovery。
全庫三處寫入該字串,另兩處(452/472)是名副其實的 failover,不動;確認無任何地方解析
這個字串,改名安全。
#v6.0.73 — 2026-08-26 — WI-206 Parser「值類型」下拉三個選項全部對錯韌體列舉
韌體 ParserValueType 是 BOOL=0 / NUMBER=1 / STRING=2,而網頁下拉是
0=數值 / 1=字串 / 2=布林 —— 三個都錯,沒有一個對。使用者選「數值」送出 0,
韌體當成布林,任何非零數字都變成 1.0。而 Parser 的主要用途就是抽數值、
新增解析欄位的預設 type 又正好是 0 —— 不動下拉、直接用預設的人一定中獎。
拿到的 1.0 看起來像個正常的值、不會報錯,後續拿去做門檻比較或塞進 Converter 發佈,
送出去的就是一串恆為 1 的假資料。
修的是前端不是韌體列舉:parserType 是已落盤的持久化契約,改列舉會讓既有設備上
已存的值被重新解讀成別的型別。同時把新增解析欄位的預設由布林改為數值。
遷移影響:既有通道行為不變,但畫面顯示的型別會變成它一直在做的那個 ——
升級後請回頭檢查 Parser 通道的型別設定。
#v6.0.72 — 2026-08-26 — WI-200 直接控制 API 涵蓋擴充板輸出(順帶修好一個從沒被測到的舊 bug)
POST /api/io/do 舊碼只認主機 DO 0-3,送擴充板一律回 Invalid index —— 但規則引擎
明明驅動得了擴充輸出。同一個能力規則路徑有、直接控制的 API 沒有,出廠測試與現場調機
想單獨點一路擴充輸出只能繞道「改輸出群組 → 借規則 → 發 MQTT → 再改回去」。
現在接受擴充 index(16+ch / 32+ch…),錯誤訊息附合法範圍,模組不在時明確報錯。
順帶:沿用共用的 modbusTcpWriteDO() 時發現它的擴充分支本身是壞的 ——
auto ext = OptaController.getExpansion(i); (DigitalExpansion*)&ext 是對區域暫存副本
取址再向下轉型。也就是說 Modbus TCP 寫擴充輸出一直沒有作用,而且從沒被任何測項
涵蓋到。規則引擎那份用的是 getExpansionPtr(實測會動),兩份實作走樣多時。
統一成已驗證有效的那條路 —— 這也修好了 Modbus TCP 那一側。
#v6.0.71 — 2026-08-26 — WI-203 存 Converter 模板後長度一併落盤
saveTemplate() 把模板文字 fwrite 進 /cfg/tmpl/{idx}.json(立刻落地),同時更新
config 的 converterTemplateLen —— 但只在記憶體,沒有 setConfigDirty()。
長度要靠之後某次別的設定存檔順帶刷進去;而網頁的儲存順序是先 POST /api/tcpio
(會刷盤)、再 POST /api/converter/template,剛好讓長度更新落在刷盤之後 →
重開後讀回舊值。舊值若恰好是 0,GET /api/tcpio 的內聯模板匯出(WI-133,供跨機
複製整組帶走換算公式)會變成空字串,而檔案裡明明有內容 —— 靜默遺失公式。
#v6.0.70 — 2026-08-26 — 三個「回應說一套、實際做另一套」的缺陷
WI-205(高,破壞性)— 被拒絕的規則 POST 會毀掉既有規則
POST /api/rules 的解析直接寫進 config->rules[idx],WI-174 守衛在解析之後才檢查
並回 400,而且不回滾。舊碼的緩解只有 if (idx >= config->ruleCount) rule->enabled = false;
—— 只涵蓋新槽位。覆蓋既有規則時既不回滾也不停用,結果:一個回 400 的請求把使用者
原本的規則銷毀,並留下一條 sig=0/act=0 卻 enabled=true 的死規則。使用者收到 400
會合理推論「我的操作沒生效」—— 實際上它生效了,而且是破壞性的。
修法:進 handler 時整份備份,拒絕路徑完整還原。拒絕就是什麼都沒發生。
WI-202(高)— 一個引號讓整頁設定人間蒸發
22 處使用者可編輯的字串欄位未做 JSON 跳脫。名稱或設定值裡出現一個 ",整包 API 回應
就變成不合法 JSON,網頁 JSON.parse 失敗後靜默 render 成空清單 —— 使用者看到
「尚無 TCP IO 通道」,以為自己沒有設定過。HTTP 仍是 200,畫面上沒有任何錯誤。
含 WiFi SSID、MQTT 設定、規則與群組名稱、TCP IO 各欄位。
currentStrValue 的值來自進來的 MQTT payload —— 任何能發訊息到該設備訂閱 topic
的人,payload 帶一個引號就能讓設定頁消失。
寫入端同步修:parseStr 用 indexOf("\"") 找「下一個引號」、不認跳脫,值含引號就被
截斷在跳脫序列中間(實測壞資料 {\ 正是如此)。新增 jsonExtractString()。
讀寫兩端是一對,只修一邊都不夠。
這個跳脫 helper 是 WI-191 為了同一種事故建立的(
/api/poll設備名被截斷 → 整頁數值消失)。專案已經吃過一模一樣的虧、修好了一處,兄弟端點卻從沒跟上。
WI-199(中高)— 連續快速新增規則會靜默覆蓋前一條
saveRule 在 save mutex 外用本地快取 rules.length 算新規則 index,而存檔後的
loadRules() 沒有 await、不在 mutex 內。連續新增時第二次在第一次的刷新回來之前
取得 mutex → 算出同一個 index → 第二條覆蓋第一條,兩次都回 200。
修法:index 改在 mutex 內、await loadRules() 之後才算;刷新也改成 await。
saveGroup 同源一併修。
#v6.0.69 — 2026-08-25 — WI-193 插線不自動切回有線 / WI-194 恢復原廠後雲端還原被 401 擋掉
兩個缺陷都是「同一個保護只做在部分路徑上」,而漏掉的那條剛好是現場最常見的。
WI-193 — AUTO 模式「開機時就沒線」不會進入有線回復監看
interfaceMode=AUTO 的註解是「有線優先 + WiFi 備援」,但介面選擇只在
NetworkStateManager::initializeNetwork() 跑一次。執行期的 failover 狀態機確實寫了
回切有線的路(FO_WIFI_ACTIVE → FO_RECOVERY_PENDING → 重開走 Ethernet),但唯一入口
enterRecoveryMonitoring() 只在 if (isFailoverBoot) 下被呼叫 —— 也就是只認
「曾在有線上、線被拔掉 → 重開」那一條。開機時就沒插線而 fallback 到 WiFi 的機器
不設旗標,_foState 停在 FO_IDLE,而 FO_IDLE 的監看條件是 _activeNetwork==NET_ETHERNET
—— 跑在 WiFi 上永遠不成立。實測:插上網路線 11.8 小時完全無反應,人工重開才切過去。
修法:啟用條件改成守性質 —— 「AUTO 模式且現在跑在 WiFi 上」。WIFI_ONLY 與 AP 模式排除。
WI-194 — 恢復原廠之後,雲端還原永遠失敗且無聲
L2 恢復原廠會把 cloudToken 清成空字串,而 downloadConfigFrom() 在 token 為空時
不送 Authorization,雲端該端點要求授權 → 401。雲端那側一切正常(rollback 回
success、pendingCommand 送達並被消耗),於是「後台顯示還原成功、設備什麼都沒做、
兩邊都沒有錯誤提示」—— 而且正好命中人最需要還原的那一刻。
推送與心跳早就有「token 空就改用 licenseId」的 fallback,只有下載漏掉。
修法:補上同一段 fallback(雲端 middleware/auth.js 本來就接受 license token,不需改);
下載失敗改印出目標與 HTTP 狀態碼,401 額外提示去填 Cloud Token。
同時發現、尚未修(已開卡):
- WI-195 —
autoPush會在數秒內覆蓋掉雲端剛寫入的還原,使還原變成無效操作。 - 發佈程序文件的
.mesb資產清單漏了log.html.gz(實際應為 8 個),已更正。 - L2 恢復原廠會連 QSPI 網頁資產一起清掉,設備只剩「資產救援」頁 —— 已寫進驗收單 §R.4。
#v6.0.68 — 2026-08-21 — WI-192 WiFi 連上卻沒拿到 IP(0.0.0.0)的有上限自救
設備開機快於上游網路就緒時,會連上 WiFi 但拿不到 DHCP,停在 0.0.0.0 不會自己恢復。
加入偵測與自救重開。第一版節奏設計錯誤 —— 會剛好在 router 準備好的那一刻放棄,
改為 5 分鐘首次寬限 + 加倍退避 + 移除次數上限。
#v6.0.67 — 2026-08-21 — WI-191 JSON 字串未跳脫,一個壞名稱會讓整個畫面空白
設備名稱/通道名稱等自由文字未做 JSON 跳脫,含有引號或反斜線的名稱會產出不合法 JSON, 前端解析失敗 → 整頁空白。修:輸出前一律跳脫。
#v6.0.66 — 2026-08-21 — WI-190 存檔序列化不完整會把設定連同備份一起毀掉
現場事故(2026-08-20 22:05:18,序列埠 log 完整記錄): 設定檔從 6332 bytes 變成 2108 bytes,主檔與備份在同一秒被同一份殘缺內容填滿, 設備等同回到無設定狀態 —— 隔天現場 RS485 四台掃描得到卻一台都讀不到值,因為 「已設定的設備」是 0 台。
失效機制:
ArduinoJson v7 的 JsonDocument 記憶體不足時不會報錯,只是裝不下。
serializeJson 仍吐出格式完全合法、CRC 完全正確的 JSON,只是內容少一大半。
於是它通過了下游每一道檢查 —— configJsonIsValid() 只驗格式與 CRC,不驗完整性。
接著「備份不可用 → 用本次內容重建」把唯一的好備份也覆蓋掉,救援路徑自我摧毀。
WI-180b 的兩道防護前後矛盾:上一行才判定「主檔內容不完整」,下一行就拿同一份內容重建備份。
修正:
- 存檔前對帳 ——
doc.overflowed()加上「宣告數量 vs 實際寫出筆數」比對(rules/actions/tokens)。 對不上就拒絕存檔,主檔與備份都不動。 取捨:不存 → 這次修改沒生效,使用者會發現;存下去 → 設定全毀,沒有人會發現。 - 重建備份前先驗證新內容本身;沒過就不重建 —— 壞的備份仍勝過用壞內容覆蓋。
教訓:「格式正確」不等於「內容完整」。 唯一可靠的判準是拿序列化結果跟記憶體裡 真正持有的數量對帳。
#韌體 Changelog 指南(WI-114)
從 cloud server 5.9.50 起,每次上傳韌體都必須夾帶 changelog。 沒帶或太短(< 10 chars)會被 cloud 直接 400 拒絕。 設計目的:避免 manifest / git log / wiki 等多處紀錄碎片化。
#為什麼這樣設計?
單一來源原則:
release_firmware.py → Upload API (header) → Cloud Manifest → Admin UI
--changelog ... X-Firmware-Changelog-B64 firmware.json 顯示
(or auto-git-log) (base64-encoded UTF-8) changelog field /api/firmware- 唯一寫入點 =
release_firmware.py - 唯一儲存 = cloud
/app/firmware/<version>.jsonmanifest 的changelog欄位 - 唯一顯示 = admin UI 韌體列表展開
- 不維護獨立 markdown / wiki / git 抓取(會跟 manifest 不同步)
#三種使用方式
#A) 直接字串(單行 / 短描述)
python3 scripts/release_firmware.py --bump patch --upload https://opta.smms.com.tw \
--changelog "WI-114: 強制 changelog upload + admin UI 展開"#B) 從檔案讀(多行 markdown)
最推薦的方式 — CHANGELOG.md 跟 commit 一起入 git:
# 寫一個 CHANGELOG.md(隨手寫,Markdown 格式)
cat > CHANGELOG.md <<EOF
## v5.9.50 — 2026-05-05
### Features
- WI-114: Cloud changelog 強制夾帶(單一來源防碎片化)
- Cloud admin UI 韌體列表加展開區塊
### Bug Fixes
- 修正 X-Firmware-Changelog header 多行字元編碼
### 升級注意
- 新版 cloud 拒絕沒 changelog 的 upload
EOF
# 用 @ 字首把檔讀進去
python3 scripts/release_firmware.py --bump patch --upload https://opta.smms.com.tw \
--changelog @CHANGELOG.md#C) 從 stdin 讀(CI / pipe)
git log v5.9.49..HEAD --pretty='- %s' --no-merges \
| python3 scripts/release_firmware.py --bump patch --upload https://opta.smms.com.tw \
--changelog @-#D) 自動從 git log 產生(沒帶 --changelog 時)
python3 scripts/release_firmware.py --bump patch --upload https://opta.smms.com.tw
# 不帶 --changelog 時:
# 1. 嘗試 `git log v<prev>..HEAD --pretty='- %s' --no-merges`
# 2. 若 v<prev> tag 不存在,fallback 到最近 5 個 commits
# 3. 全部失敗 → 互動式 prompt 強制要求輸入#編寫慣例(建議)
不強制,但 admin 看 changelog 時的可讀性很重要:
## v<version> — <YYYY-MM-DD>
### Features
- WI-XXX: short description
- 關鍵新功能或 endpoint
### Bug Fixes
- 修了什麼
### 升級注意 / Breaking Changes
- 客戶端需要做什麼動作(重 login?重新校時?)
### 內部變更
- refactor / 文件 / 工具
### Deploy notes
- ⚠️ Cloud 需要重啟 docker container?
- ⚠️ 此版本之後不允許降級?對應「升級注意」段落,下次可考慮在 manifest 加
breakingChanges: bool讓 admin UI 標紅醒目(sprint-28 候選)
#Cloud API 變動(v5.9.50+)
#POST /api/firmware/upload
新 header(推薦):
X-Firmware-Changelog-B64: <base64-encoded UTF-8 string>- 支援多行(
\n)+ 中文 / 日文 / 任何 UTF-8 - 解碼後 trim 至少 10 字元
舊 header(向後相容):
X-Firmware-Changelog: <plain string, URL-encoded>- 適合單行短描述
- 不建議帶 newline / CJK
400 失敗回應:
{
"error": "Changelog required",
"message": "Send X-Firmware-Changelog-B64 (base64 UTF-8, recommended for multi-line/CJK) or X-Firmware-Changelog header. Min 10 chars after decode.",
"examples": {
"base64": "echo -n \"WI-114: bug fixes\" | base64",
"cli": "release_firmware.py --changelog \"...\" or --changelog @CHANGELOG.md"
}
}#GET /api/firmware
回傳列表,每筆含完整 changelog 欄位(可能多行 UTF-8)。
#既有韌體(5.9.49 以前)
Backfill 一次性處理:在 cloud 升 5.9.50 之前,sprint-27 已執行:
// 對 changelog 缺失或太短的 manifest 補上
m.changelog = "(legacy, no changelog record — see git log v5.9.X)";4 筆被補(5.9.8 / 5.9.40 / 5.9.41 / 5.9.42),其餘 9 筆原本就有有意義的 changelog。
Admin UI 對
(legacy開頭的 changelog 顯示為灰色斜體,方便辨識
#Troubleshooting
#Q1: release_firmware.py 沒帶 --changelog,又沒 git tag?
腳本順序:
git log v<prev>..HEAD— 嘗試找 prev tag(沒有 tag 表示沒打 release tag)- 若失敗,fallback
git log HEAD~5..HEAD - 若仍失敗(lab 機器沒 git),互動式 prompt 強制輸入
如果連互動式 prompt 也沒人輸入,最終 sys.exit(1) — 不會白編譯 + 丟個沒記錄的 binary 上去。
#Q2: 我已經 build 好 .bin 了,可以直接 curl upload 嗎?
可以,但要自己處理 base64:
CHANGELOG=$(cat CHANGELOG.md | base64)
curl -X POST https://opta.smms.com.tw/api/firmware/upload \
-H "Authorization: Bearer admin-token" \
-H "Content-Type: application/octet-stream" \
-H "X-Firmware-Version: 5.9.50" \
-H "X-Firmware-Changelog-B64: $CHANGELOG" \
--data-binary @release/mes-gateway-v5.9.50-20260505.bin#Q3: Changelog 改完了想覆寫 manifest?
目前沒 PUT/PATCH endpoint。兩種選擇:
- 直接 SSH 到 cloud 改
/app/firmware/<version>.json(admin 可做) - 重新跑 release_firmware.py(會以新時間戳產出新 .bin,舊的還在)
未來如果有需求,可以加
PATCH /api/admin/firmware/:filename/changelogendpoint(sprint-28 候選)
#Q4: changelog 欄位 size 上限?
Cloud server 沒設限(manifest JSON 寫多大都可以)。實務上保持在 5KB 內,admin UI 才好看。
#近期版本重點(v6.0.41 ~ v6.0.65 — 穩定性補強與「靜默失敗」清剿)
這一段的主題可以用一句話概括:把「回成功但其實沒發生」的路徑一條條堵掉。 這類缺陷比功能壞掉更難查 —— 畫面正常、API 回 200,只有行為不對。
#韌性 / 不再自己把自己弄死
| 版本 | 內容 |
|---|---|
| 6.0.41 | WI-164 重開前刷寫統計(否則業主看到的計數會歸零);probe 預設 8N1;掃描逾時 |
| 6.0.44 | WI-167 Modbus TCP server 改可設定且預設關。實測 WiFi inbound 失敗率 13% → 0%(省下 1 個 TCP socket,全機只有 4 個) |
| 6.0.46 | WI-169 WiFi 帳密被無聲清空的三條路徑(只有一條原本有守衛);WI-168b 卡點雙槽防 torn-write |
| 6.0.51 | WI-175 OTA 撞上背景雲端 TLS 必掛。三次 OTA 兩次死在 Pre-unlink 後 7.8x 秒(硬體 IWDG 8s 窗)。修:unmount→mount→fopen 每步餵狗 + 進場前等雲端連線落地 |
| 6.0.57 | WI-180/181 設定檔原子存檔(暫存檔→讀回驗證→rename);備份只在主檔完好時才降級;WiFi 帳密獨立存 /cfg/wifi.cred,設定全毀時仍連得上 → 可遠端救援 |
| 6.0.61 | WI-183 對外連線硬性總時限 + 卡死自我復原。黑洞實測:connect() 可卡 5 分鐘不返回,setSocketTimeout 完全管不住 |
#靜默失敗清剿
| 版本 | 內容 |
|---|---|
| 6.0.48 | WI-173 /api/modbus 不再說謊(回報實際 RS485 參數,而非無關舊物件的硬編值) |
| 6.0.51 | WI-174 規則可存成 signalId=0 → 回 200、列表正常、永遠不會觸發。前後端都擋 |
| 6.0.55 | WI-179 欄位名打錯不再靜默照收。{"channel":0,"state":true} 曾被當成 value:false 並回 success |
| 6.0.58 | WI-182 只接官方雲端的設備永遠無法還原設定 —— downloadConfigFromServer 只認地端 URL,沒設就靜默放棄。而 url 留空正是預設 |
| 6.0.62 | WI-184 擋下「條件永遠不可能成立」的設定(TCP 通道來源只有布林語意,設 >50 永不觸發) |
| 6.0.65 | WI-188 改 token 名稱/權限被迫連帶換密碼 —— 畫面寫「留空=不變」但兩端都沒實作 |
#網頁 / 使用者體驗
| 版本 | 內容 |
|---|---|
| 6.0.47 | WI-170/171 畫面操作流暢性:併發閘門放到唯一出口 |
| 6.0.52 | WI-177 剛開頁面按導覽鈕沒反應(app.js 未載完就可以按);WI-178 index.html 外置 QSPI,flash 97.1% → 94.0% |
| 6.0.54 | WI-185b index.html 也帶 i18n 短碼,納入同版指紋檢查(漏更會讓整頁標籤指到錯字串,且無錯誤訊息) |
| — | WI-189(純前端)頁面切到背景時連 telemetry 一起停。實測主迴圈卡頓 −57% |
#⚠ 已知特性(非缺陷,但要讓業主知道)
- 控制延遲:典型 < 200 ms,但 p99 約 2~3 秒(實測極值 3.7 秒),每分鐘都會發生。
成因是單核單迴圈 + 主迴圈裡的阻塞式 TLS。詳見
docs/testing/qa-latency-characteristics.md - 還原舊快照會帶回當時的 cloudToken:若那份快照的 token 為空,雲端備份會變成 「推得出去、讀不回來」。畫面有明確提示,出廠測試 ST.15 / BK.3 會攔截
#近期版本重點(v6.0.37 ~ v6.0.40 — 主迴圈卡死時,記下「卡在哪一段」)
🟡 影響範圍:診斷能力為主;v6.0.38 另含一項 HTTP 解析的穩定性修正。 一般使用者不會看到行為差異,但設備若再發生
UNEXPECTED重開,原因會直接寫在設備管理頁上。
問題:v6.0.36 把誤殺修掉之後,剩下的 UNEXPECTED 重開就是「真的卡死」了 —— 但卡在哪裡看不到。
[SLOW LOOP] 這行是在一輪主迴圈跑完才印的,而真正把設備卡死的那一輪永遠跑不完,
所以最關鍵的那筆資料按定義不存在。實測 .46 兩次超過 30 秒的卡死,翻遍設備 log
看到的最大值只有 4.5 秒 —— 全是無關的雜訊。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.37 | 主迴圈每通過一個檢查點,就把段名寫進重置後仍會保留的備份記憶體(BKPSRAM)。看門狗重置後開機即可回讀,並由 GET /api/diag 帶出(lastSeg),遠端免接 USB 即可定位。 |
WI-163 |
實戰命中:.46 在 v6.0.37 上跑滿 23 小時 09 分後發生 UNEXPECTED 重開,卡點成功存活過重置被讀出來 —— 這是這個問題第一次留下實體證據。 |
— | |
| v6.0.38 | 把檢查點切細。原本名為 http 的那一段其實橫跨 1390 行(Modbus TCP、擴充模組 I2C、規則引擎、網頁伺服器、非同步測試全在裡面),回報「卡在 http」等於什麼都沒定位到。現已拆成 15 段:led/net/io/mqtt/modbus/mqpub/scan/mbtcp/exp/rules/save/web/async/cs/ota。 |
WI-163b |
順手補上一個死角:checkOtaReboot / OTA 下載 / network.maintain() 原本在所有檢查點之外,卡在那裡會無從得知,現歸入 ota 段。 |
— | |
lastSeg 語意改為**「卡住的那一段」本身**(原本存的是「最後跑完」的段,要自己推下一段)。這個 off-by-one 極易誤讀 —— .46 首次回報 lastSeg="modbus" 第一眼就會被判成卡在 Modbus,實際卡在它後面那段。 |
— | |
🔧 穩定性修正:網頁伺服器解析 Content-Length 標頭數值的內層迴圈沒有時間上界 —— 條件被 available() 短路掉,而唯一的出口是讀到換行。若 socket 在狀態轉換的瞬間回報「有資料」但實際讀出 -1,這個迴圈會永遠轉下去、吃光記憶體,並讓主迴圈再也回不去餵狗。已改用與其餘標頭解析相同的硬性逾時。 |
— | |
| v6.0.39 | 卡點一併上雲。設備 BKPSRAM 只留最後一次(開機即清空重新累計),而 lastSeg 先前只有 GET /api/diag 拿得到 —— 等於「沒有人在線上盯著的那幾次卡死」段名永久遺失,偏偏那正是最需要它的時候。心跳改帶 lastSeg/lastSegUptime → 雲端 devices.meta,遠端/離線也查得到。需搭配 config-server ≥ 本版(心跳欄位是明列白名單,舊版會直接丟棄)。 |
WI-163b |
⚠ 殘留限制:devices.meta 每次心跳覆蓋同一列,雲端只留最新一次。多次卡死只看得到最後一次;bootCount 單調遞增,故「卡了幾次」仍然準確。完整歷史需雲端另建事件表(未做)。 |
— | |
| v6.0.40 | exp 段再切兩半。實測 .46 的 UNEXPECTED 落在 exp,但該段混著兩件性質完全不同的事:expupd(expansionManager.update() → 熱插拔偵測,內含 OptaController.cpp:630 沒有時間上界的 while (enter_while))與 expio(5 槽 I2C 讀取;唯一真走匯流排的是 updateDigitalInputs(),digitalOutRead()/digitalRead() 讀的是快取暫存器)。兩者修法完全不同,不切開分不出來。 |
WI-163c |
段序共 16 段:led→net→io→mqtt→modbus→mqpub→scan→mbtcp→expupd→expio→rules→save→web→async→cs→ota。 |
— |
實機驗證(.250,-D WEDGE_TEST 測試韌體):在網頁伺服器段內故意讓主迴圈忙碌卡死 45 秒 ——
| 檢查項 | 預測 | 實測 |
|---|---|---|
| 重開原因 | UNEXPECTED |
UNEXPECTED ✅ |
| 卡點段名 | web(wedge 端點所在段) |
lastSeg="web" ✅ |
| 重置時機 | 30s 軟體期限 + 8s IWDG | 觸發後約 37 秒 ✅ |
| 卡死當下 uptime | 103s | prevUptimeSec=103 ✅ |
Content-Length 修正的回歸測試:2/3/4 位數長度的 POST body 各送一次,回傳內容與送出完全一致;
765,608 bytes 的 OTA(6 位數長度)亦正常完成。
#近期版本重點(v6.0.36 — 看門狗誤殺:對外 TLS 連線一慢就自己重開)
🔴 影響範圍:所有連雲端 / 連 MQTT broker 的設備,不分 8310 / 8320、不分有線 / WiFi。 這是業主長期回報「設備不定時自己重開、API 不穩定」的其中一個根因。
問題:硬體看門狗(IWDG)的期限是 8 秒,但一次對外 TLS 握手可以合法地花上 10 秒以上。
createWebClient() 的 setSocketTimeout(5000) 只管「單次 socket 收送」,而 TLS 握手要好幾個
來回、每一段各自可以吃滿 5 秒。逾時預算比看門狗還長,所以只要雲端或 broker 剛好慢一下,
設備就會在一件完全正常的工作中途被自己的看門狗打死。
實測證據(v6.0.35,兩台設備一整夜):4 次 UNEXPECTED 重開,指紋完全一致 ——
全部發生在一次對外 TLS 連線開始後的 10~11 秒:
| 時間 | 設備 | 重開前最後一筆 | 間隔 |
|---|---|---|---|
| 01:32 | .46 (WiFi) | [CS-Cloud] Connecting to opta.smms.com.tw:443 |
10s |
| 02:21 | .46 (WiFi) | [CS-Cloud] Connecting to opta.smms.com.tw:443 |
10s |
| 03:41 | .46 (WiFi) | [CS-Cloud] Connecting to opta.smms.com.tw:443 |
11s |
| 04:46 | .250 (有線) | MQTT: Connecting to mosquitto.smms.com.tw(TLS) |
10s |
設備並沒有當機 —— 它在做一件正常的事,只是做得比 8 秒久。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.36 | 「卡多久算當機」由硬體 8s 改為軟體 30s。主迴圈只蓋「我還活著」時戳,改由獨立高優先權執行緒依時戳決定餵不餵狗。保護沒有變弱,只是把誤判窗口拉開。 | WI-162 |
| 保護矩陣:整個 RTOS 死掉(含餵狗執行緒)→ 沒人餵 → IWDG 8s 內 reset(不變);主迴圈卡死但 RTOS 還活著 → 逾 30s 停餵 → 最晚 38s reset(原 8s);合法的 10s TLS 握手 → 不再重開(原必定重開)。 | — | |
實機主動驗證(.250):把設定伺服器指向一個「接受 TCP 但永不回應」的黑洞,誘發 [SLOW LOOP] total=11144ms(主迴圈停頓 11.1 秒)。bootCount 全程不變 238,設備未重開。同一情境在 v6.0.35 是必定重置。 |
— |
兩側都驗過(改的是安全機制本身,只驗「不再誤報」不夠 —— 若改壞了等於把看門狗拆掉, 而且症狀靜默:要等現場某台真的卡死、永遠不恢復才會有人發現):
| 測試 | 手法 | 結果 |
|---|---|---|
| 不再誤殺(false positive) | 設定伺服器指向「接受 TCP 但永不回應」的黑洞,誘發 [SLOW LOOP] total=11144ms |
bootCount 不變,未重開 ✅ |
| 未達期限仍存活 | -D WEDGE_TEST 測試韌體,主迴圈忙碌卡死 15 秒 |
bootCount 不變,uptime 連續 ✅ |
| 真當機仍抓得到(true positive) | 同上,卡死 45 秒 | 約 37 秒被重置,bootCount 239→240、reason=UNEXPECTED,與預測的 30s+8s=38s 吻合 ✅ |
| 新韌體仍可 OTA | 跑在 6.0.36 上再發起 OTA | 成功 ✅ |
回歸測試方式:PLATFORMIO_BUILD_FLAGS="-D WEDGE_TEST" pio run -e opta,燒錄後
GET /api/debug/wedge?ms=45000。該端點以 #ifdef WEDGE_TEST 保護,正式版編不進去。
代價:真正當機的自動復原從 8 秒變成最長 38 秒。換掉的是「每隔幾小時被誤殺一次」。 更長時間的迴圈卡死另有 v5.9.250 的 gateway 探測自救(>3 分鐘自重開)接手。
現場自我檢查:設備管理頁看「重開原因」,若出現 UNEXPECTED 而設備當時明明在正常運作,
且重開時間點對得上雲端心跳或 MQTT 重連,即為此缺陷。升級至 v6.0.36 以上即解。
#近期版本重點(v6.0.32 — WiFi 上線後未掛回 QSPI:開機卡死 / 網頁失效 / 授權無法還原)
🔴 影響範圍:
interfaceMode=AUTO、未插網線、靠 WiFi 上線的設備(WIFI_ONLY 與純有線不受影響)。 此缺陷自 WI-124/125 落實 QSPI 並存時就存在(當時只改了 WIFI_ONLY 路徑),於 v6.0.32 修正。 ⚠️ v6.0.21 已因此自 stable 降回 dev;stable 目前為 v6.0.9。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.32 | WiFi 連線成功後重新掛載 QSPI:NetworkStateManager 在 AUTO 模式 Ethernet 失敗改走 WiFi 時會 unmount() QSPI,但只有 WiFi 連線失敗才 remount(),成功反而不掛回→ 整個運行階段 QSPI 皆為 unmounted。 |
ADR-014 追記 |
症狀一:開機永久卡死。licenseManager.begin() 停在 License... 不再前進,且卡點在 IWDG 啟用之前,看門狗救不回 —— 現場只能拆機 USB DFU。 |
— | |
症狀二:網頁只剩裸 HTML。/app.js、/styles.css、語系檔全部 404(首頁由韌體 PROGMEM 提供,故「看得到畫面卻完全不能用」)。 |
— | |
| 症狀三:授權無法由持久 token 還原,連帶統計/模板等 QSPI 檔案皆讀不到。 | — | |
實機驗證(.46):qspiMounted False→True、/app.js 404→200、開機由卡死→走完 [IWDG] enabled、授權由無法還原→ACTIVE。 |
— |
現場自我檢查:若設備網頁打開後版面全亂(無樣式)、或重開後停在啟動階段連不上, 且該設備是「WiFi 連線 + 未插網線」,即為此缺陷。升級至 v6.0.32 以上即解; 無法連線者需以 USB DFU 燒錄。
#近期版本重點(v6.0.20 — 通知列改為文件流內,取代 v6.0.19 的浮動版)
v6.0.19 把右下角 toast 改成浮動的置頂通知列,但實機量到它壓住頁首的 🔄 按鈕 —— 等於把 死區從右下角搬到上面。根因:容器可以
pointer-events:none,但卡片本身必須可互動(✕ 要能按), 所以只要是浮動定位就一定會蓋住底下的東西。v6.0.19 請勿發佈給客戶,用本版取代。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.20 | 通知列改放 <main> 文件流內(position:sticky):通知佔自己的版面空間、把內容往下推,結構上不可能遮到任何控制項;:empty{display:none} 讓無通知時不留空白條;沒有 <main> 的頁面(AP 模式/開荒頁)退回浮動版。這才是 AWS flashbar 的實際做法。 |
業主回報 |
| .46 實機驗證:9 顆可見按鈕 0 遮擋(6 顆被往下推);通知在畫面上時點按鈕實際送出 5 個 API 請求;✕ 關閉、同訊息合併 ×N、重開後自動重建皆正常。 | — |
#近期版本重點(v6.0.19 — 設定持久化修正 + 網頁操作阻塞修正)
三個症狀來自業主端回報:「勾選存不住」「密碼送不出去」「通知還在就不能按」。 前兩者是真 bug,第三者的真因與通知機制無關 — 是 CSS 命中測試。dev channel 先行。
⚠️ 本表在 v6.0.12 之後、v6.0.19 之前(v6.0.13 ~ v6.0.18)尚未補記,待回填。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.19 | 「變更時才發」勾選重開後消失:converterOnChange 在 LocalConfig 的存檔與讀檔兩處都漏(v5.9.263 新增時遺漏)。POST 成功、GET 讀得到,但寫進 flash 的 JSON 沒這個 key → 下次自 flash 載入即回未勾選;症狀「時有時無」取決於中間有無經過重開/設定重載。TcpIoChannel 同層 22 欄位已逐一稽核,此為唯一缺漏。 |
業主回報 |
Toast 永久攔截右下角點擊 → 改置頂通知列:舊 .toast 是 position:fixed+z-index:99999 卻無 pointer-events:none,且 .show 只切 opacity 不切 display → 元素永遠佔著右下角矩形吃掉點擊。API 沒被卡住,是點擊沒落到按鈕上。改 AWS flashbar:置頂堆疊、容器 pointer-events:none、多則並存、可 ✕ 關、同訊息併 ×N、上限 4 則。 |
業主回報 | |
登入密碼送出顯示「網路錯誤」:驗證的第一發 /api/system 無任何重試 — 全站唯一裸 fetch 的關鍵路徑(fetchWithRetry/apiSave 皆為同一毛病而生,同函式第二發 /api/tokens 早有 3 次重試)。設備忙碌時 lwIP backlog 直接 reject → 一次失敗即判死。改 4 次重試+遞增退避+每次自帶 8s timeout;401 仍即判密碼錯誤不重試;加 in-flight 閘門與按鈕 disable(登入鈕與 Enter 同綁一函式,連按會開並發連線)。 |
業主回報 | |
登入文案自帶三語系:語系字典自 QSPI 載失敗時 t() 會把壓縮短碼(如 c6)直接顯示給使用者;登入是第一個畫面也最易撞上,故整個登入流程不再依賴外部字典。 |
業主回報 |
#近期版本重點(v6.0.10 ~ v6.0.12 — MQTT 在線可觀測性:LWT + RS485 設備狀態事件 + UI 離線呈現)
📡 針對「客戶機台/控制器非 24h 在線(夜間斷電)」的觀測盲點。dev channel 先行,134 驗證後 promote stable。 v6.0.11/12 為 134 dogfood 即時修正:
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.12 | MQTT keepalive 10s(庫預設)→60s:單執行緒 loop 卡頓(HTTP/掃描/TLS,3 |
WI-158 |
| v6.0.11 | TCP IO 編輯「找不到通道」誤報修正:fetchWithRetry 從不 throw — 重試耗盡/401 一律 resolve null,v6.0.10 誤流入「找不到此通道資料」→ 現以 null 判「載入失敗(設備忙碌)」+重試鈕(134 Playwright 實證誤導)。 |
WI-158 |
狀態列排版:插入點從 .modal-h(flex 標題列,被擠成直排)改 .modal-b 頂部;清除時同步清 textContent(避免讀殘字誤判)。 |
WI-158 | |
| resync 收斂:connect-success 不再重複設旗標,統一由 reconnect-edge 觸發。 | WI-158 |
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.10 | MQTT LWT(遺囑):connect() 前 setWill({prefix}/status, "offline", retained) — 控制器斷電/斷網(不經 disconnect)時 broker 於 keepalive 逾時自動翻 retained offline。舊行為:retained online 在設備斷電後整晚誤導 MES。 |
WI-158 |
RS485 設備斷線/回線事件:狀態轉變 → retained {prefix}/rs485/slave/{slaveId}/status=online/offline;PowerMonitor 集中偵測轉變(涵蓋全機型 flip 點)入佇列,main loop 發佈(每 loop ≤4 筆);MQTT (re)connect 重發全部現況。掛 slaveId 穩定身分,與既有 rs485/{deviceIdx}/{field}(WI-074,索引會因刪機台位移)分樹。 |
WI-158 | |
UI 離線呈現:離線設備數值變暗(明示舊值)+ 徽章「離線 · 最後回應 HH:MM」(琥珀,沿 v6.0.6 過時慣例);lastUpdate==0 顯「從未連線」;回線復原。 |
WI-158 |
#近期版本重點(v6.0.9 — 出廠預設 9600 + 「載入失敗仍可儲存」防呆 + 掃描涵蓋 4800/2400)
🛡️ 源於現場事故:cfg 載入失敗時通訊埠 modal 以預設值 19200 渲染、且變更確認被繞過,按儲存把 19200 靜默寫進設備 → RS485 全離線。本版根治整族「載入失敗 → 顯示預設值 → 儲存 = 靜默改設定」。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.9 | RS485 出廠預設 19200→9600:現場電表以 9600 為大宗;initDefaultConfig、載入缺鍵 fallback、無 config 掃描起點三處對齊。 |
WI-157 |
通訊埠設定 modal 防呆(根治幽靈存檔):cfg 未載入 → 顯示「載入中/重試」,絕不以預設值渲染表單;saveBusSettings/_forceSaveBus 雙守衛,未載入一律拒存(舊行為:oldB0 與表單同時 fallback 19200 → hasChanged=false → 跳過確認直接寫入)。 |
WI-157 | |
TCP IO 編輯 modal 防呆:/api/tcpio 載入失敗不再靜默留全空白(補 .catch)→ modal 頂部狀態列 + 「🔄 重新載入」鈕;通道資料未回填完成前 saveTcpIo 拒存(防以空白覆蓋原通道)。 |
WI-157 | |
掃描涵蓋 4800/2400:scanBauds 加入兩檔(電表常見出廠值,先前永遠掃不到);組合 15→21,掃描上限 params[] 擴 24 防溢位、組合迴圈改 sizeof 推導;rs485Buses baud 白名單同步加 2400/4800(UI 選 4800/2400 需韌體 ≥6.0.9)。 |
WI-157 | |
rs485Buses 解析陣列界限修正:以 ] 為界,防陣列物件數 < 2 時掃進後續 JSON 物件(潛伏 bug)。 |
WI-157 | |
baud 變更審計 log:Updated rs485Buses[i]: baud 9600->19200 帶 old→new(事後可從 /api/log 追認誰動過)。 |
WI-157 |
#近期版本重點(v6.0.8 — SF965 原生解碼 + 移除/新增暫存器保證)
🔢 徹底解決 SF965 計數值的小數/整數與設備增刪串台問題。與 6.0.x 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.8 | SF965 原生解碼(新 dataType VAR_SF965_PV=8):SF965 PV(addr 4)為特規 32-bit —— 高 3 byte=計數值、低 byte=小數點位數(dot) → 值 = (raw>>8) / 10^dot。原生正確:dot=0 即乾淨整數、dot≠0 自動小數,不再靠 ÷256 scale 湊(舊法只在 scale 精確=1/256 且 dot=0 才對)。SF965 型號預設暫存器改用此型別 → 選型號、套預設即正確(顯示 + 發佈一致)。 |
晉榮 SF965 解碼實證 |
| 移除一台→再新增,暫存器不串台(保證):暫存器以 slaveId 綁定(6.0.7 起),刪設備觸發陣列重編號時,其他設備暫存器不受影響;再新增同 slaveId 的設備會自動掛回其暫存器。46 實測:刪 slave50 → slave51 暫存器完好 → re-add slave50 自動掛回。 | WI-153 |
#近期版本重點(v6.0.7 — RS485 離線行為改善)
🔌 針對「工廠下班/午休關機 → RS485 slave 離線」的兩個困擾。與 6.0.x 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.7 | 離線來源不發假 0:converter 模板引用到離線 RS485 設備時,整筆跳過發佈(不送 {"counter":0} 讓 MES 誤判為「計數歸零」)。機台關機/斷線期間保持沉默,回線自動恢復。實作:ConverterEngine 填充時若來源 isOnlineByIndex==false 設旗標 → pushTemplate 跳過。 |
— |
離線退避輪詢 10s→30s:OFFLINE_INTERVAL 拉長,關機期間離線 slave 少拖累單執行緒 loop(每台 poll 逐次 timeout),網頁較不卡;機台回線 30s 內偵測恢復。 |
— | |
| 前提:SF965 計數器斷電保留計數(業主確認)→ 關機一晚不丟每日累計,回線續讀。 | — |
#近期版本重點(v6.0.6 — 畫面更新健壯化 + 模板 :N 說明)
🩺 針對「Opta API 偶爾失敗導致畫面難判斷狀況」。與 6.0.x 向後相容(app.js + index.html,無新韌體邏輯)。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.6 | 失敗保留舊值(根):config/signals/actions/rules 抓取失敗時不再 || {} / || [] 清空畫面,改保留上次好資料 → 「空」不再偽裝成「失敗」。根治「總覽 RS485 區塊過一分鐘消失」類。輪詢 io/power 本就只在成功時更新。 |
— |
| 每頁針對性重整:標題旁「🔄 重新整理此頁」只重抓當前頁資料(非整頁 reload)—— 單執行緒設備一次少打幾個請求,較不易失敗。 | — | |
| 資料新鮮度顯示:標題旁「更新於 N 秒前」,超過 2× 輪詢間隔變琥珀=可能過時;成功取得資料即回綠。讓使用者一眼知道畫面是否即時。 | — | |
模板 :N 說明上線:JSON 模板欄提示 + 可用變數參考範例(${rs485.1.計數值:0}=整數);文件(spec §3.4 / 操作手冊變數表)同步。 |
— |
#近期版本重點(v6.0.5 — Converter 存檔穩定性 + 模板小數位數修飾詞)
🔧 修 Converter 模板存檔失敗、總覽 RS485 區塊消失;新增模板變數小數位數控制。與 6.0.x 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.5 | Converter 模板存檔失敗修正:存完 /api/tcpio 會觸發 MQTT 重訂閱(遠端 broker TLS 握手阻塞單執行緒 loop 數秒),緊接的模板 POST 撞死窗 →「Failed to fetch」。前端在兩 POST 間插入 _waitDeviceReady(輪詢 /api/system 到連兩次 OK)才送模板。 |
— |
apiSave 檢查 success:韌體存檔 handler 恆回 HTTP 200,成敗在 body success。前端改為 success:false 視為可重試錯誤並報錯,杜絕「說成功其實沒存」的靜默失敗。 |
— | |
總覽 RS485 區塊消失修正:refreshOverview 週期重載 config,fetch 失敗時原本 || {} 把快取清空 → RS485 卡片在忙碌設備上「過一分鐘消失」。改 || cfg(失敗保留舊值,對齊其他 loader)。 |
— | |
模板變數小數位數修飾詞:${rs485.x.field:N} — :0=整數(計數器)、:N=N 位小數、無修飾詞=預設 2 位(相容既有模板)。由模板作者逐次控制發佈格式;通用的「每暫存器小數位數」設定為後續工作。 |
— |
#近期版本重點(v6.0.4 — MQTT 連線測試 TLS 修正 + 小 UI)
🔧 修「設定頁 MQTT 測試連線在 TLS 時必失敗(直接存檔卻成功)」。與 6.0.x 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.4 | MQTT 測試改 deferred + 現有連線短路:(1) 測試排程到主 loop 執行(POST 回 202 → GET /api/mqtt/test 輪詢,前端顯示「測試中 n/30」),不在 inbound handler 內開 TLS;(2) 真兇=設備單執行緒 + 有限 mbedTLS,對「已連線的同一 broker」再開第二條 TLS 必握手失敗 → 若主 MQTT 已連到相同 broker:port+帳號+用已存密碼,直接以現有連線判定成功(既有連線本身即證明);任一參數改動則落到真實連線測試。 |
[[device_outbound_tls_in_handler_fails]] |
測試參數補洞:測試現在帶 useTls/tlsInsecure(以前只送 plain 參數);密碼欄留空 = 用已儲存密碼(對齊存檔語意,以前拿空密碼認證必失敗);補 Authorization header。 |
— | |
| 小 UI:設定頁「儲存名稱」與「📟 設備日誌」改同一列並排(原各佔一行)。 | — |
#近期版本重點(v6.0.2 — RS485 同型號多台防串台)
🔧 修 WI-153 crosstalk 家族「重開機後復發」的根因。與 6.0.x 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.2 | register↔device 綁定 slaveId 持久化:ModbusRegister.slaveId 執行期有值(存檔當下正常)但沒存進 flash → 重開機後全歸 0 → 退回 deviceIndex(陣列位置)比對 → 同型號多台(如兩台 SF965)在刪台/重排後重開機會串台。存/讀各補一行(src/LocalConfig.h),缺鍵給 0 向後相容。 |
WI-153 |
| 正確用法:同型號多台各設不同 slaveId(≤32);存檔即以 slaveId 穩定綁定,跨重開機不退化、不串台。 | — |
#近期版本重點(v6.0.1 — 遠端診斷:網頁 Serial Log + 開機原因)
🩺 遠端維運:針對「設備只能經 VPN 進入、現場無法插 USB 看 serial」的痛點。與 6.0.0 向後相容。
| 版本 | 重點 | 關聯 |
|---|---|---|
| v6.0.1 | 網頁即時 Serial Log(Tier 1):GET /api/log(PERM_ADMIN)把韌體 8KB RAM ring buffer 吐成純文字;設定頁「📟 設備日誌」按鈕或直接開 /log.html(走 QSPI 熱更)即可遠端看即時 log(RS485 輪詢 / MQTT / 開機序列)。 |
[[opta_default_admin_token]] |
開機診斷(Tier 3):GET /api/diag + 心跳帶 bootReason/bootDetail/prevUptime/bootCount → 雲端 devices.meta。設備隨後失聯,雲端仍看得到「上次為何重開 / 撐多久 / 累積開機次數」。 |
— | |
為何重開的判定:此 Opta(MCUboot)在 app 啟動前已清 RCC->RSR(實測 rawRsr 恆 0),硬體旗標不可用;改用 BKPSRAM「有意重開」標記 —— 有標記 = SOFTWARE(附細節 ota / loop-wedge / mac-revert / api-reboot / net-failover / …),無標記 = UNEXPECTED(看門狗 IWDG / 斷電 / HardFault = 當機、重開迴圈的關鍵訊號)。 |
— | |
安全:/api/log、/api/diag 需 PERM_ADMIN;修 3 處明文外洩(MQTT 密碼 POST body/mqttJson 改 redactSecrets 遮罩、license 回應改印長度、AP 密碼印 ****)—— 避免經 log ring 由網頁外洩。 |
[[webpage_blank_terser_fallback_footgun]] |
#近期版本重點(v6.0.0 — WI-155 多專案 × 多環境平台化)
🚩 v6.0.0 里程碑:自 5.9.285 起跳大版號,整合 WI-155 全系列(對外 MQTT 命名空間 + MAC 身分)。 與 5.9.x 向後相容(未設命名空間即沿用 legacy
mes/gateway/{uid})。權威 per-version changelog 仍在 cloud manifest。
| 版本 | 重點 | 卡片 / 記憶 |
|---|---|---|
| v6.0.0 | 對外 MQTT topic 命名空間:可設 {projectname}-{env}/gateway/{mac12hex}(全小寫;設定頁填專案名稱+環境),未設則 legacy mes/gateway/{uid},向後相容。 |
WI-155、[[mqtt_protocol_revamp_wi155]] |
MAC 對外身分:心跳 / whoami / info 帶 mac;雲端建 MAC↔UID 查表,relay 訂 +/gateway/# 並把命名空間 topic 改寫成 canonical(內部仍以 UID 為鍵,不破 WI-147)。 |
WI-155 | |
MAC 撞號自動解決(AC4):後到者自動降級回 legacy topic(不動硬體 MAC、不誤路由)+ 撞號排除後自癒;GET /api/devices/mac-collisions。 |
WI-155 | |
| MAC 位址軟體覆寫(設定頁進階):指定軟體 MAC(不碰硬體 OTP)+ 即時驗證 + 試用期 90s 無 IP 自動回退(防失聯);複製鈕給對外格式(無冒號小寫)。 | WI-155、[[mac_change_arp_router_interference]] | |
payload {"value":N} 雙支援(cmd/signal,相容裸數字);新欄位 i18n 三語系;修 MQTT 設定頁留空清密碼 footgun;build_webpage node --check 後盾。 |
WI-155 |
#近期版本重點(v5.9.241 ~ v5.9.244 — 8310 機型支援)
ℹ️ 權威 changelog 仍在 cloud manifest(單一來源,見上)。本段為文件側快速索引,方便對照本批 8310 相關改動, 對應 sprint cards WI-149 ~ WI-152。
| 版本 | 重點 | 卡片 / 記憶 |
|---|---|---|
| v5.9.241 | 8310 安裝根治:QSPI 空白 MBR 回 -3101,舊韌體只救 -3102 → FS 不掛載 → 設定/網路存不了。src/LocalConfig.h 救援涵蓋 -3101 + -3102,任何空白 QSPI 設備第一次燒就自我建分割區 + 格式化(相容 8320,init 成功就跳過)。 |
WI-149 前置、[[opta_8310_qspi_blank_mbr_3101]] |
| v5.9.242 ~ .244 | 拓荒包(Pioneer)韌體變體 + 開荒設定頁(env:opta_pioneer,WI-149):自包單一網頁(UID + 網路 + 大「上傳 .mesb」鈕),USB 首燒即可用。 |
WI-149 |
型號感知 UI 過濾(WI-151):OTP 讀板型 → g_hasWifi,曝光 /api/system、/api/license 的 hasWifi/boardModel;8310 拓荒頁與完整版網路設定過濾掉 WiFi(隱藏 SSID/密碼 + 介面模式選單,強制乙太網路)。 |
WI-151、[[opta_otp_board_functionalities]] | |
| 8310 心跳燈(WI-152):藍燈是 BLE 模組燈(僅 WiFi 版能亮),8310 改用**琥珀色(紅+綠同亮)**心跳,8320 維持藍燈。 | WI-152 | |
.mesb 一鍵完整安裝 + favicon + ETag 修正:.mesb 打包韌體 + 6 資產 + favicon.ico;拓荒頁內嵌真 favicon;根網頁 ETag 併入頁面大小,避免 pioneer→完整版(同版號)OTA 後顯示舊快取頁。 |
WI-149 |
注意:AP 模式需要 WiFi,只有 8320 能進;8310 無 WiFi 進不了 AP(會 fallback)。User button 開機長按:0-5s 正常 / 5-10s AP / 10s+ 原廠重置。
#相關程式(原始碼,非本站文件)
scripts/release_firmware.py— 打包腳本(含resolve_changelog)config-server/modules/firmware.js— Cloud upload endpointconfig-server/public/index.html— Admin UI 韌體列表
#1.1.9 測試候選 — 2026-10-27(未發布)
- Slave worker 改為接收事件喚醒,只有尚未完成的 RTU frame 使用 1 ms 跟進;保留 Normal 優先權與原堆疊。首次 I/O 快照會喚醒尚未處理的接收事件。
- Slave 總覽隱藏已暫停的外接設備,顯示本機 DI 與 Slave ID;原設備設定保留。
- 主機測試已通過;實機並行驗證待執行,不沿用 1.1.8 的短輪結果作為放行。
#1.1.10 排程對照候選 — 2026-10-27(尚未安裝,未發布)
保留 1.1.9 的事件喚醒與總覽修正,只將 Slave worker 從 Normal 改為 AboveNormal,測試 RTU 回覆是否受同優先權工作干擾。不恢復舊版閒置時每毫秒醒來的行為。1.1.9 實機仍有 HTTP 連線拒絕及 RS485 離線退避,不能視為穩定版。
#1.1.11 診斷候選(2026-10-27,未發布)
- Master FC03 短回覆或無回覆時,交易結束後輸出
[RTU-T] id write_us turnaround_us first_rx_us received_bytes。不在 TX 窗口列印;目前為診斷,尚非根因修復。 - 保留事件型 Slave worker 與總覽角色顯示,待大小檢查及實機驗證。