Add pushall + print_status cache; fix envelope, race, and matching bugs

PrintReport now flattens CommandEnvelope (sequence_id/command) -
push_status.schema.json composes the shared envelope like every other
Bambu message; the struct's old doc comment concluded otherwise from a
capture that turned out to be an incomplete example (also missing dozens
of other real fields), not proof the message lacks them.

Added BambuMqttSession's PrintStatusCache: the in-memory
print.push_status object, replaced wholesale on v2 (always full) or on a
v1 message with msg != 1 (forced/full), merged field-by-field otherwise
(v1's organic partial updates) via PrintReport::merge_from. Tracks
last_sequence_id to warn on a gap in the organic stream. BambuPrinter
gets pushall() (v1-only; pushing.pushall doesn't exist in v2's schema at
all) and print_status().

Three real bugs found and fixed via live testing against an actual P1P,
none of them guessable from the schema alone:
- pushall's reply doesn't echo the sequence_id we send - the printer
  stamps its own free-running counter regardless of what triggered it.
  request()'s ID correlation silently can't work here; first fix attempt
  added request_next_of_category (wait for the next message under a
  category, no ID needed).
- That undershot too: matching by category alone let an unrelated
  "print" message (that category carries many commands - gcode_line,
  pause, push_status, ...) satisfy the wait before the real push_status
  reply arrived, so pushall() returned Ok having consumed the wrong
  message - now keyed by (category, command).
- A real ordering race: handle_incoming_report woke a waiting oneshot
  before updating the print_status cache, so a caller could resume and
  check print_status() before the write landed - pushall() returned Ok
  immediately followed by print_status() still showing None. Fixed by
  applying the cache update before sending on any matched oneshot.

Superseded by real protocol knowledge before any of the above needed to
ship, though: push_status carries its own msg field (1 = automatic/
partial, 0 = forced/full) - the actual, reliable, self-describing signal
for which kind a message is, regardless of timing or which client (if
any) triggered it. PrintStatusCache now reads that directly instead of
inferring it from request correlation, and pushall() is plain fire-and-
forget (publish, no wait) since there's nothing left to correlate for -
confirmed live: msg reads correctly off a real P1P, and the cache
populates/updates correctly with no gap warnings across several organic
pushes. request_next_of_category is kept (a future case may still need
"no correlation ID at all" for real), just no longer used here.

Also: the poll loop now logs when it exits instead of returning silently
- that silence is exactly what made a real dropped MQTT connection to a
live H2C indistinguishable from "nothing happening" during testing.
This commit is contained in:
2026-09-07 14:39:55 +00:00
parent 835a4ae778
commit 6ce60542c6
4 changed files with 452 additions and 86 deletions
+51
View File
@@ -402,6 +402,57 @@ impl BambuPrinter {
serde_json::from_value(response)
.map_err(|err| PrinterError(format!("failed to parse get_version response: {err}")))
}
/// Bambu-only, same reasoning as `get_version` above. Publishes
/// `pushing.pushall`, which - unlike `get_version` - doesn't reply
/// under its own category: it triggers/refreshes the ongoing
/// `print.push_status` stream instead. Fire-and-forget, same as
/// `send_gcode_line`: there's nothing to wait for here, since the
/// forced full update this triggers self-describes as such via its
/// own `msg` field once it arrives (see `PrintStatusCache::apply`),
/// whenever that turns out to be - waiting on it added two real bugs
/// (a race, and matching the wrong "print" message) for no benefit
/// once that was understood.
///
/// Call this periodically for v1 printers specifically - v2 has no
/// `pushing` category at all (checked: `schemas/bambulab/v2/
/// request.schema.json` only wires camera/info/print/system/upgrade/
/// xcam). Whether v2 needs some *other* trigger to get
/// `print.push_status` flowing at all is still an open question (a
/// live test meant to check that lost its connection entirely, an
/// unrelated failure, not real evidence either way) - if it does,
/// that's a different method to add once identified, not a variant
/// of this one. A v2 connection makes this call a no-op rather than
/// an error, since `pushing.pushall` specifically is confirmed not to
/// exist for it regardless.
pub async fn pushall(&self) -> Result<(), PrinterError> {
let (session, version) =
self.connection.as_ref().ok_or_else(|| PrinterError(format!("{} is not connected", self.base.name)))?;
if matches!(version, BambuProtocolVersion::V2) {
return Ok(());
}
let mut body = continuum_schema_instance::build_instance_from_file(&get_schema_for_command(
*version,
"pushing".into(),
"pushall".into(),
))
.map_err(|err| PrinterError(format!("failed to build pushall request: {err}")))?;
body["push_target"] = serde_json::Value::from(1);
session.publish("pushing", &body).await
}
/// The current in-memory `print.push_status` object - `None` until at
/// least one has arrived (which, on v1, requires having called
/// `pushall` at least once since connecting; v2 pushes on its own).
pub async fn print_status(&self) -> Option<super::PrintReport> {
match self.connection.as_ref() {
Some((session, _)) => session.print_status().await,
None => None,
}
}
}
impl GenericPrinter for BambuPrinter {