Added HMS error handling to printer card

This commit is contained in:
Martin Ziegler
2025-11-29 11:44:32 +01:00
parent 12f763efde
commit 813ce19219
7 changed files with 533 additions and 396 deletions
+4
View File
@@ -236,6 +236,10 @@ class BambuMQTTClient:
and self._previous_gcode_file is not None
)
if is_new_print or is_file_change:
# Clear any old HMS errors when a new print starts
self.state.hms_errors = []
if (is_new_print or is_file_change) and self.on_print_start:
logger.info(
f"[{self.serial_number}] PRINT START detected - file: {current_file}, "
+125
View File
@@ -0,0 +1,125 @@
import { X, AlertTriangle, AlertCircle, Info, ExternalLink } from 'lucide-react';
import type { HMSError } from '../api/client';
interface HMSErrorModalProps {
printerName: string;
errors: HMSError[];
onClose: () => void;
}
// HMS error code descriptions (common ones)
const HMS_DESCRIPTIONS: Record<string, string> = {
'0x20054': 'The heatbed temperature is abnormal. The sensor may be disconnected or damaged.',
'0x50005': 'Motor driver overheated. Let the printer cool down.',
'0x50006': 'Motor driver communication error.',
'0x70001': 'AMS communication error.',
'0x70002': 'AMS filament runout.',
'0x70003': 'AMS filament not detected.',
'0xC0003': 'First layer inspection failed.',
'0xC0004': 'Nozzle clog detected.',
'0xC8000': 'Foreign object detected on print bed.',
'0x50000': 'Motor X axis lost steps.',
'0x50001': 'Motor Y axis lost steps.',
'0x50002': 'Motor Z axis lost steps.',
};
function getSeverityInfo(severity: number): { label: string; color: string; bgColor: string; Icon: typeof AlertTriangle } {
switch (severity) {
case 1:
return { label: 'Fatal', color: 'text-red-500', bgColor: 'bg-red-500/20', Icon: AlertTriangle };
case 2:
return { label: 'Serious', color: 'text-red-400', bgColor: 'bg-red-500/15', Icon: AlertTriangle };
case 3:
return { label: 'Warning', color: 'text-orange-400', bgColor: 'bg-orange-500/20', Icon: AlertCircle };
case 4:
default:
return { label: 'Info', color: 'text-blue-400', bgColor: 'bg-blue-500/20', Icon: Info };
}
}
function getHMSWikiUrl(code: string): string {
// Convert hex code to format used by Bambu Lab wiki
// Example: 0x20054 -> HMS_0200_0005_0004
const codeNum = parseInt(code.replace('0x', ''), 16);
const part1 = ((codeNum >> 24) & 0xFF).toString(16).padStart(2, '0').toUpperCase();
const part2 = ((codeNum >> 16) & 0xFF).toString(16).padStart(2, '0').toUpperCase();
const part3 = ((codeNum >> 8) & 0xFF).toString(16).padStart(2, '0').toUpperCase();
const part4 = (codeNum & 0xFF).toString(16).padStart(2, '0').toUpperCase();
return `https://wiki.bambulab.com/en/x1/troubleshooting/hmscode/HMS_${part1}${part2}_${part3}${part4}`;
}
export function HMSErrorModal({ printerName, errors, onClose }: HMSErrorModalProps) {
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-bambu-dark-secondary rounded-lg shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-bambu-dark-tertiary">
<div className="flex items-center gap-2">
<AlertTriangle className="w-5 h-5 text-orange-400" />
<h2 className="text-lg font-semibold text-white">HMS Errors - {printerName}</h2>
</div>
<button
onClick={onClose}
className="p-1 hover:bg-bambu-dark-tertiary rounded-lg transition-colors"
>
<X className="w-5 h-5 text-bambu-gray" />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
{errors.length === 0 ? (
<div className="text-center py-8 text-bambu-gray">
<AlertCircle className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p>No HMS errors</p>
</div>
) : (
<div className="space-y-3">
{errors.map((error, index) => {
const { label, color, bgColor, Icon } = getSeverityInfo(error.severity);
const description = HMS_DESCRIPTIONS[error.code] || 'Unknown error. Click the link below for details.';
const wikiUrl = getHMSWikiUrl(error.code);
return (
<div
key={`${error.code}-${index}`}
className={`p-4 rounded-lg ${bgColor} border border-white/10`}
>
<div className="flex items-start gap-3">
<Icon className={`w-5 h-5 ${color} flex-shrink-0 mt-0.5`} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className={`font-mono text-sm ${color}`}>{error.code}</span>
<span className={`text-xs px-2 py-0.5 rounded-full ${bgColor} ${color}`}>
{label}
</span>
</div>
<p className="text-sm text-bambu-gray mb-2">{description}</p>
<a
href={wikiUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-bambu-green hover:underline"
>
<ExternalLink className="w-3 h-3" />
View on Bambu Lab Wiki
</a>
</div>
</div>
</div>
);
})}
</div>
)}
</div>
{/* Footer */}
<div className="p-4 border-t border-bambu-dark-tertiary">
<p className="text-xs text-bambu-gray">
HMS (Health Management System) monitors printer health. Clear errors on the printer to dismiss them here.
</p>
</div>
</div>
</div>
);
}
+16 -8
View File
@@ -24,6 +24,7 @@ import { Button } from '../components/Button';
import { ConfirmModal } from '../components/ConfirmModal';
import { FileManagerModal } from '../components/FileManagerModal';
import { MQTTDebugModal } from '../components/MQTTDebugModal';
import { HMSErrorModal } from '../components/HMSErrorModal';
function formatTime(seconds: number): string {
const hours = Math.floor(seconds / 3600);
@@ -88,6 +89,7 @@ function PrinterCard({ printer, hideIfDisconnected }: { printer: Printer; hideIf
const [showMQTTDebug, setShowMQTTDebug] = useState(false);
const [showPowerOnConfirm, setShowPowerOnConfirm] = useState(false);
const [showPowerOffConfirm, setShowPowerOffConfirm] = useState(false);
const [showHMSModal, setShowHMSModal] = useState(false);
const { data: status } = useQuery({
queryKey: ['printerStatus', printer.id],
@@ -173,25 +175,22 @@ function PrinterCard({ printer, hideIfDisconnected }: { printer: Printer; hideIf
</span>
{/* HMS Status Indicator */}
{status?.connected && (
<span
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs ${
<button
onClick={() => setShowHMSModal(true)}
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs cursor-pointer hover:opacity-80 transition-opacity ${
status.hms_errors && status.hms_errors.length > 0
? status.hms_errors.some(e => e.severity <= 2)
? 'bg-red-500/20 text-red-400'
: 'bg-orange-500/20 text-orange-400'
: 'bg-bambu-green/20 text-bambu-green'
}`}
title={
status.hms_errors && status.hms_errors.length > 0
? `${status.hms_errors.length} HMS error(s)`
: 'No HMS errors'
}
title="Click to view HMS errors"
>
<AlertTriangle className="w-3 h-3" />
{status.hms_errors && status.hms_errors.length > 0
? status.hms_errors.length
: 'OK'}
</span>
</button>
)}
<div className="relative">
<Button
@@ -491,6 +490,15 @@ function PrinterCard({ printer, hideIfDisconnected }: { printer: Printer; hideIf
onCancel={() => setShowPowerOffConfirm(false)}
/>
)}
{/* HMS Error Modal */}
{showHMSModal && (
<HMSErrorModal
printerName={printer.name}
errors={status?.hms_errors || []}
onClose={() => setShowHMSModal(false)}
/>
)}
</Card>
);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
<script type="module" crossorigin src="/assets/index-B0NJFRfx.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BKPvAiVm.css">
<script type="module" crossorigin src="/assets/index-8ml33qQA.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BKGWUT04.css">
</head>
<body>
<div id="root"></div>