Feature #1397 » redmine-1397-driver-state-offpeak-requirements-rev-20260731.md
Redmine #1397 Driver State / Off-Peak Requirements
Status: Requirement review draft
Date: 2026-07-29
Revision: 2026-07-31
Requirement origin: Redmine #1382
Scope: Branch/HQ API contract and end-to-end LIFF behavior. This document does not authorize implementation yet.
1. Goal
driverState must describe what the driver is experiencing and what operation is currently possible. It must not be a direct translation of the raw OCPP connector status.
The design must preserve the pre-#1382 user experience while correctly handling:
- PUBLIC and PRIVATE connectors.
- Manual charging and long-term off-peak charging.
- PRIVATE Charge Group slots and queueing.
- Vendor differences between CloudLink and Fortune.
- Asynchronous RemoteStart/StartTransaction and RemoteStop/StopTransaction flows.
- OCPP offline, fault, restart, timeout and late-event recovery.
- Shared WebSocket updates and user-specific command authorization.
2. Responsibility Boundaries
2.1 Branch
Branch is the source of truth for:
-
driverState. - Operation capability exposed in
driverState.actions. - Remote operation lifecycle.
- Queue and off-peak scheduling state.
- Authoritative command validation and authorization.
- User-facing operation result and current blocking reason.
Branch derives these values from raw connector status, OCPP connection state, active transactions, queue records, off-peak settings and persisted remote operations.
2.2 HQ
HQ must:
- Derive the authenticated driver from the LINE identity.
- Send a trusted Branch user identity to Branch.
- Pass Branch
driverStateand operation results through without deriving a second state machine. - Publish the same Branch presentation through WebSocket initial data and subsequent connector events.
- Broadcast shared connector state to legal connector sessions, but deliver a transient
operationResultonly to the session whose authenticated Branch user identity matches
the eventtargetUserId.
HQ must not trust an arbitrary userId supplied by LIFF.
2.3 LIFF
FE source implementation is delivered by Redmine #1399. This document defines the
contract that #1398 Backend and #1399 Frontend implement together.
LIFF is responsible for:
- Rendering Branch-provided title, description and actions.
- Hiding the off-peak switch for
areaType=public. - Sending commands and displaying structured success/rejection results.
- Treating WebSocket connectivity as a prerequisite for operations.
- Showing confirmation modals where required.
LIFF must not derive charging capability from raw status when driverState is available.
3. Domain Definitions
3.1 PUBLIC connector
- Does not participate in PRIVATE rotation queueing.
- Does not expose an off-peak switch.
- May be started by an authorized public user without PRIVATE Charge Group queueing.
- Uses capacity reserved in advance by the Charge Group slot calculation.
- Only the user associated with the active public transaction may stop it; the command API is authoritative.
For a Charge Group with seven physical slots and two PUBLIC connectors, five slots remain available for PRIVATE scheduling. PUBLIC slot reservation is based on configured PUBLIC connector count, not current online or charging status.
3.2 PRIVATE connector
- Is associated with one or more Branch users through
e_connector_user. - Participates in Charge Group slot allocation and rotation queueing.
- Supports the long-term off-peak setting.
- Any currently linked resident may stop charging, cancel a manual queue, cancel off-peak waiting or cancel a supported PREPARING operation.
- The API must audit the actual actor even when the transaction or off-peak mode was initiated by another linked resident.
The existing term "owner" is misleading. A Connector has linked residents, not a first-class owner role.
3.3 Off-peak charging
Off-peak is a long-term preference, not a one-time charging request.
-
enableOffPeak=truemeans the connector remains enrolled across future off-peak windows. - Enabling off-peak must remember the resident who enabled it.
- A queue record is created only when the connector enters an applicable off-peak window and the scheduler evaluates it.
- The long-term preference remains enabled after charging completes, after a temporary fault/offline period, and after the current window is suppressed.
- Disabling off-peak is an explicit user operation.
3.4 Off-peak window
One continuous tariff OFF_PEAK interval is one window.
- A range crossing midnight remains one continuous window.
- Two disjoint
OFF_PEAKtariff ranges are two windows. - Window suppression expires at the end of that continuous range.
- The end of an off-peak window controls automatic start eligibility only. It must not force-stop an already active transaction.
4. Core Driver-State Rule
Raw OCPP PREPARING does not imply driverState=PREPARING.
driverState=PREPARING
only when Branch has dispatched a RemoteStart operation
and that operation is still waiting for StartTransaction.
This preserves plug-first behavior:
| Vendor | Plugged in, no RemoteStart | Raw status | Driver state in manual mode |
|---|---|---|---|
| Fortune | Yes | Usually PREPARING
|
READY |
| CloudLink | Yes | Usually AVAILABLE
|
READY |
In off-peak mode, the same plug-only connector remains OFF_PEAK_WAITING or QUEUED, depending on whether a real Priority record has been created.
5. Driver-State Codes
| Code | Meaning | Primary driver operation |
|---|---|---|
READY |
No active transaction, queue or remote operation; manual start is available | Start charging |
OFF_PEAK_WAITING |
Long-term off-peak is enabled, but the connector is not currently dispatchable in the active queue | Disable off-peak / cancel waiting |
QUEUED |
A real manual or off-peak queue record is waiting for dispatch | Cancel the correct queue source |
PREPARING |
RemoteStart has been sent or accepted and StartTransaction is pending | Cancel start when the vendor supports it |
CHARGING |
An active Branch or CP transaction is present | Stop charging |
FINISHING |
RemoteStop has been sent or accepted and StopTransaction is pending | No repeated stop |
OFFLINE_CHARGING |
OCPP is offline but Branch/CP transaction evidence says charging may still be active | No remote operation |
OFFLINE_UNAVAILABLE |
OCPP is offline and there is no active transaction evidence | Disable off-peak remains possible through a connected command channel only |
FAULTED |
The connector is faulted and no stronger active-transaction presentation applies | Disable off-peak may remain possible |
UNAVAILABLE |
Connector is unavailable for another reason | No charging command |
UNKNOWN |
Branch cannot derive a safe state | No charging command |
Internal ConnectorPriority states such as BLOCKED, PAUSED, ELIGIBLE and NOT_PLUGGED are not automatically public driver-state codes.
6. Presentation Precedence
The exact implementation may be expressed as a decision service, but it must preserve these priorities:
- Active transaction evidence must never be presented as
READY. - An unresolved STOP operation is
FINISHING. - An unresolved START operation is
PREPARING. - OCPP offline without an active transaction is
OFFLINE_UNAVAILABLE. - OCPP offline with active transaction evidence is
OFFLINE_CHARGING. - FAULTED/OFFLINE must not be hidden by
OFF_PEAK_WAITINGorQUEUED. - A real applicable queue record may project
QUEUED. - Long-term off-peak without an applicable queue record projects
OFF_PEAK_WAITING. - Raw Fortune
PREPARINGwithout an operation falls back to the applicable manual/off-peak state.
7. Actions
The API should expose semantic actions rather than forcing FE to reinterpret start and stop labels.
Action identifiers:
START_CHARGINGSTOP_CHARGINGCANCEL_STARTCANCEL_MANUAL_STARTENABLE_OFF_PEAKDISABLE_OFF_PEAK
Each action should provide at least:
{
"code": "CANCEL_START",
"enabled": true,
"label": "取消啟動",
"disabledMessage": null,
"requiresConfirmation": true
}
The new contract contains only driverState.actions[]. The old driverState.start and
driverState.stop fields are removed without a compatibility layer. Redmine #1398 and
#1399 must be deployed together.
Shared WebSocket actions describe operational/device capability. They are not proof that the current user is authorized. The command API performs the final user/state check.
8. Manual Charging Flow
8.1 Immediate start
READY
-> user requests START_CHARGING
-> RemoteStart operation SENT/ACCEPTED
-> PREPARING
-> StartTransaction
-> CHARGING
8.2 PRIVATE slots full
READY
-> user requests START_CHARGING
-> PRIVATE slots are full
-> create MANUAL queue request
-> QUEUED
The queue must preserve:
source=MANUALrequestedByUserId
When a slot is available, dispatch must use the original requester's IdTag. It must not select the first linked resident.
CANCEL_MANUAL_START deletes only the manual queue request. It does not modify enableOffPeak.
8.3 Manual start failure
- Manual RemoteStart does not auto-retry.
- Rejected, failed or final timeout returns to
READY. - The explicit operation failure is returned/pushed separately from the current state.
- A late StartTransaction is accepted as the stronger fact and changes the connector to
CHARGING.
9. PREPARING Behavior
Both operation states below project PREPARING:
SENTACCEPTED
Vendor cancellation differs:
| Vendor | PREPARING action |
|---|---|
| CloudLink |
CANCEL_START enabled; Branch may use the supported waiting-mode exit flow |
| Fortune |
CANCEL_START unavailable/disabled with a clear explanation; the driver must unplug or wait for timeout |
If cancellation succeeds:
- Manual source returns to
READY. - Off-peak source disables the long-term off-peak mode, removes applicable Priority records and returns to manual mode.
If RemoteStart receives an explicit Rejected:
- Manual source returns to
READY. - Off-peak source stops retrying for the current window and becomes
OFF_PEAK_WAITING. - Off-peak reason is
START_REJECTED_FOR_CURRENT_WINDOW. - The next separate off-peak window may schedule again.
If RemoteStart receives no response:
- Preserve each vendor's existing Charge Point RemoteStart wait time.
- The Branch business deadline uses the same wait duration and starts from
RemoteStartAccepted; this work does not change Charge Point wait behavior. - After the business deadline, allow a 30-second message-reconciliation grace period.
This grace receives an in-flight StartTransaction/StatusNotification; it does not
extend Charge Point relay or current-detection behavior. - If the Charge Point reports AVAILABLE/FAULTED without a Transaction after its wait
expires, Branch may terminate the operation from that explicit device result. - Publish
TIMED_OUTonly after reconciliation grace expires with no StartTransaction
or explicit terminal device result. - Do not blindly resend while an operation is unresolved.
- A transient off-peak timeout may return to
QUEUEDand retry in a later normal scheduler cycle. - There must be no overlapping START operation.
- Retry stops at the end of the current off-peak window.
10. CHARGING and FINISHING
CHARGING
-> user confirms STOP_CHARGING
-> RemoteStop operation SENT/ACCEPTED
-> FINISHING
-> StopTransaction
-> transaction completed
While FINISHING:
- Start is disabled.
- Stop is disabled.
- Off-peak switch commands are rejected.
- Branch restart must recover the FINISHING state from persistence.
If RemoteStop has no response:
- Keep
FINISHINGuntil the configurable 30-second stop deadline. - Do not automatically resend.
- If StopTransaction arrives, complete normally.
- At deadline, re-check the actual transaction.
- If still active, mark the operation
TIMED_OUT, return toCHARGING, expose the failure and allow a new explicit stop request. - If already completed, reconcile as a successful stop.
- A late StopTransaction still completes transaction reconciliation, but it does not
reopen the terminal operation or replay the timeout modal.
Off-peak current-window suppression is applied only after the transaction is actually confirmed stopped.
11. Long-Term Off-Peak Flow
11.1 Enable
After a successful PRIVATE off-peak ON command:
enableOffPeak = true
enabledByUserId = authenticated linked resident
driverState = OFF_PEAK_WAITING
This response is immediate. It does not wait for e_connector_priority.
11.2 Enter applicable window
When the scheduler evaluates the connector in an applicable off-peak window:
create/update OFF_PEAK Priority
driverState = QUEUED
Both OFF_PEAK_WAITING and OFF_PEAK QUEUED provide DISABLE_OFF_PEAK, shown to the driver as "取消排隊".
11.3 No charging demand
If dispatch cannot establish charging demand:
driverState = QUEUED
reason = NO_CHARGING_DEMAND
This is neutral, not an error. The description must not imply that the cable is connected incorrectly.
The scheduler may try again in a later normal cycle in the same off-peak window because the vehicle may arrive later.
11.4 Active charging
When StartTransaction is received:
driverState = CHARGING
SUSPENDED_EV and SUSPENDED_EVSE remain charging-like because the Transaction is still active. They must not trigger a new RemoteStart.
11.5 Driver stops off-peak charging
LIFF must confirm that stopping:
- Ends the active charging transaction.
- Prevents automatic restart for the rest of the current off-peak window.
- Does not disable the long-term off-peak preference.
After confirmed StopTransaction:
enableOffPeak = true
driverState = OFF_PEAK_WAITING
reason = USER_CANCELLED_FOR_CURRENT_WINDOW
The driver can re-enter the same window only by switching off-peak OFF and then ON again.
11.6 Transaction ends for another reason
OCPP 1.6 has no reliable FullyCharged StopReason. A17 also cannot reliably distinguish full charge from unplugging:
- A17 has no SoC MeterValues.
- The CP stop detector may map sustained low current to
EVDisconnected.
Therefore the presentation must not claim "fully charged" or "unplugged".
If an off-peak Transaction actually started and later receives StopTransaction:
enableOffPeak = true
driverState = OFF_PEAK_WAITING
reason = TRANSACTION_ENDED_FOR_CURRENT_WINDOW
The current window is suppressed and the next off-peak window schedules normally.
If the connector reports SUSPENDED_EV without StopTransaction, the Transaction remains active and this rule does not apply.
11.7 Current-window suppression
Suppression must be persisted because the current scheduler resets non-CHARGING Priority states during later cycles.
Suppression reasons include:
USER_CANCELLED_FOR_CURRENT_WINDOWSTART_REJECTED_FOR_CURRENT_WINDOWTRANSACTION_ENDED_FOR_CURRENT_WINDOW
Suppression remains until the end of the current continuous off-peak tariff range.
12. Semantic Command API and Off-Peak Switch
12.1 Unified LIFF command API
LIFF sends every driver operation to one HQ endpoint:
POST /api/connector/commands
{
"buildingId": "BLD000000014",
"connectorId": "CP001-001",
"action": "START_CHARGING"
}
HQ derives the authenticated Branch user from the LINE session. LIFF must not send
userId. HQ forwards the same semantic command to Branch through:
POST /api/cp/hq/connector/commands
The command identifiers are:
START_CHARGINGSTOP_CHARGINGCANCEL_STARTCANCEL_MANUAL_STARTENABLE_OFF_PEAKDISABLE_OFF_PEAK
RemoteStart/RemoteStop commands return HTTP 202 Accepted after validation,
operation persistence and successful OCPP dispatch. The response includes
operationId, operationStatus=SENT and the latest driverState; it does not wait
for the OCPP CALLRESULT or StartTransaction/StopTransaction.
Synchronous cancellation and off-peak preference commands return HTTP 200 OK with
the latest actual state. Pre-dispatch authorization/state conflicts use structured
403/409 responses.
The old LIFF-facing /connector/start, /connector/stop, /connector/dequeue and
/connector/offpeak-switch endpoints are removed in the coordinated #1398/#1399
deployment. Low-level engineer/OCPP endpoints remain separate and are not available
to LIFF.
12.2 FE behavior
- PUBLIC: do not render the switch.
- PRIVATE and WebSocket connected: the switch may remain clickable.
- WebSocket disconnected: do not use stale actions or switch state to submit commands.
FE does not maintain a large business-state enable/disable matrix.
The switch maps to ENABLE_OFF_PEAK or DISABLE_OFF_PEAK on the unified command
endpoint. DISABLE_OFF_PEAK exposed as the primary action uses the same command.
12.3 API authority
After a switch request, Branch validates current data and returns:
- Success: actual
enableOffPeak, latestdriverStateand event. - Rejection: structured error code/message, unchanged actual
enableOffPeak, latestdriverState.
The API rejects:
- PUBLIC connector.
- Active
PREPARING,CHARGINGorFINISHING. - Enabling off-peak while a MANUAL queue request exists.
- Unauthorized resident.
- Permanent/structural configuration failure.
12.4 Fail-fast on enable
Reject ON when:
- Connector is not PRIVATE.
- Authenticated user is not a linked resident.
- Connector has no Charge Group.
- Connector has no valid tariff or no OFF_PEAK range.
- Enabling user has no IdTag.
- Enabling user's IdTag is unauthorized.
12.5 Temporary conditions that do not block enable
Allow ON despite temporary conditions:
- Currently outside off-peak hours.
- No current slot.
- No vehicle/no charging demand.
- OCPP offline.
- Connector faulted.
12.6 Configuration changes after enable
- Enabling user loses Connector association: disable off-peak, clear Priority and actor; do not transfer to another resident.
- Connector has no linked residents: disable off-peak and clear Priority.
- IdTag later becomes invalid while the user remains linked: preserve preference and actor, pause automatic start, show a current reason, resume when authorization is restored.
- Tariff later becomes invalid/disabled: preserve preference, pause, and resume after valid configuration returns.
- Connector changes PRIVATE to PUBLIC: disable off-peak and clear related state.
Charge Group deletion protection is tracked separately in Redmine #1395.
PUBLIC slot configuration validation is tracked separately in Redmine #1396.
13. Queue Source and Cancellation
e_connector_priority must distinguish:
source = MANUAL | OFF_PEAK
requestedByUserId
Semantics:
| Queue source | Driver label | Command | Effect |
|---|---|---|---|
MANUAL |
取消排隊 | CANCEL_MANUAL_START |
Delete only the manual request |
OFF_PEAK |
取消排隊 | DISABLE_OFF_PEAK |
Set enableOffPeak=false, delete priorities, clear enabled actor and suppression |
The existing ambiguous dequeue behavior must not be used as the source of truth.
A FINISHED or suppressed Priority row must not project QUEUED merely because the row still exists.
14. Authorization
14.1 Shared presentation
WebSocket/MQ driverState.actions is shared operational capability. It is not personalized for each resident session.
This avoids coupling MQ status generation to a user-specific authorization matrix.
Shared driverState, reason and actions are broadcast to every legal session for the
connector. Transient operation results are targeted:
- Manual command:
targetUserId=requestedByUserId. - Scheduler OFF_PEAK command:
targetUserId=enabledByUserId. - Branch includes internal
operationResult.targetUserIdin MQ. - HQ filters by the authenticated session's Branch user identity and does not repeat
Branch authorization. - HQ does not expose another user's target identity to the browser.
14.2 Command API
The command API re-checks:
- Authenticated user identity.
- Connector area and association.
- Current transaction/request actor.
- Latest operation and driver state.
- Current off-peak and queue state.
- OCPP connectivity and vendor capability.
Expected rejections use structured 403/409 responses.
14.3 Stop rules
- PUBLIC: only the transaction user may stop; another user receives
TRANSACTION_OWNED_BY_OTHER_USER. - PRIVATE: any currently linked resident may stop; preserve original transaction owner but audit
stopRequestedBy. - PRIVATE queue/off-peak/PREPARING cancellation: any currently linked resident may perform it; audit
cancelledByordisabledBy.
15. Persisted Remote Operation
15.1 Existing A17 evidence
e_remote_transaction_log already persists one row per remote command. A17 validation showed:
-
START/SUCCESS: 17,508 rows. -
START/REJECTED: 761 rows. - Current
SUCCESSmeans RemoteStart.confAccepted, not StartTransaction. -
transaction_idremains null even when a later Transaction is created.
Therefore the existing row is suitable to extend, but its current status cannot represent the full business lifecycle.
15.2 Direction
Extend e_remote_transaction_log; do not add a second operation table unless implementation review finds an external compatibility constraint.
Keep the existing command response status and add a separate business lifecycle status.
The API/MQ/WebSocket operationId is the existing e_remote_transaction_log.id.
No additional operation-id column is added. ocpp_unique_id remains internal OCPP
correlation, and transaction_id is filled only when transaction correlation exists.
Recommended new data:
source MANUAL / OFF_PEAK
requested_by_user_id actual authenticated actor
operation_status SENT / ACCEPTED / STARTED / STARTED_LATE /
REJECTED / TIMED_OUT / CANCELLED / FAILED /
STOPPED
deadline_at
completed_at
failure_code
transaction_id populated when StartTransaction is correlated
Required query support:
- Latest unresolved START by connector.
- Latest unresolved STOP by connector/transaction.
- Active operation required by a PREPARING/FINISHING initial snapshot.
- At most one unresolved START and one unresolved STOP for the applicable connector/transaction.
Ordinary indexes should support these queries. Per repository convention, correctness should be enforced in service transactions rather than adding a database UNIQUE constraint without separate approval.
15.3 Lifecycle
RemoteStart:
SENT -> ACCEPTED -> STARTED
| | |
| | +-> active Transaction / CHARGING
| +-> REJECTED / TIMED_OUT / CANCELLED / FAILED
+-> FAILED
RemoteStop:
SENT -> ACCEPTED -> STOPPED
| |
| +-> REJECTED / TIMED_OUT / FAILED
+-> FAILED
Branch restart reconstructs PREPARING and FINISHING from unresolved persisted operations and their deadlines.
Old rows without the new lifecycle fields remain historical. They must not be treated as currently active operations.
16. Operation Result Contract
Current state, active operation and transient terminal operation result are separate.
An accepted asynchronous command response is:
{
"action": "START_CHARGING",
"operationId": "18456",
"operationStatus": "SENT",
"driverState": {
"code": "PREPARING",
"title": "準備中",
"description": "正在等待充電樁開始交易",
"actions": []
}
}
A later targeted WebSocket event may contain:
{
"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
}
}
Rules:
- Immediate pre-dispatch failures are represented by the HTTP
403/409response. - An asynchronous terminal result is pushed once when the transition occurs.
- Manual results target
requestedByUserId; scheduler OFF_PEAK results target
enabledByUserId. - FE deduplicates transient results by
operationIdand shows the modal once. - Initial WebSocket snapshots never replay terminal
operationResult. - Initial snapshots include only an unresolved
activeOperationneeded to correlate
PREPARING/FINISHING withoperationId. - A permanent unresolved condition remains in current
driverState.reasonand
description rather than a replayed modal. - Actual StartTransaction/StopTransaction evidence supersedes an earlier timeout
presentation.
17. WebSocket Detail-Page Contract
17.1 Initial open
- Connector detail does not make a separate REST detail request.
- WebSocket initial data supplies connector information and
driverState. - Initial data includes
activeOperationonly when START/STOP is unresolved. - Initial data does not include a terminal transient
operationResult. - No polling is used.
If initial WebSocket connection fails:
- Perform one short reconnect attempt.
- If it still fails, return to the connector list.
- Show a modal explaining that real-time connector information is unavailable.
- Do not fall back to REST detail data.
17.2 Established connection drops
- Immediately stop using old
driverState.actions. - Disable Start, Stop and off-peak operations.
- Perform one reconnect attempt.
- Restore operations only after a new initial snapshot is received.
- If reconnect fails, return to the list and show the failure modal.
17.3 Targeted result delivery
- Shared connector presentation continues to all legal connector sessions.
- HQ sends
operationResultonly to the authenticated target session. - Non-target residents receive the shared state transition without the operation modal.
- FE deduplicates by
operationId, including reconnect races, and never executes a
command from stale actions while WebSocket is disconnected.
18. Failure and Retry Classification
18.1 Manual
- No automatic retry.
- Explicit result to driver.
- Return to the latest safe current state.
18.2 Off-peak recoverable
- No immediate retry loop.
- At most one attempt per normal scheduler cycle.
- No overlapping operation.
- Retry only while the current off-peak window remains eligible.
-
NO_CHARGING_DEMANDis neutral and may retry in the same window. - Timeout may retry in a later cycle if no stronger suppression applies.
18.3 Off-peak current-window terminal
- Explicit RemoteStart
Rejected. - Driver-confirmed stop after StopTransaction.
- A Transaction that started and subsequently ended.
These project OFF_PEAK_WAITING with a current-window reason and resume only in the next window or after an explicit OFF -> ON re-entry.
18.4 Permanent/configuration pause
- Invalid authorization after enable.
- Invalid/disabled tariff after enable.
- Other structural conditions that cannot succeed by waiting.
Preserve the long-term preference where specified, stop repeated commands and show a current actionable reason.
19. Required FE Mapping
FE implementation is outside this Backend work, but the contract must support:
- Render semantic action labels from API.
- "準備中" for operation-backed PREPARING.
- "停止中" for operation-backed FINISHING.
- "取消排隊" for both
OFF_PEAK_WAITINGand OFF_PEAKQUEUED. - Different command semantics for manual queue cancellation and off-peak disable.
- Confirmation before stopping active off-peak charging.
- PUBLIC off-peak switch hidden.
- PRIVATE switch command sent to API without a duplicated FE business matrix.
- WebSocket disconnected state disables all operations.
- Structured command rejection modal without optimistic state drift.
- Every operation sends the semantic code through
/api/connector/commands. - Async
202responses correlate later targeted results byoperationId. - Terminal operation results are shown once and are not replayed by an initial snapshot.
20. Required Regression Coverage
This behavior changes user-visible charging, API DTO, schema, queueing, off-peak scheduling, OCPP and WebSocket behavior. E2E catalog impact is Add and Update.
At minimum, later implementation planning must cover:
- CloudLink PUBLIC/PRIVATE manual RemoteStart -> PREPARING -> CHARGING.
- Fortune plug-first raw PREPARING remains READY until RemoteStart.
- PREPARING cancellation: CloudLink supported, Fortune unavailable.
- Branch restart recovers PREPARING and FINISHING.
- Manual slots-full queue preserves requester and supports cancellation.
- Off-peak ON immediately returns OFF_PEAK_WAITING before Priority exists.
- Off-peak Priority projects QUEUED and supports DISABLE_OFF_PEAK.
- NO_CHARGING_DEMAND is neutral and retries later in the same window.
- Explicit off-peak RemoteStart Rejected suppresses the current window.
- Driver stop suppresses the current window only after StopTransaction.
- Transaction-ended suppression prevents A17-style repeated RemoteStart commands.
- SuspendedEV remains an active transaction and does not start another operation.
- RemoteStop pending projects FINISHING and blocks duplicate stop.
- Offline/fault preserves long-term off-peak and blocks unsafe remote operations.
- PUBLIC transaction stop authorization and PRIVATE linked-resident stop behavior.
- PRIVATE off-peak switch API success/rejection returns actual state.
- WebSocket initial failure and established disconnect do not use stale actions.
- Late StartTransaction/StopTransaction reconciles to actual transaction state.
- Unified semantic commands distinguish CANCEL_START, CANCEL_MANUAL_START and
DISABLE_OFF_PEAK without ambiguous dequeue behavior. - Async start/stop returns
202with operationId; synchronous commands return200. - Shared state reaches all legal sessions while operationResult reaches only the
requestedBy/enabledBy target and is deduplicated. - RemoteStart uses the unchanged vendor wait, a matching business deadline and
30-second reconciliation grace. - RemoteStop uses the configurable 30-second deadline and permits explicit retry
after timeout while reconciling a late StopTransaction.
Release-pack selection and exact P0-P3 assignments belong to the implementation plan gate after this requirement document is approved.
21. Out of Scope
- FE source implementation in Backend ticket #1398; it is tracked by Redmine #1399.
- Charge Group deletion guard: Redmine #1395.
- PUBLIC slot configuration guard and runtime
slots==0defense: Redmine #1396. - Redesign of the raw OCPP ConnectorStatus enum.
- Claiming that a StopTransaction proves full charge or unplugging.
- New polling behavior.
22. Review Gate
Before implementation:
- Ken reviews this requirement/state contract as one document.
- Any corrections are applied here.
- A separate implementation plan lists exact code/schema/test files and E2E catalog impact.
- Coding starts only after that plan is explicitly approved.