MES I/O Gateway / 開發者 / 整合
04開發者 / 整合

出廠測試規範

版本:3.4 | 日期:2026-08-28 | 韌體版本:6.0.83(基線) 來源 PRDdocs/prd/prd-factory-test.md(內部文件,未公開) v1.2(結構基線)+ 本文件 v2.1 增量(依 6.0.8 源碼/實機核校) 操作手冊guide-user-operation-manual.md

v2.9 修訂摘要(2026-08-26,完整重跑驗收流程後)

  • 新增 RG.7(WI-206:Parser「值類型」下拉的 option value 必須對得上韌體 ParserValueType)。該缺陷是走情境 M.3 時抓到的:選「數值」實際會得到布林, 任何非零數字變成 1.0RG.7 必須斷言 UI 的 option value —— 只用 API 送 parserType:1 驗「韌體會給數值」是抓不到的(韌體本來就對,錯的是前端綁定)。
  • 韌體基線 6.0.72 → 6.0.74(WI-206 + WI-197)。
  • 全流程重跑結果:test:gate 35/35、test:regression 7/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 .mesb OTA 上機,test:gate 35/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.jsDUT_URL.77(有線)、EXPECT_FW6.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-v6EXPECT_FW 還停在 6.0.8 —— 文件卻宣稱「換設備只改一個檔」。 已全部改為從 factory-env.js 取值,那句承諾現在才是真的。
  • 已知未處理:package.json 有 4 處明文 broker 密碼(見 WI-204,交付優先暫緩)。

v2.6 修訂摘要(2026-08-20,交付定案版)

  • 測項與腳本對齊(文件先前寫錯)test:converter5 項 + afterAll 收尾(CV.9 是清理不是測項); test:enable7 項,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)、bootDetailprevResetReasonbootCountprevUptimeSec(非 prevUptime)、uptimeSecrawRsrfwVersion
    • /api/system boardModel 值 = 字串 "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)

v1.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 結構從 05 擴充為 08,補上 OTA、E2E 業務流、出貨前清理、結果輸出
  • 各步驟加上 PRD FR-* 對應 ID + DOM ID 速查表
  • 補上 EEPROM DIP+斷電 物理步驟(先前完全漏掉)
  • 附錄 C 列 PRD v1.2 已知 UI bug 對工站的影響

#⚡ 先跑自動化閘門(v2.2 起,任何改動後都必須執行)

任何韌體或網頁改動之後,先跑這一道再談其他。 它是唯讀的(不改設備設定), 交機前、OTA 後都能安全執行:

bash
cd web
DUT_URL=http://<設備IP> EXPECT_FW=<版本> npm run test:gate

涵蓋 35 項:頁面渲染(2A–2F)、v6 能力契約、穩定性與持久化(ST.*)。 全數通過才算通過 —— 任何一項紅燈都代表出貨品質有缺口。

另有三支會改動設備狀態、不在閘門內、交機前各跑一次:

bash
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 全綠。閘門對它完全無感。

bash
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.jsontest: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 .mesb OTA 上機驗證,test:gate 35/35、test:regression 6/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 還原後雲端連線不得斷掉(否則還原一次就不能再還原 = 自斷後路)

寫這幾項時踩了兩個判準錯誤,值得記下來:

  1. 不能用「快照數量增加」當判準 —— 雲端會裁切舊快照,數量可能不變;要比對最新那份有沒有換人。
  2. 設定沒變更時推送不會產生新快照,那是正確行為(內容相同不重複存,實測連推 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

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/configmodbusDevices[].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:func
web/tests/factory-pages.spec.js Phase 2 五頁面渲染(Overview/Config/Rules/Devices/Settings,5 tests) npm run test:factory:pages
web/tests/factory-v6.spec.js v6.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:v6
web/tests/factory-cleanup.spec.js Phase 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/1ethernet 變體(H6 Failover Skip)
  • netType="WiFi"interfaceMode=2wifi 變體(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 / 4DODigital-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 型號套預設暫存器即用此型別(不再靠 ÷256 scale 湊)。

# 步驟 驗收條件 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_E2EIG_All_InputsOG_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(Bearer smmsadmin)。

# 步驟 驗收條件 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/fwVersionfwVersion 應等於 /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 用的 wdprobe signal,屬部署時佈建、非出廠基線,會被步驟 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 欄位名對齊實際韌體