feat(components): Regulated Power Supply with per-instance current limit

Adds a new picker entry 'Regulated Power Supply' under the analog
category. Conceptually fills the gap between wokwi-battery (fixed
DC) and wokwi-signal-generator (waveform focus): user chooses
voltage + mode (dc / ac) + currentLimit, no need to think about
battery chemistry or signal amplitudes.

Properties:
  mode:         'dc' | 'ac'   (default 'dc')
  voltage:      V             (default 5)
  frequency:    Hz            (default 50, only for AC)
  currentLimit: A             (default 1)

Design notes:
  - No new Web Component. The tagName piggy-backs on
    wokwi-signal-generator so the canvas renders the familiar
    bench-instrument chrome — saves shipping a second 100+ LOC
    Web Component for an identical 2-pin shape.
  - SPICE: ideal V-source + ESR sized so a near-short reads
    I ≈ 1.5·limit. ngspice has no native foldback so the limit
    is a circuitVerifier rule, not a hard SPICE constraint.
  - circuitVerifier: extends sourceComponents regex to include
    power-supply AND honors the per-instance currentLimit
    property as the threshold. Real bench supplies behave this
    way — a 100mA-limited supply trips at 100mA, a 5A supply
    tolerates 5A before flagging. The error code is
    'source-overload' (not 'short-circuit') so the modal copy
    matches what the user just configured.

The board GND / VCC pins of Arduino / ESP32 / etc. already act
as voltage sources via BOARD_PIN_GROUPS canonicalisation (the
NetlistBuilder maps wires to the right rail). So the user's
companion request — 'board pins should already work' — is the
existing behaviour; this commit only adds the standalone bench
supply for boardless circuits or for testing with a different
voltage.
This commit is contained in:
David Montero Crespo 2026-05-17 23:55:37 -03:00
parent 3b527f3dce
commit 305170aeb9
4 changed files with 177 additions and 9 deletions

View File

@ -517,6 +517,66 @@
"category": "analog",
"description": "Single-channel optocoupler. CTR 80600% (typ. 100%). Higher drive than 4N25, commonly used for MCU-to-mains isolation."
},
{
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"10\" y=\"14\" width=\"44\" height=\"30\" fill=\"#2a3548\" rx=\"3\" stroke=\"#4a5878\"/>\n <text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"9\" font-family=\"monospace\" fill=\"#4ade80\" font-weight=\"bold\">5.00V</text>\n <text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#fbbf24\">1.00A</text>\n <circle cx=\"18\" cy=\"52\" r=\"3\" fill=\"#dc2626\"/>\n <circle cx=\"46\" cy=\"52\" r=\"3\" fill=\"#0f172a\" stroke=\"#64748b\"/>\n <text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"6\" fill=\"#9d9d9d\">PSU</text>\n </svg>",
"tags": [
"power-supply",
"regulated",
"source",
"psu",
"bench",
"lab",
"dc",
"ac",
"analog"
],
"properties": [
{
"name": "mode",
"type": "string",
"defaultValue": "dc",
"control": "select",
"options": [
"dc",
"ac"
],
"description": "DC for fixed rails, AC for sine waveform output"
},
{
"name": "voltage",
"type": "number",
"defaultValue": 5,
"control": "text",
"description": "Output voltage in V (DC mode) or peak amplitude (AC mode)"
},
{
"name": "frequency",
"type": "number",
"defaultValue": 50,
"control": "text",
"description": "Frequency in Hz (AC mode only)"
},
{
"name": "currentLimit",
"type": "number",
"defaultValue": 1,
"control": "text",
"description": "Maximum sourced current in A. The verifier warns when exceeded."
}
],
"defaultValues": {
"mode": "dc",
"voltage": 5,
"frequency": 50,
"currentLimit": 1
},
"pinCount": 2,
"id": "power-supply",
"tagName": "wokwi-signal-generator",
"name": "Regulated Power Supply",
"category": "analog",
"description": "Bench DC/AC power supply with optional current limit. DC mode for fixed rails; AC mode for sine. Verifier enforces the current limit per instance."
},
{
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n SIGNAL-GENERATOR\n </text>\n </svg>",
"tags": [

View File

@ -548,6 +548,41 @@ const MAPPERS: Record<string, Mapper> = {
};
},
// ── Regulated power supply (DC / AC, current-limit aware) ───────────────
// Properties:
// mode: 'dc' | 'ac' (default 'dc')
// voltage: V (default 5)
// frequency: Hz (default 50, only used in AC mode)
// currentLimit: A (default 1; enforced by circuitVerifier,
// NOT SPICE — ngspice has no native
// foldback limiter)
// The visual element reuses wokwi-signal-generator (no new Web Component
// needed). ESR is derived from the currentLimit so a near-short reads
// as I ≈ 1.5·limit, which the verifier then flags as source-overload.
'power-supply': (comp, netLookup) => {
const pos = netLookup('+') ?? netLookup('SIG') ?? netLookup('VCC');
const neg = netLookup('') ?? netLookup('-') ?? netLookup('GND');
if (!pos || !neg) return null;
const mode = String(comp.properties.mode ?? 'dc').toLowerCase();
const voltage = Number(comp.properties.voltage ?? 5);
const currentLimit = Math.max(0.01, Number(comp.properties.currentLimit ?? 1));
const esr = Math.max(0.01, voltage / (currentLimit * 1.5));
let source: string;
if (mode === 'ac') {
const freq = Number(comp.properties.frequency ?? 50);
source = `SIN(0 ${voltage} ${freq})`;
} else {
source = `DC ${voltage}`;
}
return {
cards: [
`V_${comp.id} ${pos} ${comp.id}_int ${source}`,
`R_${comp.id}_esr ${comp.id}_int ${neg} ${esr}`,
],
modelsUsed: new Set(),
};
},
// ── Signal generator (AC / pulse / DC) ───────────────────────────────────
// Properties:
// waveform: 'sine' | 'square' | 'dc' (default 'sine')

View File

@ -128,22 +128,36 @@ export async function verifyCircuit(
const branchCurrents = solve.branchCurrents;
// ── Rule 1: short circuit / power source overload ──────────────────────
// Every voltage source (battery / signal-generator) emits a branch current
// `i(v_<id>)`. SPICE convention: V-source's current is measured + →
// INTERNALLY, so external current draw is the absolute value.
// Every voltage source (battery / signal-generator / power-supply) emits
// a branch current `i(v_<id>)`. SPICE convention: V-source's current is
// measured + → INTERNALLY, so external current draw is the absolute
// value.
//
// power-supply components carry a per-instance `currentLimit` property
// that overrides the global short-circuit threshold — that matches what
// a real bench supply does: a 100mA-limited supply trips at 100mA, a
// 5A-limited supply tolerates up to 5A before flagging fault.
const sourceComponents = input.components.filter((c) =>
/^(battery|signal-generator)/.test(c.metadataId),
/^(battery|signal-generator|power-supply)/.test(c.metadataId),
);
for (const src of sourceComponents) {
const i = Math.abs(branchCurrents[`v_${src.id}`] ?? 0);
if (i >= config.shortCircuitAmps) {
const perInstanceLimit =
src.metadataId === 'power-supply'
? Number(src.properties?.currentLimit ?? config.shortCircuitAmps)
: config.shortCircuitAmps;
const threshold = Number.isFinite(perInstanceLimit) && perInstanceLimit > 0
? perInstanceLimit
: config.shortCircuitAmps;
if (i >= threshold) {
const isPsu = src.metadataId === 'power-supply';
errors.push({
severity: 'error',
code: 'short-circuit',
code: isPsu ? 'source-overload' : 'short-circuit',
componentId: src.id,
message: `Possible short circuit — ${src.metadataId} ${src.id} is delivering ${formatAmps(
i,
)} (threshold ${formatAmps(config.shortCircuitAmps)}). Check for 5 V tied directly to GND.`,
message: isPsu
? `Power supply ${src.id} is being asked for ${formatAmps(i)} — past its ${formatAmps(threshold)} current limit. A real bench supply would foldback or cut out. Raise the currentLimit or add more series resistance to the load.`
: `Possible short circuit — ${src.metadataId} ${src.id} is delivering ${formatAmps(i)} (threshold ${formatAmps(threshold)}). Check for power tied directly to GND.`,
metric: i,
});
}

View File

@ -1044,6 +1044,65 @@
"analog"
]
},
{
"id": "power-supply",
"tagName": "wokwi-signal-generator",
"name": "Regulated Power Supply",
"category": "analog",
"description": "Bench DC/AC power supply with optional current limit. DC mode for fixed rails (5V, 12V, etc.); AC mode for sine sources. Visually shares the signal-generator chassis so no new Web Component is needed. The current limit is enforced by the circuit verifier — if any load pulls more than the configured amperage, Run is blocked with a 'source-overload' warning, mimicking a real bench supply's foldback behavior.",
"properties": [
{
"name": "mode",
"type": "string",
"defaultValue": "dc",
"control": "select",
"options": [
"dc",
"ac"
],
"description": "DC for fixed rails, AC for sine waveform output"
},
{
"name": "voltage",
"type": "number",
"defaultValue": 5,
"control": "text",
"description": "Output voltage in V (DC mode) or peak amplitude (AC mode)"
},
{
"name": "frequency",
"type": "number",
"defaultValue": 50,
"control": "text",
"description": "Frequency in Hz (AC mode only)"
},
{
"name": "currentLimit",
"type": "number",
"defaultValue": 1,
"control": "text",
"description": "Maximum sourced current in A. The verifier warns when exceeded."
}
],
"defaultValues": {
"mode": "dc",
"voltage": 5,
"frequency": 50,
"currentLimit": 1
},
"pinCount": 2,
"tags": [
"power-supply",
"regulated",
"source",
"psu",
"bench",
"lab",
"dc",
"ac",
"analog"
]
},
{
"id": "signal-generator",
"tagName": "wokwi-signal-generator",