MeterValues is the OCPP message that carries the data your billing system, dashboards, and analytics all depend on. Every session generates a stream of MeterValues messages reporting energy delivered, current power, voltage, current, temperature, state of charge, and whatever else the charger is capable of sampling.
It’s also one of the messages where implementations vary the most. Two chargers from different OEMs charging the same vehicle at the same rate may report dramatically different MeterValues — different measurands, different frequencies, different precision. Understanding what’s being reported and how to handle the variation is essential for any team building a CSMS or working with charging data.
This article covers MeterValues in depth: structure, configuration, the common measurands, and the operational issues that cause real-world support tickets. If you’re new to the protocol, start with what OCPP is and how it works; for the message that opens the connection see the BootNotification message (coming soon).
The structure
A MeterValues message in OCPP 1.6:
{
"connectorId": 1,
"transactionId": 12345,
"meterValue": [
{
"timestamp": "2026-06-26T19:15:00Z",
"sampledValue": [
{
"value": "12.45",
"context": "Sample.Periodic",
"format": "Raw",
"measurand": "Energy.Active.Import.Register",
"unit": "kWh"
},
{
"value": "11000",
"context": "Sample.Periodic",
"measurand": "Power.Active.Import",
"unit": "W"
}
]
}
]
}
Two key levels:
- meterValue is a list of “readings at a point in time” — usually one entry per sampling interval, but a single message can carry multiple readings if the charger batches them.
- sampledValue within each reading is the list of measurands sampled at that timestamp. A charger reporting energy + power + voltage + current would have 4 entries.
Each sampledValue has:
- value — the measurement as a string.
- context — what kind of reading is this (more on this below).
- format — Raw (numeric string) or SignedData (cryptographically signed).
- measurand — what’s being measured (default: Energy.Active.Import.Register).
- phase — which electrical phase (L1, L2, L3, N, L1-N, etc.) for AC.
- location — where the measurement was taken (Inlet, Outlet, EV, Body, Cable).
- unit — units (kWh, W, V, A, Celsius, Percent, etc.).
It’s a rich structure that handles a lot of variability. The downside is that two implementations might fill it in differently for the same physical reading.
flowchart TD
MV[MeterValues message] --> R1[meterValue<br/>reading at timestamp]
R1 --> S1[sampledValue<br/>Energy Register]
R1 --> S2[sampledValue<br/>Power Import]
R1 --> S3[sampledValue<br/>Voltage per phase]
S1 --> F[value / context<br/>measurand / unit]
style MV fill:#1e3a5f,stroke:#2b6cb0,color:#fff
The contexts
The context field tells you why this reading exists. Common values:
- Sample.Periodic — routine sampling during a session. Most readings are this.
- Sample.Clock — a reading triggered by the clock (e.g. on-the-minute), independent of transactions.
- Transaction.Begin — the meter reading at the moment the transaction started. Crucial for billing — the end meter minus this is the total energy delivered.
- Transaction.End — the meter reading at the moment the transaction stopped. The other anchor for billing.
- Trigger — a reading sent in response to a TriggerMessage from the CSMS.
- Other — vendor-specific, less common.
The Transaction.Begin and Transaction.End readings are arguably the most important. Periodic samples are nice for dashboards but the billing math anchors on the begin/end pair.
The measurands
OCPP defines a long list of possible measurands. The ones most commonly reported:
Energy.Active.Import.Register — cumulative energy delivered to the EV in this session (or sometimes lifetime), in kWh. This is THE measurand for billing.
Power.Active.Import — instantaneous power being delivered, in W.
Voltage — instantaneous voltage. Often reported per phase for AC.
Current.Import — instantaneous current. Often per phase.
Frequency — grid frequency (50 Hz in Europe, 60 Hz in North America, sometimes notable for power-quality monitoring).
Temperature — temperature in degrees Celsius. Could be the charger body, the inlet, the cable, or vehicle-reported battery temperature depending on location.
SoC (State of Charge) — battery percentage. Only reported when the vehicle communicates this via ISO 15118 (coming soon) or a similar mechanism.
Less common but useful:
Energy.Reactive.Import.Register — reactive power. Relevant for industrial billing.
Power.Reactive.Import — instantaneous reactive power.
Energy.Active.Export.Register / Power.Active.Export — energy/power flowing FROM the vehicle (V2G). Important for bidirectional charging.
RPM — only relevant for vehicles with reported drivetrain data (rare).
Distance — vehicle odometer if reported.
Different chargers report different subsets. A budget AC charger might only report Energy.Active.Import.Register. A modern DC fast charger might report 10-15 measurands per sample.
Sampling configuration
The CSMS controls how often MeterValues fire via configuration variables. Key ones in OCPP 1.6:
- MeterValueSampleInterval — how often to sample during a session, in seconds. 0 means “don’t sample periodically.” Typical values: 30, 60, 300.
- MeterValuesSampledData — comma-separated list of measurands to sample. E.g. “Energy.Active.Import.Register,Power.Active.Import,Voltage,Current.Import”.
- ClockAlignedDataInterval — how often to send Sample.Clock readings (independent of any transaction), in seconds. 0 means “don’t.”
- MeterValuesAlignedData — what to sample for Sample.Clock readings.
- StopTxnSampledData — what to sample at transaction end.
In OCPP 2.0.1 the names are slightly different (and tied into the device model) but the concepts are equivalent.
You set these via SetConfiguration (1.6) or SetVariables (2.0.1). The defaults vary by hardware OEM — some come with sensible defaults, others come with everything off and you have to configure from scratch.
Sample intervals: what to choose
A practical guide.
For pure billing: 60 seconds is plenty. Some operators use 300 seconds. You really only need begin + end + occasional snapshots for trust/verification.
For real-time dashboards: 10-30 seconds. Users watching their session in an app want to see the energy counter tick up.
For high-frequency analytics (rare): 1-5 seconds. Only useful for engineering analysis or grid services.
For demand-response participation: depends on the program. Some require 1-second granularity, some accept 1-minute.
Faster sampling = more bandwidth, more storage, more processing. For a fleet of 1000 chargers each sampling every 10 seconds for 8 hours of charging per day, you’re generating ~3 million sample events per day. Storage and indexing add up.
A sensible default: 60-second periodic samples plus Transaction.Begin and Transaction.End. Adjust upward only when there’s a specific need.
The phase tagging issue
For AC three-phase chargers, MeterValues should report per-phase data. The phase field accepts:
- L1, L2, L3 — individual line phases.
- N — neutral.
- L1-N, L2-N, L3-N — line-to-neutral measurements.
- L1-L2, L2-L3, L3-L1 — line-to-line measurements.
Some chargers report per-phase voltage and current; others only report aggregate / line-to-neutral; some report L1 only and treat it as representative.
For accurate per-phase analytics (power factor, phase balance), you need per-phase data. Many operators discover late that their charger reports only aggregate and they can’t reconstruct what they wanted.
When evaluating hardware, ask about per-phase reporting if you care.
The OCPP 2.0.1 TransactionEvent model
OCPP 2.0.1 changed how meter data flows. Instead of separate MeterValues messages during a transaction, meter readings are embedded inside TransactionEvent messages of type “Updated”:
{
"eventType": "Updated",
"transactionInfo": { "transactionId": "abc123" },
"timestamp": "2026-06-26T19:15:00Z",
"meterValue": [ ... same structure as 1.6 ... ]
}
The data is identical; the message envelope is different. The benefit of the 2.0.1 approach is unification — all transaction-related events (start, update, end, energy data) flow through one message type. The downside is that legacy code transitioning from 1.6 needs to rewire its MeterValues handler.
OCPP 2.0.1 also adds:
- Aligned data can flow through TransactionEvent or a separate MeterValuesRequest (the standalone message still exists for non-transactional readings).
- Tariff information can flow alongside meter data, useful for in-session display of cost.
Common operational issues
A list of things that go wrong with MeterValues in production.
Missing measurands. Charger doesn’t report what you expected. Fix at provisioning: set MeterValuesSampledData explicitly per charger model.
Cumulative energy resets. Some chargers reset their session energy counter at session start (giving you 0 at begin, X at end). Some report a lifetime accumulator (so begin is 12000.0, end is 12012.4). Don’t assume one or the other; use both Transaction.Begin and Transaction.End anchors.
Sampling stops mid-session. Charger disconnects from the network, no MeterValues for several minutes, then they resume. The data has gaps. Your billing must handle this — use Transaction.End as ground truth, not the sum of periodic samples. (See Heartbeat and the connection lifecycle (coming soon) for how these drops are detected.)
Time precision varies. Some chargers send timestamps to the millisecond. Some only to the second. Don’t depend on sub-second precision for ordering.
Inconsistent units. Spec says kWh for energy and W for power. Some chargers send Wh or kW instead. Validate units on ingest; convert if needed.
Phase aggregation confusion. Charger reports L1 only, you assume it’s three-phase aggregate. Your per-phase analytics are wrong.
Pre-paid metering issues. Some chargers have an internal “pre-paid” counter that drifts from the actual meter. If you bill on the OCPP-reported value, you may overpay or underpay. For pre-paid charger fleets, special handling needed.
Vendor-specific extensions. Some OEMs add custom measurands not in the standard. Your CSMS should accept and ignore unknown measurands gracefully rather than crashing.
Auditability and regulated markets
In jurisdictions with calibration/measurement regulation (Germany’s MessEV, similar elsewhere), MeterValues need to be:
- Sourced from a calibrated meter inside the charger.
- Reported with the SignedData format for tamper-evidence.
- Accompanied by appropriate metadata (calibration date, public key, etc.).
OCPP’s SignedData support handles this. The implementation complexity is significant — you need a proper PKI for the charger’s signing key, you need the back-end to validate signatures, you need the audit trail to be queryable for regulatory inspection.
For non-regulated markets, regular Raw-format MeterValues are sufficient. Don’t add SignedData complexity unless you need it.
Storage and analytics design
If you’re building a CSMS, the data design around MeterValues matters.
Hot store: recent samples (last 30 days), indexed for per-session queries. PostgreSQL with proper indexes is fine here.
Warm store: aggregated daily/hourly summaries by charger, by session, by tariff. Often a separate analytical store (ClickHouse, BigQuery, Snowflake).
Cold store: raw samples archived for compliance and rare audit needs. S3 or similar object store.
Don’t keep raw samples forever in your hot store. A 100-charger fleet with 30-second sampling generates ~10 GB per year of raw sample data. A 10,000-charger fleet is 1 TB+ per year. Aggregate, archive, expire.
Use time-series databases for monitoring. InfluxDB, TimescaleDB, or similar are well-suited for the high-write, time-indexed nature of meter samples.
The honest summary
MeterValues is one of the messages where the spec is fine and the implementation variation is large. Plan for charger-to-charger differences in what’s reported and how often. Anchor billing on Transaction.Begin and Transaction.End, treat periodic samples as supplementary data for dashboards and analytics. Set sample intervals based on your actual operational needs, not maximum granularity. Most “billing is wrong” tickets in EV charging trace back to MeterValues handling — invest in this layer carefully.