Edge 開發者指南:線上磅秤校正
2026-10-06,6.0.134 beta/calibration-candidate 已在 .46 完成實秤三段與收尾,原重量 payload 傳回各步 ACK。延續 128 毫秒時間、132 TDA08B healthy-high、133 短暫讀取容錯,134 新增續期回覆。精度允收、五段實秤、斷電保存、業主 Edge 與長時驗收未完成。透過 pio run -e opta-calibration 編譯;一般 opta 建置不含本功能。取代 WI-248 的訊號觸發流程作為新 Edge 整合入口,不宣稱舊 cmd/signal 路徑已有同等防呆。
135 測試候選:穩定憑證。 原重量
calibration.v=2;sample改為穩定區段編號,reading才是每次採樣嘗試流水號。OPTA 持續成功量測且控制器 stable 時,連續至少 5000 ms 的全區段「最大-最小」不超過 0.1 kg 才 ready。begin可選填"toleranceKg":"0.1";是最大差,不是 ±0.1 kg,也不是精度驗收容差。省略用0.1;必須正十進位字串、可按控制器小數位表示且不超過capacity。整公斤控制器的0.1向下成0 raw,只允許示值相同,不向上放寬。手動 MQTTX: 保活正常、砝碼不動時,看到
ready/valid/stable=true,複製sample/revision後可以從容填寫同一步命令;不必追著每秒採樣改sample。送出前再次確認仍為同boot/sid/revision/sample且ready。每秒的新reading繼續回報;最後成功讀取仍須≤2500ms。超出全區段範圍、失穩、讀取失敗/缺口或步驟變更後重新累積5秒,舊憑證不能因重量回到原值而復活。每次校正寫入前與提交前都再讀取並納入同一重量範圍。失效後需新命令,不排隊等將來自行校正。v1(134以前)仍要求最新採樣編號;Edge須依v分支,不可把v2固定sample當成停止採樣。新版工具以reading去重並保留v1相容;人工確認後仍接收三筆新的不同reading,且最後ready=true。保活60秒/每10秒ping未變;134的保活失敗紀錄仍未結案。
#開發者從這裡開始
讀者:負責 Edge 網頁、後端服務與 MQTT 整合的開發者。 目標是讓操作員只需確認空秤、輸入新增砝碼重量並按「下一步」,由程式負責會話、保活、取新樣本、送命令及核對回覆。現場安裝與逐步操作見磅秤校正操作指南,驗收見現場驗收流程。本頁是同一個既有協定網址的開發者版,不另建第二套協定。
#Edge、OPTA、控制器各自負責什麼
| 角色 | 責任 |
|---|---|
| 操作員 | 清空秤盤、放妥砝碼、確認單位與安全秤量、輸入「本次新增」重量、確認下一步 |
| Edge | 先訂閱再 query;管理 boot/sid/seq;每10秒 ping;用十進位精確累加總量;取得確認後的三筆新穩定樣本;串行發命令;核對 ACK;顯示中斷原因並保存原始事件 |
| OPTA | 驗證會話/順序/新鮮度/穩定/範圍;讀控制器倍率;執行 Modbus 交易;在原重量 payload 夾帶狀態、樣本與回覆;超時停止 |
| TDA08B | 實際量測並套用校正曲線。交易 ACK 不等於精度或斷電保存驗收 |
同一台 OPTA 同時只有一個校正會話。Edge 應由單一控制端擁有該會話,避免兩個網頁/服務同時發命令;sid 是順序識別,不是身分授權憑證,仍要靠 broker ACL 限制誰能送命令。不要讓一般重量訂閱者自動發 ping;操作頁/控制服務離開或失聯後,不應由另一個無關程序永久代續期。
#最小完整實作流程
- 連上 broker,訂閱該台磅秤的原重量 topic,等訂閱確認後發
query;不處理 retained 舊回報。 - 保存
boot/sid/seq/state/slave。若已有await_zero/collecting/processing/unknown,顯示既有會話,禁止自動接管。由人明確處理/取消後才可重新開始。 - 操作員確認空秤、kg、3或5段、安全秤量。新sid=本次boot最新sid+1,seq=1,送begin。若uint32已到上限,不可回繞成0。
- 收到匹配begin的
applied + await_zero才啟動10秒保活,進入等待空秤確認;未完成begin不要搶先送下一步。 - 每個zero/point/finish:人先確認目前秤盤狀態;清掉確認前的樣本累計;取得同boot/sid/slave/revision的三筆不同reading、有效且穩定新樣本,最後ready=true;用最後一筆sample/revision立即送命令。
- 同時只保留一個未完成的校正動作。收到匹配的applied及預期state/step才增加seq/顯示下一步。新一段只在point成功後固定已確認總量,重試不能再次加同一顆砝碼。
- 做滿段數後,再取得新穩定樣本送finish;匹配
applied + complete + step=steps才顯示「校正流程完成」、停止ping。之後另做精度與斷電驗收。 - partial/unknown/recovery_required、boot改變、無法確認ACK或保活時,停用下一步並顯示原因;停止自動流程,不偷偷建立新會話續做。取消不回滾已寫曲線。
#Topic 與 JSON 契約
P = mes/gateway/<UID>。命令全部發 P/cmd/calibration,QoS 1、retain=false。先訂閱既有重量topic(預設 P/scale/weight),其中的 calibration 就是應用層回覆;不需另外訂閱ACK topic。回報的QoS由重量流程設定決定,不能假設一定是1。
| 命令 | 必填 JSON 欄位 | 是否消耗 seq/寫控制器 |
|---|---|---|
| query | op | 不消耗、不寫、不續期 |
| begin | op, boot, sid, seq, slave, steps, unit, model, capacityKg;選填toleranceKg | 成功消耗;只讀前置檢查,不標零 |
| ping | op, boot, sid | 不消耗、不寫;只續期匹配的活動會話 |
| zero | op, boot, sid, seq, slave, steps, sample, revision | 成功/未知結果消耗;寫標零 |
| point | 上列zero欄位,加totalKg | 成功/未知結果消耗;寫下一個正重量點 |
| finish | 與zero相同,不需totalKg | 成功/未知結果消耗;以內部重量0收尾 |
| cancel | op, boot, sid, seq, slave, steps | 成功消耗、不寫;結果partial,不回滾 |
boot 是16位十六進位字串;sid/seq/sample/revision 是uint32 JSON整數,sid/seq必須非0;slave是1~247,steps只能3或5。unit固定 "kg",model固定 "tda08b"。capacityKg/totalKg是正的十進位字串,不能送JSON浮點數。OPTA依控制器小數位精確換算,不能表示的精度拒絕,不四捨五入。
#ping 保活:Edge 必須實作
這裡的 ping 是校正應用協定命令,與MQTT連線層的Keep Alive/PINGREQ不同;MQTT客戶端仍連線、收到PUBACK,或OPTA持續傳重量,都不能代替校正ping。它不是PIN碼,也不需要增加按鈕讓操作員每10秒點一次。
#資料格式與定時器
以下所有JSON是格式範例,0123456789abcdef 不是現場boot。必須從最新query取得boot,sid改為這一輪實際值。
{
"op": "ping",
"boot": "0123456789abcdef",
"sid": 1
}| 欄位 | 型別 | 來源/規則 |
|---|---|---|
| op | string | 固定小寫 ping |
| boot | string | 本輪begin使用的boot;發現重開不得直接替換後繼續舊流程 |
| sid | integer | 本輪begin成功的sid;不能用seq或RS485站號代替 |
ping不帶seq、sample、revision、重量或steps,不增加動作編號,也不改校正點。 begin成功後啟動每10秒一次的單一排程;操作員搬砝碼、等穩定、等命令ACK時都保持排程。finish/cancel確認完成、會話中斷、控制端關閉或停止控管時,取消排程並清除timer,避免雙重保活。
60秒是OPTA的租期,10秒是Edge建議送出間隔。有效ping被設備處理後續期;成功處理一般會話動作也更新租期,但不能以操作頻率代替獨立ping。到期後的ping不能復活partial會話。
#成功/失敗回覆
回覆仍在原重量payload的calibration,下列僅節錄保活相關欄位,不是另一個topic或完整payload:
{
"calibration": {
"boot": "0123456789abcdef",
"sid": 1,
"seq": 2,
"state": "collecting",
"result": "state",
"reason": "lease_renewed",
"requestSid": 1,
"requestSeq": 0
}
}核對boot、slave、requestSid、requestSeq=0、result=state、reason=lease_renewed;seq仍保留最近校正動作的編號,revision也不因ping增加。這份回覆不是zero/point/finish的applied,不可讓UI前進一段。
| 收到的情況 | Edge 行為 |
|---|---|
| 匹配的lease_renewed | 更新「最近確認續期時間」,維持等待/操作流程 |
| rejected / lease_not_active | sid不匹配或會話不再active;停自動流程,query核對,不自動begin |
| rejected / boot_mismatch | boot已不符;停止舊流程,重新query,由人決定完整重做。早期拒絕可能requestSid/requestSeq都是0 |
| 沒收到匹配回覆 | 不宣稱續期成功。顯示「保活未確認」,暫停新寫入;保留本輪ping排程等待恢復,若已失去狀態/租期則停止並query |
| complete後晚到lease_not_active,requestSeq=0 | 可是finish完成後最後一筆ping被拒;不能覆蓋已保存、匹配finish的成功ACK |
目前ping沒有獨立的nonce/序號,無法逐筆配對某一次timer,也無法從延遲到達的回覆精算設備租期剩餘秒數。依已訂閱的本輪非retained事件追蹤,重連後先query核對;不要把重複/舊回覆當作可保證再寫入的授權。134實機曾有MQTT重連和接收缺口,10秒排程也不是無失聯保證。
#時間門檻:不要混用這些時限
| 項目 | 目前規則 | Edge 要做什麼 |
|---|---|---|
| 會話保活 | 每10秒ping;60秒無有效續期則partial | 與UI操作分開排程,保存設備lease回覆;不拿query代替 |
| 人工搬放砝碼 | 工具等待確認時無固定步驟時限 | 持續保活,重量變動不等於通訊故障 |
| 人工確認後等穩定 | 參考工具預設300秒,可由 --stable-timeout 調整 |
到時仍未有新穩定樣本,停止並顯示「本步未送出」 |
| 寫入量測新鮮度/憑證 | 最後成功量測≤2500ms;同一穩定區段sample不變,ready需至少5秒 | v2帶入當前sample/revision;新reading不使憑證失效;重量或品質改變會失效 |
| 連續量測缺口 | 133起有效讀取中斷達10000ms或設定/感測器異常→unknown | 不把放砝碼的不穩定當斷訊;unknown後不續寫 |
| 命令回覆等待 | 參考工具每次等12秒,僅原封不動重送一次 | 這是工具策略,不是設備完成時間保證;兩次都未確認即停,不換seq盲重試 |
135 v2支援手動MQTTX逐步校正。 另以工具持續ping。zero/point/finish送出前,須核對同一筆最新回報的 calibration.ready === true、calibration.valid === true、calibration.stable === true 全部成立,boot/sid/slave與當前會話相符,state/step符合即將執行的步驟,再從該筆複製sample/revision。三個旗標不能從不同回報拼湊,外層stable數值不能代替calibration.stable;任一條件不符就不送出。這是下一筆寫入命令的條件,begin成功ACK與finish完成ACK不要求三者為true。量測新鮮度檢查保留,指令到達時OPTA仍重讀檢查;不可把partial當成等待中。
#三段完整命令範例
逐步人工操作與每一步完整成功ACK、begin後ready重量回報,集中在操作指南:發送與預期回覆。兩者須分別判定:applied ACK的ready=false正常;後續ready=true不是前一個指令的ACK。finish的complete/ready=false即完成,不等待下一筆ready。
以下boot、sid、sample、revision均是示意;不可一次整批發送。revision是設備狀態修訂號,並不保證每輪由1開始;sample也不會每段歸零。每一步須由最新事件取代示例值、等匹配ACK及人員確認後才執行下一步。
| 順序 | 操作 | 本次新增 kg | 送出的totalKg | sid | seq | 預期applied後state/step |
|---|---|---|---|---|---|---|
| 1 | begin | — | — | 1 | 1 | await_zero/0 |
| 2 | zero | 空秤 | — | 1 | 2 | collecting/0 |
| 3 | point | 5 | "5" | 1 | 3 | collecting/1 |
| 4 | point | 20 | "25" | 1 | 4 | collecting/2 |
| 5 | point | 20 | "45" | 1 | 5 | collecting/3 |
| 6 | finish | 保持45 kg | 不填 | 1 | 6 | complete/3 |
{
"op": "query"
}{
"op": "begin",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 1,
"slave": 3,
"steps": 3,
"unit": "kg",
"model": "tda08b",
"capacityKg": "200"
}begin成功後啟動上節ping定時器。人確認空秤,等至少5秒穩定及ready;Edge另取三筆不同reading:
{
"op": "zero",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 2,
"slave": 3,
"steps": 3,
"sample": 100,
"revision": 1
}人確認5 kg已放妥,再取新的穩定樣本:
{
"op": "point",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 3,
"slave": 3,
"steps": 3,
"totalKg": "5",
"sample": 110,
"revision": 2
}保留5 kg,加20 kg,確認總量25 kg:
{
"op": "point",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 4,
"slave": 3,
"steps": 3,
"totalKg": "25",
"sample": 120,
"revision": 3
}保留25 kg,再加20 kg,確認總量45 kg:
{
"op": "point",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 5,
"slave": 3,
"steps": 3,
"totalKg": "45",
"sample": 130,
"revision": 4
}第三點applied後,保持砝碼並等至少5秒穩定及ready;Edge另取三筆不同reading才收尾:
{
"op": "finish",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 6,
"slave": 3,
"steps": 3,
"sample": 140,
"revision": 5
}若需要中途取消,先query並帶目前sid、steps、slave及「目前已消耗seq+1」。以下示例是在第二點已成功(seq4)後取消,所以用seq5;不是完成後照抄的清理命令:
{
"op": "cancel",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 5,
"slave": 3,
"steps": 3
}五段使用 steps=5,例如新增5/5/10/10/15,總量5/10/20/30/45;begin seq1、zero seq2、五筆point seq3~7、finish seq8,其餘規則相同。新增20 kg與再次新增20 kg合法,檢查的是總量嚴格遞增。
#ACK 與畫面控制
Edge保存待處理命令原文及 (boot,sid,seq,slave,op)。收到原重量訊息後先核對boot與slave,再用 requestSid/requestSeq 匹配;不要用外層weight變化或calibration.seq單獨判斷ACK。
| result/state | UI/流程行為 |
|---|---|
| accepted / processing | 顯示處理中,保持本步pending,禁止再次點下一步 |
| applied / 預期state及step | 保存ACK,再增加本機seq;只有point才提交該段累計總量 |
| rejected / not_fresh_stable | 未寫校正;重新確認穩定憑證(Edge以reading去重),保留同sid/seq/總量,更新sample/revision再送 |
| 其他rejected | 顯示reason並停下;先核對狀態/輸入,不能自動跳段 |
| unknown | 控制器結果不明,禁止更多zero/point/finish;由人明確cancel後完整重做 |
| state / sample、snapshot、lease_renewed | 是採樣/快照/續期,不能完成pending動作 |
未收到ACK時,最多以完全相同欄位重送一次,不能刷新sample、改seq或再次累加砝碼;若已明確收到not_fresh_stable拒絕,才走更新sample的分支。快取只保留最後已消耗命令;舊命令不保證可無限重播查詢。ping/query不消耗校正seq,但傳輸要序列化並避免同時塞入;韌體只有單一命令接收槽,可能回 inbox_busy_or_oversized。
Edge重開/MQTT重連後不能自動宣告上次成功,也不能自動接管現有會話;先query。boot改變必須停止舊命令。partial/unknown/recovery_required都應保留「需重新校正」業務標記,不能把砝碼數據混入生產統計。
#原重量 payload 完整格式
下例為校正中25 kg的格式示例,時間、boot及編號不能作實際命令固定值:
{
"weight": 25.0,
"stable": 1.0,
"timestamp": "2026-10-06T08:03:45.111Z",
"calibration": {
"v": 2,
"boot": "0123456789abcdef",
"slave": 3,
"sid": 1,
"seq": 4,
"revision": 4,
"state": "collecting",
"step": 2,
"steps": 3,
"sample": 12,
"reading": 121,
"toleranceRaw": 10,
"stableMs": 5000,
"sampleEvent": true,
"ready": true,
"valid": true,
"stable": true,
"weightRaw": 2500,
"decimals": 2,
"result": "state",
"reason": "sample",
"requestSid": 0,
"requestSeq": 0
}
}| 欄位 | JSON型別 | Edge解讀 |
|---|---|---|
| weight | number或null | 外層重量;本場確認kg。單位不在此payload中,不憑欄名推論。無有效值用null,不當0 |
| stable | number或null | 外層一般穩定欄;1表示穩定,但不足以授權下一步 |
| timestamp | string或null | 範本用${sys.timestamp}為訊息時間,用${rs485.3.實時重量.timestamp}為量測時間;UTC ISO8601毫秒,未對時null。解析度不是精度保證 |
| calibration | object | 以下各欄位所在位置 |
| v | integer | 協定版本,135起為2;134以前為1,sample語意不同 |
| boot | string | 本次開機識別;變更即使舊會話失效 |
| slave | integer | 目標RS485站號;不同站號不能混算 |
| sid | integer | 目前/最近會話;0表示本次開機未建立 |
| seq | integer | 最後已消耗動作序號,不等於訊息筆數/成功旗標 |
| revision | integer | 狀態修訂號;依回覆帶入,不由Edge自行推算 |
| state | string | recovery_required/await_zero/collecting/processing/complete/partial/unknown;非目標重量流程可能為idle |
| step | integer | 已成功的正重量點數,標零後仍0 |
| steps | integer | 目標點數3或5;尚未建立時可為0 |
| sample | integer | v2穩定區段編號;同區段不變,失效重建後遞增;只有ready時能作為寫入憑證 |
| reading | integer | v2每次採樣嘗試編號,失敗也增加;重複Converter以此去重 |
| toleranceRaw | integer | 本輪最大-最小容許差,kg=此值÷10^decimals |
| stableMs | integer | 穩定觀察最低時間,目前5000ms |
| sampleEvent | boolean | true才是採樣事件;狀態/ACK不可計入三筆樣本 |
| ready | boolean | 閘道器有足夠新鮮穩定樣本;仍要有人確認重量及命令次序 |
| valid | boolean | 校正樣本有效且新鮮;不是外層一般輪詢重量的品質旗標 |
| stable | boolean | calibration內的穩定旗標;與外層數字stable不同,必須連同valid/sampleEvent/ready判斷 |
| weightRaw | integer或null | 有號原始示值;kg=raw÷10^decimals,不是砝碼目標值 |
| decimals | integer | 本輪控制器小數位0~5;未begin的初值0不證明真設定 |
| result | string | state/accepted/applied/rejected/unknown |
| reason | string | 原因碼,完整保存,不只顯示「失敗」;見本頁故障處理 |
| requestSid | integer | ACK所對應請求sid;134的ping回本輪sid,query/sample通常0 |
| requestSeq | integer | 動作ACK配對序號;ping固定0。部分早期拒絕也為0,不得誤配 |
v2同boot/sid/slave/revision/reading只算一次;sample相同但reading不同是新的量測,兩個Converter送同reading才是重複。常規訊息在校正外出現 calibration.valid=false,不能因此把外層正常weight判成無效。timestamp為null也不等於weight必為null,須分開看資料有效與時鐘是否已對時。
#中止、cancel 與 Rollback:目前不會還原曲線
6.0.135 仍沒有完整曲線備份/Rollback。 這個流程不是「先暫存全部點、最後finish才一次提交」;zero及每筆point都會對控制器寫入。取消、失聯、OPTA重開或沒有送finish,都不代表回到校正前的零點、校正係數或讀值。
| 中止位置 | 已知影響 | Edge要顯示/後續動作 |
|---|---|---|
| begin已成功,尚未送zero/point | begin只做唯讀前置檢查,沒有校正寫入 | 可明確cancel;仍保存此次狀態,不宣稱恢復了一條其實未備份的曲線 |
| zero已applied | 已寫入標零;沒有保存舊零點可還原 | 顯示「已變更零點,取消不還原」;中止後完整重做並驗證 |
| 一部分point已applied | 已執行各點的提交,不等待finish才生效 | 顯示已成功段數與「校正未完成」;不得把砝碼移除後的讀值直接視為生產可信 |
| 交易結果unknown/ACK始終未確認 | 可能已寫入,也可能未完成;不能靠網路失敗猜測控制器狀態 | 停止寫入並保留原命令及回覆;明確處理/cancel後完整重做 |
| cancel或60秒租期到期 | 結束會話,partial;釋放OPTA的保留鎖,不回滾 | 保留業務端「需重新校正」狀態,直到完整重做與精度核對 |
Edge的取消確認視窗至少寫清楚:「取消會停止後續校正,但不會還原已寫入的零點或校正點。中止後需完整重新校正並驗證。」不要把按鈕命名為「還原」或顯示「已恢復原值」。
原Converter/「數值變動才送」設定恢復,僅指訊息發送策略恢復;它不是秤重曲線Rollback。OPTA設定備份保存的是閘道器設定/模板,不能代替TDA08B控制器曲線備份。回復舊OPTA韌體也不會替控制器還原曲線。
若產品要求可撤銷校正,後續需先由原廠協定證實可完整讀出/寫回所有曲線資料,或提供明確的暫存、提交與放棄機制,再設計原始資料備份、還原交易、斷電中斷與讀回驗證。目前未確認有這個能力,不得把一個cancel命令包裝成Rollback。 這是待設計能力,不屬134已交付功能。
#Edge 最小驗收清單
- 收到訂閱確認後才query;新boot/舊retained/錯slave不混用。
- 5、20、20的畫面輸入只轉成5、25、45一次;重送不重加。
- begin後自動10秒ping,觀察lease_renewed;操作員等超過5分鐘仍可保活,未收到續期確認時有明顯提示。
- 同reading重複、失效sample、前一revision、不穩定/null都不能觸發下一段。
- accepted/一般重量/MQTT PUBACK不當成applied;每步匹配boot/requestSid/requestSeq/state/step。
- ACK遺失只相同命令重送一次;明確not_fresh_stable才重新取樣,不把未知結果當未執行。
- finish/cancel/中斷停止ping;晚到lease_not_active不抹掉已保存的finish ACK。
- 原checkbox與門控不永久改寫;收尾後勾選恢復去重、未勾選恢復原定時。unknown待明確cancel。
- Edge/OPTA重開、MQTT失聯不自動接管;精度、斷電保存、長時另依OC.1~12執行。
參考實作為操作工具下載與執行說明中的 scale_edge_sim.py。它是CLI參考與驗證工具,不是完整的業主Edge產品;仍需實作頁面生命週期、控制權、告警、事件儲存及後端資料隔離,並逐項驗收。工具送出ping不等於已確認每筆續期,Edge應自行記錄與監看設備回覆。
#韌體與控制器實作細節
#使用者承諾與範圍
Edge 控制流程、累加已知砝碼;TDA08B 儲存並套用校正。輸入新增砝碼 5、20、20 kg,提交總量 5、25、45 kg。總量不是目前未校準的面板讀值,也不是本段增量。第一版單一會話、通道 1、kg、3 或 5 段。單位與安全秤量須由操作人員確認;文件沒有提供可可靠讀取的單位/安全秤量設定,不能自行推斷。
開始前必須選 TDA08B 樣板、設備啟用、bus 0、大端、具有通道 1 的 INT32 weight 暫存器(11 或 203);現場不得同時透過另一個 Modbus master 或控制器面板更改校正。舊校正規則應停用。會話期間,閘道器阻擋同站號的一般 API/規則寫入與匯流排變速;取消/收尾/逾時釋放保留,不改永久設定。
#主題與命令
命令:{prefix}/cmd/calibration。Edge 只需訂閱既有重量流程的 MQTT topic(標準樣板為 {prefix}/scale/weight),重量 payload 追加 calibration 物件,包含狀態、採樣與 ACK。{prefix}/calibration/status、sample 保留為相容診斷,非必要訂閱。命令不可 retained;Edge 忽略 retained 回報。broker ACL 沿用設備命令授權,不能讓未授權客戶發送 cmd/#。
所有命令為小於 384 bytes 的平面 JSON。協定字串為不含跳脫的 ASCII;數值欄位是 uint32 整數,不能使用浮點、負數、指數或重複鍵。最多 12 欄位。韌體使用固定記憶體解析器,不為命令配置 heap。公斤重量必須用十進位字串。
先送 {"op":"query"} 取得 boot、sid、seq、state。boot 是每次啟動由硬體亂數產生的 64-bit token;取得亂數失敗時拒絕所有修改命令。每個寫入請求都必須綁定目前 boot。
{
"op": "begin",
"boot": "0123456789abcdef",
"sid": 1,
"seq": 1,
"slave": 3,
"steps": 3,
"unit": "kg",
"model": "tda08b",
"capacityKg": "50"
}上述 token 欄為範例佔位字,實際必須是 query 返回的 ASCII token。新 sid 必須大於本次 boot 已用 sid;seq 自 1 開始逐次加 1。request ID 是 (boot,sid,seq)。每次只能有一個未完成請求。
begin 唯讀查詢 200–204,驗證感測器狀態、小數位 0–5、分度值 1/2/5/10/20/50。capacityKg 與各段 totalKg 使用該小數位精確換算,不四捨五入、不乘分度值。手冊 195 的正重量值域為 1–99999;另受操作人員指定 capacityKg 限制。舊 FC06 流程的 65535 上限不適用新 FC16 流程。
zero/point/finish/cancel 都帶 boot、sid、seq、slave、steps;ping/query 的例外見表:
| op | 額外欄位 | 實際效果 |
|---|---|---|
| zero | sample、revision | 選通道 107=1,再 113=1;操作者先清空秤盤 |
| point | totalKg、sample、revision | 檢查總量 > 上段且 ≤ capacity;按順序寫下一段 |
| finish | sample、revision | 僅已完成指定段數後,下一段重量 0、199=100 收尾 |
| cancel | 無 | 結束會話、標示 partial;不寫控制器、不回滾已生效段 |
| ping | 只需 boot、sid | 延長目前 active 會話 60 秒租期,不增加 seq。134 起回覆 lease_renewed,requestSid=本輪 sid、requestSeq=0;不符合活動會話回覆 rejected/lease_not_active |
| query | 無 | 查現況,不延長租期、不執行寫入 |
zero、point、finish 都需要本revision的穩定憑證:至少5秒連續有效且控制器穩定、全區段最大最小差≤toleranceRaw;最後成功量測≤2500ms,命令sample/revision符合目前區段。每次成功或未知結果後重置穩定區段與revision。Edge確認放妥後另取三筆不同reading且ready;手動操作以持續有效的ready憑證提交。not_fresh_stable拒絕沒有硬體寫入,可保持同總量與seq、更新憑證重試。
#交易與 ACK 邊界
所有校正寫入走 FC16,195 寫完整 32-bit 高字在前。點交易:107=1 → 194=段號 → 195/196=總量 → FC03 讀回 194–196 驗證 → 重讀狀態及設定 → 199=100。每筆回應均驗證站號、功能碼、地址、數量、CRC。每次命令寫入前重新查狀態/設定;中途設定變動或不穩定不得送 commit。
accepted 只是處理中;applied 表示寫入交易取得控制器回覆,不代表精度通過或控制器非揮發儲存經斷電驗證。生效後仍需新穩定樣本,不能靠寫入 ACK 立即送下一段/收尾。MQTT QoS 或重量抵達皆不等於命令 ACK。
任一交易失敗採保守 unknown,即使失敗發生在 commit 前;不猜測失敗是否已改動控制器。保留目標站號、繼續狀態與校正重量回報,拒絕更多 zero/point/finish,只允許明確 cancel。相同 ID、相同內容重送回傳相同結果,不再次寫硬體;已消耗序號的相同 ID 不同內容拒絕。檢查階段的 rejected 不消耗序號,可修正後重送。只快取最後一個已消耗序號,較舊命令由 seq/sid 拒絕,沒有無限成長快取。
#連續重量與資料用途
先建立並啟用正常重量 Converter 流程,再開始校正。系統按已設定 TDA08B 的重量變數辨識流程,不靠 topic 名稱;原模板與永久 on-change 設定不變;活動校正暫時繞過數值去重,正常 finish/cancel/租期結束後回到原間隔與門控,原勾選或未勾選狀態均保留。unknown 仍保留會話占用,須明確 cancel,不能冒充正常完成。原 payload 必須為 JSON object,不得已有頂層 calibration,不得混合多台磅秤重量。為保留固定緩衝區餘裕,模板字面長度加每個變數預留 31 bytes 的總和不得超過 192;標準重量/stable/timestamp 模板符合。不符合條件的既有生產模板維持原發送行為,但不能作為校正通道。無可用流程時,寫入前拒絕 weight_stream_missing;請恢復標準重量模板並核對流程啟用、topic 與來源連線。
校正會話(包括 unknown)每隔至少 1000 ms 嘗試 FC03 200–204,取得同一回覆的狀態、倍率與重量。回報仍走原重量 topic,暫時繞過該重量流程的 on-change、間隔及觸發閘;流程停用與輸出授權仍有效。結果變更立即回報,會話狀態另每 5 秒回報。取消/收尾/逾時後補一次最後採樣,再按原流程規則發送。其他設備沿用原規則。
原 weight/stable/timestamp 欄位保留;校正事件使用此次直接採樣,失敗為 null,不回送舊重量。timestamp的語意依原模板:訊息時間使用sys.timestamp,量測時間使用RS485欄位.timestamp;量測時間在成功取得樣本時固定,無效量測或本次開機未對時為null。增加的 calibration 包含 v、boot、slave、sid、seq、revision、state、step、steps、sample、sampleEvent、ready、valid、stable、weightRaw、decimals、result、reason、requestSid、requestSeq。例如:
{
"weight": 25.0,
"stable": 1,
"timestamp": null,
"calibration": {
"v": 2,
"boot": "0123456789abcdef",
"slave": 3,
"sid": 1,
"seq": 3,
"revision": 3,
"state": "collecting",
"step": 1,
"steps": 3,
"sample": 9,
"reading": 60,
"toleranceRaw": 10,
"stableMs": 5000,
"sampleEvent": true,
"ready": true,
"valid": true,
"stable": true,
"weightRaw": 2500,
"decimals": 2,
"result": "state",
"reason": "sample",
"requestSid": 0,
"requestSeq": 0
}
}Edge 只將 sampleEvent=true 且 reading 新增的包計作新採樣,不能把狀態事件計入三次穩定;只有匹配 boot/requestSid/requestSeq 且 result=applied 才能推進命令。重量抵達本身不是 ACK。一般非校正採樣仍依原 weight/timestamp 使用,不能把 calibration.valid 當成一般輪詢品質。
Modbus 寫入與輪詢共用匯流排,不保證每秒準點,也不承諾寫入期間有新重量。Edge 應以 2500 ms 新鮮度停止斷訊後的下一步,並依 calibration.state 將校正砝碼排除生產統計。partial/unknown/recovery_required 需人工處理,不直接恢復生產可信狀態。
#中斷與恢復
- MQTT 中斷:沒有 ping 超過 60 秒轉 partial、釋放保留。Edge 等不到 ACK 只能原封不動重送一次;仍無回覆就停。
- 6.0.133 起,週期性唯讀採樣的短暫逾時/格式/CRC 失敗:當筆 weightRaw/timestamp=null、valid=false、ready=false,清空穩定累計;保留 await_zero/collecting。恢復後重新累積至少5秒穩定量測,且收到新命令才可寫入,失敗命令不自動重播。
- 從最後一次有效讀取起達 10000 ms 沒有有效量測(包含直到門檻後才讀回成功)、感測器錯誤、倍率/分度值或設備映射改變:轉 unknown,需取消並重做。控制器沒有已確認的開機識別欄位,短暫通訊恢復不能證明控制器未重開;這是有界的讀取容錯,並非斷電續跑保證。
- 人工搬放砝碼與穩定等待不使用上述通訊門檻。Edge 每 10 秒 ping;人工確認前無固定步驟時限,確認後預設最多等 300 秒取得至少5秒穩定量測及ready,可用 --stable-timeout 調整。有效但不穩定的讀值持續回報,不計為通訊失聯;逾時不送此步命令。
- ACK 遺失:同 ID 重送取得快取結果;不得把 total 再累加。
- 網頁/模擬器重開:先 query,存在會話時拒絕自動接管;明確 cancel 後用較大 sid 從清空秤盤重做。
- OPTA 重開:新 boot、
recovery_required。沒有持久化交易日誌,無法知道上次是否有部分點已寫入;明確要求完整重做,不自動續跑。舊 boot 命令拒絕。 - cancel、租期結束與 unknown 都不承諾原曲線回復;Edge 應維持需重新校正標記,直到完整重做並驗證。
#驗收邊界
原生測試使用正式狀態機、正式命令解析器、正式 PowerMonitor FC16/FC03 路徑,UART 傳輸端替身注入成功、失聯、回覆遺失、不穩定與設定變更。MQTT 模擬用真正的本機 broker,透過同一 C++ 程式處理命令,不另外複製閘道器邏輯。
UART 替身不是 TDA08B 的內部校正演算法,也不能證明 RS485 電氣連線、控制器寫入時序/32-bit 行為、曲線精度、重開持久化或長時穩定。這些必須有轉接器/實體秤另驗。USB 接 OPTA 不能替代 USB-RS485 轉接器。
#2026-10-06 現場勘誤:TDA-08B 測量狀態
實測控制器版本為 260521(不是 OPTA 的 6.0.x)。FC03 讀位址 7、數量 2,高字先合併;.46 Slave 3 回覆 00 03 F9 A9,即 260521。
| 感測器狀態 | 暫存器 200 | bit 4 | bit 3 |
|---|---|---|---|
| 正常且穩定 | 0x0018 |
1=正常 | 1=穩定 |
| 拆線、面板 ERR02 | 0x0008 |
0=故障 | 仍可能為 1,不能單靠穩定判定有效 |
證據為 KC 提供的正常/拆線對照;KC 確認是在現場同一台控制器上實測。圖片的250829版本標示有誤;先前我們也把原始版本值算錯,130部署後重新讀取及換算確認260521。此勘誤不是原廠公告;實測證據只涵蓋現場控制器。KC 決定採用 TDA-08B 型號統一的正常高位策略,不以韌體版本白名單限制;這不表示已實測所有版本。原手冊表格保留作歷史對照。
OPTA 6.0.132 候選對 TDA-08B 統一以 bit 4=1 正常/0 故障檢查開始、寫入前、提交前與連續採樣,不讀版本來阻擋 begin。感測器故障的 begin 回覆 sensor_fault。拒絕 begin 不消耗 sid/seq、不寫入校正。通訊、倍率、穩定、新鮮樣本與交易檢查維持。129/130白名單誤設250829;131限制260521;132依KC要求移除版本限制。
儲存設備設定只同步小數位倍率;控制器2位、網頁0.01時直接儲存,不修正重量曲線。三段校正使用已知砝碼累計5/25/45 kg,不使用校正前錯誤示值。132安裝與實秤精度仍須各自驗收。