mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
slop
This commit is contained in:
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -78,8 +78,8 @@
|
||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||
<title>Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-ylLtL6Of.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BzceGRD0.css">
|
||||
<script type="module" crossorigin src="/assets/index-U2WtZsRF.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BTjcgOin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -23,13 +23,7 @@ const FRAME_ROOM = 'balance-board-viewers';
|
||||
const TARE_SAMPLE_COUNT = 20;
|
||||
const MAX_AUTOMATIC_TARE_KG = 2;
|
||||
const execFileAsync = promisify(execFile);
|
||||
const ALERT_COLORS = {
|
||||
connected: '#10b981',
|
||||
sleeping: '#64748b',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444',
|
||||
info: '#38bdf8',
|
||||
};
|
||||
const ALERT_COLOR = '#38bdf8';
|
||||
|
||||
function loadStore() {
|
||||
try {
|
||||
@@ -89,49 +83,25 @@ let lastAlertKey = '';
|
||||
let unpairing = false;
|
||||
|
||||
function sendStatusAlert(workerState, message = '') {
|
||||
let alert = null;
|
||||
if (workerState === 'connected') {
|
||||
alert = {
|
||||
color: ALERT_COLORS.connected,
|
||||
title: 'Balance Board connected',
|
||||
message: 'Live weight is available.',
|
||||
};
|
||||
} else if (workerState === 'sleeping') {
|
||||
alert = {
|
||||
color: ALERT_COLORS.sleeping,
|
||||
title: 'Balance Board sleeping',
|
||||
message: 'Press the front power button when you are ready to use it.',
|
||||
};
|
||||
} else if (workerState === 'waiting' && previousWorkerState === 'connected') {
|
||||
alert = {
|
||||
color: ALERT_COLORS.warning,
|
||||
title: 'Balance Board disconnected',
|
||||
message: message || 'Press the front power button to reconnect it.',
|
||||
};
|
||||
} else if (workerState === 'connection-failed') {
|
||||
alert = {
|
||||
color: ALERT_COLORS.error,
|
||||
title: 'Balance Board connection failed',
|
||||
message: message || 'The Bluetooth connection did not complete.',
|
||||
};
|
||||
} else if (workerState === 'error') {
|
||||
alert = {
|
||||
color: ALERT_COLORS.error,
|
||||
title: 'Balance Board needs attention',
|
||||
message: message || 'The hardware worker stopped.',
|
||||
};
|
||||
}
|
||||
const shouldAlert =
|
||||
workerState === 'connected' ||
|
||||
workerState === 'sleeping' ||
|
||||
workerState === 'connection-failed' ||
|
||||
workerState === 'error' ||
|
||||
(workerState === 'waiting' && previousWorkerState === 'connected');
|
||||
|
||||
previousWorkerState = workerState;
|
||||
if (!alert) return;
|
||||
if (!shouldAlert) return;
|
||||
|
||||
// Native reconnect retries may repeat the same result while hardware is out
|
||||
// of range. Collapse identical status/message pairs so feed alerts remain
|
||||
// meaningful state changes instead of turning into a transport log.
|
||||
const key = `${workerState}:${alert.message}`;
|
||||
// Keep the alert at the same system-level boundary as the worker protocol:
|
||||
// state first, followed by its exact detail when one exists. The service does
|
||||
// not reinterpret failures as friendlier product copy, but still collapses
|
||||
// identical retries so a failing reconnect cannot flood the activity feed.
|
||||
const rawMessage = message ? `${workerState}: ${message}` : workerState;
|
||||
const key = rawMessage;
|
||||
if (key === lastAlertKey) return;
|
||||
lastAlertKey = key;
|
||||
sendAlert(alert);
|
||||
sendAlert({ color: ALERT_COLOR, title: 'Balance Board', message: rawMessage });
|
||||
}
|
||||
|
||||
function getState() {
|
||||
@@ -197,9 +167,9 @@ function handleWorkerMessage(message = {}) {
|
||||
hardware?.setAddress(address);
|
||||
lastAlertKey = 'paired';
|
||||
sendAlert({
|
||||
color: ALERT_COLORS.info,
|
||||
title: 'Balance Board paired',
|
||||
message: 'Bluetooth setup completed.',
|
||||
color: ALERT_COLOR,
|
||||
title: 'Balance Board',
|
||||
message: 'paired',
|
||||
});
|
||||
updateStatus('connecting', 'Paired. Connecting to the board now.');
|
||||
return;
|
||||
@@ -291,9 +261,9 @@ io.on('connection', (socket) => {
|
||||
hardware?.restart();
|
||||
updateStatus('starting', 'Starting Bluetooth discovery.');
|
||||
sendAlert({
|
||||
color: ALERT_COLORS.warning,
|
||||
title: 'Balance Board unpaired',
|
||||
message: 'Press the red Sync button underneath the board to pair it again.',
|
||||
color: ALERT_COLOR,
|
||||
title: 'Balance Board',
|
||||
message: 'unpaired',
|
||||
});
|
||||
cb({ success: true, warning: bluetoothWarning || null });
|
||||
} catch (err) {
|
||||
|
||||
@@ -36,7 +36,7 @@ function centerOfPressure(corners) {
|
||||
// An empty board naturally has tiny load-cell noise. Keep the marker centered
|
||||
// and subdued until there is enough weight for its position to mean anything;
|
||||
// once loaded, map the normalized left/right and top/bottom balance into the
|
||||
// safe interior of the illustrated board.
|
||||
// safe interior of the load map.
|
||||
if (total < 0.5) return { left: 50, top: 50, active: false };
|
||||
const horizontal = ((topRight + bottomRight) - (topLeft + bottomLeft)) / total;
|
||||
const vertical = ((bottomLeft + bottomRight) - (topLeft + topRight)) / total;
|
||||
@@ -49,7 +49,7 @@ function centerOfPressure(corners) {
|
||||
|
||||
function CornerReading({ className, label, value }) {
|
||||
return (
|
||||
<div className={`absolute rounded bg-slate-950/80 px-1 py-0.5 text-center shadow ${className}`}>
|
||||
<div className={`surface absolute min-w-[5.5rem] text-center ${className}`}>
|
||||
<div className="text-[0.62rem] text-slate-400">{label}</div>
|
||||
<div className="font-mono text-sm font-semibold text-slate-100">{formatWeight(value)}</div>
|
||||
</div>
|
||||
@@ -70,7 +70,6 @@ function BalanceBoardPanelContent() {
|
||||
const role = useSessionSelector((state) => state.session?.role || null);
|
||||
const [frame, setFrame] = useState(EMPTY_FRAME);
|
||||
const [unpairing, setUnpairing] = useState(false);
|
||||
const [adminMessage, setAdminMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket) return undefined;
|
||||
@@ -115,82 +114,71 @@ function BalanceBoardPanelContent() {
|
||||
if (unpairing || !board?.paired) return;
|
||||
if (!window.confirm('Unpair this Balance Board and require the red Sync button to pair it again?')) return;
|
||||
setUnpairing(true);
|
||||
setAdminMessage('');
|
||||
socket.emit('balanceBoard:unpair', {}, (response = {}) => {
|
||||
setUnpairing(false);
|
||||
if (response.error) {
|
||||
setAdminMessage(response.error);
|
||||
window.alert(response.error);
|
||||
} else if (response.warning) {
|
||||
setAdminMessage('Board forgotten locally, but BlueZ reported a bond-removal warning.');
|
||||
} else {
|
||||
setAdminMessage('Board unpaired. Press the red Sync button to pair it again.');
|
||||
window.alert('Board forgotten locally, but BlueZ reported a bond-removal warning.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const actions = (
|
||||
<div className="flex items-center gap-0.5">
|
||||
{battery != null ? (
|
||||
<span className="font-mono text-xs text-slate-300">{Math.round(battery)}% battery</span>
|
||||
) : null}
|
||||
{isAdmin ? (
|
||||
<button
|
||||
type="button"
|
||||
className="button-dark text-xs disabled:opacity-50"
|
||||
disabled={!board?.paired || unpairing}
|
||||
onClick={unpair}
|
||||
>
|
||||
{unpairing ? 'Unpairing…' : 'Unpair'}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<CardFrame
|
||||
title="Balance Board"
|
||||
className="relative w-full"
|
||||
bodyClassName="text-sm text-slate-200"
|
||||
actions={battery != null ? (
|
||||
<span className="font-mono text-xs text-slate-300">{Math.round(battery)}% battery</span>
|
||||
) : null}
|
||||
actions={actions}
|
||||
>
|
||||
{sleeping ? (
|
||||
<div className="absolute inset-0 z-20 flex items-center justify-center rounded-md bg-slate-950/85 px-2 text-center">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-lg font-semibold text-slate-100">Balance Board is asleep</p>
|
||||
<p className="text-lg font-semibold text-slate-100">The Balance Board is asleep</p>
|
||||
<p className="text-sm text-slate-300">Press the front power button on the board to wake it.</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-0.5 md:grid-cols-[minmax(0,1.25fr)_minmax(9rem,0.75fr)]">
|
||||
<section className="surface-muted p-1">
|
||||
<div className="relative mx-auto aspect-[1.35/1] w-full max-w-md overflow-hidden rounded-[1.5rem] border-2 border-slate-500 bg-gradient-to-b from-slate-700 to-slate-800 shadow-inner">
|
||||
<div className="absolute inset-[12%] rounded-[1rem] border border-slate-500/70 bg-slate-900/35" />
|
||||
<CornerReading className="left-1 top-1" label="Top left" value={corners.topLeft} />
|
||||
<CornerReading className="right-1 top-1" label="Top right" value={corners.topRight} />
|
||||
<CornerReading className="bottom-1 left-1" label="Bottom left" value={corners.bottomLeft} />
|
||||
<CornerReading className="bottom-1 right-1" label="Bottom right" value={corners.bottomRight} />
|
||||
<div
|
||||
aria-label="Center of pressure"
|
||||
className={`absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 shadow-lg transition-all duration-100 ${
|
||||
center.active
|
||||
? 'border-cyan-100 bg-cyan-400 shadow-cyan-400/50'
|
||||
: 'border-slate-400 bg-slate-600 opacity-50'
|
||||
}`}
|
||||
style={{ left: `${center.left}%`, top: `${center.top}%` }}
|
||||
/>
|
||||
<div className="absolute bottom-0.5 left-1/2 h-1.5 w-8 -translate-x-1/2 rounded-full bg-sky-400/70" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid content-start gap-0.5">
|
||||
<section className="surface-muted px-1 py-1.5 text-center">
|
||||
<div className="text-xs text-slate-400">Total weight</div>
|
||||
<div className="mt-0.5 font-mono text-4xl font-bold leading-none text-white">
|
||||
<div className="panel-section relative h-40 overflow-hidden">
|
||||
<CornerReading className="left-0.5 top-0.5" label="Top left" value={corners.topLeft} />
|
||||
<CornerReading className="right-0.5 top-0.5" label="Top right" value={corners.topRight} />
|
||||
<CornerReading className="bottom-0.5 left-0.5" label="Bottom left" value={corners.bottomLeft} />
|
||||
<CornerReading className="bottom-0.5 right-0.5" label="Bottom right" value={corners.bottomRight} />
|
||||
<div
|
||||
aria-label="Center of pressure"
|
||||
className={`absolute z-10 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border transition-all duration-100 ${
|
||||
center.active
|
||||
? 'border-sky-200 bg-sky-500'
|
||||
: 'border-neutral-500 bg-neutral-600 opacity-50'
|
||||
}`}
|
||||
style={{ left: `${center.left}%`, top: `${center.top}%` }}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="surface px-1 py-0.5 text-center">
|
||||
<div className="text-[0.65rem] text-slate-400">Total weight</div>
|
||||
<div className="font-mono text-3xl font-bold leading-none text-white">
|
||||
{formatWeight(liveFrame.totalKg)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{isAdmin ? (
|
||||
<section className="surface-muted grid gap-0.5 p-0.5">
|
||||
{board?.address ? (
|
||||
<div className="truncate text-center font-mono text-[0.65rem] text-slate-500">{board.address}</div>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className="button-dark w-full text-xs disabled:opacity-50"
|
||||
disabled={!board?.paired || unpairing}
|
||||
onClick={unpair}
|
||||
>
|
||||
{unpairing ? 'Unpairing…' : 'Unpair board'}
|
||||
</button>
|
||||
{adminMessage ? <p className="text-center text-xs text-slate-400">{adminMessage}</p> : null}
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFrame>
|
||||
|
||||
Reference in New Issue
Block a user