專案

一般

配置概況

Bug #1436

是由 陳國瑋 於 約 2 個月 前更新

## 問題摘要 

 2026-08-13,CT1(寶台 A17)`CP003` 的四個 connectors 在 Branch/使用者畫面全部顯示無法使用;但 Charge Point edge 端四槍皆為 `AVAILABLE`,Modbus meter read 持續更新,設備並無實體故障。 

 調查確認:CP003 在 OCPP 斷線後已重新建立 WebSocket、完成 BootNotification 並重送初始 `StatusNotification(Available)`;Branch heartbeat watchdog 隨後仍依重連前的舊 `last_heartbeat`,再次把四槍覆寫為 `UNAVAILABLE / OCPP heartbeat stale`。下一筆 Heartbeat 只恢復 charge point connection,不會偽造 connector operational status,因此 Branch 與 edge 狀態長期分裂,使用者無法啟動充電。 

 本單是 #1382 offline watchdog 的 production regression,**與 #1421 Fortune command capability/real-load E2E 無關**。 

 ## 與既有 Issue 的關係 

 - 原始功能:#1382。 
 - #1393 曾記錄相同 reconnect/watchdog 缺口,但於 2026-08-12 被標為 Rejected,未留下拒絕理由。 
 - CT1/CP003 於 2026-08-13 再次在 production 發生,證明缺口仍存在;因此建立本單追蹤本次復發與正式修補。 
 - 不合併進 #1421,避免混入 Fortune vendor certification 範圍。 

 ## 環境與影響 

 - 案場:CT1(寶台 A17) 
 - Charge Point:`CP003` 
 - Connectors:`CP003-001`、`CP003-004`、`CP003-005`、`CP003-008` 
 - 發生日期:2026-08-13(Asia/Taipei) 
 - 使用者影響:四支槍全部顯示無法使用,無法開始充電 
 - Branch watchdog:Heartbeat interval 300 秒、offline timeout 330 秒、scan fixed delay 60 秒 

 ## 現場證據 

 ### Branch 

 - `e_charge_points.CP003`:`status = AVAILABLE`、`ocpp_connection_status = ONLINE`,Heartbeat 後續持續更新。 
 - 四筆 `e_connectors`:`UNAVAILABLE / NOERROR / OCPP heartbeat stale`,`last_status_change = 2026-08-13 05:16:26`。 

 ### Charge Point edge 

 - 四筆 `cp_connectors` 均為 `AVAILABLE`。 
 - Meter read 與 Modbus 輪詢持續成功。 
 - Branch 與 edge 皆無進行中 transaction。 

 上述證據排除 connector 硬體、Modbus gateway 與 meter 故障。 

 ## 事件時序 

 1. 05:06:57:最後一筆舊 Heartbeat 成功。 
 2. 05:13:26:watchdog 依 stale Heartbeat 將 CP003 判離線。 
 3. 05:14:43:CP client 因 WebSocket pong timeout 關閉連線。 
 4. 05:14:46:Branch 收到 broken pipe 並套用 WebSocket-close offline sync。 
 5. 05:15:13:CP003 reconnect,BootNotification Accepted。 
 6. 05:15:26:watchdog 仍使用舊 Heartbeat,再次判離線。 
 7. 05:15:26~05:15:30:CP 初始 StatusNotification 將四槍短暫恢復 `AVAILABLE`。 
 8. 05:16:26:第一個 post-reconnect Heartbeat 尚未到達,watchdog 再次把四槍覆寫成 `UNAVAILABLE`。 
 9. 05:19:56:Heartbeat 恢復 charge point 為 `ONLINE`,但 connector 狀態仍卡在 `UNAVAILABLE`。 

 ## 程式碼根因 

 1. `ChargePointRepository.findEnabledStaleHeartbeat()` 只判斷 `lastHeartbeat`,不考慮剛成功的 `lastBootNotification`。 
 2. `ChargePointOfflineService.markOfflineIfHeartbeatStale()` 的 locked recheck 同樣只重讀 `lastHeartbeat`。 
 3. BootNotification 把 connection 設為 ONLINE,但依設計不應偽造 `lastHeartbeat`。 
 4. Connector recovery 依實際 StatusNotification;Heartbeat 不會批次恢復 connectors。 
 5. 因此「舊 Heartbeat + 新 Boot + 初始 StatusNotification + 第一個新 Heartbeat 尚未到」期間,watchdog 可覆寫剛恢復的 connector,之後狀態永久分裂。 

 ## 已執行的暫時復原 

 復原前確認 Branch 與 edge 未結束交易皆為 0,四槍 `current_transaction_id` 皆為 null。 

 只受控重啟 CP003 edge service,未重啟 Branch/HQ/其他 CP,也未以 SQL 直接改 connector 狀態。標準 OCPP reconnect 完成 WebSocket、BootNotification、Heartbeat 與四筆 StatusNotification;Branch/edge 四槍恢復 `AVAILABLE`,下一輪 Heartbeat 與 meter read 持續更新。 

 此操作只恢復現場資料,尚未部署永久修補。 

 ## 建議修正方向 

 1. Watchdog candidate query 同時檢查 `lastHeartbeat` 與 `lastBootNotification`。 
 2. Locked recheck 同時重讀兩者;任一時間不早於 cutoff,就略過離線覆寫。 
 3. BootNotification 更新與 watchdog 共用 charge point row lock。 
 4. BootNotification 只提供第一筆 Heartbeat 前、等同 offline timeout 的 reconnect grace;不可刷新或偽裝 `lastHeartbeat`。 
 5. 兩者都過期時仍須正常判離線,不能以 live session 永久略過,以保留 TCP half-open 偵測。 

 ## 驗收條件 

 1. 舊 Heartbeat 早於 cutoff,但 fresh BootNotification 位於 cutoff 內時,不得覆寫為 OFFLINE/UNAVAILABLE。 
 2. reconnect 初始 StatusNotification 已回報 AVAILABLE,在 grace 內不得被舊 Heartbeat 再次覆寫。 
 3. BootNotification 超過 timeout 且仍無 Heartbeat 時,watchdog 必須正常判離線。 
 4. Heartbeat 與 BootNotification timestamp 等於 cutoff 時不算 stale。 
 5. BootNotification 不更新 `lastHeartbeat`,也不覆寫 operational status。 
 6. TCP half-open 與無 Boot/無 Heartbeat 的既有離線偵測正常。 
 7. `currentTransactionId`、Branch ACTIVE transaction、edge ACTIVE transaction 的保護不得退化。 
 8. 補回歸測試並通過 Branch build。 
 9. CT1 或等價 simulator 驗證 reconnect → Boot → StatusNotification → watchdog → first Heartbeat 全時序。 

 ## E2E Impact 

 - 分類:**Update** 
 - 案例:`OCP-007` 
 - Priority:**P1** 
 - Trigger:offline watchdog、OCPP reconnect liveness 或相關 production incident 修正 
 - Release pack:**Major / Charging / OCPP** 
 - Gap:需補可控制時間的 simulator/整合時序;production recovery 可作現場證據,但不能取代修補版部署後驗證 

 ## 目前程式碼狀態 

 已有本機未提交草稿用於驗證修補方向;尚未 commit、尚未 push、尚未部署。建立本單後,必須依 Redmine SOP 在本單專屬 branch 重新走需求確認、實作規劃、測試計畫與文件 gate。

返回