In OCPP 1.6, configuration is a flat list of string keys like HeartbeatInterval and MeterValueSampleInterval, plus whatever vendor keys the manufacturer invented. OCPP 2.0.1 replaces this with the device model: a structured, self-describing view of the station as components that have variables, where each variable has typed attributes and characteristics. It is the part of 2.0.1 that takes longest to get right, and almost everything else (transactions, smart charging, security) is configured through it.
Components and variables
A component is a logical or physical part of the station: a controller like OCPPCommCtrlr or TxCtrlr, or a physical thing like a connector, a power converter or a fan. A component is identified by a ComponentType: a name, an optional instance (when there are several of the same thing), and an optional evse (to place it on an EVSE or connector).
A variable is a property of that component, identified by VariableType: a name and an optional instance. The pair (component, variable) is the address of a configuration item or a piece of state.
{
"component": {
"name": "OCPPCommCtrlr"
},
"variable": {
"name": "HeartbeatInterval"
}
}
{
"component": {
"name": "Connector",
"evse": {
"id": 1,
"connectorId": 1
}
},
"variable": {
"name": "AvailabilityState"
}
}Attributes
Each variable can have up to four attributes, identified by AttributeEnumType. Most configuration variables only have Actual; the others matter for things like setpoints and limits.
| AttributeEnumType | Meaning |
|---|---|
Actual | The current value. This is the default when attributeType is omitted. |
Target | A desired value the component is working towards (for example a target temperature). |
MinSet | The configured lower limit. |
MaxSet | The configured upper limit. |
Each attribute also carries metadata: mutability (ReadOnly, WriteOnly, ReadWrite), persistent (survives reboot) and constant (never changes). Separately, VariableCharacteristicsType describes the variable itself: dataType (string, decimal, integer, dateTime, boolean, OptionList, SequenceList, MemberList), unit, minLimit, maxLimit, valuesList for list types, and supportsMonitoring.
Standardised components you will use daily
The device model appendix of the spec defines standard component and variable names. Stations may add their own, but the standard ones are what a CSMS can rely on across vendors. A non-exhaustive list of the ones most CSMS code touches:
| Component | Example variables |
|---|---|
OCPPCommCtrlr | HeartbeatInterval, NetworkConfigurationPriority, OfflineThreshold, MessageAttempts, MessageAttemptInterval, WebSocketPingInterval |
SecurityCtrlr | SecurityProfile, Identity, BasicAuthPassword, OrganizationName |
TxCtrlr | TxStartPoint, TxStopPoint, EVConnectionTimeOut, StopTxOnEVSideDisconnect, StopTxOnInvalidId |
AuthCtrlr | AuthorizeRemoteStart, LocalAuthorizeOffline, LocalPreAuthorize, OfflineTxForUnknownIdEnabled |
AuthCacheCtrlr / LocalAuthListCtrlr | Enabled, LifeTime, Entries, ItemsPerMessage |
SampledDataCtrlr | TxStartedMeasurands, TxUpdatedMeasurands, TxUpdatedInterval, TxEndedMeasurands, TxEndedInterval |
AlignedDataCtrlr | Measurands, Interval, TxEndedMeasurands, TxEndedInterval |
SmartChargingCtrlr | Enabled, ProfileStackLevel, PeriodsPerSchedule, RateUnit |
DeviceDataCtrlr | ItemsPerMessage, BytesPerMessage (instanced per message type) |
GetVariables and SetVariables
Both messages are batched: one request carries a list of component/variable pairs, and the response returns one result per entry, each with its own status. Here the CSMS reads the heartbeat interval and the transaction start point:
[2, "5f0c7a1e-0b1c-4c39-8a55-2b7d1f3e9c01", "GetVariables", {
"getVariableData": [
{
"component": { "name": "OCPPCommCtrlr" },
"variable": { "name": "HeartbeatInterval" }
},
{
"component": { "name": "TxCtrlr" },
"variable": { "name": "TxStartPoint" },
"attributeType": "Actual"
}
]
}]
[3, "5f0c7a1e-0b1c-4c39-8a55-2b7d1f3e9c01", {
"getVariableResult": [
{
"attributeStatus": "Accepted",
"attributeType": "Actual",
"attributeValue": "300",
"component": { "name": "OCPPCommCtrlr" },
"variable": { "name": "HeartbeatInterval" }
},
{
"attributeStatus": "Accepted",
"attributeType": "Actual",
"attributeValue": "PowerPathClosed",
"component": { "name": "TxCtrlr" },
"variable": { "name": "TxStartPoint" }
}
]
}]attributeStatus (GetVariableStatusEnumType) is one of Accepted, Rejected, UnknownComponent, UnknownVariable or NotSupportedAttributeType. Values are always strings on the wire, even for integers and booleans, so parse according to the variable's dataType.
[2, "8a7e44d2-3a0b-4e0a-b9a2-6d1c0f9b7e55", "SetVariables", {
"setVariableData": [
{
"attributeType": "Actual",
"attributeValue": "60",
"component": { "name": "SampledDataCtrlr" },
"variable": { "name": "TxUpdatedInterval" }
},
{
"attributeValue": "Energy.Active.Import.Register,Power.Active.Import",
"component": { "name": "SampledDataCtrlr" },
"variable": { "name": "TxUpdatedMeasurands" }
}
]
}]
[3, "8a7e44d2-3a0b-4e0a-b9a2-6d1c0f9b7e55", {
"setVariableResult": [
{
"attributeType": "Actual",
"attributeStatus": "Accepted",
"component": { "name": "SampledDataCtrlr" },
"variable": { "name": "TxUpdatedInterval" }
},
{
"attributeType": "Actual",
"attributeStatus": "Accepted",
"component": { "name": "SampledDataCtrlr" },
"variable": { "name": "TxUpdatedMeasurands" }
}
]
}]SetVariableStatusEnumType adds RebootRequired to the Get statuses: the value was stored but only takes effect after a reset. Treat it as success-with-follow-up, and track it per station so an operator can schedule the Reset rather than having the change silently not apply.
Reports: GetBaseReport, GetReport and NotifyReport
To discover what a station has, the CSMS asks for a report. GetBaseReport takes a requestId and a reportBase of ConfigurationInventory (configurable variables), FullInventory (everything) or SummaryInventory (a short status overview). The station answers the request immediately with a status, then sends the data asynchronously in one or more NotifyReport messages.
[2, "c0ffee01-1111-4a2b-9c3d-000000000001", "GetBaseReport", {
"requestId": 1017,
"reportBase": "FullInventory"
}]
[3, "c0ffee01-1111-4a2b-9c3d-000000000001", {
"status": "Accepted"
}][2, "3e1d9b9c-77aa-4f0e-8d3a-5b2c6e7f8a90", "NotifyReport", {
"requestId": 1017,
"generatedAt": "2026-10-26T09:20:11.000Z",
"seqNo": 0,
"tbc": true,
"reportData": [
{
"component": { "name": "OCPPCommCtrlr" },
"variable": { "name": "HeartbeatInterval" },
"variableAttribute": [
{
"type": "Actual",
"value": "300",
"mutability": "ReadWrite",
"persistent": true,
"constant": false
}
],
"variableCharacteristics": {
"unit": "s",
"dataType": "integer",
"supportsMonitoring": false
}
}
]
}]seqNo starts at 0 and increments per NotifyReport for the same requestId; tbc ("to be continued") is true on every part except the last. A full inventory from a DC charger can run to many parts and thousands of entries. GetReport is the filtered version: you pass componentVariable entries and/or componentCriteria (Active, Available, Enabled, Problem) to get only part of the model. The response status for both is GenericDeviceModelStatusEnumType: Accepted, Rejected, NotSupported or EmptyResultSet.
Monitoring: NotifyEvent instead of error codes
2.0.1 lets the CSMS ask the station to watch variables and report when something happens. Each monitor is set with SetVariableMonitoring and has a type from MonitorEnumType: UpperThreshold, LowerThreshold, Delta, Periodic or PeriodicClockAligned, a value, and a severity from 0 (Danger) to 9 (Debug).
[2, "a1b2c3d4-0000-4000-8000-00000000a001", "SetVariableMonitoring", {
"setMonitoringData": [
{
"value": 85,
"type": "UpperThreshold",
"severity": 4,
"component": { "name": "PowerConverter", "evse": { "id": 1 } },
"variable": { "name": "Temperature" }
}
]
}]When a monitor fires, the station sends NotifyEvent with one or more eventData entries. Each entry has an eventId, timestamp, trigger (Alerting, Delta, Periodic), the actualValue, the component and variable, an eventNotificationType (HardWiredNotification, HardWiredMonitor, PreconfiguredMonitor, CustomMonitor), and optionally cleared, techCode and techInfo.
[2, "e9d8c7b6-1234-4abc-9def-0123456789ab", "NotifyEvent", {
"generatedAt": "2026-10-26T13:02:45.000Z",
"seqNo": 0,
"eventData": [
{
"eventId": 5531,
"timestamp": "2026-10-26T13:02:44.000Z",
"trigger": "Alerting",
"actualValue": "86.5",
"techCode": "PC_OVERTEMP",
"eventNotificationType": "CustomMonitor",
"variableMonitoringId": 12,
"component": { "name": "PowerConverter", "evse": { "id": 1 } },
"variable": { "name": "Temperature" }
}
]
}]Other monitoring messages: SetMonitoringBase (All, FactoryDefault, HardWiredOnly) to choose which built-in monitors are active, SetMonitoringLevel to filter by severity, ClearVariableMonitoring, and GetMonitoringReport/NotifyMonitoringReport to list what is configured. Hardwired and preconfigured monitors from the manufacturer are where most fault reporting comes from in practice; this is where 1.6's errorCode and vendorErrorCode information now lives.
Field gotchas
- Treat names as case-sensitive.
HeartBeatIntervalis notHeartbeatInterval; you will getUnknownVariable. - Do not assume every standard variable exists. Many are optional. Discover with a report, cache the result, and fall back gracefully.
- Instances matter.
MessageAttemptsandMessageAttemptIntervalonOCPPCommCtrlruse the instanceTransactionEvent; omitting the instance addresses a different (usually non-existent) variable. - Read-only means read-only. Setting a
ReadOnlyattribute returnsRejected. Show mutability in your admin UI so operators stop trying. - Report volume. A
FullInventoryarriving as manyNotifyReportframes right after boot competes with everything else; request it once, then use targetedGetVariables.
Next
With the device model in hand, the next step is the connection lifecycle: boot, provisioning and network profiles. If you are mapping existing 1.6 keys to variables, the migration guide has a table, and the 1.6 side is covered in configuration keys.

