Explorar el Código

Show ethernet indicator instead of WiFi signal for wired printers (#585)

  Parse home_flag bit 18 (0x00040000) from MQTT to detect ethernet
  connections. When set, the printer card shows a green "Ethernet"
  badge with a cable icon instead of WiFi signal strength in dBm.
  The printer info modal also displays "Ethernet" instead of WiFi
  signal details.
maziggy hace 2 meses
padre
commit
6388b0df9b

+ 2 - 0
CHANGELOG.md

@@ -10,6 +10,8 @@ All notable changes to Bambuddy will be documented in this file.
 - **SpoolBuddy Kiosk Stability** — Disabled Chromium's swipe-to-navigate gesture (`--overscroll-history-navigation=0`) in the install script to prevent accidental back-navigation on the touchscreen. Added the `video` group to the SpoolBuddy system user for DSI backlight access.
 - **SpoolBuddy Kiosk Stability** — Disabled Chromium's swipe-to-navigate gesture (`--overscroll-history-navigation=0`) in the install script to prevent accidental back-navigation on the touchscreen. Added the `video` group to the SpoolBuddy system user for DSI backlight access.
 - **SpoolBuddy Touch-Friendly UI** — Enlarged all interactive elements across the SpoolBuddy kiosk UI for comfortable finger use on the 1024×600 RPi touchscreen. Bottom nav icons and labels increased (20→24px icons, 10→12px labels, 48→56px bar height). Top bar printer selector and clock enlarged. Dashboard stats bar compacted, printers card removed (printer selection via top bar is sufficient), section headers and device status text bumped up. AMS page single-slot cards, spool visualizations, and fill bars enlarged. AMS unit cards get larger spool previews (56→64px), bigger material/slot text, and larger humidity/temperature indicators. Inventory spool cards, settings page headers, and calibration inputs all sized up to meet 44px minimum tap targets. The AMS slot configuration modal now renders in a two-column full-screen layout on the kiosk display (filament list on left, K-profile and color picker on right) instead of the standard centered dialog, eliminating scrolling.
 - **SpoolBuddy Touch-Friendly UI** — Enlarged all interactive elements across the SpoolBuddy kiosk UI for comfortable finger use on the 1024×600 RPi touchscreen. Bottom nav icons and labels increased (20→24px icons, 10→12px labels, 48→56px bar height). Top bar printer selector and clock enlarged. Dashboard stats bar compacted, printers card removed (printer selection via top bar is sufficient), section headers and device status text bumped up. AMS page single-slot cards, spool visualizations, and fill bars enlarged. AMS unit cards get larger spool previews (56→64px), bigger material/slot text, and larger humidity/temperature indicators. Inventory spool cards, settings page headers, and calibration inputs all sized up to meet 44px minimum tap targets. The AMS slot configuration modal now renders in a two-column full-screen layout on the kiosk display (filament list on left, K-profile and color picker on right) instead of the standard centered dialog, eliminating scrolling.
 
 
+- **Ethernet Connection Indicator** ([#585](https://github.com/maziggy/bambuddy/issues/585)) — Printers connected via ethernet now show a green "Ethernet" badge with a cable icon instead of the WiFi signal strength indicator. Detected via `home_flag` bit 18 from the printer's MQTT data. The printer info modal also shows "Ethernet" instead of WiFi signal details.
+
 ### New Features
 ### New Features
 - **SpoolBuddy NFC Tag Writing (OpenTag3D)** — SpoolBuddy can now write NFC tags for third-party filament spools using the OpenTag3D format on NTAG213/215/216 stickers. A new "Write" page (`/spoolbuddy/write-tag`) in the kiosk UI provides three workflows: write a tag for an existing inventory spool (no tag linked yet), create a new spool and write in one flow, or replace a damaged tag (unlinks old, writes new). The left panel shows a searchable spool list or a compact creation form (material dropdown, color picker, brand, weight); the right panel shows real-time NFC status with tag detection, a spool summary, and the write button. The backend encodes spool data as a 133-byte OpenTag3D NDEF message (MIME type `application/opentag3d`, fits NTAG213's 144-byte capacity) containing material, color, brand, weight, temperature, and RGBA color data. The write command flows through the existing heartbeat polling mechanism — the frontend queues a write, the daemon picks it up on the next heartbeat, writes page-by-page with read-back verification via the PN5180's NTAG WRITE (0xA2) command, and reports success/failure via WebSocket. On success the tag UID is automatically linked to the spool with `data_origin=opentag3d`. Written tags are readable by any OpenTag3D-compatible reader including SpoolBuddy itself. Translations added for all 6 languages.
 - **SpoolBuddy NFC Tag Writing (OpenTag3D)** — SpoolBuddy can now write NFC tags for third-party filament spools using the OpenTag3D format on NTAG213/215/216 stickers. A new "Write" page (`/spoolbuddy/write-tag`) in the kiosk UI provides three workflows: write a tag for an existing inventory spool (no tag linked yet), create a new spool and write in one flow, or replace a damaged tag (unlinks old, writes new). The left panel shows a searchable spool list or a compact creation form (material dropdown, color picker, brand, weight); the right panel shows real-time NFC status with tag detection, a spool summary, and the write button. The backend encodes spool data as a 133-byte OpenTag3D NDEF message (MIME type `application/opentag3d`, fits NTAG213's 144-byte capacity) containing material, color, brand, weight, temperature, and RGBA color data. The write command flows through the existing heartbeat polling mechanism — the frontend queues a write, the daemon picks it up on the next heartbeat, writes page-by-page with read-back verification via the PN5180's NTAG WRITE (0xA2) command, and reports success/failure via WebSocket. On success the tag UID is automatically linked to the spool with `data_origin=opentag3d`. Written tags are readable by any OpenTag3D-compatible reader including SpoolBuddy itself. Translations added for all 6 languages.
 - **SpoolBuddy On-Screen Keyboard** — Added a virtual QWERTY keyboard for the SpoolBuddy kiosk UI (and login page) since the Raspberry Pi has no physical keyboard and system-level virtual keyboards (squeekboard, wvkbd) don't auto-show/hide in the labwc/Chromium kiosk environment. Uses `react-simple-keyboard` with a dark theme matching the bambu-dark/bambu-green palette. Auto-shows when any text/password/email input is focused, supports shift, caps lock, backspace, and email-friendly keys (@, .). Inputs with `data-vkb="false"` are excluded (e.g. SpoolBuddySettingsPage's own numpad). A two-phase close prevents ghost-click passthrough to elements underneath the keyboard.
 - **SpoolBuddy On-Screen Keyboard** — Added a virtual QWERTY keyboard for the SpoolBuddy kiosk UI (and login page) since the Raspberry Pi has no physical keyboard and system-level virtual keyboards (squeekboard, wvkbd) don't auto-show/hide in the labwc/Chromium kiosk environment. Uses `react-simple-keyboard` with a dark theme matching the bambu-dark/bambu-green palette. Auto-shows when any text/password/email input is focused, supports shift, caps lock, backspace, and email-friendly keys (@, .). Inputs with `data-vkb="false"` are excluded (e.g. SpoolBuddySettingsPage's own numpad). A two-phase close prevents ghost-click passthrough to elements underneath the keyboard.

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

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

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

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

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

@@ -113,6 +113,7 @@ class PrinterState:
     timelapse: bool = False  # Timelapse recording active
     timelapse: bool = False  # Timelapse recording active
     ipcam: bool = False  # Live view / camera streaming enabled
     ipcam: bool = False  # Live view / camera streaming enabled
     wifi_signal: int | None = None  # WiFi signal strength in dBm
     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)
     # Nozzle hardware info (for dual nozzle printers, index 0 = left, 1 = right)
     nozzles: list = field(default_factory=lambda: [NozzleInfo(), NozzleInfo()])
     nozzles: list = field(default_factory=lambda: [NozzleInfo(), NozzleInfo()])
     # AI detection and print options
     # AI detection and print options
@@ -1930,6 +1931,8 @@ class BambuMQTTClient:
                     f"[{self.serial_number}] store_to_sdcard changed: {self.state.store_to_sdcard} -> {store_to_sdcard}"
                     f"[{self.serial_number}] store_to_sdcard changed: {self.state.store_to_sdcard} -> {store_to_sdcard}"
                 )
                 )
             self.state.store_to_sdcard = store_to_sdcard
             self.state.store_to_sdcard = store_to_sdcard
+            # Bit 18 (0x00040000) indicates wired/ethernet connection
+            self.state.wired_network = bool((home_flag >> 18) & 1)
 
 
         # Parse timelapse status (recording active during print)
         # Parse timelapse status (recording active during print)
         if "timelapse" in data:
         if "timelapse" in data:

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

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

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

@@ -210,6 +210,7 @@ export interface PrinterStatus {
   timelapse: boolean;  // Timelapse recording active
   timelapse: boolean;  // Timelapse recording active
   ipcam: boolean;  // Live view enabled
   ipcam: boolean;  // Live view enabled
   wifi_signal: number | null;  // WiFi signal strength in dBm
   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)
   nozzles: NozzleInfo[];  // Nozzle hardware info (index 0=left/primary, 1=right)
   nozzle_rack: NozzleRackSlot[];  // H2C 6-nozzle tool-changer rack
   nozzle_rack: NozzleRackSlot[];  // H2C 6-nozzle tool-changer rack
   print_options: PrintOptions | null;  // AI detection and print options
   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 { useState, useEffect } from 'react';
 import { useTranslation } from 'react-i18next';
 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 { Card, CardContent } from './Card';
 import { formatDateOnly } from '../utils/date';
 import { formatDateOnly } from '../utils/date';
 import { getPrinterImage, getWifiStrength } from '../utils/printer';
 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);
     const wifi = getWifiStrength(status.wifi_signal);
     rows.push({
     rows.push({
       label: t('printers.wifiSignalLabel'),
       label: t('printers.wifiSignalLabel'),

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

@@ -44,6 +44,7 @@ import {
   Home,
   Home,
   Printer as PrinterIcon,
   Printer as PrinterIcon,
   Info,
   Info,
+  Cable,
 } from 'lucide-react';
 } from 'lucide-react';
 
 
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
@@ -2329,8 +2330,17 @@ function PrinterCard({
                 )}
                 )}
                 {status?.connected ? t('printers.connection.connected') : t('printers.connection.offline')}
                 {status?.connected ? t('printers.connection.connected') : t('printers.connection.offline')}
               </span>
               </span>
-              {/* WiFi signal strength indicator */}
-              {status?.connected && wifiSignal != null && (
+              {/* 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>
+              )}
+              {status?.connected && !status?.wired_network && wifiSignal != null && (
                 <span
                 <span
                   className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs ${
                   className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs ${
                     wifiSignal >= -50
                     wifiSignal >= -50

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
static/assets/index-J5C4anSn.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BNe8DBiX.js"></script>
+    <script type="module" crossorigin src="/assets/index-J5C4anSn.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-D5I4wfky.css">
     <link rel="stylesheet" crossorigin href="/assets/index-D5I4wfky.css">
   </head>
   </head>
   <body>
   <body>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio