11 KiB
Connection and status
Owns the runtime sequence in order: selecting an agent and machine, starting a connection, receiving status, sending commands. Defers the structural rules those steps must obey - ownership, no-fallback, unknown IDs, threading - to Architecture, and cites them at the point where they bite.
The four runtime concepts
Keep these concepts separate when tracing a connection problem:
- A preset stores an agent ID and printer address.
NetworkAgentholds the active printer agent for that agent ID.MachineObjectrepresents the selected printer at that address.- Freshness and status-confirmed readiness are separate states.
An agent ID selects a printer agent implementation. A device ID selects one printer within that implementation.
A non-Bambu printer reaches the machine list through Bind with Access
Code, the tile in the Device tab's machine-select popup. The user enters
an address and an access code, bind_detect() probes the address before
any connect, and DeviceManager::insert_local_device() creates the
MachineObject. For the Moonraker family the address itself becomes the
device ID: MoonrakerPrinterAgent::bind_detect() seeds dev_name and
dev_id from the entered address, so an unreachable or unnamed printer
still shows up as its IP rather than blank.
Binding is the only route for that family. MoonrakerPrinterAgent::start_discovery()
deliberately announces nothing, because a partial discovery implementation
would populate the machine list with stale hosts. Bambu is the exception:
it has its own discovery identity and does not use the address as an ID.
DeviceManager::selected_machine is only a selected ID. It can name no
resolvable object. get_selected_machine() answers whether an object is
actually available. set_selected_machine() accepting an ID therefore
does not prove the printer is connected. The selected ID can remain when its
object is unavailable, so connection state must come from the object itself.
Selecting the agent and machine
GUI_App::switch_printer_agent() reads the edited printer preset and
resolves its stored agent ID through NetworkAgentFactory.
- An empty stored ID is a legacy sentinel. It resolves to
bblfor a Bambu vendor preset and toorcaotherwise. - If that effective ID is registered, the factory provides the matching printer agent implementation.
- Clear the live printer agent only when a nonempty stored ID is unregistered or the factory cannot construct the matching registered agent.
- When the active printer agent changes, clear the current selection, user selection, stale device discoveries, sidebar state, and AMS state before installing the replacement.
- Select the preset's address-derived machine for non-Bambu agents.
The lifetimes are easier to see than to read. Note that the agent pointer can be unchanged while the machine still must be re-selected - that is the trap in the same-agent path below:
sequenceDiagram
participant U as User
participant P as Printer preset
participant G as GUI_App::switch_printer_agent
participant F as NetworkAgentFactory
participant N as NetworkAgent
participant M as MachineObject
U->>P: Edit or switch the printer preset
P->>G: printer_agent value, possibly empty
G->>G: resolve_printer_agent_id - empty becomes bbl or orca
G->>F: get_printer_agent_info(effective ID)
alt ID not registered, and stored value was nonempty
F-->>G: no info
G->>N: clear the live printer agent
Note over N: null is inert - never fall back to another agent
else ID registered
F->>F: create once per ID, then serve from cache
F-->>G: the cached IPrinterAgent for this ID
alt Agent pointer changed
G->>N: clear selection, user selection, discoveries, sidebar, AMS
G->>N: install the replacement agent
else Same cached pointer returned
Note over G,F: Two presets can share one agent ID
end
G->>M: compare device ID, re-select if the address differs
end
Do not use the first available machine as a fallback (rule owned by Architecture, Runtime objects and ownership). It connects to a printer the user did not choose, including one owned by a different printer agent.
The same-agent path is important too. Two presets can use one agent type but point at different addresses, and the factory caches one agent per ID, so switching between them returns the same pointer and would otherwise skip reselection entirely. Re-select the machine whenever the preset's address changes, even when the factory returned the same active agent. Otherwise status and filament work can continue against the previous printer.
Note: this is a legacy coupling, not the primary workflow. It reads an
address stored on the printer preset itself (print_host and
printhost_port, named here only so the keys can be found in the code)
and derives a device ID from it with dev_id_from_address(). Those keys
predate printer agents and are edited through PhysicalPrinterDialog,
which despite its name writes the printer preset rather than a
PhysicalPrinter object - that object is no longer constructed. Printers
normally arrive through Bind with Access Code instead, which does not
touch the preset. Both routes end at insert_local_device(), so they must
agree on the device ID: dev_id_from_address() strips the URL scheme and
drops an empty port, while the bind path stores the address as the user
typed it.
The unknown-coString compatibility rule belongs to architecture.md under
Backward compatibility. Keep a nonempty unknown printer_agent ID unchanged
and display a missing state if needed; do not rewrite it during plugin unload
or choose an arbitrary replacement, so the preset can round-trip while its
plugin is temporarily unavailable.
Starting a connection
Machine selection causes MachineObject::connect() to invoke the active
agent's connect_printer() with the selected address and effective access
code. A success return means that the connection attempt started. It does
not mean that the printer is ready or that a status stream is alive.
Moonraker-family agents must force HTTP. Moonraker and print-host installations commonly serve plain HTTP, while the generic machine path can request TLS by default. Passing that default through turns a valid connection into an HTTPS request the printer will refuse. The agent therefore must keep the connection on HTTP unless its protocol support changes deliberately and is verified.
Access codes: four coordinated slots
One effective access code can live in four places:
| Slot | Location | Purpose |
|---|---|---|
| Device runtime | MachineObject::access_code |
Code learned from the device. |
| User runtime | MachineObject::user_access_code |
Code entered by the user. |
| Device config | access_code[dev_id] |
Persisted device value. |
| User config | user_access_code[dev_id] |
Persisted user value. |
The effective code prefers the user value when present, then the device value. Keep user input in the user path and device replies in the device path. Crossing those paths obscures which value should win.
set_access_code() deliberately does not save configuration immediately.
Device replies and polls can update it often; forcing a full config write
for each message adds unnecessary work. The normal deferred config save
persists dirty state later. Do not add an eager save just to make this one
path symmetric: device replies and polls update it often, so a config
write per message is wasted work.
Do not erase the user access code when a printer connects. On the LAN reselection path that code can be the only credential that lets the machine pass the access check and receive the status or access-code reply that would refresh it, so erasing it at connection time can leave the machine permanently unable to receive updates. A failed connection is the place to handle a proven bad credential.
Receiving status
An agent receives native status, translates it to the existing payload
shape, and dispatches it to the matching MachineObject. The object
parses the payload and records when it last received an update.
Readiness is four states, and three of them look connected:
stateDiagram-v2
[*] --> SelectedIdOnly
SelectedIdOnly: Selected ID only
SelectedIdOnly: selected_machine names no resolvable object
SelectedIdOnly --> FreshWindow: connect_printer returns success
FreshWindow: Fresh window
FreshWindow: reset seeded the update time - no status yet
FreshWindow --> Connecting: still fresh, still no push-status
Connecting: Connecting
Connecting: is_connecting true - the honest state
Connecting --> Ready: first push-status message parsed
Ready: Status-confirmed ready
Ready: the only state that proves a usable printer
Ready --> Stale: update time ages out
Stale: Stale
Stale: is_connected false
Stale --> Ready: a later status message arrives
note right of FreshWindow
is_connected() is true from here on.
It is a freshness test over the update
time - not proof that any status arrived.
end note
is_connected() is a freshness test over the update time. It does not
describe whether connect_printer() returned success or whether any status
message arrived: reset initializes the update time, creating an initial
freshness window. is_connecting() distinguishes that window from
status-confirmed readiness: while the object is fresh and no push-status
message has arrived, it remains connecting.
Do not treat freshness or a successful connect as proof of readiness. Code that needs a usable printer must wait for status-confirmed readiness, because the fresh window exists before any status has been parsed.
The UI-thread mutation rule belongs to architecture.md under Threading rule.
Dispatch the status callback to the UI thread before changing device maps,
selection, or MachineObject state, because network callbacks may run in a
worker thread and mutating these structures there races with the Device tab.
Sending commands
MachineObject builds the established command JSON and sends it through
the active NetworkAgent. The agent translates it or returns an explicit
unsupported result. It must not report success when no translation exists.
The pushing command exception belongs to architecture.md under Commands
and unsupported work. It asks for status, and a working status stream already
supplies it, so accepting it avoids false unsupported warnings from the Device
Manager's repeated keepalive.
Maintainer constraints
- Preserve same-agent reselection by address, because an agent type can serve more than one printer.
- Preserve the null-agent, no-fallback, and unknown-
coStringrules inarchitecture.md; selection must remain an explicit user or preset choice, and stale state must not belong to a replacement printer agent. - An empty value is the legacy Bambu-or-Orca sentinel, not a missing printer agent.
- Preserve deferred access-code saves and the no-on-connect-erase rule; they prevent excessive config writes and credential-driven status loss.
- Keep Moonraker connections HTTP-only unless the agent's protocol support changes deliberately and is verified.
- Do not treat freshness as proof that status arrived; wait for
status-confirmed readiness. The UI-thread mutation rule is in
architecture.mdunder Threading rule.