1pub 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
25pub const SUBPROTOCOL: &str = "ocpp1.6";
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31
32 #[test]
33 fn decode_spec_boot_notification() {
34 let c = StationCall::decode(
35 "BootNotification",
36 r#"{"chargePointVendor": "VendorX", "chargePointModel": "SingleSocketCharger"}"#,
37 )
38 .unwrap();
39 match c {
40 StationCall::BootNotification(b) => {
41 assert_eq!(b.charge_point_vendor, "VendorX");
42 assert_eq!(b.charge_point_model, "SingleSocketCharger");
43 assert!(b.firmware_version.is_none());
44 }
45 other => panic!("{other:?}"),
46 }
47 }
48
49 #[test]
50 fn violations_map_to_error_codes() {
51 use ocpp_core::ViolationKind::*;
52 let kind = |a: &str, p: &str| match StationCall::decode(a, p) {
53 Err(DecodeError::Invalid(v)) => v.kind,
54 other => panic!("{other:?}"),
55 };
56 assert_eq!(
58 kind("BootNotification", r#"{"chargePointVendor":"V"}"#),
59 Occurrence
60 );
61 assert_eq!(
63 kind(
64 "BootNotification",
65 r#"{"chargePointVendor":"V","chargePointModel":"123456789012345678901"}"#
66 ),
67 Property
68 );
69 assert_eq!(kind("Heartbeat", r#"{"x":1}"#), Format);
71 assert_eq!(
72 kind(
73 "StatusNotification",
74 r#"{"connectorId":"1","errorCode":"NoError","status":"Available"}"#
75 ),
76 Type
77 );
78 assert_eq!(
80 kind(
81 "StatusNotification",
82 r#"{"connectorId":1,"errorCode":"NoError","status":"Broken"}"#
83 ),
84 Property
85 );
86 }
87
88 #[test]
89 fn direction_is_enforced() {
90 assert!(matches!(
91 StationCall::decode("Reset", r#"{"type":"Hard"}"#),
92 Err(DecodeError::WrongDirection(Action::Reset))
93 ));
94 assert!(matches!(
95 StationCall::decode("Nope", "{}"),
96 Err(DecodeError::UnknownAction(_))
97 ));
98 assert!(matches!(
99 CsmsCall::decode("Reset", r#"{"type":"Hard"}"#),
100 Ok(CsmsCall::Reset(_))
101 ));
102 assert!(StationCall::decode("DataTransfer", r#"{"vendorId":"x"}"#).is_ok());
103 assert!(CsmsCall::decode("DataTransfer", r#"{"vendorId":"x"}"#).is_ok());
104 }
105
106 #[test]
107 fn errata_celsius_and_optional_reason() {
108 let p = r#"{"transactionId":1,"timestamp":"2024-01-01T00:00:00Z","meterStop":10,
109 "transactionData":[{"timestamp":"2024-01-01T00:00:00Z","sampledValue":[{"value":"20","unit":"Celsius"},{"value":"20","unit":"Celcius"}]}]}"#;
110 assert!(StationCall::decode("StopTransaction", p).is_ok());
111 }
112
113 #[test]
114 fn multiple_of_constraint() {
115 let mk = |limit: &str| {
116 format!(
117 r#"{{"connectorId":1,"csChargingProfiles":{{"chargingProfileId":1,"stackLevel":0,"chargingProfilePurpose":"TxDefaultProfile","chargingProfileKind":"Absolute",
118 "chargingSchedule":{{"chargingRateUnit":"A","chargingSchedulePeriod":[{{"startPeriod":0,"limit":{limit}}}]}}}}}}"#
119 )
120 };
121 assert!(CsmsCall::decode("SetChargingProfile", &mk("8.1")).is_ok());
122 assert!(CsmsCall::decode("SetChargingProfile", &mk("8.15")).is_err());
123 }
124
125 #[test]
126 fn response_roundtrip() {
127 let r = BootNotificationResponse {
128 status: RegistrationStatus::Accepted,
129 current_time: ocpp_core::DateTime::parse("2013-02-01T20:53:32.486Z").unwrap(),
130 interval: 300,
131 };
132 let s = serde_json::to_string(&r).unwrap();
133 assert_eq!(
134 s,
135 r#"{"status":"Accepted","currentTime":"2013-02-01T20:53:32.486Z","interval":300}"#
136 );
137 assert!(validate_response(Action::BootNotification, &s).is_ok());
138 }
139}
140
141#[cfg(doctest)]
143#[doc = include_str!("../README.md")]
144pub struct ReadmeDoctests;