138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
/**
|
|
* End-to-end test for the `readAmmeter(..., timeWaveforms)` AC path.
|
|
*
|
|
* Sine source (5 Vpk, 50 Hz) drives a 1 kΩ resistor through an inline
|
|
* ammeter. Expected RMS current ≈ (5/√2) / 1000 ≈ 3.54 mA, peak ≈ 5 mA,
|
|
* DC ≈ 0. The ammeter's SPICE model injects a 0 V sense source named
|
|
* `v_<ammeterId>_sense`; `timeWaveforms.branches` reports the current
|
|
* through that source sample-by-sample.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { readAmmeter } from '../simulation/spice/probes';
|
|
import type { ComponentForSpice } from '../simulation/spice/types';
|
|
import type { Wire } from '../types/wire';
|
|
|
|
describe('readAmmeter with .tran timeWaveforms', () => {
|
|
it(
|
|
'reports RMS ≈ Vpk/(√2·R), peak ≈ Vpk/R, DC ≈ 0 for a centered sine',
|
|
{ timeout: 30_000 },
|
|
async () => {
|
|
const { solveInput } = await import('./helpers/solveInput');
|
|
|
|
const components: ComponentForSpice[] = [
|
|
{
|
|
id: 'sg1',
|
|
metadataId: 'signal-generator',
|
|
properties: { waveform: 'sine', frequency: 50, amplitude: 5, offset: 0 },
|
|
},
|
|
{ id: 'r1', metadataId: 'resistor', properties: { value: '1k' } },
|
|
{ id: 'am', metadataId: 'instr-ammeter', properties: {} },
|
|
];
|
|
// SIG → R → A+ [ammeter] A- → GND
|
|
const wires: Wire[] = [
|
|
{
|
|
id: 'w1',
|
|
start: { componentId: 'sg1', pinName: 'SIG' },
|
|
end: { componentId: 'r1', pinName: '1' },
|
|
} as Wire,
|
|
{
|
|
id: 'w2',
|
|
start: { componentId: 'r1', pinName: '2' },
|
|
end: { componentId: 'am', pinName: 'A+' },
|
|
} as Wire,
|
|
{
|
|
id: 'w3',
|
|
start: { componentId: 'am', pinName: 'A-' },
|
|
end: { componentId: 'sg1', pinName: 'GND' },
|
|
} as Wire,
|
|
];
|
|
|
|
const solve = await solveInput({
|
|
components,
|
|
wires: wires.map((w) => ({ id: w.id, start: w.start, end: w.end })),
|
|
boards: [],
|
|
analysis: { kind: 'tran', step: '1e-3', stop: '8e-2' },
|
|
});
|
|
|
|
expect(solve.analysisMode).toBe('tran');
|
|
expect(solve.timeWaveforms).toBeDefined();
|
|
|
|
const am = components.find((c) => c.id === 'am')!;
|
|
const reading = readAmmeter(am, solve, solve.timeWaveforms);
|
|
|
|
expect(reading.kind).toBe('ammeter');
|
|
expect(reading.stale).toBe(false);
|
|
expect(reading.ac).toBeDefined();
|
|
if (!reading.ac) return;
|
|
|
|
// 5 V peak across 1 kΩ → 5 mA peak, 5/√2 mA RMS. Allow ±15 %.
|
|
const idealRmsA = 5 / Math.sqrt(2) / 1000;
|
|
const idealPeakA = 5 / 1000;
|
|
expect(reading.ac.rms).toBeGreaterThan(idealRmsA * 0.85);
|
|
expect(reading.ac.rms).toBeLessThan(idealRmsA * 1.15);
|
|
expect(reading.ac.peak).toBeGreaterThan(idealPeakA * 0.9);
|
|
expect(reading.ac.peak).toBeLessThan(idealPeakA * 1.1);
|
|
expect(Math.abs(reading.ac.dc)).toBeLessThan(idealRmsA * 0.25);
|
|
|
|
expect(reading.ac.rmsDisplay).toMatch(/^RMS /);
|
|
expect(reading.ac.peakDisplay).toMatch(/^pk /);
|
|
expect(reading.ac.dcDisplay).toMatch(/^DC /);
|
|
// mA formatting (0.001 ≤ |i| < 1).
|
|
expect(reading.ac.rmsDisplay).toMatch(/mA/);
|
|
expect(reading.display).toBe(reading.ac.rmsDisplay);
|
|
},
|
|
);
|
|
|
|
it('leaves `ac` undefined for a pure DC circuit (.op path)', { timeout: 30_000 }, async () => {
|
|
const { solveInput } = await import('./helpers/solveInput');
|
|
|
|
const components: ComponentForSpice[] = [
|
|
{ id: 'r1', metadataId: 'resistor', properties: { value: '220' } },
|
|
{ id: 'am', metadataId: 'instr-ammeter', properties: {} },
|
|
];
|
|
const wires: Wire[] = [
|
|
{
|
|
id: 'w1',
|
|
start: { componentId: 'uno', pinName: '5V' },
|
|
end: { componentId: 'r1', pinName: '1' },
|
|
} as Wire,
|
|
{
|
|
id: 'w2',
|
|
start: { componentId: 'r1', pinName: '2' },
|
|
end: { componentId: 'am', pinName: 'A+' },
|
|
} as Wire,
|
|
{
|
|
id: 'w3',
|
|
start: { componentId: 'am', pinName: 'A-' },
|
|
end: { componentId: 'uno', pinName: 'GND' },
|
|
} as Wire,
|
|
];
|
|
|
|
const solve = await solveInput({
|
|
components,
|
|
wires: wires.map((w) => ({ id: w.id, start: w.start, end: w.end })),
|
|
boards: [
|
|
{
|
|
id: 'uno',
|
|
vcc: 5,
|
|
pins: {},
|
|
groundPinNames: ['GND'],
|
|
vccPinNames: ['5V'],
|
|
},
|
|
],
|
|
analysis: { kind: 'op' },
|
|
});
|
|
|
|
expect(solve.analysisMode).toBe('op');
|
|
expect(solve.timeWaveforms).toBeUndefined();
|
|
|
|
const am = components.find((c) => c.id === 'am')!;
|
|
const reading = readAmmeter(am, solve, solve.timeWaveforms);
|
|
|
|
expect(reading.ac).toBeUndefined();
|
|
// 5V / 220Ω = 22.7 mA.
|
|
expect(Math.abs(reading.value)).toBeCloseTo(22.7, 0);
|
|
expect(reading.display).toMatch(/mA/);
|
|
});
|
|
});
|