mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
slop
This commit is contained in:
@@ -11,9 +11,10 @@ const DEFAULT_CONFIG = Object.freeze({
|
||||
encoderNoiseFloorMmPerSec: 15,
|
||||
fullStallProgressRatio: 0.1,
|
||||
movingProgressRatio: 0.6,
|
||||
movingOrUnknownRatePerSec: 0.25,
|
||||
fullStallRatePerSec: 1,
|
||||
wheelRecoveryRatePerSec: 0.75,
|
||||
movingOrUnknownRatePerSec: 0.2,
|
||||
fullStallRatePerSec: 0.6,
|
||||
wheelRecoveryDelaySec: 0.75,
|
||||
wheelRecoveryRatePerSec: 0.4,
|
||||
brushOvercurrentRatePerSec: 1,
|
||||
brushRecoveryRatePerSec: 0.75,
|
||||
clearBeforeUnlockSec: 0.75,
|
||||
@@ -44,6 +45,7 @@ function createMotorState() {
|
||||
classification: 'unknown',
|
||||
progressSamples: [],
|
||||
windowCommandSign: 0,
|
||||
clearSec: 0,
|
||||
stress: 0,
|
||||
cap: 1,
|
||||
};
|
||||
@@ -325,6 +327,7 @@ function createOvercurrentProtectionService(options = {}) {
|
||||
motor.commandedSpeed = commandNumber;
|
||||
motor.measuredSpeed = measuredValid ? measuredNumber : null;
|
||||
motor.currentMa = Number.isFinite(Number(currentMa)) ? Number(currentMa) : null;
|
||||
motor.clearSec = motor.overcurrent ? 0 : motor.clearSec + deltaSec;
|
||||
|
||||
if (!motor.overcurrent) {
|
||||
resetWheelProgressWindow(motor, commandSign);
|
||||
@@ -354,9 +357,14 @@ function createOvercurrentProtectionService(options = {}) {
|
||||
|
||||
const riseRate = config.movingOrUnknownRatePerSec
|
||||
+ (config.fullStallRatePerSec - config.movingOrUnknownRatePerSec) * motor.stallFactor;
|
||||
const recoveryAllowed = !motor.overcurrent && motor.clearSec >= config.wheelRecoveryDelaySec;
|
||||
motor.stress = clampUnit(
|
||||
motor.stress
|
||||
+ (motor.overcurrent ? riseRate * deltaSec : -config.wheelRecoveryRatePerSec * deltaSec),
|
||||
+ (motor.overcurrent
|
||||
? riseRate * deltaSec
|
||||
: recoveryAllowed
|
||||
? -config.wheelRecoveryRatePerSec * deltaSec
|
||||
: 0),
|
||||
);
|
||||
motor.cap = calculateCap(motor.stress);
|
||||
}
|
||||
@@ -524,7 +532,11 @@ function createOvercurrentProtectionService(options = {}) {
|
||||
// still inside the transient grace region. Reporting it separately keeps
|
||||
// HUD visibility immediate without falsely claiming output is being scaled.
|
||||
if (anyOvercurrent) return 'overcurrent';
|
||||
if (anyLimited) return 'recovering';
|
||||
// Stress below the command-limiting grace threshold still represents a
|
||||
// recent hardware event. Keeping recovery visible until it reaches zero
|
||||
// prevents the HUD from vanishing the instant the raw flag clears.
|
||||
const anyStress = MOTOR_KEYS.some((key) => state.motors[key].stress > 0);
|
||||
if (anyStress) return 'recovering';
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ test('a short stalled-wheel spike remains inside the grace region', () => {
|
||||
wheelSpeedsMmPerSecond: { left: 0 },
|
||||
}), start + 100);
|
||||
|
||||
assert.equal(snapshot.motors.leftWheel.stress, 0.025);
|
||||
// Stress is accumulated with floating-point time arithmetic, so compare
|
||||
// within a tiny tolerance instead of depending on an exact binary decimal.
|
||||
assert.ok(Math.abs(snapshot.motors.leftWheel.stress - 0.02) < 1e-9);
|
||||
assert.equal(snapshot.motors.leftWheel.cap, 1);
|
||||
// The hardware event is visible immediately even though the grace region
|
||||
// correctly leaves the command cap at full output.
|
||||
@@ -62,7 +64,7 @@ test('persistent stalled-wheel overcurrent scales both wheels and then stops dri
|
||||
driveDirect: { left: 300, right: 200 },
|
||||
});
|
||||
|
||||
for (let step = 0; step <= 12; step += 1) {
|
||||
for (let step = 0; step <= 18; step += 1) {
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0, right: 200 },
|
||||
@@ -70,15 +72,15 @@ test('persistent stalled-wheel overcurrent scales both wheels and then stops dri
|
||||
}
|
||||
|
||||
/*
|
||||
The first 400 ms establish that the wheel is making no net progress. Once
|
||||
classified, the faster stalled rate should approach—but not yet cross—the
|
||||
hard-stop boundary at 1.2 seconds.
|
||||
The first 400 ms establish that the wheel is making no net progress. The
|
||||
tuned stalled rate should then approach—but not yet cross—the hard-stop
|
||||
boundary at 1.8 seconds.
|
||||
*/
|
||||
assert.equal(service.getPublicState('rover').drive.blocked, false);
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0, right: 200 },
|
||||
}), start + 1300);
|
||||
}), start + 1900);
|
||||
|
||||
const snapshot = service.getPublicState('rover');
|
||||
assert.equal(snapshot.status, 'stopped');
|
||||
@@ -140,7 +142,7 @@ test('encoder wobble around zero is classified as a full stall', () => {
|
||||
driveDirect: { left: 300, right: 300 },
|
||||
});
|
||||
|
||||
for (let step = 0; step <= 13; step += 1) {
|
||||
for (let step = 0; step <= 19; step += 1) {
|
||||
const wobbleSpeed = step % 2 === 0 ? 10 : -9;
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
@@ -222,7 +224,7 @@ test('wheel comparison follows scaled output and resets after reversal', () => {
|
||||
driveDirect: { left: 300, right: 300 },
|
||||
});
|
||||
|
||||
for (let step = 0; step <= 6; step += 1) {
|
||||
for (let step = 0; step <= 8; step += 1) {
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0 },
|
||||
@@ -237,7 +239,7 @@ test('wheel comparison follows scaled output and resets after reversal', () => {
|
||||
const reversed = service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: -250 },
|
||||
}), start + 700);
|
||||
}), start + 900);
|
||||
assert.equal(reversed.motors.leftWheel.classification, 'unknown');
|
||||
assert.equal(reversed.motors.leftWheel.progressRatio, null);
|
||||
});
|
||||
@@ -248,14 +250,14 @@ test('a stopped drive stays blocked until both clear time and neutral are observ
|
||||
service.protectCommand('rover', 'drive', {
|
||||
driveDirect: { left: 300, right: 300 },
|
||||
});
|
||||
for (let step = 0; step <= 13; step += 1) {
|
||||
for (let step = 0; step <= 19; step += 1) {
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0 },
|
||||
}), start + step * 100);
|
||||
}
|
||||
|
||||
for (let step = 14; step <= 28; step += 1) {
|
||||
for (let step = 20; step <= 52; step += 1) {
|
||||
service.processTelemetry('rover', makeSensors(), start + step * 100);
|
||||
}
|
||||
assert.equal(service.getPublicState('rover').drive.blocked, true);
|
||||
@@ -276,6 +278,34 @@ test('a stopped drive stays blocked until both clear time and neutral are observ
|
||||
assert.deepEqual(resumed.driveDirect, { left: 300, right: 300 });
|
||||
});
|
||||
|
||||
test('cleared wheel stress remains visible through the hold and drains to idle', () => {
|
||||
const { service } = createHarness();
|
||||
const start = Date.now();
|
||||
service.protectCommand('rover', 'drive', {
|
||||
driveDirect: { left: 300, right: 300 },
|
||||
});
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0 },
|
||||
}), start);
|
||||
service.processTelemetry('rover', makeSensors({
|
||||
wheelOvercurrents: { leftWheel: true },
|
||||
wheelSpeedsMmPerSecond: { left: 0 },
|
||||
}), start + 100);
|
||||
|
||||
const justCleared = service.processTelemetry('rover', makeSensors(), start + 200);
|
||||
assert.equal(justCleared.status, 'recovering');
|
||||
assert.ok(justCleared.motors.leftWheel.stress > 0);
|
||||
|
||||
const held = service.processTelemetry('rover', makeSensors(), start + 800);
|
||||
assert.equal(held.status, 'recovering');
|
||||
assert.ok(held.motors.leftWheel.stress > 0);
|
||||
|
||||
const recovered = service.processTelemetry('rover', makeSensors(), start + 1000);
|
||||
assert.equal(recovered.status, 'idle');
|
||||
assert.equal(recovered.motors.leftWheel.stress, 0);
|
||||
});
|
||||
|
||||
test('brush stress limits only the brush that reports overcurrent', () => {
|
||||
const { service } = createHarness();
|
||||
const start = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user