Certification
The 203 abstract test cases of the four High-Level Test Specifications as data, a coverage number from cargo test, and the runtime signals a laboratory reads off a device.
Certification for LPC, LPP, MPC and MGCP is a paid laboratory booking. What the laboratory runs is published: each use case has a High-Level Test Specification listing its abstract test cases — an identifier, the requirements it covers, which actor is under test, and whether it is mandatory.
This crate carries all 203 of them as data, drives every one of them, and prints what that adds up to.
The catalogue
use eebus::conformance::{self, Coverage};
use eebus::usecases::descriptor::{Support, actors, names};
let mandatory: Vec<_> = conformance::for_actor(names::LPC, actors::CONTROLLABLE_SYSTEM)
.filter(|tc| tc.level == Support::Mandatory)
.collect();
let claimed = ["ATC_LPC_COM_PT_CSTransition5_001"];
let report = Coverage::of(mandatory.iter().copied(), &claimed);
println!("{}%", report.percent());Each AbstractTestCase carries the specification's own description, the [LPC-TS-…] requirements it covers, and the conditions that raise an optional marking to mandatory — "if the device is black-start capable", "if the unlimited/autonomous state is implemented", "if the phase is one the device actually operates on". A device's parameter sheet is those conditions answered, so the same list tells a manufacturer what it has committed to.
A coverage number from cargo test
tests/conformance.rs drives this crate through the abstract test cases' own steps, one test per identifier, and prints what it adds up to:
$ cargo test --features full --test conformance -- --nocapture coverage
LPC ControllableSystem 39/43 90%
LPC EnergyGuard 5/8 62%
LPP ControllableSystem 39/43 90%
LPP EnergyGuard 5/8 62%
MPC MonitoredUnit 20/20 100%
MPC MonitoringAppliance 34/34 100%
MGCP GridConnectionPoint 18/18 100%
MGCP MonitoringAppliance 29/29 100%
total 189/203 93%The fourteen missing are exactly the seven device-level test cases, counted once for LPC and once for LPP; a test holds the gap to that set, so the number moves neither by dropping a case nor by relabelling one. The Energy Guard's 62 % is the same arithmetic, not a shortfall: three of its eight cases are about a device rebooting. Each is listed with the reason:
Not covered here, and why (the same list for LPP):
CSConnection_006 what the appliance actually draws, which the library reports a
ceiling for and does not control
CSConnection_009 powering the device off and on again
CSInit_002 a factory reset and the defaults it restores are the device's
CSInit_003 where values are stored across a power cut is the device's decision
EGConnection_001 the test rebuilds the actor rather than rebooting a deviceEach is an obligation on the integrator. conformance::device_level() yields them with their reasons, so a harness that drives a real device — one that can cut its power and press its reset — can iterate exactly those cases, filtered by actor and by what the parameter sheet commits to. The suite asserts the list matches the number.
use eebus::conformance;
use eebus::usecases::descriptor::actors;
for (case, reason) in conformance::device_level()
.filter(|(case, _)| case.dut == actors::CONTROLLABLE_SYSTEM)
{
println!("{}: {}\n because {reason}", case.id, case.description);
}The catalogue is behind the default-on conformance feature, which a build with default-features = false has to name.
What a passing suite still cannot see
Both ends of every test in this repository are this crate, and that hides a whole class of defect: anything where the two sides agree on a convention the specification left to the device. Identifiers are the sharp edge. limitId, the failsafe keyIds, MGCP's curtailment key and a car's per-phase limits are all placeholders — <l1#(1..1)>, <k1#(1..1)>, <x1>…<x3> — and a client that assumes its own numbering writes a well-formed message to a real entry of the peer's and is acknowledged — and every abstract test case still passes.
Two suites cover it. tests/interop.rs runs against a live eebus-go peer, and tests/foreign_identifiers.rs builds a Controllable System by hand that numbers everything differently — its limit on 7, its failsafe keys on 5 and 6, and both directions on one LoadControl feature so that limitId 1 is the production limit an LPC guard must not touch. Neither is a conformance test; both catch what conformance cannot.
The report prints one softer case alongside them: the library notifies a change the moment the application publishes it (SPINE IG §2.4), but whether the measured value reaches it inside the 120 seconds MPC and MGCP allow is the application's own publish cadence.
The tester hooks
Every High-Level Test Specification carries the same footnote: "the manufacturer must specify conditions on how the test case can be tested (e.g. via debug interface)." Half of the LPC transitions are timers — a duration expiring, a heartbeat that stops — and nothing goes on the wire when one fires, so a tester that can only see the wire cannot tell unlimited/controlled from failsafe state.
usecases::signals is that interface, as a shape rather than a transport:
use eebus::usecases::signals::Signals;
use eebus::usecases::lpc;
println!("{}", controllable_system.signals(lpc::DIRECTION));lpc:state limited
lpc:limit 3000 W
lpc:duration 600 s
lpc:isActive true
lpc:failsafeLimit 4200 W
lpc:failsafeDuration 7200 s
lpc:nominalMax 11000 W
lpc:contractualMax -
lpc:effectiveLimit 3000 W
lpc:powerCeiling 3000 W
lpc:lastHeartbeat 11 s
lpc:nextDeadline 130 sThree details are deliberate:
- The state is spelled the way §2.3.2 spells it —
unlimited/controlled, notUnlimitedControlled— because a laboratory compares it against the specification and against theCF_CS_…pre-conditions, not against Rust. - An unset value is
-, never0. A tester reading nought watts for a limit that was never set would record a device limited to zero, which is the opposite of unlimited. nextDeadlineis there because the timed transitions are exactly what the wire cannot show.ATC_LPC_COM_PT_CSTransition5_001waits 120 seconds for one.
A Monitored Unit reports the same way under mpc: or mgcp:, and a measurand whose value state is not normal reports the state instead of a number — which is the whole subject of the NT_ test cases in those two specifications.
What this does not claim
A passing test here does not mean a pass in the laboratory — only that the rules are the ones the laboratory checks, under the same names, and that what is not checked is written down.