PLAN_PRINTER_CONTROL.md 19 KB

Full Printer Control - Implementation Plan

Overview

Add a dedicated Control Page (/control) with full printer control capabilities, including:

  • Live camera feed
  • Print control (pause/resume/stop)
  • Temperature control (bed, nozzle, chamber)
  • Speed adjustment
  • Fan control
  • Light control
  • Axis movement
  • AMS visualization and operations

Phase 1: Backend - MQTT Control Commands

1.1 Add Control Methods to bambu_mqtt.py

# Print Control
async def pause_print(self) -> bool
async def resume_print(self) -> bool
# stop_print() already exists

# Temperature Control
async def set_bed_temperature(self, target: int) -> bool
async def set_nozzle_temperature(self, target: int, nozzle: int = 0) -> bool

# Speed Control
async def set_print_speed(self, mode: int) -> bool  # 1=silent, 2=standard, 3=sport, 4=ludicrous

# Fan Control
async def set_part_fan(self, speed: int) -> bool  # 0-255
async def set_aux_fan(self, speed: int) -> bool   # 0-255
async def set_chamber_fan(self, speed: int) -> bool  # 0-255

# Light Control
async def set_chamber_light(self, on: bool) -> bool

# Movement Control
async def home_axes(self, axes: str = "XYZ") -> bool
async def move_axis(self, axis: str, distance: float, speed: int = 3000) -> bool
async def disable_motors(self) -> bool

# AMS Control
async def ams_load_filament(self, tray_id: int) -> bool
async def ams_unload_filament(self) -> bool

# G-code
async def send_gcode(self, gcode: str) -> bool

1.2 MQTT Command Formats

Command JSON Payload
Pause {"print": {"sequence_id": "0", "command": "pause"}}
Resume {"print": {"sequence_id": "0", "command": "resume"}}
Bed Temp {"print": {"sequence_id": "0", "command": "gcode_line", "param": "M140 S{temp}"}}
Nozzle Temp {"print": {"sequence_id": "0", "command": "gcode_line", "param": "M104 S{temp}"}}
Print Speed {"print": {"sequence_id": "0", "command": "print_speed", "param": "{1-4}"}}
Fan (P1=part, P2=aux, P3=chamber) {"print": {"sequence_id": "0", "command": "gcode_line", "param": "M106 P{n} S{0-255}"}}
Light On {"system": {"sequence_id": "0", "command": "ledctrl", "led_node": "chamber_light", "led_mode": "on", ...}}
Home {"print": {"sequence_id": "0", "command": "gcode_line", "param": "G28 {axes}"}}
Move {"print": {"sequence_id": "0", "command": "gcode_line", "param": "G91\nG0 {axis}{dist} F{speed}\nG90"}}
AMS Load {"print": {"sequence_id": "0", "command": "ams_change_filament", "target": {tray_id}}}
AMS Unload {"print": {"sequence_id": "0", "command": "ams_change_filament", "target": 255}}

1.3 Model-Specific Handling

  • P1/A1 series: Use blocking temp commands (M109/M190) instead of M104/M140
  • H2D: Handle dual nozzle targeting
  • Store printer model in status for frontend to adapt UI

Phase 2: Backend - Control API Endpoints

2.1 New Routes in backend/app/api/routes/printer_control.py

# Print Control
POST /api/v1/printers/{id}/control/pause
POST /api/v1/printers/{id}/control/resume
POST /api/v1/printers/{id}/control/stop

# Temperature
POST /api/v1/printers/{id}/control/temperature/bed
  Body: {"target": 60}
POST /api/v1/printers/{id}/control/temperature/nozzle
  Body: {"target": 200, "nozzle": 0}

# Speed
POST /api/v1/printers/{id}/control/speed
  Body: {"mode": 2}  # 1-4

# Fans
POST /api/v1/printers/{id}/control/fan/part
  Body: {"speed": 255}  # 0-255
POST /api/v1/printers/{id}/control/fan/aux
POST /api/v1/printers/{id}/control/fan/chamber

# Light
POST /api/v1/printers/{id}/control/light
  Body: {"on": true}

# Movement
POST /api/v1/printers/{id}/control/home
  Body: {"axes": "XYZ"}  # optional, default all
POST /api/v1/printers/{id}/control/move
  Body: {"axis": "Z", "distance": 10, "speed": 600}
POST /api/v1/printers/{id}/control/motors/disable

# AMS
POST /api/v1/printers/{id}/control/ams/load
  Body: {"tray_id": 0}
POST /api/v1/printers/{id}/control/ams/unload

# G-code (advanced)
POST /api/v1/printers/{id}/control/gcode
  Body: {"command": "G28"}

2.2 Safety Confirmations

Commands that need confirmation token (generated and validated server-side):

  • stop - Aborts print
  • home while printing - Could cause issues
  • move while printing - Dangerous
  • motors/disable - Causes position loss

Flow:

  1. Frontend calls endpoint without token
  2. Backend returns {"requires_confirmation": true, "token": "abc123", "warning": "This will abort..."}
  3. Frontend shows confirmation dialog
  4. Frontend calls again with {"confirm_token": "abc123"}
  5. Backend validates token and executes

Phase 3: Backend - Camera Streaming

3.1 Streaming Approach

Option A: MJPEG Stream (simpler)

  • Backend captures RTSP frames via ffmpeg
  • Serves as MJPEG stream at /api/v1/printers/{id}/camera/stream
  • Frontend uses <img src="..."> with streaming

Option B: WebSocket Frames (more control)

  • Backend sends JPEG frames via WebSocket
  • Frontend renders on canvas
  • Allows frame rate control, pause/resume

Recommended: Option A (MJPEG) - Simpler, works in all browsers

3.2 Implementation

# backend/app/api/routes/camera.py

@router.get("/printers/{printer_id}/camera/stream")
async def camera_stream(printer_id: int):
    """Stream camera as MJPEG"""
    printer = get_printer(printer_id)

    async def generate():
        process = await asyncio.create_subprocess_exec(
            'ffmpeg',
            '-rtsp_transport', 'tcp',
            '-i', f'rtsps://bblp:{printer.access_code}@{printer.ip_address}:{port}/streaming/live/1',
            '-f', 'mjpeg',
            '-q:v', '5',
            '-r', '15',  # 15 fps
            '-',
            stdout=asyncio.subprocess.PIPE
        )

        while True:
            frame = await read_jpeg_frame(process.stdout)
            if not frame:
                break
            yield (
                b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n'
            )

    return StreamingResponse(
        generate(),
        media_type='multipart/x-mixed-replace; boundary=frame'
    )

@router.get("/printers/{printer_id}/camera/snapshot")
async def camera_snapshot(printer_id: int):
    """Get single camera frame"""
    # Use existing camera.py capture_frame logic

3.3 Camera Ports by Model

Model Port Protocol
X1/X1C/H2D 322 RTSPS
P1/P1S/P1P 6000 RTSPS
A1/A1 Mini 6000 RTSPS

Phase 4: Frontend - Control Page

4.1 Page Structure

/control
├── ControlPage.tsx           # Main page with printer tabs
├── components/
│   ├── CameraFeed.tsx        # Live video stream
│   ├── PrintControls.tsx     # Pause/Resume/Stop + progress
│   ├── TemperaturePanel.tsx  # Bed/Nozzle/Chamber controls
│   ├── SpeedControl.tsx      # Speed mode selector
│   ├── FanControls.tsx       # Part/Aux/Chamber fan sliders
│   ├── LightToggle.tsx       # Chamber light on/off
│   ├── MovementControls.tsx  # Home + XYZ jog buttons
│   ├── AMSPanel.tsx          # AMS visualization + load/unload
│   └── ConfirmDialog.tsx     # Safety confirmation modal

4.2 Layout (Desktop)

┌──────────────────────────────────────────────────────────────────┐
│  [Printer 1] [Printer 2] [Printer 3]                    tabs     │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────────┐  ┌────────────────────────────────┐│
│  │                         │  │  Print Status                  ││
│  │     Camera Feed         │  │  ┌────────────────────────┐    ││
│  │     (16:9 aspect)       │  │  │ State: RUNNING         │    ││
│  │                         │  │  │ File: benchy.3mf       │    ││
│  │                         │  │  │ Progress: ████████░░ 78%│   ││
│  │                         │  │  │ Layer: 156/200         │    ││
│  │                         │  │  │ Time: 45min remaining  │    ││
│  │                         │  │  └────────────────────────┘    ││
│  │                         │  │                                ││
│  │  [⏸ Pause] [■ Stop]     │  │  [⏸ Pause] [▶ Resume] [■ Stop]││
│  └─────────────────────────┘  └────────────────────────────────┘│
│                                                                  │
│  ┌─────────────────────────┐  ┌────────────────────────────────┐│
│  │  Temperatures           │  │  Speed & Fans                  ││
│  │  ┌───────────────────┐  │  │  Speed: [Silent][Std][Sport][!]││
│  │  │ 🛏️ Bed             │  │  │                                ││
│  │  │ 60°C → 60°C       │  │  │  Part Fan:    ████████░░ 80%  ││
│  │  │ [-] [target] [+]  │  │  │  Aux Fan:     ░░░░░░░░░░  0%  ││
│  │  ├───────────────────┤  │  │  Chamber Fan: ████░░░░░░ 40%  ││
│  │  │ 🔥 Nozzle          │  │  └────────────────────────────────┘│
│  │  │ 205°C → 210°C     │  │                                    │
│  │  │ [-] [target] [+]  │  │  ┌────────────────────────────────┐│
│  │  ├───────────────────┤  │  │  Movement                      ││
│  │  │ 📦 Chamber: 35°C   │  │  │        [Y+]                   ││
│  │  └───────────────────┘  │  │  [X-]  [Home]  [X+]            ││
│  └─────────────────────────┘  │        [Y-]       [Z+][Z-]     ││
│                               │  [Disable Motors]              ││
│  ┌─────────────────────────┐  └────────────────────────────────┘│
│  │  💡 Light  [ON] / [OFF] │                                    │
│  └─────────────────────────┘                                    │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────────┐│
│  │  AMS                                                         ││
│  │  ┌────┐ ┌────┐ ┌────┐ ┌────┐    [Load] [Unload]             ││
│  │  │ 1  │ │ 2  │ │ 3  │ │ 4  │                                ││
│  │  │ 🔴 │ │ 🔵 │ │ ⚪ │ │ ⬛ │    Selected: Slot 1 (PLA Red)  ││
│  │  │80% │ │45% │ │100%│ │ -- │                                ││
│  │  └────┘ └────┘ └────┘ └────┘                                ││
│  └──────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘

4.3 Mobile Layout

Stacked vertically:

  1. Camera (full width)
  2. Print controls
  3. Temperatures (collapsible)
  4. Speed/Fans (collapsible)
  5. Movement (collapsible)
  6. AMS (collapsible)

4.4 State Management

Use React Query for:

  • Printer status (already exists, real-time via WebSocket)
  • Control mutations with optimistic updates

    // Example mutation
    const pausePrint = useMutation({
    mutationFn: (printerId: number) =>
    api.post(`/printers/${printerId}/control/pause`),
    onSuccess: () => {
    // Optimistic: printer status will update via WebSocket
    }
    });
    

Phase 5: Component Details

5.1 CameraFeed Component

interface CameraFeedProps {
  printerId: number;
  enabled: boolean;
}

// Features:
// - MJPEG stream from /api/v1/printers/{id}/camera/stream
// - Fallback to static thumbnail if stream fails
// - Loading state with skeleton
// - Click to fullscreen
// - Optional: snapshot button

5.2 TemperaturePanel Component

interface TemperaturePanelProps {
  printerId: number;
  bed: { current: number; target: number };
  nozzle: { current: number; target: number };
  nozzle2?: { current: number; target: number }; // H2D
  chamber?: number;
}

// Features:
// - Visual temperature bars (current vs target)
// - Input field or +/- buttons for target
// - Presets: Off (0), PLA (60/200), PETG (70/230), ABS (90/250)
// - Debounced API calls (don't spam on rapid clicks)
// - Disable controls during print (optional setting)

5.3 SpeedControl Component

// Speed modes as toggle buttons:
// [Silent] [Standard] [Sport] [Ludicrous]
// Visual feedback for current mode
// Warning tooltip for Ludicrous mode

5.4 FanControls Component

// Sliders for each fan (0-100%)
// Convert to 0-255 for API
// Real-time value display
// Disable chamber fan if not available (check model)

5.5 MovementControls Component

// Grid layout:
//        [Y+10] [Y+1]
// [X-10] [X-1] [Home] [X+1] [X+10]
//        [Y-1] [Y-10]
//                    [Z+10] [Z+1] [Z-1] [Z-10]
//
// [Disable Motors] button with confirmation
// Warning: "Movement controls disabled during print" overlay

5.6 AMSPanel Component

// Visual representation matching Bambu style:
// - 4 slots per AMS unit
// - Color-coded by filament
// - Percentage remaining
// - Active slot indicator (animated)
// - Click to select slot
// - [Load Selected] [Unload] buttons
// - Support for external spool indicator

Phase 6: Safety Features

6.1 Confirmation Dialogs

Required for:

  • Stop Print: "This will abort the current print. Are you sure?"
  • Home During Print: "Homing during a print is not recommended. Continue?"
  • Move During Print: "Manual movement during printing can damage your print. Continue?"
  • Disable Motors: "This will disable motors and lose position. Home before next print."
  • High Temperatures: Warning for temps > 260°C nozzle or > 100°C bed

6.2 State-Based Disabling

Control IDLE RUNNING PAUSE FINISH
Pause
Resume
Stop
Temp Control ⚠️
Speed
Fans ⚠️
Movement ⚠️
AMS Load

⚠️ = Allowed with warning


Phase 7: WebSocket Updates

7.1 Extended Status Data

Ensure these fields are included in printer status broadcasts:

interface PrinterStatus {
  // Existing
  state: string;
  progress: number;
  remaining_time: number;
  temperatures: {...};

  // Add for control page
  print_speed_mode: number;      // 1-4
  fan_speeds: {
    part: number;      // 0-255
    aux: number;
    chamber: number;
  };
  light_state: boolean;
  ams_status: {
    units: [{
      id: number;
      trays: [{
        id: number;
        color: string;      // hex
        type: string;       // PLA, PETG, etc
        remaining: number;  // percentage
        active: boolean;
      }];
    }];
    current_tray: number;
  };
  position?: {
    x: number;
    y: number;
    z: number;
  };
}

Implementation Order

  1. Backend MQTT commands - Add all control methods
  2. Backend API endpoints - Create control routes with safety
  3. Backend camera streaming - MJPEG endpoint
  4. Frontend ControlPage - Basic structure with tabs
  5. Frontend CameraFeed - Live stream component
  6. Frontend PrintControls - Pause/Resume/Stop
  7. Frontend TemperaturePanel - Temp controls
  8. Frontend SpeedControl - Speed mode
  9. Frontend FanControls - Fan sliders
  10. Frontend LightToggle - Light switch
  11. Frontend MovementControls - Jog buttons
  12. Frontend AMSPanel - AMS visualization
  13. Navigation integration - Add to sidebar
  14. Testing & refinement - All printer models

Files to Create/Modify

New Files

backend/app/api/routes/printer_control.py
backend/app/api/routes/camera.py
backend/app/schemas/control.py
frontend/src/pages/ControlPage.tsx
frontend/src/components/control/CameraFeed.tsx
frontend/src/components/control/PrintControls.tsx
frontend/src/components/control/TemperaturePanel.tsx
frontend/src/components/control/SpeedControl.tsx
frontend/src/components/control/FanControls.tsx
frontend/src/components/control/LightToggle.tsx
frontend/src/components/control/MovementControls.tsx
frontend/src/components/control/AMSPanel.tsx
frontend/src/components/control/ConfirmDialog.tsx

Modified Files

backend/app/services/bambu_mqtt.py     # Add control methods
backend/app/api/routes/__init__.py     # Register new routes
backend/app/main.py                    # Include new router
backend/app/schemas/printer.py         # Extend status schema
frontend/src/App.tsx                   # Add route
frontend/src/components/Sidebar.tsx    # Add nav item
frontend/src/api/client.ts             # Add control API calls

Estimated Scope

  • Backend: ~500 lines new code
  • Frontend: ~1500 lines new code
  • Total: ~2000 lines

Ready to begin implementation?