1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
//! Strongly typed OCPP message types for 1.6J, 2.0.1, and 2.1.
//!
//! `ocpp-types` provides the request/response payload types for the Open
//! Charge Point Protocol, generated from the official JSON schemas. It is
//! `no_std` and allocation-free: field sizes are bounded at the type level
//! with [`heapless`] collections, sized to the limits stated in each
//! version's specification.
//!
//! Each protocol version lives in its own module -- [`v16`], [`v201`],
//! [`v21`] -- since the same message name can differ in shape across
//! versions.
//!
//! # 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 become a const
//! generic the caller picks (with a default, so most code never needs to
//! think about it):
//!
//! ```
//! # #[cfg(not(feature = "alloc"))]
//! # {
//! use ocpp_types::v16::DataTransferResponse;
//! use ocpp_types::v16::common::DataTransferResponseStatus;
//!
//! // Uses the default capacity (1024):
//! let response: DataTransferResponse = DataTransferResponse {
//! data: Some(heapless::String::try_from("vendor payload").unwrap()),
//! status: DataTransferResponseStatus::Accepted,
//! };
//!
//! // Or pick a smaller one explicitly:
//! let response: DataTransferResponse<64> = DataTransferResponse {
//! data: Some(heapless::String::try_from("vendor payload").unwrap()),
//! status: DataTransferResponseStatus::Accepted,
//! };
//! # }
//! ```
//!
//! With the `alloc` feature enabled, these fields become plain
//! `alloc::string::String`/`alloc::vec::Vec<T>` instead, 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.
//!
//! # Timestamps
//!
//! Every version types its `dateTime` fields as `{"type": "string",
//! "format": "date-time"}` without a `maxLength`, so they would fall under
//! the rule above and reserve 1024 bytes each. They are [`OcppTimestamp`]
//! instead: 16 bytes, no const generic, no allocator, and comparable --
//! which a string is not.
//!
//! ```
//! use ocpp_types::{OcppTimestamp, v16::HeartbeatResponse};
//!
//! let response = HeartbeatResponse {
//! current_time: OcppTimestamp::parse_rfc3339("2024-01-01T00:00:00Z").unwrap(),
//! };
//!
//! assert_eq!(response.current_time.unix_seconds(), 1_704_067_200);
//! ```
//!
//! Enable the `chrono` feature for `From`/`Into` conversions with
//! `chrono::DateTime`. That is interop only -- chrono is never on the wire
//! path, since its own serde support formats through an allocating
//! `to_rfc3339`, which the no-`alloc` build cannot use.
//!
//! # Sizing for allocation-free targets
//!
//! With `default-features = false` every field is stored inline at its
//! declared capacity, so a message's `size_of` is the sum of what it *could*
//! hold, not what it does. Most of the protocol is small under that rule --
//! the median message is a few hundred bytes and around 85% are under 4 KB --
//! but a few families reserve far more, and those need their capacities
//! named rather than defaulted.
//!
//! Every capacity below is a const generic with a default, so nothing has to
//! be specified to compile; specifying them is how a station trades unused
//! headroom for stack space.
//!
//! | If your station does | Set | Because the default is |
//! | --- | --- | --- |
//! | Smart charging | `chargingSchedulePeriod`, `chargingProfile` caps | 8 each, and they nest three deep |
//! | V2X / bidirectional | `v2xFreqWattCurve`, `v2xSignalWattCurve` | 8 points each, on every schedule period |
//! | ISO 15118-20 pricing | `priceRuleStacks`, `priceLevelScheduleEntries`, `salesTariffEntry` | 8 each; set to 0 if unused |
//! | Tariffs (2.1) | `energyPrices`, `timePrices`, `fixedPrices` | 8 each, per tariff kind |
//! | Plug and Charge | `certificate`, `certificateChain`, `csr`, `signingCertificate` | 1024 -- *too small for a real PEM chain* |
//! | Signed metering | `signedMeterData` | 1024; the spec allows 32768 |
//! | Local auth lists | `localAuthorizationList` | 8 entries |
//! | Metering | `meterValue`, `sampledValue` | 8 each, and they multiply |
//!
//! Two of these are worth calling out for opposite reasons. The Plug and
//! Charge fields are the only ones whose default is deliberately *too small*:
//! a PEM chain will not fit in 1024 bytes, so a deployment that uses
//! certificates must raise them and will discover this immediately in
//! testing. Erring the other way would have cost every deployment that never
//! sees a certificate. Conversely, capacities you set to `0` cost nothing at
//! all, which is the cheapest way to exclude a feature you do not implement.
//!
//! As a worked example, `ReportChargingProfilesRequest` defaults to 530 KB.
//! A station that advertises `PeriodsPerSchedule = 8`, reports one profile at
//! a time, and implements neither V2X nor ISO 15118-20 pricing compiles the
//! same message at 18 KB by naming those capacities.
//!
//! The specification expects this: `SmartChargingCtrlr.PeriodsPerSchedule` is
//! a required 2.x variable and 1.6 has `ChargingScheduleMaxPeriods`, so every
//! station already declares its own limits. Compiling in the capacity you
//! advertise is conformant; reserving the protocol ceiling you will never
//! accept is merely large.
//!
//! # Fields the spec leaves untyped
//!
//! 2.0.1 and 2.1's `DataTransfer` carries a `data` field the specification
//! deliberately 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 the caller's to pick, as a
//! type parameter defaulting to `()` (i.e. "this deployment sends no
//! data"):
//!
//! ```
//! use ocpp_types::v201::DataTransferRequest;
//!
//! // Whatever this vendor agreed on; add `serde::Serialize`/`Deserialize`
//! // to send it on the wire.
//! #[derive(Debug, Clone, PartialEq)]
//! struct VendorPayload {
//! session_id: u32,
//! }
//!
//! let request: DataTransferRequest<VendorPayload> = DataTransferRequest {
//! custom_data: None,
//! data: Some(VendorPayload { session_id: 42 }),
//! message_id: None,
//! vendor_id: heapless::String::try_from("com.example").unwrap(),
//! };
//!
//! // Or, sending no vendor payload at all, the default:
//! let plain: DataTransferRequest = DataTransferRequest {
//! custom_data: None,
//! data: None,
//! message_id: None,
//! vendor_id: heapless::String::try_from("com.example").unwrap(),
//! };
//! ```
//!
//! 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.
//!
//! # Example
//!
//! ```
//! use ocpp_types::Action;
//! use ocpp_types::v16::{AuthorizeRequest, IdTag};
//!
//! let request = AuthorizeRequest {
//! id_tag: IdTag::try_from("ABC123").unwrap(),
//! };
//!
//! assert_eq!(AuthorizeRequest::ACTION, "Authorize");
//! ```
//!
//! # Serialization
//!
//! With the `serde` feature enabled, every message implements
//! `serde::Serialize`/`serde::Deserialize`, and [`Action`] gains
//! zero-allocation JSON helpers backed by
//! [`serde-json-core`](https://docs.rs/serde-json-core) -- the caller owns
//! the buffer, nothing is heap-allocated:
//!
//! ```ignore
//! use ocpp_types::Action;
//!
//! let mut buf = [0u8; 256];
//! let json: &str = request.to_json_str(&mut buf)?;
//! let parsed = AuthorizeRequest::from_json_str(json)?;
//! ```
//!
//! # RPC errors
//!
//! Each version also exposes an `RpcErrorCode` enum covering the
//! `CALLERROR` codes defined by that version's OCPP-J specification (e.g.
//! [`v16::RpcErrorCode`]), implementing [`core::error::Error`].
//!
//! # WebSocket envelopes
//!
//! With `serde`, `Call`/`CallResult`/`CallError` model the OCPP-J
//! array-based envelope every message travels in (`[2, messageId, action,
//! payload]`, etc.) -- generic over the payload type, so no per-version
//! duplication is needed. `CallResultError`/`SendMessage` cover 2.1's
//! additional `CALLRESULTERROR`/`SEND` message types (the shapes work for
//! any version; whether a given deployment actually uses them is a
//! protocol-level concern, not something the types enforce). See
//! `examples/envelope.rs`.
extern crate alloc;
pub use Action;
pub use ;
pub use NoCustomData;
pub use MessageId;
pub use ;