mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
96 lines
3.1 KiB
JavaScript
96 lines
3.1 KiB
JavaScript
// Hook: useDockIr
|
|
// Purpose: Tracks dock IR telemetry/status values for docking-related UI indicators. Scope: Converts telemetry feed updates into component-friendly reactive state.
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
|
const HOLD_MS = 650;
|
|
|
|
const DOCK_CODES = {
|
|
161: { red: true }, // Red buoy
|
|
164: { green: true }, // Green buoy
|
|
165: { force: true }, // Force field only
|
|
168: { red: true, green: true }, // Red + green
|
|
169: { red: true, force: true }, // Red + force
|
|
172: { green: true, force: true }, // Green + force
|
|
173: { red: true, green: true, force: true }, // Red + green + force
|
|
};
|
|
|
|
function decodeDockBits(code) {
|
|
if (typeof code !== 'number' || code <= 0) return null;
|
|
const bits = DOCK_CODES[code];
|
|
if (!bits) return null;
|
|
return { code, ...bits };
|
|
}
|
|
|
|
/**
|
|
* Returns a smoothed view of dock IR signals. Keeps the last seen code alive for HOLD_MS to mask blinks.
|
|
*/
|
|
export function useDockIr(sensors, options = {}) {
|
|
const holdMs = options.holdMs || HOLD_MS;
|
|
const [state, setState] = useState({
|
|
left: null,
|
|
right: null,
|
|
omni: null,
|
|
});
|
|
const [tick, setTick] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const id = setInterval(() => setTick(Date.now()), Math.max(holdMs / 2, 100));
|
|
return () => clearInterval(id);
|
|
}, [holdMs]);
|
|
|
|
useEffect(() => {
|
|
const now = Date.now();
|
|
const left = decodeDockBits(sensors?.infraredCharacterLeft);
|
|
const right = decodeDockBits(sensors?.infraredCharacterRight);
|
|
const omni = decodeDockBits(sensors?.infraredCharacterOmni);
|
|
if (!left && !right && !omni) {
|
|
return;
|
|
}
|
|
setState((prev) => ({
|
|
left: left ? { ...left, ts: now } : prev.left,
|
|
right: right ? { ...right, ts: now } : prev.right,
|
|
omni: omni ? { ...omni, ts: now } : prev.omni,
|
|
}));
|
|
}, [sensors?.infraredCharacterLeft, sensors?.infraredCharacterRight, sensors?.infraredCharacterOmni]);
|
|
|
|
return useMemo(() => {
|
|
const now = Date.now();
|
|
const withWindow = (entry) => {
|
|
if (!entry) return { active: false, red: false, green: false, force: false, age: null, code: null };
|
|
const age = now - entry.ts;
|
|
const active = age <= holdMs;
|
|
return {
|
|
active,
|
|
age,
|
|
code: entry.code,
|
|
red: Boolean(entry.red && active),
|
|
green: Boolean(entry.green && active),
|
|
force: Boolean(entry.force && active),
|
|
};
|
|
};
|
|
|
|
const left = withWindow(state.left);
|
|
const right = withWindow(state.right);
|
|
const omni = withWindow(state.omni);
|
|
const forceDetected = left.force || right.force || omni.force;
|
|
const visible = left.active || right.active || omni.active;
|
|
const bias =
|
|
left.active && !right.active
|
|
? 'right'
|
|
: right.active && !left.active
|
|
? 'left'
|
|
: 'center';
|
|
const balance = (right.active ? 1 : 0) - (left.active ? 1 : 0); // positive means steer left-to-right
|
|
|
|
return {
|
|
visible,
|
|
forceDetected,
|
|
left,
|
|
right,
|
|
omni,
|
|
bias, // left/right/center for steering cue
|
|
balance: Math.max(-1, Math.min(1, balance)),
|
|
};
|
|
}, [state.left, state.right, state.omni, holdMs, tick]);
|
|
}
|