mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
- Show detailed printer status stages on printer cards
- Add getStatusDisplay() helper in frontend to format status nicely
- Add get_derived_status_name() in backend to compute status from:
- stg_cur calibration stages (bed leveling, vibration comp, etc.)
- Temperature data when stg_cur unavailable (heating heatbed/nozzle)
- Include stg_cur and stg_cur_name in WebSocket broadcasts
- Update broadcast status key to detect heating phase changes
Printer cards now show: "Heating heatbed", "Heating nozzle", "Preparing",
"Auto bed leveling", "Cleaning nozzle tip", and 60+ other stage names
instead of just "Printing" or "Idle".
This commit is contained in:
@@ -28,8 +28,7 @@ from backend.app.services.bambu_ftp import (
|
||||
get_storage_info_async,
|
||||
list_files_async,
|
||||
)
|
||||
from backend.app.services.bambu_mqtt import get_stage_name
|
||||
from backend.app.services.printer_manager import printer_manager
|
||||
from backend.app.services.printer_manager import get_derived_status_name, printer_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/printers", tags=["printers"])
|
||||
@@ -300,7 +299,7 @@ async def get_printer_status(printer_id: int, db: AsyncSession = Depends(get_db)
|
||||
nozzles=nozzles,
|
||||
print_options=print_options,
|
||||
stg_cur=state.stg_cur,
|
||||
stg_cur_name=get_stage_name(state.stg_cur) if state.stg_cur >= 0 else None,
|
||||
stg_cur_name=get_derived_status_name(state),
|
||||
stg=state.stg,
|
||||
airduct_mode=state.airduct_mode,
|
||||
speed_level=state.speed_level,
|
||||
|
||||
+6
-1
@@ -228,9 +228,14 @@ async def on_printer_status_change(printer_id: int, state: PrinterState):
|
||||
f"Auto-detected dual-nozzle printer {printer_id}, updated nozzle_count=2"
|
||||
)
|
||||
|
||||
# Include target temps for heating phase detection
|
||||
bed_target = round(temps.get("bed_target", 0))
|
||||
nozzle_target = round(temps.get("nozzle_target", 0))
|
||||
|
||||
status_key = (
|
||||
f"{state.connected}:{state.state}:{state.progress}:{state.layer_num}:"
|
||||
f"{nozzle_temp}:{bed_temp}:{nozzle_2_temp}:{chamber_temp}"
|
||||
f"{nozzle_temp}:{bed_temp}:{nozzle_2_temp}:{chamber_temp}:"
|
||||
f"{state.stg_cur}:{bed_target}:{nozzle_target}"
|
||||
)
|
||||
if _last_status_broadcast.get(printer_id) == status_key:
|
||||
return # No change, skip broadcast
|
||||
|
||||
@@ -5,7 +5,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.models.printer import Printer
|
||||
from backend.app.services.bambu_mqtt import BambuMQTTClient, MQTTLogEntry, PrinterState
|
||||
from backend.app.services.bambu_mqtt import BambuMQTTClient, MQTTLogEntry, PrinterState, get_stage_name
|
||||
|
||||
|
||||
class PrinterManager:
|
||||
@@ -280,6 +280,55 @@ class PrinterManager:
|
||||
return result
|
||||
|
||||
|
||||
def get_derived_status_name(state: PrinterState) -> str | None:
|
||||
"""
|
||||
Compute a human-readable status name based on printer state.
|
||||
|
||||
Uses stg_cur when available, otherwise derives status from temperature data
|
||||
when the printer is heating before a print starts.
|
||||
"""
|
||||
# If we have a valid calibration stage, use it
|
||||
if state.stg_cur >= 0:
|
||||
return get_stage_name(state.stg_cur)
|
||||
|
||||
# If not in RUNNING state, no derived status needed
|
||||
if state.state != "RUNNING":
|
||||
return None
|
||||
|
||||
# Check if we're in an early phase where temperatures are heating
|
||||
temps = state.temperatures or {}
|
||||
progress = state.progress or 0
|
||||
|
||||
# Only derive heating status when progress is very low (< 2%)
|
||||
# This indicates we're in the preparation phase, not actually printing
|
||||
if progress >= 2:
|
||||
return None
|
||||
|
||||
# Check bed temperature - if target is set and current is significantly below
|
||||
bed_temp = temps.get("bed", 0)
|
||||
bed_target = temps.get("bed_target", 0)
|
||||
|
||||
# Check nozzle temperature
|
||||
nozzle_temp = temps.get("nozzle", 0)
|
||||
nozzle_target = temps.get("nozzle_target", 0)
|
||||
|
||||
# Temperature thresholds: consider "heating" if more than 10°C below target
|
||||
TEMP_THRESHOLD = 10
|
||||
|
||||
# Determine what's heating (prioritize bed since it takes longer)
|
||||
if bed_target > 30 and (bed_target - bed_temp) > TEMP_THRESHOLD:
|
||||
return "Heating heatbed"
|
||||
elif nozzle_target > 30 and (nozzle_target - nozzle_temp) > TEMP_THRESHOLD:
|
||||
return "Heating nozzle"
|
||||
|
||||
# If targets are set but we're close to them, we might be in final prep
|
||||
if bed_target > 30 or nozzle_target > 30:
|
||||
if progress == 0 and state.layer_num == 0:
|
||||
return "Preparing"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def printer_state_to_dict(state: PrinterState, printer_id: int | None = None) -> dict:
|
||||
"""Convert PrinterState to a JSON-serializable dict."""
|
||||
# Parse AMS data from raw_data
|
||||
@@ -383,6 +432,9 @@ def printer_state_to_dict(state: PrinterState, printer_id: int | None = None) ->
|
||||
"ams_extruder_map": ams_extruder_map,
|
||||
# WiFi signal strength
|
||||
"wifi_signal": state.wifi_signal,
|
||||
# Calibration stage tracking
|
||||
"stg_cur": state.stg_cur,
|
||||
"stg_cur_name": get_derived_status_name(state),
|
||||
}
|
||||
# Add cover URL if there's an active print and printer_id is provided
|
||||
if printer_id and state.state == "RUNNING" and state.gcode_file:
|
||||
|
||||
@@ -504,6 +504,34 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
|
||||
type SortOption = 'name' | 'status' | 'model' | 'location';
|
||||
type ViewMode = 'expanded' | 'compact';
|
||||
|
||||
/**
|
||||
* Get human-readable status display text for a printer.
|
||||
* Uses stg_cur_name for detailed calibration/preparation stages,
|
||||
* otherwise formats the gcode_state nicely.
|
||||
*/
|
||||
function getStatusDisplay(state: string | null | undefined, stg_cur_name: string | null | undefined): string {
|
||||
// If we have a specific stage name (calibration, heating, etc.), use it
|
||||
if (stg_cur_name) {
|
||||
return stg_cur_name;
|
||||
}
|
||||
|
||||
// Format the gcode_state nicely
|
||||
switch (state) {
|
||||
case 'RUNNING':
|
||||
return 'Printing';
|
||||
case 'PAUSE':
|
||||
return 'Paused';
|
||||
case 'FINISH':
|
||||
return 'Finished';
|
||||
case 'FAILED':
|
||||
return 'Failed';
|
||||
case 'IDLE':
|
||||
return 'Idle';
|
||||
default:
|
||||
return state ? state.charAt(0) + state.slice(1).toLowerCase() : 'Idle';
|
||||
}
|
||||
}
|
||||
|
||||
function PrinterCard({
|
||||
printer,
|
||||
hideIfDisconnected,
|
||||
@@ -879,7 +907,7 @@ function PrinterCard({
|
||||
<span className="text-xs text-white">{Math.round(status.progress || 0)}%</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-bambu-gray capitalize">{status.state?.toLowerCase() || 'Idle'}</p>
|
||||
<p className="text-xs text-bambu-gray">{getStatusDisplay(status.state, status.stg_cur_name)}</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -897,7 +925,7 @@ function PrinterCard({
|
||||
<div className="flex-1 min-w-0">
|
||||
{status.current_print && status.state === 'RUNNING' ? (
|
||||
<>
|
||||
<p className="text-sm text-bambu-gray mb-1">Printing</p>
|
||||
<p className="text-sm text-bambu-gray mb-1">{status.stg_cur_name || 'Printing'}</p>
|
||||
<p className="text-white text-sm mb-2 truncate">
|
||||
{status.subtask_name || status.current_print}
|
||||
</p>
|
||||
@@ -933,8 +961,8 @@ function PrinterCard({
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-bambu-gray mb-1">Status</p>
|
||||
<p className="text-white text-sm mb-2 capitalize">
|
||||
{status.state?.toLowerCase() || 'Idle'}
|
||||
<p className="text-white text-sm mb-2">
|
||||
{getStatusDisplay(status.state, status.stg_cur_name)}
|
||||
</p>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex-1 bg-bambu-dark-tertiary rounded-full h-2 mr-3">
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-Dszw0rVS.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-B7Qp8Coa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CbCN6LSA.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user