OCPP smart charging is how a CSMS controls the power a charger delivers: how much, to which connector, and when. It builds on the core OCPP protocol and is the technical foundation for dynamic load management (coming soon), time-of-use optimization, demand-response participation, and grid integration.
It is also the part of OCPP where “supported” is least likely to mean what you assume. There is a structural reason for that. In 1.6 smart charging is an optional feature profile rather than core, so a charger can pass conformance without implementing it, and a datasheet that says “smart charging: yes” is not a statement anyone had to prove. The gap only shows up when you send a profile and measure whether the power actually changed.
This article covers what OCPP smart charging defines, how to use it correctly, and where the operational gotchas hide.
The core concept
A charging profile is an instruction to a charger that says “limit charging power according to this schedule, for this purpose, with this stack level.”
The CSMS sends profiles via SetChargingProfile. The charger applies them. When several profiles apply at once, say a default profile plus a transaction-specific one, they stack, and the delivered power is the minimum of all applicable limits.
This stacking is what makes OCPP smart charging more flexible than a simple “set max power” command. You can layer constraints: site-level DLM at the bottom, time-of-use optimization in the middle, per-transaction customization at the top.
The three profile purposes and the minimum rule fit together like this:
flowchart TD
A[ChargePointMaxProfile<br/>site cap] --> D[Active limit<br/>= minimum]
B[TxDefaultProfile<br/>default per session] --> D
C[TxProfile<br/>specific session] --> D
D --> E[Delivered power]
style D fill:#0c590c,stroke:#0c590c,color:#fff
The three profile purposes
OCPP defines three purposes that profiles can have.
ChargePointMaxProfile
The absolute maximum for the entire charger, regardless of any other profile. Applies to all connectors, all transactions.
Typical use: enforce a site-level DLM constraint. “This charger may never exceed X amps regardless of what other profiles say.”
Stack level: usually 0 (or low). It’s the foundation.
TxDefaultProfile
The default profile for any transaction that starts on this charger. Applies when no specific TxProfile overrides it.
Typical use: define the default charging behavior. “By default, charge up to Y amps.”
Stack level: variable.
TxProfile
A profile for a specific transaction. Identified by transactionId. Overrides TxDefaultProfile for that transaction.
Typical use is per-session smart charging, applied when a session starts based on the eMSP’s request, the driver’s preferences, or a grid signal.
Stack level: variable. Higher stack level overrides lower-level profile of the same type.
The schedule within a profile
A profile contains a ChargingSchedule, which is where the actual power-over-time data lives.
The schedule has:
- chargingRateUnit:
A(amps) orW(watts). Pick one and stay consistent. - chargingSchedulePeriod: an array of periods. Each period has:
- startPeriod: offset in seconds from the schedule start.
- limit: maximum power for this period (in the chosen unit).
- numberPhases: how many phases this limit applies to (1 or 3 for AC).
- duration: how long the whole schedule lasts (seconds).
- startSchedule: when the schedule starts (datetime).
- minChargingRate: minimum rate (rarely used; lets you say “if you charge, charge at least this much”).
A typical schedule:
{
"chargingRateUnit": "A",
"chargingSchedulePeriod": [
{ "startPeriod": 0, "limit": 16 },
{ "startPeriod": 3600, "limit": 32 },
{ "startPeriod": 10800,"limit": 48 }
],
"duration": 14400
}
This says: “Charge at 16A for the first hour, 32A for the next 2 hours, 48A for the final hour, total 4 hours.”
The charger applies these limits over time. When the schedule ends, normal behavior resumes (typically: continue at the default profile or the next applicable profile).
How profile stacking works
When multiple profiles apply, the charger computes the actual limit at each moment as the minimum across all applicable profiles.
Example:
- ChargePointMaxProfile: 60A always.
- TxDefaultProfile: 32A during peak hours, 48A off-peak.
- TxProfile (this session): 40A from now for 2 hours.
At a given moment during peak hours: min(60, 32, 40) = 32A. At a given moment during off-peak: min(60, 48, 40) = 40A.
The charger evaluates this continuously. If profiles change, the active limit changes immediately.
The stacking rule is what makes this workable at all. The site operator, the eMSP, and the driver can each push a constraint without anyone having to negotiate, because taking the minimum resolves every combination deterministically and always in the safe direction.
Practical use cases
A few scenarios that smart charging via OCPP enables.
Dynamic load management
Site has 100 kW available. 20 chargers rated 11 kW each. DLM controller computes per-charger limits in real time as cars arrive and depart. Pushes ChargePointMaxProfile updates accordingly.
When 2 cars are charging: the per-vehicle share works out at 50 kW, which is above what an 11 kW charger can deliver anyway, so each simply charges at its full 11 kW. When 10 cars are charging: each gets 10 kW (sharing the 100 kW budget). When 20 cars are charging: each gets 5 kW.
The CSMS-driven approach uses SetChargingProfile messages. Local DLM controllers may use a different mechanism (per-charger limits via local API) but the concept is the same.
Time-of-use optimization
User wants to charge cheaply overnight. CSMS knows the time-of-use tariff. CSMS pushes a TxProfile that ramps low during peak hours, high during off-peak.
Example schedule:
- 8pm-10pm (peak): 7 kW limit.
- 10pm-6am (off-peak): 22 kW max.
The car charges slow during peak (saving the user money) and fast during off-peak.
Demand-response participation
Grid operator signals a demand event. The eMSP receives the signal (via grid services API). The eMSP pushes TxProfiles to all its active sessions, throttling them during the event.
User sees: “Your charging is reduced for 30 minutes to support the grid, and you will receive a bill credit.”
Solar self-consumption
Home solar inverter signals current generation. CSMS adjusts charging profile to match generation. Charger draws more when solar is high, less when low.
The OCPP 1.6 vs 2.0.1 difference
Both versions support smart charging, but 2.0.1 is cleaner. For a broader look at what changed between versions, see the OCPP version comparison.
1.6:
- An optional feature profile rather than core, which is why support varies so widely.
- SetChargingProfile message.
- Limited stacking support.
- ChargingProfileKind: Absolute, Recurring, Relative.
- ChargingRateUnit: A or W (consistent within a profile).
2.0.1:
- Native part of the spec.
- SetChargingProfileRequest / SetChargingProfileResponse.
- Cleaner stacking with stackLevel field.
- ChargingProfilePurpose enumeration matches the three purposes above.
- Integration with the device model (smart charging variables exposed via Variables).
- Better ISO 15118 (coming soon) integration for negotiation between vehicle and charger.
If you’re building smart-charging logic today, OCPP 2.0.1 is the right target. OCPP 1.6 smart charging is workable but bumpier.
Common pitfalls
A list of things that go wrong.
Charger claims smart-charging support but ignores profiles. SetChargingProfile returns Accepted and the actual power draw never changes. Accepted means the charger parsed and stored the profile, not that it will act on it, so the only proof is measuring delivered power against the limit you set.
Profile rejected because parameters don’t match the charger’s capabilities. A profile with 80A on a 32A-rated charger gets Rejected. Validate against the charger’s known capability before sending.
Schedule expires unexpectedly. A schedule with duration=3600 (one hour) runs out after an hour. The session continues but without the constraint. If you wanted ongoing limits, send a Recurring profile or extend.
Wrong unit (A vs W). Sending limit:11 with unit=W means 11 watts, not 11 kilowatts. The session crawls. Get the unit right.
Phase confusion. numberPhases matters for AC. A 16A limit on 3 phases is different from a 16A limit on 1 phase (3x different total power). Match phases to the actual installation.
Stacking surprises. Multiple profiles apply but the operator only checked one. Active limit is unexpectedly low. Implement a “show me what’s actually active” query (CompositeSchedule in OCPP) to debug.
Profile collisions. Two parties (CSMS and a local controller) both push profiles. They conflict. Behavior is implementation-defined. Coordinate or pick one source of truth.
What CSMS implementers should do
If you’re building smart-charging logic in a CSMS:
Treat smart-charging support as a per-charger capability. Query it, store it against the model and firmware version, and re-check it after firmware updates. It is a property of a specific build, not of a product line.
Validate profiles before sending. Reject malformed or impossible profiles in your own code; don’t let them go to the charger and come back as Rejected.
Test against the actual hardware. Send a profile; verify (via MeterValues or via observing the actual energy flow) that the charger honors it. Many discrepancies emerge here.
Implement CompositeSchedule queries. Lets you ask the charger “what’s actually active right now after stacking?” Essential for debugging.
Coordinate with local DLM. If a site has both CSMS-pushed profiles AND a local DLM controller, define which wins. Document.
Audit smart-charging behavior in production. Periodic checks that profiles you’ve pushed are still in effect; if they’ve been cleared or replaced unexpectedly, investigate.
What charger firmware should do
For OEMs building charger firmware:
Implement profiles correctly. Apply the limits, respect the stacking, honour the schedule. Since Accepted is cheap to return and correct behaviour is not, this is a place where a firmware team can be measurably better than the datasheet average.
Report capabilities accurately. Don’t claim ChargingProfileMaxStackLevel:8 if you actually only support 3.
Handle profile updates gracefully. A profile update mid-session should take effect smoothly without disrupting the session.
Honor units. A and W are different. Implementations that confuse them are a frequent bug source.
Support CompositeSchedule queries. Lets operators verify what’s actually active.
Handle profile expiration. When a schedule ends, fall back gracefully to the next applicable profile or the default behavior.
The honest summary
OCPP smart charging is a real and powerful capability when both the CSMS and the charger implement it well. The protocol mechanism (SetChargingProfile, ChargingSchedule, stacking) is well-designed and supports a wide range of use cases. The gap between “supported in the spec” and “works in production” is where the difficulty lives. Implementations vary, edge cases are common, and debugging is genuinely hard because the thing you want to inspect, the effective limit, is computed inside the charger.
That last point is the one to design around. GetCompositeSchedule exists precisely because you cannot infer the applied limit from the profiles you sent, and a smart-charging deployment without it is a deployment where you find out about stacking mistakes from an energy bill. Treat charger-level smart-charging support as a per-vendor capability to be tested rather than a checkbox to be trusted, and build the observability before you build the optimization.