# ๐ OCPP Client
> **A lightweight, embedded-friendly Rust OCPP communication framework for building real charge points and CSMS integrations.**
[](https://www.rust-lang.org/)
[](#license)
[](https://crates.io/crates/ocpp-client)
[](https://docs.rs/ocpp-client)
[](https://github.com/flowionab/ocpp-client/actions/workflows/ci.yaml)
[](#features)
---
## ๐ Overview
**OCPP Client** is the communication layer of the **Flowion Rust OCPP ecosystem**, providing the networking and transport foundation required to build OCPP-enabled charge points and backend integrations.
The library handles the complexities of establishing and managing OCPP connections, including:
* Connection lifecycle management
* Transport handling
* Message routing
* Communication reliability
OCPP message types and protocol definitions are provided by [`ocpp-types`](https://github.com/flowionab/ocpp-types), while OCPP Client focuses on the communication layer required to exchange messages between charge points and Charge Station Management Systems (CSMS).
Designed for both cloud/server environments and resource-constrained embedded systems, OCPP Client speaks WebSocket out of the box and compiles for `no_std` + `alloc` targets. An `embassy-net`-based transport and an STM32 board scaffold ship alongside it as **experimental** crates - see [Supported Transports](#-supported-transports).
The library currently supports **OCPP 1.6J**, **OCPP 2.0.1**, and **OCPP 2.1**.
---
## โจ Features
* ๐ฆ Native Rust implementation
* ๐ OCPP communication layer
* โก OCPP **1.6J support**
* ๐ OCPP **2.0.1 support**
* โก OCPP **2.1 support**
* ๐ WebSocket transport
* ๐ Secure WebSocket (WSS)
* ๐ `embassy-net` transport for embedded targets (experimental)
* ๐ Connection lifecycle management
* ๐ Scheduled WebSocket keepalive with dead-peer detection
* ๐จ Message routing
* ๐งฉ Transport abstraction
* ๐ชถ Lightweight runtime
* ๐พ `no_std` support for embedded environments
* ๐ฅ๏ธ `std` support enabled by default for desktop and server applications
---
## ๐ Supported Protocols
| OCPP 1.6J | โ
Supported | all 39 |
| OCPP 2.0.1 | โ
Supported | all 64 |
| OCPP 2.1 | โ
Supported | all 91 |
1.6's 39 includes the eleven actions from the security whitepaper (`SignCertificate`, `GetLog`,
`SignedUpdateFirmware` and friends), wired up in **0.4.0** when `ocpp-types` first defined them.
Every action defined by [`ocpp-types`](https://crates.io/crates/ocpp-types) for each version has a
`send_*`/`on_*` method - `tests/action_coverage.rs` fails the build otherwise, so the table can't
drift. If a method you expect is missing, check [CHANGELOG.md](CHANGELOG.md) before filing an
issue: five actions were only wired up in **0.2.1**, so a 0.2.0 build is missing
`SecurityEventNotification` (2.0.1) and `TriggerMessage`, `SetDisplayMessage`, `GetDERControl`,
`SetDERControl`, `UpdateDynamicSchedule` (2.1).
---
## ๐ Supported Transports
| WebSocket | โ
Supported |
| Secure WebSocket (WSS), incl. mutual TLS | โ
Supported |
| `embassy-net` (embedded, `no_std` + `alloc`) | ๐งช Experimental |
The embedded transport (`crates/ocpp-transport-embassy-net`) and the NUCLEO-H723ZG firmware
scaffold (`crates/ocpp-board-stm32h723-nucleo`) compile and fully link against the real
`thumbv7em-none-eabihf` target in CI, but **neither has been run against real hardware or a real
CSMS**, and the embedded transport has no TLS. Treat them as a starting point for a board bring-up
rather than a supported deployment path. Each crate's README states its exact status.
---
## โ๏ธ Feature Flags
OCPP Client supports both standard Rust environments and embedded systems.
By default, the `std` feature is enabled:
```toml
[dependencies]
ocpp-client = "0.x"
```
For embedded targets or `no_std` environments:
```toml
[dependencies]
ocpp-client = { version = "0.x", default-features = false }
```
This lets the same OCPP communication core compile for resource-constrained devices as well as server-side applications. Embedded users supply their own `Executor`/`Timer` implementations (e.g. backed by `embassy-executor`/`embassy-time`) and a `critical-section` backend for their target.
The optional `chrono` feature adds `From`/`Into` between `ocpp_types::OcppTimestamp` - the type every `dateTime` field uses - and `chrono::DateTime`, for applications that already keep time in chrono. It is interop only; chrono never reaches the wire.
---
## ๐๏ธ Architecture
OCPP Client separates protocol definitions from communication.
```text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Application โ
โ Charge Point / CSMS Logic โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ
โ ocpp-charge-point โ
โ โ
โ Complete charge point firmware framework โ
โ Add hardware bindings and deploy โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ
โ ocpp-client โ
โ โ
โ OCPP communication runtime โ
โ Transport abstraction โ
โ WebSocket / embedded transports โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ
โ ocpp-types โ
โ โ
โ OCPP message types โ
โ Protocol models โ
โ Serialization โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
---
## ๐ Flowion OCPP Ecosystem
OCPP Client is designed as a modular building block within the **Flowion Rust OCPP ecosystem**.
Each project has a focused responsibility, allowing developers to choose the right level of abstraction for their application.
---
### ๐ฆ ocpp-types
**OCPP protocol definitions and data models**
[`ocpp-types`](https://github.com/flowionab/ocpp-types) provides the foundation for working with OCPP messages in Rust.
It contains:
* OCPP message types
* Protocol models
* Serialization and deserialization
* Version-specific protocol definitions
OCPP Client builds on top of `ocpp-types` to provide communication capabilities.
---
### ๐ ocpp-client
**OCPP communication and transport layer**
This repository provides the runtime required to connect OCPP-enabled systems.
It handles:
* Connection management
* Transport abstraction
* Message routing
* WebSocket communication
* Embedded-compatible transports
* `no_std` environments
It is designed to run in both:
* ๐ฅ๏ธ Server environments
* ๐ Embedded charge point environments
---
### โก ocpp-charge-point
**Complete charge point firmware framework**
[`ocpp-charge-point`](https://github.com/flowionab/ocpp-charge-point) provides a complete framework for building OCPP-enabled charge point firmware.
The goal is to make developing custom charging hardware as simple as implementing the required hardware bindings.
Developers provide hardware-specific implementations such as:
* GPIO control
* Contactor control
* Metering interfaces
* Connector handling
* LEDs and user interfaces
* Hardware drivers
while the framework handles:
* Charge point state management
* OCPP communication
* Charging workflows
* Backend communication
* Protocol integration
This allows manufacturers and developers to build custom OCPP-compatible chargers without implementing the complete protocol stack from scratch.
---
## ๐ฏ Use Cases
OCPP Client can be used for:
* ๐ Building EV charge point firmware
* ๐ญ Developing OCPP-enabled hardware
* ๐ฅ๏ธ Building CSMS integrations
* ๐งช Testing OCPP implementations
* ๐ Connecting embedded devices to charging platforms
* โก Creating custom charging solutions
* ๐ค Automated integration testing
---
## ๐ฆ Installation
Add the dependency to your `Cargo.toml`:
```toml
[dependencies]
ocpp-client = "0.x"
```
---
## ๐ Quick Example
```rust
use ocpp_client::connect_1_6;
use ocpp_client::ocpp_types::v16::HeartbeatRequest;
#[tokio::main]
async fn main() {
// `None` takes the defaults: 5s request timeout, automatic reconnect, and keepalive
// pinging every 60s.
let client = connect_1_6("wss://example.com/ocpp", None).await.unwrap();
let response = client.send_heartbeat(HeartbeatRequest {}).await.unwrap();
println!("CSMS time: {}", response.current_time);
}
```
Use `connect_2_0_1`/`connect_2_1` for those versions, or `connect` to negotiate whichever version
the server picks.
### Vendor extensions (`customData`)
2.0.1 and 2.1 hang an optional `customData` object on nearly every message. The `send_*`/`on_*`
methods use the specification's own shape - a bare `vendorId` - which is all most deployments
need. To carry your own, name the type on the action marker and go through `call`/`on`:
```rust,ignore
#[derive(serde::Serialize, serde::Deserialize)]
struct AcmeExtension {
#[serde(rename = "vendorId")]
vendor_id: String,
#[serde(rename = "siteId")]
site_id: u32,
}
let response = client.call::<Reset<AcmeExtension>>(request).await?;
```
`NoCustomData` is the other end of the trade: it accepts whatever a peer sends and discards it,
costing one byte per node instead of the field's full width - worth naming on an MCU.
---
## ๐ Keepalive & `WebSocketPingInterval`
By default a client pings the CSMS every 60 seconds and, after two unanswered pings, drops the
connection and redials. Without this a half-open link - a dropped NAT entry, a mobile connection
that vanished without a FIN - is undetectable: the socket accepts writes and nothing ever comes
back, and reconnect can't help because nothing reports the connection as closed.
```rust
use ocpp_client::{ConnectOptions, KeepaliveBehavior, KeepalivePolicy, connect_1_6};
use std::time::Duration;
let options = ConnectOptions {
keepalive: KeepaliveBehavior::Enabled(KeepalivePolicy {
interval: Duration::from_secs(30),
timeout: None, // fall back to the client's request timeout
max_missed: 2,
}),
..Default::default()
};
let client = connect_1_6("wss://example.com/ocpp", Some(options)).await?;
```
Set `keepalive: KeepaliveBehavior::Disabled` if the CSMS pings the charge point instead, or if the
deployment forbids unsolicited traffic.
This crate does not implement a device model, but it owns the ping timer, so it exposes the value
for the layer that does:
| 2.0.1 / 2.1 | `OCPPCommCtrlr.WebSocketPingInterval` (`GetVariables`/`SetVariables`) | `client.ping_interval()` | `client.set_ping_interval(..)` |
| 1.6 (security whitepaper) | `WebSocketPingInterval` (`GetConfiguration`/`ChangeConfiguration`) | `client.ping_interval()` | `client.set_ping_interval(..)` |
Both are non-`async`, so a `GetVariables` handler can call them directly. `None` maps to the
spec's `0` (disabled) in both directions, writes take effect immediately rather than after the
current interval finishes, and a write can enable pinging on a client that started with keepalive
disabled.
---
## ๐งช Testing
OCPP Client is designed for:
* Integration testing
* Charge point development
* Embedded testing
* CSMS validation
* Automated test environments
It can be combined with simulators and real charging hardware to validate complete OCPP workflows.
---
## ๐ฃ๏ธ Roadmap
Planned improvements:
* ๐ Additional embedded transports
* ๐ More examples
* ๐งช Expanded integration tests
* ๐ง Improved developer tooling
---
## ๐ค Contributing
Contributions are welcome!
You can help by:
* ๐ Reporting issues
* ๐ก Suggesting improvements
* ๐ Improving documentation
* ๐ง Submitting pull requests
---
## ๐ License
OCPP Client is dual licensed:
* MIT License
* Apache License 2.0
You may choose either license.
---
## ๐ข About Flowion
**OCPP Client** is developed by **Flowion AB** as part of our effort to make EV charging development more accessible through modern, open-source tooling.
Flowion builds software solutions for electric vehicle charging using open standards such as **OCPP**, helping developers and businesses build reliable and scalable charging infrastructure.
---
## โญ Support the Project
If you find this library useful:
* โญ Star the repository
* ๐ Report issues
* ๐ก Suggest improvements
* ๐ค Contribute
Together we can make EV charging development easier and more accessible.