An app that simulates an electric vehicle speaking ISO 15118-2 and -20 — the SupportedAppProtocol handshake, the -2/-20 charging sessions, Plug & Charge, signed metering, the -20 TLS/PKI — against a real SECC counterpart (a Raspberry Pi charging station). The physical layer a real car uses to get onto the wire — plug-in, SLAC, PLC — is replaced by a QR scan over WLAN: one code stands in for plug-in + SLAC + SDP and carries the endpoint, transport and crypto profile the session needs.
It is a teaching, debugging and security-research tool: it shows the exact EXI bytes on the wire, the
canonical fragment a signature is computed over, the certificate chain being validated — the parts of
ISO 15118 nobody can normally see. See docs/CONCEPT.md for the full feasibility
study and design, and docs/roadmap.md for status.
Not the ISO 15118 stack. The stack itself — the EXI codec, SLAC, SDP, V2GTP, the V2G PKI, and since 2026-08-08 the EVCC/SECC session state machines and the CLI that runs them — lives in the
WWCP_ISO15118submodule and is documented there (libs/WWCP_ISO15118/README.md). This repository is everything built on top of it to make an EV simulator: the app, the shells, the pairing, and the language ports.
git submodule update --init --recursive # WWCP_ISO15118, Hermod, Styx, DynamicQRCodes
bash libs/WWCP_ISO15118/tools/download-schemas.sh # the ISO schemas are ISO's, not shipped here
dotnet build EVSimulatorApp.slnx # the C# side: bridge, pairing, Pi, codegenThe ISO schemas are gitignored — running download-schemas.sh fetches them and is you accepting ISO's
licence; the source generators need them present under libs/WWCP_ISO15118/**/Schemas/.
The WebView app is its own npm package:
cd app && npm install && npm test && npm run buildThe suites that hold the language back ends to the C# side. One script defines them, so a person and CI cannot end up running different things:
bash tools/port-gates.sh| Gate | What it needs |
|---|---|
kotlin |
a JDK. Uses the wrapper in kotlin/ — not a gradle on the PATH |
swift |
a Swift toolchain, so macOS in practice. Skipped, loudly, where there is none |
typescript · app |
Node. No install step |
capacitor |
Node, and an npm ci the script runs itself |
Named gates run alone: bash tools/port-gates.sh kotlin typescript.
None of them needs the ISO schemas — every codec is generated and checked in, and so are the
vectors. download-schemas.sh is for the C# build above.
And they need nothing above this repository either. The session corpus lives in
vectors/, here, so a checkout of EVSimulatorApp on its own passes its own suite. It
used to live in the conformance repository that carries this one as libs/EVSimulatorApp, and the
ports read it back up as ../../ISO15118ConformanceTests.Simulation/Vectors/ — a submodule
depending on its own superproject, which cost fifty-odd failures in a standalone checkout and a
two-checkout layout in CI. The regenerators there now write down into vectors/ instead.
Both repositories run these on push
(.github/workflows/port-gates.yml).
The EV simulator
| Path | What it is |
|---|---|
app/ |
The WebView UI — scan, confirmation sheet, session inspector (its own README + npm package) |
shell/, capacitor/ |
The native iOS/Android Capacitor shells the WebView runs inside |
pairing/ |
QR pairing (the "virtual plug") and EVSimulatorApp.Pi, the Raspberry-Pi SECC counterpart |
bridge/, tools/EVSimulatorApp.WsBridge |
The bridge that carries frames and events between the WebView and the session/Pi |
The ISO 15118 stack it drives
| Path | What it is |
|---|---|
libs/WWCP_ISO15118 |
Submodule. The whole ISO 15118 stack: the EXI codec, SLAC, SDP, V2GTP, the V2G PKI builder (incl. its "Evil" cert factory), the -2/-20 EVCC and SECC state machines with their SLAC/SDP/TLS/SAP front stages, and the CLI that drives either role |
libs/Hermod, libs/Styx |
Submodules. Supporting libraries; WWCP_ISO15118_SLAC reaches for them as siblings under libs/ |
libs/DynamicQRCodes |
Submodule. The AFIR / OCPP v2.1 dynamic-QR (TOTP) mechanism the pairing code is built on |
simulation/EVSimulatorApp.Ocpp |
A stub of a different protocol, reached through the stack's ISessionBackend seam — where a station reports what it delivered. In real operation a real OCPP project hangs here |
experiments/ |
Post-quantum-crypto experiment (ML-KEM / ML-DSA) — wire-non-conformant, flagged as such |
The native ports — so the session runs on the device with no .NET runtime
| Path | What it is |
|---|---|
kotlin/, swift/ |
The codec, XMLDSig, V2GTP and dispatch — and the EVCC state machines, certificates, keystore, metering, pairing and a live runner over a real socket |
typescript/ |
The codec, XMLDSig and the JSON-LD pass. No state machines: a browser cannot open a TCP socket, so this one decodes and replays |
tools/EVSimulatorApp.Codegen |
The generator that emits the codecs by retargeting the C# source generator — bash {kotlin,swift,typescript}/regenerate.sh |
Each port is held byte-for-byte to the same vector corpus the C# codec is, and to the checked-in JSON-LD documents; regenerating without an emitter change must leave every file identical. The state machines are held one layer up, to whole sessions recorded by the C# side.
The phone is the car. It is never the station, and that is the design. All three ports carry the vehicle side only, and no
Seccof any kind — the station role has its own home here and it is C# on a Raspberry Pi (pairing/EVSimulatorApp.Pi). Every←SECCrow of the conformance repository's interop matrix is therefore out of scope on the phone by construction, not by omission. What is planned:docs/mobile-workplan.md.
They are not here — they wrap this repository. The ISO15118ConformanceTests repository carries EVSimulatorApp as a submodule and holds the interop suite that runs this stack against independent stacks (Josev, EVerest, EVDriveFlow, TuxEVSE). That is the harness that proves the simulator behaves the way the standard and the field expect.
The C# throughout — this repository's own projects and the WWCP_ISO15118 codec submodule
(libs/WWCP_ISO15118/LICENSE) — is AGPL-3.0, as are the generated
Kotlin/Swift/TypeScript ports, whose header the code generator emits. Check the header of the
file you are looking at.