Skip to main content

envelope/
envelope.rs

1//! OCPP-J WebSocket envelopes: `CALL`, `CALLRESULT`, `CALLERROR`.
2//!
3//! Run with: `cargo run --example envelope -p ocpp-types --features serde`
4
5use ocpp_types::v16::{AuthorizeRequest, HeartbeatResponse, IdTag, RpcErrorCode};
6use ocpp_types::{Call, CallError, CallResult, EmptyPayload, MessageId};
7
8fn main() {
9    // A CALL wraps a request with a correlation id. The wire's "Action"
10    // string is derived from `AuthorizeRequest::ACTION`, not stored
11    // redundantly -- and validated against it when parsing one back.
12    let call = Call {
13        message_id: MessageId::try_from("19223201").unwrap(),
14        payload: AuthorizeRequest {
15            id_tag: IdTag::try_from("ABC123").unwrap(),
16        },
17    };
18
19    let mut buf = [0u8; 256];
20    let len = serde_json_core::to_slice(&call, &mut buf).unwrap();
21    let json = core::str::from_utf8(&buf[..len]).unwrap();
22    println!("CALL:        {json}");
23
24    let (parsed, _): (Call<AuthorizeRequest>, usize) =
25        serde_json_core::from_slice(&buf[..len]).unwrap();
26    assert_eq!(parsed, call);
27
28    // A CALLRESULT correlates back to the CALL by `message_id` alone --
29    // there's no Action on the wire for it, since the receiver already
30    // knows (from tracking its own outstanding CALLs) what kind of
31    // response to expect.
32    // One type in both builds: `dateTime` fields are `OcppTimestamp`, not a
33    // capacity-parameterized string.
34    let current_time =
35        ocpp_types::OcppTimestamp::parse_rfc3339("2024-01-01T00:00:00Z").unwrap();
36
37    let result: CallResult<HeartbeatResponse> = CallResult {
38        message_id: MessageId::try_from("19223201").unwrap(),
39        payload: HeartbeatResponse { current_time },
40    };
41
42    let mut buf = [0u8; 256];
43    let len = serde_json_core::to_slice(&result, &mut buf).unwrap();
44    println!("CALLRESULT:  {}", core::str::from_utf8(&buf[..len]).unwrap());
45
46    // A CALLERROR carries the version's own RpcErrorCode. `errorDetails`
47    // defaults to `EmptyPayload` ({}), since the spec leaves it
48    // deliberately undefined in shape.
49    let error: CallError<RpcErrorCode> = CallError {
50        message_id: MessageId::try_from("19223201").unwrap(),
51        error_code: RpcErrorCode::NotImplemented,
52        error_description: heapless::String::try_from("unrecognized action").unwrap(),
53        error_details: EmptyPayload,
54    };
55
56    let mut buf = [0u8; 256];
57    let len = serde_json_core::to_slice(&error, &mut buf).unwrap();
58    println!("CALLERROR:   {}", core::str::from_utf8(&buf[..len]).unwrap());
59
60    // A frame with the wrong MessageTypeId or a mismatched Action is
61    // rejected outright, not silently accepted.
62    let wrong_type: Result<(Call<AuthorizeRequest>, usize), _> =
63        serde_json_core::from_str(r#"[3,"1","Authorize",{"idTag":"ABC123"}]"#);
64    println!(
65        "a CALLRESULT-shaped frame parsed as Call<T> is rejected: {}",
66        wrong_type.is_err()
67    );
68}