This commit is contained in:
legop3
2026-07-17 14:28:44 -04:00
parent d7fb15d891
commit c9842d7ba5
2 changed files with 33 additions and 12 deletions
@@ -6,10 +6,15 @@ const logger = require('../../globals/logger').child('overcurrentProtectionServi
const DEFAULT_CONFIG = Object.freeze({ const DEFAULT_CONFIG = Object.freeze({
minimumUsefulWheelIntent: 75, minimumUsefulWheelIntent: 75,
stressGrace: 0.2, // A fully stalled wheel now accumulates 0.25 stress per second, producing a
baseWheelOvercurrentRatePerSec: 0.75, // roughly four-second hard-stop window. The smaller base rate also prevents
stalledWheelAdditionalRatePerSec: 1.25, // a wheel that is still making progress from being treated like a hard jam.
wheelRecoveryRatePerSec: 1, stressGrace: 0.25,
baseWheelOvercurrentRatePerSec: 0.08,
stalledWheelAdditionalRatePerSec: 0.17,
// Clear telemetry removes stress faster than even a complete stall adds it,
// so short threshold climbs and direction-change spikes do not linger.
wheelRecoveryRatePerSec: 0.5,
brushOvercurrentRatePerSec: 1, brushOvercurrentRatePerSec: 1,
brushRecoveryRatePerSec: 0.75, brushRecoveryRatePerSec: 0.75,
clearBeforeUnlockSec: 0.75, clearBeforeUnlockSec: 0.75,
@@ -48,7 +48,7 @@ test('a short stalled-wheel spike remains inside the grace region', () => {
wheelSpeedsMmPerSecond: { left: 0 }, wheelSpeedsMmPerSecond: { left: 0 },
}), start + 100); }), start + 100);
assert.equal(snapshot.motors.leftWheel.stress, 0.2); assert.equal(snapshot.motors.leftWheel.stress, 0.025);
assert.equal(snapshot.motors.leftWheel.cap, 1); assert.equal(snapshot.motors.leftWheel.cap, 1);
assert.equal(snapshot.status, 'idle'); assert.equal(snapshot.status, 'idle');
}); });
@@ -60,13 +60,24 @@ test('persistent stalled-wheel overcurrent scales both wheels and then stops dri
driveDirect: { left: 300, right: 200 }, driveDirect: { left: 300, right: 200 },
}); });
for (let step = 0; step <= 5; step += 1) { for (let step = 0; step <= 39; step += 1) {
service.processTelemetry('rover', makeSensors({ service.processTelemetry('rover', makeSensors({
wheelOvercurrents: { leftWheel: true }, wheelOvercurrents: { leftWheel: true },
wheelSpeedsMmPerSecond: { left: 0, right: 200 }, wheelSpeedsMmPerSecond: { left: 0, right: 200 },
}), start + step * 100); }), start + step * 100);
} }
/*
Thirty-nine accumulated 100 ms intervals represent 3.9 seconds at the
maximum 0.25/s rate. The drive must still be available immediately before
the intended four-second hard-stop boundary.
*/
assert.equal(service.getPublicState('rover').drive.blocked, false);
service.processTelemetry('rover', makeSensors({
wheelOvercurrents: { leftWheel: true },
wheelSpeedsMmPerSecond: { left: 0, right: 200 },
}), start + 4000);
const snapshot = service.getPublicState('rover'); const snapshot = service.getPublicState('rover');
assert.equal(snapshot.status, 'stopped'); assert.equal(snapshot.status, 'stopped');
assert.equal(snapshot.drive.blocked, true); assert.equal(snapshot.drive.blocked, true);
@@ -85,10 +96,15 @@ test('persistent stalled-wheel overcurrent scales both wheels and then stops dri
const scaledDrive = issued.find((entry) => entry.payload.type === 'drive' const scaledDrive = issued.find((entry) => entry.payload.type === 'drive'
&& entry.payload.driveDirect.left > 0); && entry.payload.driveDirect.left > 0);
assert.ok(scaledDrive); assert.ok(scaledDrive);
assert.equal( const leftScale = scaledDrive.payload.driveDirect.left / 300;
scaledDrive.payload.driveDirect.left / 300, const rightScale = scaledDrive.payload.driveDirect.right / 200;
scaledDrive.payload.driveDirect.right / 200, /*
); Motor commands are integers, so applying one shared fractional cap can
round the two differently sized wheel commands by different sub-unit
amounts. A one-percent tolerance verifies the shared curve without
pretending integer transport preserves an exact floating-point ratio.
*/
assert.ok(Math.abs(leftScale - rightScale) < 0.01);
}); });
test('administrator commands and telemetry bypass all enforcement', () => { test('administrator commands and telemetry bypass all enforcement', () => {
@@ -121,14 +137,14 @@ test('a stopped drive stays blocked until both clear time and neutral are observ
service.protectCommand('rover', 'drive', { service.protectCommand('rover', 'drive', {
driveDirect: { left: 300, right: 300 }, driveDirect: { left: 300, right: 300 },
}); });
for (let step = 0; step <= 5; step += 1) { for (let step = 0; step <= 40; step += 1) {
service.processTelemetry('rover', makeSensors({ service.processTelemetry('rover', makeSensors({
wheelOvercurrents: { leftWheel: true }, wheelOvercurrents: { leftWheel: true },
wheelSpeedsMmPerSecond: { left: 0 }, wheelSpeedsMmPerSecond: { left: 0 },
}), start + step * 100); }), start + step * 100);
} }
for (let step = 6; step <= 14; step += 1) { for (let step = 41; step <= 61; step += 1) {
service.processTelemetry('rover', makeSensors(), start + step * 100); service.processTelemetry('rover', makeSensors(), start + step * 100);
} }
assert.equal(service.getPublicState('rover').drive.blocked, true); assert.equal(service.getPublicState('rover').drive.blocked, true);