All files / client/src/components/features pin-monitor.tsx

4.76% Statements 1/21
0% Branches 0/21
0% Functions 0/7
5.26% Lines 1/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                  1x                                                                                                                                                                        
import { useMemo, useRef, useState } from "react";
import type { BatchStats, PinState } from "@/hooks/use-simulation-store";
import { clsx } from "clsx";
 
interface PinMonitorProps {
  pinStates: PinState[];
  batchStats?: BatchStats;
}
 
const PWM_ALPHA = 0.2; // smoothing factor
 
export function PinMonitor({ pinStates, batchStats }: PinMonitorProps) {
  const [showPerf, setShowPerf] = useState(false);
  const pwmAveragesRef = useRef<Map<number, number>>(new Map());
 
  const displayStates = useMemo(() => {
    const sorted = [...pinStates].sort((a, b) => a.pin - b.pin);
    const next = sorted.map((state) => {
      if (state.type !== "pwm") {
        return { ...state, displayValue: state.value };
      }
 
      const prevAvg = pwmAveragesRef.current.get(state.pin) ?? state.value;
      const smoothed = prevAvg + PWM_ALPHA * (state.value - prevAvg);
      pwmAveragesRef.current.set(state.pin, smoothed);
 
      return { ...state, displayValue: smoothed };
    });
 
    return next;
  }, [pinStates]);
 
  return (
    <div
      className="w-full rounded-lg border border-border bg-card p-3"
      data-testid="pin-monitor"
    >
      <div className="flex items-center justify-between mb-2">
        <div className="text-sm font-semibold text-foreground">Pin Monitor</div>
        <button
          type="button"
          className="text-xs text-muted-foreground hover:text-foreground"
          onClick={() => setShowPerf((prev) => !prev)}
        >
          {showPerf ? "Hide FPS" : "Show FPS"}
        </button>
      </div>
 
      {showPerf && batchStats && (
        <div className="mb-2 text-xs text-muted-foreground">
          <div>Batch ms: {batchStats.lastBatchMs.toFixed(2)}</div>
          <div>Last batch size: {batchStats.lastBatchSize}</div>
        </div>
      )}
 
      <div className="grid grid-cols-2 md:grid-cols-3 gap-2">
        {displayStates.map((state) => {
          const isHigh = state.type !== "pwm" && state.value > 0;
          const isPwm = state.type === "pwm";
          const displayValue = isPwm
            ? Math.round(state.displayValue)
            : state.value > 0
              ? "HIGH"
              : "LOW";
 
          return (
            <div
              key={state.pin}
              data-pin={state.pin}
              className={clsx(
                "flex items-center justify-between rounded-md border px-2 py-1 text-xs",
                isHigh && !isPwm
                  ? "border-green-400 text-green-500"
                  : "border-border text-muted-foreground",
              )}
            >
              <span>Pin {state.pin}</span>
              <span
                className={clsx(
                  "font-mono",
                  isPwm && "text-amber-500",
                )}
                data-value
              >
                {displayValue}
              </span>
            </div>
          );
        })}
      </div>
    </div>
  );
}