mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-04 21:21:54 +02:00
523 lines
18 KiB
Markdown
523 lines
18 KiB
Markdown
# 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`
|
|
|
|
```python
|
|
# 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`
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// Speed modes as toggle buttons:
|
|
// [Silent] [Standard] [Sport] [Ludicrous]
|
|
// Visual feedback for current mode
|
|
// Warning tooltip for Ludicrous mode
|
|
```
|
|
|
|
### 5.4 FanControls Component
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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?
|