106 lines
2.7 KiB
JavaScript
106 lines
2.7 KiB
JavaScript
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);
|
|
});
|