This commit is contained in:
legop3
2026-05-01 18:56:11 -04:00
parent 2833175fca
commit b76405ed46
7 changed files with 45 additions and 32 deletions
+3
View File
@@ -0,0 +1,3 @@
# web UI optimization
- reduce amount of invalidation for unrelated session and socket updates, wherever possible.
- example: new log line invalidates more than just log panels
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-FrukTaHm.js"></script> <script type="module" crossorigin src="/assets/index-BT4tyIib.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DYarsG-0.css"> <link rel="stylesheet" crossorigin href="/assets/index-DYarsG-0.css">
</head> </head>
<body> <body>
@@ -11,6 +11,10 @@ function sanitizeLogText(value) {
.replace(/\x1B\[[0-9;]*[A-Za-z]/g, ''); .replace(/\x1B\[[0-9;]*[A-Za-z]/g, '');
} }
function shellQuote(value) {
return `'${String(value || '').replace(/'/g, `'\"'\"'`)}'`;
}
function parsePayloadFromLine(line) { function parsePayloadFromLine(line) {
const raw = sanitizeLogText(line).trim(); const raw = sanitizeLogText(line).trim();
if (!raw) return null; if (!raw) return null;
@@ -264,20 +268,26 @@ function createLidarRuntime({ logger, host, port = 6053, key, logFile = '', shou
if (state.process) return; if (state.process) return;
ensureLogStream(); ensureLogStream();
const args = [ const baseCommand = [
'env',
'PYTHONUNBUFFERED=1',
'NO_COLOR=1',
'TERM=dumb',
'uvx',
'--from', '--from',
'aioesphomeapi', 'aioesphomeapi',
'aioesphomeapi-logs', 'aioesphomeapi-logs',
host, shellQuote(host),
'--port', '--port',
String(port || 6053), shellQuote(String(port || 6053)),
'--noise-psk', '--noise-psk',
key, shellQuote(key),
'--no-states', '--no-states',
]; ].join(' ');
const launchCommand = `if command -v script >/dev/null 2>&1; then exec script -qefc ${shellQuote(baseCommand)} /dev/null; else exec ${baseCommand}; fi`;
logger.info('Starting Neato lidar log stream', { host, port: Number(port || 6053) }); logger.info('Starting Neato lidar log stream', { host, port: Number(port || 6053) });
const proc = spawn('uvx', args, { const proc = spawn('bash', ['-lc', launchCommand], {
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],
}); });
state.process = proc; state.process = proc;
-2
View File
@@ -16,7 +16,6 @@ export default function VipPanel() {
const { const {
session, session,
neatoLidar, neatoLidar,
neatoLidarLines,
identifySession, identifySession,
requestVerification, requestVerification,
requestPrivateRoverAccess, requestPrivateRoverAccess,
@@ -81,7 +80,6 @@ export default function VipPanel() {
<VipNeatoCard <VipNeatoCard
neato={session?.neato || null} neato={session?.neato || null}
lidar={neatoLidar} lidar={neatoLidar}
lidarLines={neatoLidarLines}
onStart={neatoStart} onStart={neatoStart}
onSendHome={neatoSendHome} onSendHome={neatoSendHome}
onLocate={neatoLocate} onLocate={neatoLocate}
+16 -2
View File
@@ -2,6 +2,7 @@
// Purpose: Defines the Vip Neato Card module and the local helpers/components used in this file. // Purpose: Defines the Vip Neato Card module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit. // Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSocket } from '../../context/SocketContext.jsx';
function normalizeState(value) { function normalizeState(value) {
return String(value || '').trim(); return String(value || '').trim();
@@ -58,7 +59,6 @@ function buildLidarDots(points = []) {
export default function VipNeatoCard({ export default function VipNeatoCard({
neato, neato,
lidar, lidar,
lidarLines,
onStart, onStart,
onSendHome, onSendHome,
onLocate, onLocate,
@@ -66,9 +66,11 @@ export default function VipNeatoCard({
onPowerCycle, onPowerCycle,
fullWidth = false, fullWidth = false,
}) { }) {
const socket = useSocket();
const [working, setWorking] = useState(''); const [working, setWorking] = useState('');
const [lidarFlash, setLidarFlash] = useState(false); const [lidarFlash, setLidarFlash] = useState(false);
const [showLidarDebug, setShowLidarDebug] = useState(false); const [showLidarDebug, setShowLidarDebug] = useState(false);
const [recentLidarLines, setRecentLidarLines] = useState([]);
const wrapClass = fullWidth ? 'w-full' : 'w-full max-w-xl'; const wrapClass = fullWidth ? 'w-full' : 'w-full max-w-xl';
const configured = Boolean(neato?.configured); const configured = Boolean(neato?.configured);
@@ -85,7 +87,6 @@ export default function VipNeatoCard({
const robotError = normalizeState(neato?.telemetry?.robotError) || '--'; const robotError = normalizeState(neato?.telemetry?.robotError) || '--';
const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--'; const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--';
const lidarPoints = Array.isArray(lidar?.points) ? lidar.points : []; const lidarPoints = Array.isArray(lidar?.points) ? lidar.points : [];
const recentLidarLines = Array.isArray(lidarLines) ? lidarLines : [];
const lidarDots = buildLidarDots(lidarPoints); const lidarDots = buildLidarDots(lidarPoints);
const lidarStatus = normalizeState(lidar?.status) || '--'; const lidarStatus = normalizeState(lidar?.status) || '--';
const lidarDebug = lidar?.debug && typeof lidar.debug === 'object' ? lidar.debug : null; const lidarDebug = lidar?.debug && typeof lidar.debug === 'object' ? lidar.debug : null;
@@ -134,6 +135,19 @@ export default function VipNeatoCard({
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [lidar]); }, [lidar]);
useEffect(() => {
function handleLidarLine(payload = null) {
const next = payload && typeof payload === 'object' ? payload : null;
if (!next?.line) return;
setRecentLidarLines((prev) => [...prev.slice(-199), next]);
}
socket.on('neato:lidarLine', handleLidarLine);
return () => {
socket.off('neato:lidarLine', handleLidarLine);
};
}, [socket]);
return ( return (
<section className={`surface text-sm text-slate-200 ${wrapClass}`}> <section className={`surface text-sm text-slate-200 ${wrapClass}`}>
<div className="grid gap-0.5"> <div className="grid gap-0.5">
-12
View File
@@ -9,7 +9,6 @@ const INITIAL_STATE = {
connected: false, connected: false,
session: null, session: null,
neatoLidar: null, neatoLidar: null,
neatoLidarLines: [],
logs: [], logs: [],
adminLogs: [], adminLogs: [],
llmCommentaryState: null, llmCommentaryState: null,
@@ -133,18 +132,8 @@ export function SessionProvider({ children }) {
const next = payload && typeof payload === 'object' ? payload : null; const next = payload && typeof payload === 'object' ? payload : null;
setState((prev) => ({ ...prev, neatoLidar: next })); setState((prev) => ({ ...prev, neatoLidar: next }));
} }
function handleNeatoLidarLine(payload = null) {
const next = payload && typeof payload === 'object' ? payload : null;
if (!next?.line) return;
setState((prev) => ({
...prev,
neatoLidarLines: [...prev.neatoLidarLines.slice(-199), next],
}));
}
socket.on('session:sync', handleSession); socket.on('session:sync', handleSession);
socket.on('neato:lidar', handleNeatoLidar); socket.on('neato:lidar', handleNeatoLidar);
socket.on('neato:lidarLine', handleNeatoLidarLine);
socket.on('log:init', handleLogInit); socket.on('log:init', handleLogInit);
socket.on('log:entry', handleLogEntry); socket.on('log:entry', handleLogEntry);
socket.on('adminlog:init', handleAdminLogInit); socket.on('adminlog:init', handleAdminLogInit);
@@ -154,7 +143,6 @@ export function SessionProvider({ children }) {
return () => { return () => {
socket.off('session:sync', handleSession); socket.off('session:sync', handleSession);
socket.off('neato:lidar', handleNeatoLidar); socket.off('neato:lidar', handleNeatoLidar);
socket.off('neato:lidarLine', handleNeatoLidarLine);
socket.off('log:init', handleLogInit); socket.off('log:init', handleLogInit);
socket.off('log:entry', handleLogEntry); socket.off('log:entry', handleLogEntry);
socket.off('adminlog:init', handleAdminLogInit); socket.off('adminlog:init', handleAdminLogInit);