const test = require('node:test'); const assert = require('node:assert/strict'); const { PIDController, CascadePIDController } = require('../src/pid/index.js'); test('pid supports freeze/unfreeze with held output', () => { const pid = new PIDController({ kp: 2, ki: 0.5, kd: 0.1, sampleTime: 100, outputMin: 0, outputMax: 100, }); const t0 = Date.now(); const first = pid.update(10, 2, t0 + 100); pid.freeze({ output: first, trackMeasurement: true }); const frozen = pid.update(10, 4, t0 + 200); assert.equal(frozen, first); pid.unfreeze(); const resumed = pid.update(10, 4, t0 + 300); assert.equal(Number.isFinite(resumed), true); }); test('pid supports dynamic tunings and gain scheduling', () => { const pid = new PIDController({ kp: 1, ki: 0, kd: 0, sampleTime: 100, outputMin: -100, outputMax: 100, gainSchedule: [ { min: Number.NEGATIVE_INFINITY, max: 5, kp: 1, ki: 0, kd: 0 }, { min: 5, max: Number.POSITIVE_INFINITY, kp: 3, ki: 0, kd: 0 }, ], }); const t0 = Date.now(); const low = pid.update(10, 9, t0 + 100, { gainInput: 4 }); const high = pid.update(10, 9, t0 + 200, { gainInput: 6 }); assert.equal(high > low, true); const tuned = pid.update(10, 9, t0 + 300, { tunings: { kp: 10, ki: 0, kd: 0 } }); assert.equal(tuned > high, true); }); test('pid applies deadband and output rate limits', () => { const pid = new PIDController({ kp: 10, ki: 0, kd: 0, deadband: 0.5, sampleTime: 100, outputMin: 0, outputMax: 100, outputRateLimitUp: 5, // units per second outputRateLimitDown: 5, // units per second }); const t0 = Date.now(); const out1 = pid.update(10, 10, t0 + 100); // inside deadband -> no action const out2 = pid.update(20, 0, t0 + 200); // strong error but limited by rate assert.equal(out1, 0); // 5 units/sec * 0.1 sec = max 0.5 rise per cycle assert.equal(out2 <= 0.5 + 1e-9, true); }); test('cascade pid computes primary and secondary outputs', () => { const cascade = new CascadePIDController({ primary: { kp: 2, ki: 0, kd: 0, sampleTime: 100, outputMin: 0, outputMax: 100, }, secondary: { kp: 1, ki: 0, kd: 0, sampleTime: 100, outputMin: 0, outputMax: 100, }, }); const t0 = Date.now(); const result = cascade.update({ setpoint: 10, primaryMeasurement: 5, secondaryMeasurement: 2, timestamp: t0 + 100, }); assert.equal(typeof result.primaryOutput, 'number'); assert.equal(typeof result.secondaryOutput, 'number'); assert.equal(result.primaryOutput > 0, true); assert.equal(result.secondaryOutput > 0, true); });