OCPP WS IOocpp-ws-io
Smart Charge Engine

Grid & Load Management

Multi-panel sites, hierarchical grids, VIP chargers, hardware caps, and OCPP profile types.

Grid & Load Management


How Real Sites Are Structured

A charging site is rarely one flat pool of power. In reality:

Utility Grid


Main Site Breaker (e.g., 250kW)

    ├──► Sub-panel A: Floor 1     (80kW) ──► CP-01, CP-02, CP-03, CP-04
    ├──► Sub-panel B: Floor 2     (80kW) ──► CP-05, CP-06, CP-07, CP-08
    ├──► Sub-panel C: VIP/Dedicated(50kW) ──► CP-09, CP-10  (NOT load-balanced)
    └──► Sub-panel D: Employee Lot (40kW) ──► CP-11, CP-12

Rule 1 — Not in the Engine = Not Load Balanced

The engine only manages sessions you explicitly register via addSession(). Simply skip addSession() for chargers you don't want managed:

server.route("/ocpp/:identity").on("client", (client) => {
  client.handle("ocpp1.6", "StartTransaction", async (ctx) => {
    if (!isVipCharger(client.identity)) {
      engine.addSession({ /* ... */ });
      await engine.dispatch();
    }
    // VIP chargers: no addSession → engine ignores, charger runs at full speed
    return {
      idTagInfo: { status: "Accepted" },
      transactionId: ctx.payload.transactionId,
    };
  });
});

Rule 2 — One Engine Per Sub-panel

Each engine manages one independent power pool. Create one per sub-panel:

const engineFloor1 = new SmartChargingEngine({
  siteId: "FLOOR-1",
  maxGridPowerKw: 80,
  dispatcher: /* ... */,
});

const engineFloor2 = new SmartChargingEngine({
  siteId: "FLOOR-2",
  maxGridPowerKw: 80,
  algorithm: Strategies.PRIORITY,
  dispatcher: /* ... */,
});

const engineEmployee = new SmartChargingEngine({
  siteId: "EMPLOYEE",
  maxGridPowerKw: 40,
  algorithm: Strategies.PRIORITY,
  dispatcher: /* ... */,
});

function getEngine(clientId: string) {
  if (FLOOR1_CHARGERS.has(clientId)) return engineFloor1;
  if (FLOOR2_CHARGERS.has(clientId)) return engineFloor2;
  if (EMPLOYEE_CHARGERS.has(clientId)) return engineEmployee;
  return null; // VIP — no engine
}

Rule 3 — Hardware Caps Per Session

The engine never assigns more than a charger can physically deliver:

engine.addSession({
  transactionId: tx.id,
  clientId: "CP-01",
  maxHardwarePowerKw: 22,       // charger hardware limit
  maxEvAcceptancePowerKw: 11,   // EV acceptance limit
  minChargeRateKw: 1.4,         // IEC 61851 minimum (6A × 230V)
  phases: 3,
});

Rule 4 — OCPP Profile Types

PurposeScopeUse Case
TxProfilePer transactionNormal load balancing (engine default)
TxDefaultProfileAll future transactions on connectorPre-set cap before car connects
ChargePointMaxProfileEntire stationHard limit regardless of any transaction
// Hard cap on a station (connectorId: 0 = whole station in OCPP 1.6)
buildOcpp16Profile(sessionProfile, {
  purpose: "ChargePointMaxProfile",
  stackLevel: 0,
});

Stack levels: A ChargePointMaxProfile at stackLevel: 0 acts as a hard ceiling, and a TxProfile at stackLevel: 1 operates within it. Both can coexist.


Rule 5 — Hierarchical Grid (Master + Sub-panels)

A master engine enforces a site-wide limit. Its dispatcher updates sub-panel engine budgets instead of sending OCPP commands:

const masterEngine = new SmartChargingEngine({
  siteId: "SITE-MASTER",
  maxGridPowerKw: 200,
  dispatcher: async ({ clientId, sessionProfile }) => {
    const subEngine = subPanelEngines.get(clientId);
    if (subEngine) {
      subEngine.setGridLimit(sessionProfile.allocatedKw);
      await subEngine.dispatch();
    }
  },
});

// Each sub-panel is a "session" in the master engine
masterEngine.addSession({
  transactionId: "floor1",
  clientId: "PANEL-FLOOR-1",
  maxHardwarePowerKw: 80,
});
masterEngine.addSession({
  transactionId: "floor2",
  clientId: "PANEL-FLOOR-2",
  maxHardwarePowerKw: 80,
});
masterEngine.startAutoDispatch(60_000); // master rebalances every 60s

Scenario Reference

Site LayoutSolution
Single pool of chargers1 engine, all sessions registered
Chargers on dedicated linesDon't call addSession() for them
Sub-panels with own breakers1 engine per sub-panel
Mixed strategiesDifferent engine instances with different algorithm
Site-wide + sub-panel limitsMaster engine updates setGridLimit() on sub-engines
Dynamic grid (utility DR signal)engine.setGridLimit(newKw) at runtime
VIP / priority chargersPRIORITY strategy with priority per session
EV minimum charge requirementminChargeRateKw per session
Time-based pricingTIME_OF_USE strategy + startAutoDispatch()

OCPP Protocol Capabilities

FeatureOCPP 1.6OCPP 2.0.1 / 2.1
Per-transaction profile (TxProfile)
Station-wide hard capChargePointMaxProfileChargingStationMaxProfile
Per-connector default (TxDefaultProfile)
Multiple schedule periods
Multiple schedule arrays per profile
V2G / discharge✅ (2.1 only)
Stack level profile ordering
connectorId: 0 (whole station)Replaced by evseId model

On this page