mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
18 KiB
18 KiB
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 printhomewhile printing - Could cause issuesmovewhile printing - Dangerousmotors/disable- Causes position loss
Flow:
- Frontend calls endpoint without token
- Backend returns
{"requires_confirmation": true, "token": "abc123", "warning": "This will abort..."} - Frontend shows confirmation dialog
- Frontend calls again with
{"confirm_token": "abc123"} - 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:
- Camera (full width)
- Print controls
- Temperatures (collapsible)
- Speed/Fans (collapsible)
- Movement (collapsible)
- 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
- Backend MQTT commands - Add all control methods
- Backend API endpoints - Create control routes with safety
- Backend camera streaming - MJPEG endpoint
- Frontend ControlPage - Basic structure with tabs
- Frontend CameraFeed - Live stream component
- Frontend PrintControls - Pause/Resume/Stop
- Frontend TemperaturePanel - Temp controls
- Frontend SpeedControl - Speed mode
- Frontend FanControls - Fan sliders
- Frontend LightToggle - Light switch
- Frontend MovementControls - Jog buttons
- Frontend AMSPanel - AMS visualization
- Navigation integration - Add to sidebar
- 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?