Bug #1418
已結束Bug #1343: [Branch] 華城充電樁 FINISHING 後未送 StatusNotification(Available),導致 connector 卡住無法充電 (FINISHING timeout fallback 修補)
[Backend Bug] EXPO Fortune 停止期間 PREPARING 誤觸 RemoteStart,LIFF 由 FINISHING 倒退 CHARGING
概述
問題描述¶
2026-08-10 在 EXPO 案場實測 Fortune 私人樁 CBDAX50A-24L-XX-B351-001。車主按下「停止充電」後,LIFF 先顯示 FINISHING / 停止中,隨後短暫倒退為 CHARGING / 充電中,並出現「充電樁未接受啟動指令」。使用者並未再次按下開始充電。
此問題與父單 #1343 的 Fortune PREPARING -> SEND_REMOTE_START 根因相同,但本單聚焦於 RemoteStop 期間相反方向 operation 重疊,以及 LIFF 狀態倒退。
實機時序¶
| 時間 | 事件 |
|---|---|
| 14:20:27.824 | 車主送出 STOP_CHARGING
|
| 14:20:27.913 | CP 回 RemoteStopTransaction.conf(Accepted)
|
| 14:20:30.080 | CP 回 StatusNotification(PREPARING)
|
| 14:20:30.151 | Branch 由 Fortune strategy 自動建立 START operation 並送出 RemoteStartTransaction
|
| 14:20:30.261 | CP 回 RemoteStartTransaction.conf(Rejected),LIFF 顯示啟動失敗 |
| 14:20:32.755 | CP 回 StatusNotification(FINISHING)
|
| 14:20:34.704 | CP 回 StopTransaction(reason=Remote)
|
| 14:20:41.123 | CP 回 StatusNotification(AVAILABLE)
|
DB/Log 結果¶
- 原 transaction 正常
COMPLETED,未建立第二筆充電 transaction。 - STOP operation 最終為
STOPPED。 - 停止期間自動建立的 START operation 最終為
REJECTED / REMOTE_START_REJECTED。 - LIFF 跳回充電中是 operation/driverState projection 錯誤,不代表實際重新供電。
- Branch request log 只有原 START command 與本次 STOP command;14:20:30 的 RemoteStart 由 StatusNotification 流程內部觸發,不是第二次使用者請求。
根本原因¶
-
FortuneConnectorTypeRotationStrategy.onPreparingWithoutRotation()無條件回傳SEND_REMOTE_START。 -
RemoteChargingSupport.remoteStart()只阻擋重複 active START,未阻擋 active STOP 期間建立 START。 -
ConnectorDriverPresentationService只取得最新一筆 active operation;新 START 會遮蔽仍 active 的 STOP,既有 transaction 尚未完成時 driverState 因而回到CHARGING。
預期行為¶
- RemoteStop dispatch 後,收到同 transaction 的
StopTransaction或 timeout/failure 前,LIFF 維持FINISHING。 - Active STOP 存在期間,raw
PREPARING不得觸發 RemoteStart。 - 同一 connector 不得同時存在相反方向的 active START/STOP operation。
- 防禦性 projection 必須讓 STOP 優先,LIFF 不得從
FINISHING倒退為CHARGING。 - StopTransaction 後、Available 前不得再顯示充電中;應呈現安全的已停止/等待拔槍狀態。
-
StatusNotification(AVAILABLE)後回到READY。 - 不得以全域禁止 raw
PREPARINGRemoteStart 修正,必須保留先插槍後由合法 command 啟動的流程。
影響範圍¶
- Backend:
ems_branchFortune strategy、Remote operation conflict guard、driverState projection。 - LIFF:原則上沿用 Backend driverState,不新增前端自行推導規則。
- CloudLink:正常 START/STOP、
CANCEL_START、ChangeAvailability 與輪充行為不得改變。 - DB schema:預期無變更。
E2E Impact¶
-
Add:Fortune private
CHARGING -> RemoteStop -> PREPARING -> FINISHING -> StopTransaction -> AVAILABLEproduction regression。 - Priority:P0。
- Trigger:Fortune/Other connector strategy、Remote operation lifecycle、driverState 或 LIFF charging flow 變更,以及 Fortune 案場發版。
- Release pack:Charging/OCPP + Site/Vendor。
- CloudLink baseline:更新或執行既有 CHG-001、CHG-005、CHG-034 及相關 cancel/rotation 案例,證明無回歸。
驗收標準¶
- 不產生非使用者授權的第二筆 START operation/RemoteStartTransaction。
- 不產生相反方向 active operation 重疊。
- LIFF 狀態順序符合預期,且成功/失敗 modal 對應真正發起的 operation。
- Fortune 實機 P0 regression 通過。
- CloudLink public/private baseline regression 通過。
- E2E catalog validator 通過。
是由 陳國瑋 於 約 2 個月 前更新
需求確認 1:StopTransaction 後的 LIFF 文案¶
Ken 確認:
- Active STOP 尚未收到同 transaction 的 StopTransaction:維持
FINISHING,title「停止中」,description「正在等待充電樁結束本次交易」。 - 已收到 StopTransaction、raw connector 仍為
FINISHING、尚未收到AVAILABLE:沿用driverState.code=FINISHING,改顯示 title「充電已停止」、description「請拔槍」。 - 收到
StatusNotification(AVAILABLE)後才回READY。 - 不新增新的 driverState code,因此 LIFF 沿用 Backend title/description 與既有 FINISHING tone,不需修改 FE。
是由 陳國瑋 於 約 2 個月 前更新
需求確認 2:受影響 connector type¶
Ken 確認本次一併修正:
FORTUNEDEFAULTTESLAUNKNOWN
上述類型目前共用/繼承 Fortune 的 PREPARING -> SEND_REMOTE_START 行為;修正後 raw PREPARING 本身不得成為未授權 RemoteStart 的來源。
CLOUDLINK 保持既有 strategy 與正常 START/STOP、CANCEL_START、ChangeAvailability、rotation 行為。合法的使用者 command、OFF_PEAK scheduler 與 rotation dispatch 仍可依持久化 request context 送出 RemoteStart。
是由 陳國瑋 於 約 2 個月 前更新
需求確認 3:StopTransaction 後未收到 AVAILABLE 的處理¶
Ken 確認:
- 已收到 StopTransaction 後,若 CP 未回
StatusNotification(AVAILABLE),driverState 持續顯示FINISHING / 充電已停止 / 請拔槍。 - Backend 不得自行把 connector operational status 偽造為
AVAILABLE,避免實體槍或設備尚未 ready 卻開放下一次啟動。 - Timeout 僅記錄可追蹤的 warning/audit,不改寫 connector status;收到真實 OCPP StatusNotification 後再正常轉態。
是由 陳國瑋 於 約 2 個月 前更新
階段 2:實作規劃已確認¶
Ken 已於 2026-08-10 確認規劃可接受,進入實作階段。
實作範圍:
- 移除 FORTUNE/DEFAULT/TESLA/UNKNOWN 由 raw
PREPARING直接觸發的未授權 RemoteStart。 - 在 semantic command 與
RemoteChargingSupport增加相反方向 active operation guard。 - STOP projection 優先;StopTransaction 後至真實
AVAILABLE前顯示FINISHING / 充電已停止 / 請拔槍。 - raw
FINISHING超過 120 秒時,以connectorId + last_status_change去重記錄 warning,不改寫狀態或偽造 event。 - 新增 P0 E2E regression,保留 CloudLink baseline。
本次不變更 React FE,也不進行 DB schema migration。多案例測試仍依 SOP 等待 Ken 明確下令「開始測試」。
是由 陳國瑋 於 約 2 個月 前更新
階段 3:實作完成,等待測試啟動¶
已完成:
- FORTUNE/DEFAULT/TESLA/UNKNOWN 不再由 raw
PREPARING直接產生 RemoteStart。 - Semantic command 與
RemoteChargingSupport均加入 START/STOP 相反方向 active operation guard。 - Driver projection 改為 STOP 優先;StopTransaction 完成而 raw status 尚為
FINISHING時回傳「充電已停止/請拔槍」。 - 新增 raw
FINISHING120 秒 warning,使用connectorId + last_status_change在 process 內去重,不改寫狀態。 - 已新增 vendor strategy、operation conflict、driver projection、warning 與 scheduler Unit Test。
已執行安全驗證:
-
mvn -DskipTests package:PASS,main/test source 均編譯成功。 - 單一隔離 Unit Test:
ConnectorDriverPresentationServiceTest#build_whenTransactionStoppedButRawFinishing_returnsStoppedPleaseUnplug:PASS(1 test)。
依 SOP,以下多案例測試尚未執行,等待 Ken 明確下令「開始測試」:
mvn -Dtest=ConnectorTypeRotationStrategyTest,RemoteChargingSupportOperationConflictTest,ConnectorCommandServiceTest,ConnectorDriverPresentationServiceTest,FinishingStatusWarningServiceTest,RemoteOperationTimeoutTaskTest,RemoteOperationPersistenceServiceTest,RemoteOperationLifecycleServiceTest,ChargingOrchestratorQueueCorrelationTest,StatusNotificationHandlerLastStatusChangeTest test
這些案例為 Mockito/純 Unit Test,不啟動 Spring integration profile、不連接外部服務或共用 DB,也不修改共用資料。
是由 陳國瑋 於 約 2 個月 前更新
階段 5:Unit Test 報告待確認¶
測試報告已產出:test_report/20260810_001_redmine-1418-unit.md
結果:
-
mvn -DskipTests package:PASS。 - 單一 isolated projection test:PASS。
- 純 Unit Test:50 tests,0 failures,0 errors,0 skipped。
- 初始 command 中的
StatusNotificationHandlerLastStatusChangeTest是@SpringBootTestintegration case,因ems_branch_e2e_unconfigured不存在而有 4 個 environment errors;未修改程式,也未視為 PASS。 - 該 integration case 後續必須使用 isolated integration-test wrapper 與明確
EMS_E2E_SOURCE_DB執行。
請先確認這份測試報告;確認後才進入文件/E2E catalog 更新關卡。
是由 陳國瑋 於 約 2 個月 前更新
階段 6:文件與 E2E Catalog 待確認¶
已完成:
- E2E impact:Add
CHG-044、UpdateCHG-032、UpdateROT-013。 -
CHG-044為 P0 Conditional,trigger 包含 Fortune/Other strategy、Remote operation conflict、driverState/LIFF stop flow 與 Fortune 案場發版。 - Fortune 條件式 P0 Core Smoke 已加入
CHG-044。 - Inventory 統計更新為 167 cases:P0 51、P1 65、P2 46、P3 5。
- 更新
ai/02-backend-services.md、ai/04-frontend.md、ai/06-domain-glossary.md與本單本地紀錄。 - Catalog validator:PASS。
PASS: documents/E2E_TEST_CASE_INVENTORY.md (167 cases; P0=51, P1=65, P2=46, P3=5)
Run-specific Unit 結果仍只保留於 test_report/20260810_001_redmine-1418-unit.md,未寫入長期 catalog。實機 CHG-044 與 CloudLink baseline 尚未部署執行,不能視為 PASS。
是由 陳國瑋 於 約 2 個月 前更新
已完成 commit、push、合併 private 與 EXPO 部署。
Git:
- feature commit: f32e13a fix(#1418): 修正 Fortune 停止競態與 FINISHING 顯示
- private merge commit: 898c795 merge(#1418): 整併 Fortune 停止競態修正
- feature 與 private 均已推送 origin
部署:
- 環境:EXPO
- 服務:ems-branch-api
- 部署時間:2026-08-10 15:28 (Asia/Taipei)
- openclaw JAR SHA-256: 129fa8ee9917624fe393302e2834e3b4fd16f205b2cfc20da068a8da1dd325d9
- 備份:/opt/ems/backups/20260810-1526-redmine-1418
- 上傳檔:/opt/ems/deployments/20260810-1526-redmine-1418/api.jar
部署前安全檢查:
- CBDAX50A-24L-XX-B351-001 = AVAILABLE,current_transaction_id=NULL
- Branch ACTIVE transaction=0
- CP ACTIVE transaction=0
- active remote operation=0
部署後健康檢查:
- ems-branch-api running,RestartCount=0
- /api-docs HTTP 200
- Spring Boot Started Application(14.216 秒)
- CBDAX50A-24L-XX-B351 OCPP WebSocket 已重連,DB 為 ONLINE
- 目標 connector 仍為 AVAILABLE,Branch/CP ACTIVE transaction=0,active remote operation=0
- 無 APPLICATION FAILED / OOM / BeanCreation / SQL syntax 嚴重啟動錯誤
已知既有環境警告:
- RabbitMQ 使用 guest 驗證失敗;舊版部署前 log 已存在相同錯誤,因此非 #1418 變更造成。Branch API 與 OCPP 上線不受阻,但 connector.events 發布可能失敗,建議另案追蹤環境 MQ 認證。
尚未執行真實充電 E2E;依 SOP 等待明確「開始 E2E 測試」後再執行 CHG-044。
是由 陳國瑋 於 約 2 個月 前更新
狀態更新:
- #1419 EXPO 獨立 profile/RabbitMQ 認證修正已部署、MQ transport smoke 通過,並已合併 private(merge commit 698aba6)。
- #1418 程式修正仍在 EXPO 現行 JAR 中,MQ 推播阻礙已排除。
- 本單完成度更新為 90%,維持 In Progress。
- 唯一未完成 release gate:Fortune 實機 P0 CHG-044,需確認 STOP 後 LIFF 不倒退 CHARGING、沒有額外 RemoteStart/相反 active operation,StopTransaction 後顯示「充電已停止/請拔槍」,真實 AVAILABLE 後回 READY。
- 未取得上述實機證據前不宣稱 CHG-044 PASS,也不將 #1418 設為 Resolved。
是由 陳國瑋 於 約 2 個月 前更新
- 狀態 從 In Progress 變更為 Resolved
EXPO Fortune 實機回歸完成¶
- 修正 commit:
f32e13a - private merge:
898c795 - E2E 報告 commit:
cba4bf7 - 文件結果 commit:
e3e47d1 - 測試報告:
test_report/20260810_002_redmine-1418-expo-fortune-e2e.md
實機結果¶
- 2026-08-10 17:58:09~18:01:46(Asia/Taipei)於 EXPO Fortune PRIVATE connector
CBDAX50A-24L-XX-B351-001執行 CHG-044/CHG-032。 - Ken 手動確認 LIFF 未再出現
FINISHING -> CHARGING倒退或錯誤 RemoteStart 失敗訊息。 - 刻意維持插槍超過 120 秒;第 123 秒只記錄一次 FINISHING warning,第 169 秒仍顯示「充電已停止/請拔槍」,收到真實
StatusNotification(AVAILABLE)後才解除 FINISHING。 - STOP operation
324:SUCCESS / STOPPED;transaction33:COMPLETED / Remote。 - 停止區間只有一筆
RemoteStopTransaction與一筆StopTransaction,沒有RemoteStartTransaction、第二筆 START operation或新 transaction。 - E2E Catalog validator PASS:167 cases;P0=51、P1=65、P2=46、P3=5。
結論:P0 CHG-044 實機回歸 PASS,#1418 驗收完成。