專案

一般

配置概況

動作

Bug #1345

已結束

[Branch] StatusNotificationHandler 沒更新 Connector.last_status_change,導致 last_status_change 欄位不可靠 (修補並修 #1343 方案 A 前置)

是由 陳國瑋約 2 個月 前加入. 於 21 天 前更新.

狀態:
Closed
優先權:
Normal
被分派者:
開始日期:
2026-06-18
完成日期:
預估工時:

概述

問題描述

StatusNotificationHandler 處理充電樁送過來的 StatusNotification 訊息時,不會更新 Connector.last_status_change 欄位。只有 DataTransferHandler(處理華城樁的 ChargeErrorNotification)才會透過 Connector.updateStatus() 同步更新 last_status_change

結果e_connectors.last_status_change 欄位不可靠,無法作為「status 真正變更時間」的判斷依據。

影響範圍

  • 影響所有 vendor 的 connector(不限華城)
  • 影響所有 StatusNotification 事件(每個狀態變更都會被 OCPP 送過來)
  • 直接影響 Redmine #1343 方案 A(Finishing timeout fallback)— timeout 判斷邏輯會誤判
  • 影響任何依賴 last_status_change 欄位的業務邏輯(例如:卡 Finishing 超時、卡 PREPARING 超時等的偵測)

復現

  1. 部署任意版本的 ems_branch
  2. 任一充電樁送 StatusNotification: <新狀態> 給 EMS
  3. 觀察 DB:
    SELECT id, status, last_status_change, update_time 
    FROM e_connectors 
    WHERE id = '<受影響 connector>';
    
  4. 預期:last_status_change 應該要被更新成「StatusNotification 處理當下的時間」
  5. 實際:last_status_change 沒被更新,停在 DataTransfer 上次觸發的時間(或 NULL)

實際案例(2026-06-18 ct1 觀察)

connector CBDAX50A-25L-A6-A072-001

時間 事件 status last_status_change update_time
14:28:21 StatusNotification: Finishing FINISHING 14:28:21 (DataTransfer 觸發) 14:28:21
14:31:18 StatusNotification: Finishing (重送) FINISHING (不變) 14:31:18
14:56:13 StatusNotification: Finishing (重開機後) FINISHING (不變) 14:56:13
15:04:26 StatusNotification: Available AVAILABLE (不變) 15:04:26
15:05:00 DataTransfer: ChargeErrorNotification SUSPENDED_EV 15:05:00 (DataTransfer 觸發) 15:05:00
15:05:18 StatusNotification: Finishing FINISHING (不變) 15:05:18
15:05:26 StatusNotification: Available AVAILABLE (不變) 15:05:26
15:13:15 DataTransfer: ChargeErrorNotification (txId=509) SUSPENDED_EV 15:13:15 (DataTransfer 觸發) 15:13:15
15:14:01 StatusNotification: Available AVAILABLE (不變) ← 應該要是 15:14:01 15:14:01

根本原因

StatusNotificationHandler.handle() (line 190-194) 使用 6 個 setter 來更新 connector:

connector.setStatus(ConnectorStatus.getEnum(status));        // ← 只改 status
connector.setErrorCode(ErrorCode.getEnum(errorCode));
connector.setInfo(info);
connector.setVendorErrorCode(vendorErrorCode);
connector.setUpdateTime(...);  // ← 用 OCPP 訊息 timestamp 或現在時間
connector.setUpdater(Constants.DEFAULT_UPDATER);
// ❌ 沒呼叫 updateStatus(),所以 last_status_change 不更新

Connector.updateStatus() (line 256-262) 才是統一更新 status + last_status_change 的 method:

public void updateStatus(ConnectorStatus status, ErrorCode errorCode, String info, String vendorErrorCode, String updater) {
    this.status = status;
    this.errorCode = errorCode;
    this.info = info;
    this.vendorErrorCode = vendorErrorCode;
    this.lastStatusChange = LocalDateTime.now();  // ← 統一更新
    this.setUpdater(updater);
    this.setUpdateTime(new Date());
}

DataTransferHandler (line 184) 跟 ConnectorService (line 651) 都正確使用 updateStatus(),只有 StatusNotificationHandler 漏了。

修法方向

方案 A(推薦):把 StatusNotificationHandler 改成呼叫 Connector.updateStatus()

  • 改動量:1 個 method call 取代 6 個 setter call(2 處:line 144 跟 line 190 附近)
  • 影響範圍:所有 StatusNotification 事件都會更新 last_status_change
  • 時機一致性updateStatus()new Date()(現在時間),原本的 setUpdateTime 用 OCPP 訊息 timestamp — 差異是「OCPP 訊息產生時間」vs「EMS 處理時間」,差異通常 < 1 秒,業務上可接受
  • 風險:低。updateStatus() 內部行為跟目前 6 個 setter 組合等價,只是多更新 last_status_change

方案 B:在 Connector entity 加 helper method

  • setStatusAndLastChangeTime(ConnectorStatus status) 一行 method
  • StatusNotificationHandler 改成呼叫這個 helper
  • 比方案 A 多了 entity method,但保留「OCPP 訊息 timestamp 用於 updateTime」的彈性

方案 C:把 DataTransferHandler 改成不要動 status

  • 讓 DataTransfer 只記 log,不主動改 status
  • 由後續的 StatusNotification 統一改
  • 改動量較大,且會影響華城樁的 vendor-specific 行為

建議採用:方案 A

理由:

  • 改動最小
  • 跟現有 DataTransferHandler 跟 ConnectorService 行為一致
  • 修完後 last_status_change 欄位成為可靠的 status 變更時間依據
  • 連帶讓 #1343 方案 A(Finishing timeout fallback)能正確運作

測試計劃

新增 StatusNotificationHandlerLastStatusChangeTest

  1. 驗證 StatusNotification 處理後 last_status_change 被更新成「處理當下時間」
  2. 驗證連續多次 StatusNotification 都能正確更新 last_status_change
  3. 驗證 OCPP 訊息 timestamp 不會影響 last_status_change(用 new Date()

參考資訊

  • 受影響檔案:java/ems_branch/src/main/java/com/sylksoft/ems/branch/ocpp/handler/StatusNotificationHandler.java (line 144-149, 190-194)
  • 現有正確實作:DataTransferHandler.java (line 184), ConnectorService.java (line 651)
  • Connector entity method:Connector.java (line 256-262)
  • 相關 ticket:#1343 (Finishing 卡住,方案 A 依賴 last_status_change 判斷 timeout)
  • 觀察案例:2026-06-18 ct1 connector CBDAX50A-25L-A6-A072-001
  • Branch:待建立 feature/redmine-1345-status-notification-last-status-change

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

  • 狀態New 變更為 In Progress

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

2026-06-18 21:34 實作完成

Commit: 27e7d76 fix(#1345): StatusNotificationHandler 改用 Connector.updateStatus(),同步更新 last_status_change
Branch: feature/redmine-1345-status-notification-last-status-change (base: private)
Pushed: ✅ origin 已建立

改動檔案

  • java/ems_branch/src/main/java/com/sylksoft/ems/branch/ocpp/handler/StatusNotificationHandler.java (-43 / +17 行)
  • java/ems_branch/src/test/java/com/sylksoft/ems/branch/ocpp/handler/StatusNotificationHandlerLastStatusChangeTest.java (+148 行新檔案)

測試結果

  • StatusNotificationHandlerLastStatusChangeTest: 4/4 通過 (last_status_change 更新、連續更新、其他欄位不損壞、事件發布)
  • 跟既有 test 一起跑 ConnectorServiceUpdateOffPeakTest + ChargingOrchestratorDispatchOffPeakGuardTest: 9/9 通過,BUILD SUCCESS

連帶影響

  • 修正後 e_connectors.last_status_change 欄位成為可靠的「status 真正變更時間」依據
  • 讓 Redmine #1343 方案 A (Finishing timeout fallback) 判斷邏輯變可行(原 ticket 已更新說明)
  • 任何依賴 last_status_change 欄位的業務邏輯(卡 PREPARING 超時偵測等)都會更可靠

Pre-existing 問題(跟本 ticket 無關)

跑「全部 mvn test」時 invoice 模組的 SpringBootTest 會 fail(53 errors),root cause 為 InvoiceStatusConverter not present。建議另開 ticket 追。

尚未處理

  • 未 merge to private(Ken 指示先不 merge)
  • 未部署到測試環境驗證
  • ticket status 改為 In Progress(commit 完但還沒 merge + 部署)

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

  • 狀態In Progress 變更為 Resolved

已merge to private branch。有時間deploy至A17與jimmy那

是由 陳國瑋21 天 前更新

  • 狀態Resolved 變更為 Closed

2026-07-13 A17 實機部署查核:目前 ems-branch-api 正在執行的 jar 已包含 #1345 修正;StatusNotificationHandler 在新增與更新 connector 時皆呼叫 Connector.updateStatus(...),因此 StatusNotification 會同步更新 last_status_change。確認已部署至 A17,故關閉。

是由 陳國瑋21 天 前更新

補齊關閉狀態的完成度:#1345 已於 A17 實機部署查核確認,done ratio 設為 100%。

動作

匯出至 Atom PDF