feat: wireview pin-imbalance warning, per-pin rating bars, pin_imbalance_a

- Per-pin bar now scales against the 9.2 A per-pin rating (10 A full
  scale, red at/over rating) instead of the 55 A total connector OCP
- Debounced app-wide warning banner (visible on all tabs) when the
  current between two pins diverges by > 2 A for ~7 s - the signature
  of a bad pin contact that can overheat and melt the 12VHPWR
  connector; 5 s grace period after clearing
- New pin_imbalance_a sample field (max - min pin current) in both the
  serial and hwmon sample builders, plus frontend type
- Docs: README + WireView.md cover the warning and threshold; fresh
  screenshot with the new per-pin labels
- Tests: vitest setup + 9 unit tests for the debounce state machine;
  3 new backend checks for pin_imbalance_a
This commit is contained in:
ARIA committed 2026-10-03 22:13:13 +02:00
1 parent 92356d1409
commit b69b7502b3
14 files changed
+810 -18

No files matched your search

@@ -0,0 +1,78 @@
import { useEffect, useRef, useState } from "react";
import type { WireViewSample } from "../types.js";
// Current difference between any two pins that indicates a bad contact.
const PIN_IMBALANCE_A = 2;
// Show only after the imbalance holds for this many consecutive samples (~1 s each).
// AI workloads produce frequent short load ramps that skew one pin transiently,
// so the threshold is deliberately high: a bad contact stays imbalanced, a ramp does not.
const SHOW_AFTER = 7;
// Keep showing this many samples after the imbalance clears (grace period).
const HIDE_AFTER = 5;
export interface ImbalanceAlert {
diff: number;
maxI: number;
minI: number;
/** 1-based pin index carrying the most current */
hiPin: number;
/** 1-based pin index carrying the least current */
loPin: number;
}
/**
* Debounced pin-current imbalance detection.
*
* A single sample can transiently skew (load change, measurement glitch), so
* the alert only appears after the imbalance persists for a few samples, and
* stays visible briefly after it clears instead of flickering.
*/
export function useWireviewImbalance(
sample: WireViewSample | null,
): ImbalanceAlert | null {
const [alert, setAlert] = useState<ImbalanceAlert | null>(null);
const alertRef = useRef<ImbalanceAlert | null>(null);
const streak = useRef(0);
const grace = useRef(0);
useEffect(() => {
function update(next: ImbalanceAlert | null) {
alertRef.current = next;
setAlert(next);
}
if (!sample || sample.pins.length < 2) {
streak.current = 0;
grace.current = 0;
if (alertRef.current) update(null);
return;
}
const currents = sample.pins.map((p) => p.current_a);
const maxI = Math.max(...currents);
const minI = Math.min(...currents);
const diff = maxI - minI;
if (diff > PIN_IMBALANCE_A) {
streak.current += 1;
grace.current = 0;
if (streak.current >= SHOW_AFTER) {
update({
diff,
maxI,
minI,
hiPin: currents.indexOf(maxI) + 1,
loPin: currents.indexOf(minI) + 1,
});
}
} else {
streak.current = 0;
if (alertRef.current) {
grace.current += 1;
if (grace.current >= HIDE_AFTER) update(null);
}
}
}, [sample]);
return alert;
}