Smart charging lets the CSMS limit how much power a charger delivers and when. It is how you keep a site within its grid connection, shift load to cheaper hours, and share capacity among several vehicles. In OCPP 1.6 it is the Smart Charging feature profile, built around one structure, the charging profile, and three messages. This is part 9; for the business side of load management see smart charging and load management.
The ChargingProfile structure
{
"chargingProfileId": 101,
"stackLevel": 0,
"chargingProfilePurpose": "TxDefaultProfile",
"chargingProfileKind": "Recurring",
"recurrencyKind": "Daily",
"chargingSchedule": {
"startSchedule": "2026-10-26T00:00:00Z",
"duration": 86400,
"chargingRateUnit": "A",
"chargingSchedulePeriod": [
{ "startPeriod": 0, "limit": 32.0 },
{ "startPeriod": 61200, "limit": 16.0 },
{ "startPeriod": 75600, "limit": 32.0 }
]
}
}That profile, applied to a charger, limits every session to 32 A, except from 17:00 to 21:00 UTC each day when it is 16 A. startPeriod is in seconds from the start of the schedule.
| Field | Values / notes |
|---|---|
chargingProfileId | Integer chosen by the CSMS. A new profile with the same ID replaces the old one. |
transactionId | Only for TxProfile: the transaction the profile applies to. |
stackLevel | Integer 0 or higher. Higher wins within the same purpose. Maximum given by ChargeProfileMaxStackLevel. |
chargingProfilePurpose | ChargePointMaxProfile, TxDefaultProfile, TxProfile |
chargingProfileKind | Absolute, Recurring, Relative |
recurrencyKind | Daily or Weekly, for Recurring profiles |
validFrom / validTo | Optional validity window |
chargingSchedule | chargingRateUnit (A or W), chargingSchedulePeriod[], optional duration, startSchedule, minChargingRate |
Each period has startPeriod, limit (a decimal, the spec recommends at most one digit after the point), and optional numberPhases (defaults to 3).
The three purposes
| Purpose | Scope | Typical use |
|---|---|---|
ChargePointMaxProfile | Whole charger; only on connectorId 0 | Hard cap for the charger, for example a site's share of grid capacity |
TxDefaultProfile | Default for new transactions; on connector 0 for all connectors or on a specific connector | Time-of-day limits, default power policy |
TxProfile | One specific running transaction | Per-session limit from a load balancer or a driver's choice |
For a given connector at a given moment, the charger takes the TxProfile for the running transaction if there is one, otherwise the applicable TxDefaultProfile (a connector-specific one takes precedence over one set on connector 0). It then applies the ChargePointMaxProfile as an overall cap. Within a purpose, the valid profile with the highest stackLevel wins, which lets you lay a temporary override on top of a base profile without deleting it.
Profile kinds
- Absolute: the schedule starts at
startSchedule, a fixed point in time. - Recurring: the schedule repeats daily or weekly from
startSchedule. Use for time-of-day tariffs and building load patterns. - Relative: the schedule starts when the transaction starts; no
startSchedule. Use for things like "full power for the first 30 minutes, then reduce".
SetChargingProfile
A load balancer reducing a running session to 11 kW:
[
2,
"csms-sp-501",
"SetChargingProfile",
{
"connectorId": 1,
"csChargingProfiles": {
"chargingProfileId": 7001,
"transactionId": 880214,
"stackLevel": 1,
"chargingProfilePurpose": "TxProfile",
"chargingProfileKind": "Relative",
"chargingSchedule": {
"chargingRateUnit": "W",
"chargingSchedulePeriod": [
{ "startPeriod": 0, "limit": 11000.0 }
]
}
}
}
][3, "csms-sp-501", {"status": "Accepted"}]The field name is csChargingProfiles (plural, despite holding one profile). The response is Accepted, Rejected or NotSupported. A TxProfile sent when no transaction is active on that connector is rejected. That is a common race: the load balancer reacts to a StartTransaction before the CSMS has finished processing it. If you want a session-specific limit from the very first second, include a chargingProfile in RemoteStartTransaction instead (see part 7).
When a new profile has the same chargingProfileId, or the same stackLevel and purpose on the same connector, as an existing one, it replaces the existing profile. Use stable IDs per purpose and connector so you replace rather than accumulate profiles; chargers have a limit (MaxChargingProfilesInstalled) and behave unpredictably when you hit it.
Amps or watts
chargingRateUnit is A or W. The ChargingScheduleAllowedChargingRateUnit key tells you which the charger accepts (Current, Power, or both). Many AC chargers only accept amps, which they apply per phase; DC chargers more commonly take watts. For a single-phase vehicle on a three-phase AC charger, a 16 A limit is roughly 3.7 kW, not 11 kW. Your load balancer has to know the phase count of each session (from numberPhases, meter values per phase, or site data) to translate kW budgets into amp limits correctly. The optional ConnectorSwitch3to1PhaseSupported key indicates whether the charger can switch phases based on numberPhases.
Also note that IEC 61851 AC charging does not allow offering less than 6 A. A limit of, say, 4 A is typically treated as 0 (pause, SuspendedEVSE) or rounded up, depending on firmware. Set minChargingRate where useful and design your allocator to give a session either at least 6 A or nothing.
GetCompositeSchedule
To see what the charger will actually do after combining all profiles, ask for the composite schedule:
[2, "csms-gcs-502", "GetCompositeSchedule", {"connectorId": 1, "duration": 3600, "chargingRateUnit": "W"}][
3,
"csms-gcs-502",
{
"status": "Accepted",
"connectorId": 1,
"scheduleStart": "2026-10-26T11:10:00Z",
"chargingSchedule": {
"duration": 3600,
"chargingRateUnit": "W",
"chargingSchedulePeriod": [
{ "startPeriod": 0, "limit": 11000.0 }
]
}
}
]This is the best debugging tool in the profile. When a driver complains about slow charging, the composite schedule tells you whether the cause is your profiles or the vehicle. connectorId 0 asks for the expected consumption of the whole charger. Support varies: some chargers return Rejected for a rate unit they do not use internally, so omit chargingRateUnit if you get rejections.
ClearChargingProfile
[2, "csms-ccp-503", "ClearChargingProfile", {"connectorId": 1, "chargingProfilePurpose": "TxDefaultProfile", "stackLevel": 1}]
[3, "csms-ccp-503", {"status": "Accepted"}]All fields are optional: id, connectorId, chargingProfilePurpose, stackLevel. With id, that profile is cleared; otherwise the other fields act as filters, and an empty request clears every profile. The response is Accepted or Unknown (nothing matched). Clearing everything is a blunt tool, since it also removes your ChargePointMaxProfile safety cap.
Field gotchas
- Profiles do not always survive a reboot on every firmware, even though they should be stored persistently. After a
BootNotification, re-send the profiles that should be in place. TxProfileprofiles end with their transaction. Do not expect them on the next session.- Chargers apply limits with a delay, and vehicles respond with further delay. Allow tens of seconds before deciding a limit was ignored, and check
Current.OfferedorPower.Offeredin meter values. - If the CSMS loses connection, chargers keep the last profiles. Make sure the default profile is safe on its own if your load balancer disappears.
Key takeaways
- Three purposes (
ChargePointMaxProfile,TxDefaultProfile,TxProfile), three kinds (Absolute,Recurring,Relative); higheststackLevelwins within a purpose. - Know whether each charger takes amps or watts, and how many phases a session uses.
- Use
GetCompositeScheduleto verify, and re-apply profiles after reboots.
Previous: Part 8, configuration keys. Next: Part 10, firmware and diagnostics. For how 2.0.1 extends this model, see OCPP 2.0.1 smart charging. HashTech builds smart charging and load management into the charging platforms we deliver; see EV charging.

