pub mod actions;
pub mod datatypes;
pub mod enums;
pub mod messages;
pub use actions::*;
pub use datatypes::*;
pub use enums::*;
pub use messages::*;
pub const SUBPROTOCOL: &str = "ocpp1.6";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_spec_boot_notification() {
let c = StationCall::decode(
"BootNotification",
r#"{"chargePointVendor": "VendorX", "chargePointModel": "SingleSocketCharger"}"#,
)
.unwrap();
match c {
StationCall::BootNotification(b) => {
assert_eq!(b.charge_point_vendor, "VendorX");
assert_eq!(b.charge_point_model, "SingleSocketCharger");
assert!(b.firmware_version.is_none());
}
other => panic!("{other:?}"),
}
}
#[test]
fn violations_map_to_error_codes() {
use ocpp_core::ViolationKind::*;
let kind = |a: &str, p: &str| match StationCall::decode(a, p) {
Err(DecodeError::Invalid(v)) => v.kind,
other => panic!("{other:?}"),
};
assert_eq!(
kind("BootNotification", r#"{"chargePointVendor":"V"}"#),
Occurrence
);
assert_eq!(
kind(
"BootNotification",
r#"{"chargePointVendor":"V","chargePointModel":"123456789012345678901"}"#
),
Property
);
assert_eq!(kind("Heartbeat", r#"{"x":1}"#), Format);
assert_eq!(
kind(
"StatusNotification",
r#"{"connectorId":"1","errorCode":"NoError","status":"Available"}"#
),
Type
);
assert_eq!(
kind(
"StatusNotification",
r#"{"connectorId":1,"errorCode":"NoError","status":"Broken"}"#
),
Property
);
}
#[test]
fn direction_is_enforced() {
assert!(matches!(
StationCall::decode("Reset", r#"{"type":"Hard"}"#),
Err(DecodeError::WrongDirection(Action::Reset))
));
assert!(matches!(
StationCall::decode("Nope", "{}"),
Err(DecodeError::UnknownAction(_))
));
assert!(matches!(
CsmsCall::decode("Reset", r#"{"type":"Hard"}"#),
Ok(CsmsCall::Reset(_))
));
assert!(StationCall::decode("DataTransfer", r#"{"vendorId":"x"}"#).is_ok());
assert!(CsmsCall::decode("DataTransfer", r#"{"vendorId":"x"}"#).is_ok());
}
#[test]
fn errata_celsius_and_optional_reason() {
let p = r#"{"transactionId":1,"timestamp":"2024-01-01T00:00:00Z","meterStop":10,
"transactionData":[{"timestamp":"2024-01-01T00:00:00Z","sampledValue":[{"value":"20","unit":"Celsius"},{"value":"20","unit":"Celcius"}]}]}"#;
assert!(StationCall::decode("StopTransaction", p).is_ok());
}
#[test]
fn multiple_of_constraint() {
let mk = |limit: &str| {
format!(
r#"{{"connectorId":1,"csChargingProfiles":{{"chargingProfileId":1,"stackLevel":0,"chargingProfilePurpose":"TxDefaultProfile","chargingProfileKind":"Absolute",
"chargingSchedule":{{"chargingRateUnit":"A","chargingSchedulePeriod":[{{"startPeriod":0,"limit":{limit}}}]}}}}}}"#
)
};
assert!(CsmsCall::decode("SetChargingProfile", &mk("8.1")).is_ok());
assert!(CsmsCall::decode("SetChargingProfile", &mk("8.15")).is_err());
}
#[test]
fn response_roundtrip() {
let r = BootNotificationResponse {
status: RegistrationStatus::Accepted,
current_time: ocpp_core::DateTime::parse("2013-02-01T20:53:32.486Z").unwrap(),
interval: 300,
};
let s = serde_json::to_string(&r).unwrap();
assert_eq!(
s,
r#"{"status":"Accepted","currentTime":"2013-02-01T20:53:32.486Z","interval":300}"#
);
assert!(validate_response(Action::BootNotification, &s).is_ok());
}
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;