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

OCPP 1.6 Smart Charging: SetChargingProfile and Stack Levels

OCPP 1.6 smart charging in practice: charging profile purposes, kinds, stackLevel, schedules, ClearChargingProfile and GetCompositeSchedule with examples.

HashTech Engineering · October 26, 2026 · 5 min read

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

json
{
  "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.

FieldValues / notes
chargingProfileIdInteger chosen by the CSMS. A new profile with the same ID replaces the old one.
transactionIdOnly for TxProfile: the transaction the profile applies to.
stackLevelInteger 0 or higher. Higher wins within the same purpose. Maximum given by ChargeProfileMaxStackLevel.
chargingProfilePurposeChargePointMaxProfile, TxDefaultProfile, TxProfile
chargingProfileKindAbsolute, Recurring, Relative
recurrencyKindDaily or Weekly, for Recurring profiles
validFrom / validToOptional validity window
chargingSchedulechargingRateUnit (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

PurposeScopeTypical use
ChargePointMaxProfileWhole charger; only on connectorId 0Hard cap for the charger, for example a site's share of grid capacity
TxDefaultProfileDefault for new transactions; on connector 0 for all connectors or on a specific connectorTime-of-day limits, default power policy
TxProfileOne specific running transactionPer-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:

json
[
  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 }
        ]
      }
    }
  }
]
json
[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:

json
[2, "csms-gcs-502", "GetCompositeSchedule", {"connectorId": 1, "duration": 3600, "chargingRateUnit": "W"}]
json
[
  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

json
[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.
  • TxProfile profiles 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.Offered or Power.Offered in 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); highest stackLevel wins within a purpose.
  • Know whether each charger takes amps or watts, and how many phases a session uses.
  • Use GetCompositeSchedule to 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.

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.