Files
continuum-proxy/printers.example.toml
T
octoturge a401773406 Discover Klipper, PrusaLink and serial printers too
Discovery found only Bambu printers, so a mixed farm still meant typing
most of printers.toml by hand. It now covers the four vendors fleet.rs
accepts, over both transports.

Detection splits in two, and keeping the halves apart is what makes this
honest. "Something at this address speaks X" needs no credential from any
vendor, and is most of the value - it yields the address and the vendor,
which is two of the three or four fields an entry needs. "What is it called,
what firmware does it run" only has an answer where the vendor publishes one
unauthenticated: Bambu always, Moonraker usually, PrusaLink never.

So an HTTP 401 counts as a find rather than a failure. A password prompt
from /api/version is proof that something is serving /api/version, which is
the question being asked. It also makes the Moonraker probe correct without
having to know how a given host configured [authorization], which is not
knowable from outside.

Klipper is `GET :7125/printer/info`, Moonraker's own identity endpoint,
which returns hostname and software_version. PrusaLink is
`GET :80/api/version`; its OpenAPI specification puts digestAuth on every
operation including /api/v1/info, so there is no unauthenticated identity
endpoint at all and a 401 is the best available answer - which is enough,
since the only field still missing is the API key no probe could supply.
This cannot distinguish PrusaLink from OctoPrint, and that is not fixable
from outside: PrusaLink deliberately implements OctoPrint's API. A wrong
guess an operator can see and correct beats skipping a real printer.

Two guards against claiming things that are not printers, both now tested
against what a real LAN actually answers. Three devices on the test segment
answer /api/version with HTTP 200 and a body that is not JSON - a router, a
device with a logon page, and one serving gzip - so a 200 must carry
OctoPrint's guaranteed `api` field, and Moonraker's must carry the `result`
wrapper every Moonraker reply has. And a bare 401 is not enough either:
routers, NASes and cameras all password protect port 80, so a 401 must carry
a *Digest* challenge, which PrusaLink's spec guarantees and the Basic-auth
crowd does not send. A sweep of the real /24 still finds exactly the two
Bambu printers and nothing else.

Serial detection is passive by default, and that is a safety property rather
than a limitation. Opening a serial port toggles DTR, which reboots an
Arduino-based board - marlin_serial.rs documents the same thing from the
other direction - so a firmware handshake against a machine that happens to
be printing ruins the print, and discovery is exactly what someone runs
while a farm is busy. So list_ports opens nothing: it reads USB descriptors
out of sysfs, which is where udev reads them from, and gets the
manufacturer, product and USB serial without touching the device. That is
enough to render an entry, since a serial printer's block needs only a path
and a baud rate. probe_m115 is the opt-in extra that learns the firmware and
the exact machine type, and it refuses to run without
--yes-reset-the-printer, the same shape as bambu_print_probe's
--yes-start-a-real-print.

sysfs rather than tokio-serial's libudev feature: a gateway appliance should
not need libudev-dev at build time to list its own ports, and reading the
four files udev would read is thirty lines and no dependency. The walk
climbs to the USB device holding idVendor rather than counting parents,
because a ttyACM link points at an interface and a ttyUSB link at a
usb-serial port, each a different depth down.

Only Prusa's own USB vendor id counts as evidence of a printer. Everything
else in the table is a generic bridge chip - FTDI, CH340, CP210x - that a
printer is likely to be behind but that a GPS or an Arduino project could
equally be, so those are reported with "could be a printer or any other
serial device" rather than asserted.

Entries now carry exactly the fields each vendor's loader demands and no
others, with a test that fails if to_toml_entry and fleet.rs drift apart,
and a rendered entry for a vendor whose client is still a stub says so -
otherwise an operator pastes in a Klipper printer and gets a gateway that
connects and does nothing.

Found while testing the M115 parser: a value ran to end-of-input instead of
end-of-line, so the last field on a line swallowed what followed and UUID
read as "cede2a2f-...\nok".

Unverified, and untestable here: the Klipper and PrusaLink probes (no such
hardware) and all serial detection (no serial device attached, and
/sys/class/tty holds only virtual consoles). Every parser those depend on is
unit tested against recorded protocol text, including the real non-printer
responses above.
2026-10-19 07:28:05 +00:00

69 lines
2.6 KiB
TOML

# Copy this file to printers.toml (gitignored) and fill in your real
# printers. See src/fleet.rs for the loader, examples/test_bambu_certs.rs
# for a way to test a Bambu printer's certificate against this config.
#
# Rather than reading these fields off each printer's screen:
#
# cargo run --example discover_printers -- all 192.168.0.0/24
#
# finds the printers on your network and on your USB ports and prints blocks
# like the ones below, with the addresses and serials already filled in. It
# covers Bambu, Klipper/Moonraker, PrusaLink and Marlin-over-serial. Also
# `listen` (Bambu on this subnet only), `sweep <subnet>` (works through a
# router), `identify <host>`, and `serial`.
#
# It cannot find a credential - a Bambu access code is on the printer's
# screen and a PrusaLink API key is in its web UI, which is the point of both
# - so every block it prints still needs that one field before this file will
# load. Note that only `bambu` has a working client today; the others are
# found and reported, but their drivers in src/printer/ are still stubs.
[[printer]]
id = "p1p"
name = "P1P"
# Forced instead of "bambu" here since this one's a known-old printer -
# see fleet.rs's PrinterEntry doc comment for the full vendor list.
vendor = "bambu_v1"
host = "192.168.1.50"
access_code = "REPLACE_WITH_ACCESS_CODE"
sn = "REPLACE_WITH_SERIAL_NUMBER" # printer screen > Settings > Device (used for MQTT topics later)
# No ca_cert_path needed even though P1P doesn't chain to the bundled CA -
# `connect()` auto-pins a certificate via trust-on-first-connect the first
# time it can't verify against the bundled one, and saves it under
# certs/pinned/<sn>.pem for every run after that (see BambuPrinter::
# ensure_trusted's doc comment). Only set ca_cert_path explicitly if you
# want to point at a certificate you've captured/vetted some other way.
[[printer]]
id = "h2c"
name = "H2C"
# "bambu" auto-detects MQTT v5-vs-v3.1.1 (BambuMQTT v2-vs-v1) on connect -
# the recommended default, since a firmware update can change which one a
# given printer speaks without you knowing ahead of time.
vendor = "bambu"
host = "192.168.1.51"
access_code = "REPLACE_WITH_ACCESS_CODE"
sn = "REPLACE_WITH_SERIAL_NUMBER"
# No ca_cert_path - current-generation printers verify against the bundled
# CA (certs/bambu_ca2.pem) fine.
# [[printer]]
# id = "voron"
# name = "Voron 2.4"
# vendor = "klipper"
# host = "192.168.1.52"
# [[printer]]
# id = "mk4"
# name = "MK4"
# vendor = "prusa_link"
# host = "192.168.1.53"
# api_key = "REPLACE_WITH_API_KEY"
# [[printer]]
# id = "mk3s"
# name = "MK3S+"
# vendor = "prusa_serial"
# port = "/dev/ttyACM0"
# baud_rate = 115200 # optional, defaults to 115200