Skip to main content

codetics_ocpp201/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)
3
4//! OCPP 2.0.1 protocol messages (Edition 4).
5//!
6//! Pure Rust definitions of every OCPP 2.0.1 message. Type names are the
7//! specification datatype names without their trailing `Type`
8//! (e.g. `ChargingProfileType` → [`ChargingProfile`]).
9//!
10//! - [`enums`], [`datatypes`], [`messages`]: payload types with serde mappings
11//!   and [`ocpp_core::Validate`] impls for length / range / cardinality constraints.
12//! - [`actions`]: [`Action`] with message direction, and the typed
13//!   [`StationCall`] / [`CsmsCall`] dispatch enums.
14
15pub mod actions;
16pub mod datatypes;
17pub mod enums;
18pub mod messages;
19
20pub use actions::*;
21pub use datatypes::*;
22pub use enums::*;
23pub use messages::*;
24
25/// WebSocket subprotocol of this version.
26pub const SUBPROTOCOL: &str = "ocpp2.0.1";
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn decode_spec_boot_notification() {
34        let c = StationCall::decode("BootNotification", r#"{"reason": "PowerUp", "chargingStation": {"model": "SingleSocketCharger", "vendorName": "VendorX"}}"#).unwrap();
35        let StationCall::BootNotification(b) = c else {
36            panic!()
37        };
38        assert_eq!(b.reason, BootReasonEnum::PowerUp);
39        assert_eq!(b.charging_station.vendor_name, "VendorX");
40    }
41
42    #[test]
43    fn custom_data_is_extensible() {
44        let p = r#"{"customData":{"vendorId":"com.mycompany.customheartbeat","mainMeterValue":12345,"sessionsToDate":342}}"#;
45        let StationCall::Heartbeat(h) = StationCall::decode("Heartbeat", p).unwrap() else {
46            panic!()
47        };
48        let cd = h.custom_data.unwrap();
49        assert_eq!(cd.vendor_id, "com.mycompany.customheartbeat");
50        assert_eq!(cd.extra["sessionsToDate"], 342);
51        // ...but other objects reject unknown properties.
52        assert!(StationCall::decode("Heartbeat", r#"{"foo":1}"#).is_err());
53    }
54
55    #[test]
56    fn transaction_event_and_min_items() {
57        let ok = r#"{"eventType":"Started","timestamp":"2024-05-01T10:00:00Z","triggerReason":"Authorized","seqNo":0,
58            "transactionInfo":{"transactionId":"tx-1"},"idToken":{"idToken":"ABC","type":"ISO14443"},"evse":{"id":1,"connectorId":1},
59            "meterValue":[{"timestamp":"2024-05-01T10:00:00Z","sampledValue":[{"value":12.5}]}]}"#;
60        assert!(StationCall::decode("TransactionEvent", ok).is_ok());
61        let empty_samples = ok.replace(r#"[{"value":12.5}]"#, "[]");
62        let Err(DecodeError::Invalid(v)) = StationCall::decode("TransactionEvent", &empty_samples)
63        else {
64            panic!()
65        };
66        assert_eq!(v.kind, ocpp_core::ViolationKind::Occurrence);
67        assert!(
68            v.message.contains("$.meterValue[0].sampledValue"),
69            "{}",
70            v.message
71        );
72    }
73
74    #[test]
75    fn data_transfer_any_data() {
76        assert!(
77            StationCall::decode("DataTransfer", r#"{"vendorId":"x","data":{"a":[1,2]}}"#).is_ok()
78        );
79        assert!(StationCall::decode("DataTransfer", r#"{"vendorId":"x","data":"text"}"#).is_ok());
80    }
81}
82
83/// Compiles and runs the examples of the README.
84#[cfg(doctest)]
85#[doc = include_str!("../README.md")]
86pub struct ReadmeDoctests;