go2rtc has been supervised since before this commit, with nothing to serve: `CONTINUUM_GO2RTC_CONFIG` pointed at `./go2rtc.yaml`, and that file did not exist in the repository or anywhere else. A watchdog was restarting a process that had no cameras configured. `camera.rs` writes it, from the `rtsp_url` a printer publishes in its own `print.push_status` `ipcam` block rather than from `printers.toml`. The URL is already something the machine tells us, so asking an operator to keep a second copy of it - and to update it by hand when a printer's address changes - would be asking for drift. Two tasks, not one: the builder decides what should be served, the watchdog keeps the thing serving it alive. They meet at one `Notify`, because go2rtc reads its configuration only at startup, so a changed config needs a restart. The watchdog now selects the child's exit against that notification and skips its backoff on a deliberate change. What this does *not* cover is most printers, and that is the honest limit. Only the X1/H2 class publishes an RTSP URL - confirmed against two real machines: the H2C reports `rtsps://192.168.0.21:322/streaming/live/1`, the P1P reports an `ipcam` block with `ipcam_dev: "1"` and no URL at all. "A camera exists" and "a camera you can connect to" are different facts. A P1/A1 needs Bambu's proprietary port-6000 JPEG stream, which this crate does not speak, so those printers report no `camera_stream` and a UI should offer a player only when one is present. Details worth keeping: - The config is rendered sorted so an unchanged fleet is byte-identical, and on the first pass it is compared against the file on disk - without that, every daemon restart rewrites an identical file and drops every live viewer. - Written through a `.tmp` and renamed. The watchdog can restart go2rtc at any moment, and a half-written config fails to parse, which takes every working stream down rather than one. - Never logged, and gitignored: the credentials go in the URL because that is the only place go2rtc takes them for an RTSP source, so the file holds printer access codes. - A reported URL with no scheme is refused rather than repaired. One bad entry makes go2rtc fail to load the whole file. - No `rtsp:` or `webrtc:` block. The API listener is what serves WebRTC to a browser, which is the only consumer here; opening go2rtc's own RTSP server would publish every printer's camera on the LAN unauthenticated. - `stream_base_url` is reported in `hello`, not derived from the listen address: `:1984` is a bind address and not something a browser can open. Unset means "advertise nothing", which is the right default, because a wrong URL makes the UI offer a player that cannot connect. It is a LAN address either way - there is no relay, so this works for an operator in the workshop and not for anyone outside it. 63 tests, clippy unchanged at the two deliberate variant-size warnings, `cargo deny check licenses` ok. go2rtc itself stays operator-installed and is not distributed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
3.5 KiB
Rust
77 lines
3.5 KiB
Rust
//! 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 Bambu *category* (`get_version.rs` for "info",
|
|
//! `pushall.rs` + `print.rs` for "pushing"/"print" - see below for why
|
|
//! those are two files, not one). Copy `get_version.rs`'s shape for your
|
|
//! next command.
|
|
//!
|
|
//! Mirrors continuum-schemas' `schemas/bambulab/` JSON Schemas - same
|
|
//! category split, same "print vs pushing" correction (see that
|
|
//! directory's README for the full explanation): a `pushall` *request*
|
|
//! goes to the "pushing" key, but the ongoing state-push stream it
|
|
//! triggers arrives under a different key, "print" - so `PushallRequest`
|
|
//! lives in `pushall.rs` and the thing it triggers, `PrintReport`, gets
|
|
//! its own file, `print.rs`, rather than living together as if one were
|
|
//! simply "the response to" the other.
|
|
|
|
mod get_version;
|
|
mod print;
|
|
mod pushall;
|
|
|
|
pub use get_version::{GetVersionRequest, GetVersionResponse, ModuleInfo};
|
|
pub use print::{
|
|
AmsState, AmsTray, AmsUnit, EmptyTray, Ipcam, LightReport, LoadedTray, PrintReport, VtTray,
|
|
XcamSettings,
|
|
};
|
|
pub use pushall::PushallRequest;
|
|
|
|
/// 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", ...). A
|
|
/// category can have more than one possible request shape - if that ever
|
|
/// happens here, that variant's payload becomes its own small
|
|
/// `#[serde(tag = "command")]` enum instead of a single struct.
|
|
///
|
|
/// The "print" category isn't a variant here at all: unlike `info`/
|
|
/// `pushing`, it has many possible request shapes (`gcode_line`, `pause`,
|
|
/// `resume`, ...) in continuum-schemas' own `oneOf`, so
|
|
/// `bambu.rs`'s `send_gcode_line_impl` builds that one directly via
|
|
/// `continuum-schema-instance` instead of adding it here.
|
|
#[derive(serde::Serialize, Debug)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BambuRequest {
|
|
Info(GetVersionRequest),
|
|
Pushing(PushallRequest),
|
|
}
|
|
|
|
/// Same idea, the response direction - note "print", not "pushing" (see
|
|
/// this module's doc comment).
|
|
#[derive(serde::Deserialize, Debug)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BambuReport {
|
|
Info(GetVersionResponse),
|
|
Print(PrintReport),
|
|
}
|