This commit is contained in:
legop3
2025-11-15 21:49:00 -05:00
parent 392e2f661b
commit edcb465630
13 changed files with 235 additions and 357 deletions
+67 -17
View File
@@ -1,3 +1,4 @@
import { useMemo, useState } from 'react';
import { useSession } from '../context/SessionContext.jsx';
import { useTelemetryFrame } from '../context/TelemetryContext.jsx';
@@ -7,50 +8,99 @@ function formatMetric(value, fallback = '--') {
}
export default function TelemetryPanel() {
const { session } = useSession();
const { connected, session, requestControl } = useSession();
const roverId = session?.assignment?.roverId;
const frame = useTelemetryFrame(roverId);
const sensors = frame?.sensors || {};
const voltage = sensors.voltageMv != null ? (sensors.voltageMv / 1000).toFixed(2) : null;
const current = sensors.currentMa != null ? (sensors.currentMa / 1000).toFixed(2) : null;
const voltage = sensors.voltageMv != null ? `${(sensors.voltageMv / 1000).toFixed(2)} V` : null;
const current = sensors.currentMa != null ? `${sensors.currentMa} mA` : null;
const charge = sensors.batteryChargeMah;
const capacity = sensors.batteryCapacityMah;
const updated = frame?.receivedAt ? new Date(frame.receivedAt).toLocaleTimeString() : null;
const rawSnippet = frame?.raw ? (frame.raw.length > 80 ? `${frame.raw.slice(0, 80)}` : frame.raw) : null;
const roster = session?.roster ?? [];
const isAdmin = useMemo(
() => session?.role === 'admin' || session?.role === 'lockdown' || session?.role === 'lockdown-admin',
[session?.role],
);
const [pending, setPending] = useState({});
async function handleRequest(roverId) {
if (!roverId) return;
setPending((prev) => ({ ...prev, [roverId]: true }));
try {
await requestControl(roverId);
} catch (err) {
alert(err.message);
} finally {
setPending((prev) => ({ ...prev, [roverId]: false }));
}
}
return (
<section className="rounded-lg border border-slate-900 bg-slate-950/70 p-2 text-[0.8rem] text-slate-200">
<div className="flex items-center justify-between text-[0.65rem] uppercase tracking-[0.3em] text-slate-500">
<span>Telemetry</span>
<span>{updated ? `Updated ${updated}` : 'waiting'}</span>
<section className="rounded-sm bg-[#1b1b1b] p-1 text-sm text-slate-200">
<div className="text-[0.7rem] text-slate-400">
<span>{connected ? 'online' : 'offline'}</span>
<span> · role {session?.role || 'unknown'}</span>
<span> · mode {session?.mode || '--'}</span>
{updated && <span> · sensors {updated}</span>}
</div>
{!roverId ? (
<p className="mt-2 text-[0.75rem] text-slate-400">Assignment required to view sensors.</p>
<p className="mt-1 text-[0.75rem] text-slate-400">Assign a rover to view sensors.</p>
) : !frame ? (
<p className="mt-2 text-[0.75rem] text-slate-400">No sensor frames yet.</p>
<p className="mt-1 text-[0.75rem] text-slate-400">Waiting for sensor frames</p>
) : (
<div className="mt-2 space-y-2">
<div className="mt-1 space-y-1 text-[0.75rem]">
<Metric label="Charge" value={formatMetric(charge != null && capacity != null ? `${charge}/${capacity} mAh` : null)} />
<Metric label="Charging" value={formatMetric(sensors.chargingState?.label)} />
<Metric label="OI Mode" value={formatMetric(sensors.oiMode?.label)} />
<Metric label="Voltage" value={formatMetric(voltage ? `${voltage} V` : null)} />
<Metric label="Current" value={formatMetric(current ? `${current} A` : null)} />
<Metric label="OI mode" value={formatMetric(sensors.oiMode?.label)} />
<Metric label="Voltage" value={formatMetric(voltage)} />
<Metric label="Current" value={formatMetric(current)} />
</div>
)}
{rawSnippet && (
<pre className="mt-2 overflow-hidden rounded border border-slate-800 bg-black/60 p-1 text-[0.6rem] text-lime-300">
<pre className="mt-1 overflow-hidden rounded-sm bg-black/60 p-1 text-[0.6rem] text-lime-300">
{rawSnippet}
</pre>
)}
<div className="mt-1">
<p className="text-xs text-slate-400">Rovers</p>
{roster.length === 0 ? (
<p className="text-xs text-slate-500">No roster data.</p>
) : (
<ul className="mt-1 space-y-1 text-xs">
{roster.map((rover) => (
<li key={rover.id} className="flex items-center justify-between gap-1">
<div>
<p className="text-slate-200">{rover.name}</p>
<p className="text-[0.6rem] text-slate-500">
{rover.locked ? 'locked' : 'free'} · {rover.lastSeen ? 'seen' : 'unknown'}
</p>
</div>
{isAdmin && (
<button
type="button"
onClick={() => handleRequest(rover.id)}
disabled={pending[rover.id]}
className="rounded-sm bg-black/40 px-1 py-0.5 text-[0.6rem] text-slate-200 disabled:opacity-40"
>
{pending[rover.id] ? '...' : 'request'}
</button>
)}
</li>
))}
</ul>
)}
</div>
</section>
);
}
function Metric({ label, value }) {
return (
<div className="flex items-center justify-between text-[0.75rem]">
<span className="text-slate-500">{label}</span>
<span className="font-semibold text-slate-200">{value}</span>
<div className="flex items-center justify-between">
<span className="text-slate-400">{label}</span>
<span className="text-slate-200">{value}</span>
</div>
);
}