Last Updated: 2026-03-11
This document explains the multi-printer context system that enables simultaneous connections to multiple printers.
-
Context creation:
PrinterContextManagerissues IDs likecontext-1-<timestamp>wheneverConnectionFlowManagercompletes a connect path. Tabs inPrinterTabsComponentdrive the active context via IPC. -
Backend wiring:
PrinterBackendManagerinstantiates the correct backend (Legacy, Adventurer5M, Adventurer5M Pro, AD5X) per context, exposes capability flags, and registers printer-specific helpers (material station ops, gcode routing, etc.). -
Polling cadence:
MultiContextPollingCoordinatorspins up aPrinterPollingServiceper context. All contexts (active and inactive) poll every 3 seconds to prevent TCP keep-alive failures; cached data is pushed instantly on tab switch.MainProcessPollingCoordinatorremains for legacy single-printer paths. -
Derived monitors:
MultiContextPrintStateMonitor,MultiContextTemperatureMonitor,MultiContextSpoolmanTracker, andMultiContextNotificationCoordinatorlisten for new/remove events to wire per-context instances (print monitors, cooling monitors, spool usage trackers, notification coordinators). Services expect untouchedpolling-updatepayloads. -
Integrations:
Go2rtcServiceprovides unified camera streaming (WebRTC/MSE/MJPEG) via the bundled go2rtc binary, withGo2rtcBinaryManagerhandling binary lifecycle. Ports 1984 (API) and 8555 (WebRTC) are hardcoded, not allocated per context. Discord + desktop notifications, Spoolman usage updates, and eventual web push flows (ai_specs/webui-push-notifications.md) hang off the same events. -
Cleanup: When
PrinterContextManageremitscontext-removed, every coordinator disposes listeners, closes sockets/servers, removes go2rtc streams, and removes spoolman usage trackers/Discord timers to prevent leaks.
User Connect → Discovery → Connection → Context Creation
↓
PrinterContextManager.createContext()
↓
emit('context-created')
↓
PrinterBackendManager.initializeBackend()
↓
emit('backend-initialized')
↓
Service Cascade (polling, monitoring, notifications, camera)
PrinterContextManager.removeContext()
↓
emit('context-removed')
↓
All Coordinators Cleanup:
- Stop polling
- Dispose monitors
- Remove go2rtc streams
- Remove trackers
- Cleanup notifications
Singleton Coordinators + Per-Context Services:
MultiContextPollingCoordinator (singleton)
├── PrinterPollingService (context-1)
├── PrinterPollingService (context-2)
└── PrinterPollingService (context-3)
Key Coordinators:
- MultiContextPollingCoordinator: Manages polling services per context
- MultiContextPrintStateMonitor: Print lifecycle tracking
- MultiContextTemperatureMonitor: Temperature monitoring
- MultiContextSpoolmanTracker: Filament usage tracking
- MultiContextNotificationCoordinator: Notification orchestration
- ContextServiceInitializer: Initializes the polling, monitor, and notification coordinator stack together for a connected context (used by both GUI and headless paths)
- Active Context: 3 seconds
- Inactive Contexts: 3 seconds (prevents TCP keep-alive failures - connections drop if not polled regularly)
- Instant Switch: Cached data emitted immediately on context switch
- Renderer: Only receives active context data
- Services: All coordinators receive all context data
- Discord: Updates all printer statuses
- Notifications: Work for all contexts
PrinterPollingService → data-updated
↓
MultiContextPollingCoordinator → polling-data (contextId, data)
↓
├── Renderer (if active) → polling-update
├── Discord → updatePrinterStatus(contextId)
└── Services → monitoring, tracking, notifications
PrintStateMonitor (foundation)
↓
├── TemperatureMonitoringService (depends on PrintStateMonitor)
├── SpoolmanUsageTracker (depends on PrintStateMonitor)
└── NotificationCoordinator (depends on both)
Initialization Order:
- Create PrintStateMonitor
- Create TemperatureMonitor (with PrintStateMonitor)
- Create SpoolmanTracker (with PrintStateMonitor)
- Create NotificationCoordinator (with both monitors)
interface PrinterContext {
id: string; // "context-{counter}-{timestamp}"
name: string;
printerDetails: PrinterDetails;
backend: BasePrinterBackend | null;
connectionState: ContextConnectionState;
pollingService: PrinterPollingService | null;
notificationCoordinator: PrinterNotificationCoordinator | null;
cameraProxyPort: number | null; // Legacy/unused with Go2rtcService
isActive: boolean;
createdAt: Date;
lastActivity: Date;
activeSpoolId: number | null;
activeSpoolData: ActiveSpoolData | null;
}PrinterContextManager:
createContext(printerDetails): Create with unique IDremoveContext(contextId): Cleanup and removeswitchContext(contextId): Change active contextgetActiveContext(): Get current activeupdateContext(contextId, updates): Partial updates
Events:
context-created: { contextId, contextInfo }context-removed: { contextId, contextInfo }context-switched: { fromId, toId, contextInfo }context-updated: { contextId } - Emitted when printer details are updated (used by PrinterBackendManager, camera-ipc-handler)
Event Consumers:
- MultiContextPollingCoordinator
- MultiContextPrintStateMonitor
- MultiContextTemperatureMonitor
- MultiContextSpoolmanTracker
- MultiContextNotificationCoordinator
- Go2rtcService (via camera-ipc-handler)
- WebUI