The message
// Charge point -> CSMS
[2, "19223208", "MeterValues", {
"connectorId": 1,
"transactionId": 770142,
"meterValue": [{
"timestamp": "2026-08-11T09:47:10Z",
"sampledValue": [
{ "value": "4490220", "context": "Sample.Periodic",
"measurand": "Energy.Active.Import.Register",
"location": "Outlet", "unit": "Wh" },
{ "value": "48200", "context": "Sample.Periodic",
"measurand": "Power.Active.Import", "unit": "W" },
{ "value": "74", "context": "Sample.Periodic",
"measurand": "SoC", "unit": "Percent" },
{ "value": "398.2", "context": "Sample.Periodic",
"measurand": "Voltage", "phase": "L1-N", "unit": "V" }
]
}]
}]
// CSMS -> charge point
[3, "19223208", {}]transactionId is optional. Samples can be sent outside a transaction — an idle charger reporting voltage, for instance — and those must not be attributed to any session. A platform that assumes every MeterValues belongs to a transaction will mis-bill.
Measurands
| Measurand | Is | Matters for |
|---|---|---|
Energy.Active.Import.Register | Cumulative energy from the grid, in Wh | Billing. This is the one that matters. |
Power.Active.Import | Instantaneous power draw | Live progress, load management |
Current.Import | Current draw | Diagnostics, phase balance |
Voltage | Supply voltage | Power quality, fault diagnosis |
SoC | Vehicle state of charge, percent | Driver UX — only if the car reports it |
Temperature | Component temperature | Thermal derating and fault prediction |
Energy.Active.Export.Register | Energy returned to the grid | V2G |
Energy.Active.Import.Register is cumulative, like an odometer. A reading of 4,490,220 Wh does not mean 4.49 MWh in this session — it is the meter's lifetime total. Session energy is always a difference between two readings. Treating a register value as session energy produces invoices in the thousands of kWh, and it is the most costly arithmetic error in EV charging.SoC is worth a note: it comes from the vehicle over the charging protocol, not from the charger. Many vehicles do not report it, especially over AC. Build the UI so that a missing state of charge degrades gracefully rather than showing 0%.
Context: why the sample was taken
| Context | Taken because |
|---|---|
Sample.Periodic | Routine interval — the bulk of traffic |
Sample.Clock | Clock-aligned interval, e.g. on the hour |
Transaction.Begin | Session start — the billing baseline |
Transaction.End | Session end — the billing close |
Interruption.Begin / Interruption.End | Charging paused and resumed |
Trigger | Requested via TriggerMessage |
Other | Vendor-specific |
Transaction.Begin and Transaction.End samples are the ones to trust for billing boundaries. They should agree with meterStart and meterStop on the transaction messages — and when they do not, you have a data integrity problem worth investigating before you invoice.
Sampling interval: the real trade-off
Controlled by MeterValueSampleInterval in configuration. The choice is not free in either direction.
| Interval | Samples per hour per session | Consequence |
|---|---|---|
| 10 s | 360 | Excellent granularity; heavy data cost and ingestion load |
| 60 s | 60 | The usual balance |
| 300 s | 12 | Cheap; poor resolution across tariff boundaries |
| 900 s | 4 | Billing-boundary risk on time-of-use tariffs |
The billing consequence is specific. If a tariff changes price at 22:00 and your last sample before it was at 21:52, the energy consumed in those eight minutes has to be attributed to one side or the other — and whichever you choose, you and your roaming partner may choose differently. Sampling finer than your narrowest tariff boundary is what avoids that argument.
Also configure MeterValuesSampledData deliberately: it decides which measurands are sent. A charger configured without the cumulative energy register produces sessions you cannot bill at all.
Validation
Meter data is the input to money, so validate it on arrival rather than at invoice time:
| Check | Rule | A failure means |
|---|---|---|
| Monotonicity | The cumulative register must never decrease | Meter reset, replacement, or corrupt data |
| Plausible rate | Energy delta ÷ time must not exceed the connector rating | Bad reading or wrong unit |
| Unit sanity | Wh vs kWh — a 1000× jump is a unit bug | Vendor implementation difference |
| Timestamp ordering | Samples should advance | Clock drift — see Heartbeat |
| Transaction linkage | transactionId matches a known session | Orphan samples, or an untracked session |
A decreasing register is the one to alert on immediately. It means the meter was reset or replaced, and every energy calculation spanning that point is wrong until someone establishes a new baseline.
Reconciliation
Three checks that should run continuously on any billing platform:
- MeterValues against StopTransaction. The last sample should closely match
meterStop. A persistent gap means samples are being lost. - Sum of deltas against the overall difference. They should agree; disagreement points to duplicated or missing samples.
- Session energy against the OCPI CDR. If your CDR does not match your own meter data, the CDR is wrong — and a roaming partner's dispute will be decided on exactly this comparison.
Production lessons
- Treat the energy register as cumulative. Always compute differences, never read a register as session energy.
- Sample finer than your narrowest tariff boundary.
- Verify
MeterValuesSampledDataat commissioning for every charger model. - Alert on non-monotonic registers immediately.
- Handle samples with no
transactionIdwithout attributing them to a session. - De-duplicate against
transactionDatadelivered on StopTransaction, or offline sessions double-count. - Store raw samples, not just derived totals. A billing dispute is resolved from the samples.
- Stagger clock-aligned sampling across the fleet or every charger reports simultaneously.
Frequently asked questions
The cumulative energy imported from the grid, in watt-hours — a lifetime meter total, like an odometer. Session energy is always the difference between two readings. Treating a register value as session energy produces invoices in the thousands of kWh.
Sixty seconds is the usual balance. The binding constraint is that you should sample finer than your narrowest tariff boundary, otherwise energy consumed between the last sample and a price change has to be attributed arbitrarily — and your roaming partner may attribute it differently.
Why the sample was taken — Sample.Periodic for routine intervals, Sample.Clock for clock-aligned samples, Transaction.Begin and Transaction.End for billing boundaries, and Interruption.Begin or Interruption.End when charging pauses and resumes.
Because SoC comes from the vehicle over the charging protocol, not from the charger. Many vehicles do not report it, particularly over AC, so the UI should degrade gracefully rather than displaying zero.
Alert immediately. A decreasing cumulative register means the meter was reset or replaced, and every energy calculation spanning that point is wrong until a new baseline is established.