OCPP Types
A strongly typed Rust implementation of the Open Charge Point Protocol (OCPP) message model.
ocpp-types provides the request/response payload types for OCPP 1.6J, 2.0.1, and 2.1, generated
from the official JSON schemas. It's no_std and, by default, allocation-free: field sizes are
bounded at the type level with heapless collections, sized to the
limits stated in each version's specification.
The crate is designed to be lightweight and reusable across embedded firmware, Linux-based charge points, simulators, CSMS implementations, and tooling.
Supported Versions
| Version | Status |
|---|---|
| OCPP 1.6J | ✅ Available |
| OCPP 2.0.1 | ✅ Available |
| OCPP 2.1 | ✅ Available |
Each version lives in its own module (v16, v201, v21), since the same message name can
differ in shape across versions.
Example
use Action;
use ;
let request = BootNotificationRequest ;
assert_eq!;
Every field that OCPP bounds with a maxLength/maxItems is sized exactly to that bound (a
heapless::String<20>, not an arbitrary String), so a value that doesn't fit fails at
construction (try_into()/try_from()), not somewhere downstream at serialization time.
Runnable examples
crates/ocpp-types/examples/ has complete, runnable programs for
the topics on this page:
Serialization
With the serde feature enabled, every message implements Serialize/Deserialize, and the
[Action] trait gains zero-allocation JSON helpers backed by
serde-json-core — the caller owns the buffer, nothing is
heap-allocated:
use Action;
let mut buf = ;
let json: &str = request.to_json_str?;
let parsed = from_json_str?;
to_json_slice/from_json_slice are also available for working with raw bytes instead of &str.
Fields with no spec-given bound
A handful of fields (free-text strings, a few arrays) have no maxLength/maxItems in the spec,
so there's no size to give a heapless collection without guessing one. These expose a const
generic parameter the caller can pick, defaulting to a reasonable size (1024 for strings, 16 for
arrays) so most code never has to think about it:
use HeartbeatResponse;
// Uses the default capacity:
let response: HeartbeatResponse = HeartbeatResponse ;
// Or pick a smaller one explicitly:
let response: = HeartbeatResponse ;
With the alloc feature enabled instead, these fields become plain alloc::string::String /
alloc::vec::Vec<T>, and the const generic disappears entirely — useful on targets with a real
allocator (a CSMS backend, a simulator) that would rather not pick a bound at all.
Fields the spec leaves untyped
2.0.1 and 2.1's DataTransfer carries a data field the specification gives no type at all —
"open to implementation", agreed between the two parties. There's no single Rust type for arbitrary
JSON without an allocator, so the payload type is yours to pick, as a type parameter defaulting to
() ("this deployment sends no data"):
use DataTransferRequest;
let request: = DataTransferRequest ;
1.6J's DataTransfer.data is a plain string in that version's schema, so it stays
Option<heapless::String<N>> and needs no parameter.
RPC errors
Each version also exposes an RpcErrorCode enum covering the CALLERROR codes defined by that
version's OCPP-J specification, implementing core::error::Error. These aren't identical across
versions — 2.0.1/2.1 renamed FormationViolation to FormatViolation, fixed a spelling error in
Occur(r)enceConstraintViolation, and added two new codes — so each version's is its own type,
not shared:
use RpcErrorCode;
let error = NotImplemented;
println!; // "Requested Action is not known by receiver"
WebSocket envelopes
With the serde feature, Call/CallResult/CallError model the OCPP-J array-based envelope
every message travels in — generic over the payload type, so there's one definition covering every
version rather than one per version:
use ;
use ;
let call = Call ;
let mut buf = ;
let len = to_slice?;
// [2,"19223201","Authorize",{"idTag":"ABC123"}]
The wire's "Action" string comes from AuthorizeRequest::ACTION, not a redundant stored field —
and is validated against it when parsing a Call<T> back, so a Call<AuthorizeRequest> you get out
really is one. CallResultError/SendMessage cover OCPP 2.1's additional CALLRESULTERROR/SEND
message types. See crates/ocpp-types/examples/envelope.rs.
Feature flags
| Feature | Default | Effect |
|---|---|---|
serde |
off | Serialize/Deserialize on every type, plus [Action]'s JSON helpers. |
alloc |
off | Fields with no spec-given bound become alloc collections instead of const-generic heapless ones. Combine with serde to also serialize them. |
Without any features, the crate has exactly one dependency: heapless.
What this crate does
- OCPP request/response types for 1.6J, 2.0.1, and 2.1
- Common/shared data structures and enums, deduplicated per version
serdeserialization, including OCPP-JCALLERRORerror codes and theCALL/CALLRESULT/CALLERROR(and 2.1'sCALLRESULTERROR/SEND) WebSocket envelopes- Doc comments carried over from the spec's own field/message descriptions
What this crate does NOT do
This crate intentionally does not implement:
- The actual WebSocket transport (opening/maintaining the connection, framing, reconnects)
- Message routing, charge point state machines, or CSMS logic
- Smart charging algorithms
Those responsibilities belong in higher-level crates built on top of this one.
Ecosystem
ocpp-types is intended as the foundation for a broader Rust OCPP ecosystem — the shared data
model that other crates build transport, state machines, and application logic on top of.
ocpp-types
│
┌───────────┴───────────┐
│ │
ocpp-transport ocpp-charge-point
│ │
└───────────┬───────────┘
│
Applications
| Crate | Purpose | Status |
|---|---|---|
ocpp-types |
Protocol data model | ✅ This crate |
ocpp-transport |
Message framing and transport abstractions | 📋 Planned |
ocpp-charge-point |
Charge Point implementation | 📋 Planned |
charge-point-simulator |
OCPP testing and simulation tools | 📋 Planned |
Related projects
- ocpp-charge-point — a reusable Rust implementation of an OCPP Charge Point.
- rust-ocpp — shared Rust libraries for the Open Charge Point Protocol.
How the types are generated
Nothing in ocpp-types is hand-written except a handful of primitives (IdTag, RpcErrorCode)
that have no equivalent in the JSON schemas. Everything else is generated by ocpp-codegen (a
workspace-internal, unpublished dev tool) from the schemas in schemas/. If a type looks wrong,
the fix belongs in the generator, not in a hand-edit of the generated file — every generated file
says as much at the top, and regenerating overwrites hand-edits anyway.
CI regenerates and diffs on every push, so the committed output can't silently drift from what the schemas and generator would actually produce.
Design principles
- Transport agnostic. No knowledge of WebSockets or networking — this crate just models the protocol's data shapes.
no_stdby default. Works on embedded microcontrollers, Linux-based charge points, cloud services, and desktop simulators alike;allocis opt-in, never required.- Specification-first. Field names, bounds, and doc comments come directly from the OCPP JSON schemas and OCPP-J specifications, not reinterpreted by hand.
License
Licensed under either of
at your option.
Contributing
Contributions are welcome — implementing new message types, fixing specification inconsistencies, adding tests, or improving documentation. See CONTRIBUTING.md for the workflow, since types are generated rather than hand-written. Participation is governed by our Code of Conduct. Found a security issue? See SECURITY.md.