main.rs never actually loaded printers.toml or connected anything -
fleet/printer were only ever exercised by standalone examples. Now
main() loads the fleet (path configurable via CONTINUUM_PRINTERS_PATH,
default "printers.toml") and connects every printer concurrently at
startup, logging success/failure per printer rather than failing
startup over one bad printer.
That surfaced a real bug the instant it ran against a live printer:
complete_pending() was sending the whole {"info": {...}} wrapper through
the pending oneshot, not the unwrapped body - BambuPrinter::get_version
deserializing that into GetVersionResponse failed with "missing field
module" even though module was right there, just one level deeper than
expected. Fixed by unwrapping the single category key before completing
the pending request, symmetric with how a request gets wrapped on the
way out.
Verified live against a real H2C (MQTT v5/BambuMQTT v2): connect,
get_version, and the full pending-request correlation all worked
end-to-end, returning real firmware versions for every module (main
board 01.01.05.00, both AMS units, AMS HT, etc). The other configured
printer (P1P, forced v1) failed with a real, expected TLS error - it
doesn't chain to the bundled CA, matching the already-documented P1P
quirk; needs the ca_cert_path/fetch_bambu_cert workflow, not a code fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
64 lines
2.6 KiB
Rust
64 lines
2.6 KiB
Rust
use std::collections::HashMap;
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Config {
|
|
pub gateway_id: String,
|
|
pub farm_id: String,
|
|
pub gateway_token: String,
|
|
pub uplink_url: String,
|
|
pub go2rtc_bin: String,
|
|
pub go2rtc_config: String,
|
|
/// Path to the fleet config file - see `fleet.rs` and
|
|
/// `printers.example.toml` for the shape.
|
|
pub printers_path: String,
|
|
|
|
/// Per-printer CA certificate overrides, keyed by printer ID. Most
|
|
/// Bambu printers chain to the bundled shared CA (`certs/bambu_ca2.pem`)
|
|
/// and don't need an entry here - but some models (P1P, at least) ship
|
|
/// their own certificate instead of one signed by that shared CA, with
|
|
/// no single file covering all of them. Look up a printer's own entry
|
|
/// (if any) when constructing it; there's no fleet-wide "the" override.
|
|
pub bambu_ca_cert_overrides: HashMap<String, PathBuf>,
|
|
/// When `false`, Bambu MQTTS connections skip certificate verification
|
|
/// entirely instead of checking against a CA. `true` (the default) is
|
|
/// the secure choice; only flip this fleet-wide policy if you have
|
|
/// printers you can't get to verify any other way.
|
|
pub bambu_require_valid_cert: bool,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env() -> anyhow::Result<Self> {
|
|
Ok(Self {
|
|
gateway_id: require("CONTINUUM_GATEWAY_ID")?,
|
|
farm_id: require("CONTINUUM_FARM_ID")?,
|
|
gateway_token: require("CONTINUUM_GATEWAY_TOKEN")?,
|
|
uplink_url: env_or("CONTINUUM_UPLINK_URL", "wss://api.continuum.local/ws/edge/v1"),
|
|
go2rtc_bin: env_or("CONTINUUM_GO2RTC_BIN", "go2rtc"),
|
|
go2rtc_config: env_or("CONTINUUM_GO2RTC_CONFIG", "./go2rtc.yaml"),
|
|
printers_path: env_or("CONTINUUM_PRINTERS_PATH", "printers.toml"),
|
|
|
|
bambu_ca_cert_overrides: parse_cert_overrides(&env_or("CONTINUUM_BAMBU_CA_CERT_OVERRIDES", "")),
|
|
bambu_require_valid_cert: env_or("CONTINUUM_BAMBU_REQUIRE_VALID_CERT", "true") == "true",
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Parses `"printer_id=path,printer_id2=path2"` into a lookup map. Empty
|
|
/// input (the common case - no overrides needed) yields an empty map.
|
|
fn parse_cert_overrides(raw: &str) -> HashMap<String, PathBuf> {
|
|
raw.split(',')
|
|
.filter_map(|entry| entry.split_once('='))
|
|
.map(|(id, path)| (id.trim().to_string(), PathBuf::from(path.trim())))
|
|
.collect()
|
|
}
|
|
|
|
fn require(key: &str) -> anyhow::Result<String> {
|
|
env::var(key).map_err(|_| anyhow::anyhow!("missing required env var {key}"))
|
|
}
|
|
|
|
fn env_or(key: &str, default: &str) -> String {
|
|
env::var(key).unwrap_or_else(|_| default.to_string())
|
|
}
|