Browse Source

Fix ethernet badge shown on WiFi-only printers (#585)

    home_flag bit 18 is set on all printers regardless of hardware,
    causing the ethernet badge to appear on WiFi-only models (A1, P1P,
    etc.). The previous fix removed the feature entirely, so ethernet
    printers lost their badge too.

    Now only trusts bit 18 on models with an ethernet port (X1C, X1E,
    P1S, P2S, H2D, H2D Pro, H2C, H2S). WiFi-only models always show
    the WiFi signal badge.
maziggy 2 months ago
parent
commit
26a3b2172d

+ 1 - 1
CHANGELOG.md

@@ -28,7 +28,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Bed Cooled Notification Never Fires** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — The bed cooldown monitor always timed out after 30 minutes without sending a notification. After print completion, P1S (and likely other models) sends partial MQTT status updates that don't include `bed_temper`, so the cached bed temperature stayed frozen at the end-of-print value and never dropped below the threshold. The monitor now sends periodic `pushall` commands to the printer to force fresh temperature data. Also added debug logging to the polling loop for future diagnostics.
 - **Notification Provider Missing Event Toggles on Create** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — When creating a new notification provider, the `on_bed_cooled` toggle and all 7 queue event toggles (`on_queue_job_added`, `on_queue_job_assigned`, `on_queue_job_started`, `on_queue_job_waiting`, `on_queue_job_skipped`, `on_queue_job_failed`, `on_queue_completed`) were silently discarded. The create endpoint manually listed each field but omitted these 8 toggles, so they always defaulted to `false` regardless of user selection. Editing an existing provider worked correctly.
 - **Clear Plate Prompt Shown for Staged Queue Items** — The "Clear Plate & Start Next" button on the printer card appeared when all pending queue items were staged (`manual_start`/Queue Only), even though the scheduler won't auto-start them. The clear plate prompt now only appears when there are auto-dispatchable items that the scheduler will actually start after the plate is cleared.
-- **Ethernet Badge Always Shown on Printer Cards** — The printer card network badge always showed "Ethernet" instead of the WiFi signal indicator, even on printers without an ethernet port. The `home_flag` bit 18 was incorrectly interpreted as indicating a wired connection. Removed the faulty ethernet detection; the WiFi signal badge now displays correctly when the printer reports signal strength.
+- **Ethernet Badge Shown on WiFi-Only Printers** ([#585](https://github.com/maziggy/bambuddy/issues/585)) — The ethernet badge was shown on all printer models, including WiFi-only models like A1 and P1P that don't have an ethernet port. The `home_flag` bit 18 is set on all printers regardless of hardware. Now only parses the ethernet flag on models that actually have an ethernet port (X1C, X1E, P1S, P2S, H2D, H2D Pro, H2C, H2S). Printers connected via ethernet show an "Ethernet" badge; all others show the WiFi signal indicator.
 - **GitHub Backup Required Cloud Login** ([#655](https://github.com/maziggy/bambuddy/issues/655)) — The GitHub backup settings card was completely blocked behind Bambu Cloud authentication, showing "Bambu Cloud login required" even though the backup feature works without it (K-profiles and app settings don't need cloud). Removed the cloud auth gate so GitHub backup can be configured and used without Bambu Cloud. The "Cloud Profiles" checkbox is disabled with a hint when not logged in. Reported by @TravisWilder.
 - **GitHub Backup Log Timestamps Off by 1 Hour** — Backup log timestamps in the history table were displayed in UTC instead of the user's local timezone. The local `formatDateTime` function didn't use `parseUTCDate`, so timezone-less timestamps from SQLite were interpreted as local time. Now uses the shared `parseUTCDate` utility for correct UTC-to-local conversion.
 - **H2D AMS Units Shown on Wrong Nozzle** ([#659](https://github.com/maziggy/bambuddy/issues/659)) — On the H2D dual-nozzle printer, AMS units were displayed on the wrong nozzle (e.g. both AMS-HT and AMS2 Pro shown on the left nozzle instead of their correct assignments). Three interrelated bugs in the AMS `info` field parsing: (1) the field was parsed as decimal instead of hexadecimal (BambuStudio uses `std::stoull(str, nullptr, 16)`), (2) the extruder ID was extracted as a single bit instead of a 4-bit field, and (3) partial MQTT updates overwrote the full extruder map instead of merging. Now correctly hex-parses the `info` field, extracts the 4-bit extruder ID from bits 8-11, skips uninitialized AMS units (`0xE`), and merges partial updates into the existing map. Reported by @cadtoolbox.

+ 1 - 0
backend/app/api/routes/printers.py

@@ -571,6 +571,7 @@ async def get_printer_status(
         timelapse=state.timelapse,
         ipcam=state.ipcam,
         wifi_signal=state.wifi_signal,
+        wired_network=state.wired_network,
         nozzles=nozzles,
         nozzle_rack=nozzle_rack,
         print_options=print_options,

+ 1 - 0
backend/app/schemas/printer.py

@@ -212,6 +212,7 @@ class PrinterStatus(BaseModel):
     timelapse: bool = False  # Timelapse recording active
     ipcam: bool = False  # Live view enabled
     wifi_signal: int | None = None  # WiFi signal strength in dBm
+    wired_network: bool = False  # Ethernet connection detected
     nozzles: list[NozzleInfoResponse] = []  # Nozzle hardware info (index 0=left/primary, 1=right)
     nozzle_rack: list[NozzleRackSlot] = []  # H2C 6-nozzle tool-changer rack
     print_options: PrintOptionsResponse | None = None  # AI detection and print options

+ 7 - 0
backend/app/services/bambu_mqtt.py

@@ -120,6 +120,7 @@ class PrinterState:
     timelapse: bool = False  # Timelapse recording active
     ipcam: bool = False  # Live view / camera streaming enabled
     wifi_signal: int | None = None  # WiFi signal strength in dBm
+    wired_network: bool = False  # Ethernet connection detected (home_flag bit 18)
     # Nozzle hardware info (for dual nozzle printers, index 0 = left, 1 = right)
     nozzles: list = field(default_factory=lambda: [NozzleInfo(), NozzleInfo()])
     # AI detection and print options
@@ -2076,6 +2077,12 @@ class BambuMQTTClient:
                     f"[{self.serial_number}] store_to_sdcard changed: {self.state.store_to_sdcard} -> {store_to_sdcard}"
                 )
             self.state.store_to_sdcard = store_to_sdcard
+            # Bit 18 (0x00040000) indicates wired/ethernet connection
+            # Only trust this on models that actually have an ethernet port
+            from app.utils.printer_models import has_ethernet
+
+            if has_ethernet(self.model):
+                self.state.wired_network = bool((home_flag >> 18) & 1)
 
         # Parse timelapse status (recording active during print)
         if "timelapse" in data:

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

@@ -668,6 +668,7 @@ def printer_state_to_dict(state: PrinterState, printer_id: int | None = None, mo
         "ams_extruder_map": ams_extruder_map,
         # WiFi signal strength
         "wifi_signal": state.wifi_signal,
+        "wired_network": state.wired_network,
         # Calibration stage tracking
         "stg_cur": state.stg_cur,
         "stg_cur_name": get_derived_status_name(state, model),

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

@@ -103,6 +103,41 @@ LINEAR_RAIL_MODELS = frozenset(
 )
 
 
+# Models with an ethernet port.
+# X1, P1P, A1, A1 Mini do NOT have ethernet.
+ETHERNET_MODELS = frozenset(
+    [
+        # Display names (uppercase, no spaces)
+        "X1C",
+        "X1E",
+        "P1S",
+        "P2S",
+        "H2D",
+        "H2DPRO",
+        "H2C",
+        "H2S",
+        # Internal codes
+        "C11",  # X1C
+        "C13",  # X1E
+        "P1S",  # P1S
+        "O1D",  # H2D
+        "O1E",  # H2D Pro
+        "O2D",  # H2D Pro (alternate)
+        "O1C",  # H2C
+        "O1C2",  # H2C (dual nozzle variant)
+        "O1S",  # H2S
+    ]
+)
+
+
+def has_ethernet(model: str | None) -> bool:
+    """Return True if the printer model has an ethernet port."""
+    if not model:
+        return False
+    normalized = model.strip().upper().replace(" ", "").replace("-", "")
+    return normalized in ETHERNET_MODELS
+
+
 def get_rod_type(model: str | None) -> str | None:
     """Return the rod/rail type for a printer model.
 

+ 1 - 0
frontend/src/api/client.ts

@@ -212,6 +212,7 @@ export interface PrinterStatus {
   timelapse: boolean;  // Timelapse recording active
   ipcam: boolean;  // Live view enabled
   wifi_signal: number | null;  // WiFi signal strength in dBm
+  wired_network: boolean;  // Ethernet connection detected
   nozzles: NozzleInfo[];  // Nozzle hardware info (index 0=left/primary, 1=right)
   nozzle_rack: NozzleRackSlot[];  // H2C 6-nozzle tool-changer rack
   print_options: PrintOptions | null;  // AI detection and print options

+ 13 - 3
frontend/src/components/PrinterInfoModal.tsx

@@ -1,6 +1,6 @@
 import { useState, useEffect } from 'react';
 import { useTranslation } from 'react-i18next';
-import { X, Copy, Check, Signal } from 'lucide-react';
+import { X, Copy, Check, Signal, Cable } from 'lucide-react';
 import { Card, CardContent } from './Card';
 import { formatDateOnly } from '../utils/date';
 import { getPrinterImage, getWifiStrength } from '../utils/printer';
@@ -109,8 +109,18 @@ export function PrinterInfoModal({ printer, status, totalPrintHours, onClose }:
     ),
   });
 
-  // WiFi signal
-  if (status?.wifi_signal != null) {
+  // Network connection
+  if (status?.wired_network) {
+    rows.push({
+      label: t('printers.networkLabel', 'Network'),
+      value: (
+        <span className="flex items-center gap-2">
+          <Cable className="w-4 h-4 text-bambu-green" />
+          <span className="text-bambu-green">{t('printers.connection.ethernet', 'Ethernet')}</span>
+        </span>
+      ),
+    });
+  } else if (status?.wifi_signal != null) {
     const wifi = getWifiStrength(status.wifi_signal);
     rows.push({
       label: t('printers.wifiSignalLabel'),

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

@@ -44,6 +44,7 @@ import {
   Home,
   Printer as PrinterIcon,
   Info,
+  Cable,
 } from 'lucide-react';
 
 import { useNavigate } from 'react-router-dom';
@@ -2469,8 +2470,18 @@ function PrinterCard({
                 )}
                 {status?.connected ? t('printers.connection.connected') : t('printers.connection.offline')}
               </span>
+              {/* Network connection indicator */}
+              {status?.connected && status?.wired_network && (
+                <span
+                  className="flex items-center gap-1 px-2 py-1 rounded-full text-xs bg-status-ok/20 text-status-ok"
+                  title={t('printers.connection.ethernet', 'Ethernet')}
+                >
+                  <Cable className="w-3 h-3" />
+                  {t('printers.connection.ethernet', 'Ethernet')}
+                </span>
+              )}
               {/* WiFi signal indicator */}
-              {status?.connected && wifiSignal != null && (
+              {status?.connected && !status?.wired_network && wifiSignal != null && (
                 <span
                   className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs ${
                     wifiSignal >= -50

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-COMdij7Q.js


+ 1 - 1
static/index.html

@@ -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-Y5pjE2wq.js"></script>
+    <script type="module" crossorigin src="/assets/index-COMdij7Q.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BoLtXYT2.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff