OCPP's Reservation feature profile lets a CSMS hold a connector for a specific driver before they arrive, which is what sits behind an app-based "reserve a charger" flow. It also tends to go under-tested, because exercising it against real hardware means physically blocking a charger and timing an expiry by hand. A simulator that tracks reservation state makes the same checks run in seconds.
How a reservation works in OCPP 1.6
The CSMS sends ReserveNow.req to the station with a connectorId, an expiryDate, the idTag allowed to use the reservation, a reservationId, and optionally a parentIdTag. If the station accepts, the connector's status becomes Reserved and it reports this via StatusNotification. The CSMS can release the reservation early with CancelReservation.req, or let it lapse. The station is responsible for expiring it on its own once expiryDate passes, returning the connector to Available without any further CSMS involvement.
What "enforcing" a reservation actually means
A reservation does not stop a cable from being plugged in. The wrong driver can still connect. OCPP 1.6 §5.11 is specific about this: the restriction applies to starting a transaction, not to the physical connection. A correctly behaving station accepts the plug-in, transitioning Reserved to Preparing just as an unreserved connector would, and then rejects any attempt to start a transaction unless the idTag matches the reservation's own idTag or its parentIdTag. The parent group matters because a reservation is meant to work for an entire fleet card group rather than one physical tag.
Common reservation bugs this surfaces
- Missing parentIdTag fallback. The reservation's own idTag is checked, but a driver presenting a different tag from the same parent group is wrongly rejected.
- RemoteStart bypassing the check. A CSMS-initiated RemoteStartTransaction with the wrong idTag is accepted anyway, because the reservation logic was only wired into the local card-swipe path.
- Expiry never fires. The connector stays stuck in Reserved indefinitely because nothing is watching the clock on the CSMS's own reservation record.
- CancelReservation on an unknown ID. This should be rejected cleanly; some implementations throw an unhandled error instead.
- Double reservation. A second ReserveNow on an already-Reserved or Occupied connector should be refused rather than silently overwriting the first.
Testing the reservation flow with SimItNow
SimItNow's simulated stations track reservation state per §5.11, including the idTag/parentIdTag match, the Reserved to Preparing transition on plug-in, and automatic expiry. A typical test sequence:
- Create a charging station via the API and let it connect to your CSMS.
- From your CSMS, send ReserveNow for a connector with a specific idTag and a short expiryDate. A minute or two is enough for a test.
- Confirm your CSMS receives a StatusNotification showing the connector as Reserved.
- Plug in a virtual EV (POST /v2/ev/{ev_id}/connection). The connector moves to Preparing, but the reservation is still active underneath.
- Send RemoteStartTransaction with a different idTag and expect Rejected.
- Send RemoteStartTransaction with the reserved idTag (or its parentIdTag) and expect Accepted. The transaction starts and the reservation is released.
- Run the sequence again, but this time send CancelReservation before anyone plugs in. Confirm the connector returns to Available immediately.
- Run it a third time and wait past expiryDate without cancelling. Confirm the connector returns to Available on its own, with no action from your CSMS.
That covers the three ways a reservation can be released: a matching idTag, an explicit cancel, and a timeout. Each one is a separate code path in your CSMS, and none of them needs real hardware to verify.
Create a free SimItNow account and test your reservation handling end to end.