出廠測試規範
版本:3.4 | 日期:2026-08-28 | 韌體版本:6.0.83(基線) 來源 PRD:
docs/prd/prd-factory-test.md(內部文件,未公開) v1.2(結構基線)+ 本文件 v2.1 增量(依 6.0.8 源碼/實機核校) 操作手冊:guide-user-operation-manual.mdv2.9 修訂摘要(2026-08-26,完整重跑驗收流程後):
- 新增 RG.7(WI-206:Parser「值類型」下拉的 option value 必須對得上韌體
ParserValueType)。該缺陷是走情境 M.3 時抓到的:選「數值」實際會得到布林, 任何非零數字變成1.0。RG.7 必須斷言 UI 的 option value —— 只用 API 送parserType:1驗「韌體會給數值」是抓不到的(韌體本來就對,錯的是前端綁定)。- 韌體基線 6.0.72 → 6.0.74(WI-206 + WI-197)。
- 全流程重跑結果:
test:gate35/35、test:regression7/7。v2.8 修訂摘要(2026-08-26,六張卡修完並實機驗證):
test:regression全數通過,已納入test:gate:full—— WI-199/200/202/203/205 五個缺陷 已修並經「先紅後綠」驗證(修正前紅、修正後綠,同一台設備同一支測試); WI-201 經查是誤判(WI-170 的寫入佇列早就處理了),已大幅更正該卡。- 韌體基線 6.0.69 → 6.0.72,經 LAN
.mesbOTA 上機,test:gate35/35。- 新增 RG.6(WI-205:被拒絕的規則 POST 不得毀掉既有規則)。
- 順帶修好一個從沒被測到的舊 bug:
modbusTcpWriteDO的擴充分支對區域副本取址轉型, Modbus TCP 寫擴充輸出一直沒有作用;已與規則引擎統一用getExpansionPtr。v2.7 修訂摘要(2026-08-26,與驗收走查對齊):
- 新增
npm run test:regression(RG.1–RG.5) —— 驗收走查挖到五個缺陷, 既有閘門一個都沒攔到。這五項是它們的回歸,目前預期紅燈,修好前不列入出貨門檻。- 修正一個實驗證明的閘門盲點:
2B原本只斷言「TCP 通道」標題可見, 注入 WI-202 的壞值(整份 TCP IO 設定在網頁上消失)後仍 6/6 全綠。 已改為比對「設備上有幾個通道」與「畫面上列出幾個」。- 環境預設值對齊:
factory-env.js的DUT_URL改.77(有線)、EXPECT_FW改6.0.69;package.json內的舊值一併更新。ST.2改為非破壞性:原本寫死index: 7,設備規則數 ≥ 8 時會覆蓋現場既有規則 —— 2026-08-26 實際毀掉一條A2-MQTT亮。已改用「附加位置」(ruleCount)。 該次紅燈同時揭露韌體缺陷 WI-205(回 400 卻仍寫入且不回滾),已補RG.6回歸。- 測試設定來源收斂:先前 6 支 spec 各自寫死
DUT_URL預設(3 支.46、3 支.77),factory-v6的EXPECT_FW還停在6.0.8—— 文件卻宣稱「換設備只改一個檔」。 已全部改為從factory-env.js取值,那句承諾現在才是真的。- 已知未處理:
package.json有 4 處明文 broker 密碼(見 WI-204,交付優先暫緩)。v2.6 修訂摘要(2026-08-20,交付定案版):
- 測項與腳本對齊(文件先前寫錯):
test:converter是 5 項 +afterAll收尾(CV.9 是清理不是測項);test:enable是 7 項,EN.5(停用 RS485 設備)已從腳本移除 —— 該卡片在畫面上只有「刪除」沒有停用開關, 測一個使用者碰不到的控制項沒有意義;新增 EN.8(停用 Config Server 總開關)。- 新增章節「本批韌性修正:閘門測不到的那些」:WI-175/180/181/182/183/189 六項, 列出各自「為什麼會壞」與「怎麼驗的」。這張表的用途是改到那塊之前先讀,不是測試清單。
- 新增
npm run test:gate:full:唯讀閘門 + 三支會改動設備狀態的套件一次跑完(交機前用)。test:gate維持唯讀語意不變(OTA 後、現場都能安全跑)。- 本文件已發佈到雲端文件中心
/docs/factory-test.html。v2.1 修訂摘要(2026-07-16,韌體 6.0.4 → 6.0.8 對齊 + 源碼核校修正):
- 韌體基線 6.0.4 → 6.0.8:全文版號斷言改
v6.0.8;新增 v6.0.5~6.0.8 能力。- 新增自動化套件
web/tests/factory-v6.spec.js(13 tests,純 HTTP 唯讀契約):Phase 0 機型身分 + Phase 6B 遠端診斷 + Phase 2E.6a MQTT 測試契約 + Phase 2E.6b namespace 欄位;npm run test:factory:v6。已對實機 8320(6.0.8)全綠。- ⚠️ 源碼核校修正欄位名(舊文件錯,自動化已抓出):
/api/diag實際欄位 =resetReason(非bootReason)、bootDetail、prevResetReason、bootCount、prevUptimeSec(非prevUptime)、uptimeSec、rawRsr、fwVersion。/api/systemboardModel值 = 字串"Opta RS485 (8310)"/"Opta WiFi (8320)"(非"8310"/"8320");hasWifi為 boolean;兩者一致。mac在/api/config(非/api/system),格式為冒號大寫(如A8:61:0A:50:8B:61);對外 topic 12-hex 身分 = 去冒號轉小寫。- SF965 原生解碼(6.0.8):H3 新增
VAR_SF965_PV(dataType=8)手動驗證列(需 SF965 硬體;PV@addr4 高 3 byte=值、低 byte=小數點)。- 離線來源不發假 0(6.0.7):Phase 4 新增 H3-B3(converter 引用離線 RS485 來源 → 整筆跳過發佈,不送假 0 誤導 MES)。
v2.0 修訂摘要(2026-07-02,韌體 5.9.45 → 6.0.4 對齊):
- 韌體基線 5.9.45 → 6.0.4:全文版號斷言改
v6.0.4;Phase 0 讀boardModel/hasWifi/mac。- 機型維度(8310 / 8320)新增:變體識別從單一「網路變體」擴為 機型 × 網路 二維(8310 無 WiFi / 8320 有 WiFi),來源 = OTP
boardInfo().wifi。新增 Phase 4 H8 機型感知 UI 過濾 + 心跳燈(8310 隱藏 WiFi 欄位 + 琥珀燈;8320 露 WiFi + 藍燈)。- WI-155 平台化(6.0.0):新增 Phase 2E MQTT 命名空間(
projectName+env→{proj}-{env}/gateway/{mac},留空 = legacy)與 MAC 軟體覆寫 + 90s 試用回退 驗證。- 遠端診斷(6.0.1):新增 Phase 6B 遠端維運健檢——
/api/log網頁 Serial Log(/log.html)+/api/diag開機原因(BKPSRAM intent 標記)+ 密碼遮罩不外洩。- RS485 slaveId 持久化(6.0.2):H3 新增 同型號多台防串台 驗證(各設不同 slaveId ≤32,重開機後不退回陣列位置、不串台)。
- MQTT 測試連線修正(6.0.4):Phase 2E MQTT 測試改 deferred(POST 202 → GET
/api/mqtt/test輪詢)+ 對已連線同 broker 短路判定;密碼留空 = 用已存密碼。- 授權撤銷門控(WI-145):Phase 7 補「撤銷後 TCPIO/MQTT 擋、DIO/管理不擋」的門控驗證。
- MQTT TLS 出廠預設:
tlsInsecure=true(業主指定,不驗 CA)——Phase 2E 斷言此為出廠預設,勿改回驗 CA。- Web 部署模型:index.html 編進韌體、app.js 等 6 資產走 QSPI 熱更——出廠燒錄後首開機須確認網頁完整(非裸 HTML)。
- 附錄 A 補
/api/log、/api/diag、/api/mqtt/test、/api/license/token端點。v1.2 修訂摘要(2026-05-27):
- H3-C 新增 Step 5 校正引導精靈 chain 驗證(4 步驟 + 燈號 + 順序強制,v5.9.130+)
- 附錄 A 補
/api/signals、/api/actions、/api/converter/template端點- Phase 7 註記 wizard / wdprobe / watchdog 屬部署佈建非出廠基線
- 相關:
spec-mqtt-chain-workflow.md、guide-calibration-wizard.md、guide-failover-resilience.mdv1.1 修訂摘要:
- 韌體版本對齊:5.9.25 → 5.9.45
- 治具校正:Expansion 1 → Expansion 0(array 從 0 起);YX523R Slave ID 2 → 4
- 新增 MQTT broker fixture(FR-G E2E 必需)與變體識別(ethernet/wifi)
- Phase 結構從 0
5 擴充為 08,補上 OTA、E2E 業務流、出貨前清理、結果輸出- 各步驟加上 PRD FR-* 對應 ID + DOM ID 速查表
- 補上 EEPROM DIP+斷電 物理步驟(先前完全漏掉)
- 附錄 C 列 PRD v1.2 已知 UI bug 對工站的影響
#⚡ 先跑自動化閘門(v2.2 起,任何改動後都必須執行)
任何韌體或網頁改動之後,先跑這一道再談其他。 它是唯讀的(不改設備設定), 交機前、OTA 後都能安全執行:
cd web
DUT_URL=http://<設備IP> EXPECT_FW=<版本> npm run test:gate涵蓋 35 項:頁面渲染(2A–2F)、v6 能力契約、穩定性與持久化(ST.*)。 全數通過才算通過 —— 任何一項紅燈都代表出貨品質有缺口。
另有三支會改動設備狀態、不在閘門內、交機前各跑一次:
npm run test:converter # ★ MQTT Converter 鏈路(產品主幹),素材自建自清,約 3 分鐘
npm run test:enable # 啟用/停用機制(⚠ 見下方說明,目前不作為出貨門檻)
npm run test:backup # ★ 雲端備份/還原閉環(會讓設備重開一次),約 2 分鐘
npm run test:persist # 真的重開設備,驗設定與統計跨重開存活(約 1 分鐘)
npm run test:factory # 完整功能測(含 MQTT 佈建,會寫入設定)#驗收走查回歸(npm run test:regression,RG.1–RG.8)—— 全數通過,已納入出貨門檻
2026-08-25/26 照著驗收單實機走了一遍,挖到五個缺陷。既有出廠閘門一個都沒攔到, 而且不是「剛好沒測到」,是測試的形狀不對 —— 既有測項驗的是「東西在不在、頁面開不開」, 五個缺陷全部落在「內容對不對、值有沒有真的寫進去」。
最直接的證據:把 WI-202 的壞值注回設備(整份 TCP IO 設定在網頁上消失、
顯示「尚無 TCP IO 通道」),factory-pages 依然 6/6 全綠。閘門對它完全無感。
cd web
DUT_URL=http://<設備IP> npm run test:regression| 測項 | 對應卡 | 驗什麼 | 預設 |
|---|---|---|---|
| RG.1 | WI-202 | 字串欄位含 " \ 時,各 GET 端點仍須回合法 JSON,且值讀回不得被截斷 |
執行 |
| RG.2 | WI-199 | 網頁上連續快速新增兩條規則,兩條都要在(必須走瀏覽器 —— 缺陷在前端算 index 的邏輯,直接打 API 碰不到) | 執行 |
| RG.3 | WI-170/WI-201 | 網頁上連續改多個通道名稱,全部都要寫入(守 WI-170 的寫入佇列還在) | 執行 |
| RG.4 | WI-200 | POST /api/io/do 要能驅動擴充板輸出 |
執行 |
| RG.5 | WI-203 | 存 Converter 模板後立刻重開,長度須與模板一致 | 略過(需 RG_ALLOW_REBOOT=1) |
| RG.6 | WI-205 | 被拒絕(400)的規則 POST 不得毀掉既有規則,也不得留下啟用中的死規則 | 執行 |
| RG.7 | WI-206 | Parser「值類型」下拉的 option value 必須對得上韌體列舉(選「數值」不能拿到布林)—— 斷言 UI 的 option value,不是 API 回值 | 執行(需 MQTT 憑證) |
| RG.8 | WI-208 / WI-208b | 主迴圈不得被授權處理凍結 —— 兩條斷言:① 安定後 300 秒內 [WI-208] gate 重驗 須為 0 次;② 同一窗內 net 段不得有任何超過 1 秒的卡頓。窗長 300 秒是為了蓋過雲端心跳的 127 秒週期(至少 2 次),否則 WI-208b 那條路徑抓不到 |
執行(約 5 分) |
只有 RG.5 預設略過(它會重開設備,要跑帶 RG_ALLOW_REBOOT=1;已實測通過)。
⚠️ 2026-08-27:
RG.7在此之前從來沒有真的執行過。package.json的test:regression是唯一沒帶 MQTT 環境變數的腳本 (旁邊四個都有),RG.7 因此每次都靜默skip—— 報表顯示「6 passed」, 看起來很健康。已補齊環境變數,現在會真的跑。教訓:
skipped不是中性的。一個永遠跳過的測項,和沒有這個測項是一樣的, 但它在報表上長得像有在守。看回歸結果時要同時看 skipped 的數量與原因。
⚠️ 2026-08-28:
RG.7在整套連跑時偶發假紅燈,已修測試方法。 單獨跑綠、RG.5+RG.6+RG.7最小組合也綠,只有整套跑會紅 —— 而整套跑到 RG.7 時 設備剛經歷 RG.5 的重開加一輪負載。原本的寫法是「建通道 → 固定等 2.5 秒 → 發一次 → 固定等 5 秒 → 讀一次」。MQTT 對「訂閱之前就發出的訊息」沒有補送 —— 設備若還沒訂好新通道的 topic,那則訊息就永遠消失,測試必紅。已改為週期重發 + 輪詢讀回(45 秒內每三輪重發一次)。這個教訓
acceptance_run.py早就記過(「通道剛建好時設備可能還沒訂閱」),RG.7 沒套用。誠實記一筆:那次的錯誤訊息因為輸出被 grep 過濾而遺失,根因是從程式碼推斷、 不是從錯誤訊息實證。修正本身站得住(移除的是本檔已咬過兩次的同一類問題), 但若日後 RG.7 再紅,不要假設就是這個原因。
教訓:跑長時間測試套件時,不要用
grep過濾輸出 —— 失敗細節只有一次機會保留。
⚠️ RG.2 / RG.3 必須走瀏覽器,不能用 API 並發代替。 這兩個缺陷都在前端 (
saveRule算 index 的邏輯、saveIoName走 WI-170 的寫入佇列),直接打 API 測到的是我們自己的測試碼,不是產品。初版就是這樣寫的:RG.2 一次就綠(bug 還在)、 RG.3 則「測出」一個其實不存在的缺陷(WI-201 因此大幅更正)。
2026-08-27 追加
RG.8(WI-208)。 業主人工驗收時回報「藍色呼吸燈會卡住, 卡住的時候按鈕就延遲、甚至沒反應」—— 量出主迴圈有 6.8% 的時間完全凍結, 真兇是授權門控每 30 秒重驗一次簽章(663ms)。改為到期驅動後降到約 0.8%。這一項有實測的鑑別力,不是推論:修正的第一版(v6.0.76)把
expiresAt==0誤當成「1970 年就過期」,快取完全沒生效 —— 同樣的 200 秒觀測窗裡gate 重驗出現 8 次。RG.8 的判準當時會紅。2026-08-27 更新:七項全綠(RG.5 另以
RG_ALLOW_REBOOT=1單獨驗過)。 這一輪另外修掉兩個測試自身的問題,兩個都會產生假紅燈: ① RG.2/RG.3 用固定 4 秒等window.nav備妥 —— 單獨跑夠,整套跑時設備有負載就不夠 (實測單獨 2 秒即備妥、整套跑時逾時)。已改為輪詢至函式真的存在。 ② RG.3 用譯文/儲存中|Saving/判斷寫入佇列是否忙碌,但徽章實際顯示「💾 bH 1」 ——io.saving這個 i18n 鍵三個語系檔全缺(WI-207),t()查不到時回傳鍵本身, 讓|| "儲存中"變成死碼。判斷失效 → 提早讀取 → 假紅燈。已改為比對不會被翻譯的💾。2026-08-26 更新:六張卡全部修好,這一組已全綠。 每一項都經過「先紅後綠」—— 修正前該測項紅、修正後綠,同一台設備、同一支測試。 韌體 v6.0.72 經 LAN
.mesbOTA 上機驗證,test:gate35/35、test:regression6/6。 已納入test:gate:full,現在它真的有在守。
test:converter是最該跑的一支。 業主買的就是「MQTT 進來能驅動輸出、 量測值能定時送出去」—— 頁面全部正常但這條鏈斷掉,設備等於沒有用。 它會自建CV-*前綴的素材、測完自己刪光,並以「素材必須清乾淨」作為最後一項斷言。
#閘門守著的「已知會壞的那幾類」(逐項對應真實事故)
| 測項 | 守的是什麼 | 來源事故 |
|---|---|---|
| ST.0 / ST.9 | 測試期間設備不得重開 | 功能全對但設備會重開,對業主而言就是壞的 |
| ST.1 | 連續請求不得連兩次失敗 | 併發過頭會把閘道器打重開(WI-176) |
| ST.2 / ST.3 | 規則不得存成永遠不觸發 | 下拉未載入就存 → signalId=0(WI-174) |
| ST.4 | 設定寫入後讀回一致 | 「回成功但沒進去」的靜默失敗 |
| ST.5 | 統計/開機計數有落盤 | 重開後統計歸零(WI-164) |
| ST.6 | index.html / app.js / 語系檔三者同版 | 短碼位移 → 全站標籤指到錯字串(WI-172/185b) |
| ST.7 | WiFi 帳密不得為空 | 無聲清空 → 重開後失聯(WI-169) |
| ST.8 | 救援頁可用、主畫面沒退回救援頁 | index.html 移到 QSPI 後的發佈陷阱(WI-178) |
| ST.10 | 欄位名打錯要回 400 | 靜默照收 → 整合商拿著 success 找三天(WI-179) |
| ST.11 | TCP 來源的不可能條件要被拒 | 設了「>50」永遠不成立且無提示(WI-184) |
| ST.12 | 改 token 名稱/權限不必連帶換密碼 | 畫面寫「留空=不變」但兩端都沒實作(WI-188) |
| 2F | 畫面不得殘留 {UID} 這類佔位符 |
該告訴使用者的資訊變成一串符號(WI-185) |
| ST.13 | I/O 名稱改了要能存進去 | 名稱是現場辨識訊號的唯一依據,存不進去整頁沒用 |
| ST.14 | 現場硬體與環境設定檔相符 | 換了設備卻沒更新設定檔 → 這裡先紅,不必等功能測莫名失敗才回頭猜 |
| ST.15 | 雲端備份設定完整(有 cloudUrl 就要有 cloudToken) | 還原舊快照會把 token 一起還原成空 → 備份能寫不能讀,而且只壞一半所以難察覺 |
#MQTT Converter 鏈路(npm run test:converter,5 項 + 收尾清理)
| 測項 | 守的是什麼 |
|---|---|
| CV.0 | 佈建入向通道 / 輸入群組 / 規則 / 輸出群組(素材自建,不碰現場設定) |
| CV.1 | 入向 Parser:MQTT JSON 的數值要進到通道 |
| CV.2 | 端對端:MQTT → 輸入群組 → 規則 → 輸出群組 → 實體 DO |
| CV.3 | 出向 Converter:閘門關不發、開了照間隔發 |
| CV.4 | onChange:值不變只發一次;閘門關再開要補發(v5.9.266 —— 否則使用者重新觸發卻沒反應) |
| (afterAll) | 收尾:本檔建立的素材必須全部清乾淨(留下殘骸就是污染現場設定) |
這幾項固化的是 2026-08-18 夜間的完整手動驗證。當時結論只存在於對話裡, 下次改動不會有人幫忙擋 —— 這正是「測試樣本要全面覆蓋」的意思。
#啟用/停用機制(npm run test:enable,7 項)
畫面上到處是啟用/停用的開關,先前一個都沒測。這類機制壞掉的方式特別惡劣: 「畫面顯示停用、實際還在動」(業主以為關掉了,機器卻自己跑), 或「停用之後再啟用回不來」(現場只能重開機碰運氣)。兩種都不會有錯誤訊息。
| 測項 | 守的是什麼 |
|---|---|
| EN.0 | 佈建 + 基線:鏈路本身要先是通的(否則「停用後不動」毫無意義) |
| EN.1 | 停用規則 → 觸發後輸出不動;重新啟用要回得來 |
| EN.2 | 停用輸入群組 → 規則不觸發(signalSlotUsable 的 fail-safe) |
| EN.3 | 停用輸出群組 → 規則觸發但輸出不動 |
| EN.4 | 停用TCP 通道 → 不再接收 MQTT(值凍結);重新啟用要收得到 |
| EN.8 | 停用Config Server 總開關 → 不得再對外推送(業主關掉雲端同步就必須真的停) |
| EN.6 | 停用token → 該憑證不得再通過認證(以為撤銷了、其實還能用 = 安全問題) |
⚠
test:enable目前不列入出貨門檻。 它已經達成目的 —— 2026-08-20 完整跑過 7/7,證實六種停用機制的語意都正確 (停用真的停、重新啟用都回得來)。但這支測試本身還不夠穩: 測項之間有順序耦合(前一項的雲端等待會影響後一項的量測)、 依賴 MQTT 環境變數(用npx playwright -g單獨跑會因前綴/密碼未設而失敗)。 結論可信,工具待磨。修穩之前不要拿它當出貨紅綠燈,否則會訓練人忽略紅燈。 | 收尾 | 用afterAll清素材(不能寫成最後一個 test,見下方) |
每一項都同時驗兩個方向:停用要真的停、重新啟用要回得來。 只驗前者的話,「停用之後就再也回不來」這種更難救的缺陷會漏掉。
收尾一定要用
afterAll,不能寫成最後一個 test。 序列模式下只要前面任何一項失敗, 後續 test 就不會執行 → 素材殘留 → 下一輪再建一份同名的 → 名稱查找抓到第一份 → 鏈路指向錯的通道 → 之後每一輪都失敗。2026-08-20 實際踩到:一次失敗滾成兩份素材, 手動清掉才恢復。 一律走「真的送 MQTT、真的看實體 DO」,不看欄位存不存得進去(那是 ST.4 的事)。
#雲端備份 / 還原閉環(npm run test:backup,4 項)
備份/還原是所有其他測試的安全網 —— 出廠測試會寫入設定、現場調參數也會出錯, 能不能救回來全靠它。它壞掉時最危險的地方是平常看不出來:設備→雲端的推送 有自己的授權路徑、照樣 HTTP 200,只有真的要「讀回來」時才發現 401。
| 測項 | 守的是什麼 |
|---|---|
| BK.0 | 雲端設定完整(沒有 token = 能寫不能讀) |
| BK.1 | 改設定 → 推送 → 雲端真的出現新快照(不是只回 200) |
| BK.2 | 還原前一份 → 改動要被還原回去(閉環的關鍵一步) |
| BK.3 | 還原後雲端連線不得斷掉(否則還原一次就不能再還原 = 自斷後路) |
寫這幾項時踩了兩個判準錯誤,值得記下來:
- 不能用「快照數量增加」當判準 —— 雲端會裁切舊快照,數量可能不變;要比對最新那份有沒有換人。
- 設定沒變更時推送不會產生新快照,那是正確行為(內容相同不重複存,實測連推 80 秒都不會多)。 所以測試要先真的改一個欄位再推,否則會把正確行為誤判成「回成功但沒發生」。
維護原則:每修一個缺陷,就在這裡加一列。 出廠測試的價值來自「它涵蓋了已知會壞的東西」,不是「它有很多項」。
#本批韌性修正:閘門測不到的那些(v6.0.41 → 6.0.65)
上面的表格是「有自動化守著」的部分。但這批交付還改了一些自動化測不到、卻直接影響 現場可靠度的行為。列在這裡不是為了完整,是因為下一個人改到這些地方時要知道當初為什麼這樣寫 —— 沒有測試會替他擋。
| 項目 | 改了什麼 | 為什麼會壞 | 怎麼驗的 |
|---|---|---|---|
| WI-175 | QSPI 長時間寫入期間持續餵狗(loopKeepAlive) |
OTA 撞上背景雲端 TLS → 主迴圈被佔住 → 看門狗砍掉升級 | 實機:雲端握手後 0.6s 觸發 OTA,守衛等 3765ms 後成功 |
| WI-180/180b | 設定存檔原子化:寫暫存 → 驗證 → 確認主檔有效才降級為備份 → rename | 舊流程會在主檔還沒驗證前就覆蓋備份,一次斷電兩份全毀 | 單元級:刻意寫入半截 JSON,重開後仍載入得到有效設定 |
| WI-181/181b | WiFi 帳密改用 RAM 暫存再落盤 | beginWiFi() 期間 QSPI 未掛載,在那裡讀寫會永久卡死、只能 DFU |
實機:此路徑曾兩次把 .46 變磚,修正後重開 5 次帳密皆存活 |
| WI-182 | 設定下載先試地端、再試雲端 | 地端 config server 不在時整個還原路徑斷掉 | 實機:拔掉地端來源仍能從雲端取回 |
| WI-183/183b | 對外連線硬性總時限 + 雲端卡死自我復原 | connect() 對黑洞位址可阻塞 5 分鐘以上,期間整台設備沒有回應 |
黑洞位址實測:確認阻塞>5min,並在對抗性輪詢下觸發自我復原 |
| WI-189 | 分頁切到背景時停掉輪詢與 telemetry | 背景分頁持續要資料,排擠掉真正重要的控制迴圈 | 見 控制延遲特性 的前後對照 |
這張表的維護方式和上面那張不同。 上面那張是「每修一個缺陷加一列測試」; 這張是「改到這塊之前先讀這一列」。如果哪天有辦法替其中一項寫出自動化測試, 就把它搬到上面那張表去 —— 那才是真正被守住了。
#🔧 換設備 / 換現場時要改哪裡
測試分成兩類,換環境時只有第二類需要動:
| 類別 | 內容 | 換設備要改嗎 |
|---|---|---|
| 環境無關 | 契約與穩定性(ST.1–ST.13、2A–2F、V6-*)。這些測項自己建立所需的素材、測完自己清掉 | ❌ 不用 |
| 環境相關 | 現場實際接了什麼:RS485 型號與 slave、擴充模組數量、MQTT broker | ✅ 改 web/tests/factory-env.js |
#只改一個檔:web/tests/factory-env.js
const HARDWARE = {
// RS485 上預期存在的設備。空陣列 = 該站沒有 RS485,相關測項自動略過
rs485: [ { slaveId: 1, model: 'finder6m', name: '電流計' } ],
// 預期的擴充模組「實際連線」數量。0 = 沒有擴充槽
expansionModules: 1,
hostDoWiring: '4 個 DO 接三色燈(DO1=紅 DO2=綠 DO3=藍 DO4=蜂鳴器)',
};改完之後 ST.14 會替你把關:設定檔宣告的硬體與設備實際回報不符就直接紅燈, 不必等某個功能測莫名其妙失敗才回頭猜哪裡不一樣。
#常見情境
| 情境 | 怎麼改 |
|---|---|
| 換一台設備(IP 不同) | DUT_URL=http://新IP npm run test:gate,不必改檔 |
| 對外 MQTT topic 前綴 | 不必改 —— 留空時測試會向設備問它自己的 UID 自動組出來 |
| RS485 換型號或 slave | 改 HARDWARE.rs485(model 用 /api/config 的 modbusDevices[].type 值) |
| 該站沒有 RS485 | EXPECT_RS485='' npm run test:gate,或改檔案的 rs485: [] |
| 一份設定檔要服務多台不同配置 | 用環境變數逐台覆寫,不必改檔:EXPECT_RS485='1:finder6m:電流計' EXPECT_EXPANSIONS=1(8320)EXPECT_RS485='' EXPECT_EXPANSIONS=0(8310) |
| 沒有擴充模組 | EXPECT_EXPANSIONS=0 → 自動略過 |
| 韌體出新版 | EXPECT_FW=6.0.xx npm run test:gate,或改 factory-env.js 的預設值 |
原則:測試碼一行都不該為了換環境而改。要改就改設定檔 —— 改測試碼會讓「這一站為什麼要特別處理」的知識散落各處,下一個人接手就看不懂。
#文件用途
本文件是正式工站操作規範,供產線測試人員逐步執行。所有步驟可由一位測試員在單一工站完成。
自動化實作(v5.9.130+,跑真實 DUT):
🚀 一鍵測試(自動偵測 UID + 前置檢查 + 跑功能+頁面):
bash ./scripts/run-factory-test.sh [DUT_IP] [ADMIN_TOKEN] # 預設 192.168.72.77 / smmsadmin;輸出 12 tests PASS/FAIL
套件 涵蓋 執行 web/tests/factory-functional.spec.js決定性韌體能力(beforeAll 自佈建 fixtures,不依賴設備現狀/RS485):連線、MQTT 輸入、2-source AND 順序強制、signal→rule→CH_TCP publish→broker loopback(6 tests) npm run test:factory:funcweb/tests/factory-pages.spec.jsPhase 2 五頁面渲染(Overview/Config/Rules/Devices/Settings,5 tests) npm run test:factory:pagesweb/tests/factory-v6.spec.jsv6.0.x 能力回歸(純 HTTP 唯讀契約,不改 config):Phase 0 機型身分(hasWifi/boardModel/version/mac)、Phase 6B 遠端診斷(/api/log + /api/diag + auth 401)、Phase 2E.6a MQTT 測試契約(202+輪詢)、Phase 2E.6b namespace 欄位 + TLS 出廠預設(13 tests) npm run test:factory:v6web/tests/factory-cleanup.spec.jsPhase 7 出貨清理(FR-H,部分 test.skip 待韌體 endpoint) — 一次跑功能+頁面+v6 回歸:
npm run test:factory(於web/,共 24 tests)或./scripts/run-factory-test.sh(repo root,自動偵測 UID)。EXPECT_FW環境變數鎖定出廠版號基線(預設6.0.8)。決定性原則(出廠品質基準):functional 測試在
beforeAll自己 idempotent 佈建 fixtures(wdprobe + ft_* 引擎元件)+ 清 retained,不依賴設備現有 config、不依賴 RS485 硬體。因此同韌體 → 同結果;唯有韌體能力改變才會讓結果變。對出廠空機也成立。fixtures 由 Phase 7 一併清除。 物理治具步驟(砝碼/SG-002/DI 開關/拔網路線)無法自動化,仍由本文件手動 ☐ 執行。 ⚠️ 跑前 watchdog 須暫停(會 reboot 干擾)。
#工站前置條件
#治具接線
| 連接點 | 接線對象 | API/UI 位置 | 用途 |
|---|---|---|---|
| Host DI 1 | Toggle Button | /api/io di[0] |
數位輸入驗證 |
| Host AI 2 | FNIRSI-SG-002 信號產生器(0–10V) | /api/io ai[1] |
類比輸入驗證 |
| Host DO 1~4 | 三色燈 + 蜂鳴器(紅/綠/藍/蜂鳴) | /api/io do[0..3] |
數位輸出驗證 |
| Expansion 0 DO 3~6 | 第二組三色燈 + 蜂鳴器 | expansions[0].doNames[2..5] |
擴充模組驗證 |
| RS485 Bus 0 | Finder 6M.TB(Slave ID=1, Baud 9600, machineId=48) | /api/config modbusDevices[0] |
電力表讀取 |
| RS485 Bus 0 | YX523R 光警器(Slave ID=4, machineId=24325) | /api/config modbusDevices[1] |
RS485 寫入 |
| Ethernet | 工站電腦區網 | — | Web UI + Playwright |
| MQTT Broker (新增) | 工站本地 Mosquitto(明文 1883) | factory/<topicPrefix>/... |
FR-G E2E 雙向訊息 |
#平台容量基準(硬體衍生,6.0.8 沿用)
| 項目 | 容量 | 用途 |
|---|---|---|
| Host DI / DO / AI | 8 / 4 / 3 | 主機 I/O |
| Expansion 槽位 | 5(Exp0~Exp4,目前 fixture 只用 Exp0) | 擴充模組 |
| Modbus 設備 / Register | 12 / 32 | RS485 |
| RS485 Bus | 韌體 2 條,UI 只外露 Bus 0(Bus 1 由 FR-H1-03 確保不誤露) | — |
#變體識別(機型 × 網路,二維)
工站開始前必須讀 /api/system 判定 兩個維度,並寫入結果摘要 metadata:
維度 1 — 機型(來源 = OTP boardInfo().wifi,不可靠 WiFi.status()):
hasWifi=false/boardModel="8310"→ 8310 機型(無 WiFi 模組):強制乙太網路;設定頁隱藏 WiFi 欄位;心跳燈 = 琥珀色(紅+綠同亮);H6 Failover Skip;AP 模式進不了。hasWifi=true/boardModel="8320"→ 8320 機型(有 WiFi):可 WiFi;心跳燈 = 藍燈(LEDB);H6 Failover 依網路變體決定。
維度 2 — 網路變體(讀 network.netType 與 /api/config interfaceMode):
netType="Ethernet"且interfaceMode=0/1→ ethernet 變體(H6 Failover Skip)netType="WiFi"或interfaceMode=2→ wifi 變體(H6 Failover 必測,僅 8320 可能)
⚠️ 8310 恆為 ethernet 變體(無 WiFi 硬體)。wifi 變體必為 8320。
工站 profile 需於啟動時設 model: 8310|8320 + variant: ethernet|wifi,寫入結果摘要 metadata。
#啟動檢查清單
- DUT 已上電,Status LED 閃爍中
- DUT 已取得 IP,工站電腦可 ping 通
- 瀏覽器可開啟 DUT Web UI(
http://<DUT-IP>) - RS485 設備已上電(Finder + YX523R 兩台)
- SG-002 信號產生器已上電
- MQTT Broker 已啟動(明文 1883)
- 機型 + 網路變體 已從
/api/system讀取並寫入結果摘要 - 首次燒錄後首開機:Web UI 完整渲染(非裸 HTML;app.js 走 QSPI 已載入)
#Phase 0:連線檢查(FR-F、FR-A1-07)
| # | 步驟 | 驗收條件 | 類型 |
|---|---|---|---|
| 0.1 | 開啟瀏覽器,連接 DUT Web UI | Dashboard 頁面載入完成;無 JS error;非裸 HTML | 自動 |
| 0.2 | 確認左下角韌體版本號 #sideVersion |
顯示 v6.0.8(/api/system version;自動化 V6-ID.3 斷言,可 EXPECT_FW 覆寫) |
自動 |
| 0.3 | 記錄 DUT UID #sysDeviceUid |
24-hex 格式 | 自動 |
| 0.4 | 讀取 /api/config configCrc |
寫入結果摘要(firmware.configCrcReference) |
自動 |
| 0.5 | 讀取 network.netType 判定網路變體 |
profile 對齊;異常則 fail | 自動 |
| 0.6 | 讀取 /api/system boardModel/hasWifi 判定機型 |
hasWifi=boolean;boardModel="Opta RS485 (8310)"(hasWifi=false)/ "Opta WiFi (8320)"(hasWifi=true);兩者一致;profile 對齊(自動化 V6-ID.1/2) |
自動 |
| 0.7 | 讀取 /api/config mac |
冒號大寫 MAC(如 A8:61:0A:50:8B:61);對外 topic 身分 = 去冒號轉小寫 12-hex;寫入結果摘要(自動化 V6-ID.4) |
自動 |
測試記錄:
DUT UID: ___________________
DUT MAC: ___________________ (12-hex 對外身分)
FW Version: 6.0.8 (預期)
Board Model: 8310 | 8320
Variant: ethernet | wifi
configCrc: ___________________ (出廠 baseline)
測試日期: ___________________
測試員: ___________________#Phase 1:初始化(FR-C1)
| # | 步驟 | 驗收條件 | 類型 |
|---|---|---|---|
| 1.1 | 清空所有殘留規則(含未命名「Rule 2」) | /api/rules ruleCount = 0 |
自動 |
| 1.2 | 關閉所有 DO 輸出 | /api/io do[] 全為 false |
自動 |
| 1.3 | 載入 baseline 設定檔(含 Finder + YX523R) | 設定載入 Toast,configCrc 等於 baseline 預期值 |
自動 |
| 1.4 | 確認 eepromPendingReboot == false |
/api/system 欄位 |
自動 |
#Phase 2:UI 驗證
#2A. Dashboard 總覽頁(FR-A1)
| # | 測試項目 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 2A.1 | 統計卡片 | #diCnt / #doCnt / #ruleCnt 非空 |
FR-A1-01 |
| 2A.2 | MQTT 卡片四態 | #mqttSt 為「未設定/連線中/已連線/失敗」之一 |
FR-A1-02 |
| 2A.3 | Modbus TCP 卡片 | 卡片靜態存在;⚠️ 已知 UI bug:硬編碼顯示「Port 502」(FR-A1-03 註腳,PRD 附錄 E E-01) | FR-A1-03 |
| 2A.4 | 物理 / Expansion 切換 Tab | 🏠 主機 8DI / 4DO ↔ Digital-SSR #0 16DI / 8DO 可切換 |
FR-A1-04 |
| 2A.5 | RS485 卡片 — Finder 12 量測 | 12 格全顯示(電壓 V/電流 A/有功 W/無功 var/視在 VA/PF/頻率 Hz/諧波 %/總/正/反向 kWh),無 NaN | FR-A1-05 |
| 2A.6 | 頂部控制列 | Poll Rate Selector + IP + 連線狀態徽章 | FR-A1-06 + A1-10 |
| 2A.7 | 側邊欄頁尾 | 系統時間跳動 + #sideVersion 顯示 v6.0.8 |
FR-A1-07 |
| 2A.8 | I/O 卡片三態徽章 | DI 顯示「待機/未啟用」;DO 顯示「關閉/開啟/未啟用」 | FR-A1-08 |
| 2A.9 | 每張卡片 ↺ 清除統計 | 點按只重設該通道 | FR-A1-09 |
#2B. Config 配置頁(FR-A2)
| # | 測試項目 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 2B.1 | Tab 切換 | 🏠 主機 / Digital-SSR #0 可切;物理通道 / 虛擬通道 / TCP 通道 三子分頁可切 |
FR-A2-01 / 02 |
| 2B.2 | TCP 通道列表 | 進頁面後等 5 秒,#tcpChannelsList 渲染為清單或「尚無 TCP IO 通道」(不可保留「載入中...」) |
FR-A2-02b |
| 2B.3 | 物理輸入「資料型態」下拉 | I1~I8 每行有 數位/類比 dropdown |
FR-A2-02c |
| 2B.4 | DO 標籤 1-based | 物理通道輸出列表顯示 DO1~`DO4;不可出現 DO0` |
FR-A2-02d / WI-102 |
| 2B.5 | 虛擬通道類型 | 計數器 / 計時器 / 類比邏輯三型可切換 | FR-A2-03 |
| 2B.6 | 設備樣板 | 「PATLITE NE-M1ATB-M」+「Toggle Button」可點擊套用 | FR-A2-06 |
| 2B.7 | TCP IO Modal | + 新增通道 → #tcpIoModal 開啟 + 欄位完整 |
FR-A2-08~15 |
| 2B.8 | 群組 Modal | #groupModal 編輯成員清單 + Device Tag |
FR-A2-16 |
| 2B.9 | 儲存回饋 | #toast 出現綠色成功訊息 |
FR-A2-04 / FR-A6-01 |
#2C. Rules 規則頁(FR-A3)
| # | 測試項目 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 2C.1 | 規則列表 | #rulesList 不停在「載入中」 |
FR-A3-01 |
| 2C.2 | 優先權提示 banner | 顯示「越下方的規則擁有最終覆蓋優先權」 | FR-A3-05 |
| 2C.3 | 規則卡片優先權徽章 | 每卡片標題前綴 🟢/🟡/🔴 之一(priority 0/1/2 對應) | FR-A3-06b / WI-101 |
| 2C.4 | 新增規則 | + 新增規則 → 右側 inline 編輯器 #ruleEditor 開啟 |
FR-A3-02 |
| 2C.5 | 三級優先權選擇器 | #rulePriority 下拉含 🟢 一般 / 🟡 高 / 🔴 緊急 (互鎖) |
FR-A3-06 |
| 2C.6 | 互鎖實測 | 兩條互斥規則 + 一條設「緊急」→ DO 最終由互鎖決定 | FR-A3-07 |
| 2C.7 | 啟停切換 | toggle 後 /api/rules 與畫面同步 |
FR-A3-03 |
#2D. Devices 設備頁(FR-A4)
| # | 測試項目 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 2D.1 | 5 槽 Expansion 卡片 | #expSlots 顯示 5 槽,未連接者標「未連接」 |
FR-A4-01 |
| 2D.2 | 匯流排掃描 | #btnScan → 進度條 + 結果列表(含 Finder slave 1, YX523R slave 4) |
FR-A4-02 |
| 2D.3 | Modbus Probe | #btnProbe 對 Slave 1, Reg 0, Count 4 → 成功且 raw hex 有資料 |
FR-A4-03 |
| 2D.4 | EEPROM Modal 開啟 | openEepromModal() → 顯示橘色警告「DIP OFF/OFF + 斷電上電」 |
FR-A4-04 |
| 2D.5 | RS485 Modal 11 fieldMask | 新增 RS485 設備 → 「總覽顯示」11 個 checkbox 全可勾選 | FR-A4-05 |
| 2D.6 | RS485 樣板 | dropdown 含 Finder 6M / YX523R / 自訂三選項 | FR-A4-06 |
| 2D.7 | Bus Settings 不外露 Bus 1 | #busSettingsModal 內僅 Port 1,無 Port 2 / bus1* |
FR-A4-08 + FR-H1-03 |
#2E. Settings 設定頁(FR-A5)
| # | 測試項目 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 2E.1 | 裝置識別 | #cfgName + #sysDeviceUid 可讀;UID 點擊複製 |
FR-A5-01 |
| 2E.2 | 系統時間 | #sysCurrentTime.value 每秒跳動(用 .value 不是 .innerText) |
FR-A5-03 |
| 2E.3 | 授權卡片 | #sysExpiresAt.value 顯示「永久授權」/具體日期/「未授權」 |
FR-A5-03 |
| 2E.4 | License 啟用 | #btnLicenseActivate → 輪詢 /api/license 直到 ACTIVE |
FR-A5-03 |
| 2E.5 | License Renewal | #renewalTokenInput + renewLicense() 路徑 |
FR-A5-11 |
| 2E.6 | MQTT 設定 | Broker / Port / Client / Auth 欄位儲存;TLS 已實作(#mqttTls 勾選 + #mqttTlsInsecure);出廠預設 tlsInsecure=true(不驗 CA,業主指定,勿改回) |
FR-A5-02 |
| 2E.6a | MQTT 測試連線(v6.0.4) | 點「測試連線」→ POST 回 202 → 前端輪詢 /api/mqtt/test 顯示「測試中 n/30」→ 結果成功/失敗;密碼欄留空 = 用已存密碼;對已連線同 broker 直接短路判定成功 |
FR-A5-02 |
| 2E.6b | MQTT 命名空間(WI-155 / 6.0.0) | #mqttProject+#mqttEnv 兩欄留空 = legacy mes/gateway/{uid};填值 = {proj}-{env}/gateway/{mac}(全小寫);儲存後心跳 topic 對齊 |
FR-A5-02 |
| 2E.7 | Config Server 雙位址 + auto-disable banner | #csUrl + #csCloudUrl 各自獨立;configServerAutoDisabled=true 時 banner 顯示 |
FR-A5-04 / 15 |
| 2E.8 | 網路模式三選一 | #netModeSelect radio:自動 / 僅有線 / 僅 WiFi |
FR-A5-05 |
| 2E.9 | DHCP/Static 切換 | #ethDHCP ↔ #ethStatic 對應顯示 / 隱藏 IP 欄位 |
FR-A5-21 |
| 2E.10 | WiFi AP 掃描流程(變體 = wifi) | switchToApMode() → scanWifi() → 選 SSID → 儲存並重啟 |
FR-A5-06 / 20 |
| 2E.11 | OTA 雙路徑 | 拖曳 #otaFileInput + 伺服器 #otaVersionSelect |
FR-A5-08 / 23 |
| 2E.12 | 破壞性維護防呆 | 重啟 / 恢復原廠 / 清除 三 Modal 各有遮罩 | FR-A5-09 |
| 2E.13 | MAC 軟體覆寫(進階,WI-155) | 進階區可填軟體 MAC(不碰硬體 OTP)+ 即時驗證;設定後 90s 內無 IP 自動回退 防失聯;複製鈕給對外格式(無冒號小寫) | FR-A5(WI-155) |
| 2E.14 | 📟 設備日誌 按鈕(v6.0.1) | 設定頁「儲存名稱」與「📟 設備日誌」同一列並排(v6.0.4 UI);點按開 /log.html 顯示即時 serial log |
FR-A5(WI-155-遠端診斷) |
#Phase 3:跨頁與持久化驗證(FR-B)
| # | 步驟 | 驗收條件 | 對應 PRD |
|---|---|---|---|
| 3.1 | 修改 Device Name + I/O 別名 | 出現成功 Toast | FR-B1-01 / 03 |
| 3.2 | F5 重新整理 | 設定保留 | FR-B3-02 |
| 3.3 | 觸發系統重啟(rebootDevice() → #rebootOverlay 倒數) |
90 秒內回上線 | FR-B3-03 / FR-A5-22 |
| 3.4 | 重啟後驗證設定未掉 | 步驟 3.1 修改仍在 | FR-B3-03 |
| 3.5 | 跨頁設定驅動呈現驗證 | Config Alias 改 → Dashboard 卡片標題同步 | FR-B1-01 |
| 3.6 | TCP IO 持久化 | 新增 channel → 重啟後仍存在 | FR-B1-07 |
#Phase 4:硬體煙霧測試(FR-C)
#H1. 初始化
| # | 步驟 | 驗收條件 |
|---|---|---|
| H1.1 | 清空規則綁定 | /api/rules ruleCount = 0 |
| H1.2 | 關閉所有 DO | 全部 OFF |
#H2. Host / Expansion DO 跑馬燈(FR-C2)
測試員操作:觀察燈號與通道對應
| # | 動作 | 預期結果 | Pass/Fail |
|---|---|---|---|
| H2.1 | Host DO 1 ON (1秒) | 🔴 紅燈亮 | ☐ |
| H2.2 | Host DO 2 ON (1秒) | 🟢 綠燈亮 | ☐ |
| H2.3 | Host DO 3 ON (1秒) | 🔵 藍燈亮 | ☐ |
| H2.4 | Host DO 4 ON (1秒) | 🔊 蜂鳴器響 | ☐ |
| H2.5 | Expansion 0 DO 3 ON (1秒) | 🔴 第二組紅燈亮 | ☐ |
| H2.6 | Expansion 0 DO 4 ON (1秒) | 🟢 第二組綠燈亮 | ☐ |
| H2.7 | Expansion 0 DO 5 ON (1秒) | 🔵 第二組藍燈亮 | ☐ |
| H2.8 | Expansion 0 DO 6 ON (1秒) | 🔊 第二組蜂鳴器響 | ☐ |
#H3. RS485 驗證(FR-C3)
#H3-A. YX523R 光警器寫入(Slave ID=4)
透過 Dashboard
setDO()或POST /api/modbus/manual_write(FR-I1-02)
| # | 命令 | 暫存器 | 寫入值 | 預期結果 | Pass/Fail |
|---|---|---|---|---|---|
| H3.1 | 紅燈長亮 | 17 | 0x11 |
光警器顯示紅色 | ☐ |
| H3.2 | 綠燈長亮 | 17 | 0x13 |
光警器顯示綠色 | ☐ |
| H3.3 | 紅燈爆閃 | 17 | 0x31 |
紅燈爆閃 2 秒 | ☐ |
| H3.4 | 關閉 | 17 | 0x60 |
警報熄滅 | ☐ |
人工確認點:測試員目視確認光警器燈色 / 爆閃模式(FR-C3-01)
#H3-B. Finder 6M.TB 讀取驗證(Slave ID=1)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.5 | Dashboard RS485 卡片顯示 Finder 12 量測 | 12 格非 NaN,數值合理 | ☐ |
| H3.6 | Modbus Probe Slave 1 / Reg 0 / Count 4 | 回應成功,rawHex 有資料 | ☐ |
#H3-B2. 同型號多台 slaveId 持久化(v6.0.2,選做)
驗 6.0.2 修正:
ModbusRegister.slaveId存進 flash,重開機後不歸零、不退回陣列位置比對 → 同型號多台不串台。需兩台同型號設備(如兩台 SF965)。單台工站可跳過並標H3-B2: SKIPPED (單台)。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.6a | 兩台同型號各設不同 slaveId(≤32) 並儲存 | 兩張卡片各顯示自己 slaveId 的即時值,無交叉 | ☐ |
| H3.6b | 重開機後回到 Dashboard | 兩台仍以 slaveId 穩定綁定;值不互換、不串台 | ☐ |
#H3-B3. 離線來源不發假 0(v6.0.7,選做)
驗 6.0.7:converter 模板引用到離線 RS485 來源時,整筆跳過發佈(不送
{"counter":0}讓 MES 誤判計數歸零)。ConverterEngine::pushTemplate依_fillHadOfflineSource跳過。需 broker 訂閱觀測。單台工站可跳過並標H3-B3: SKIPPED。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.6c | 建 converter 模板引用某 RS485 來源 → 閘控發佈,訂閱其 topic | 來源在線時:每期收到含實值的 payload | ☐ |
| H3.6d | 拔線 / 關機讓來源離線,續觀測 topic | 完全靜默(不送 0);設備 log 出現 skip publish: RS485 source offline |
☐ |
| H3.6e | 來源回線 | 自動恢復發佈實值 | ☐ |
#H3-B4. SF965 計數器原生解碼(v6.0.8,選做,需 SF965 硬體)
驗 6.0.8 新 dataType
VAR_SF965_PV(=8):SF965 PV@addr4 為特規 32-bit——高 3 byte=計數值、低 byte=小數點位數(dot)→ 值 =(raw>>8)/10^dot。選 SF965 型號套預設暫存器即用此型別(不再靠÷256scale 湊)。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.6f | RS485 新增設備選「SF965 計數器」樣板 → 套預設暫存器 | PV 暫存器 dataType 顯示 SF965 PV(=8);儲存後讀回仍為 8 |
☐ |
| H3.6g | Dashboard 顯示 SF965 計數即時值 | dot=0 為乾淨整數、dot≠0 自動小數;與機台面板顯示一致(顯示 + 發佈一致) | ☐ |
#H3-C. TDA-08B 稱重控制器情境驗證(Slave ID=1,v5.9.111 sensor-fusion 計件器情境)
對應源碼提交
9cea4cd(v5.9.111)TDA-08B + sensor-fusion 虛擬通道 + 計數器 peak-detection。 v5.9.112+ 已移除 TDA-08B 模板,本情境必須手動建立暫存器與虛擬通道。 完整逐欄位 / 標定流程 / 排錯:docs/hardware/setup-tda-08b.md(內部文件,未公開) 治具:TDA-08B 稱重控制器 1 台、5kg 標準砝碼、感測器(壓力 / 應變片)。❗ curl 配置時 JSON 必須 compact(無空格):Python 用
json.dumps(obj, separators=(',', ':')),否則 firmware parser 靜默忽略。詳見 setup-tda-08b.md §2B。
#Step 1:手動建立 RS485 設備(Slave ID=1, 9600/8N1, ABCD Big-Endian)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.7 | RS485 設定 → 新增「自訂設備」名為 TDA-08B 稱重控制器,Slave=1,輪詢 2000 ms |
卡片顯示 🟢 在線 | ☐ |
| H3.8 | 加 5 筆讀取暫存器(FC03):實時值@11(INT32) / 穩定狀態@9(UINT16) / 線上狀態@10(UINT16) / 峰值@43(INT32) / 谷值@75(INT32) |
5 行顯示 ✅ 啟用,倍率=1,偏移=0 | ☐ |
| H3.9 | 加 2 筆寫入暫存器(FC06):標定指令@113(UINT16) / 砝碼值@108(UINT16) |
2 行顯示,模式=Write | ☐ |
| H3.10 | 儲存設備設定 | 設備卡片顯示 7 個暫存器即時值 | ☐ |
#Step 2:建立 Scale 虛擬通道(sensor-fusion)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.11 | Config → 邏輯類型「⚖️ 秤重計(重量→件數)」新增虛擬通道 | Scale 通道卡片出現 | ☐ |
| H3.12 | Scale 設定 — 來源=Modbus TDA-08B 實時值,tare=空秤值,unitWeight=單件重量(克),tolerance=±10% |
公式預覽顯示 round((讀值 − tare) / uw) |
☐ |
| H3.13 | 空秤點「⚙️ 自動校準」→ 寫入 標定指令=4(清零) |
實時值歸零 ±0.5g | ☐ |
#Step 3:計數器 peak-detection 規則
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.14 | 新增計數器虛擬通道 合格計件器,triggerSource=Scale.currentCount,threshold=1 |
通道卡片顯示 count=0 | ☐ |
| H3.15 | 規則:合格達標 (count ≥ 10) → MQTT 通知 mes/gateway/{uid}/scale/done |
規則 🟢 Active | ☐ |
| H3.16 | 📢 提示:放上 1 顆 5g 標準砝碼於秤上 | Scale.currentCount = 1,計數 +1 | ☐ |
| H3.17 | 重複 9 次(共 10 顆砝碼) | 每次讀值穩定在 unitWeight ± tolerance 才 +1;雜訊或半顆不算 | ☐ |
| H3.18 | 達到 count=10 觸發 MQTT 通知 | broker 收到 scale/done 訊息 |
☐ |
#Step 4:標定指令往返驗證
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.19 | 規則「MQTT→清零」: 外部 mes/gateway/{uid}/scale/calibrate → Modbus Write 標定指令=4 |
TDA-08B 實時值歸零 | ☐ |
| H3.20 | 規則「MQTT→零點」: 外部 ... /scale/zero → 標定指令=1 |
TDA-08B 重新標定零點 | ☐ |
| H3.21 | (選做)寫 砝碼值=5000 + 標定指令=2 標定增益 |
5g 砝碼讀值 = 5000 ±5 | ☐ |
人工確認點:H3.16 / H3.17 / H3.21 需測試員實際操作砝碼並目視確認讀值穩定(FR-C3-01 延伸)
#Step 5:校正引導精靈 chain 驗證(v5.9.130+,選做)
驗證 MQTT-Chain 工作流校正精靈(4 步驟 + 燈號)。需先佈建 wizard(見
guide-calibration-wizard.md§5)。無 wizard 佈建則跳過並標H3 Step5: SKIPPED。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H3.22 | 重置:發 …/cmd/signal/3=1→0 |
…/cmd/signal/0(state)=0,燈號全滅 |
☐ |
| H3.23 | 推進標零:發 …/cmd/signal/1=1→0(瞬時) |
收到 …/rules/{n}/trigger 校正標零;state→1;第一顆燈亮 |
☐ |
| H3.24 | 順序強制:state=0 時發 …/cmd/signal/2(標增益) |
不該 fire(AND 條件 state==1 不成立) | ☐ |
| H3.25 | 推進標增益:state=1 時發 …/cmd/signal/2=1→0 |
state→2;第二顆燈亮;reg108/113 寫入 | ☐ |
| H3.26 | (有砝碼)放 5kg 砝碼跑標零→標增益完整流程 | weight 校到 5.00 ±0.01 | ☐ |
人工確認點:H3.23/H3.25 目視燈號切換;H3.26 需實體砝碼。
#H4. 類比輸入 AI 驗證(FR-C4)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H4.1 | 📢 提示測試員:將 SG-002 調至 5.00V | — | — |
| H4.2 | 等待 3 秒後讀取 /api/io ai[1] |
值在 5.00V ±5%(4.75 ~ 5.25V) | ☐ |
| H4.3 | 📢 提示測試員:將 SG-002 調至 0.00V | — | — |
| H4.4 | 等待 3 秒後讀取 /api/io ai[1] |
值在 0 ~ 0.25V | ☐ |
容差數值在 HW 拆工後由
web/tests/fixtures/factory-baseline.json鎖定。
#H5. 數位輸入 DI 驗證(FR-C5)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H5.1 | 📢 提示測試員:按下 DI 1 開關 | — | — |
| H5.2 | 等待偵測 /api/io di[0] = true |
10 秒內偵測到 | ☐ |
| H5.3 | 📢 提示測試員:放開 DI 1 開關 | — | — |
| H5.4 | 等待偵測 /api/io di[0] = false |
10 秒內偵測到 | ☐ |
#H6. 網路 Failover 驗證(變體 = wifi 限定,FR-C6)
Skip 條件:
variant != wifi時整個章節跳過,並在報告標H6: SKIPPED (ethernet-only)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H6.1 | 📢 拔除 Ethernet 網路線 | — | — |
| H6.2 | 等 30 秒,DUT 自動切到 WiFi | WiFi IP 可 ping,MQTT 重連 | ☐ |
| H6.3 | 📢 插回 Ethernet | — | — |
| H6.4 | 等 30 秒,DUT 切回 Ethernet | 有線 IP 可 ping,MQTT 重連 | ☐ |
#H7. 跨協議多對多路由(FR-C7)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| H7.1 | Fan-out: TCP Parser → Host DO 1 + Expansion DO 3 + MQTT Converter | 一次外部 MQTT 觸發 → 兩燈同亮 + 收到 broker 推送 | ☐ |
| H7.2 | 防迴圈: Converter Topic = Parser Topic | 10 秒內無 MCU 重啟 / 無 watchdog | ☐ |
#H8. 機型感知 UI 過濾 + 心跳燈(WI-151 / WI-152,8310 / 8320)
來源 = OTP
boardInfo().wifi。此章節依機型分流:8310 驗「無 WiFi + 琥珀燈」,8320 驗「有 WiFi + 藍燈」。
| # | 步驟 | 8310(hasWifi=false) | 8320(hasWifi=true) | Pass/Fail |
|---|---|---|---|---|
| H8.1 | /api/system hasWifi/boardModel |
false / 8310 |
true / 8320 |
☐ |
| H8.2 | 設定頁網路區 WiFi 欄位 | 隱藏 SSID/密碼 + 介面模式選單(強制乙太網路) | 露出 SSID/密碼 + 三選一模式 | ☐ |
| H8.3 | 📢 目視心跳燈 | 琥珀色(紅+綠同亮) | 藍燈(LEDB) | ☐ |
| H8.4 | AP 模式進入 | 進不了(無 WiFi,會 fallback) | 可進 AP(switchToApMode()) |
☐ |
#Phase 5:端到端業務流(FR-G)
必須在 Phase 4 全通過後才執行。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| 5.1 | 配置 1 個 DI / 1 個 TCP Parser / 1 個虛擬輸入 | 三者 Enabled | ☐ |
| 5.2 | 配置 1 個 DO / 1 個 TCP Converter / 1 個虛擬輸出 | 三者 Enabled | ☐ |
| 5.3 | 建立 IG_All_Inputs / OG_All_Outputs 各納 3 件 |
群組 Modal 儲存成功 | ☐ |
| 5.4 | 建立 Rule_E2E:IG_All_Inputs → OG_All_Outputs(priority 0) |
規則卡片含 🟢 徽章 | ☐ |
| 5.5 | 觸發外部 MQTT → Parser 命中 | 實體 DO 點亮 + Broker 收到 Converter 推送 | ☐ |
| 5.6 | Dashboard 三類卡片同步 Active | Input Group / Rule / Output Group 皆變色 | ☐ |
#Phase 6:OTA + 升級迴歸(FR-D / FR-E)
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| 6.1 | 上傳 OTA payload(拖曳或伺服器版本) | #otaProgressBar 100% 完成 |
☐ |
| 6.2 | 觸發重啟 | RTC Backup Register swap flag 設為待 swap | ☐ |
| 6.3 | 📢 重啟期間目視確認 | 無紅燈閃爍(紅燈 = swap fail) | ☐ |
| 6.4 | 重啟後比對版本 | /api/system version 已切到新版,無 rollback |
☐ |
| 6.5 | 跨版本升級驗證(N-1 → N) | 既有 Rule / Modbus / MQTT 設定無損;新欄位有預設值 | ☐ |
| 6.6 | 模擬 OTA 中斷後復原 | 5 分鐘內以舊版可開機 | ☐ |
#Phase 6B:遠端維運健檢(v6.0.1)
驗證 VPN-only / 現場無 USB 情境的遠端診斷能力。需
PERM_ADMIN(Bearersmmsadmin)。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| 6B.1 | GET /api/log(帶 Bearer) |
回純文字 ring buffer(text/plain;RS485 輪詢 / MQTT / 開機序列可見);無 Bearer → 401(自動化 V6-DIAG.1/2) |
☐ |
| 6B.2 | 開 /log.html(走 QSPI) |
頁面渲染即時 log;設定頁「📟 設備日誌」按鈕可達 | ☐ |
| 6B.3 | GET /api/diag(帶 Bearer) |
回 resetReason/bootDetail/prevResetReason/bootCount/prevUptimeSec/uptimeSec/rawRsr/fwVersion;fwVersion 應等於 /api/system version;無 Bearer → 401(自動化 V6-DIAG.3/4/5) |
☐ |
| 6B.4 | 主動重啟後查 resetReason |
標記正確重開 → SOFTWARE(bootDetail 附細節);非當機不應標 UNEXPECTED |
☐ |
| 6B.5 | 密碼遮罩不外洩 | log 內 MQTT/AP/license 密碼皆遮罩(****/長度),無明文 |
☐ |
⚠️ 勿以「打整頁 + 高頻並發」壓
/api/log(會觸 IWDG 重開);/是 gzip,curl 用--compressed。
#Phase 7:出貨前清理(FR-H)
由 WI-103 自動化腳本執行。
| # | 步驟 | 驗收條件 | Pass/Fail |
|---|---|---|---|
| 7.0 | 授權撤銷門控(WI-145,選做) | 撤銷狀態下:TCPIO / MQTT 對外功能被擋;DIO / 管理介面不擋(設備仍可設定/救援);撤銷 banner 於下次心跳(~127s)顯示 | ☐ |
| 7.1 | License Reset → /api/license status = UNLICENSED |
重啟後維持 UNLICENSED | ☐ |
| 7.2 | 清除測試殘留(Rules / Virtual / TCP IO / MQTT broker / Config Server / WiFi 密碼) | 各端點回應全空 / 預設值 | ☐ |
| 7.3 | 確認 #busSettingsModal 不外露 Bus 1 |
DOM 不含 bus1* element |
☐ |
| 7.4 | 記錄出廠 baseline configCrc |
寫入結果摘要 + DUT QR 標籤 | ☐ |
| 7.5 | 重啟一次 → 30 秒內三 endpoint 200 | /api/system /api/config /api/io |
☐ |
註 — 校正精靈 / wdprobe / watchdog:H3-C Step5 佈建的 wizard signals/actions/rules、以及 watchdog 用的
wdprobesignal,屬部署時佈建、非出廠基線,會被步驟 7.2 一併清除。出廠不保留;現場部署時依guide-first-deployment.md§6 重新佈建。
#Phase 8:結果輸出與彙整(FR-F)
#總結表
| Phase | 測試項目數 | Pass | Fail | Skip |
|---|---|---|---|---|
| Phase 0 連線 | 7 | |||
| Phase 1 初始化 | 4 | |||
| Phase 2 UI(A~E) | 45 | |||
| Phase 3 跨頁/持久化 | 6 | |||
| Phase 4 硬體(H1~H8) | 32(H6 wifi/8320 限定 4;H3-B2 需兩台同型;H3-B3 需 broker 觀測;H3-B4 需 SF965;H8 依機型分流) | |||
| Phase 5 E2E | 6 | |||
| Phase 6 OTA + 升級 | 6 | |||
| Phase 6B 遠端診斷 | 5 | |||
| Phase 7 出貨清理 | 6 | |||
| 合計 | 117(8310/ethernet 變體約 111,視選做項而定) |
自動化覆蓋:
factory-functional(6)+factory-pages(5)+factory-v6(13,含 Phase 0 身分 4 / Phase 6B 診斷 5 / Phase 2E.6a-6b 契約 4)= 24 tests 一鍵(npm run test:factory)。其餘為需硬體/人工目視的手動項(H2~H8 燈號/砝碼/DI/AI、Phase 6 OTA、Phase 7 清理等)。
#結果摘要 metadata
DUT UID: ___________________
DUT MAC: ___________________ (12-hex 對外身分)
Board Model: 8310 | 8320
Variant: ethernet | wifi
FW Version (前): 6.0.8
FW Version (後): ___________________ (Phase 6 後)
configCrc (前): ___________________
configCrc (後): ___________________ (Phase 7 後)
Boot Reason: ___________________ (Phase 6B /api/diag)
測試日期: ___________________
測試員: ___________________
工站位置: ___________________
PRD v1.2 已知 bug 命中: E-01 / E-04 / E-08 / E-09 (列舉)
擋出貨 bug: 無 | E-XX#判定
- ☐ PASS — 全部通過 + 0 個擋出貨 bug → 可出貨
- ☐ CONDITIONAL — 全部通過 + 含 🟡 不擋 bug → 可出貨但記錄
- ☐ FAIL — 任一擋出貨 bug 或 Phase 失敗 → 退回工程修復
#失敗項目記錄
| Phase # | 描述 | 對應 PRD FR / 附錄 D 編號 | 對應 sprint card | 備註 |
|---|---|---|---|---|
#附錄 A:API 快速參考
| 用途 | API | Method | 對應 PRD |
|---|---|---|---|
| 韌體 + 系統摘要 | /api/system |
GET | FR-0 |
| 全配置 roundtrip | /api/config |
GET / POST | FR-A5-10 |
| Config Server payload | /api/config_server |
GET | FR-A5-04 |
| I/O 即時值 | /api/io |
GET | FR-C |
| 直接驅動 DO | /api/io/do |
POST | FR-I1-01 |
| 規則 | /api/rules |
GET / POST | FR-A3 |
| 訊號群組(Signal) | /api/signals |
GET / POST | FR-A3 |
| 動作群組(Action) | /api/actions |
GET / POST | FR-A3 |
| TCP IO | /api/tcpio |
GET / POST | FR-A2 |
| Converter 模板 | /api/converter/template |
GET / POST | FR-A2 |
| 授權狀態 | /api/license |
GET | FR-A5-03 |
| 授權啟用 / 續約 | /api/license/activate / /renew |
POST | FR-A5-03 / 11 |
| 授權憑證遞送(救 outbound 壞設備) | /api/license/token |
POST | WI-147 |
| 網頁 Serial Log(v6.0.1) | /api/log |
GET(PERM_ADMIN) | Phase 6B |
| 開機診斷(v6.0.1) | /api/diag |
GET(PERM_ADMIN) | Phase 6B |
| MQTT 測試連線(v6.0.4) | /api/mqtt/test |
POST(202)/ GET(輪詢) | Phase 2E.6a |
| OTA 上傳 / 伺服器 | /api/ota/upload / /api/ota/server |
POST | FR-D / FR-A5-08 |
| Modbus 掃描 / Probe / 改 EEPROM / 手寫 | /api/modbus/{scan, probe, eeprom_write, manual_write} |
POST | FR-A4 / FR-I |
| 重啟 / 恢復原廠 | /api/reboot / /api/factory_reset |
POST | FR-A5-09 / FR-H1-01 |
不存在的端點(404,腳本不可引用):
/api/devices、/api/status、/api/health、/api/version、/api/expansion、/api/tcp_io、/api/templates。
#附錄 B:操作手冊章節對照
| 出廠測試 Phase | 操作手冊章節 |
|---|---|
| Phase 0 連線 | §6 首次上線流程 |
| Phase 2A Dashboard | §8 總覽頁操作 |
| Phase 2B Config | §9 配置頁操作 |
| Phase 2C Rules | §10 規則頁操作 |
| Phase 2D Devices | §11 設備頁與 RS485 操作(含 EEPROM) |
| Phase 2E Settings | §12 設定頁操作 |
| Phase 3 持久化 | §14 備份、還原與工廠重置 |
| Phase 4 H2 DO | §9.2 設定 DO1-DO4 輸出 |
| Phase 4 H3 RS485 | §11.2a 自訂 Modbus 設備 / §11.2b 手動寫入 |
| Phase 4 H4 AI | §9.3 類比邏輯處理 |
| Phase 4 H5 DI | §9.1 設定 I1-I8 輸入 |
| Phase 4 H7 多對多 | §10 規則引擎 + TCP IO |
| Phase 6 OTA | §13 韌體更新 |
#附錄 C:PRD v1.2 已知 UI bug 對工站的影響
| Bug ID | 說明 | 影響 Phase | 處理方式 |
|---|---|---|---|
| E-01 | Modbus TCP 卡片硬寫 Port 502 | Phase 2A.3 | 腳本對 /api/config tcp.port=5000 斷言;UI 文字差異記為 known issue |
| E-03 | Rules 卡片 priority 0 缺 🟢 徽章 | Phase 2C.3 | ✅ 已修(WI-101) |
| E-04 | HTTP server 並發 reset | Phase 2D 全段 | 腳本層序列化 fetch;FR-A5-24 韌體層長期解 |
| E-06 | Config DO 0-based | Phase 2B.4 | ✅ 已修(WI-102) |
| E-07 | baseline 殘留未命名規則 | Phase 1.1 / Phase 7 | 由 WI-103 cleanup 處理 |
| E-08 | Modbus Probe 預設 Slave 3 | Phase 2D.3 | UX 改善卡,不擋 |
| E-09 | UI「Port 1」 vs API busId=0 |
Phase 2D.7 / RS485 Modal | 腳本斷言 API 數值,不對 UI 文字斷言 |
已關閉 (False Positive):E-02(TCP loading 慢顯示)、E-05(時間欄位 probe 用錯 selector)。
#文件修訂紀錄
| 日期 | 版本 | 韌體基線 | 變更 |
|---|---|---|---|
| 2026-08-20 | 2.5 | 6.0.65 | 新增雲端備份/還原閉環(test:backup,4 項)與 ST.15 雲端設定完整性 |
| 2026-08-20 | 2.4 | 6.0.65 | 新增啟用/停用機制回歸(test:enable,6 項);factory-functional 的 CH_TCP 輸出改用 channelId、MQTT 輔助函式支援 TLS broker;ST.3 判準改為只看已啟用的規則 |
| 2026-08-19 | 2.3 | 6.0.65 | 新增 MQTT Converter 鏈路回歸(test:converter,6 項);新增環境設定檔 factory-env.js 與「換設備要改哪裡」章節;ST.14 環境符合性 |
| 2026-08-19 | 2.2 | 6.0.65 | 新增「先跑自動化閘門」章節與缺陷對應表;閘門擴充到 29 項(新增 ST.11 TCP 來源不可能條件、ST.12 token 免密碼更新、2F 佔位符殘留);基線自 6.0.8 更新 |
| 2026-07-16 | 2.1 | 6.0.8 | /api/diag 欄位名對齊實際韌體 |