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. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
This commit is contained in:
@@ -343,6 +343,33 @@ impl BambuPrinter {
|
|||||||
Err(err) => Err(err),
|
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<super::GetVersionResponse, PrinterError> {
|
||||||
|
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 {
|
impl GenericPrinter for BambuPrinter {
|
||||||
|
|||||||
@@ -162,6 +162,27 @@ impl BambuMqttSession {
|
|||||||
/// `body` already had (continuum-schema-instance fills it with an
|
/// `body` already had (continuum-schema-instance fills it with an
|
||||||
/// empty placeholder, since it has no way to know a real one) with a
|
/// empty placeholder, since it has no way to know a real one) with a
|
||||||
/// fresh one from this session's own counter.
|
/// 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<Value, PrinterError> {
|
pub async fn request(&self, category: &str, mut body: Value) -> Result<Value, PrinterError> {
|
||||||
let sequence_id = self.next_sequence_id.fetch_add(1, Ordering::Relaxed).to_string();
|
let sequence_id = self.next_sequence_id.fetch_add(1, Ordering::Relaxed).to_string();
|
||||||
body["sequence_id"] = Value::String(sequence_id.clone());
|
body["sequence_id"] = Value::String(sequence_id.clone());
|
||||||
|
|||||||
Reference in New Issue
Block a user