Replace proto-based bambu_protocol with plain-Rust bambu_commands/
One file per command (get_version.rs, pushall.rs), each holding its request AND response together. CommandEnvelope (sequence_id + command) is the shared-fields piece every command embeds via #[serde(flatten)] — the 'inheritance' replacement for these structs, same composition idea as BambuGenericPrinter in bambu.rs, just expressed as a struct field instead of impl-block delegation. examples/bambu_commands_demo.rs round-trips both commands against Bambu's exact real JSON (from the original conversation) and confirms byte-for- byte matches, same as the removed proto version did — this replacement is behaviorally identical, just without protoc/build.rs in the loop.
This commit is contained in:
+2
-2
@@ -30,8 +30,8 @@ name = "auto_connect_bambu"
|
|||||||
path = "examples/auto_connect_bambu.rs"
|
path = "examples/auto_connect_bambu.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "bambu_protocol_demo"
|
name = "bambu_commands_demo"
|
||||||
path = "examples/bambu_protocol_demo.rs"
|
path = "examples/bambu_commands_demo.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.40", features = ["full"] }
|
tokio = { version = "1.40", features = ["full"] }
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! Run with: cargo run --example bambu_commands_demo
|
||||||
|
//!
|
||||||
|
//! Round-trips both commands against the exact JSON from Bambu's real
|
||||||
|
//! protocol — proves the plain hand-written struct + serde approach
|
||||||
|
//! matches byte for byte, same as the proto-based version did, with none
|
||||||
|
//! of the build.rs ceremony.
|
||||||
|
|
||||||
|
use continuum_proxy::printer::{CommandEnvelope, GetVersionRequest, GetVersionResponse, PushallRequest};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// --- get_version request ---
|
||||||
|
let req = GetVersionRequest {
|
||||||
|
envelope: CommandEnvelope { sequence_id: "0".to_string(), command: "get_version".to_string() },
|
||||||
|
};
|
||||||
|
let actual = serde_json::to_string(&req).unwrap();
|
||||||
|
let expected = r#"{"sequence_id":"0","command":"get_version"}"#;
|
||||||
|
println!("get_version request: {actual}");
|
||||||
|
println!("matches Bambu's shape: {}\n", actual == expected);
|
||||||
|
|
||||||
|
// --- get_version response, deserialized from Bambu's real payload ---
|
||||||
|
let real_response = r#"{"command":"get_version","module":[{"hw_ver":"","name":"ota","sn":"","sw_ver":"01.01.01.00"},{"hw_ver":"","name":"xm","sn":"","sw_ver":"00.00.00.00"}],"sequence_id":"0"}"#;
|
||||||
|
let parsed: GetVersionResponse = serde_json::from_str(real_response).unwrap();
|
||||||
|
println!("get_version response parsed fine, no result/reason present: {parsed:#?}\n");
|
||||||
|
|
||||||
|
// --- pushall request ---
|
||||||
|
let pushall = PushallRequest {
|
||||||
|
envelope: CommandEnvelope { sequence_id: "0".to_string(), command: "pushall".to_string() },
|
||||||
|
version: 1,
|
||||||
|
push_target: 1,
|
||||||
|
};
|
||||||
|
let actual = serde_json::to_string(&pushall).unwrap();
|
||||||
|
let expected = r#"{"sequence_id":"0","command":"pushall","version":1,"push_target":1}"#;
|
||||||
|
println!("pushall request: {actual}");
|
||||||
|
println!("matches Bambu's shape: {}", actual == expected);
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//! Run with: cargo run --example bambu_protocol_demo
|
|
||||||
//!
|
|
||||||
//! Proves the whole pipeline end to end: a message written in
|
|
||||||
//! continuum-schemas' proto/bambulab/v1/request.proto, compiled by prost
|
|
||||||
//! into a real Rust struct, wrapped in the hand-written BambuRequest enum,
|
|
||||||
//! and serialized by plain serde — no protobuf binary encoding involved,
|
|
||||||
//! since Bambu's actual wire format is JSON over MQTT.
|
|
||||||
|
|
||||||
use continuum_proxy::printer::BambuRequest;
|
|
||||||
use continuum_types::bambulab::v1::InfoCommand;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let request = BambuRequest::Info(InfoCommand {
|
|
||||||
sequence_id: "0".to_string(),
|
|
||||||
command: "get_version".to_string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let actual = serde_json::to_string(&request).unwrap();
|
|
||||||
let expected = r#"{"info":{"sequence_id":"0","command":"get_version"}}"#;
|
|
||||||
|
|
||||||
println!("generated: {actual}");
|
|
||||||
println!("Bambu's real wire format: {expected}");
|
|
||||||
println!("match: {}", actual == expected);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use super::CommandEnvelope;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
pub struct GetVersionRequest {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub envelope: CommandEnvelope,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct GetVersionResponse {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub envelope: CommandEnvelope,
|
||||||
|
/// Not always present — `Option<T>` handles that natively, no
|
||||||
|
/// `#[serde(default)]` needed (unlike the proto-generated version this
|
||||||
|
/// replaced, which needed one explicit `field_attribute` call per
|
||||||
|
/// field like this).
|
||||||
|
pub result: Option<String>,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub module: Vec<ModuleInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct ModuleInfo {
|
||||||
|
pub hw_ver: String,
|
||||||
|
pub name: String,
|
||||||
|
pub sn: String,
|
||||||
|
pub sw_ver: String,
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//! Bambu's raw MQTT command/report structs — plain hand-written Rust +
|
||||||
|
//! serde, no protobuf. This data only ever needs to be understood by this
|
||||||
|
//! crate (raw vendor wire format, never crosses into TypeScript), and the
|
||||||
|
//! actual wire format is JSON over MQTT, not binary protobuf — protobuf's
|
||||||
|
//! codegen tooling kept fighting us for a payoff (cross-language codegen,
|
||||||
|
//! binary wire efficiency) that doesn't apply here at all. Proto stays in
|
||||||
|
//! continuum-schemas for continuum.v1, which is a schema we designed
|
||||||
|
//! ourselves and that both TS and Rust genuinely need.
|
||||||
|
//!
|
||||||
|
//! One file per command (`get_version.rs`, `pushall.rs`, ...) — each holds
|
||||||
|
//! its request AND response together, since they're conceptually one unit.
|
||||||
|
//! Copy `get_version.rs`'s shape for your next command.
|
||||||
|
|
||||||
|
mod get_version;
|
||||||
|
mod pushall;
|
||||||
|
|
||||||
|
pub use get_version::{GetVersionRequest, GetVersionResponse, ModuleInfo};
|
||||||
|
pub use pushall::{PushallRequest, PushallResponse};
|
||||||
|
|
||||||
|
/// Fields every Bambu command/response shares. This is the "inheritance"
|
||||||
|
/// replacement for these structs — composition, not extension: every
|
||||||
|
/// command below *has* one of these, embedded via `#[serde(flatten)]`
|
||||||
|
/// (which unwraps it onto the parent object at serialize/deserialize
|
||||||
|
/// time, so the JSON stays flat — no extra nesting), rather than
|
||||||
|
/// *extending* a base class. Exactly the same idea as `BambuGenericPrinter`
|
||||||
|
/// in `../bambu.rs`, just expressed with a struct field instead of a
|
||||||
|
/// struct-holding-a-struct in `impl` blocks.
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct CommandEnvelope {
|
||||||
|
pub sequence_id: String,
|
||||||
|
pub command: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One request you can send to a Bambu printer, keyed by Bambu's top-level
|
||||||
|
/// JSON category ("info", "pushing", ...). NOTE: that top-level key is a
|
||||||
|
/// *category*, not the specific command — `CommandEnvelope::command` is
|
||||||
|
/// what actually says which one ("get_version", "pushall", ...). Right
|
||||||
|
/// now each category maps to exactly one known command, one-to-one. If a
|
||||||
|
/// category ever needs a second command (e.g. "info" gaining something
|
||||||
|
/// besides get_version), that variant's payload becomes its own small
|
||||||
|
/// `#[serde(tag = "command")]` enum instead of a single struct — ask if
|
||||||
|
/// you get there and want a worked example.
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum BambuRequest {
|
||||||
|
Info(GetVersionRequest),
|
||||||
|
Pushing(PushallRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same idea, the response direction.
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum BambuReport {
|
||||||
|
Info(GetVersionResponse),
|
||||||
|
Pushing(PushallResponse),
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use super::CommandEnvelope;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
pub struct PushallRequest {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub envelope: CommandEnvelope,
|
||||||
|
pub version: i32,
|
||||||
|
pub push_target: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct PushallResponse {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub envelope: CommandEnvelope,
|
||||||
|
pub result: Option<String>,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
// The real pushall/print report has a lot more fields (ams, xcam,
|
||||||
|
// tray_exist_bits, ...) — add them here the same way, one field per
|
||||||
|
// line, `Option<T>` for anything that isn't always present.
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
//! Bambu's real MQTT wire format: `{"info": {...}}`, `{"pushing": {...}}`,
|
|
||||||
//! `{"print": {...}}` — one JSON object with a single key naming which
|
|
||||||
//! command/report it is.
|
|
||||||
//!
|
|
||||||
//! Deliberately NOT modeled as a proto `oneof` for the top level — see the
|
|
||||||
//! long comment in continuum-schemas/packages/rust-types/build.rs for why
|
|
||||||
//! (short version: prost-build's field/variant attribute matching can't
|
|
||||||
//! keep "the oneof field" and "the variants inside it" apart, so
|
|
||||||
//! `#[serde(flatten)]` can't be scoped correctly and won't compile). This
|
|
||||||
//! plain Rust enum sidesteps the problem entirely: serde's *default* enum
|
|
||||||
//! representation already produces exactly `{"info": {...}}` with zero
|
|
||||||
//! attributes needed.
|
|
||||||
//!
|
|
||||||
//! `Info` below is the one fully-wired example — run
|
|
||||||
//! `cargo run --example bambu_protocol_demo` to see it actually produce
|
|
||||||
//! Bambu's real wire JSON. Copy this exact shape for every other command:
|
|
||||||
//! 1. Write the message in continuum-schemas' bambulab/v1/request.proto
|
|
||||||
//! (or report.proto for a response).
|
|
||||||
//! 2. `use` it up top, same as `InfoCommand` below.
|
|
||||||
//! 3. Add one variant here, named to match Bambu's JSON key exactly
|
|
||||||
//! (`#[serde(rename_all = "snake_case")]` on the enum handles simple
|
|
||||||
//! cases automatically — a Rust variant `Pushing` becomes JSON key
|
|
||||||
//! `"pushing"` for free; only reach for `#[serde(rename = "...")]` on
|
|
||||||
//! a specific variant if the key doesn't snake_case cleanly from the
|
|
||||||
//! Rust name).
|
|
||||||
|
|
||||||
use continuum_types::bambulab::v1::InfoCommand;
|
|
||||||
|
|
||||||
/// One request you can send to a Bambu printer. Grows by one variant per
|
|
||||||
/// message you define in bambulab/v1/request.proto.
|
|
||||||
#[derive(serde::Serialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum BambuRequest {
|
|
||||||
Info(InfoCommand),
|
|
||||||
// Pushing(PushingCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One report/response received from a Bambu printer. Same idea, the other
|
|
||||||
/// direction — grows by one variant per message in bambulab/v1/report.proto.
|
|
||||||
/// Empty for now: no response messages defined yet (InfoResponse is the
|
|
||||||
/// natural first one — see the JSON example in bambu_protocol_demo.rs).
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum BambuReport {}
|
|
||||||
|
|
||||||
// V1/V2 fork here once you know how much of the wire format actually
|
|
||||||
// differs: if it's mostly the same shape, one BambuRequest/BambuReport
|
|
||||||
// covering fields from both packages may be enough (some variants simply
|
|
||||||
// unused on one generation); if the reports genuinely diverge, mirror this
|
|
||||||
// file as bambu_protocol_v2.rs against continuum_types::bambulab::v2.
|
|
||||||
+4
-2
@@ -29,12 +29,14 @@
|
|||||||
//! `tokio::spawn`, not yet).
|
//! `tokio::spawn`, not yet).
|
||||||
|
|
||||||
mod bambu;
|
mod bambu;
|
||||||
mod bambu_protocol;
|
mod bambu_commands;
|
||||||
mod klipper;
|
mod klipper;
|
||||||
mod prusa;
|
mod prusa;
|
||||||
|
|
||||||
pub use bambu::{BambuGenericPrinter, BambuTls, BambuV1Printer, BambuV2Printer};
|
pub use bambu::{BambuGenericPrinter, BambuTls, BambuV1Printer, BambuV2Printer};
|
||||||
pub use bambu_protocol::{BambuReport, BambuRequest};
|
pub use bambu_commands::{
|
||||||
|
BambuReport, BambuRequest, CommandEnvelope, GetVersionRequest, GetVersionResponse, PushallRequest, PushallResponse,
|
||||||
|
};
|
||||||
pub use klipper::KlipperPrinter;
|
pub use klipper::KlipperPrinter;
|
||||||
pub use prusa::{PrusaLinkPrinter, PrusaSerialPrinter};
|
pub use prusa::{PrusaLinkPrinter, PrusaSerialPrinter};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user