Commit Graph
20 Commits
Author SHA1 Message Date
octoturgeandClaude Opus 5 7d68e8e069 Make the filament source explicit, and print from the AMS
`start_uploaded_file` hardcoded `use_ams: false` / `ams_mapping: ""`, which
silently overrode whatever the slicer had chosen. On a P1P with an AMS and a
project sliced for it, that is not a missing feature - it points the print at
the external spool. Confirmed on 2026-10-11: the print started, heated to
250C, reached 1%, then the printer paused itself with `print_error: 50364420`
and `tray_now: 255`. Nothing in the command's reply said so; `result` was
`success`, because the command had been accepted.

So `FilamentSource` is now a required argument with no default, because there
is no safe default - picking wrong draws the wrong filament or none. The
`ExternalSpool` variant sends the empty *string* the schema's trailing
`oneOf` demands, and `ams` takes slots per project filament index and pads to
every tray the printer has, per the padding rule in
`schemas/bambulab/v1/common/ams/ams_mapping.schema.json`. It refuses an empty
mapping (padding `[]` would mean `use_ams: true` with nothing mapped), one
longer than the printer has trays, and any negative that is not -1.

Confirmed live with the real mapping: `ams_mapping: [3,-1,-1,-1,-1]`,
`use_ams: true` took the printer to RUNNING, `tray_now` became 3 - the slot
the project was sliced for - and it ran past 1% to 13% with `print_error: 0`,
where the external-spool attempt had paused.

Finding the mapping needed reading the project back off the card, so FTPS
gains `download`/`download_file` and the probe a `fetch` step. The archive's
`Metadata/slice_info.config` lists each plate's filaments with
`tray_info_idx` and colour - here one PETG GFG99 at `#2850E0`, matching AMS
slot 3's `2850E0FF` - and `Metadata/plate_1.gcode` is present, which also
confirms the hardcoded `param`.

`upload_project_file` is left on the external spool and is the one remaining
place that can pick wrong, because it is vendor-generic and an AMS mapping is
a Bambu concept. It is unreachable - there is no start-print command on the
uplink - and the fix is for it to derive the mapping from the `.3mf` it
already holds, which needs a zip reader and so a dependency decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 22:35:51 +00:00
octoturgeandClaude Opus 5 79df3e7153 Record what a real sliced project did, and widen the start guard
A second live run, this time against a project the owner sliced in Bambu
Studio rather than a file already on the card, and through
`start_uploaded_file` itself rather than a hand-built body - so the
root-vs-cache fix is confirmed end to end. `gcode_file` in the printer's own
status came back as `cube-test-2026-10-11.gcode.3mf`, 150 layers, print_type
`local`.

It also found a defect the first run could not. The print heated to 250C,
reached 1%, and then the printer paused itself: `print_error: 50364420`
(`0x03008004`), `hms: []`, `tray_now: 255`. 255 is the external spool, which
is exactly what the hardcoded `use_ams: false` selects - so that pair
overrode the slicer's own filament choice and pointed an AMS-sliced project
at an external spool. The first run looked fine only because it was stopped
during PREPARE, before filament mattered. The doc comment said that pair was
"accepted, and the print ran"; it now says what it actually does, because
silently changing which filament a print uses is worse than not supporting
AMS at all.

The `start` guard demanded IDLE, which meant the harness refused every print
following a cancelled one - a Bambu reports an operator cancellation as
FAILED. IDLE, FINISH and FAILED are all terminal; PREPARE, RUNNING, PAUSE and
SLICING mean the machine is committed, and anything unrecognised is still
refused rather than assumed idle.

`bambu_cache_list` now prints nozzle/bed temperatures and remaining time.
State plus layer number cannot tell a print that is working from one that is
stalled: a P1P sits at layer 0 for minutes during bed levelling and purge,
and the only thing moving in that window is the heat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 17:55:01 +00:00
octoturgeandClaude Opus 5 4d644937af Confirm the print path on a real P1P, and stop discarding its answer
Print control (pause/resume/stop) and print start are now both confirmed
against the P1P on the LAN, and doing that turned up three real defects.

`start_uploaded_file` published fire-and-forget, so the printer's reply was
thrown away. Asking it to print a file it does not have returned `Ok(())`,
and watching `print.push_status` could not tell that apart from a successful
start, since both leave the machine IDLE for a while. The printer does in
fact answer - `{"command":"gcode_file","param":"...","reason":"error
string","result":"fail"}` - and it echoes the `sequence_id` we send, so
`request` correlates it. Both this and `control_print` now go out as
correlated requests and run the reply through `print_command_outcome`.

The upload and the print command disagreed about where files live. `upload`
cwd'd into `cache/` while the printable projects are in the card's root: the
root holds 43 `<name>.gcode.3mf` archives, `cache/` holds the printer's own
extracted `<project>_plate_N.gcode` working copies with `.gcode.bbl`
sidecars. Those extracted copies are not independently printable -
`gcode_file` with `param: "/cache/Cube_plate_1.gcode"` was rejected for a
file the FTPS listing shows is right there - while `project_file` with
`url: "ftp:///Cube.gcode.3mf"` started a print. So the destination is now a
parameter and both halves point at `SD_ROOT`.

What the hardware settled, recorded in the doc comments: the `ftp:///` url
spelling is right, `param: "Metadata/plate_1.gcode"` works for a
single-plate project, `use_ams: false` + `ams_mapping: ""` is accepted, and
a stop during PREPARE/RUNNING moves the printer to FAILED, which is how
Bambu reports an operator cancellation. What it did not settle: all three
control verbs answer `result: "success"` on an *idle* printer, so the reply
means "accepted", not "the state changed" - callers still have to read
`gcode_state` back.

`PrinterCommand::ControlPrint` makes this reachable from the cloud at all;
until now pause/resume/stop existed in the proxy with no way to send them.
Still no start-print variant, now for a different reason: the path around it
is missing, not the hardware proof.

Also: `list_dir` for any directory (how the layout above was found, without
sending a print command to guess), `request_raw` for probing an unconfirmed
command shape, and two examples - `bambu_cache_list` (read-only) and
`bambu_print_probe` (gated steps, `start` needing both IDLE and
--yes-start-a-real-print).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 17:38:27 +00:00
octoturgeandClaude Opus 5 4ab95ff7ce Compile the modules once, and clear the warning backlog
`cargo check --all-targets` emitted 32 warnings; it now emits none. Almost all
of them came from one cause: main.rs declared `mod config/fleet/printer/uplink`
while lib.rs declared the same modules, so every file was compiled twice, once
per crate. In the binary copy nothing is externally reachable, so every
`pub use` re-export and every method that only an example or a test calls was
genuinely dead - and the warnings were real about the binary while being wrong
about the library's actual public API. main.rs now uses the library crate and
keeps only `mod go2rtc`, which nothing else needs.

The rest, individually:

- `ModuleInfo` was re-exported from bambu_commands but not from printer, so it
  was unreachable even though `GetVersionResponse.module` is a public field of
  type `Vec<ModuleInfo>` - a caller could not name the type of a field it can
  read. Re-exported rather than dropped.
- Three stub `publish` impls take a payload they do not use yet; renamed to
  `_payload`.
- One unused import in examples/test_bambu_certs.rs.

That leaves the `async_fn_in_trait` lint on GenericPrinter, which is now
`allow`ed with the reasoning written down rather than left to fire on every
build. The old comment said it "only matters once this is wired into the
multi-threaded daemon", which has since happened - so the question got
answered instead of deferred: the futures cannot be required to be `Send`, and
that is fine because every printer is reached through one Arc<Mutex<Vec<..>>>
from the uplink's own task, so they are created and polled in the same place.
The note says what to do if a printer ever gets its own task.

No behaviour change. Tests still pass and the daemon was re-verified against
the live P1P and H2C through the full backend path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 12:16:48 +00:00
octoturgeandClaude Opus 5 1d95451c4c Fix print.push_status parsing, and stop losing reports silently
Pointed at a real H2C that was 20% into a job, `print_status()` returned
nothing at all. The printer was in fact pushing a full status object every
second; every one was arriving, failing to deserialize, and being dropped
invisibly by an `if let Ok(report) = ..` with no `else`.

That silence produced two confidently-wrong claims in our own comments,
both corrected here:

- `pushall`'s doc said whether v2 emits `push_status` unprompted was "an
  open question". It is not: v2 pushes a full 97-field object roughly once
  a second from the moment you subscribe, no trigger needed. There is no
  missing "other trigger" to hunt for.
- The "sequence gap" warning blamed the network for dropped v1 updates.
  Those updates were not dropped in transit - they failed to parse, so
  they never reached the counter, making the next one that did look like a
  skip. Confirmed: seq 57, 62, 64 and 69 all arrived. After the fix, zero
  gaps and zero parse failures across a 25s run against both printers.

Two concrete causes, both found with captured payloads:

- v1 trays carry `tray_temp`/`tray_time`; v2 trays carry
  `drying_temp`/`drying_time`. All four were required, so *every* v1 push
  carrying AMS data failed.
- A third tray shape existed that nothing modelled: an empty v2 slot sends
  `{"id": "2", "state": 0}`, which matched neither `EmptyTray` (it denies
  unknown fields, deliberately) nor `LoadedTray` (16 fields short).

A parse failure now reports itself, naming the printer, the sequence id and
the serde error. A report this process cannot parse is a defect in
`PrintReport`, and the only way to fix it is to be told which field.

`PrintReport` itself modelled 9 of 97 top-level fields, and none of them
were progress or temperature - it was built from a truncated example. It
now carries gcode_state, percent, layer counts, remaining time, nozzle/bed
temperatures, fan gears, wifi and HMS entries. `merge_from`'s two dozen
four-line blocks collapse to `newer.x.or(self.x.take())` one-liners: the
old shape made adding a field a three-place edit, and forgetting the merge
half produced a field that silently never survived a partial update.

Two scale traps documented where they bite: reported fan speeds are 0-15
gears, not percent and not the 0-255 PWM we send (`set_fan_speed(50)`
reads back as "7"); and v2 pins `sequence_id` to one value for minutes
while contents change, so it orders and identifies nothing there.

Two examples, and the distinction between them is the point. `live_reports`
is read-only and safe against a printer mid-job. `bambu_idle_checks`
writes, so it requires an explicit printer id and refuses to run unless
that printer reports `gcode_state: "IDLE"`.

Regression fixtures are verbatim captures off both printers, hardware
identifiers scrubbed - the only way to keep this from regressing without a
printer plugged in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 10:12:05 +00:00
octoturgeandClaude Sonnet 5 94484bb190 Update docs and prune examples superseded by real functionality
README/certs/README/.env.example/printers.example.toml were all still
describing this as a learning-stage stub with no real printer protocol
clients - rewritten to reflect the real Bambu MQTT client, cert
auto-pinning, and marlin serial connection that now exist.

- Remove examples/fetch_bambu_cert.rs: its manual fetch-and-save workflow
  is now what BambuPrinter::connect()'s ensure_trusted does automatically
  on every connect, so it has no remaining use case.
- Fix examples/printer_polymorphism.rs, which no longer compiled -
  dispatch_gcode was replaced by real methods earlier this session;
  swapped in print_status() as the vendor-only-extension example.
- Remove Config::bambu_ca_cert_overrides/bambu_require_valid_cert and the
  .env vars documenting them - confirmed dead (nothing read them),
  fully superseded by printers.toml's per-printer ca_cert_path/
  require_valid_cert plus automatic trust-on-first-connect pinning.
- Fix stale doc comments in printer/mod.rs and printer/bambu.rs that
  described certificate trust and MQTT as not built yet, or referencing
  the now-removed example.

No behavior change in the daemon itself - verified with cargo build
--examples and a live run of printer_polymorphism and auto_connect_bambu
against the real P1P/H2C printers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 16:17:15 +00:00
octoturgeandClaude Sonnet 5 133db9e4aa Add pushall + print_status cache; fix envelope, race, and matching bugs
PrintReport now flattens CommandEnvelope (sequence_id/command) -
push_status.schema.json composes the shared envelope like every other
Bambu message; the struct's old doc comment concluded otherwise from a
capture that turned out to be an incomplete example (also missing dozens
of other real fields), not proof the message lacks them.

Added BambuMqttSession's PrintStatusCache: the in-memory
print.push_status object, replaced wholesale on v2 (always full) or on a
v1 message with msg != 1 (forced/full), merged field-by-field otherwise
(v1's organic partial updates) via PrintReport::merge_from. Tracks
last_sequence_id to warn on a gap in the organic stream. BambuPrinter
gets pushall() (v1-only; pushing.pushall doesn't exist in v2's schema at
all) and print_status().

Three real bugs found and fixed via live testing against an actual P1P,
none of them guessable from the schema alone:
- pushall's reply doesn't echo the sequence_id we send - the printer
  stamps its own free-running counter regardless of what triggered it.
  request()'s ID correlation silently can't work here; first fix attempt
  added request_next_of_category (wait for the next message under a
  category, no ID needed).
- That undershot too: matching by category alone let an unrelated
  "print" message (that category carries many commands - gcode_line,
  pause, push_status, ...) satisfy the wait before the real push_status
  reply arrived, so pushall() returned Ok having consumed the wrong
  message - now keyed by (category, command).
- A real ordering race: handle_incoming_report woke a waiting oneshot
  before updating the print_status cache, so a caller could resume and
  check print_status() before the write landed - pushall() returned Ok
  immediately followed by print_status() still showing None. Fixed by
  applying the cache update before sending on any matched oneshot.

Superseded by real protocol knowledge before any of the above needed to
ship, though: push_status carries its own msg field (1 = automatic/
partial, 0 = forced/full) - the actual, reliable, self-describing signal
for which kind a message is, regardless of timing or which client (if
any) triggered it. PrintStatusCache now reads that directly instead of
inferring it from request correlation, and pushall() is plain fire-and-
forget (publish, no wait) since there's nothing left to correlate for -
confirmed live: msg reads correctly off a real P1P, and the cache
populates/updates correctly with no gap warnings across several organic
pushes. request_next_of_category is kept (a future case may still need
"no correlation ID at all" for real), just no longer used here.

Also: the poll loop now logs when it exits instead of returning silently
- that silence is exactly what made a real dropped MQTT connection to a
live H2C indistinguishable from "nothing happening" during testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
2026-09-07 14:39:55 +00:00
octoturgeandClaude Sonnet 5 2c4ef21291 Add upload_project_file/send_gcode_line, real Marlin serial, Bambu protocol auto-detect
- GenericPrinter grows upload_project_file, send_gcode_line, and a
  generic upload_gcode<P> driver that streams a whole gcode file line by
  line through any vendor's send_gcode_line.
- PrusaSerialPrinter's connect/send_gcode_line are real (not stubs): a
  held-open MarlinSerialConnection (src/printer/marlin_serial.rs) writes
  each line and waits for Marlin's "ok" before sending the next. Config
  changed from com_port: i16 to port: String + baud_rate: u32 to hold a
  real device path.
- Bambu: BambuGenericPrinter + BambuV1Printer/BambuV2Printer collapsed
  into one BambuPrinter, since which MQTT/BambuMQTT version (v3.1.1/v1
  vs v5/v2) a printer speaks is discovered at connect() time (or forced),
  not something config should pick a type for - the same physical
  printer can change after a firmware update. printers.toml gets a new
  "bambu" vendor (auto-detect, the default) alongside bambu_v1/bambu_v2
  (force a specific one).
- Bambu's gcode_line request is now built for real via
  continuum-schema-instance against continuum-schemas' JSON Schema,
  picking the v1 or v2 schema directory based on the detected protocol
  version, instead of a hand-written serde struct.
- Added rumqttc as a dependency (not wired up yet - the real MQTT
  v5-then-v3.1.1 fallback connect and the publish/receive plumbing are
  still stubs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
2026-09-05 20:40:21 +00:00
octoturge a2d4e578ec Replace em dashes with hyphens across docs and comments 2026-08-29 08:13:54 +00:00
octoturge 1583f531fa Add print.rs (AmsState/AmsUnit/AmsTray/XcamSettings), fix print vs pushing
Mirrors continuum-schemas' schemas/bambulab/ restructuring: PushallResponse
removed (it never existed on the wire — pushall's request goes to
"pushing", but the state-push stream it triggers arrives under "print",
a different root key, not a direct reply). BambuReport::Pushing(...) ->
BambuReport::Print(PrintReport).

AmsTray is an untagged enum (Empty/Loaded) rather than one struct full of
Option<T> fields, matching the JSON Schema's oneOf for the same slot -
#[serde(deny_unknown_fields)] on EmptyTray is load-bearing: untagged enums
try variants in order, and a loaded tray (superset of Empty's one field)
would otherwise incorrectly match Empty first.

Verified against the full real ams/xcam example from the conversation via
examples/bambu_commands_demo.rs - both the empty and loaded tray in that
payload deserialize to the correct enum variant.
2026-08-28 23:19:20 +00:00
octoturge 166761479b 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.
2026-08-28 22:49:06 +00:00
octoturge 8e5d85202f Wire InfoCommand through as one real, runnable example
BambuRequest::Info(InfoCommand) is now live (was commented out) —
examples/bambu_protocol_demo.rs constructs one, serializes it, and prints
it next to Bambu's real wire JSON for comparison. Run it and copy this
exact shape (proto message -> use it here -> add an enum variant) for
every other command.
2026-08-28 22:41:55 +00:00
octoturge 6e18215af5 Add GenericPrinter::set_fan_speed() across all five printer kinds
Same override pattern as connect(): required, no default body, five
completely different bodies.

- BambuGenericPrinter::set_fan_speed_impl() is shared by BambuV1Printer and
  BambuV2Printer (composition reuse, same as connect_impl) - both speak
  the same MQTT gcode-injection mechanism.
- PrusaLinkPrinter and PrusaSerialPrinter both send the same M106 gcode
  but over different transports (HTTP command injection vs. raw serial
  bytes) - they share the percent->PWM conversion despite having unrelated
  connect() implementations.
- KlipperPrinter uses a hypothetical Moonraker-native endpoint that takes
  a percentage directly - no PWM conversion at all, since that math only
  applies to the gcode-speaking vendors.

percent_to_pwm() lives in mod.rs as a private free function rather than a
trait default: it's genuine shared logic, but only for the subset of
vendors that need it, which is exactly the case a trait default can't
express cleanly. Visible to bambu.rs/prusa.rs via super:: because Rust's
module privacy reaches into child modules.

Verified with cargo check --all-targets and a full run of
printer_polymorphism: 50% converges on the same PWM value (127) across all
three gcode-based printers, Klipper's native path takes 0.50 directly.
2026-08-28 21:40:11 +00:00
octoturge a77e93940f Fix ensure_trusted(): pin by serial number, not the arbitrary id label
id in printers.toml is just a label you chose for config purposes - nothing
ties it to a specific physical printer, and renaming it (or reusing it for
a different unit down the line) would silently break the pin lookup and
re-trigger trust-on-first-connect for hardware that was already trusted.
The serial number is the one thing about a printer that can't change, so
that's what a pin should be keyed by: certs/pinned/<sn>.pem instead of
certs/pinned/<id>.pem.

Verified against a real TLS server: pinned a printer under one id, renamed
it in printers.toml with the sn left unchanged, and confirmed the second
run found the existing pin silently (no re-TOFU) rather than re-pinning.
2026-08-28 21:22:16 +00:00
octoturge af2f20a45b Add BambuGenericPrinter::ensure_trusted() — smooth auto-connect, SSH-style
The manual fetch_bambu_cert workflow works but needs two commands and a
printers.toml edit. ensure_trusted() collapses that into one call, using
the same trust model SSH uses for host keys:
- a pin already on disk (certs/pinned/<id>.pem) is used directly, no new
  trust decision is made on every run
- no pin yet + bundled CA verifies fine (current-gen printers): nothing to
  do
- no pin yet + bundled CA fails (P1P, etc.): auto-pins via
  trust-on-first-connect, same as fetch_bambu_cert, but automatic
- an *explicit* pin (you set ca_cert_path yourself) never gets silently
  auto-pinned over — a failure there is a real error

examples/auto_connect_bambu.rs demonstrates the one-command flow.

Verified all three states against local test servers, not just compiled:
first run with no pin auto-pins and connects; second run against the same
server is silent (no re-TOFU message) and just verifies against the pin;
third run after swapping the server's certificate correctly FAILS instead
of silently re-pinning — confirms the security property survives the
smoother UX.
2026-08-28 21:18:46 +00:00
octoturge 8572d564b1 Split BambuPrinter into BambuGenericPrinter -> BambuV1Printer/BambuV2Printer
Completes the GenericPrinter -> BambuGenericPrinter -> BambuV1/V2 chain this
project wanted from the start. BambuGenericPrinter holds the fields and
behavior every Bambu printer shares (access code, serial number, CA trust,
the TLS test/fetch methods); BambuV1Printer and BambuV2Printer each *have*
one (composition) and are now genuinely separate types, ready to carry
flavour-specific report-schema fields later. Also threads through a new
'sn' (serial number) field the real MQTT topics will need.

Adds real 'fetch the CA automatically' capability, verified against a live
TLS server (not just compiled):
- BambuGenericPrinter::fetch_certificate() does trust-on-first-connect —
  connects once with verification disabled, captures the certificate the
  printer actually presents via native_tls's peer_certificate()/to_der(),
  and returns it as PEM. Deliberately a method you call once by hand
  (examples/fetch_bambu_cert.rs), not something connect() falls back to
  silently, since TOFU trusts whoever's on the network the moment you run
  it. Verified end-to-end against a local openssl s_server: the fetched
  PEM's SHA-256 fingerprint exactly matched the server's real certificate.
- Verified the other direction too: test_bambu_certs (bundled-CA mode)
  correctly REJECTS that same test server's cert, since it wasn't signed
  by the real Bambu CA.

Splitting BambuV1Printer/BambuV2Printer into distinct types broke the
PrinterHandle::BambuV1(x) | PrinterHandle::BambuV2(x) or-pattern in both
examples (or-patterns require every alternative to bind the same type) —
fixed by giving each variant its own match arm.
2026-08-28 21:04:56 +00:00
octoturge d8b7430296 Add printers.toml (gitignored) + real TLS cert test against actual hardware
New:
- printers.example.toml (committed template) / printers.toml (gitignored,
  real hosts + access codes don't belong in git) — a list of real printers
  with vendor, host, access_code/api_key/com_port, and an optional
  per-printer CA override.
- src/fleet.rs loads that file into ready-to-use PrinterHandles, matching
  vendor strings to the right constructor.
- BambuPrinter::test_tls_handshake() does a real (blocking, one-shot) TLS
  handshake to port 8883 using that printer's configured BambuTls trust
  mode — no MQTT protocol, just 'does the certificate verify'. Added
  native-tls and toml as direct dependencies for this.
- examples/test_bambu_certs.rs loads printers.toml and runs the handshake
  test against every Bambu entry.

Also fixes a real bug found while testing against unreachable IPs: plain
TcpStream::connect has no timeout and hung indefinitely on an offline
printer — switched to connect_timeout (5s).

Verified with cargo check --all-targets (0 errors) and by actually running
test_bambu_certs against a local printers.toml (correctly errored on a
missing cert file, then correctly timed out against unreachable test IPs
instead of hanging).
2026-08-28 20:48:05 +00:00
octoturge 9eab5e5a04 Bambu TLS trust config + finish PrusaLink/PrusaSerial split
Certs:
- Vendor Bambu's shared LAN-mode root CA (certs/bambu_ca2.pem, verified
  self-signed CA:TRUE, see certs/README.md for provenance/fingerprint).
- Config gains bambu_ca_cert_path (per-printer override) and
  bambu_require_valid_cert (the allow/reject flag); printer::bambu::BambuTls
  turns those into BundledCa/Custom/Insecure. connect() doesn't perform a
  real handshake yet (no TLS-capable MQTT client wired in), just reports
  which trust mode it would use.

Also reconciles a rename in flight (Printer -> GenericPrinter trait) and
finishes the PrusaPrinter -> PrusaLinkPrinter/PrusaSerialPrinter split:
added the missing GenericPrinter impl for PrusaSerialPrinter, fixed
PrinterHandle's variant payload types, updated mod.rs's pub use list and
doc comments, and updated the example to the new 5-variant shape.

Verified with cargo check --all-targets (0 errors) and a full run of
cargo run --example printer_polymorphism.
2026-08-28 20:38:10 +00:00
octoturge 5705f36c01 Simplify printer/ module for someone learning Rust
Drop reqwest, RPITIT+Send signature, and multi-variant error enum from the
teaching example so the only new idea per file is the trait override itself.
Real networking stays in adapters/, unaffected.
2026-08-28 18:04:13 +00:00
octoturge 03bd07fbd7 Add Printer trait + PrinterHandle enum domain model with polymorphism example 2026-08-28 17:03:21 +00:00