專案

一般

配置概況

動作

Bug #1517

進行中

[Backend Bug] A17 CP003 已回報狀態仍被心跳 watchdog 覆寫為 UNAVAILABLE,修正 OCPP 存活判定與恢復同步

是由 陳國瑋 於 6 天 前加入. 於 4 天 前更新.

狀態:
In Progress
優先權:
High
被分派者:
-
開始日期:
2026-09-26
完成日期:
預估工時:

概述

1. 本單要解決什麼問題

寶台 A17 的充電樁列表中,CP003-001、CP003-004、CP003-008 持續顯示「無法使用」。然而,2026-09-26 凌晨查核時,CP003 已經與 Branch 恢復通訊,設備端記錄這三支 Connector 的狀態都是 AVAILABLE(可用),而且仍持續成功讀取電表。

本次已確認的問題是:

CP 曾經主動把「可用」狀態送給 Branch,Branch 也已成功收到;但 Branch 後來只根據過舊的心跳時間,將三支 Connector 改成「無法使用」。心跳恢復後,Branch 沒有重新取得並更新 Connector 狀態,導致列表長時間保留錯誤的不可用狀態。

本單需要同時處理兩件事:

  1. 避免誤判離線: CP 還在傳送有效 OCPP 訊息時,Branch 應將這些訊息納入「設備仍有通訊」的判斷,不能只看最後一筆 Heartbeat。
  2. 讓狀態可以恢復: 若 Branch 已經因離線把 Connector 標成不可用,通訊恢復後應重新取得 CP 的實際狀態,避免永久停留在舊的不可用狀態。

目前的證據可以確認「後台狀態與設備端狀態不一致」,但本次沒有操作實機開始充電,不能把電表通訊正常直接當成「實際充電功能已驗證正常」。

2. 系統角色與名詞

本單使用的名詞如下,避免把「通訊在線」和「可以充電」混為一談。

名稱 在本案中的意思
CP(Charge Point) 設備端。本案是 CP003,由它與實體設備通訊,再透過 OCPP 回報給 Branch。
Connector 個別充電連接器。本案 CP003 底下有 001、004、005、008。
Branch/CS 接收 OCPP 的後台服務。Branch 在本系統扮演 OCPP 的 Central System(CS),充電樁列表所使用的狀態也由它提供。
Heartbeat CP 傳給 Branch 的心跳訊息,用來表示仍有通訊。它不包含每一支 Connector 的充電狀態。
StatusNotification CP 主動回報設備或 Connector 狀態的訊息,例如 Available、Charging、Faulted、Unavailable。
BootNotification CP 啟動註冊時使用的訊息。現行 CP 程式在部分重連流程也會嘗試發送,但有冷卻時間,短時間內不一定再次發送。
Watchdog Branch 定時執行的離線檢查程式。若判定通訊已逾時,會把 CP 標成離線,並依交易保護規則處理其 Connectors。
TriggerMessage Branch 請 CP 重新送出某類 OCPP 訊息的標準指令。本案可用來請 CP 回報目前的 StatusNotification。

CP 為 ONLINE,只能證明通訊狀態;某支 Connector 究竟是可用、故障或正在充電,仍須依該支的實際狀態判斷。 因此不能在收到 Heartbeat 時,把所有 Connector 一律改成 AVAILABLE。

3. 現場症狀與查核結果

以下是 2026-09-26 約 01:19(台灣時間)的查核快照,並非持續即時監控結果。

項目 查核結果 代表的意義
CP003 的 Branch 連線狀態 ONLINE;最近 Heartbeat 為 01:18:01 CP 與 Branch 當時已恢復通訊。
Branch 的 CP003-001 UNAVAILABLE;原因為 OCPP heartbeat stale 後台仍保存「心跳逾時造成的不可用」。
Branch 的 CP003-004 同上 同上。
Branch 的 CP003-008 同上 同上。
三支 Connector 的 Branch 狀態變更時間 均為 2026-09-25 06:11:57 三支是同一輪離線判定一起被改寫。
CP 設備端的三支 Connector 均為 AVAILABLE,current_transaction_id 均為 NULL 設備端記錄與 Branch 不一致。
三支 Connector 的電表讀取 持續更新;當時可讀到約 230V、0A,polling log 顯示繼電器斷開 當時仍能取得設備量測,但尚未做實際充電測試。
06:11:57 之後的 CP003 StatusNotification 截至查核時,OCPP audit 中 request 共 0 筆 Branch 改成不可用後,沒有再收到可用來恢復狀態的通知。

使用者看到的直接影響,是這三支在列表中長時間顯示「無法使用」,無法正確判斷設備是否可用。本次未實際送出啟動充電指令,因此不以此查核宣稱已驗證每一個啟動入口的阻擋結果。

4. 事件經過:CP 有回報,Branch 後來又改掉狀態

以下時間均為 2026-09-25,時區為 Asia/Taipei。

時間 發生的事 與問題的關係
05:59:47 Branch 保存的上一筆 Heartbeat 時間。 後續離線檢查仍使用這個舊時間。
06:06:02 Branch 收到 BootNotification 並接受。 這是當時最後一次 BootNotification 時間。
06:08:01 CP003 的 WebSocket 因未及時收到 pong 而關閉。 觸發本次重連;為何未收到 pong 的底層原因尚未查明。
06:08:31 WebSocket 重連成功。CP 因 BootNotification 冷卻時間尚餘約 150 秒,略過再次發送 BootNotification。 Branch 的 Boot 時間仍停在 06:06:02。
06:08:42~06:08:50 CP 主動送出各 Connector 的 AVAILABLE StatusNotification;Branch 收到並回覆成功。 這證明 CP 有主動回報,而且 Branch 已經收到最新狀態。
06:11:56~06:11:57 Branch watchdog 判定 Heartbeat 與 BootNotification 均已過期,將 CP003 標成離線,並將 001、004、008 改成 UNAVAILABLE。 問題發生點:近期收到的 StatusNotification 沒有被納入存活判定,剛恢復的可用狀態又被覆寫。
06:13:01 CP 下一筆 Heartbeat 成功,其後每五分鐘持續成功。 CP 的連線狀態恢復,但 Heartbeat 本身不包含 Connector 狀態。
至 9/26 約 01:19 沒有再收到新的 StatusNotification,三支仍是 UNAVAILABLE。 設備端維持 AVAILABLE,沒有新的狀態變化可自然觸發通知;後台也沒有完成重新同步。

為什麼 CP003-005 沒有一起變成「無法使用」?

Branch log 明確記錄:005 當時的 currentTransactionId=865,觸發既有交易保護,因此略過不可用狀態的覆寫。這可以解釋為什麼同屬 CP003,只有 001、004、008 受到本次覆寫影響;但單靠這個交易指標,不能推論 005 當時一定有實際供電。

5. 原因分析:目前有兩個缺口

5.1 缺口一:Branch 沒有把其他有效 OCPP 訊息納入存活判斷

現行 watchdog 主要查看:

  • 最後 Heartbeat:lastHeartbeat。
  • 最後 BootNotification:lastBootNotification。
  • 兩者是否都超過離線門檻;現場門檻為 330 秒。

這個判斷會漏掉一種情況:CP 沒有新 Heartbeat,但剛剛確實透過同一條連線送來 StatusNotification 或 MeterValues。

本案在 06:08:42~50 收到狀態,06:11:56 就判離線,中間約三分鐘,仍在 330 秒範圍內。Branch 卻沒有使用這份新的通訊證據,而是依較舊的 Heartbeat/Boot 時間執行離線處理。

5.2 缺口二:連線恢復後,Connector 狀態沒有重新同步

Branch 將 Connector 改成 UNAVAILABLE,是後台自行做的離線處理,並不代表 CP 設備端也把自己的狀態改成 UNAVAILABLE。

因此,可能出現:

  • CP 自己一直是 AVAILABLE。
  • Branch 保存的是 UNAVAILABLE。
  • CP 後來繼續送 Heartbeat,但 Heartbeat 沒有 Connector 狀態。
  • CP 沒有發生新的設備狀態變化,也就沒有再次自然送出 StatusNotification。
  • Branch 的舊狀態因此一直保留。

所以本單所說的「補送設備狀態」,是重新請 CP 回報目前狀態,讓 Branch 有實際依據更新紀錄,並不是認定 CP 在 06:08 沒有回報過。

5.3 已確認與尚未確認的界線

已確認:狀態回報成功、Branch 後續以 heartbeat stale 覆寫、心跳恢復後狀態仍未同步。

尚未確認:

  • 造成 WebSocket pong timeout 的最底層原因,例如通訊延遲或程式處理阻塞。
  • A17 CP003 當時實際執行的 JAR 是否包含 #1456 全部修正;本次尚未比對正式 artifact。
  • 三支實際啟動充電後的物理行為;本次僅唯讀查核。

這些待查項目不能先寫成已證實的根因。

6. OCPP 1.6 規格如何要求

本單以專案保存的 OCPP 1.6 Edition 2 與 Errata 為依據。SHALL 表示規格要求必須遵守;SHOULD 表示原則上應遵守的建議;MAY 表示允許。

6.1 CP 要主動回報 Connector 狀態

依 §4.9 與 Errata §3.19,狀態變更時,CP 必須主動送出 StatusNotification,並考慮最短狀態持續時間。

依 Errata §3.22,BootNotification 被 CS 接受後,CP 必須回報 connectorId 0 及所有 Connectors 的目前狀態;connectorId 0 代表 CP 本體。

依 §4.9,若離線期間狀態有改變,重連後應回報目前狀態。但規格沒有要求在狀態完全不變時,每隔固定時間重送所有 Connector 狀態。因此不能只因 06:13 後沒有新通知,就判定 CP 違反「固定週期回報 Connector 狀態」的義務。

6.2 Branch 不應只靠 Heartbeat 判定是否仍有通訊

依 §4.6,若 CP 已在 Heartbeat interval 內送出其他 OCPP 訊息,允許省略該次 Heartbeat;CS 應將收到其他訊息視為仍有通訊的證據。

套用到本案,Branch 剛收到 StatusNotification,這也是一份有效通訊證據。修正時應另外保存「最後有效 OCPP 通訊時間」,而不是把它冒充成真正收到 Heartbeat 的時間。

6.3 Branch 可以主動要求最新狀態

依 §5.17,CS 可透過 TriggerMessage,要求 CP 送出 StatusNotification。但必須區分:

  • CP 回覆 Accepted:只表示接受這次要求。
  • Branch 收到後續 StatusNotification:才取得該 Connector 的實際狀態。

設備可能不支援、拒絕或逾時,不能在沒有實際通知時直接把 Connector 改成可用。

原始文件位置:

  • documents/OCPP_1.6_documentation/ocpp-1.6 edition 2.pdf。
  • documents/OCPP_1.6_documentation/ocpp-1.6-errata-sheet.pdf。

7. 本次要怎麼修改

以下為待確認的實作設計;本單尚未完成程式修改或部署。

7.1 增加「最後有效 OCPP 通訊時間」

建議在 e_charge_points 新增可為空的 last_ocpp_message_at 欄位。

用途是保存 Branch 實際收到有效 OCPP 訊息的時間。例如收到 StatusNotification、MeterValues 或有效的指令回覆時,都能建立新的通訊證據。Heartbeat 欄位仍只記錄真正的 Heartbeat,避免日後查 log 時無法分辨訊息來源。

必須限制證據來源:

  • 訊息必須來自該 CP 目前有效的連線。
  • 使用 Branch 的接收時間,不使用設備 payload 裡可能過舊或錯誤的時間。
  • 舊連線延遲送達的事件、格式錯誤訊息,以及 Branch 自己送出的指令,不能拿來延長 CP 的在線時間。
  • 不因收到未知 CP 的訊息而任意建立設備資料。

資料庫變更需提供可重複執行的 migration。既有資料不以部署當下時間回填,避免把離線設備假裝成剛有通訊。

7.2 修改 watchdog,防止把新狀態覆寫成不可用

Watchdog 應一起檢查最後 Heartbeat、BootNotification,以及有效 OCPP 通訊時間。

例如:最後 Heartbeat 已超過 330 秒,但兩分鐘前有收到有效 MeterValues,則不能只因 Heartbeat 過舊就判定設備離線。

另外,檢查與寫入之間可能剛好收到新訊息。因此必須保留現有加鎖後重查機制:準備寫入 OFFLINE 前,再確認最新通訊時間,避免使用數秒前的舊查詢結果覆寫新狀態。

真正沒有通訊、或目前連線確實關閉時,仍要正常執行離線處理;既有未完成交易的保護也要保留。

7.3 通訊恢復後,重新取得 Connector 的真實狀態

對因離線而被 Branch 標成 UNAVAILABLE 的 Connector,通訊恢復後安排狀態同步。也要涵蓋本次已經卡住的情況:CP 已 ONLINE,但仍有離線處理留下的 UNAVAILABLE。

建議使用標準 TriggerMessage(StatusNotification),請 CP 重新回報。同步動作必須在背景執行,不能卡住接收 OCPP 訊息的執行緒,否則可能連 CP 的回覆都收不到。

收到實際通知後,依原本的狀態處理流程更新:

  • CP 回報 AVAILABLE,就更新為可用。
  • CP 回報 FAULTED,就保留實際故障。
  • CP 回報 CHARGING,就依既有充電/交易規則處理。
  • CP 回報 UNAVAILABLE,也應視為設備真的回報不可用,不能為了讓列表恢復而強制改成 AVAILABLE。

此同步只處理狀態,不應開關繼電器、啟停充電、清除交易,或呼叫具有這些副作用的完整設備 Recovery 流程。

7.4 同步失敗要有明確結果,不能無限重試

同一 CP/連線只能有一組進行中的同步工作,避免每收到一筆 MeterValues 就再發一次要求。

需要處理:

  • CP 不支援 TriggerMessage。
  • CP 回覆 Rejected。
  • 指令沒有回覆。
  • CP 回覆 Accepted,但後續沒有送出 StatusNotification。
  • 同步途中再次斷線,之後由新連線接手。

每種情況都要有可追查的 log、逾時與重試上限。尚未取得真實狀態時保留未同步的事實,不假造成功。

8. 與 #1436、#1456 的關係,以及本單範圍

議題 已處理的內容 與本單的差別
#1436 Branch 除 Heartbeat 外,也考慮近期 BootNotification,提供重連後的寬限。 本案重連時 Boot 被冷卻機制略過,且有效 StatusNotification 尚未納入判斷。
#1456(已關閉) CP 重連後立即送 Heartbeat,成功後重新回報 Connector 實際狀態。 該次明確未修改 Branch watchdog;本單補 Branch 對其他有效 OCPP 通訊的判斷與恢復同步。
本單 #1517 修正 Branch 存活判定與 Connector 狀態恢復,補上本次事故的回歸驗證。 同時盤點 A17 各 CP instance 的 #1456 修正版是否確實部署。

本單主要修改 java/ems_branch。A17 有 CP001、CP002、CP003 多個 instance,部署驗收時要逐一核對,不能只因 CP001 已更新,就推定 CP003 也是同一版本。

本單不包含重寫 MeterValues 佇列,也不承諾解決尚未查明的 pong timeout 底層原因。正式環境的即時復原、服務重啟與部署,要依後續核准的步驟執行;本次診斷尚未做這些操作。

9. 修改後應如何驗收

每個案例都要同時核對 OCPP 訊息與後台保存的狀態,不能只看到 HTTP 200、容器執行中,或 CP 為 ONLINE 就算通過。

案例 測試情境 預期結果
近期有狀態回報 Heartbeat/Boot 已舊,但在離線門檻內收到 StatusNotification。 不誤判離線,也不把剛回報的狀態覆寫為 UNAVAILABLE。
近期有電表回報 Heartbeat/Boot 已舊,但仍持續收到有效 MeterValues。 通訊被視為仍有活動;Connector 狀態不因舊 Heartbeat 被改成不可用。
真正通訊逾時 所有存活證據都超過門檻,或目前連線確實關閉。 正常執行離線處理,保留既有交易保護。
查詢與寫入交錯 Watchdog 先查到候選設備,但寫入前剛收到新訊息。 加鎖後重查可看到新證據,取消過時的離線寫入。
舊連線或無效訊息 重連後舊連線仍有延遲事件,或收到格式錯誤內容。 不延長目前連線的存活時間,不污染設備狀態。
離線後恢復可用 Branch 已因離線標成 UNAVAILABLE,CP 恢復後回報 AVAILABLE。 Branch 根據實際回報恢復為可用。
離線後恢復但仍故障/充電 CP 恢復後回報 FAULTED 或 CHARGING。 真實狀態原樣處理,不一律改成 AVAILABLE,交易不被誤清除。
設備明確回報不可用 CP 回報 UNAVAILABLE。 保留設備真實不可用,不與後台殘留的離線狀態混淆。
接受指令但未回報 TriggerMessage 回覆 Accepted,卻一直沒有後續狀態通知。 到期記錄同步未完成,不假裝已恢復。
不支援、拒絕或無回覆 NotImplemented、Rejected、timeout。 有明確紀錄及有界處理,不無限重試或產生指令風暴。
同步途中再次重連 前一批要求尚未完成,CP 已換新連線。 舊連線的回覆/排程不影響新連線,同一時間不重複發起多批同步。
A17 原事故重播 重現 Boot cooldown、先回報 AVAILABLE、watchdog 判定、較晚 Heartbeat 的先後順序。 修正前能重現問題;修正後不誤覆寫,已離線者也能依實際通知恢復。

現場驗收另需確認 A17 CP001/002/003 的版本與行為,並確認狀態同步過程沒有造成繼電器操作、充電啟停或交易異動。

10. 測試目錄與執行安排

既有案例需更新:

  • OCP-001:離線判定及交易保護。
  • OCP-007:watchdog 存活證據與加鎖後重查。
  • OCP-008:新舊連線交錯。
  • REL-005:部署後 OCPP 通訊與狀態驗證。

新增回歸重點:

  • P0:Heartbeat 過舊,但近期其他 OCPP 訊息仍能防止誤判。
  • P0:watchdog 已先標成離線後,恢復真實 Connector 狀態且不改變供電或交易。
  • P1:舊連線/無效訊息不能刷新存活。
  • P1:狀態同步不支援、拒絕、逾時或只有 Accepted 時,不假成功也不無限重試。

案例在 OCPP 訊息處理、watchdog、session、重連、恢復同步、資料庫欄位或相關部署變更時執行。發版測試採 Major/OCPP 範圍:所有適用 P0、P1,以及受影響 P2。

目前分支的 OCP-018 已用於 #1459 的 Fortune 案例,與 #1456 歷史文件的編號有差異。新增時必須取目前目錄未使用的 ID,不得覆寫既有案例。需要實機或特定廠商的案例,在完成前保留 Gap/Conditional,不以單元測試通過替代。

11. 目前進度與交付資料

已完成:

  • A17 正式環境唯讀查核與事件時序整理。
  • 比對本地 OCPP 1.6 原始規格與 Errata。
  • 建立本 ticket,優先級 High。
  • 從最新 origin/main(4b0e412)建立獨立分支 feature/redmine-1517-ocpp-liveness。
  • 完成實作規劃,列出預計變動檔案、migration、風險及測試設計。
  • 既有 E2E 目錄格式檢核通過;這僅表示目錄格式正確,不代表本單修正或測試已通過。

相關文件:

  • 查核報告:test_report/20260926_001_a17_cp003_unavailable_readonly.md。
  • 實作規劃:documents/20260926/redmine-1517-implementation-plan.md。
  • Ticket 快照:redmine-tickets/1517.md。

2026-09-26 11:33:20 已依 Ken 指示完成 A17 三支的單次現場復原;永久修正的程式修改、整批測試與部署尚未完成。 本次採 SSH/MySQL 維運方式,依已鎖定的 edge AVAILABLE、最新電表資料與交易/命令檢查結果,直接校正 Branch 三筆 e_connectors;不是 CP 補送 StatusNotification。11:34:16 獨立複查確認三支 Branch/edge 均為 AVAILABLE、無交易,CP003-005 與 CP003-004 原離峰隊列未變動,沒有重啟或操作充電。由於未經應用事件流程,既有畫面可能需要重新整理。本次是現場資料復原,#1517 仍需完成永久修正。復原報告:test_report/20260926_002_redmine-1517-a17-status-recovery.md。

Ken 已明確要求本案不使用瀏覽器,未經其同意不得使用 Computer Use。後續實作仍依已列出的規劃確認及測試啟動流程進行。

動作

匯出至 Atom PDF