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):
| Session | Hardware Cap | Allocated |
|---|---|---|
| CP-01 | 22 kW | 22 kW (capped) |
| CP-02 | 22 kW | 22 kW (capped) |
| CP-03 | 50 kW | 51 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 60sPower schedule example (100kW site):
| Time | Matching window | Effective Grid |
|---|---|---|
| 00:00 – 06:00 | none (off-peak) | 100 kW |
| 06:00 – 18:00 | 0.75 | 75 kW |
| 18:00 – 22:00 | 0.4 | 40 kW |
| 22:00 – 00:00 | none (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.