Model vt_tray and the stage fields, and use them
## vt_tray closes a hole in the filament decision
`filament_source_for` used to refuse a project that wanted the external
spool, on a printer that had an AMS, purely because there was no way to see
what the spool held - and claiming a match without looking is how the
`use_ams: false` bug happened. Now that `vt_tray` is on `PrintReport`, the
question can be answered: `external_spool_satisfies` reuses `map_to_trays`
with the spool as the single candidate tray, so an `Ok` means every filament
matched it.
The decision is now: the AMS first when there is one, because a project
sliced for the AMS should use the AMS even if the spool happens to hold the
same filament; the spool if nothing in the AMS fits and the spool does; and
an error otherwise, reporting the AMS mismatch rather than the spool one
since that names what was wanted against what was loaded. A reported spool
that does not match is a real negative now and is refused.
One case stays unverifiable: a v2 printer reporting neither AMS trays nor
`vt_tray`. v2 omits the key entirely, so absence means "not reported", not
"empty". That one is logged as an unverified assumption instead of being
passed off as a match - and it is the same reason `EXTERNAL_SPOOLS` is still
a constant. Counting reported spools would give 0 on exactly the printer the
ams_mapping schema says has two.
Tests use the real P1P capture's own spool (PETG P9599a25 in D3C5A3FF): it
satisfies a project asking for that filament, and does not satisfy one asking
for the AMS's blue GFG99. Those two were indistinguishable before.
## stage reaches the cloud
`stg_cur`/`stg` are modelled, and `stage` is now on `PrinterTelemetry`,
through `continuum-schemas` and the ts-types mirror. Without it progress is
ambiguous in a way that reads as a fault: on the confirmed P1P run `percent`
climbed to 14% while `layer` stayed 0 for minutes, the printer walking
`stg_cur` 54 -> 14 -> 13 -> 0 before laying the first layer. A console
showing 14% at layer 0 with no stage looks stuck.
A zero stage means "printing" and has its own test on both sides, because
`skip_serializing_if = "Option::is_none"` plus one `unwrap_or_default` is all
it takes to turn "printing" into "no stage".
The backend test asserts a *wrongly typed* stage is rejected, not just that a
valid one is accepted: `PrinterTelemetrySchema` permits unknown properties -
verified - so `{ stage: 54 }` validates whether or not the field is declared.
The negative is what fails if someone removes it.
The live capture fixture is left alone. It predates the field, the field is
optional, and editing a capture by hand would defeat the point of having one.
This commit is contained in:
@@ -131,6 +131,9 @@ fn empty_telemetry_fields_are_omitted_not_null() {
|
||||
name: "P1P".into(),
|
||||
state: Some("IDLE".into()),
|
||||
percent: Some(0),
|
||||
// An idle printer is in no stage, so this is the second field
|
||||
// after `job_name` that must be omitted rather than nulled.
|
||||
stage: None,
|
||||
layer: Some(0),
|
||||
total_layers: Some(0),
|
||||
remaining_minutes: Some(0),
|
||||
@@ -151,6 +154,38 @@ fn empty_telemetry_fields_are_omitted_not_null() {
|
||||
// the absence of "null" alone would pass on an empty object.
|
||||
assert!(json.contains(r#""state":"IDLE""#));
|
||||
assert!(json.contains(r#""bed_temp":22.40625"#));
|
||||
assert!(!json.contains("stage"), "an absent stage should not appear at all");
|
||||
}
|
||||
|
||||
/// A stage of 0 means "printing" and must survive the wire, because
|
||||
/// `skip_serializing_if = "Option::is_none"` on an `Option<i32>` is one
|
||||
/// `unwrap_or_default` away from turning "printing" into "no stage". The
|
||||
/// distinction matters: a printer at 14% and layer 0 is mid-bed-levelling if
|
||||
/// the stage is 54 and genuinely stuck if it is 0.
|
||||
#[test]
|
||||
fn a_zero_stage_is_sent_rather_than_treated_as_absent() {
|
||||
let msg = ClientMessage::Telemetry {
|
||||
printers: vec![PrinterTelemetry {
|
||||
printer_id: "p1p".into(),
|
||||
name: "P1P".into(),
|
||||
state: Some("RUNNING".into()),
|
||||
percent: Some(14),
|
||||
stage: Some(0),
|
||||
layer: Some(1),
|
||||
total_layers: Some(150),
|
||||
remaining_minutes: Some(39),
|
||||
job_name: Some("cube-test".into()),
|
||||
nozzle_temp: Some(255.2),
|
||||
nozzle_target: Some(255.0),
|
||||
bed_temp: Some(79.3),
|
||||
bed_target: Some(80.0),
|
||||
print_error: Some(0),
|
||||
active_alerts: Some(0),
|
||||
}],
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&msg).expect("should serialize");
|
||||
assert!(json.contains(r#""stage":0"#), "a zero stage must be on the wire, got:\n {json}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user