Transaction Flows - CSMS Developer Guide
Based on OCPP 2.1 Edition 2 Specification (Part 2), Section E (Transactions). This guide covers all transaction flows (E01–E17), including start/stop options, offline handling, cable disconnect, connection loss, transaction limits, resume after reboot, and remote transaction control from the CSMS perspective using OCPP-J (JSON over WebSocket).
1. Overview & Core Concepts
IntroductionThe Transactions functional block handles all transaction-related OCPP messaging. From the CSMS perspective, you must handle incoming transaction events, manage transaction lifecycles, track sequence numbers, and support remote start/stop operations.
What is a Transaction?
Single Time Frame
A transaction is the portion of a charging session recorded by the CSMS. It has a start and stop time, used by the operator for billing.
One per EVSE
At most one transaction can be active on an EVSE at any point in time.
CS-Generated IDs
Transaction IDs are generated by the Charging Station (not the CSMS). They are UUIDs (recommended), unique per CS for its entire lifetime.
Cannot Prevent Stop
The CSMS cannot prevent a transaction from stopping — it can only acknowledge the stop event.
Transaction Lifecycle
Every transaction follows this lifecycle communicated via TransactionEventRequest:
Started --> Updated (0..N times) --> Ended
eventType = Started → First message of a transaction
eventType = Updated → Intermediate messages (meter values, state changes, authorization)
eventType = Ended → Final message of a transactionFlexible Start/Stop Points
The Charging Station is configured with TxStartPoint and TxStopPoint which
determine WHEN a transaction starts/stops. The CSMS must understand these to correctly
interpret messages.
TxStartPoint Values
| Value | Transaction Starts When |
|---|---|
ParkingBayOccupancy | Parking bay detector detects EV |
EVConnected | Cable is plugged in / communication established |
Authorized | EV Driver is authorized |
DataSigned | First signed meter values received |
PowerPathClosed | Charger ready to deliver power (authorized + connected) |
EnergyTransfer | Energy flow actually starts |
TxStopPoint Values
| Value | Transaction Stops When |
|---|---|
ParkingBayOccupancy | EV leaves parking bay |
EVConnected | Cable is unplugged / communication lost |
Authorized | Authorization ends (e.g. IdToken presented again, CSMS deauthorizes) |
PowerPathClosed | Power path is opened (EV disconnected OR deauthorized) |
EnergyTransfer | Energy flow stops |
OCPP 1.6 compatibility: Set TxStartPoint = PowerPathClosed and TxStopPoint = EVConnected, Authorized.
Sequence Numbers
The Charging Station maintains a per-EVSE counter
(seqNo) for TransactionEventRequest messages:
seqNoSHOULD be0foreventType = Started- Incremented by 1 after each
TransactionEventRequest - The CSMS uses
seqNoto verify completeness
Received Started with seqNo = a
Received Ended with seqNo = o (where o > a)
Must have received messages with seqNo = n
for every integer n between a and oKey CSMS Responsibilities
- Always respond to
TransactionEventRequestwithTransactionEventResponse— never withhold a response (even for invalid data) - Validate IdTokens in every
TransactionEventRequestthat contains one (the CS may have used outdated local cache) - Track sequence numbers to detect missing messages
- Store transaction data for billing
- Send authorization status back when IdToken is present
2. Message Schemas Reference
ReferenceTransactionEventRequest (CS → CSMS)
This is the primary inbound message the CSMS must handle for all transaction flows.
Required Fields
| Field | Type | Description |
|---|---|---|
eventType | TransactionEventEnumType | Started, Updated, or Ended |
timestamp | dateTime (ISO 8601) | When the event occurred |
triggerReason | TriggerReasonEnumType | Why this message was sent |
seqNo | integer (>= 0) | Sequence number for completeness tracking |
transactionInfo | TransactionType | Transaction details (always contains transactionId) |
Optional Fields
| Field | Type | Description |
|---|---|---|
meterValue | MeterValueType[] | Array of meter readings |
offline | boolean | true if event occurred while CS was offline (default: false) |
numberOfPhasesUsed | integer (0-3) | Number of phases used for charging |
cableMaxCurrent | integer | Max current of connected cable (Amps) |
reservationId | integer (>= 0) | If this transaction ends a reservation |
preconditioningStatus | enum | BMS preconditioning: Unknown/Ready/NotReady/Preconditioning |
evseSleep | boolean | true when EVSE electronics are in sleep mode |
evse | EVSEType | EVSE identifier (sent once, after EV connected) |
idToken | IdTokenType | ID token (sent once, after authorization) |
costDetails | CostDetailsType | Cost calculated locally by CS |
TransactionType (transactionInfo object)
| Field | Required | Description |
|---|---|---|
transactionId | Yes | Unique transaction ID (UUID recommended, max 36 chars) |
chargingState | No | EVConnected / Charging / SuspendedEV / SuspendedEVSE / Idle |
timeSpentCharging | No | Seconds of actual energy transfer |
stoppedReason | No | Why the transaction stopped (in Ended events) |
remoteStartId | No | Matches RequestStartTransactionRequest.remoteStartId |
operationMode | No | Current operation mode |
tariffId | No | ID of tariff in use (max 60 chars) |
transactionLimit | No | Active limits on the transaction |
TriggerReasonEnumType Values
| Value | Description |
|---|---|
Authorized | EV Driver authorized |
CablePluggedIn | Cable was plugged in |
ChargingRateChanged | Charging rate changed |
ChargingStateChanged | Charging state changed |
CostLimitReached | Cost limit reached |
Deauthorized | EV Driver deauthorized |
EnergyLimitReached | Energy limit reached |
EVCommunicationLost | Communication with EV lost |
EVConnectTimeout | EV did not connect within timeout |
EVDeparted | EV departed from parking bay |
EVDetected | EV detected in parking bay |
LimitSet | A limit was set on the transaction |
MeterValueClock | Clock-aligned meter value |
MeterValuePeriodic | Periodic meter value |
OperationModeChanged | Operation mode changed |
RemoteStart | Remote start triggered |
RemoteStop | Remote stop triggered |
ResetCommand | Reset command received |
RunningCost | Running cost update |
SignedDataReceived | Signed meter data received |
SoCLimitReached | State of Charge limit reached |
StopAuthorized | Driver authorized stop |
TariffChanged | Tariff changed |
TariffNotAccepted | Tariff not accepted by EV |
TimeLimitReached | Time limit reached |
Trigger | Triggered by TriggerMessage |
TxResumed | Transaction resumed after interruption |
UnlockCommand | Unlock command received |
AbnormalCondition | Abnormal error or fault |
ReasonEnumType (stoppedReason values)
| Value | Description |
|---|---|
DeAuthorized | CSMS revoked authorization |
EmergencyStop | Emergency stop activated |
EnergyLimitReached | Energy limit reached |
EVDisconnected | EV disconnected |
GroundFault | Ground fault detected |
ImmediateReset | Transaction ended by reset |
Local | Stopped locally by EV Driver (MAY be omitted) |
MasterPass | Master pass used to stop |
Other | Other reason |
OvercurrentFault | Overcurrent fault |
PowerLoss | Power loss |
PowerQuality | Power quality issue |
Reboot | Reboot |
Remote | Stopped remotely via RequestStopTransaction |
SOCLimitReached | SoC limit reached |
StoppedByEV | EV stopped charging (ISO 15118) |
TimeLimitReached | Time limit reached |
Timeout | Timeout |
ReqEnergyTransferRejected | Requested energy transfer type not granted |
LocalOutOfCredit | Local out of credit |
ChargingStateEnumType
| Value | Description |
|---|---|
EVConnected | EV connected, not charging |
Charging | Active energy transfer |
SuspendedEV | Charging suspended by EV |
SuspendedEVSE | Charging suspended by EVSE |
Idle | No EV connected |
TransactionEventResponse (CSMS → CS)
All fields are optional — an empty {} is a valid response.
{
"totalCost": "number", // Running cost (Updated) or final cost (Ended)
// 0.00 = free transaction. Absence != free.
"chargingPriority": "integer", // -9 to 9, default 0. Higher = higher priority.
"idTokenInfo": { // MUST include when IdToken was in the request
"status": "AuthorizationStatusEnumType", // Accepted/Blocked/ConcurrentTx/Expired/...
"cacheExpiryDateTime": "dateTime",
"chargingPriority": "integer",
"groupIdToken": { "idToken": "string", "type": "string" },
"language1": "string (max 8)",
"language2": "string (max 8)",
"evseId": [1, 2],
"personalMessage": { "format": "string", "content": "string" }
},
"transactionLimit": { // Set/update limits on the transaction
"maxCost": "number",
"maxEnergy": "number (Wh)",
"maxTime": "integer (seconds)",
"maxSoC": "integer (0-100)"
},
"updatedPersonalMessage": { ... }, // Message to display to user
"updatedPersonalMessageExtra": [ ... ] // Additional messages (max 4)
}Note: The TransactionEventResponse has NO required fields. An empty JSON {} is a valid response.
RequestStartTransaction (CSMS → CS)
Request
| Field | Required | Description |
|---|---|---|
remoteStartId | Yes | ID for matching. CS returns this in transactionInfo.remoteStartId |
idToken | Yes | Token to use for the transaction |
evseId | No | Specific EVSE to start on (integer >= 1) |
groupIdToken | No | Group token |
chargingProfile | No | Charging profile for the transaction |
Response
| Field | Required | Description |
|---|---|---|
status | Yes | Accepted or Rejected |
statusInfo | No | Additional status information |
transactionId | No | If transaction already started (e.g. cable plugged in first) |
RequestStopTransaction (CSMS → CS)
| Field | Required | Description |
|---|---|---|
transactionId | Yes | Transaction ID to stop (max 36 chars) |
Response contains status (Accepted or Rejected) and optional statusInfo.
GetTransactionStatus (CSMS → CS)
Request
| Field | Required | Description |
|---|---|---|
transactionId | No | Specific transaction to query. If omitted, asks about any queued messages. |
Response
| Field | Required | Description |
|---|---|---|
messagesInQueue | Yes | Whether there are still messages to be delivered |
ongoingIndicator | No | Whether the transaction is still ongoing (only when transactionId was provided) |
3. E01 — Start Transaction Options
Inbound| Use Case ID | E01 |
| Direction | CS → CSMS (inbound) |
| Message | TransactionEventRequest with eventType = Started |
| Purpose | Covers all different moments a CS can start a transaction based on TxStartPoint |
{
"eventType": "Started",
"timestamp": "2025-01-15T10:30:00Z",
"triggerReason": "<varies by scenario>",
"seqNo": 0,
"transactionInfo": {
"transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"chargingState": "<varies>"
},
"evse": { "id": 1, "connectorId": 1 },
"idToken": { "idToken": "AABB1234", "type": "ISO14443" },
"meterValue": [...]
}Scenarios and Expected triggerReasons
| Scenario | TxStartPoint | triggerReason | chargingState |
|---|---|---|---|
| S1: Parking bay detector | ParkingBayOccupancy | EVDetected | (none) |
| S2: Cable plugged in | EVConnected | CablePluggedIn | EVConnected |
| S3: Driver authorized | Authorized | Authorized | (varies) |
| S4: Signed data received | DataSigned | SignedDataReceived | (varies) |
| S5: Power path closed | PowerPathClosed | Authorized / CablePluggedIn | EVConnected |
| S6: Energy flow starts | EnergyTransfer | ChargingStateChanged | Charging |
{
"idTokenInfo": {
"status": "Accepted",
"groupIdToken": { "idToken": "GROUP01", "type": "Central" }
}
}CSMS Handler Logic
function handleTransactionEventStarted(request):
// 1. Store the new transaction
transaction = createTransaction(
transactionId: request.transactionInfo.transactionId,
chargingStationId: connection.chargingStationId,
startTime: request.timestamp,
seqNo: request.seqNo
)
// 2. Store EVSE info if provided (E01.FR.16)
if request.evse:
transaction.evseId = request.evse.id
transaction.connectorId = request.evse.connectorId
// 3. If idToken is present, MUST validate it (E01.FR.11)
response = {}
if request.idToken:
authResult = validateIdToken(request.idToken)
response.idTokenInfo = {
status: authResult.status,
groupIdToken: authResult.groupIdToken // if exists (E01.FR.12)
}
// 4. Store reservation link if present (E01.FR.13)
if request.reservationId:
transaction.reservationId = request.reservationId
endReservation(request.reservationId)
// 5. If remoteStartId present, link to original request (C05.FR.03)
if request.transactionInfo.remoteStartId:
linkRemoteStart(transaction, request.transactionInfo.remoteStartId)
// 6. Store meter values if present
if request.meterValue:
storeMeterValues(transaction, request.meterValue)
// 7. Track sequence number for completeness
transaction.lastSeqNo = request.seqNo
// 8. ALWAYS return a response
return responseImportant Rules for CSMS
- E01.FR.11: CSMS SHALL verify the validity of the identifier in
TransactionEventRequest - E01.FR.12: CSMS SHALL send
idTokenInfowith authorization status ANDgroupIdTokenif one exists - E01.FR.14: Only one transaction per EVSE+Connector at a time
- The
evsefield is only provided ONCE (in the firstTransactionEventRequestafter EV connects) — cache it - The
idTokenfield is only provided ONCE (in the first request after authorization) — cache it - Never withhold a response — this only causes retries
4. E02 — Start Transaction: Cable Plugin First
InboundSequence: Cable plugged in first, then authorization happens. The transaction starts before the driver is identified.
Message Sequence (CSMS perspective)
1. [Receive] NotifyEventRequest
component.name = "Connector", variable.name = "AvailabilityState",
actualValue = "Occupied"
[Send] NotifyEventResponse {}
2. [Receive] TransactionEventRequest
eventType = Started,
triggerReason = CablePluggedIn,
chargingState = EVConnected,
transactionId = "AB1234",
seqNo = N,
evse = { id: 1, connectorId: 1 },
meterValue = [...]
[Send] TransactionEventResponse { ... }
// At this point, driver has NOT been identified yet.
// No idToken in this message.
3. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = Authorized,
transactionId = "AB1234",
seqNo = N + 1,
idToken = { idToken: "1234", type: "ISO14443" },
meterValue = [...]
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" }
}
4. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = ChargingStateChanged,
transactionId = "AB1234",
seqNo = N + 2,
chargingState = Charging,
meterValue = [...]
[Send] TransactionEventResponse { ... }CSMS Handler Logic
function handleTransactionEvent_E02(request):
tx = getTransaction(request.transactionInfo.transactionId)
switch request.eventType:
case "Started":
// Transaction started without driver being known
// E02.FR.06: When TxStartPoint contains EVConnected
tx = createTransaction(...)
// No idToken yet - cannot validate
return {}
case "Updated":
if request.triggerReason == "Authorized" and request.idToken:
// E02.FR.01: Driver identified - MUST validate
// E02.FR.04: CSMS SHALL verify validity (might have been
// authorized locally with outdated info)
authResult = validateIdToken(request.idToken)
tx.idToken = request.idToken
return {
idTokenInfo: {
status: authResult.status
}
}
if request.triggerReason == "ChargingStateChanged":
tx.chargingState = request.transactionInfo.chargingState
storeMeterValues(tx, request.meterValue)
return {}
case "Ended":
finalizeTransaction(tx, request)
return {}Key CSMS Requirements (E02)
- E02.FR.02: When CS sends
TransactionEventRequestwith idToken (triggerReason=Authorized), CSMS SHALL respond withidTokenInfoincluding authorization status - E02.FR.04: CSMS SHALL verify the identifier validity (even if CS authorized locally)
- E02.FR.09: Meter values in
Startedevent havecontext = Transaction.Begin - E02.FR.10: Meter values in
Updatedevents are periodic readings - E02.FR.11: CS may split large meter data across multiple
Updatedmessages with same timestamp
5. E03 — Start Transaction: IdToken First
InboundSequence: Driver presents IdToken first, then plugs in cable. The transaction starts with authorization before cable connection.
Message Sequence (CSMS perspective)
1. [Receive] TransactionEventRequest
eventType = Started,
triggerReason = Authorized,
transactionId = "AB1234",
seqNo = N,
idToken = { idToken: "1234", type: "ISO14443" },
meterValue = [...]
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" }
}
// Driver now plugs in cable...
2. [Receive] NotifyEventRequest
component.name = "Connector", variable.name = "AvailabilityState",
actualValue = "Occupied"
[Send] NotifyEventResponse {}
3. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = CablePluggedIn,
transactionId = "AB1234",
seqNo = N + 1,
chargingState = EVConnected,
evse = { id: 1, connectorId: 1 }
[Send] TransactionEventResponse { ... }
// Energy starts flowing...
4. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = ChargingStateChanged,
transactionId = "AB1234",
seqNo = N + 2,
chargingState = Charging,
meterValue = [...]
[Send] TransactionEventResponse { ... }EVConnectTimeout Handling
If the driver does NOT plug in the cable before EVConnectionTimeOut:
[Receive] TransactionEventRequest
eventType = Ended,
triggerReason = EVConnectTimeout,
transactionId = "AB1234",
seqNo = N + 1,
stoppedReason = Timeout
[Send] TransactionEventResponse {}CSMS Should:
- Mark the transaction as ended
- Not bill the driver (no energy was transferred)
- Ensure sensible
TxStartPoint/TxStopPointcombinations
Key CSMS Requirements (E03)
- E03.FR.02: CSMS SHALL respond with
idTokenInfocontaining authorization status - E03.FR.03: If this transaction ends a reservation, next
TransactionEventRequestSHALL containreservationId - E03.FR.05: CS should end transaction on EVConnectTimeout when
TxStopPointdoes not containParkingBayOccupancy
6. E04 — Offline Start Transaction
Inbound (Delayed)The Charging Station started a transaction while offline. When connection is restored,
queued messages arrive with offline = true.
Message Sequence (CSMS perspective)
// Connection restored. CS sends queued messages:
1. [Receive] TransactionEventRequest
eventType = Started,
triggerReason = ...,
transactionId = "AB1234",
seqNo = N,
offline = true, // <-- KEY INDICATOR
meterValue = [...],
timestamp = "2025-01-15T08:00:00Z" // actual event time
[Send] TransactionEventResponse { ... }
2. [Receive] TransactionEventRequest
eventType = Updated,
offline = true,
seqNo = N + 1,
...
[Send] TransactionEventResponse { ... }
// ... more queued messages ...CSMS Handler Logic
function handleOfflineTransactionEvent(request):
// E04.FR.03: offline flag SHALL be true for events that occurred offline
if request.offline:
// Use request.timestamp as the actual event time
// (NOT the time the message was received)
tx = getOrCreateTransaction(request.transactionInfo.transactionId)
tx.offlineOccurred = true
// Process normally but note the offline flag
// IdToken validation still applies
if request.idToken:
authResult = validateIdToken(request.idToken)
return { idTokenInfo: authResult }
return {}Key CSMS Requirements (E04)
- E04.FR.03: The
offlineflag SHALL betruefor any event that occurred offline - Use
request.timestampas the actual event time, not the receive time - The CSMS should still validate IdTokens and respond normally
- Multiple offline transactions may arrive in a batch
7. E05 — Start Transaction: Id Not Accepted
InboundThe Charging Station locally authorized a driver, but when the CSMS validates the IdToken, it is NOT accepted. This is a critical flow for security.
Message Sequence (CSMS perspective)
1. [Receive] TransactionEventRequest
eventType = Started,
transactionId = "AB1234",
seqNo = N,
evse = { id: 1, connectorId: 1 },
idToken = { idToken: "AABB1234", type: "ISO14443" },
meterValue = [...]
// CSMS checks: this IdToken is Blocked/Invalid/Expired!
[Send] TransactionEventResponse {
idTokenInfo: {
status: "Blocked" // or "Invalid", "Expired", "Unknown"
}
}
// What happens next depends on CS configuration:
// If StopTxOnInvalidId = false:
2. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = ChargingStateChanged,
chargingState = SuspendedEVSE,
seqNo = N + 1
[Send] TransactionEventResponse { ... }
// If StopTxOnInvalidId = true AND TxStopPoint = "EVConnected":
2. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = Deauthorized,
chargingState = EVConnected,
seqNo = N + 1
[Send] TransactionEventResponse { ... }
// If StopTxOnInvalidId = true AND TxStopPoint = "Authorized":
2. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = Deauthorized,
chargingState = EVConnected,
stoppedReason = DeAuthorized,
seqNo = N + 1
[Send] TransactionEventResponse { ... }CSMS Handler Logic
function handleTransactionEventWithIdValidation(request):
if request.idToken:
// E05.FR.01: CSMS MUST verify validity
authResult = validateIdToken(request.idToken)
if authResult.status != "Accepted":
// Return non-accepted status
// CS will handle suspension/termination based on its config
return {
idTokenInfo: {
status: authResult.status // "Blocked"/"Invalid"/"Expired"/etc
}
}
return {
idTokenInfo: { status: "Accepted" }
}
return {}Key CSMS Requirements (E05)
- E05.FR.01: CSMS MUST verify identifier validity (it might have been authorized locally with outdated info)
- E05.FR.02: When
StopTxOnInvalidId = falseandMaxEnergyOnInvalidIdis not exceeded: energy delivery is suspended but transaction continues - E05.FR.03: When
StopTxOnInvalidId = falseandMaxEnergyOnInvalidIdis set: energy delivery allowed until that limit - E05.FR.09 (2.1): When
StopTxOnInvalidId = true, CS stops energy transfer and sendsDeauthorizedtrigger - E05.FR.10: When
StopTxOnInvalidId = trueand TxStopPoint contains (Authorized OR PowerPathClosed OR EnergyTransfer), CS sendseventType = Ended
8. E06 — Stop Transaction Options
InboundCovers all the different moments a Charging Station can stop a transaction based on TxStopPoint configuration.
Scenarios and Expected Messages
| Scenario | TxStopPoint | triggerReason | stoppedReason |
|---|---|---|---|
| S1: Parking bay vacated | ParkingBayOccupancy | EVDeparted | Local |
| S2: Cable unplugged | EVConnected | EVCommunicationLost | EVDisconnected |
| S3: Driver deauthorized | Authorized | Deauthorized / StopAuthorized | DeAuthorized / Local |
| S5: EV/EVSE disconnected | PowerPathClosed | varies | varies |
| S6: Energy stopped | EnergyTransfer | ChargingStateChanged | varies |
| S7: Driver presents token | Authorized / PowerPathClosed | StopAuthorized | Local |
{
"eventType": "Ended",
"timestamp": "2025-01-15T12:45:00Z",
"triggerReason": "StopAuthorized",
"seqNo": 5,
"transactionInfo": {
"transactionId": "AB1234",
"chargingState": "EVConnected",
"timeSpentCharging": 7200,
"stoppedReason": "Local"
},
"idToken": { "idToken": "1234", "type": "ISO14443" },
"meterValue": [
{
"timestamp": "2025-01-15T12:45:00Z",
"sampledValue": [
{
"value": 15000,
"context": "Transaction.End",
"measurand": "Energy.Active.Import.Register",
"unitOfMeasure": { "unit": "Wh" }
}
]
}
]
}CSMS Handler Logic
function handleTransactionEventEnded(request):
tx = getTransaction(request.transactionInfo.transactionId)
// 1. Store final meter values
if request.meterValue:
storeMeterValues(tx, request.meterValue)
// 2. Store stopped reason
// E06.FR.08: stoppedReason is included if not ended by EV Driver
// E06.FR.09: stoppedReason MAY be omitted when driver ended it locally
// (CSMS can interpret absence as "Local")
tx.stoppedReason = request.transactionInfo.stoppedReason ?? "Local"
tx.endTime = request.timestamp
tx.timeSpentCharging = request.transactionInfo.timeSpentCharging
// 3. Validate IdToken if present (driver presenting token to stop)
response = {}
if request.idToken:
authResult = validateIdToken(request.idToken)
response.idTokenInfo = { status: authResult.status }
// 4. Check sequence completeness
if tx.lastSeqNo + 1 != request.seqNo:
// Missing messages! May need to use GetTransactionStatus
flagIncompleteTransaction(tx)
// 5. Calculate final cost if applicable
if shouldCalculateCost(tx):
response.totalCost = calculateFinalCost(tx)
// 6. Mark transaction as completed
tx.status = "Completed"
return responseKey CSMS Requirements (E06)
- E06.FR.08: If transaction is NOT ended by EV Driver,
stoppedReasonSHALL be present - E06.FR.09: If ended by EV Driver (local),
stoppedReasonMAY be omitted (interpret asLocal) - E06.FR.11: Meter values in
Endedevent havecontext = Transaction.End - E06.FR.16: Abnormal errors/faults have
triggerReason = AbnormalCondition - The CSMS cannot prevent a transaction from stopping
9. E07 — Transaction Locally Stopped by IdToken
InboundThe EV Driver presents an IdToken to stop a transaction. This may be the same token or a different token with the same GroupId.
Direct Stop (TxStopPoint = Authorized/PowerPathClosed)
1. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = StopAuthorized,
transactionId = "AB1234",
seqNo = N + 1,
stoppedReason = Local,
idToken = { idToken: "1234", type: "ISO14443" },
meterValue = [...]
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" }
}Update then End (TxStopPoint = EVConnected/ParkingBayOccupancy/EnergyTransfer)
1. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = StopAuthorized,
transactionId = "AB1234",
seqNo = N + 1,
idToken = { idToken: "1234", type: "ISO14443" }
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" }
}
// Energy stops, cable unlocked, driver unplugs...
2. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = ChargingStateChanged, // or EVCommunicationLost, EVDeparted
transactionId = "AB1234",
seqNo = N + 2,
chargingState = EVConnected,
stoppedReason = Local,
meterValue = [...]
[Send] TransactionEventResponse { ... }Key CSMS Requirements (E07)
- E07.FR.01: The same idToken OR an idToken with matching GroupId can stop the transaction
- E07.FR.02: CS sends
triggerReason = StopAuthorizedwith the stopping IdToken - E07.FR.04/05:
stoppedReasonisLocalwhen stopped by EV driver at CS - E07.FR.06: If stopped NOT on driver request, CS uses appropriate
ReasonEnumTypevalue - The stopping idToken may differ from the starting idToken (they share GroupId)
10. E08 — Transaction Stopped While Offline
Inbound (Delayed)A transaction stopped while the Charging Station was offline. When connection is restored,
the CSMS receives the ended event with offline = true.
Message Sequence
// Connection restored:
1. [Receive] TransactionEventRequest
eventType = Ended,
transactionId = "AB1234",
offline = true,
timestamp = "2025-01-15T08:30:00Z", // actual stop time
meterValue = [...]
[Send] TransactionEventResponse { ... }Key CSMS Requirements (E08)
- E08.FR.05: Messages from offline period have
offline = true - E08.FR.07: All messages from offline period have
offline = true - Process the same as a normal stop but note the offline flag
- Use
timestampfrom the message (actual time), not receive time
11. E09 — Cable Disconnected on EV-side: Stop Transaction
InboundPrerequisite: StopTxOnEVSideDisconnect = true.
When the cable is disconnected at the EV side, the transaction is stopped immediately.
Message Sequence
1. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = EVCommunicationLost,
transactionId = "AB1234",
stoppedReason = EVDisconnected,
meterValue = [...]
[Send] TransactionEventResponse { ... }
2. [Receive] NotifyEventRequest
component.name = "Connector",
variable.name = "AvailabilityState",
actualValue = "Available"
[Send] NotifyEventResponse {}CSMS Handler Notes
- Treat as a normal transaction end
stoppedReason = EVDisconnectedindicates cable was unplugged at EV side- Transaction is fully ended — no resumption
12. E10 — Cable Disconnected on EV-side: Suspend Transaction
InboundPrerequisite: StopTxOnEVSideDisconnect = false.
Unlike E09, the transaction is suspended (not stopped) when cable is unplugged
at EV side. The transaction can resume when cable is plugged back in.
Message Sequence
// Cable unplugged at EV side:
1. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = EVCommunicationLost,
transactionId = "AB1234",
chargingState = SuspendedEV,
meterValue = [...]
[Send] TransactionEventResponse { ... }
// Option A: Cable plugged back in
2a. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = CablePluggedIn,
transactionId = "AB1234",
chargingState = Charging,
meterValue = [...]
[Send] TransactionEventResponse { ... }
// Option B: Driver authorizes to end (cable not permanently attached)
2b. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = StopAuthorized,
transactionId = "AB1234",
meterValue = [...]
[Send] TransactionEventResponse { ... }
// Option C: Timeout (cable permanently attached)
2c. [Receive] TransactionEventRequest
eventType = Ended,
stoppedReason = Timeout,
transactionId = "AB1234"
[Send] TransactionEventResponse { ... }CSMS Handler Notes
- Track the transaction state:
SuspendedEVmeans cable disconnected at EV side - Transaction remains active until explicitly ended
- No billing action until final
Endedevent
13. E11 — Connection Loss During Transaction
Inbound (Delayed)The Charging Station loses connection during a transaction. The transaction continues on the CS. When connection is restored, queued messages arrive.
Message Sequence
// Connection restored:
1. [Receive] TransactionEventRequest
eventType = Updated,
offline = true,
seqNo = ...,
meterValue = [...]
[Send] TransactionEventResponse { ... }
2. [Receive] TransactionEventRequest
eventType = Updated,
offline = true,
seqNo = ...,
meterValue = [...]
[Send] TransactionEventResponse { ... }
// ... more queued messages ...
// Transaction may still be ongoing after queue is drainedKey CSMS Requirements (E11)
- E11.FR.01: All queued messages have
offline = true - E11.FR.07: All queued messages SHALL be set to
Offline - Process all messages in order using their
timestampvalues - Check
seqNofor completeness — CS may have dropped intermediate messages when running low on memory - E11.FR.05: CS drops intermediate messages first (2nd, 4th, 6th), never the first or last
14. E12 — Inform CSMS of Offline Occurred Transaction
Inbound (Delayed)An entire transaction (start to end) occurred while the CS was offline. The CSMS receives all messages in a batch when connection is restored.
Message Sequence
// Connection restored:
1. [Receive] HeartbeatRequest
[Send] HeartbeatResponse { currentTime: "..." }
2. [Receive] TransactionEventRequest
eventType = Started,
offline = true,
transactionId = "X",
timestamp = "2025-01-15T03:00:00Z"
[Send] TransactionEventResponse { ... }
3. [Receive] TransactionEventRequest
eventType = Updated,
offline = true,
transactionId = "X"
[Send] TransactionEventResponse { ... }
4. [Receive] TransactionEventRequest
eventType = Ended,
offline = true,
transactionId = "X",
timestamp = "2025-01-15T04:30:00Z"
[Send] TransactionEventResponse { ... }CSMS Handler Logic
function handleOfflineBatchTransaction(request):
// Process all messages with offline = true
// Build the complete transaction picture from the batch
// E12.FR.03: Meter values should be present
// E12.FR.04 (2.1): CS may drop non-critical Updated messages when low on memory
// but NOT messages with triggerReason: LimitSet, CostLimitReached,
// EnergyLimitReached, TimeLimitReached, SoCLimitReached
// E12.FR.05: Intermediate messages are dropped first (2nd, 4th, 6th)
// never the first or last
// Process normally but note all messages are historical
return processTransactionEvent(request)Key CSMS Requirements (E12)
- E12.FR.03: Meter values should be present in the batch
- E12.FR.04: CS may drop non-critical
Updatedmessages when low on memory, but NOT messages with triggerReason:LimitSet,CostLimitReached,EnergyLimitReached,TimeLimitReached,SoCLimitReached - E12.FR.05: Intermediate messages are dropped first (2nd, 4th, 6th) — never the first or last
- All messages are historical — process normally but note they are from the past
15. E13 — Transaction-related Message Not Accepted by CSMS
AwarenessDescribes how the Charging Station retries when the CSMS does not accept/respond to a message. The CSMS should be aware of this behavior.
Retry Behavior (CS-side, for CSMS awareness)
1. CS sends TransactionEventRequest
2. If CSMS does not respond within MessageTimeout, CS retries
3. Wait time between retries:
MessageAttemptIntervalTransactionEvent * attempt_number
4. Max retries: MessageAttemptsTransactionEvent
5. After final failure, CS discards the message
Example: Attempts = 3, Interval = 60
1st failure: wait 60s, resend
2nd failure: wait 120s, resend
3rd failure: discardCSMS Responsibilities
- Always respond to
TransactionEventRequest— failing to respond causes unnecessary retries - If the CSMS needs time to process, respond immediately and process asynchronously
- Sanity check failures should never cause the CSMS to withhold a response
16. E14 — Check Transaction Status
OutboundThe CSMS can query whether a transaction is ongoing and whether there are queued messages. Use this after receiving an Ended event with sequence gaps, or to verify transaction state.
Use Cases
After Ended event
Check if there are missing intermediate messages
Verify active status
Check if transaction is still active
Check queued messages
Send without transactionId to check for any queued messages
Request/Response Examples
// CSMS sends:
{
"transactionId": "AB1234"
}
// CS responds:
{
"ongoingIndicator": true, // transaction still active
"messagesInQueue": true // more messages to deliver
}// CSMS sends:
{}
// CS responds:
{
"messagesInQueue": false // no queued messages
// ongoingIndicator is NOT present when no transactionId specified
}Decision Matrix
| ongoingIndicator | messagesInQueue | Meaning |
|---|---|---|
true | true | Transaction ongoing, messages pending |
true | false | Transaction ongoing, all messages delivered |
false | true | Transaction ended, messages still pending |
false | false | Transaction ended, all messages delivered (or CS doesn't know this transaction) |
CSMS Implementation
function checkTransactionStatus(transactionId = null):
request = {}
if transactionId:
request.transactionId = transactionId
response = sendToChargingStation(
"GetTransactionStatusRequest",
request
)
if response.messagesInQueue:
// Wait for messages to be delivered before billing
scheduleRetryCheck(transactionId)
else if not response.ongoingIndicator:
// Safe to start billing
startBillingProcess(transactionId)17. E15 — End of Charging Process
InboundSpecific to ISO 15118 sessions. The EV sends a SessionStopReq(Terminate) message which causes the CS to end the transaction.
Message Sequence
// ISO 15118 session stop occurs at EV/CS level
1. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = StopAuthorized,
stoppedReason = StoppedByEV,
meterValue = [...]
[Send] TransactionEventResponse { ... }Key CSMS Requirements (E15)
- E15.FR.04: When TxStopPoint contains "Authorized"/"PowerPathClosed"/"EnergyTransfer", CS sends
eventType = EndedwithtriggerReason = StopAuthorizedandstoppedReason = StoppedByEV - E15.FR.05: When TxStopPoint does NOT contain those values, CS sends
eventType = UpdatedwithtriggerReason = StopAuthorized(transaction continues until TxStopPoint condition is met)
18. E16 — Transactions with Limits
Bidirectional New in 2.1Either the EV Driver or CSMS can set limits on a transaction in terms of cost, energy, SoC, or time. This is critical for prepaid cards, direct payments, and energy management.
Scenario 1: EV Driver Sets Energy Limit
1. [Receive] TransactionEventRequest
eventType = Started,
idToken = { idToken: "AA12345", type: "ISO14443" }
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" }
}
2. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = LimitSet,
transactionInfo: {
transactionId: "...",
transactionLimit: { maxEnergy: 20000 } // 20 kWh in Wh
}
[Send] TransactionEventResponse {}
// Charging continues until limit reached...
3. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = EnergyLimitReached,
transactionInfo: {
chargingState: "SuspendedEVSE"
}
[Send] TransactionEventResponse {}Scenario 2: CSMS Sets Cost Limit
1. [Receive] TransactionEventRequest
eventType = Started,
idToken = { idToken: "AA12345", type: "ISO14443" }
[Send] TransactionEventResponse {
idTokenInfo: { status: "Accepted" },
transactionLimit: { maxCost: 12.34 } // <-- CSMS sets limit
}
2. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = LimitSet,
transactionInfo: {
transactionLimit: { maxCost: 12.34 } // CS confirms the limit
}
[Send] TransactionEventResponse {}
// Charging continues until cost limit reached...
3. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = CostLimitReached,
transactionInfo: {
chargingState: "SuspendedEVSE"
}
[Send] TransactionEventResponse {}CSMS Handler Logic for Limits
function handleTransactionLimits(request, response):
tx = getTransaction(request.transactionInfo.transactionId)
// E16.FR.02: CSMS sets limit via TransactionEventResponse
if csmsWantsToSetLimit(tx):
response.transactionLimit = {
maxCost: getMaxCostForTransaction(tx)
}
// E16.FR.03: CS confirms limit by echoing it back
if request.transactionInfo.transactionLimit:
tx.confirmedLimits = request.transactionInfo.transactionLimit
// E16.FR.04: CS SHALL NOT exceed CSMS-set limits
// E16.FR.05/06: Handle limit reached
if request.triggerReason in ["CostLimitReached", "EnergyLimitReached",
"TimeLimitReached", "SoCLimitReached"]:
// TxStopPoint NOT "EnergyTransfer": chargingState = SuspendedEVSE
// TxStopPoint "EnergyTransfer": eventType = Ended
tx.limitReached = request.triggerReason
// E16.FR.07: CSMS can update limits
if csmsWantsToUpdateLimit(tx):
response.transactionLimit = { maxCost: newLimit }
// E16.FR.11: When maxCost is active, CSMS SHALL provide cost updates
if tx.confirmedLimits?.maxCost and not localCostCalculation:
response.totalCost = calculateRunningCost(tx)
// E16.FR.14: If limit increased after SuspendedEVSE, CS resumes charging
// E16.FR.17: To remove a limit, set it to a very high value
// (limits cannot be unset, only changed)
// E16.FR.18: Don't expect limit confirmation for offline messagesSupported Limits Check
Before setting limits, check what the CS supports via TxCtrlr.SupportedLimits:
maxEnergy— energy limitmaxCost— cost limitmaxTime— time limitmaxSoC— state of charge limit
E16.FR.12: CSMS SHALL NOT send a transactionLimit that is not
reported in TxCtrlr.SupportedLimits.
Key CSMS Requirements (E16)
- E16.FR.02: CSMS sets limits via
TransactionEventResponse.transactionLimit - E16.FR.03: CS confirms by echoing the limit back
- E16.FR.07: CSMS can update limits in any response
- E16.FR.08: Don't update if CS limit is already below CSMS requirement
- E16.FR.11: When
maxCostis active, CSMS SHALL provide cost updates - E16.FR.12: Don't send unsupported limit types
- E16.FR.14: If limit increased after SuspendedEVSE, CS resumes charging
- E16.FR.17: To remove a limit, set it to a very high value (limits cannot be unset)
19. E17 — Resuming Transaction After Forced Reboot
Inbound New in 2.1When a Charging Station reboots unexpectedly (power loss, software fault), it can resume
transactions if the interruption is within TxResumptionTimeout.
Scenario 1: Resume Within Timeout
// Power loss at t=0, restored at t=10
1. [Receive] TransactionEventRequest
eventType = Updated,
triggerReason = TxResumed,
transactionId = "AB1234",
transactionInfo: {
chargingState: <state before interruption>
}
[Send] TransactionEventResponse { ... }
// E17.FR.15: If CSMS has a TxProfile for this transaction,
// CSMS SHALL resend it via SetChargingProfileRequest
if hasChargingProfile(tx):
sendSetChargingProfileRequest(tx.chargingProfile)Scenario 2: End After Timeout Exceeded
// Power loss occurred more than TxResumptionTimeout ago
1. [Receive] TransactionEventRequest
eventType = Ended,
triggerReason = AbnormalCondition,
transactionId = "AB1234",
stoppedReason = PowerLoss // or Reboot
[Send] TransactionEventResponse { ... }CSMS Handler Logic
function handleTransactionResumed(request):
tx = getTransaction(request.transactionInfo.transactionId)
if request.triggerReason == "TxResumed":
// Transaction is resuming
tx.wasInterrupted = true
// E17.FR.15: Resend charging profile if applicable
if hasTxProfile(tx) and not chargingProfilePersisted():
sendSetChargingProfileRequest(tx.evseId, tx.chargingProfile)
return {}
if request.triggerReason == "AbnormalCondition":
// Transaction ended due to timeout or failure
tx.stoppedReason = request.transactionInfo.stoppedReason
tx.endTime = request.timestamp
finalizeTransaction(tx)
return {}Key CSMS Requirements (E17)
- E17.FR.14: CS sends
eventType = Updated,triggerReason = TxResumedwithchargingState - E17.FR.15: CSMS SHALL resend
SetChargingProfileRequestfor TxProfile charging profiles whenChargingProfilePersistencefor TxProfile is false or absent - E17.FR.21: If timeout exceeded due to power loss:
stoppedReason = PowerLoss - E17.FR.22: If timeout exceeded due to software fault:
stoppedReason = Reboot
20. Remote Transaction Control
OutboundRemote Start Transaction (CSMS → CS)
The CSMS can remotely start a transaction on a Charging Station.
// CSMS sends:
{
"remoteStartId": 42,
"idToken": {
"idToken": "AABB1234",
"type": "ISO14443"
},
"evseId": 1, // optional: specific EVSE
"chargingProfile": { ... } // optional: charging profile
}
// CS responds:
{
"status": "Accepted",
"transactionId": "existing-tx-id" // if transaction already started
}function remoteStartTransaction(chargingStationId, params):
remoteStartId = generateUniqueId()
request = {
remoteStartId: remoteStartId,
idToken: params.idToken
}
if params.evseId:
request.evseId = params.evseId
if params.chargingProfile:
request.chargingProfile = params.chargingProfile
response = sendToChargingStation(chargingStationId,
"RequestStartTransactionRequest", request)
if response.status == "Accepted":
// Store remoteStartId to match with upcoming TransactionEventRequest
// The CS will include remoteStartId in transactionInfo.remoteStartId
storeRemoteStart(remoteStartId, chargingStationId, params)
// If response.transactionId is present, transaction already started
if response.transactionId:
linkTransaction(remoteStartId, response.transactionId)
return responseRemote Stop Transaction (CSMS → CS)
// CSMS sends:
{
"transactionId": "AB1234"
}
// CS responds:
{
"status": "Accepted"
}
// Then CS sends:
// TransactionEventRequest(eventType = Ended, stoppedReason = Remote)function remoteStopTransaction(chargingStationId, transactionId):
response = sendToChargingStation(chargingStationId,
"RequestStopTransactionRequest",
{ transactionId: transactionId })
if response.status == "Accepted":
// Wait for TransactionEventRequest with eventType = Ended
// and stoppedReason = Remote
return { success: true }
return { success: false, reason: response.statusInfo }21. Sequence Number Tracking & Completeness Validation
ReferenceThe CSMS must track sequence numbers per transaction to detect missing messages and ensure completeness before billing. This is especially important for offline scenarios where messages may be dropped.
Validation Algorithm
function validateTransactionCompleteness(transactionId):
events = getTransactionEvents(transactionId)
.sortBy(seqNo)
startEvent = events.find(e => e.eventType == "Started")
endEvent = events.find(e => e.eventType == "Ended")
if not startEvent:
return { complete: false, missing: "Started event" }
if not endEvent:
return { complete: false, missing: "Ended event" }
startSeq = startEvent.seqNo
endSeq = endEvent.seqNo
// Check for gaps
missingSeqs = []
for n in range(startSeq, endSeq + 1):
if not events.find(e => e.seqNo == n):
missingSeqs.append(n)
if missingSeqs.length > 0:
// Use GetTransactionStatusRequest to check if messages are queued
status = sendGetTransactionStatus(transactionId)
if status.messagesInQueue:
return { complete: false, missingSeqs, waitForDelivery: true }
else:
// Messages were lost (CS may have dropped them due to memory)
return { complete: false, missingSeqs, messagesLost: true }
return { complete: true }When to use this:
- After receiving
TransactionEventRequest(eventType=Ended)whereseqNogaps are detected - When the CSMS wants to verify if a transaction is still ongoing
- After connection restoration to check for pending messages
22. CSMS Implementation Checklist
ReferenceMessage Handlers to Implement
| Priority | Handler | Direction | Description |
|---|---|---|---|
| P0 | TransactionEventRequest handler | CS → CSMS | Core: handle Started/Updated/Ended |
| P0 | TransactionEventResponse builder | CSMS → CS | Core: build appropriate responses |
| P1 | RequestStartTransactionRequest sender | CSMS → CS | Remote start capability |
| P1 | RequestStopTransactionRequest sender | CSMS → CS | Remote stop capability |
| P1 | GetTransactionStatusRequest sender | CSMS → CS | Query transaction status |
| P2 | NotifyEventRequest handler | CS → CSMS | Connector availability changes |
Data Model Requirements
Transaction:
transactionId: string (UUID, max 36)
chargingStationId: string
evseId: integer
connectorId: integer
startTime: datetime
endTime: datetime (nullable)
status: enum [Active, Completed, Cancelled]
idToken: IdTokenType (nullable)
stoppedReason: ReasonEnumType (nullable)
timeSpentCharging: integer (seconds)
lastSeqNo: integer
startSeqNo: integer
offline: boolean
remoteStartId: integer (nullable)
reservationId: integer (nullable)
tariffId: string (nullable)
transactionLimits: TransactionLimitType (nullable)
meterValues: MeterValueType[]
costDetails: CostDetailsType (nullable)
TransactionEvent:
transactionId: string (FK)
eventType: enum [Started, Updated, Ended]
seqNo: integer
timestamp: datetime
triggerReason: TriggerReasonEnumType
chargingState: ChargingStateEnumType (nullable)
offline: boolean
meterValues: MeterValueType[]
receivedAt: datetime // when CSMS actually received the messageCritical Business Rules
- ALWAYS respond to
TransactionEventRequest— never withhold a response - Validate every IdToken received in a
TransactionEventRequest(E05.FR.01) - Include
idTokenInfowithgroupIdTokenin response when IdToken is in the request (E01.FR.12) - Track sequence numbers per transaction to detect missing messages
- Use
timestampfrom the message for event times, not receive time (especially for offline events) - Distinguish
offline = truemessages — they are historical events - Check
TxCtrlr.SupportedLimitsbefore sendingtransactionLimit(E16.FR.12) - Provide cost updates when
maxCostlimit is active (E16.FR.11) - Resend TxProfile after transaction resume if not persisted (E17.FR.15)
- Use
GetTransactionStatusRequestwhen sequence gaps are detected
Response Decision Matrix
| eventType | Has idToken? | CSMS Response Should Include |
|---|---|---|
Started | Yes | idTokenInfo (with status + groupIdToken) |
Started | No | {} (empty) or transactionLimit if applicable |
Updated | Yes (Authorized) | idTokenInfo (validate the token!) |
Updated | No, triggerReason = LimitSet | Optionally transactionLimit to override |
Updated | No, periodic | {} or totalCost for running cost |
Ended | Yes | idTokenInfo + totalCost (final cost) |
Ended | No | totalCost (final cost) |
Error Handling
Malformed request
Still send a TransactionEventResponse (empty {} is valid). Log the error.
Do NOT withhold the response.
Unknown transactionId
In Updated/Ended: create the transaction record and process. The Started message may have been lost.
Duplicate seqNo
Idempotent handling. The CS may be retrying a message.
Out-of-order seqNo
Buffer and reorder if possible. CS retries can cause reordering.