Major change of motor state handling to increase reliability, clear responsibility of handling state, and readability of the code.

BoostMotor now has a status getter/setter that replaces isOn() and is responsible for clearing various motor state parameters.

A new BoostMotorState-enum contains the possible states a motor can be in.

Since time-based motor commands really just trigger a BoostMotor.turnOn(), it's the opcodes that are responsible for setting the motor state.
This commit is contained in:
Kevin Andersen 2019-04-11 10:39:56 -04:00
parent 66ff92433e
commit 63726044e4

View file

@ -219,6 +219,17 @@ const BoostMode = {
UNKNOWN: 0 // Anything else will use the default mode (mode 0) UNKNOWN: 0 // Anything else will use the default mode (mode 0)
}; };
/**
* Enum for Boost motor states.
* @param {number}
*/
const BoostMotorState = {
OFF: 0,
ON_FOREVER: 1,
ON_FOR_TIME: 2,
ON_FOR_ROTATION: 3
};
/** /**
* Helper function for converting a JavaScript number to an INT32-number * Helper function for converting a JavaScript number to an INT32-number
* @param {number} number - a number * @param {number} number - a number
@ -297,7 +308,7 @@ class BoostMotor {
* @type {boolean} * @type {boolean}
* @private * @private
*/ */
this._status = BoostPortFeedback.IDLE; this._status = BoostMotorState.OFF;
/** /**
* If the motor has been turned on or is actively braking for a specific duration, this is the timeout ID for * If the motor has been turned on or is actively braking for a specific duration, this is the timeout ID for
@ -394,13 +405,31 @@ class BoostMotor {
} }
/** /**
* @return {boolean} - true if this motor is currently moving, false if this motor is off or braking. * @return {BoostMotorState} - the motor's current state.
*/ */
get isOn () { get status () {
if (this._status & (BoostPortFeedback.BUSY_OR_FULL ^ BoostPortFeedback.IN_PROGRESS)) { return this._status;
return true; }
/**
* @param {BoostMotorState} value - set this motor's state.
*/
set status (value) {
switch (value) {
case BoostMotorState.OFF:
case BoostMotorState.ON_FOREVER:
this._clearRotationState();
this._clearTimeout();
break;
case BoostMotorState.ON_FOR_TIME:
this._clearRotationState();
break;
case BoostMotorState.ON_FOR_ROTATION:
this._clearRotationState();
this._clearTimeout();
break;
} }
return false; this._status = value;
} }
/** /**
@ -452,7 +481,6 @@ class BoostMotor {
MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100), MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100),
BoostMotorProfile.DO_NOT_USE BoostMotorProfile.DO_NOT_USE
]); ]);
this._status = BoostPortFeedback.BUSY_OR_FULL;
this._parent.send(BoostBLE.characteristic, cmd); this._parent.send(BoostBLE.characteristic, cmd);
@ -497,7 +525,6 @@ class BoostMotor {
] ]
); );
this._pendingPositionOrigin = this.position;
this._pendingPositionDestination = this.position + (degrees * this.direction * direction); this._pendingPositionDestination = this.position + (degrees * this.direction * direction);
this._parent.send(BoostBLE.characteristic, cmd); this._parent.send(BoostBLE.characteristic, cmd);
} }
@ -556,6 +583,19 @@ class BoostMotor {
this._pendingTimeoutStartTime = Date.now(); this._pendingTimeoutStartTime = Date.now();
this._pendingTimeoutDelay = delay; this._pendingTimeoutDelay = delay;
} }
/**
* Clear the motor states related to rotation-based commands, if any.
* Safe to call even when there is no pending timeout.
* @private
*/
_clearRotationState () {
if (this._pendingPromiseFunction) {
this._pendingPromiseFunction();
this._pendingPromiseFunction = null;
}
this._pendingPositionDestination = null;
}
} }
/** /**
@ -953,13 +993,13 @@ class Boost {
const feedback = data[4]; const feedback = data[4];
const motor = this.motor(portID); const motor = this.motor(portID);
if (motor) { if (motor) {
motor._status = feedback;
// Makes sure that commands resolve both when they actually complete and when they fail // Makes sure that commands resolve both when they actually complete and when they fail
const isBusy = feedback & BoostPortFeedback.IN_PROGRESS; const isBusy = feedback & BoostPortFeedback.IN_PROGRESS;
const commandCompleted = feedback & (BoostPortFeedback.COMPLETED ^ BoostPortFeedback.DISCARDED); const commandCompleted = feedback & (BoostPortFeedback.COMPLETED ^ BoostPortFeedback.DISCARDED);
if (!isBusy && commandCompleted && motor.pendingPromiseFunction) { if (!isBusy && commandCompleted) {
motor.pendingPromiseFunction(); if (motor.status === BoostMotorState.ON_FOR_ROTATION) {
motor.pendingPromiseFunction = null; motor.status = BoostMotorState.OFF;
}
} }
} }
break; break;
@ -1619,6 +1659,7 @@ class Scratch3BoostBlocks {
this._forEachMotor(args.MOTOR_ID, motorIndex => { this._forEachMotor(args.MOTOR_ID, motorIndex => {
const motor = this._peripheral.motor(motorIndex); const motor = this._peripheral.motor(motorIndex);
if (motor) { if (motor) {
motor.status = BoostMotorState.ON_FOR_TIME;
motor.turnOnFor(durationMS); motor.turnOnFor(durationMS);
} }
}); });
@ -1654,12 +1695,8 @@ class Scratch3BoostBlocks {
const promises = motors.map(portID => { const promises = motors.map(portID => {
const motor = this._peripheral.motor(portID); const motor = this._peripheral.motor(portID);
if (motor) { if (motor) {
if (motor.pendingPromiseFunction) {
// If there's already a promise for this motor it must be resolved to avoid hanging blocks.
motor.pendingPromiseFunction();
motor.pendingPromiseFunction = null;
}
return new Promise(resolve => { return new Promise(resolve => {
motor.status = BoostMotorState.ON_FOR_ROTATION;
motor.pendingPromiseFunction = resolve; motor.pendingPromiseFunction = resolve;
motor.turnOnForDegrees(degrees, sign); motor.turnOnForDegrees(degrees, sign);
}); });
@ -1684,6 +1721,7 @@ class Scratch3BoostBlocks {
this._forEachMotor(args.MOTOR_ID, motorIndex => { this._forEachMotor(args.MOTOR_ID, motorIndex => {
const motor = this._peripheral.motor(motorIndex); const motor = this._peripheral.motor(motorIndex);
if (motor) { if (motor) {
motor.status = BoostMotorState.ON_FOREVER;
motor.turnOn(); motor.turnOn();
} }
}); });
@ -1706,6 +1744,7 @@ class Scratch3BoostBlocks {
this._forEachMotor(args.MOTOR_ID, motorIndex => { this._forEachMotor(args.MOTOR_ID, motorIndex => {
const motor = this._peripheral.motor(motorIndex); const motor = this._peripheral.motor(motorIndex);
if (motor) { if (motor) {
motor.status = BoostMotorState.OFF;
motor.turnOff(); motor.turnOff();
} }
}); });
@ -1729,15 +1768,18 @@ class Scratch3BoostBlocks {
const motor = this._peripheral.motor(motorIndex); const motor = this._peripheral.motor(motorIndex);
if (motor) { if (motor) {
motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100); motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);
if (motor.isOn) { switch (motor.status) {
if (motor.pendingTimeoutDelay) { case BoostMotorState.ON_FOREVER:
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now()); motor.turnOn();
} else if (motor.pendingPromiseFunction) { break;
const p = Math.abs(motor.pendingPositionDestination - motor.position); case BoostMotorState.ON_FOR_TIME:
motor.turnOnForDegrees(p, Math.sign(p)); motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
} else { break;
motor.turnOn(); case BoostMotorState.ON_FOR_ROTATION: {
} const p = Math.abs(motor.pendingPositionDestination - motor.position);
motor.turnOnForDegrees(p, Math.sign(p));
break;
}
} }
} }
}); });
@ -1770,14 +1812,20 @@ class Scratch3BoostBlocks {
break; break;
} }
// keep the motor on if it's running, and update the pending timeout if needed // keep the motor on if it's running, and update the pending timeout if needed
if (motor.isOn) { if (motor) {
if (motor.pendingTimeoutDelay) { motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);
switch (motor.status) {
case BoostMotorState.ON_FOREVER:
motor.turnOn();
break;
case BoostMotorState.ON_FOR_TIME:
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now()); motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
} else if (motor.pendingPromiseFunction) { break;
case BoostMotorState.ON_FOR_ROTATION: {
const p = Math.abs(motor.pendingPositionDestination - motor.position); const p = Math.abs(motor.pendingPositionDestination - motor.position);
motor.turnOnForDegrees(p, Math.sign(p)); motor.turnOnForDegrees(p, Math.sign(p));
} else { break;
motor.turnOn(); }
} }
} }
} }