Преглед изворни кода

Add H2C dual nozzle variant O1C2 model support (#489)

  The H2C dual nozzle variant reports model code O1C2 via MQTT, but only
  O1C was recognized. This caused the camera to use the wrong protocol
  (chamber image on port 6000 instead of RTSP on port 322), producing a
  reconnect loop. Added O1C2 to all model ID maps across 8 files.
maziggy пре 2 месеци
родитељ
комит
240eeb4f5d

+ 1 - 0
CHANGELOG.md

@@ -22,6 +22,7 @@ All notable changes to Bambuddy will be documented in this file.
 =======
 - **SpoolBuddy Scale Tare & Calibration Not Applied** — The SpoolBuddy scale tare and calibrate buttons on the Settings page queued commands but never executed them. Five bugs in the chain: (1) the daemon received the `tare` command via heartbeat but never called `scale.tare()` — a comment said "need cross-task communication" but the ScaleReader was already available in the shared dict; (2) no API endpoint existed for the daemon to report the new tare offset back to the backend database, so tare results were lost; (3) when calibration values changed in heartbeat responses, the daemon updated its config object but never called `scale.update_calibration()`, so the ScaleReader kept using its initial values forever; (4) the heartbeat response that delivered the tare command still contained pre-tare calibration values, which immediately overwrote the new tare offset back to zero; (5) the `set-factor` endpoint computed `calibration_factor` using the DB `tare_offset`, which could be stale or zero if the tare hadn't persisted yet — producing a wildly wrong factor (e.g., 5000g displayed with empty scale). Added a `POST /devices/{device_id}/calibration/set-tare` endpoint and `update_tare()` API client method. The heartbeat loop now executes `scale.tare()` when the tare command is received, persists the result via the new endpoint, propagates calibration changes to the ScaleReader instance, and skips calibration sync on the heartbeat cycle that delivers a tare command. The calibration flow now captures the raw ADC at tare time and sends it alongside the loaded-weight ADC in step 2, so the factor is computed from the actual tare reference rather than the DB value — making calibration self-contained and independent of the tare persistence round-trip. The calibration weight input uses a compact touch-friendly numpad since the RPi kiosk has no physical keyboard.
 - **A1 Mini Shows "Unknown" Status After MQTT Payload Decode Failure** ([#549](https://github.com/maziggy/bambuddy/issues/549)) — Some printer firmware versions (observed on A1 Mini 01.07.02.00) occasionally send MQTT payloads containing non-UTF-8 bytes. The `_on_message` handler called `msg.payload.decode()` (strict UTF-8), and the resulting `UnicodeDecodeError` was not caught — only `json.JSONDecodeError` was handled. The entire message was silently dropped, causing printer status to show "unknown", temperatures to read 0°C, and AMS data to disappear. Now catches `UnicodeDecodeError` and falls back to `decode(errors="replace")`, which substitutes invalid bytes with U+FFFD while keeping the JSON structure intact. Logs a warning for diagnostics.
+- **H2C Dual Nozzle Variant (O1C2) Not Recognized** ([#489](https://github.com/maziggy/bambuddy/issues/489)) — The H2C dual nozzle variant reports model code `O1C2` via MQTT, but only `O1C` was in the recognized model maps. This caused the camera to use the wrong protocol (chamber image on port 6000 instead of RTSP on port 322) — the printer immediately closed the connection, producing a reconnect loop. Also affected model display names, chamber temperature support detection, linear rail classification, and virtual printer model mapping. Added `O1C2` to all model ID maps across backend and frontend.
 - **SpoolBuddy NFC Reader Fails to Detect Tags** — The PN5180 NFC reader had two polling issues. First, each `activate_type_a()` call that returned `None` (no tag) corrupted the PN5180 transceive state — subsequent calls silently failed even when a tag was physically present, making it impossible to detect tags placed after startup (only tags already on the reader during init were detected). Fixed by performing a full hardware reset (RST pin toggle + RF re-init, ~240ms) before every idle poll, giving a ~1.8 Hz effective poll rate. Second, after a successful SELECT the card stayed in ACTIVE state and ignored subsequent WUPA/REQA, causing false "tag removed" events after ~1 second. Fixed with a light RF off/on cycle (13ms) before each poll when a tag is present, resetting the card to IDLE for re-selection. Also added error-based auto-recovery (full hardware reset after 10 consecutive poll exceptions), periodic status logging every 60 seconds, and accurate heartbeat reporting of NFC/scale health.
 >>>>>>> 6912344 (  Fix A1 Mini "unknown" status from non-UTF-8 MQTT payload (#549))
 

+ 2 - 2
backend/app/services/camera.py

@@ -72,7 +72,7 @@ def supports_rtsp(model: str | None) -> bool:
       - BL-P001: X1/X1C
       - C13: X1E
       - O1D: H2D
-      - O1C: H2C
+      - O1C, O1C2: H2C
       - O1S: H2S
       - O1E, O2D: H2D Pro
       - N7: P2S
@@ -83,7 +83,7 @@ def supports_rtsp(model: str | None) -> bool:
         if model_upper.startswith(("X1", "H2", "P2")):
             return True
         # Internal codes for RTSP models
-        if model_upper in ("BL-P001", "C13", "O1D", "O1C", "O1S", "O1E", "O2D", "N7"):
+        if model_upper in ("BL-P001", "C13", "O1D", "O1C", "O1C2", "O1S", "O1E", "O2D", "N7"):
             return True
     # A1/P1 and unknown models use chamber image protocol
     return False

+ 1 - 0
backend/app/services/printer_manager.py

@@ -31,6 +31,7 @@ CHAMBER_TEMP_SUPPORTED_MODELS = frozenset(
         "C13",  # X1E
         "O1D",  # H2D
         "O1C",  # H2C
+        "O1C2",  # H2C (dual nozzle variant)
         "O1S",  # H2S
         "O1E",  # H2D Pro
         "O2D",  # H2D Pro (alternate code)

+ 2 - 0
backend/app/services/virtual_printer/manager.py

@@ -41,6 +41,7 @@ VIRTUAL_PRINTER_MODELS = {
     # H2 Series
     "O1D": "H2D",  # H2D
     "O1C": "H2C",  # H2C
+    "O1C2": "H2C",  # H2C (dual nozzle variant)
     "O1S": "H2S",  # H2S
 }
 
@@ -68,6 +69,7 @@ MODEL_SERIAL_PREFIXES = {
     # H2 Series
     "O1D": "09400A",  # H2D
     "O1C": "09400A",  # H2C
+    "O1C2": "09400A",  # H2C (dual nozzle variant)
     "O1S": "09400A",  # H2S
 }
 

+ 1 - 0
backend/app/services/virtual_printer/mqtt_server.py

@@ -28,6 +28,7 @@ MODEL_PRODUCT_NAMES = {
     "N1": "A1 mini",
     "O1D": "H2D",
     "O1C": "H2C",
+    "O1C2": "H2C",
     "O1S": "H2S",
 }
 

+ 2 - 0
backend/app/utils/printer_models.py

@@ -44,6 +44,7 @@ PRINTER_MODEL_ID_MAP = {
     "O1E": "H2D Pro",  # Some devices report O1E
     "O2D": "H2D Pro",  # Some devices report O2D
     "O1C": "H2C",
+    "O1C2": "H2C",
     "O1S": "H2S",
 }
 
@@ -88,6 +89,7 @@ LINEAR_RAIL_MODELS = frozenset(
         "O1E",  # H2D Pro
         "O2D",  # H2D Pro (alternate)
         "O1C",  # H2C
+        "O1C2",  # H2C (dual nozzle variant)
         "O1S",  # H2S
     ]
 )

+ 1 - 0
frontend/src/pages/PrintersPage.tsx

@@ -1363,6 +1363,7 @@ function mapModelCode(ssdpModel: string | null): string {
     'O1E': 'H2D Pro',
     'O2D': 'H2D Pro',
     'O1C': 'H2C',
+    'O1C2': 'H2C',
     'O1S': 'H2S',
     // X1 Series
     'BL-P001': 'X1C',

+ 25 - 0
frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx

@@ -14,6 +14,31 @@ function getAmsName(amsId: number): string {
   return `AMS ${amsId}`;
 }
 
+<<<<<<< HEAD
+=======
+function mapModelCode(ssdpModel: string | null): string {
+  if (!ssdpModel) return '';
+  const modelMap: Record<string, string> = {
+    'O1D': 'H2D', 'O1E': 'H2D Pro', 'O2D': 'H2D Pro', 'O1C': 'H2C', 'O1C2': 'H2C', 'O1S': 'H2S',
+    'BL-P001': 'X1C', 'BL-P002': 'X1', 'BL-P003': 'X1E',
+    'C11': 'P1S', 'C12': 'P1P', 'C13': 'P2S',
+    'N2S': 'A1', 'N1': 'A1 Mini',
+    'X1C': 'X1C', 'X1': 'X1', 'X1E': 'X1E', 'P1S': 'P1S', 'P1P': 'P1P', 'P2S': 'P2S',
+    'A1': 'A1', 'A1 Mini': 'A1 Mini', 'H2D': 'H2D', 'H2D Pro': 'H2D Pro', 'H2C': 'H2C', 'H2S': 'H2S',
+  };
+  return modelMap[ssdpModel] || ssdpModel;
+}
+
+function isTrayEmpty(tray: AMSTray): boolean {
+  return !tray.tray_type || tray.tray_type === '';
+}
+
+function trayColorToCSS(color: string | null): string {
+  if (!color) return '#808080';
+  return `#${color.slice(0, 6)}`;
+}
+
+>>>>>>> ce97a47 (  Add H2C dual nozzle variant O1C2 model support (#489))
 export function SpoolBuddyAmsPage() {
   const { selectedPrinterId, setAlert } = useOutletContext<SpoolBuddyOutletContext>();
   const { t } = useTranslation();

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
static/assets/index-BawYMb0M.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
static/assets/index-tKUolDFn.js


+ 5 - 0
static/index.html

@@ -23,6 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
+<<<<<<< HEAD
 <<<<<<< HEAD
     <script type="module" crossorigin src="/assets/index-CxFtC4Kb.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-DJjXosw8.css">
@@ -30,6 +31,10 @@
     <script type="module" crossorigin src="/assets/index-tKUolDFn.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BKdrvUCu.css">
 >>>>>>> 7eb7bf7 (Fix queue badge showing on printers without matching filament (#486))
+=======
+    <script type="module" crossorigin src="/assets/index-BawYMb0M.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-BW78djlt.css">
+>>>>>>> ce97a47 (  Add H2C dual nozzle variant O1C2 model support (#489))
   </head>
   <body>
     <div id="root"></div>

Неке датотеке нису приказане због велике количине промена