EV Charging Software: Why the Hard Part Is Offline Chargers
By Maplecode
Charge point management systems look like a straightforward distributed application: devices report status, users start sessions, the platform bills. The protocols are standardised, the domain is not conceptually deep, and a working prototype comes together quickly.
Then you deploy to a car park with one bar of signal, and the assumptions start failing. A charger that cannot reach your backend still has a car plugged into it and a customer expecting electricity. What your system does in that moment determines whether the product is viable.
Chargers are unreliable clients you do not control
A charge point is an embedded device in an outdoor enclosure, on a connection that varies from adequate to absent, running firmware from a manufacturer whose interpretation of the specification is their own. It may be rebooted by a passer-by. It may lose connectivity mid-session and return with a meter reading that does not match what you last recorded.
OCPP defines the message exchange, which helps considerably. What it does not do is make implementations consistent. Different vendors handle reconnection, message ordering and error states differently, and in practice you will discover a firmware version that sends a StopTransaction with no matching StartTransaction. Your system has to tolerate that rather than treating it as impossible.
The design consequence: treat charger messages as an event stream that may arrive late, out of order, or duplicated, and reconcile from meter values rather than from your own assumption about session state. A session's energy delivered should be derived from start and stop meter readings, not accumulated from periodic updates that may have gaps.
Offline authorisation is a business decision
When a charger cannot reach the backend, someone must decide whether to deliver energy. There is no technically correct answer, only a commercial one, and it needs deciding explicitly rather than defaulting to whatever the firmware does.
Refusing all offline sessions protects revenue and produces the worst possible customer experience — a charger that appears functional and refuses to work. Allowing them accepts some unbilled energy and theft risk. Most operators land in between, using a locally cached authorisation list so known-good tokens work offline while unknown ones do not.
That cache introduces its own problem: it goes stale. A revoked card keeps working until the charger reconnects. Bounding that window is a real design parameter, and it interacts with how long you are willing to let a charger operate disconnected at all.
Roaming multiplies the failure modes
Roaming — letting a driver from another network use your chargers — is where most complexity enters. You now have a third party in the authorisation path, meaning an additional network hop with its own latency and availability, and a settlement relationship where both sides must agree on what happened.
Disputes are routine rather than exceptional. Your record says 34.2 kWh; theirs says 33.8. Someone has to reconcile, which means you need session records detailed enough to defend — meter readings with timestamps, not just a total. Sessions that failed to start need to be distinguishable from sessions that started and delivered nothing, because those settle differently.
Two things reduce the pain considerably: an authoritative session record built from meter values, and a reconciliation process that runs continuously rather than at month end. Discovering a systematic discrepancy after thirty days means thirty days of disputed settlement.
Tariffs are more complicated than they look
Charging tariffs combine energy price, time-based components, occupancy fees for remaining plugged in after charging completes, session fees, and often different rates by time of day. Some jurisdictions require price transparency before a session starts, which means you must be able to quote a rate for a session whose duration and energy are unknown.
Build this as a rating engine over session events rather than as pricing logic inside the session handler. The reason is dull and important: tariffs change, and you will need to re-rate historical sessions after discovering a configuration error. If pricing was applied inline and only the total was stored, you cannot.
Payment sits awkwardly between two models
Charging bills for an amount that is unknown when the session starts, which does not fit how card payments prefer to work. The usual approach is a pre-authorisation for an estimated maximum, then a capture for the actual amount when the session ends.
Two consequences follow. The pre-authorisation holds funds the driver cannot use, and if you estimate high to be safe, you are holding a meaningful amount of someone's money for a session that cost a fraction of it. Estimate low and you risk a capture exceeding the authorisation, which can fail outright.
The awkward case is a session that ends when the backend is unreachable. You have delivered energy and cannot yet capture payment. That needs a queue of pending captures with retry, an expiry policy for authorisations that aged out, and an accepted write-off rate — because some of them will not be recoverable, and pretending otherwise means your revenue reporting is wrong.
Support needs to see what the driver saw
Most charging complaints are one of three things: it did not start, it charged slower than expected, or the bill looks wrong. All three are answerable from data, and none are answerable from a status dashboard showing current state.
What support needs is the session timeline — every message from the charger, the meter readings, the authorisation result, the power delivered over time, and the tariff applied. Slower than expected is very often the vehicle tapering as its battery filled, which is normal behaviour and completely convincing once you can show the curve. Without that, it is an argument.
What to get right early
Meter-derived session records, so energy delivered is defensible. An explicit, deliberate offline authorisation policy with a bounded staleness window. A rating engine separate from session handling. Continuous reconciliation with roaming partners. And enough tolerance for malformed firmware behaviour that one vendor's quirk does not corrupt your billing.
The customer-facing app, which is where most attention goes at the start, is comparatively simple — and much easier to build once the session record underneath it is trustworthy.