專案

一般

配置概況

Feature #1397 » redmine-1397-driver-state-offpeak-requirements-zh-TW-rev-20260731.md

繁體中文需求修訂版(2026-07-31);取代附件 #1101。 - 陳國瑋, 2026-07-31 03:59

 

Redmine #1397 車主狀態與離峰充電需求規格

狀態:需求審查草稿

日期:2026-07-29

修訂日期:2026-07-31

需求來源:Redmine #1382

範圍:Branch/HQ API 契約與 LIFF 端到端行為。本文件尚未授權進入實作。

1. 目標

driverState 必須描述車主目前實際面對的情況,以及現在可以執行的操作。它不能只是 raw OCPP Connector status 的中文翻譯。

本次設計需要保留 #1382 之前的使用者體驗,同時正確處理:

  • 公樁與私樁。
  • 手動充電與長期離峰充電。
  • 私樁 Charge Group slots 與排隊。
  • CloudLink 與 Fortune 的廠牌差異。
  • RemoteStart/StartTransaction 與 RemoteStop/StopTransaction 的非同步流程。
  • OCPP 離線、故障、服務重啟、逾時與晚到事件。
  • 共用 WebSocket 狀態與個別使用者的 API 授權。

2. 系統責任邊界

2.1 Branch

Branch 是下列資料的唯一判斷來源:

  • driverState
  • driverState.actions 所提供的操作能力。
  • Remote operation 的生命週期。
  • 手動排隊與離峰排程狀態。
  • Command API 最終的狀態檢查與使用者授權。
  • 車主可理解的操作結果與目前阻擋原因。

Branch 必須綜合判斷:

  • raw Connector status。
  • OCPP connection status。
  • Branch 與 CP 的 active transaction。
  • e_connector_priority
  • 長期離峰設定。
  • 已持久化的 RemoteStart/RemoteStop operation。

2.2 HQ

HQ 必須:

  • 從 LINE 身分取得已驗證的車主。
  • 將可信任的 Branch user identity 傳給 Branch。
  • 原樣傳遞 Branch 的 driverState 與 operation result,不建立第二套狀態機。
  • WebSocket initial data 與後續 connector event 都使用同一份 Branch presentation。
  • 共用 Connector 狀態廣播給所有合法 sessions,但 transient
    operationResult 只送給 authenticated Branch user identity 符合 event
    targetUserId 的 session。

HQ 不得信任 LIFF 任意傳入的 userId

2.3 LIFF

FE source implementation 由 Redmine #1399 負責。本文件定義 #1398 Backend 與
#1399 Frontend 必須共同實作的契約。

LIFF 負責:

  • 顯示 Branch 提供的 title、description 與 actions。
  • areaType=public 時不顯示離峰 switch。
  • 發送 command,並顯示結構化的成功或拒絕結果。
  • WebSocket 連線正常時才允許操作。
  • 在指定情境顯示確認 modal。

driverState 存在時,LIFF 不得再用 raw status 推導是否可以充電。

3. 領域名詞

3.1 公樁(PUBLIC Connector)

  • 不參與私樁 rotation queue。
  • 不顯示離峰 switch。
  • 合法的公樁使用者不需要經過私樁 Charge Group 排隊即可要求啟動。
  • Charge Group 計算 slots 時,已預先保留公樁容量。
  • 公樁正在充電時,只有該 Transaction 的使用者可以停止;最終由 Command API 判斷。

範例:

Charge Group 實體總容量 = 7 slots
PUBLIC Connector = 2
PRIVATE 可排程容量 = 5 slots

公樁 slot 是依照設定在 Charge Group 內的公樁數量保留,不會因公樁目前離線、故障或未充電而暫時釋放給私樁。

3.2 私樁(PRIVATE Connector)

  • 透過 e_connector_user 與一位或多位 Branch user 關聯。
  • 參與 Charge Group slot allocation 與 rotation queue。
  • 支援長期離峰設定。
  • 目前仍與該 Connector 關聯的任一住戶,都可以停止私樁充電、取消手動排隊、取消離峰等待,或取消廠牌支援的 PREPARING。
  • 即使 Transaction 或離峰模式由另一位關聯住戶建立,也必須記錄真正執行停止或取消的人。

現有程式中的 owner 用語並不精確。Connector 目前只有「關聯住戶」,沒有正式的 owner role。

3.3 離峰充電

離峰充電是長期偏好,不是單次 ChargingRequest。

  • enableOffPeak=true 表示 Connector 會持續參加未來的離峰時段。
  • 開啟離峰時,必須記錄是哪一位關聯住戶開啟。
  • 只有進入適用離峰時段、scheduler 實際評估後,才建立 queue record。
  • 完成充電、暫時故障/離線,或目前 window 被抑制時,長期偏好仍維持開啟。
  • 關閉離峰必須是明確的使用者操作。

3.4 離峰 window

一段連續的 tariff OFF_PEAK 區間,就是一個 window。

  • 跨越午夜但時間連續,仍屬同一個 window。
  • 同一天兩段不連續的 OFF_PEAK 區間,屬於兩個 window。
  • Current-window suppression 到該連續 OFF_PEAK 區間結束時失效。
  • 離峰時段結束只影響「是否可自動開始」,不能強制停止已經進行中的 Transaction。

4. DriverState 核心規則

Raw OCPP PREPARING 不等於 driverState=PREPARING

只有 Branch 已送出 RemoteStart,
而且仍在等待 StartTransaction 時,
driverState 才是 PREPARING。

這項規則用來保留「先插槍、再開 App」的體驗:

廠牌 已插槍但未送 RemoteStart Raw status 手動模式 driverState
Fortune 通常為 PREPARING READY
CloudLink 通常為 AVAILABLE READY

在離峰模式下,同樣只是插槍但尚未 dispatch 的 Connector,應依 Priority 是否真的存在顯示 OFF_PEAK_WAITINGQUEUED

5. DriverState codes

Code 車主語意 主要操作
READY 沒有 active transaction、queue 或 remote operation,可手動開始 開始充電
OFF_PEAK_WAITING 長期離峰已開啟,但目前不在可派發 queue 中 關閉離峰/取消等待
QUEUED 已存在真正的手動或離峰 queue record,等待派發 取消對應來源的排隊
PREPARING RemoteStart 已送出或已接受,等待 StartTransaction 廠牌支援時可取消啟動
CHARGING Branch 或 CP 有 active transaction 停止充電
FINISHING RemoteStop 已送出或已接受,等待 StopTransaction 不可重複停止
OFFLINE_CHARGING OCPP 離線,但 Branch/CP 證據顯示可能仍在充電 不可遠端操作
OFFLINE_UNAVAILABLE OCPP 離線,且沒有 active transaction 證據 不可開始或停止
FAULTED Connector 故障,且沒有更強的 active transaction 狀態 不可開始充電
UNAVAILABLE Connector 因其他原因不可用 不可執行充電 command
UNKNOWN Branch 無法安全判斷 不可執行充電 command

BLOCKEDPAUSEDELIGIBLENOT_PLUGGEDConnectorPriority 內部狀態,不應直接變成公開的 driverState code。

6. 狀態優先順序

實作可以使用 decision service 或狀態機,但必須遵守以下規則:

  1. 只要存在 active transaction 證據,就不能顯示 READY
  2. 未完成的 STOP operation 顯示 FINISHING
  3. 未完成的 START operation 顯示 PREPARING
  4. OCPP 離線且沒有 active transaction 時,顯示 OFFLINE_UNAVAILABLE
  5. OCPP 離線但有 active transaction 證據時,顯示 OFFLINE_CHARGING
  6. FAULTED/OCPP offline 不得被 OFF_PEAK_WAITINGQUEUED 遮蔽。
  7. 真正且適用的 queue record 才可顯示 QUEUED
  8. 長期離峰已開啟、但尚無適用 queue record 時,顯示 OFF_PEAK_WAITING
  9. Fortune raw PREPARING 若沒有 RemoteStart operation,回到對應的手動或離峰狀態。

7. Actions

API 應提供具備明確業務語意的 action,FE 不應再從 startstop label 猜測用途。

Action code:

  • START_CHARGING
  • STOP_CHARGING
  • CANCEL_START
  • CANCEL_MANUAL_START
  • ENABLE_OFF_PEAK
  • DISABLE_OFF_PEAK

每個 action 至少提供:

{
  "code": "CANCEL_START",
  "enabled": true,
  "label": "取消啟動",
  "disabledMessage": null,
  "requiresConfirmation": true
}

新契約只提供 driverState.actions[]。舊版 driverState.start
driverState.stop 直接移除,不提供相容層。Redmine #1398 與 #1399 必須協調部署。

WebSocket 中的共用 actions 表達設備與流程能力,不代表目前使用者一定有權執行。Command API 仍需做最後授權。

8. 手動充電流程

8.1 可立即開始

READY
  -> 車主要求 START_CHARGING
  -> RemoteStart operation SENT/ACCEPTED
  -> PREPARING
  -> 收到 StartTransaction
  -> CHARGING

8.2 私樁 slots 已滿

READY
  -> 車主要求 START_CHARGING
  -> PRIVATE slots 已滿
  -> 建立 MANUAL queue request
  -> QUEUED

Queue 必須保存:

source = MANUAL
requestedByUserId = 原始要求開始的車主

Slot 釋放後,scheduler 必須使用原始 requester 的 IdTag dispatch,不能再選 Connector 關聯住戶中的第一人。

CANCEL_MANUAL_START 只刪除手動 queue,不得修改 enableOffPeak

8.3 手動啟動失敗

  • 手動 RemoteStart 不自動重試。
  • REJECTEDFAILED 或最終 TIMED_OUT 後回 READY
  • 失敗結果與目前 driverState 分開回傳。
  • 如果 timeout 後仍收到晚到的 StartTransaction,實際 Transaction 優先,driverState 改為 CHARGING

9. PREPARING 行為

以下 operation status 都顯示 PREPARING

  • SENT
  • ACCEPTED

廠牌取消能力:

廠牌 PREPARING action
CloudLink CANCEL_START 可用;Branch 可執行既有的 waiting-mode exit 流程
Fortune CANCEL_START 不可用,並提供原因;車主需拔槍或等待 timeout

取消成功後:

  • 手動來源回 READY
  • 離峰來源同時關閉長期離峰、移除相關 Priority,回到手動模式。

RemoteStart 明確收到 Rejected

  • 手動來源回 READY
  • 離峰來源停止本 window 重試,回 OFF_PEAK_WAITING
  • reason 使用 START_REJECTED_FOR_CURRENT_WINDOW
  • 下一個獨立 off-peak window 可以重新排程。

RemoteStart 沒有回應:

  • 保留各廠牌現有的 Charge Point RemoteStart 等待時間。
  • Branch 使用相同等待長度作為 operation business deadline,並從
    RemoteStart Accepted 開始計時;本需求不修改 Charge Point 等待行為。
  • Business deadline 後保留 30 秒訊息 reconciliation grace。Grace 只接收已在
    傳輸中的 StartTransaction/StatusNotification,不延長 Charge Point
    relay 或電流偵測行為。
  • Charge Point 等待到期後若回報 AVAILABLE/FAULTED 且沒有 Transaction,
    Branch 可依明確設備結果終結 operation,不必等滿 grace。
  • Grace 結束仍沒有 StartTransaction 或明確 terminal device result,才發布
    TIMED_OUT
  • Operation 未完成期間不得盲目重送。
  • 離峰的 transient timeout 可回 QUEUED,在之後正常 scheduler cycle 重試。
  • 同一 Connector 不得同時存在重疊 START operation。
  • 目前 window 結束後停止重試。

10. CHARGING 與 FINISHING

CHARGING
  -> 車主確認 STOP_CHARGING
  -> RemoteStop operation SENT/ACCEPTED
  -> FINISHING
  -> 收到 StopTransaction
  -> Transaction 完成

FINISHING 期間:

  • Start 停用。
  • Stop 停用。
  • Off-peak switch command 由 API 拒絕。
  • Branch 重啟後必須從持久化 operation 還原 FINISHING。

RemoteStop 沒有回應時:

  1. Backend 可設定的 30 秒 Stop deadline 以前保持 FINISHING
  2. 不自動重送 RemoteStop。
  3. 期間收到 StopTransaction 就正常完成。
  4. Deadline 到期時重新查實際 Transaction。
  5. Transaction 仍為 ACTIVE:operation 標記 TIMED_OUT,driverState 回 CHARGING,顯示失敗並允許新的明確停止要求。
  6. Transaction 已完成:視為晚到或對應延遲,進行成功 reconciliation。
  7. Deadline 後收到 late StopTransaction 仍完成 Transaction reconciliation,
    但不重新開啟已終結 operation,也不重播 timeout modal。

離峰 current-window suppression 只有在 Transaction 確認停止後才生效。

11. 長期離峰流程

11.1 開啟離峰

私樁 off-peak ON 成功後立即:

enableOffPeak = true
enabledByUserId = 已驗證的關聯住戶
driverState = OFF_PEAK_WAITING

API 不等待 e_connector_priority 建立才回應。

11.2 進入適用離峰時段

Scheduler 在適用 window 評估 Connector 後:

建立或更新 source=OFF_PEAK 的 Priority
driverState = QUEUED

OFF_PEAK_WAITING 與 OFF_PEAK QUEUED 都提供 DISABLE_OFF_PEAK,LIFF 顯示「取消排隊」。

11.3 尚無充電需求

若 dispatch 後沒有建立充電需求:

driverState = QUEUED
reason = NO_CHARGING_DEMAND

這是中性狀態,不是錯誤。Description 不得暗示「充電槍沒有正確連接」。

因為車輛可能稍晚才到,scheduler 可以在同一 window 的後續正常 cycle 再試。

11.4 已開始充電

收到 StartTransaction:

driverState = CHARGING

SUSPENDED_EVSUSPENDED_EVSE 仍屬 charging-like,因為 Transaction 還是 ACTIVE。此時不得建立另一筆 RemoteStart。

11.5 車主停止離峰充電

LIFF modal 必須清楚告知:

  • 將停止目前的 active transaction。
  • 本次離峰 window 不會再自動啟動。
  • 長期離峰偏好不會因此關閉。

收到 StopTransaction、確認停止後:

enableOffPeak = true
driverState = OFF_PEAK_WAITING
reason = USER_CANCELLED_FOR_CURRENT_WINDOW

車主若要在同一 window 重新加入排程,必須先將離峰 switch OFF,再切回 ON。

11.6 Transaction 因其他原因結束

OCPP 1.6 沒有可靠的 FullyCharged StopReason,A17 也無法可靠區分滿電或拔槍:

  • A17 MeterValues 沒有 SoC。
  • CP stop detector 可能把持續低電流寫成 EVDisconnected

因此 UI 不得宣稱「已充滿」或「已拔槍」。

如果本 off-peak window 內確實建立過 Transaction,之後收到 StopTransaction:

enableOffPeak = true
driverState = OFF_PEAK_WAITING
reason = TRANSACTION_ENDED_FOR_CURRENT_WINDOW

本 window 不再自動啟動,下一個 off-peak window 正常重新排程。

若只有 SUSPENDED_EV、沒有 StopTransaction,表示 Transaction 尚未結束,不適用本規則。

11.7 Current-window suppression

Suppression 必須持久化。原因是現有 scheduler 會在後續 cycle 重設非 CHARGING 的 Priority。

Suppression reason 至少包含:

  • USER_CANCELLED_FOR_CURRENT_WINDOW
  • START_REJECTED_FOR_CURRENT_WINDOW
  • TRANSACTION_ENDED_FOR_CURRENT_WINDOW

Suppression 一直維持到目前連續 OFF_PEAK tariff range 結束。

12. Semantic Command API 與離峰 switch

12.1 統一 LIFF Command API

LIFF 的所有車主操作都送到同一個 HQ endpoint:

POST /api/connector/commands
{
  "buildingId": "BLD000000014",
  "connectorId": "CP001-001",
  "action": "START_CHARGING"
}

HQ 從 LINE session 取得 authenticated Branch user。LIFF 不得傳入 userId
HQ 再以相同 semantic command 呼叫 Branch:

POST /api/cp/hq/connector/commands

Command code:

  • START_CHARGING
  • STOP_CHARGING
  • CANCEL_START
  • CANCEL_MANUAL_START
  • ENABLE_OFF_PEAK
  • DISABLE_OFF_PEAK

RemoteStart/RemoteStop 完成驗證、持久化 operation 並成功 dispatch OCPP 後,
回 HTTP 202 Accepted。Response 包含 operationId
operationStatus=SENT 與最新 driverState,不等待 OCPP CALLRESULT 或
StartTransaction/StopTransaction。

同步完成的取消與離峰偏好 command 回 HTTP 200 OK 與最新實際狀態。
Dispatch 前的授權或狀態衝突使用結構化 403409

#1398/#1399 協調部署後,移除既有 LIFF-facing /connector/start
/connector/stop/connector/dequeue/connector/offpeak-switch
低階 engineer/OCPP endpoint 保留,但 LIFF 不得直接使用。

12.2 FE 行為

  • PUBLIC:不顯示 switch。
  • PRIVATE 且 WebSocket 已連線:switch 可維持可點擊。
  • WebSocket 斷線:不得使用舊 actions 或舊 switch state 發送 command。

FE 不維護複雜的 business-state enabled/disabled matrix。

Switch 分別對應統一 command endpoint 的 ENABLE_OFF_PEAK
DISABLE_OFF_PEAK。Primary action 提供的 DISABLE_OFF_PEAK 也使用同一 command。

12.3 API 是最終判斷者

Switch request 送出後,Branch 以最新資料驗證並回傳:

  • 成功:實際 enableOffPeak、最新 driverState,並發布 event。
  • 拒絕:結構化 error code/message、未變更的實際 enableOffPeak、最新 driverState

API 拒絕以下操作:

  • PUBLIC Connector。
  • Active PREPARINGCHARGINGFINISHING
  • 存在 MANUAL queue 時要求開啟 off-peak。
  • 非合法關聯住戶。
  • 永久或結構性的設定錯誤。

12.4 開啟時必須立即拒絕的情況

  • Connector 不是 PRIVATE。
  • 使用者不是目前關聯住戶。
  • 沒有 Charge Group。
  • 沒有有效 tariff 或 tariff 沒有 OFF_PEAK range。
  • 開啟者沒有 IdTag。
  • 開啟者的 IdTag 未授權。

12.5 不應阻止開啟的暫時情況

  • 目前不在離峰時段。
  • 目前沒有 slot。
  • 沒有車或沒有充電需求。
  • OCPP offline。
  • Connector faulted。

12.6 開啟後資料發生變化

  • 開啟者失去 Connector 關聯:關閉離峰、清除 Priority 與 enabled actor,不轉移給其他住戶。
  • Connector 沒有任何關聯住戶:關閉離峰並清除 Priority。
  • 開啟者 IdTag 之後失效,但仍保持關聯:保留長期偏好與 actor,暫停自動啟動,顯示原因;授權恢復後自動恢復排程。
  • Tariff 之後失效或停用:保留長期偏好並暫停;有效設定恢復後自動恢復。
  • Connector 從 PRIVATE 改成 PUBLIC:關閉離峰並清除相關狀態。

Charge Group 刪除保護另由 Redmine #1395 處理。

PUBLIC slot 設定 guard 與 runtime slots==0 防線另由 Redmine #1396 處理。

13. Queue 來源與取消語意

e_connector_priority 必須區分:

source = MANUAL | OFF_PEAK
requestedByUserId
Queue 來源 LIFF 顯示 Command 實際效果
MANUAL 取消排隊 CANCEL_MANUAL_START 只刪除手動 request
OFF_PEAK 取消排隊 DISABLE_OFF_PEAK enableOffPeak=false、刪除 Priority、清除 enabled actor 與 suppression

現有語意不明確的 dequeue 行為不能再當作 source of truth。

Priority row 已 FINISHED 或已被 current-window suppression 抑制時,不能只因 row 還存在就顯示 QUEUED

14. 使用者授權

14.1 共用 presentation

MQ/WebSocket 的 driverState.actions 是共用的設備與流程能力,不依每個 WebSocket session 個人化。

這可以避免 MQ 狀態產生器與個別使用者授權高度耦合。

共用 driverState、reason 與 actions 廣播給該 Connector 的所有合法 sessions。
Transient operation result 則定向傳送:

  • 手動 command:targetUserId=requestedByUserId
  • Scheduler OFF_PEAK command:targetUserId=enabledByUserId
  • Branch MQ 內部 payload 提供 operationResult.targetUserId
  • HQ 依 session 已驗證的 Branch user identity 篩選,不重新執行 Branch 授權。
  • HQ 不把其他使用者的 target identity 暴露給 browser。

14.2 Command API

Command API 必須重新檢查:

  • 已驗證的使用者。
  • Connector area 與住戶關聯。
  • 目前 Transaction/request actor。
  • 最新 operation 與 driverState。
  • 最新 off-peak 與 queue 狀態。
  • OCPP connection 與廠牌能力。

不允許的操作使用結構化 403/409 回應。

14.3 停止規則

  • PUBLIC:只有 Transaction user 可以停止;其他使用者收到 TRANSACTION_OWNED_BY_OTHER_USER
  • PRIVATE:任一目前關聯住戶都可以停止;保留原 Transaction owner,並 audit stopRequestedBy
  • PRIVATE queue/off-peak/PREPARING:任一目前關聯住戶都可以取消,並 audit cancelledBydisabledBy

15. Remote operation 持久化

15.1 A17 現況

e_remote_transaction_log 已經會為每次 remote command 保存一筆資料。

A17 驗證結果:

  • START/SUCCESS:17,508 筆。
  • START/REJECTED:761 筆。
  • 現有 SUCCESS 只代表 RemoteStart.conf Accepted,不代表已收到 StartTransaction。
  • 即使後續真的建立 Transaction,現有 transaction_id 仍可能是 NULL。

因此現有 row 適合擴充,但目前的 status 無法表達完整 operation lifecycle。

15.2 決定方向

優先擴充 e_remote_transaction_log,不新增第二張 operation table;除非 implementation review 發現無法相容的外部限制。

保留現有 command response status,另外新增 business lifecycle status。

API/MQ/WebSocket 的 operationId 直接使用既有
e_remote_transaction_log.id,不新增 operation-id column。
ocpp_unique_id 保持為內部 OCPP correlation;只有完成 Transaction correlation
時才回填 transaction_id

建議新增:

source                  MANUAL / OFF_PEAK
requested_by_user_id    實際已驗證的操作人
operation_status        SENT / ACCEPTED / STARTED / STARTED_LATE /
                        REJECTED / TIMED_OUT / CANCELLED / FAILED /
                        STOPPED
deadline_at
completed_at
failure_code
transaction_id          StartTransaction 對應成功後回填

需要支援的查詢:

  • Connector 最新未完成 START。
  • Connector/Transaction 最新未完成 STOP。
  • PREPARING/FINISHING initial snapshot 所需的 active operation。
  • 同一 Connector 最多只能有一筆適用的未完成 START;同一 Transaction 最多一筆未完成 STOP。

依 repository 慣例,使用普通 index 支援查詢;正確性由 service transaction 控制。若要新增 DB UNIQUE constraint,必須另外說明並取得同意。

15.3 生命週期

RemoteStart:

SENT -> ACCEPTED -> STARTED
  |        |
  |        +-> REJECTED / TIMED_OUT / CANCELLED / FAILED
  +-> FAILED

RemoteStop:

SENT -> ACCEPTED -> STOPPED
  |        |
  |        +-> REJECTED / TIMED_OUT / FAILED
  +-> FAILED

Branch 重啟後,依未完成 operation 與 deadline 還原 PREPARINGFINISHING

舊資料沒有新增 lifecycle 欄位時,只視為歷史紀錄,不能拿來判定目前 operation。

16. Operation result API

目前狀態、active operation 與 transient terminal operation result 是三個不同概念。

非同步 command 接受後的 response:

{
  "action": "START_CHARGING",
  "operationId": "18456",
  "operationStatus": "SENT",
  "driverState": {
    "code": "PREPARING",
    "title": "準備中",
    "description": "正在等待充電樁開始交易",
    "actions": []
  }
}

後續 targeted WebSocket event 可以包含:

{
  "driverState": {
    "code": "READY",
    "title": "可開始充電",
    "description": "充電樁已待命,可以開始充電",
    "actions": [
      {
        "code": "START_CHARGING",
        "enabled": true,
        "label": "開始充電",
        "disabledMessage": null,
        "requiresConfirmation": false
      }
    ]
  },
  "operationResult": {
    "operationId": "18456",
    "type": "START_CHARGING",
    "source": "MANUAL",
    "status": "REJECTED",
    "errorCode": "REMOTE_START_REJECTED",
    "message": "充電樁未接受啟動指令",
    "retryable": true,
    "occurredAt": "2026-07-29T14:27:41",
    "nextRetryAt": null
  }
}

規則:

  • Dispatch 前即可判定的失敗由 HTTP 403409 response 表達。
  • 非同步 terminal result 只在狀態轉換發生時推送一次。
  • 手動 result 的 target 是 requestedByUserId;scheduler OFF_PEAK result 的
    target 是 enabledByUserId
  • FE 以 operationId 去重,modal 只顯示一次。
  • WebSocket initial snapshot 不重播 terminal operationResult
  • Initial snapshot 只提供尚未終結的 activeOperation,用於把
    PREPARING/FINISHING 與 operationId 對應。
  • 尚未解除的永久問題留在目前 driverState.reason 與 description,不靠 modal
    重播。
  • 實際 StartTransaction/StopTransaction 的事實優先於先前 timeout
    presentation。

17. WebSocket Connector 詳細頁

17.1 第一次開啟

  • Connector 詳細頁不另外呼叫 REST detail API。
  • Connector 資訊與 driverState 由 WebSocket initial data 提供。
  • Initial data 只在 START/STOP 尚未終結時提供 activeOperation
  • Initial data 不包含 terminal transient operationResult
  • 不使用 polling。

WebSocket initial connection 失敗時:

  1. 進行一次短時間 reconnect。
  2. 再次失敗就返回 Connector list。
  3. 顯示 modal,告知目前無法取得即時 Connector 資訊。
  4. 不使用 REST detail fallback。

17.2 已連線後斷線

  • 立即停止使用舊 driverState.actions
  • 停用 Start、Stop 與 off-peak 操作。
  • 嘗試 reconnect 一次。
  • 只有收到新的 initial snapshot 後才恢復操作。
  • Reconnect 失敗就返回 list 並顯示 modal。

17.3 Targeted result 傳送

  • 共用 Connector presentation 仍送給所有合法 Connector sessions。
  • HQ 只把 operationResult 傳給 authenticated target session。
  • 非 target 住戶收到共用狀態變更,但不顯示該次 operation modal。
  • FE 以 operationId 處理 reconnect race 去重;WebSocket 斷線期間不得使用 stale
    actions 執行 command。

18. 失敗與重試分類

18.1 手動來源

  • 不自動重試。
  • 明確通知車主操作結果。
  • 回到最新且安全的 driverState。

18.2 可恢復的離峰失敗

  • 不建立 immediate retry loop。
  • 每個正常 scheduler cycle 最多嘗試一次。
  • 不允許重疊 operation。
  • 只有目前 off-peak window 仍適用時才可重試。
  • NO_CHARGING_DEMAND 是中性狀態,可在同一 window 後續重試。
  • Timeout 在沒有 suppression 時,可於之後 cycle 重試。

18.3 本 window 終止

  • RemoteStart 明確 Rejected
  • 車主要求停止,且已收到 StopTransaction。
  • 本 window 已建立 Transaction,之後 Transaction 結束。

以上狀態顯示 OFF_PEAK_WAITING,附帶 current-window reason;只有下一個 window 或車主明確 OFF -> ON 才重新加入。

18.4 永久/設定問題暫停

  • 開啟後 IdTag 授權失效。
  • 開啟後 tariff 失效或停用。
  • 其他只靠等待不會成功的結構性問題。

依已確認規則保留長期偏好、停止重複 command,並提供目前可理解的 reason。

19. FE 必須能完成的畫面行為

雖然 FE 實作不屬於本 Backend 工作,但 API 必須支援:

  • 依 semantic action 顯示 label。
  • Operation-backed PREPARING 顯示「準備中」。
  • Operation-backed FINISHING 顯示「停止中」。
  • OFF_PEAK_WAITING 與 OFF_PEAK QUEUED 都顯示「取消排隊」。
  • 手動 queue cancellation 與 off-peak disable 使用不同 command。
  • 停止 active off-peak charging 前顯示確認 modal。
  • PUBLIC 不顯示 off-peak switch。
  • PRIVATE switch command 交由 API 判斷,不在 FE 複製 business matrix。
  • WebSocket 斷線時停用所有 operation。
  • Command 被拒絕時顯示結構化 modal,且 switch/driverState 不做錯誤的 optimistic update。
  • 所有操作都將 semantic code 送到 /api/connector/commands
  • 非同步 202 response 使用 operationId 對應後續 targeted result。
  • Terminal operation result 只顯示一次,initial snapshot 不重播。

20. 必要回歸測試範圍

本需求涉及使用者可見行為、API DTO、schema、queue、off-peak scheduler、OCPP 與 WebSocket。E2E Catalog impact 為 AddUpdate

後續 implementation plan 至少必須涵蓋:

  1. CloudLink 公/私樁手動 RemoteStart -> PREPARING -> CHARGING。
  2. Fortune 先插槍的 raw PREPARING,在 RemoteStart 前仍顯示 READY。
  3. PREPARING 取消:CloudLink 支援、Fortune 不支援。
  4. Branch 重啟後可還原 PREPARING 與 FINISHING。
  5. 私樁 slots full 的手動 queue 保存 requester 並可取消。
  6. Off-peak ON 在 Priority 建立前立即回 OFF_PEAK_WAITING。
  7. Off-peak Priority 顯示 QUEUED 並可執行 DISABLE_OFF_PEAK。
  8. NO_CHARGING_DEMAND 使用中性文案,且可在同一 window 後續重試。
  9. Off-peak RemoteStart 明確 Rejected 後抑制目前 window。
  10. 車主停止離峰,只有 StopTransaction 後才抑制目前 window。
  11. Transaction 結束後不再發生 A17 每 10 分鐘重送 RemoteStart。
  12. SuspendedEV 保持 active transaction,不建立另一筆 Start。
  13. RemoteStop pending 顯示 FINISHING 並阻止重複 Stop。
  14. Offline/fault 保留長期 off-peak,並阻止不安全 remote operation。
  15. PUBLIC Transaction stop 授權與 PRIVATE 關聯住戶 stop 行為。
  16. PRIVATE off-peak switch API 成功/拒絕都回實際狀態。
  17. WebSocket initial failure 與 established disconnect 不使用 stale actions。
  18. 晚到 StartTransaction/StopTransaction 以實際 Transaction 狀態完成 reconciliation。
  19. 統一 semantic command 能區分 CANCEL_START、CANCEL_MANUAL_START 與
    DISABLE_OFF_PEAK,不再使用模糊 dequeue。
  20. 非同步 Start/Stop 回 202 與 operationId;同步 command 回 200
  21. 共用狀態送給所有合法 sessions,但 operationResult 只送給
    requestedBy/enabledBy target 並完成去重。
  22. RemoteStart 保留既有廠牌等待時間、使用一致的 business deadline 與 30 秒
    reconciliation grace。
  23. RemoteStop 使用可設定的 30 秒 deadline;timeout 後允許明確重試,且能
    reconciliation late StopTransaction。

Release pack 與 P0-P3 分級,等需求文件確認後在 implementation plan gate 決定。

21. 不在本次範圍

  • Backend ticket #1398 內的 FE source implementation;FE 另由 Redmine #1399 處理。
  • Charge Group 刪除 guard:Redmine #1395。
  • PUBLIC slot 設定 guard 與 runtime slots==0 防線:Redmine #1396。
  • 重新設計 raw OCPP ConnectorStatus enum。
  • 宣稱 StopTransaction 能證明滿電或拔槍。
  • 新增 polling。

22. Review gate

進入實作前:

  1. Ken 以本文件一次審查需求與狀態契約。
  2. 所有修正先更新至本文件。
  3. 另外產出 implementation plan,列出確切程式、schema、測試檔案與 E2E impact。
  4. Implementation plan 明確同意後才開始 coding。
(4-4/5)