專案

一般

配置概況

動作

Bug #1393

進行中

Bug #1382: [Branch] OCPP 充電樁離線後 connector 狀態仍停留 AVAILABLE

[Branch Bug] Branch 重啟後 watchdog 以舊 Heartbeat 誤將已重連 Connector 設為 UNAVAILABLE

是由 陳國瑋6 天 前加入.

狀態:
New
優先權:
High
被分派者:
-
開始日期:
2026-07-28
完成日期:
預估工時:

概述

問題摘要

CT3(勝華寓)在 ems_branch 重啟後,CP001 已成功重新建立 OCPP WebSocket、送出 BootNotification,且 CP 本機 CP001-001 為 AVAILABLE、Modbus 通訊正常;但 Branch heartbeat watchdog 在第一個重連後 Heartbeat 到達前,仍以重啟前留下的舊 last_heartbeat 判斷離線,將 e_connectors.CP001-001 誤寫為 UNAVAILABLE。

後續 Heartbeat 只恢復 e_charge_points.ocpp_connection_status=ONLINE,不會恢復 Connector;CP 本機狀態沒有變化,也不會再主動送 StatusNotification,造成 Branch 與 CP 狀態永久分裂,使用者無法啟動充電。

本問題為 #1382 離線同步機制在 Branch restart / reconnect 情境下的回歸。

環境

  • 案場:CT3 勝華寓
  • Charge Point:CP001
  • Connector:CP001-001(CloudLink / public / slaveId=1)
  • 發生時間:2026-07-28 15:06~15:12(Asia/Taipei)
  • Branch watchdog:
    • Heartbeat interval:300 秒
    • offline timeout:330 秒
    • scan fixed delay:60 秒

現場資料(2026-07-28 15:28)

Branch

  • e_charge_points.CP001
    • ocpp_connection_status = ONLINE
    • status = AVAILABLE
    • last_heartbeat = 2026-07-28 15:27:06
    • last_boot_notification = 2026-07-28 15:07:20
  • e_connectors.CP001-001
    • status = UNAVAILABLE
    • error_code = NOERROR
    • info = OCPP heartbeat stale
    • last_status_change = 2026-07-28 15:09:24

charge_point 本機

  • cp_connectors.CP001-001
    • status = AVAILABLE
    • slave_id = 1
    • last_meter_read 持續更新(15:28 仍正常)

Engineer Modbus audit

  • 15:16:59 CONNECTION_TEST:COMPLETED / Accepted / result=true
  • 15:17:28 READ_SUPPLY_STATE:COMPLETED / Accepted / value=0

上述證據表示 OCPP WebSocket、DataTransfer、Modbus gateway 與 slave 1 都可達;不是硬體或 Modbus 故障。

完整時序

  1. 15:06:50:ems_branch 重啟,舊 WebSocket 關閉。
  2. 15:07:20:CP001 重新建立 WebSocket 並送 BootNotification;Branch 將 connection 設為 ONLINE。
  3. 此時 last_heartbeat 仍是重啟前的 15:02:06。WebSocket establish 與 BootNotification 依 #1382 設計不更新 last_heartbeat。
  4. 15:08:06:ChargePointOfflineWatchdogTask 以 cutoff=15:02:36 找到 CP001,將 connection 設為 OFFLINE,並把無 ACTIVE transaction 的 CP001-001 投影為 UNAVAILABLE / HEARTBEAT_STALE。
  5. CP 於 15:07:32 產生的初始 StatusNotification(1, AVAILABLE),Branch 在 15:08:24 處理,短暫把 CP001-001 恢復 AVAILABLE。
  6. 15:09:24:第一個 post-reconnect Heartbeat 尚未到達;watchdog 再次以舊 last_heartbeat 判定 stale,將 CP001-001 覆寫為 UNAVAILABLE。
  7. 15:12:06:第一個 post-reconnect Heartbeat 到達,connection 恢復 ONLINE,但 Connector 不會自動恢復。
  8. CP 本機 CP001-001 始終為 AVAILABLE,沒有本機狀態變化,因此後續不再送 StatusNotification;Branch 保持 UNAVAILABLE。

次要觀察:第一次 offline propagation 發布 MQ connector event 時發生 RabbitMQ connect timeout,使該輪 watchdog 約耗時 18 秒,也延後 OCPP StatusNotification 處理;但即使沒有 MQ timeout,下一輪 watchdog 仍會因舊 last_heartbeat 再次覆寫 Connector,故 MQ 不是主要根因。

程式碼根因

  1. ChargePointRepository.findEnabledStaleHeartbeat() 只判斷 lastHeartbeat,不考慮剛收到的 lastBootNotification 或其他 OCPP application-level liveness。
  2. ChargePointOfflineService.markOfflineIfHeartbeatStale() 的 row-lock recheck 同樣只重讀 lastHeartbeat。
  3. ChargePointService.markOcppConnected() 與 handleBootNotification() 會將 connection 設為 ONLINE,但刻意不刷新 lastHeartbeat。
  4. charge_point HeartbeatScheduler 保持原 300 秒 fixed-delay;Branch 重啟後快速重連時,不會立即補送 Heartbeat,而是等到下一個既有週期。
  5. 重連後 StatusNotification 只發送一次。Branch 若稍後單方面再投影 UNAVAILABLE,CP 本機狀態未改變,不會再次通知。
  6. RemoteStart 只允許 Branch Connector status=AVAILABLE/PREPARING,因此 e_connectors 的誤狀態會直接造成「無法使用」。
  7. Engineer Modbus dispatch 只要求 WebSocket 可送達,不檢查 e_connectors.status,因此 Modbus 指令仍能成功。

暫時恢復方式

在 Heartbeat 已正常更新後,對 CP001 connector 1 發送:

{
  "requestedMessage": "StatusNotification",
  "connectorId": 1
}

CP 會依本機狀態重新回報 AVAILABLE。此方式僅恢復現場資料,不解決下次 Branch restart 的競態。

建議修正方向

  • Watchdog 使用最近一次可信的 OCPP application activity,而不是只看 lastHeartbeat:
    • 最小修正:candidate query 與 locked recheck 都以 max(lastHeartbeat, lastBootNotification) 判斷;
    • 較完整作法:新增 lastOcppActivity,於 BootNotification、Heartbeat(必要時含有效 StatusNotification)更新。
  • charge_point 在 OCPP reconnect 成功後立即送一次 Heartbeat,避免等待原本 300 秒週期;此項可作第二層保護。
  • 不建議單純看到 SessionRegistry 有 session 就永久略過 watchdog,否則會破壞 TCP half-open 偵測。

驗收條件

  1. Branch restart 前 lastHeartbeat 已超過 cutoff,但 CP 在 restart 後重新建立 WebSocket並送出 BootNotification時,第一個 post-reconnect Heartbeat 到達前不得將 CP 或 Connector 判為離線。
  2. fresh BootNotification 的保護只維持設定的 grace/timeout;超過 timeout 仍未收到 Heartbeat 時,watchdog 必須正常判離線。
  3. CP 回報 Connector AVAILABLE 後,不得在有效 reconnect grace 內被舊 lastHeartbeat 覆寫成 UNAVAILABLE。
  4. 首個 Heartbeat 到達後,e_charge_points.ocpp_connection_status 應維持 ONLINE,e_connectors 應與 CP StatusNotification 一致。
  5. TCP half-open、無 BootNotification、無 Heartbeat 的既有離線偵測仍正常。
  6. ACTIVE transaction/currentTransactionId/cp_transactions ACTIVE 的既有保護不得退化。
  7. 補充至少以下回歸測試:
    • old heartbeat + fresh BootNotification + watchdog before first Heartbeat;
    • fresh BootNotification 過期且仍無 Heartbeat;
    • reconnect StatusNotification(AVAILABLE) 後 watchdog 不應以舊 Heartbeat再次覆寫;
    • 正常 stale heartbeat 與 TCP half-open path。

沒有任何資料可供顯示

動作

匯出至 Atom PDF