PHP2Shell alert: critical PHP flaws CVE-2026-109618 / 109619 — check your servers and get patched fast
HashTech Systems
EV Charging
Part 1 of 11

OCPP 2.0.1 Tutorial: Architecture, EVSE Model and Functional Blocks

Start here for OCPP 2.0.1: OCPP-J framing, the station/EVSE/connector model, the A to P functional blocks, and how to read use case IDs.

HashTech Engineering · October 26, 2026 · 6 min read

OCPP 2.0.1 is the version of the Open Charge Point Protocol that new charging station firmware and new CSMS platforms are being built against. It is not an incremental update to 1.6: the transaction model, configuration, error reporting and security were all redesigned. This series walks through it the way you would implement it, with real OCPP-J frames and the mistakes we see when stations and backends meet for the first time.

If you already know OCPP 1.6, some of this will feel familiar (WebSocket, JSON arrays, BootNotification, Heartbeat). Much of it will not. If you want a higher-level comparison first, read OCPP 1.6 vs 2.0. For the 1.6 equivalent of this article, see the OCPP 1.6 introduction.

A note on versions: 2.0 vs 2.0.1

OCPP 2.0 was published in 2018 and quickly found to have issues; OCPP 2.0.1 followed in 2020 and replaced it. Nobody should implement plain 2.0. The Open Charge Alliance has since published further editions of 2.0.1 that fold in errata and clarifications, so when you implement, work from the latest 2.0.1 edition and its errata sheet rather than an old PDF someone emailed you. OCPP 2.1 (January 2025) extends 2.0.1 and is covered in part 11.

The specification is split into parts. In practice you will live in Part 2 (the specification itself: use cases, requirements, messages, data types and the device model appendix), Part 3 (the JSON schemas) and Part 4 (the OCPP-J implementation guide for JSON over WebSockets). Part 0 and Part 1 are introduction and architecture and are worth one careful read.

What actually changed from 1.6

  • Transactions: StartTransaction, StopTransaction and transaction-related MeterValues are replaced by a single TransactionEvent message with eventType Started/Updated/Ended. The station generates the transactionId, not the CSMS.
  • Configuration: flat 1.6 configuration keys are replaced by a structured device model of components, variables and attributes, accessed with GetVariables/SetVariables and reported with GetBaseReport/NotifyReport.
  • Status and errors: StatusNotification only carries one of five connector states. Fault details move to NotifyEvent, driven by monitors you can configure remotely.
  • Security: the security profiles, certificate management and security events that were a whitepaper add-on for 1.6 are part of the core spec.
  • ISO 15118: Plug & Charge, contract certificate handling and EV charging needs are built in.
  • Customer-facing features: display messages, tariff and running-cost information, and richer authorization (IdTokenType with a type, group tokens, more status values).
  • Topology: an explicit three-level model of Charging Station, EVSE and Connector.

Transport: OCPP-J over WebSocket

The station opens a WebSocket to the CSMS. The URL is the CSMS endpoint with the station's identity appended as the last path segment, and the station offers the ocpp2.0.1 subprotocol. A CSMS that supports several versions picks one and echoes it in the response; if it returns no subprotocol, the station should close the connection.

http
GET /ocpp/CS-DEPOT-0042 HTTP/1.1
Host: csms.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: ocpp2.0.1, ocpp1.6
Authorization: Basic Q1MtREVQT1QtMDA0MjpzM2NyZXQtcGFzc3dvcmQtZm9yLWRlbW8=

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: ocpp2.0.1

Every message is a JSON array. The first element is the message type, the second a unique message ID chosen by the sender, then either the action and payload (CALL), the payload (CALLRESULT), or error details (CALLERROR).

MessageTypeIdNameShape
2CALL[2, messageId, action, payload]
3CALLRESULT[3, messageId, payload]
4CALLERROR[4, messageId, errorCode, errorDescription, errorDetails]
json
[2, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", "Heartbeat", {}]

[3, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", {
  "currentTime": "2026-10-26T09:15:02.000Z"
}]

[4, "b7c1e2a0-4d5f-4c1e-9a77-1f0b2f6c9d10", "FormatViolation", "Payload is not valid JSON", {}]

The 2.0.1 error codes are FormatViolation, GenericError, InternalError, MessageTypeNotSupported, NotImplemented, NotSupported, OccurrenceConstraintViolation, PropertyConstraintViolation, ProtocolError, RpcFrameworkError, SecurityError and TypeConstraintViolation. Note the spelling: 1.6 had the typo FormationViolation; 2.0.1 fixed it to FormatViolation, and code shared between versions often gets this wrong.

Charging Station, EVSE, Connector

2.0.1 models hardware in three levels. The Charging Station is the physical unit with one OCPP connection. An EVSE is an independently operated and managed part of the station that can deliver energy to one EV at a time. A Connector is a physical socket or cable on an EVSE. An EVSE may have several connectors (for example a CCS cable and a CHAdeMO cable), but only one can be in use at a time.

  • EVSE ids are numbered from 1 upwards, sequentially, per station.
  • Connector ids are numbered from 1 upwards within each EVSE. Connector 1 on EVSE 2 is { "id": 2, "connectorId": 1 }.
  • evseId 0 is used to address the whole station in messages that take an EVSE id (for example a station-wide charging profile).
json
{
  "evse": {
    "id": 2,
    "connectorId": 1
  }
}

This is the single biggest modelling change for a CSMS coming from 1.6, where a flat connectorId with 0 meaning "the whole station" did everything. Your database needs an EVSE table between station and connector, and it is the EVSE (not the connector) that holds a transaction, a charging profile or a reservation. EVSE ids are also what you map to OCPI EVSE objects, covered in OCPP to OCPI mapping.

Functional blocks and use cases

1.6 grouped messages into feature profiles. 2.0.1 instead organises the specification into lettered functional blocks, each containing numbered use cases. A use case such as E02 (Start Transaction, Cable Plugin First) has a sequence diagram and numbered requirements like E02.FR.01. When a vendor or test report cites a requirement, this is the ID they mean, and it is the fastest way to settle an interop argument.

BlockNameTypical messages
ASecuritySignCertificate, CertificateSigned, SecurityEventNotification
BProvisioningBootNotification, GetVariables, SetVariables, GetBaseReport, Reset
CAuthorizationAuthorize, ClearCache
DLocal Authorization List ManagementSendLocalList, GetLocalListVersion
ETransactionsTransactionEvent, GetTransactionStatus
FRemote ControlRequestStartTransaction, RequestStopTransaction, UnlockConnector, TriggerMessage
GAvailabilityStatusNotification, ChangeAvailability, Heartbeat
HReservationReserveNow, CancelReservation, ReservationStatusUpdate
ITariff and CostCostUpdated, cost fields in TransactionEvent
JMeter ValuesMeterValues, meter values inside TransactionEvent
KSmart ChargingSetChargingProfile, GetCompositeSchedule, NotifyEVChargingNeeds
LFirmware ManagementUpdateFirmware, FirmwareStatusNotification
MISO 15118 Certificate ManagementGet15118EVCertificate, GetCertificateStatus, InstallCertificate
NDiagnosticsGetLog, SetVariableMonitoring, NotifyEvent, CustomerInformation
ODisplay MessageSetDisplayMessage, GetDisplayMessages, ClearDisplayMessage
PData TransferDataTransfer

A station does not have to implement every block. Certification is done against defined profiles (a core set plus optional ones such as smart charging, ISO 15118 support or advanced security), so check which profiles a station was certified for rather than assuming. See OCPP certification explained.

Schemas are strict, and customData is your escape hatch

The 2.0.1 JSON schemas set additionalProperties to false. A station that adds a vendor field to BootNotificationRequest will, against a strictly validating CSMS, get a CALLERROR back. Every 2.0.1 data type instead allows an optional customData object with a mandatory vendorId, and that is where vendor extensions belong:

json
{
  "reason": "PowerUp",
  "chargingStation": {
    "model": "DC-150",
    "vendorName": "ExampleVendor"
  },
  "customData": {
    "vendorId": "com.examplevendor",
    "hardwareRevision": "C2"
  }
}

How this series is organised

  1. Introduction (this article)
  2. The device model: components, variables, reports and monitoring
  3. Boot and provisioning: BootNotification, network profiles, Heartbeat, StatusNotification
  4. TransactionEvent: the new transaction model
  5. Authorization: IdTokens, cache, local list, remote start
  6. Smart charging: profiles, external limits, EV charging needs
  7. Security: profiles, certificates, events, signed firmware
  8. ISO 15118 Plug & Charge
  9. Display messages and costs
  10. Migrating from 1.6 to 2.0.1
  11. What's new in OCPP 2.1

Key takeaways

  • Use subprotocol ocpp2.0.1, the station identity as the last URL segment, and the latest 2.0.1 edition plus errata.
  • Model Charging Station, EVSE and Connector explicitly; EVSE is where transactions live.
  • Learn the functional block letters and use case IDs; they are the shared vocabulary for interop debugging.
  • Expect strict schema validation and put vendor extensions in customData.

HashTech builds and operates CSMS platforms that run OCPP 1.6-J and 2.0.1 side by side; more on that on our EV charging page.

Let's Talk

Ready to build something that lasts?

Whether it's a charging network, an AI rollout, or a core enterprise system — let's talk about what you're building.