OverviewFilesCommitsIssuesDocsReleasesPeers

electrical-review / design / schematics.jsx

287 lines · jsx ref:
/* ============================================================================
   PRSM SOLAR — SCHEMATICS (native SVG single-line + battery wiring)
   Redrawn faithfully from the validated SLD topology & computed values.
   ========================================================================== */
const { useState, useRef, useEffect, useCallback } = React;

/* ---- label inside a component box (mono, multi-line) ----------------- */
function NodeLabel({ x, y, w, h, title, lines }) {
  return (
    <foreignObject x={x} y={y} width={w} height={h}>
      <div xmlns="http://www.w3.org/1999/xhtml" className="dlabel">
        <div className="dt">{title}</div>
        {lines && lines.map((l, i) => (
          <div className="ds" key={i} dangerouslySetInnerHTML={{ __html: l }} />
        ))}
      </div>
    </foreignObject>
  );
}

function Node({ x, y, w, h, accent, title, lines }) {
  return (
    <g>
      <rect className={"node-rect " + (accent || "")} x={x} y={y} width={w} height={h} rx="5" />
      <NodeLabel x={x} y={y} w={w} h={h} title={title} lines={lines} />
    </g>
  );
}

const Wire = ({ d, cls = "" }) => <path className={"wire " + cls} d={d} />;
const Dot = ({ x, y, r = 3.5 }) => <circle className="junction" cx={x} cy={y} r={r} />;

/* ---- electrical symbols (kept to simple primitives) ------------------ */
function Fuse({ x, y, label }) {
  return (
    <g>
      <rect className="sym fill" x={x - 14} y={y - 8} width="28" height="16" rx="3" />
      <line className="sym" x1={x - 14} y1={y} x2={x + 14} y2={y} />
      {label && <text className="slabel" x={x + 20} y={y + 3.5}>{label}</text>}
    </g>
  );
}
function Disconnect({ x, y, label, side = "right" }) {
  return (
    <g>
      <Dot x={x} y={y - 11} r={3} />
      <line className="sym" x1={x} y1={y - 11} x2={x + 11} y2={y + 9} />
      <Dot x={x} y={y + 11} r={3} />
      {label && (side === "right"
        ? <text className="slabel" x={x + 16} y={y + 3.5}>{label}</text>
        : <text className="slabel" x={x - 16} y={y + 3.5} textAnchor="end">{label}</text>)}
    </g>
  );
}
function Ground({ x, y }) {
  return (
    <g>
      <line className="sym" x1={x} y1={y} x2={x} y2={y + 10} />
      <line className="sym" x1={x - 13} y1={y + 10} x2={x + 13} y2={y + 10} />
      <line className="sym" x1={x - 8} y1={y + 15} x2={x + 8} y2={y + 15} />
      <line className="sym" x1={x - 3} y1={y + 20} x2={x + 3} y2={y + 20} />
    </g>
  );
}
function PvSym({ x, y, w, h }) {
  const cols = 4, rows = 2, cw = w / cols, ch = h / rows;
  const lines = [];
  for (let i = 1; i < cols; i++) lines.push(<line key={"c" + i} className="sym" x1={x + i * cw} y1={y} x2={x + i * cw} y2={y + h} />);
  for (let j = 1; j < rows; j++) lines.push(<line key={"r" + j} className="sym" x1={x} y1={y + j * ch} x2={x + w} y2={y + j * ch} />);
  return (
    <g>
      <rect className="sym fill" x={x} y={y} width={w} height={h} rx="2" />
      {lines}
      <line className="sym" x1={x + 6} y1={y + h - 6} x2={x + w - 6} y2={y + 6} />
    </g>
  );
}
function Busbar({ x, y, w, label }) {
  return (
    <g>
      <rect x={x} y={y - 5} width={w} height="10" rx="2" fill="var(--ink)" />
      {label && <text className="slabel" x={x + w / 2} y={y - 12} textAnchor="middle">{label}</text>}
    </g>
  );
}
const WLabel = ({ x, y, children, anchor = "start" }) => (
  <text className="wlabel" x={x} y={y} textAnchor={anchor}>{children}</text>
);

/* ====================== ZOOM / PAN STAGE ============================== */
function ZoomPan({ contentW, contentH, height = 560, fig, children }) {
  const stageRef = useRef(null);
  const [t, setT] = useState({ s: 1, x: 0, y: 0 });
  const [drag, setDrag] = useState(false);
  const last = useRef(null);

  const fit = useCallback(() => {
    const el = stageRef.current; if (!el) return;
    const W = el.clientWidth, H = el.clientHeight;
    const pad = 36;
    const s = Math.min((W - pad) / contentW, (H - pad) / contentH);
    setT({ s, x: (W - contentW * s) / 2, y: (H - contentH * s) / 2 });
  }, [contentW, contentH]);

  useEffect(() => { fit(); }, [fit]);
  useEffect(() => {
    const onR = () => fit();
    window.addEventListener("resize", onR);
    return () => window.removeEventListener("resize", onR);
  }, [fit]);

  const onWheel = (e) => {
    e.preventDefault();
    const el = stageRef.current; const r = el.getBoundingClientRect();
    const mx = e.clientX - r.left, my = e.clientY - r.top;
    setT((p) => {
      const f = Math.exp(-e.deltaY * 0.0012);
      const ns = Math.min(6, Math.max(0.25, p.s * f));
      const k = ns / p.s;
      return { s: ns, x: mx - (mx - p.x) * k, y: my - (my - p.y) * k };
    });
  };
  const onDown = (e) => { setDrag(true); last.current = { x: e.clientX, y: e.clientY }; e.currentTarget.setPointerCapture(e.pointerId); };
  const onMove = (e) => {
    if (!drag) return;
    const dx = e.clientX - last.current.x, dy = e.clientY - last.current.y;
    last.current = { x: e.clientX, y: e.clientY };
    setT((p) => ({ ...p, x: p.x + dx, y: p.y + dy }));
  };
  const onUp = () => setDrag(false);
  const zoom = (f) => setT((p) => {
    const el = stageRef.current; const W = el.clientWidth, H = el.clientHeight;
    const ns = Math.min(6, Math.max(0.25, p.s * f)); const k = ns / p.s;
    return { s: ns, x: W / 2 - (W / 2 - p.x) * k, y: H / 2 - (H / 2 - p.y) * k };
  });

  return (
    <div ref={stageRef} className={"zp-stage" + (drag ? " dragging" : "")} style={{ height }}
      onWheel={onWheel} onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerLeave={onUp}>
      {fig && <div className="fig-cap"><span className="stamp plain">{fig}</span></div>}
      <svg width="100%" height="100%">
        <g transform={`translate(${t.x},${t.y}) scale(${t.s})`}>{children}</g>
      </svg>
      <div className="zp-hint"><span className="tag">DRAG TO PAN · SCROLL TO ZOOM</span></div>
      <div className="zp-controls" onPointerDown={(e) => e.stopPropagation()}>
        <div className="zp-btn" onClick={() => zoom(1.25)}>+</div>
        <div className="zp-btn" onClick={() => zoom(0.8)}>−</div>
        <div className="zp-btn wide" onClick={fit}>Fit</div>
      </div>
    </div>
  );
}

/* ========================= PHASE 1 — SLD ============================== */
function P1_SLD() {
  return (
    <ZoomPan contentW={840} contentH={720} fig="FIG. 01 · PHASE 1 SINGLE-LINE">
      <Node x={300} y={36} w={240} h={96} accent="amber" title="PV ARRAY"
        lines={["14 × 575 W &nbsp;·&nbsp; <b>2 × 7S</b>", "<b>8.05 kW</b> DC &nbsp;·&nbsp; one string / MPPT", "cold Voc <b>430.1 V</b> &nbsp;(≤ 480 V)"]} />
      <Wire d="M420 132 L420 196" />
      <WLabel x={432} y={170}><tspan className="hi">8 AWG</tspan> · 200 ft · 1.33%</WLabel>
      <Disconnect x={420} y={172} label="PV/DC DISC · 600V" />
      <Node x={300} y={216} w={240} h={150} accent="pine" title="EG4 6000XP"
        lines={["6 kW &nbsp;·&nbsp; 120/240 V split-phase", "dual MPPT &nbsp;·&nbsp; 480 VDC max PV", "140 A onboard charger"]} />
      <Wire d="M540 262 L700 262" />
      <Dot x={540} y={262} r={0.1} />
      <WLabel x={560} y={250}><tspan className="hi">8 AWG</tspan> · 35 A 2-pole</WLabel>
      <Node x={700} y={224} w={104} h={78} accent="slate" title="AC LOADS"
        lines={["120/240 V", "split-phase"]} />
      <Wire d="M300 326 L168 326 L168 352" cls="gnd" />
      <Ground x={168} y={352} />
      <text className="slabel" x={168} y={392} textAnchor="middle">GND · 690.47</text>
      <Wire d="M420 366 L420 520" />
      <Fuse x={420} y={408} label="175 A Class-T" />
      <Disconnect x={420} y={462} label="DC DISC · ≥175 A" />
      <WLabel x={300} y={500} anchor="end"><tspan className="hi">2/0 Cu</tspan> · 0.48% · </WLabel>
      <Node x={300} y={520} w={240} h={132} accent="amber" title="Docan S3 · 16 kWh"
        lines={["51.2 V · 314 Ah &nbsp;·&nbsp; EVE MB31", "16S1P &nbsp;·&nbsp; <b>JK 200 A BMS</b>", "46.4–60 V &nbsp;·&nbsp; max 140 A dis"]} />
      <Wire d="M540 586 L596 586 L596 340 L540 340" cls="comms" />
      <text className="slabel" x={604} y={466} fill="var(--slate)">BMS CAN</text>
      <text className="slabel" x={604} y={480} fill="var(--slate)">Pylontech</text>
    </ZoomPan>
  );
}

/* ===================== PHASE 1 — BATTERY WIRING ====================== */
function P1_BATT() {
  return (
    <ZoomPan contentW={760} contentH={420} height={460} fig="FIG. 02 · PHASE 1 BATTERY WIRING">
      <Node x={60} y={150} w={210} h={130} accent="pine" title="EG4 6000XP"
        lines={["48 V battery port", "BAT+ / BAT−", "140 A max charge"]} />
      <Wire d="M270 188 L360 188" />
      <WLabel x={300} y={176}>BAT+</WLabel>
      <Fuse x={392} y={188} label="175 A" />
      <Disconnect x={470} y={188} label="disc" />
      <Wire d="M420 188 L444 188" /><Wire d="M496 188 L560 188" />
      <Wire d="M560 188 L560 232" />
      <Wire d="M270 242 L560 242 L560 232" />
      <WLabel x={300} y={232}>BAT−</WLabel>
      <WLabel x={372} y={262}><tspan className="hi">2/0 Cu</tspan> · 0.48%</WLabel>
      <Node x={460} y={232} w={210} h={140} accent="amber" title="Docan S3"
        lines={["51.2 V · 16.08 kWh", "16S1P · <b>JK 200 A BMS</b>", "46.4–60 V · max 140 A dis"]} />
      <Wire d="M460 300 L300 300 L300 280" cls="comms" />
      <text className="slabel" x={372} y={314} fill="var(--slate)">JK BMS → CAN → 6000XP</text>
    </ZoomPan>
  );
}

/* ========================= PHASE 2 — SLD ============================== */
function P2_SLD() {
  return (
    <ZoomPan contentW={1000} contentH={760} fig="FIG. 03 · PHASE 2 SINGLE-LINE">
      <Node x={380} y={32} w={240} h={96} accent="amber" title="PV ARRAY"
        lines={["28 × 575 W &nbsp;·&nbsp; <b>4 × 7S</b> + 3 spare", "<b>16.1 kW</b> DC &nbsp;·&nbsp; 4 homeruns", "cold Voc <b>430.1 V</b> &nbsp;(≤ 480 V)"]} />
      <Wire d="M500 128 L500 168" />
      <Wire d="M320 168 L680 168" />
      <WLabel x={512} y={156}><tspan className="hi">8 AWG</tspan> · 200 ft · 1.33%</WLabel>
      <Wire d="M320 168 L320 214" /><Disconnect x={320} y={190} label="" />
      <Wire d="M680 168 L680 214" /><Disconnect x={680} y={190} label="" />
      <Node x={210} y={214} w={220} h={140} accent="pine" title="EG4 6000XP · #1"
        lines={["6 kW · dual MPPT", "MPPT A: 7S &nbsp; MPPT B: 7S", "master (CAN)"]} />
      <Node x={570} y={214} w={220} h={140} accent="pine" title="EG4 6000XP · #2"
        lines={["6 kW · dual MPPT", "MPPT A: 7S &nbsp; MPPT B: 7S", "slave (parallel kit)"]} />
      <Wire d="M320 354 L320 400 L500 400" />
      <Wire d="M680 354 L680 400 L500 400" />
      <Wire d="M500 400 L500 440" />
      <WLabel x={356} y={392}>35 A</WLabel><WLabel x={612} y={392} anchor="end">35 A</WLabel>
      <Node x={420} y={440} w={160} h={66} accent="slate" title="AC COMBINER"
        lines={["2 × 35 A → <b>70 A</b> · 4 AWG"]} />
      <Wire d="M500 506 L500 548" />
      <Node x={410} y={548} w={180} h={64} accent="slate" title="AC LOADS"
        lines={["120/240 V split-phase"]} />
      <Wire d="M210 284 L120 284 L120 470" />
      <Wire d="M790 284 L880 284 L880 470" />
      <Busbar x={120} y={470} w={760} label="DC BUSBAR · ≥350 A (400 A std) · bank 252 A × 125% = 315 A" />
      <Wire d="M300 470 L300 540" /><Fuse x={300} y={508} label="175 A" />
      <Node x={210} y={560} w={180} h={120} accent="amber" title="Docan S3 · #1"
        lines={["51.2 V · 16.08 kWh", "16S1P · JK 200 A", "126 A / pack"]} />
      <Wire d="M700 470 L700 540" /><Fuse x={700} y={508} label="175 A" />
      <Node x={610} y={560} w={180} h={120} accent="amber" title="Docan S3 · #2"
        lines={["51.2 V · 16.08 kWh", "16S1P · JK 200 A", "126 A / pack"]} />
      <Fuse x={120} y={430} label="" /><Fuse x={880} y={430} label="" />
      <text className="slabel" x={500} y={520} textAnchor="middle">2 packs = 25.7 kWh usable · 32 kWh nameplate</text>
      <Wire d="M390 620 L160 620 L160 354" cls="comms" />
      <Wire d="M790 620 L610 620" cls="comms" />
      <text className="slabel" x={500} y={636} textAnchor="middle" fill="var(--slate)">1 master BMS-CAN + inter-pack RS485 daisy</text>
    </ZoomPan>
  );
}

/* ===================== PHASE 2 — BATTERY WIRING ====================== */
function P2_BATT() {
  return (
    <ZoomPan contentW={820} contentH={470} height={480} fig="FIG. 04 · PHASE 2 BATTERY / BANK WIRING">
      <Busbar x={120} y={70} w={580} label="DC BUSBAR · ≥350 A (400 A std)" />
      <Wire d="M220 70 L220 36" /><Wire d="M600 70 L600 36" />
      <Node x={150} y={-44} w={150} h={80} accent="pine" title="6000XP · #1" lines={["140 A charge"]} />
      <Node x={530} y={-44} w={150} h={80} accent="pine" title="6000XP · #2" lines={["140 A charge"]} />
      <Wire d="M300 70 L300 150" /><Fuse x={300} y={104} label="175 A Class-T" /><Disconnect x={300} y={140} label="" />
      <Wire d="M520 70 L520 150" /><Fuse x={520} y={104} label="175 A Class-T" /><Disconnect x={520} y={140} label="" />
      <WLabel x={316} y={172}><tspan className="hi">2/0 Cu</tspan></WLabel>
      <WLabel x={536} y={172}><tspan className="hi">2/0 Cu</tspan></WLabel>
      <Node x={210} y={172} w={180} h={120} accent="amber" title="Docan S3 · #1"
        lines={["51.2 V · 16.08 kWh", "16S1P · JK 200 A", "126 A / pack"]} />
      <Node x={430} y={172} w={180} h={120} accent="amber" title="Docan S3 · #2"
        lines={["51.2 V · 16.08 kWh", "16S1P · JK 200 A", "126 A / pack"]} />
      <Wire d="M390 232 L430 232" cls="comms" />
      <text className="slabel" x={410} y={224} textAnchor="middle" fill="var(--slate)">RS485</text>
      <Wire d="M300 292 L300 330 L520 330 L520 292" cls="comms" />
      <text className="slabel" x={410} y={346} textAnchor="middle" fill="var(--slate)">master CAN → master inverter</text>
    </ZoomPan>
  );
}

const SCHEMATICS = {
  phase1: [
    { id: "p1-sld", label: "Single-Line (SLD)", Comp: P1_SLD },
    { id: "p1-batt", label: "Battery Wiring", Comp: P1_BATT },
  ],
  phase2: [
    { id: "p2-sld", label: "Single-Line (SLD)", Comp: P2_SLD },
    { id: "p2-batt", label: "Battery / Bank Wiring", Comp: P2_BATT },
  ],
};

Object.assign(window, { ZoomPan, SCHEMATICS, P1_SLD, P1_BATT, P2_SLD, P2_BATT });