فهرست منبع

Reduce SpoolBuddy kiosk idle CPU load from ~3.3 to ~0.9

  Frontend: replace expensive idle dashboard animations (3x animate-ping
  with scale transforms, blur-2xl glow, continuous animate-pulse on
  status dots) with static NFC rings and slow 5s color-cycling spool.
  Chromium: add --disable-extensions, --disable-background-timer-throttling,
  --memory-pressure-off, --disable-renderer-backgrounding, --disable-breakpad,
  and --js-flags=--max-old-space-size=128. Install script: mask stripped
  services (not just disable) to prevent socket/dbus reactivation; use
  /etc/systemd/user/ global overrides for user services instead of
  unreliable su-based systemctl --user. Remove chromium/upower from
  strip_packages since kiosk reinstalls them immediately.
maziggy 2 ماه پیش
والد
کامیت
09ebac1c09

+ 1 - 1
CHANGELOG.md

@@ -12,7 +12,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **External Folder Mounting for File Manager** ([#124](https://github.com/maziggy/bambuddy/issues/124)) — Host directories (NAS shares, USB drives, network storage) can now be mounted into the File Manager without copying files. Click "Link External" to point at a Docker bind-mounted path. Files are indexed into the database on scan but accessed directly from their original location — nothing is copied. Supports read-only mode (default, blocks uploads/moves/deletes), hidden file filtering, and automatic thumbnail extraction for 3MF, STL, gcode, and image files. External folders show a distinct icon and info bar with a rescan button. Deleting an external folder only removes the database index, never the actual files. Requested by @S1N4X.
 
 ### Improved
-- **SpoolBuddy Kiosk Performance Optimizations** — Reduced CPU load on Raspberry Pi. Chromium: overrides Debian's default flags via `/etc/chromium.d/spoolbuddy-kiosk` to disable GPU rasterization (falls back to CPU on Pi's VideoCore VI), enable low-end device mode (caps memory on ≤2GB Pis), and disable smooth scrolling and background networking. The system default `--enable-gpu-rasterization` was conflicting with per-launch flags — the new config replaces all system defaults. WebSocket: SpoolBuddy Dashboard and Layout pages now use React Query `select` to extract only `connected` status from printer queries, so temperature/fan/progress updates no longer trigger re-renders on every MQTT tick. Services: expanded install script stripping to disable pipewire audio stack, CUPS printing, rpcbind, upower, polkit, accounts-daemon, xdg-desktop-portal, and mpris-proxy. User-level services are masked via `systemctl --user`.
+- **SpoolBuddy Kiosk Performance Optimizations** — Reduced idle CPU load on Raspberry Pi from ~3.3 to ~0.9. Frontend: replaced expensive CSS animations on the idle dashboard (`animate-ping` with scale transforms, `blur-2xl` glow, continuous `animate-pulse` on status dots) with static elements and a slow color-cycling spool (5s interval). Chromium: added `--disable-extensions`, `--disable-background-timer-throttling`, `--memory-pressure-off`, `--disable-renderer-backgrounding`, `--disable-breakpad`, and `--js-flags=--max-old-space-size=128` to `/etc/chromium.d/spoolbuddy-kiosk`. WebSocket: SpoolBuddy Dashboard and Layout pages now use React Query `select` to extract only `connected` status from printer queries, so temperature/fan/progress updates no longer trigger re-renders on every MQTT tick. Services: stripped services are now masked (not just disabled) to prevent socket/dbus reactivation; user-level services (xdg-desktop-portal, mpris-proxy, pipewire, etc.) are masked globally via `/etc/systemd/user/` overrides instead of unreliable `su -l systemctl --user`. Removed chromium and upower from `strip_packages` since the kiosk needs them — they were being uninstalled then immediately reinstalled on every run.
 - **SpoolBuddy AMS Slot Action Picker** — Clicking an AMS slot on the SpoolBuddy AMS page now shows a picker with contextual actions: Configure AMS Slot (set filament preset, K-profile, color), and either Assign Spool / Link to Spoolman (when no spool is mapped) or Unassign / Unlink (when one is). Works with both internal inventory and Spoolman. Previously the slot click went straight to the configure modal with no way to manage spool assignments.
 - **Unassign Button in Edit Spool Modal** — The edit spool modal now has an "Unassign" button next to "Delete Tag" that removes the spool's AMS slot assignment, clearing the location column in the inventory table.
 - **SpoolBuddy Settings Device Tab No Longer Scrolls** — Removed the branding card, folded Device ID into the Device Info card, placed Backend/Auth config and diagnostic buttons side by side in a 2-column layout, removed the redundant online/offline status row from Device Info, and tightened spacing throughout. The Device tab now fits on the small SpoolBuddy touchscreen without scrolling.

+ 1 - 1
frontend/src/components/spoolbuddy/SpoolBuddyStatusBar.tsx

@@ -31,7 +31,7 @@ export function SpoolBuddyStatusBar({ alert }: SpoolBuddyStatusBarProps) {
   return (
     <div className={`h-9 bg-bambu-dark-secondary border-t-2 ${borderColor} flex items-center px-3 gap-3 shrink-0`}>
       {/* Status LED */}
-      <div className={`w-3.5 h-3.5 rounded-full ${statusColor} animate-pulse`} />
+      <div className={`w-3.5 h-3.5 rounded-full ${statusColor}`} />
 
       {/* Status message */}
       <div className="flex-1 text-sm text-white/50 truncate">

+ 20 - 26
frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx

@@ -9,12 +9,6 @@ import { SpoolInfoCard, UnknownTagCard } from '../../components/spoolbuddy/Spool
 import { AssignToAmsModal } from '../../components/spoolbuddy/AssignToAmsModal';
 import { LinkSpoolModal } from '../../components/spoolbuddy/LinkSpoolModal';
 
-// Color palette for the cycling spool animation
-const SPOOL_COLORS = [
-  '#00AE42', '#FF6B35', '#3B82F6', '#EF4444', '#A855F7',
-  '#FBBF24', '#14B8A6', '#EC4899', '#F97316', '#22C55E',
-];
-
 function normalizeHexTag(value: string | null | undefined): string {
   if (!value) return '';
   return value.replace(/[^0-9a-f]/gi, '').toUpperCase();
@@ -29,15 +23,21 @@ function tagsEquivalent(a: string | null | undefined, b: string | null | undefin
   return aNorm.endsWith(bNorm) || bNorm.endsWith(aNorm);
 }
 
-// --- Idle state with color-cycling spool and NFC waves ---
-function ColorCyclingSpool() {
+// Color palette for the cycling spool animation
+const SPOOL_COLORS = [
+  '#00AE42', '#FF6B35', '#3B82F6', '#EF4444', '#A855F7',
+  '#FBBF24', '#14B8A6', '#EC4899', '#F97316', '#22C55E',
+];
+
+// --- Idle state with slow color-cycling spool ---
+function IdleSpool() {
   const { t } = useTranslation();
   const [colorIndex, setColorIndex] = useState(0);
 
   useEffect(() => {
     const interval = setInterval(() => {
       setColorIndex((prev) => (prev + 1) % SPOOL_COLORS.length);
-    }, 2000);
+    }, 5000);
     return () => clearInterval(interval);
   }, []);
 
@@ -45,22 +45,16 @@ function ColorCyclingSpool() {
 
   return (
     <div className="flex flex-col items-center text-center">
-      {/* Animated spool with NFC waves */}
+      {/* Spool with single subtle NFC ring */}
       <div className="relative mb-6 flex items-center justify-center" style={{ width: 160, height: 160 }}>
-        {/* NFC wave rings */}
-        <div className="absolute w-24 h-24 rounded-full border-2 border-green-500/30 animate-ping" style={{ animationDuration: '2.5s' }} />
-        <div className="absolute w-32 h-32 rounded-full border border-green-500/20 animate-ping" style={{ animationDuration: '2.5s', animationDelay: '0.4s' }} />
-        <div className="absolute w-40 h-40 rounded-full border border-green-500/10 animate-ping" style={{ animationDuration: '2.5s', animationDelay: '0.8s' }} />
-
-        {/* Spool icon with color transition and glow */}
-        <div className="relative">
-          <div
-            className="absolute -inset-4 rounded-full blur-2xl opacity-30 transition-colors duration-1000"
-            style={{ backgroundColor: currentColor }}
-          />
-          <div className="transition-all duration-1000">
-            <SpoolIcon color={currentColor} isEmpty={false} size={100} />
-          </div>
+        {/* Static NFC wave rings */}
+        <div className="absolute w-24 h-24 rounded-full border-2 border-green-500/30" />
+        <div className="absolute w-32 h-32 rounded-full border border-green-500/20" />
+        <div className="absolute w-40 h-40 rounded-full border border-green-500/10" />
+
+        {/* Spool icon with slow color transition */}
+        <div className="relative transition-colors duration-[2000ms]">
+          <SpoolIcon color={currentColor} isEmpty={false} size={100} />
         </div>
       </div>
 
@@ -314,7 +308,7 @@ export function SpoolBuddyDashboard() {
             <div className="space-y-2.5">
               {/* Connection status */}
               <div className="flex items-center gap-3">
-                <div className={`w-2.5 h-2.5 rounded-full ${sbState.deviceOnline ? 'bg-green-500 animate-pulse' : 'bg-red-500'}`} />
+                <div className={`w-2.5 h-2.5 rounded-full ${sbState.deviceOnline ? 'bg-green-500' : 'bg-red-500'}`} />
                 <span className="text-base text-zinc-400">
                   {sbState.deviceOnline ? t('spoolbuddy.status.online', 'Online') : t('spoolbuddy.status.offline', 'Disconnected')}
                 </span>
@@ -410,7 +404,7 @@ export function SpoolBuddyDashboard() {
                   onClose={handleCloseSpoolCard}
                 />
               ) : (
-                <ColorCyclingSpool />
+                <IdleSpool />
               )}
             </div>
           </div>

+ 25 - 26
spoolbuddy/install/install.sh

@@ -846,33 +846,31 @@ strip_services() {
         success "No unnecessary services to disable"
     fi
 
-    # Disable user-level services (audio stack, portals, mpris-proxy)
-    # These run under the kiosk user and aren't caught by system-level disable
-    local kiosk_user="${SUDO_USER:-$(logname 2>/dev/null || echo pi)}"
-    if id "$kiosk_user" &>/dev/null; then
-        local user_services=(
-            pipewire.service
-            pipewire.socket
-            pipewire-pulse.service
-            pipewire-pulse.socket
-            wireplumber.service
-            xdg-desktop-portal.service
-            xdg-desktop-portal-gtk.service
-            xdg-document-portal.service
-            xdg-permission-store.service
-            mpris-proxy.service
-        )
-        local user_disabled=0
-        for svc in "${user_services[@]}"; do
-            if su -l "$kiosk_user" -c "systemctl --user is-enabled $svc" &>/dev/null; then
-                su -l "$kiosk_user" -c "systemctl --user disable $svc" 2>/dev/null || true
-                su -l "$kiosk_user" -c "systemctl --user mask $svc" 2>/dev/null || true
-                (( ++user_disabled ))
-            fi
-        done
-        if (( user_disabled > 0 )); then
-            success "Disabled $user_disabled unnecessary user services for $kiosk_user"
+    # Mask user-level services globally via /etc/systemd/user/ overrides.
+    # The su-based approach doesn't reliably reach the user's systemd instance
+    # when run from sudo, so we create global masks that apply before login.
+    local user_services=(
+        pipewire.service
+        pipewire.socket
+        pipewire-pulse.service
+        pipewire-pulse.socket
+        wireplumber.service
+        xdg-desktop-portal.service
+        xdg-desktop-portal-gtk.service
+        xdg-document-portal.service
+        xdg-permission-store.service
+        mpris-proxy.service
+    )
+    mkdir -p /etc/systemd/user
+    local user_masked=0
+    for svc in "${user_services[@]}"; do
+        if [[ ! -L "/etc/systemd/user/$svc" ]] || [[ "$(readlink /etc/systemd/user/$svc)" != "/dev/null" ]]; then
+            ln -sf /dev/null "/etc/systemd/user/$svc"
+            (( ++user_masked ))
         fi
+    done
+    if (( user_masked > 0 )); then
+        success "Masked $user_masked unnecessary user services globally"
     fi
 }
 
@@ -1167,6 +1165,7 @@ exec chromium --kiosk --no-first-run --disable-infobars \
     --noerrdialogs --disable-component-update \
     --overscroll-history-navigation=0 \
     --ozone-platform=wayland \
+    --disable-crash-reporter --disable-breakpad \
     "\$kiosk_url"
 EOF
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-C0h3VoP7.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-CnzPkDwq.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-DWwO6mIe.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BJ5z52r8.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-DWwO6mIe.css">
+    <script type="module" crossorigin src="/assets/index-CnzPkDwq.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-C0h3VoP7.css">
   </head>
   <body>
     <div id="root"></div>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است