FAQ

Common questions about the iso15118 Rust crate: what sans-I/O means in practice, which ISO 15118 parts are covered, TLS, no_std, DIN 70121, and how it compares to OpenV2G, Josev and EVerest.

Short answers to the questions that come up most. The rest of the documentation goes deeper — start with Getting started or the architecture.

What is ISO 15118?

ISO 15118 is the digital communication protocol between an electric vehicle and a charging station over the CCS cable — the one that carries authentication, the charging schedule and the control loop, as opposed to the analogue control pilot that only signals current limits. Part 2 (2014) is the first-generation application layer, part 20 (2022) is the second, and part 3 covers bringing the powerline link up. It is what makes Plug & Charge and bidirectional power transfer possible.

What does sans-I/O actually mean here?

That no type in the crate opens a socket, reads a clock, or spawns a task. You call handle_input(now, &bytes), then poll_event() to see what happened, then take_transmit() for bytes to write, then poll_timeout() to know when to call back. The transport, the clock, the randomness and the cryptography are yours.

The payoff is that the same protocol logic runs under Tokio on a charge-point back end and on a bare-metal vehicle controller, and that a complete DC charging session runs as a unit test in microseconds with time as a variable.

Which parts of ISO 15118 are implemented?

ISO 15118-2:2014 (all 34 body messages), ISO 15118-20:2022 (CommonMessages, AC, DC, WPT and ACDP), and ISO 15118-3 (SLAC frames and the matching state machine for both roles). SECC discovery and V2GTP framing are covered too, along with the Plug & Charge signature profile.

DIN SPEC 70121 is recognised in the handshake so a charger can decline it explicitly, but its message set is not implemented — the schemas are behind a paywall. Everything below the message set is generation-agnostic and public, so a consumer with its own DIN codec can keep the handshake, the framing and the timers; see what is not here.

How do I record which generation a session spoke?

Protocol::as_str() — "din70121", "iso15118-2", "iso15118-20". It is stable API, Display prints it, FromStr reads it back, and the serde feature uses it too, so a charge-detail record written under one release parses under the next. Protocols is the set form and round-trips the same way as one comma-separated token.

Every name says which generation. That is deliberate: DATEX II's VehicleToGridCommunicationTypeEnum, carried in the feed a European operator publishes for AFIR compliance, spells its literal iso15118, defines it in the AFIR namespace as ISO 15118-20, and has no literal for ISO 15118-2 at all. A bare "iso15118" therefore does not parse here, and the error says why rather than guessing.

What is the difference between `Protocol` and `Protocols`?

Protocol is what a session negotiated — one generation, the outcome of the supportedAppProtocol handshake. Protocols is a set of them in preference order: what a vehicle offers, what a station accepts, and what a piece of equipment implements.

The second is a different question and often the one that matters legally: a regulation binds a charge point by what it implements and when it was installed, entirely independently of what any session negotiated. Protocols is Copy, allocation-free and const-constructible, so it costs nothing to keep one on an ECU or in a datasheet record.

Does it do TLS?

No, and it will not. A sans-I/O engine consumes bytes; where they came from is not its business. Bringing your own means rustls on a server and embedded-tls on a controller without this crate choosing for either.

What is enforced is the part that is protocol rather than transport: Protocol::Iso20::requires_tls() is true, and SECC discovery refuses a security downgrade rather than silently accepting one.

Can I run it on a microcontroller?

Yes. The protocol core builds for thumbv7em-none-eabihf with std off, and CI checks that on every change. You need an allocator — alloc is a requirement rather than a feature, because in bit-packed EXI a decoded string is a run of bit-shifted code points and can never borrow from the input.

Set max_payload_len to what you can actually hold rather than leaving the one-mebibyte server default, and enable only the message sets you speak.

How is the EXI codec verified?

Against a third implementation, not against itself. Every derived grammar — 298 of them — is diffed against exificient, the EXI reference implementation. Every one of the 121 message types is generated as a schema-valid instance, encoded by that reference implementation as a document and as a signature fragment, then decoded and re-encoded byte for byte by the generated codec.

Round-tripping an encoder through its own decoder proves only that the two agree with each other.

On top of that, 3538 messages from captured charging sessions — dSPACE equipment against Josev and RiseV2G — decode and re-encode byte for byte. A reference implementation says whether the codec is right; a captured session says whether the field can read it.

Why does strict mode matter so much?

ISO 15118 transmits no EXI options document, so every coding option is out-of-band knowledge. The one implementations get wrong is that fidelity is the default — strict is false.

Non-strict grammars carry extra productions for content the schema does not declare. Those productions cost no bits of their own, but their existence widens every first-level event code: a grammar state with one declared production needs one bit non-strict and zero strict. Every element in every message is shifted.

How does it compare to OpenV2G, Josev and EVerest?

OpenV2G is C, unmaintained, and ISO 15118-2 only. Josev is reference-quality Python — excellent for understanding the semantics, but unusable on a vehicle controller, and a garbage-collection pause inside a DC charge loop is a fault. EVerest's libiso15118 is modern and was actively developed, but it is C++, coupled to the EVerest runtime, and its EXI codec is generated C.

What this crate adds is not the same thing in Rust: one codebase that runs on both a microcontroller and a back end, a wire format checked against somebody else's implementation, and the message-ordering rules — including the ones about failing — as shared data rather than as prose re-derived on each side.

Is Plug & Charge complete?

Nearly. The signature profile is there — building, verifying, the algorithm restrictions, and the EXI-fragment canonicalisation that makes a signature interoperable — and so is the PKI: X.509 chain validation under ISO 15118's Annex F certificate profiles, and the encrypted contract-key envelope of CertificateInstallationRes.

What is missing is revocation. A validated chain says a certificate was issued, not that it was not withdrawn.

Do I need the ISO schemas to build it?

No. The generated message sets are committed, so building or using the crate never touches the schemas. You only need them to regenerate the codec, and scripts/fetch-schemas.sh downloads the XSDs — which ISO publishes freely — reproducibly.

The spec texts are paid documents, and nothing in the build depends on having them.

Is it production ready?

It is pre-1.0, so the API still changes — the minor version is the breaking one. The protocol work is real: the message sets are generated from the official schemas and verified against a third implementation, the session layer implements the ordering rules of both generations including the failure and pause rules, and there is no unsafe anywhere.

The gap that should decide it for you is certificate revocation: chains validate — signatures, validity windows, path length, key usage, the ISO 15118 Annex F profiles — but nothing here asks whether a certificate was withdrawn. That answer belongs in the back end your station already talks to, and the crate hands you the EMAID to ask about.

What licence is it under?

Dual-licensed MIT or Apache-2.0, at your option, per Rust ecosystem convention. No part of the ISO standard is redistributed with it.