13 KiB
Printer agents
Printer agents isolate printer-specific communication from the rest of OrcaSlicer. The GUI and
DeviceManager operate on a shared set of printer operations and device state; a selected printer
agent implements those operations for a particular printer ecosystem. The agent boundary allows
Bambu, Moonraker-based printers, built-in integrations, and Python-provided integrations to use the
same application workflow without making the GUI understand every printer protocol.
The current boundary is an adapter boundary around the existing application contract. In particular,
some request fields and message payloads still use the Bambu-shaped representation that existing
MachineObject and DeviceManager code consumes. The printer agent is responsible for translating
that representation into the protocol spoken by its printer. This is an intentional compatibility
constraint of the current design; the interface is not yet a neutral printer protocol.
The v1 dialect migration path is deliberately narrow. DeviceManager currently speaks the Bambu JSON
dialect because that is the payload shape already used throughout the command and state workflow. The
v1 OrcaPrinterAgent also accepts that Bambu dialect. Its transport path places the small translation
needed for the target printer at deliver_to_sink, keeping the compatibility code at the edge rather
than spreading it through DeviceManager or the agent interface.
The eventual direction is for DeviceManager to produce an Orca JSON dialect. The Bambu agent will then
own the translation from Orca JSON to Bambu's protocol, while OrcaPrinterAgent can forward the Orca
payload directly to its sink. The v1 translation at deliver_to_sink can then be removed without
changing DeviceManager, the command callers, or the rest of the agent workflow.
Components
The system has four relevant layers:
GUI / DeviceManager / MachineObject
|
NetworkAgent
/ \
IPrinterAgent ICloudServiceAgent
| |
printer protocol authentication and cloud services
DeviceManager and MachineObject
DeviceManager owns the application-facing printer workflow. It maintains MachineObject instances,
updates their state, filters devices for the active printer agent, and initiates operations such as
homing, temperature changes, printing, subscriptions, and camera playback.
MachineObject remains the shared state model used by the GUI. It does not contain the implementation
of a printer protocol. When a device is discovered or returned by a cloud query, the device is tagged
with the active printer_agent_id. Device lists and selected-machine operations use that tag to avoid
sending an operation through an agent that does not own the device.
NetworkAgent
NetworkAgent is the façade used by the GUI and DeviceManager. It owns:
- the currently selected
IPrinterAgent; - the registered cloud-service instances, indexed by provider;
- callbacks shared by the active printer agent and the application;
- the forwarding methods for printer commands and cloud operations.
There is one active printer agent for the currently selected printer preset. Switching the preset increments the machine-list generation, disconnects the old printer agent, removes its callbacks, and installs the newly selected agent. The façade then forwards printer operations to that agent.
Cloud operations are selected separately using a provider key. NetworkAgent forwards a cloud request
to the matching ICloudServiceAgent, and forwards cloud camera operations with a device ID. The
printer agent receives a cloud-agent pointer through set_cloud_agent() when it is created, allowing
printer communication to obtain cloud tokens without depending on a concrete cloud implementation.
IPrinterAgent
IPrinterAgent is the printer-facing contract. It covers:
- cloud-relay and direct-LAN message delivery;
- LAN connection, discovery, binding, and certificates;
- printer subscriptions and callbacks;
- print operations;
- filament synchronization;
- camera capability and local camera URL reporting;
- printer command methods.
Concrete built-in implementations include the Bambu wrapper, the native Orca/Moonraker path, and other printer-agent implementations registered by the application. A printer agent may use either the cloud agent, a direct LAN connection, or both.
ICloudServiceAgent
ICloudServiceAgent owns authentication and services provided by a cloud backend. It covers login
state, tokens, user and printer lists, settings synchronization, model services, cloud messages, and
cloud camera operations.
Cloud camera operations are device-scoped:
get_camera_url(dev_id, callback)obtains a stream URL for one device;create_camera_signaling_channel(dev_id)creates signaling for one device where the provider supports it.
This is separate from the local camera URL exposed by IPrinterAgent, which is currently scoped to
the active printer agent because a normal LAN agent represents one physical printer connection.
Agent registration and selection
NetworkAgentFactory maintains the printer-agent registry. Each registry entry contains an agent ID,
a display name, and a factory function. Built-in agents register during application initialization.
Python printer-agent capabilities register dynamically and contribute an agent ID and factory entry.
The selected printer preset contains the printer-agent choice. If no explicit choice is stored, the
application preserves the existing default behavior: Bambu presets select the Bambu agent and other
presets select the native Orca agent. When a preset is changed, GUI_App resolves the effective agent
ID, obtains the corresponding cloud agent, creates the printer agent through the registry, and installs
it in NetworkAgent.
The registry rejects conflicting agent IDs. This matters for Python plugins because an agent ID is the stable identity used by presets and device ownership; two enabled plugin capabilities must not claim the same ID.
Message and command flow
There are two low-level message paths:
send_message()publishes a command through the printer's cloud relay;send_message_to_printer()sends a command directly to the printer over the LAN path.
Both paths accept a JSON string, quality-of-service and flag values, and return the existing network status code domain. The agent owns the conversion from that JSON contract to its native transport.
The typed command_* methods are the application-facing convenience layer. The five generic defaults
currently implemented by IPrinterAgent construct the existing JSON dialect and route through the
same message path:
| Method | Default operation |
|---|---|
command_xyz_abs() |
Send G90 for absolute positioning |
command_auto_leveling() |
Send G29 for bed leveling |
command_go_home() |
Use the supported homing operation or send G28 |
command_set_bed() |
Use the supported bed control or send M140 |
command_set_nozzle() |
Send M104 for nozzle temperature |
These are compatibility defaults for common printer workflows, not a guarantee that every firmware
implements every command identically. An agent can override a method when its protocol needs another
operation. For example, a Klipper configuration may use BED_MESH_CALIBRATE instead of G29.
The remaining common command methods default to ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED because their
existing behavior is vendor-specific or has no portable implementation:
- AMS RFID refresh;
- AMS calibration;
- AMS tray selection;
- camera start;
- axis control.
The methods remain on the common interface so an agent that supports them can override them explicitly.
sequence_id remains part of the command contract because DeviceManager creates and tracks it as
the command ID.
Device ownership and stale responses
Printer-agent ownership is represented by printer_agent_id on device records and MachineObject
instances. The active agent ID is attached when a device is discovered, returned by a cloud list, or
reused after a preset switch. Local-machine configuration also persists the agent ID so a saved LAN
device is not silently reused by an unrelated agent.
Cloud printer-list responses carry three pieces of request context added by NetworkAgent:
provider cloud provider used for the request
agent_id active printer agent when the request was made
generation machine-list generation when the request was made
DeviceManager accepts the response only when those values still match the current provider, active
agent, and generation. This prevents a slow response from the previous preset or provider from
repopulating the current device list.
The provider mapping is currently selected by GUI_App: the Bambu agent maps to the Bambu cloud
provider and other agents map to the Orca cloud provider. The generation check protects that existing
selection from races; it does not make cloud-provider ownership intrinsic to an agent. Cloud-printer
ownership and the broader Orca cloud services are therefore still separate architectural concerns.
Python printer agents
PrinterAgentPluginCapability implements IPrinterAgent directly. The live capability object is
registered with NetworkAgentFactory and handed out as the printer agent when its agent ID is selected.
The plugin receives the selected ICloudServiceAgent through set_cloud_agent() just like a built-in
printer agent.
Python plugins must implement the core communication and lifecycle methods required by the interface, including agent metadata, printer connection, discovery callbacks, and the two message-send methods. Methods that are meaningful only to a particular printer are optional overrides where the C++ base class provides a default.
All ten command_* methods are available in the Python binding and in the trampoline. Their override
status is intentionally optional:
- the five generic commands use the C++ default when Python does not override them;
- the five vendor-specific commands return
NOT_SUPPORTEDunless Python supplies an implementation; - a Python implementation can replace either behavior for its own protocol.
The Python camera binding exposes HTTP, HTTPS, RTSP, and HTTP-snapshot modes. WebRTC remains a built-in C++ camera mode, but is not exposed as a Python mode because the current Python capability does not provide the corresponding cloud signaling-channel contract.
Camera playback boundary
The camera stream mode describes how a stream is obtained; it does not by itself define ownership of
the wxWidgets view that renders it. MediaPlayCtrl selects and tears down the active backend, while
the wx parent owns the child window or renderer. This is important because a web view, native media
control, and frame-based/WebRTC renderer have different wx window-lifetime requirements.
Cloud URL and signaling requests are routed through NetworkAgent to the cloud provider selected for
the device. Local URL requests are routed to the active printer agent. The distinction keeps cloud
account services device-scoped while preserving the current one-LAN-agent/one-printer model.
Compatibility constraints
The printer-agent boundary intentionally preserves several existing application contracts:
- Bambu-shaped JSON is still the shared command representation;
- existing network status codes are reused, with Orca-specific unsupported/capability errors added in the Orca-reserved range;
MachineObjectremains the shared device-state model;- preset and local-machine data retain compatibility with the existing agent-selection behavior;
- Python plugins use the existing capability and pybind11 registration system.
The agent abstraction is therefore responsible for containing vendor differences, not for pretending
that all vendor protocols are identical. The planned Orca JSON dialect is the protocol-neutral command
model for the DeviceManager/agent boundary. Once it is introduced, Bambu-specific translation remains
inside the Bambu agent and the Orca agent's v1 sink adapter can be removed as a self-contained cleanup.
Main implementation locations
IPrinterAgent— printer-agent contract and generic command defaultsICloudServiceAgent— cloud service and per-device cloud camera contractNetworkAgent— façade and dispatch between active agentsNetworkAgentFactory— built-in and Python agent registryDeviceManager— device ownership, filtering, and stale-response checksPrinterAgentPluginCapability— Python bindingsMediaPlayCtrl— camera backend selection and playback lifecycle