- 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
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
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;
|
|
}
|