Browse Source

Replaced camera settings with wifi signal in top bar

Martin Ziegler 5 months ago
parent
commit
065f22a0e3

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

@@ -294,6 +294,7 @@ async def get_printer_status(printer_id: int, db: AsyncSession = Depends(get_db)
         store_to_sdcard=state.store_to_sdcard,
         timelapse=state.timelapse,
         ipcam=state.ipcam,
+        wifi_signal=state.wifi_signal,
         nozzles=nozzles,
         print_options=print_options,
         stg_cur=state.stg_cur,

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

@@ -114,6 +114,7 @@ class PrinterStatus(BaseModel):
     store_to_sdcard: bool = False  # Store sent files on SD card
     timelapse: bool = False  # Timelapse recording active
     ipcam: bool = False  # Live view enabled
+    wifi_signal: int | None = None  # WiFi signal strength in dBm
     nozzles: list[NozzleInfoResponse] = []  # Nozzle hardware info (index 0=left/primary, 1=right)
     print_options: PrintOptionsResponse | None = None  # AI detection and print options
     # Calibration stage tracking

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

@@ -97,6 +97,7 @@ class PrinterState:
     store_to_sdcard: bool = False  # Store sent files on SD card (home_flag bit 11)
     timelapse: bool = False  # Timelapse recording active
     ipcam: bool = False  # Live view / camera streaming enabled
+    wifi_signal: int | None = None  # WiFi signal strength in dBm
     # 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
@@ -1277,6 +1278,19 @@ class BambuMQTTClient:
             else:
                 self.state.ipcam = ipcam_data is True
 
+        # Parse WiFi signal strength (dBm)
+        if "wifi_signal" in data:
+            wifi_signal = data["wifi_signal"]
+            if isinstance(wifi_signal, (int, float)):
+                # Convert string dBm to int (e.g., "-52dBm" -> -52)
+                self.state.wifi_signal = int(wifi_signal)
+            elif isinstance(wifi_signal, str):
+                # Handle string format like "-52dBm"
+                try:
+                    self.state.wifi_signal = int(wifi_signal.replace("dBm", "").strip())
+                except ValueError:
+                    pass
+
         # Parse print speed level (1=silent, 2=standard, 3=sport, 4=ludicrous)
         if "spd_lvl" in data:
             new_speed = data["spd_lvl"]

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

@@ -117,6 +117,7 @@ export interface PrinterStatus {
   store_to_sdcard: boolean;  // Store sent files on SD card
   timelapse: boolean;  // Timelapse recording active
   ipcam: boolean;  // Live view enabled
+  wifi_signal: number | null;  // WiFi signal strength in dBm
   nozzles: NozzleInfo[];  // Nozzle hardware info (index 0=left/primary, 1=right)
   print_options: PrintOptions | null;  // AI detection and print options
   // Calibration stage tracking

+ 0 - 129
frontend/src/components/control/CameraSettingsModal.tsx

@@ -1,129 +0,0 @@
-import { useEffect } from 'react';
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { api } from '../../api/client';
-import type { PrinterStatus } from '../../api/client';
-import { X, Loader2 } from 'lucide-react';
-import { Card, CardContent } from '../Card';
-
-interface CameraSettingsModalProps {
-  printerId: number;
-  status: PrinterStatus | null | undefined;
-  onClose: () => void;
-}
-
-export function CameraSettingsModal({ printerId, status, onClose }: CameraSettingsModalProps) {
-  const queryClient = useQueryClient();
-  const isConnected = status?.connected ?? false;
-
-  // Close on Escape key
-  useEffect(() => {
-    const handleKeyDown = (e: KeyboardEvent) => {
-      if (e.key === 'Escape') onClose();
-    };
-    window.addEventListener('keydown', handleKeyDown);
-    return () => window.removeEventListener('keydown', handleKeyDown);
-  }, [onClose]);
-
-  const timelapseMutation = useMutation({
-    mutationFn: (enable: boolean) => api.setTimelapse(printerId, enable),
-    onSuccess: (data) => {
-      console.log('Timelapse mutation success:', data);
-      queryClient.invalidateQueries({ queryKey: ['printerStatuses'] });
-    },
-    onError: (error) => {
-      console.error('Timelapse mutation error:', error);
-    },
-  });
-
-  const liveviewMutation = useMutation({
-    mutationFn: (enable: boolean) => api.setLiveview(printerId, enable),
-    onSuccess: (data) => {
-      console.log('Liveview mutation success:', data);
-      queryClient.invalidateQueries({ queryKey: ['printerStatuses'] });
-    },
-    onError: (error) => {
-      console.error('Liveview mutation error:', error);
-    },
-  });
-
-  console.log('CameraSettingsModal render - isConnected:', isConnected, 'status:', status);
-
-  const handleTimelapseToggle = () => {
-    console.log('Timelapse toggle clicked, current:', status?.timelapse, 'setting to:', !status?.timelapse);
-    timelapseMutation.mutate(!status?.timelapse);
-  };
-
-  const handleLiveviewToggle = () => {
-    console.log('Liveview toggle clicked, current:', status?.ipcam, 'setting to:', !status?.ipcam);
-    liveviewMutation.mutate(!status?.ipcam);
-  };
-
-  return (
-    <div
-      className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
-      onClick={onClose}
-    >
-      <Card className="w-full max-w-sm" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
-        <CardContent className="p-0">
-          {/* Header */}
-          <div className="flex items-center justify-between px-4 py-3 border-b border-bambu-dark-tertiary">
-            <span className="text-sm font-medium text-white">Camera Settings</span>
-            <button
-              onClick={onClose}
-              className="p-1 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white"
-            >
-              <X className="w-4 h-4" />
-            </button>
-          </div>
-
-          {/* Settings */}
-          <div className="p-4 space-y-4">
-            {/* Auto-record Monitoring (ipcam_record - records to SD during print) */}
-            <div className="flex items-center justify-between">
-              <span className="text-sm text-white">Auto-record Monitoring</span>
-              <button
-                onClick={handleLiveviewToggle}
-                disabled={!isConnected || liveviewMutation.isPending}
-                className={`relative w-12 h-6 rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
-                  status?.ipcam ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
-                }`}
-              >
-                {liveviewMutation.isPending ? (
-                  <Loader2 className="absolute inset-0 m-auto w-4 h-4 animate-spin text-white" />
-                ) : (
-                  <span
-                    className={`absolute top-1 w-4 h-4 rounded-full bg-white transition-transform ${
-                      status?.ipcam ? 'left-7' : 'left-1'
-                    }`}
-                  />
-                )}
-              </button>
-            </div>
-
-            {/* Go Live (timelapse - live streaming) */}
-            <div className="flex items-center justify-between">
-              <span className="text-sm text-white">Go Live</span>
-              <button
-                onClick={handleTimelapseToggle}
-                disabled={!isConnected || timelapseMutation.isPending}
-                className={`relative w-12 h-6 rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
-                  status?.timelapse ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
-                }`}
-              >
-                {timelapseMutation.isPending ? (
-                  <Loader2 className="absolute inset-0 m-auto w-4 h-4 animate-spin text-white" />
-                ) : (
-                  <span
-                    className={`absolute top-1 w-4 h-4 rounded-full bg-white transition-transform ${
-                      status?.timelapse ? 'left-7' : 'left-1'
-                    }`}
-                  />
-                )}
-              </button>
-            </div>
-          </div>
-        </CardContent>
-      </Card>
-    </div>
-  );
-}

+ 64 - 0
frontend/src/components/icons/WifiSignal.tsx

@@ -0,0 +1,64 @@
+interface WifiSignalProps {
+  signal: number | null | undefined;  // dBm value
+  className?: string;
+}
+
+/**
+ * WiFi signal icon with 4 bars that fill based on signal strength.
+ * - 4 bars: >= -50 dBm (excellent)
+ * - 3 bars: >= -60 dBm (good)
+ * - 2 bars: >= -70 dBm (fair)
+ * - 1 bar:  < -70 dBm (weak)
+ * - 0 bars: no signal data
+ */
+export function WifiSignal({ signal, className = "w-4 h-4" }: WifiSignalProps) {
+  let bars = 0;
+  if (signal != null) {
+    if (signal >= -50) bars = 4;
+    else if (signal >= -60) bars = 3;
+    else if (signal >= -70) bars = 2;
+    else bars = 1;
+  }
+
+  const activeColor = "#00ae42";  // bambu-green
+  const inactiveColor = "#4a4a4a";  // dark gray
+
+  return (
+    <svg
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      className={className}
+    >
+      {/* Dot at bottom */}
+      <circle
+        cx="12"
+        cy="20"
+        r="1"
+        fill={bars >= 1 ? activeColor : inactiveColor}
+        stroke={bars >= 1 ? activeColor : inactiveColor}
+      />
+      {/* First arc (smallest) */}
+      <path
+        d="M8.5 16.5a5 5 0 0 1 7 0"
+        stroke={bars >= 2 ? activeColor : inactiveColor}
+        fill="none"
+      />
+      {/* Second arc */}
+      <path
+        d="M5 13a10 10 0 0 1 14 0"
+        stroke={bars >= 3 ? activeColor : inactiveColor}
+        fill="none"
+      />
+      {/* Third arc (largest) */}
+      <path
+        d="M1.5 9.5a15 15 0 0 1 21 0"
+        stroke={bars >= 4 ? activeColor : inactiveColor}
+        fill="none"
+      />
+    </svg>
+  );
+}

+ 7 - 17
frontend/src/pages/ControlPage.tsx

@@ -10,16 +10,15 @@ import { JogPad } from '../components/control/JogPad';
 import { BedControls } from '../components/control/BedControls';
 import { ExtruderControls } from '../components/control/ExtruderControls';
 import { AMSSectionDual } from '../components/control/AMSSectionDual';
-import { CameraSettingsModal } from '../components/control/CameraSettingsModal';
 import { PrinterPartsModal } from '../components/control/PrinterPartsModal';
 import { PrintOptionsModal } from '../components/control/PrintOptionsModal';
 import { CalibrationModal } from '../components/control/CalibrationModal';
-import { Loader2, WifiOff, Video, Webcam, Settings } from 'lucide-react';
+import { Loader2, WifiOff, Video, Webcam } from 'lucide-react';
+import { WifiSignal } from '../components/icons/WifiSignal';
 
 export function ControlPage() {
   const [searchParams, setSearchParams] = useSearchParams();
   const [selectedPrinterId, setSelectedPrinterId] = useState<number | null>(null);
-  const [showCameraSettings, setShowCameraSettings] = useState(false);
   const [showPrinterParts, setShowPrinterParts] = useState(false);
   const [showPrintOptions, setShowPrintOptions] = useState(false);
   const [showCalibration, setShowCalibration] = useState(false);
@@ -148,12 +147,12 @@ export function ControlPage() {
               <button className={`p-1.5 rounded hover:bg-bambu-dark-tertiary ${selectedStatus?.ipcam ? 'text-bambu-green' : 'text-bambu-gray hover:text-white'}`}>
                 <Webcam className="w-4 h-4" />
               </button>
-              <button
-                onClick={() => setShowCameraSettings(true)}
-                className="p-1.5 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white"
+              <div
+                className="p-1.5 rounded"
+                title={selectedStatus?.wifi_signal != null ? `WiFi: ${selectedStatus.wifi_signal} dBm` : 'WiFi signal unknown'}
               >
-                <Settings className="w-4 h-4" />
-              </button>
+                <WifiSignal signal={selectedStatus?.wifi_signal} className="w-4 h-4" />
+              </div>
             </div>
 
             {/* Camera Feed - Embedded directly */}
@@ -264,15 +263,6 @@ export function ControlPage() {
         </div>
       )}
 
-      {/* Camera Settings Modal */}
-      {showCameraSettings && selectedPrinter && (
-        <CameraSettingsModal
-          printerId={selectedPrinter.id}
-          status={selectedStatus}
-          onClose={() => setShowCameraSettings(false)}
-        />
-      )}
-
       {/* Printer Parts Modal */}
       {showPrinterParts && selectedPrinter && (
         <PrinterPartsModal

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


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


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-gD5e-c1O.css


+ 2 - 2
static/index.html

@@ -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-1a-urQlt.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-gD5e-c1O.css">
+    <script type="module" crossorigin src="/assets/index-DX2Pky1o.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-BpSfhfce.css">
   </head>
   <body>
     <div id="root"></div>

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