OCPP WS IOocpp-ws-io
Smart Charge Engine

Charging Strategies

Built-in allocation strategies — Equal Share, Priority, Time-of-Use — plus runtime swapping and custom strategies.

Charging Strategies

The engine ships with three built-in allocation strategies. Pass algorithm in the engine config, or swap it at runtime with engine.setAlgorithm().


Equal Share (Default)

Best for: Public parking, hospitality, retail — any site where fairness across all connected EVs matters.

Every active session gets an equal slice of available grid power after hardware caps are applied. Sessions with lower hardware limits consume less; the surplus is redistributed to the remaining sessions.

const engine = new SmartChargingEngine({
  siteId: "SITE-01",
  maxGridPowerKw: 100,
  algorithm: Strategies.EQUAL_SHARE, // default
  dispatcher: /* ... */,
});

Allocation example (100kW site, 5% safety margin → 95kW effective):

SessionHardware CapAllocated
CP-0122 kW22 kW (capped)
CP-0222 kW22 kW (capped)
CP-0350 kW51 kW → capped to 50 kW

After capping, unallocated surplus is redistributed across remaining eligible sessions in the same cycle.


Priority

Best for: Fleet depots, employee lots, subscription tiers — where some vehicles should always get more power.

Power is allocated proportionally to each session's priority weight (higher = more). Any power a session can't use (because it hit a hardware/EV cap) is redistributed to the rest by priority weight, so the grid stays fully utilized. If the priorities sum to 0, the strategy falls back to an equal split.

const engine = new SmartChargingEngine({
  siteId: "SITE-FLEET",
  maxGridPowerKw: 80,
  algorithm: Strategies.PRIORITY,
  dispatcher: /* ... */,
});

engine.addSession({
  transactionId: "tx-001",
  clientId: "CP-FLEET-01",
  maxHardwarePowerKw: 50,
  priority: 10,          // ← high: fleet truck, must be ready by 6 AM
  minChargeRateKw: 3.7,
});

engine.addSession({
  transactionId: "tx-002",
  clientId: "CP-VISITOR-01",
  maxHardwarePowerKw: 22,
  priority: 1,           // ← low: public visitor, best-effort
  minChargeRateKw: 1.4,
});

Time-of-Use (TOU)

Best for: Sites with time-of-day electricity pricing — charge faster during cheap off-peak hours, slower during expensive peak hours.

Combines Equal Share allocation with a list of peak windows. During a matching window the effective grid limit is multiplied by that window's peakPowerMultiplier (a value between 0 and 1). Outside every window, the full grid limit applies.

const engine = new SmartChargingEngine({
  siteId: "SITE-TOU",
  maxGridPowerKw: 100,
  algorithm: Strategies.TIME_OF_USE,
  timeOfUseWindows: [
    { peakStartHour: 18, peakEndHour: 22, peakPowerMultiplier: 0.4 },  // 6–10 PM → 40%
    { peakStartHour: 6,  peakEndHour: 18, peakPowerMultiplier: 0.75 }, // 6 AM–6 PM → 75%
  ],
  timeOfUseTimezone: "America/New_York", // optional — defaults to server local time
  dispatcher: /* ... */,
});

// Pair with auto-dispatch to continuously adjust as time changes
engine.startAutoDispatch(60_000); // recalculate every 60s

Power schedule example (100kW site):

TimeMatching windowEffective Grid
00:00 – 06:00none (off-peak)100 kW
06:00 – 18:000.7575 kW
18:00 – 22:000.440 kW
22:00 – 00:00none (off-peak)100 kW

Windows are evaluated top-to-bottom; the first match wins. peakStartHour / peakEndHour are integers 0–23 (overnight ranges like 22 → 6 are supported); peakPowerMultiplier must be between 0 and 1. Invalid windows throw SmartChargingConfigError. Set timeOfUseTimezone (IANA) to evaluate windows in the site's local time rather than the server's.


Runtime Strategy Swap

Switch strategy on a live engine without any downtime — to a built-in name or a custom StrategyFn:

// Evening peak — switch to TOU to reduce draw
// (windows from the constructor are retained, so no config arg is needed)
engine.setAlgorithm(Strategies.TIME_OF_USE);
await engine.dispatch(); // immediately rebalance with new strategy

// Fleet arrives overnight — switch to Priority
engine.setAlgorithm(Strategies.PRIORITY);
await engine.dispatch();

// Or swap in a custom function
engine.setAlgorithm(myCustomStrategy);

Minimum Charge Rate

All strategies respect minChargeRateKw per session. When the grid is constrained, the engine takes headroom from other sessions to honor each minimum (the floor is clamped to the session's hardware/EV cap — it can never exceed what the hardware can deliver):

engine.addSession({
  transactionId: "tx-xr5",
  clientId: "CP-01",
  maxHardwarePowerKw: 22,
  minChargeRateKw: 1.4, // 6A × 230V — below this some EVs fault
});

If the site is so over-subscribed that even the sum of all minimums exceeds the grid, the minimums cannot all be met. Grid safety wins: allocations are scaled down proportionally to fit, and a gridOverCommitted event fires with feasible: false and a starvedSessions list naming exactly which sessions fell below their floor (and by how much).


Custom Strategy (Advanced)

The engine accepts any function conforming to the StrategyFn type. It receives the active sessions, the effective grid budget (after the safety margin), and a context with the configured voltage. Use the buildSessionProfile helper to produce the raw kW/W/A numbers correctly.

import { SmartChargingEngine } from "ocpp-smart-charge-engine";
import { buildSessionProfile } from "ocpp-smart-charge-engine/strategies";
import type { StrategyFn } from "ocpp-smart-charge-engine";

// FIFO: the longest-waiting session charges first.
const fifo: StrategyFn = (sessions, effectiveGridKw, ctx) => {
  const oldest = [...sessions].sort((a, b) => a.addedAt - b.addedAt)[0];
  return sessions.map((s) =>
    buildSessionProfile(s, s === oldest ? effectiveGridKw : 0, ctx?.voltageV),
  );
};

const engine = new SmartChargingEngine({
  siteId: "CUSTOM",
  maxGridPowerKw: 100,
  algorithm: fifo,        // or: engine.setAlgorithm(fifo) at runtime
  dispatcher: /* ... */,
});
// engine.config.algorithm === "CUSTOM"

buildSessionProfile(session, allocatedKw, voltageV?) enforces the session's minChargeRateKw floor (clamped to its caps) and computes allocatedW / allocatedAmpsPerPhase for you. The engine's grid-budget guard still runs after your strategy, so you can never exceed the grid limit even with a custom function.

On this page