專案

一般

配置概況

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

English requirements revision (2026-07-31); supersedes attachment #1100. - 陳國瑋, 2026-07-31 03:59

 

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 driverState and 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
    operationResult only to the session whose authenticated Branch user identity matches
    the event targetUserId.

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=true means 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_PEAK tariff 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:

  1. Active transaction evidence must never be presented as READY.
  2. An unresolved STOP operation is FINISHING.
  3. An unresolved START operation is PREPARING.
  4. OCPP offline without an active transaction is OFFLINE_UNAVAILABLE.
  5. OCPP offline with active transaction evidence is OFFLINE_CHARGING.
  6. FAULTED/OFFLINE must not be hidden by OFF_PEAK_WAITING or QUEUED.
  7. A real applicable queue record may project QUEUED.
  8. Long-term off-peak without an applicable queue record projects OFF_PEAK_WAITING.
  9. Raw Fortune PREPARING without 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_CHARGING
  • STOP_CHARGING
  • CANCEL_START
  • CANCEL_MANUAL_START
  • ENABLE_OFF_PEAK
  • DISABLE_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=MANUAL
  • requestedByUserId

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:

  • SENT
  • ACCEPTED

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
    RemoteStart Accepted; 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_OUT only 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 QUEUED and 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:

  1. Keep FINISHING until the configurable 30-second stop deadline.
  2. Do not automatically resend.
  3. If StopTransaction arrives, complete normally.
  4. At deadline, re-check the actual transaction.
  5. If still active, mark the operation TIMED_OUT, return to CHARGING, expose the failure and allow a new explicit stop request.
  6. If already completed, reconcile as a successful stop.
  7. 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_WINDOW
  • START_REJECTED_FOR_CURRENT_WINDOW
  • TRANSACTION_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_CHARGING
  • STOP_CHARGING
  • CANCEL_START
  • CANCEL_MANUAL_START
  • ENABLE_OFF_PEAK
  • DISABLE_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, latest driverState and event.
  • Rejection: structured error code/message, unchanged actual enableOffPeak, latest driverState.

The API rejects:

  • PUBLIC connector.
  • Active PREPARING, CHARGING or FINISHING.
  • 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.targetUserId in 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 cancelledBy or disabledBy.

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 SUCCESS means RemoteStart.conf Accepted, not StartTransaction.
  • transaction_id remains 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/409 response.
  • 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 operationId and shows the modal once.
  • Initial WebSocket snapshots never replay terminal operationResult.
  • Initial snapshots include only an unresolved activeOperation needed to correlate
    PREPARING/FINISHING with operationId.
  • A permanent unresolved condition remains in current driverState.reason and
    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 activeOperation only when START/STOP is unresolved.
  • Initial data does not include a terminal transient operationResult.
  • No polling is used.

If initial WebSocket connection fails:

  1. Perform one short reconnect attempt.
  2. If it still fails, return to the connector list.
  3. Show a modal explaining that real-time connector information is unavailable.
  4. 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 operationResult only 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_DEMAND is 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_WAITING and OFF_PEAK QUEUED.
  • 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 202 responses correlate later targeted results by operationId.
  • 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:

  1. CloudLink PUBLIC/PRIVATE manual RemoteStart -> PREPARING -> CHARGING.
  2. Fortune plug-first raw PREPARING remains READY until RemoteStart.
  3. PREPARING cancellation: CloudLink supported, Fortune unavailable.
  4. Branch restart recovers PREPARING and FINISHING.
  5. Manual slots-full queue preserves requester and supports cancellation.
  6. Off-peak ON immediately returns OFF_PEAK_WAITING before Priority exists.
  7. Off-peak Priority projects QUEUED and supports DISABLE_OFF_PEAK.
  8. NO_CHARGING_DEMAND is neutral and retries later in the same window.
  9. Explicit off-peak RemoteStart Rejected suppresses the current window.
  10. Driver stop suppresses the current window only after StopTransaction.
  11. Transaction-ended suppression prevents A17-style repeated RemoteStart commands.
  12. SuspendedEV remains an active transaction and does not start another operation.
  13. RemoteStop pending projects FINISHING and blocks duplicate stop.
  14. Offline/fault preserves long-term off-peak and blocks unsafe remote operations.
  15. PUBLIC transaction stop authorization and PRIVATE linked-resident stop behavior.
  16. PRIVATE off-peak switch API success/rejection returns actual state.
  17. WebSocket initial failure and established disconnect do not use stale actions.
  18. Late StartTransaction/StopTransaction reconciles to actual transaction state.
  19. Unified semantic commands distinguish CANCEL_START, CANCEL_MANUAL_START and
    DISABLE_OFF_PEAK without ambiguous dequeue behavior.
  20. Async start/stop returns 202 with operationId; synchronous commands return 200.
  21. Shared state reaches all legal sessions while operationResult reaches only the
    requestedBy/enabledBy target and is deduplicated.
  22. RemoteStart uses the unchanged vendor wait, a matching business deadline and
    30-second reconciliation grace.
  23. 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==0 defense: 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:

  1. Ken reviews this requirement/state contract as one document.
  2. Any corrections are applied here.
  3. A separate implementation plan lists exact code/schema/test files and E2E catalog impact.
  4. Coding starts only after that plan is explicitly approved.
(3-3/5)