From 416ef6db07c853d43aea223fb2984b89c2801294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Sat, 5 Sep 2026 21:27:59 +0000 Subject: [PATCH] Add BambuPrinter::get_version, the first real request/response round trip Documents request()'s real constraint on BambuMqttSession directly (not all Bambu commands map 1:1 to a reply, and even where one exists its shape can differ by protocol version - e.g. pushing.pushall triggers an ongoing print.push_status stream rather than a direct reply, and print.calibration data lives inside push_status on BambuMQTT v1 but gets its own report on v2 - so request() is only correct for genuinely one-shot, sequence_id-correlated commands like info.get_version). get_version itself is Bambu-only (no GenericPrinter equivalent, matching test_tls_handshake/fetch_certificate/ensure_trusted's existing pattern): builds the request via continuum-schema-instance, sends it through BambuMqttSession::request, and parses the reply into the existing hand-written GetVersionResponse - reusing the struct already validated against real captured Bambu JSON in bambu_commands_demo.rs, rather than introducing a second response representation. Verified structurally (no real broker reachable from here - confirmed this sandbox has no route into the printer's LAN at all, not just a blocked port): connect fails gracefully against an unreachable host, and get_version correctly reports "not connected" rather than panicking when called without a live session. --- src/printer/bambu.rs | 27 +++++++++++++++++++++++++++ src/printer/bambu_mqtt.rs | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/printer/bambu.rs b/src/printer/bambu.rs index cfaf931..9e186cf 100644 --- a/src/printer/bambu.rs +++ b/src/printer/bambu.rs @@ -343,6 +343,33 @@ impl BambuPrinter { Err(err) => Err(err), } } + + /// Bambu-only - no `GenericPrinter` equivalent, since every vendor's + /// way of reporting firmware info (if it has one at all) is shaped + /// completely differently. Same "vendor-only extension" pattern as + /// `test_tls_handshake`/`fetch_certificate`/`ensure_trusted` above: + /// reached by matching `PrinterHandle::Bambu` back out of the enum, + /// not through the shared trait. + /// + /// The first real use of `BambuMqttSession::request` - + /// `info.get_version` is a genuine one-shot, `sequence_id`-correlated + /// reply (unlike `pushing.pushall`, see `request`'s doc comment), so + /// this is exactly the case it's for. + pub async fn get_version(&self) -> Result { + let (session, version) = + self.connection.as_ref().ok_or_else(|| PrinterError(format!("{} is not connected", self.base.name)))?; + + let body = continuum_schema_instance::build_instance_from_file(&get_schema_for_command( + *version, + "info".into(), + "get_version".into(), + )) + .map_err(|err| PrinterError(format!("failed to build get_version request: {err}")))?; + + let response = session.request("info", body).await?; + serde_json::from_value(response) + .map_err(|err| PrinterError(format!("failed to parse get_version response: {err}"))) + } } impl GenericPrinter for BambuPrinter { diff --git a/src/printer/bambu_mqtt.rs b/src/printer/bambu_mqtt.rs index 16eb7a7..a39fb7c 100644 --- a/src/printer/bambu_mqtt.rs +++ b/src/printer/bambu_mqtt.rs @@ -162,6 +162,27 @@ impl BambuMqttSession { /// `body` already had (continuum-schema-instance fills it with an /// empty placeholder, since it has no way to know a real one) with a /// fresh one from this session's own counter. + /// + /// Only correct for commands that genuinely get *one direct, + /// sequence_id-correlated reply* - `info.get_version` is the + /// canonical example (the envelope schema says as much: "Sequence ID + /// matching the original request"). It is NOT a general "send a + /// command, get the response" primitive - Bambu's own mapping isn't + /// 1:1: + /// - `pushing.pushall` doesn't reply at all - it triggers an *ongoing* + /// `print.push_status` stream, unrelated to this request's + /// `sequence_id`. Use `publish` (fire-and-forget) for it, the same + /// way `send_gcode_line` already does for `gcode_line`. + /// - Even where a field genuinely exists, which report carries it can + /// differ *by protocol version*: `print.calibration` data is bundled + /// inside `print.push_status` on BambuMQTT v1, but v2 gives it its + /// own dedicated report. Calling `request` for something like that + /// would just hang until `REQUEST_TIMEOUT` waiting for a reply that + /// was never going to arrive correlated by `sequence_id` - not a + /// crash, but a silent design bug if used on the wrong kind of + /// command. An ongoing-stream subscription (register interest in a + /// *category*, get every report under it as it arrives, not a single + /// `oneshot`) is a separate primitive this doesn't provide yet. pub async fn request(&self, category: &str, mut body: Value) -> Result { let sequence_id = self.next_sequence_id.fetch_add(1, Ordering::Relaxed).to_string(); body["sequence_id"] = Value::String(sequence_id.clone());