mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 07:25:44 +02:00
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.
This commit is contained in:
+1
-1
@@ -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.
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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' }} />
|
||||
{/* 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 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>
|
||||
{/* 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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user