Reworked motor-system to allow the setMotorPower- and setMotorDirection-blocks to modify the motor behavior if the motor is already running.

- BoostMotor-class now has pendingPositionDestination, the rotation-equivalent of pendingTimeout, that stores a destination the motor should reach. When using setMotorPower() or setMotorDirection() while a motorOnForRotation()-block is running, a new motorOnForRotation()-command will be run for the remaining amount of degrees but with new power/direction, cancelling the old command.
- BoostMotor._status is only affected by feedback from the hub.
- setMotorPower() and setMotorDirection() no longer yields, since they just set state.

From design meeting regarding block design:
- Renamed all motors-label to ABCD.
- Added 'AB' motor label to address built-in motor pair.
- use the word direction in the setMotorDirection-block
- moved argument label in motor position reporter
- changed wording of color-sensing block.
- removed isTilted-boolean reporter
- removed changeLightHueBy-block

- fixed pingDevice-function bug.
This commit is contained in:
Kevin Andersen 2019-03-19 18:34:12 -04:00
parent 55ccc4e77a
commit 5f6c8b1efd

View file

@ -313,18 +313,32 @@ class BoostMotor {
/**
* The starting time for the pending timeout.
* @type {Object}
* @type {number}
* @private
*/
this._pendingTimeoutStartTime = null;
/**
* The delay/duration of the pending timeout.
* @type {Object}
* @type {number}
* @private
*/
this._pendingTimeoutDelay = null;
/**
* The origin position of a turn-based command.
* @type {number}
* @private
*/
this._pendingPositionOrigin = null;
/**
* The target position of a turn-based command.
* @type {number}
* @private
*/
this._pendingPositionDestination = null;
/**
* If the motor has been turned on run for a specific duration,
* this is the function that will be called once Scratch VM gets a notification from the Move Hub.
@ -392,23 +406,34 @@ class BoostMotor {
* @return {boolean} - true if this motor is currently moving, false if this motor is off or braking.
*/
get isOn () {
return this._status;
if (this._status & (BoostOutputCommandFeedback.BUSY_OR_FULL ^
BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS)) {
return true;
}
return false;
}
/**
* @return {boolean} - time, in milliseconds, of when the pending timeout began.
* @return {number} - time, in milliseconds, of when the pending timeout began.
*/
get pendingTimeoutStartTime () {
return this._pendingTimeoutStartTime;
}
/**
* @return {boolean} - delay, in milliseconds, of the pending timeout.
* @return {number} - delay, in milliseconds, of the pending timeout.
*/
get pendingTimeoutDelay () {
return this._pendingTimeoutDelay;
}
/**
* @return {number} - delay, in milliseconds, of the pending timeout.
*/
get pendingPositionDestination () {
return this._pendingPositionDestination;
}
/**
* @return {boolean} - true if this motor is currently moving, false if this motor is off or braking.
*/
@ -416,6 +441,13 @@ class BoostMotor {
return this._pendingPromiseFunction;
}
/**
* @param {function} func - function to resolve promise
*/
set pendingPromiseFunction (func) {
this._pendingPromiseFunction = func;
}
/**
* Turn this motor on indefinitely.
*/
@ -433,7 +465,6 @@ class BoostMotor {
this._parent.send(BoostBLE.characteristic, cmd);
this._status = BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS;
this._clearTimeout();
}
@ -464,14 +495,15 @@ class BoostMotor {
BoostOutputSubCommand.START_SPEED_FOR_DEGREES,
[
...numberToInt32Array(degrees),
this._power * this._direction * direction, // power in range 0-100
BoostMotorMaxPower, // max speed
this._power * this._direction * direction,
BoostMotorMaxPower,
BoostMotorEndState.BRAKE,
BoostMotorProfile.DO_NOT_USE
] // byte for using acceleration/braking profile
]
);
this._status = BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS;
this._pendingPositionOrigin = this._position;
this._pendingPositionDestination = this._position + (degrees * this._direction * direction);
this._parent.send(BoostBLE.characteristic, cmd);
}
@ -494,8 +526,6 @@ class BoostMotor {
);
this._parent.send(BoostBLE.characteristic, cmd, useLimiter);
this._status = BoostOutputCommandFeedback.IDLE;
}
/**
@ -751,7 +781,7 @@ class Boost {
this._ble.disconnect();
}
if (this._pingDevice) {
if (this._pingDeviceId) {
window.clearInterval(this._pingDeviceId);
this._pingDeviceId = null;
}
@ -911,7 +941,7 @@ class Boost {
break;
case BoostIO.MOTOREXT:
case BoostIO.MOTORINT:
this._motors[portID]._position = int32ArrayToNumber(data.slice(4, 8));
this.motor(portID).position = int32ArrayToNumber(data.slice(4, 8));
break;
case BoostIO.CURRENT:
case BoostIO.VOLTAGE:
@ -924,17 +954,20 @@ class Boost {
case BoostMessage.PORT_OUTPUT_COMMAND_FEEDBACK: {
// TODO: Handle messages that contain feedback from more than one port.
const feedback = data[4];
if (this.motor(portID)) {
this.motor(portID)._status = feedback;
}
switch (feedback) {
case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_COMPLETED ^ BoostOutputCommandFeedback.IDLE:
case BoostOutputCommandFeedback.CURRENT_COMMAND_DISCARDED ^ BoostOutputCommandFeedback.IDLE:
if (this._motors[portID] && this._motors[portID].pendingPromiseFunction) {
this._motors[portID].pendingPromiseFunction();
case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_COMPLETED ^ BoostOutputCommandFeedback.IDLE: {
const motor = this.motor(portID);
if (motor && motor.pendingPromiseFunction) {
motor.pendingPromiseFunction();
}
break;
}
case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS:
break;
default:
log.warn(`Did not find a motor on portID: ${portID}`);
log.warn(`Feedback from ${portID}: ${feedback}`);
}
break;
}
@ -1039,7 +1072,8 @@ const BoostMotorLabel = {
B: 'B',
C: 'C',
D: 'D',
ALL: 'all motors'
AB: 'AB',
ALL: 'ABCD'
};
/**
@ -1221,7 +1255,7 @@ class Scratch3BoostBlocks {
opcode: 'setMotorDirection',
text: formatMessage({
id: 'boost.setMotorDirection',
default: 'set motor [MOTOR_ID] to turn [MOTOR_DIRECTION]',
default: 'set motor [MOTOR_ID] direction [MOTOR_DIRECTION]',
description: 'set the motor\'s turn direction without turning it on'
}),
blockType: BlockType.COMMAND,
@ -1242,7 +1276,7 @@ class Scratch3BoostBlocks {
opcode: 'getMotorPosition',
text: formatMessage({
id: 'boost.getMotorPosition',
default: 'motor position [MOTOR_REPORTER_ID]',
default: 'motor [MOTOR_REPORTER_ID] position',
description: 'the position returned by the motor'
}),
blockType: BlockType.REPORTER,
@ -1258,7 +1292,7 @@ class Scratch3BoostBlocks {
opcode: 'whenColor',
text: formatMessage({
id: 'boost.whenColor',
default: 'when color [COLOR]',
default: 'when [COLOR] color seen',
description: 'check for when color'
}),
blockType: BlockType.HAT,
@ -1296,22 +1330,6 @@ class Scratch3BoostBlocks {
}
}
},
{
opcode: 'isTilted',
text: formatMessage({
id: 'boost.isTilted',
default: 'tilted [TILT_DIRECTION_ANY]?',
description: 'whether the tilt sensor is tilted'
}),
blockType: BlockType.BOOLEAN,
arguments: {
TILT_DIRECTION_ANY: {
type: ArgumentType.STRING,
menu: 'TILT_DIRECTION_ANY',
defaultValue: BoostTiltDirection.ANY
}
}
},
{
opcode: 'getTiltAngle',
text: formatMessage({
@ -1342,21 +1360,6 @@ class Scratch3BoostBlocks {
defaultValue: 50
}
}
},
{
opcode: 'changeLightHueBy',
text: formatMessage({
id: 'boost.changeLightHueBy',
default: 'change light color by [HUE]',
description: 'change the LED color by a given amount'
}),
blockType: BlockType.COMMAND,
arguments: {
HUE: {
type: ArgumentType.NUMBER,
defaultValue: 5
}
}
}
],
menus: {
@ -1364,39 +1367,47 @@ class Scratch3BoostBlocks {
{
text: formatMessage({
id: 'boost.motorId.a',
default: 'A',
description: 'label for motor A element in motor menu for LEGO Boost extension'
default: BoostMotorLabel.A,
description: `label for motor A element in motor menu for LEGO Boost extension`
}),
value: BoostMotorLabel.A
},
{
text: formatMessage({
id: 'boost.motorId.b',
default: 'B',
description: 'label for motor B element in motor menu for LEGO Boost extension'
default: BoostMotorLabel.B,
description: `label for motor B element in motor menu for LEGO Boost extension`
}),
value: BoostMotorLabel.B
},
{
text: formatMessage({
id: 'boost.motorId.c',
default: 'C',
description: 'label for motor C element in motor menu for LEGO Boost extension'
default: BoostMotorLabel.C,
description: `label for motor C element in motor menu for LEGO Boost extension`
}),
value: BoostMotorLabel.C
},
{
text: formatMessage({
id: 'boost.motorId.d',
default: 'D',
description: 'label for motor D element in motor menu for LEGO Boost extension'
default: BoostMotorLabel.D,
description: `label for motor D element in motor menu for LEGO Boost extension`
}),
value: BoostMotorLabel.D
},
{
text: formatMessage({
id: 'boost.motorId.ab',
default: BoostMotorLabel.AB,
description: `label for motor A and B element in motor menu for LEGO Boost extension`
}),
value: BoostMotorLabel.AB
},
{
text: formatMessage({
id: 'boost.motorId.all',
default: 'all motors',
default: BoostMotorLabel.ALL,
description: 'label for all motors element in motor menu for LEGO Boost extension'
}),
value: BoostMotorLabel.ALL
@ -1406,7 +1417,7 @@ class Scratch3BoostBlocks {
{
text: formatMessage({
id: 'boost.motorReporterId.a',
default: 'A',
default: BoostMotorLabel.A,
description: 'label for motor A element in motor menu for LEGO Boost extension'
}),
value: BoostMotorLabel.A
@ -1414,7 +1425,7 @@ class Scratch3BoostBlocks {
{
text: formatMessage({
id: 'boost.motorReporterId.b',
default: 'B',
default: BoostMotorLabel.B,
description: 'label for motor B element in motor menu for LEGO Boost extension'
}),
value: BoostMotorLabel.B
@ -1422,7 +1433,7 @@ class Scratch3BoostBlocks {
{
text: formatMessage({
id: 'boost.motorReporterId.c',
default: 'C',
default: BoostMotorLabel.C,
description: 'label for motor C element in motor menu for LEGO Boost extension'
}),
value: BoostMotorLabel.C
@ -1430,7 +1441,7 @@ class Scratch3BoostBlocks {
{
text: formatMessage({
id: 'boost.motorReporterId.d',
default: 'D',
default: BoostMotorLabel.D,
description: 'label for motor D element in motor menu for LEGO Boost extension'
}),
value: BoostMotorLabel.D
@ -1639,7 +1650,7 @@ class Scratch3BoostBlocks {
const motor = this._peripheral.motor(motorIndex);
if (motor) {
motor.turnOnForDegrees(degrees, sign);
motor._pendingPromiseFunction = resolve;
motor.pendingPromiseFunction = resolve;
}
});
});
@ -1694,7 +1705,6 @@ class Scratch3BoostBlocks {
* @param {object} args - the block's arguments.
* @property {MotorID} MOTOR_ID - the motor(s) to be affected.
* @property {int} POWER - the new power level for the motor(s).
* @return {Promise} - a Promise that resolves after some delay.
*/
setMotorPower (args) {
// TODO: cast args.MOTOR_ID?
@ -1702,13 +1712,15 @@ class Scratch3BoostBlocks {
const motor = this._peripheral.motor(motorIndex);
if (motor) {
motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);
if (motor.isOn) {
if (motor.pendingTimeoutDelay) {
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
} else if (motor.pendingPositionDestination) {
const p = motor.pendingPositionDestination - motor.position;
motor.turnOnForDegrees(p, Math.sign(p) * motor.direction);
}
}
}
});
return new Promise(resolve => {
window.setTimeout(() => {
resolve();
}, BoostBLE.sendInterval);
});
}
@ -1718,7 +1730,6 @@ class Scratch3BoostBlocks {
* @param {object} args - the block's arguments.
* @property {MotorID} MOTOR_ID - the motor(s) to be affected.
* @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s).
* @return {Promise} - a Promise that resolves after some delay.
*/
setMotorDirection (args) {
// TODO: cast args.MOTOR_ID?
@ -1743,16 +1754,13 @@ class Scratch3BoostBlocks {
if (motor.isOn) {
if (motor.pendingTimeoutDelay) {
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
} else if (motor.pendingPositionDestination) {
const p = motor.pendingPositionDestination - motor.position;
motor.turnOnForDegrees(p, Math.sign(p) * motor.direction);
}
}
}
});
return new Promise(resolve => {
window.setTimeout(() => {
resolve();
}, BoostBLE.sendInterval);
});
}
/**
@ -1953,6 +1961,9 @@ class Scratch3BoostBlocks {
case BoostMotorLabel.D:
motors = [BoostPort.D];
break;
case BoostMotorLabel.AB:
motors = [BoostPort.A, BoostPort.B];
break;
case BoostMotorLabel.ALL:
motors = [BoostPort.A, BoostPort.B, BoostPort.C, BoostPort.D];
break;