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. * The starting time for the pending timeout.
* @type {Object} * @type {number}
* @private * @private
*/ */
this._pendingTimeoutStartTime = null; this._pendingTimeoutStartTime = null;
/** /**
* The delay/duration of the pending timeout. * The delay/duration of the pending timeout.
* @type {Object} * @type {number}
* @private * @private
*/ */
this._pendingTimeoutDelay = null; 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, * 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. * 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. * @return {boolean} - true if this motor is currently moving, false if this motor is off or braking.
*/ */
get isOn () { 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 () { get pendingTimeoutStartTime () {
return this._pendingTimeoutStartTime; return this._pendingTimeoutStartTime;
} }
/** /**
* @return {boolean} - delay, in milliseconds, of the pending timeout. * @return {number} - delay, in milliseconds, of the pending timeout.
*/ */
get pendingTimeoutDelay () { get pendingTimeoutDelay () {
return this._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. * @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; return this._pendingPromiseFunction;
} }
/**
* @param {function} func - function to resolve promise
*/
set pendingPromiseFunction (func) {
this._pendingPromiseFunction = func;
}
/** /**
* Turn this motor on indefinitely. * Turn this motor on indefinitely.
*/ */
@ -433,7 +465,6 @@ class BoostMotor {
this._parent.send(BoostBLE.characteristic, cmd); this._parent.send(BoostBLE.characteristic, cmd);
this._status = BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS;
this._clearTimeout(); this._clearTimeout();
} }
@ -464,14 +495,15 @@ class BoostMotor {
BoostOutputSubCommand.START_SPEED_FOR_DEGREES, BoostOutputSubCommand.START_SPEED_FOR_DEGREES,
[ [
...numberToInt32Array(degrees), ...numberToInt32Array(degrees),
this._power * this._direction * direction, // power in range 0-100 this._power * this._direction * direction,
BoostMotorMaxPower, // max speed BoostMotorMaxPower,
BoostMotorEndState.BRAKE, BoostMotorEndState.BRAKE,
BoostMotorProfile.DO_NOT_USE 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); this._parent.send(BoostBLE.characteristic, cmd);
} }
@ -494,8 +526,6 @@ class BoostMotor {
); );
this._parent.send(BoostBLE.characteristic, cmd, useLimiter); this._parent.send(BoostBLE.characteristic, cmd, useLimiter);
this._status = BoostOutputCommandFeedback.IDLE;
} }
/** /**
@ -751,7 +781,7 @@ class Boost {
this._ble.disconnect(); this._ble.disconnect();
} }
if (this._pingDevice) { if (this._pingDeviceId) {
window.clearInterval(this._pingDeviceId); window.clearInterval(this._pingDeviceId);
this._pingDeviceId = null; this._pingDeviceId = null;
} }
@ -911,7 +941,7 @@ class Boost {
break; break;
case BoostIO.MOTOREXT: case BoostIO.MOTOREXT:
case BoostIO.MOTORINT: case BoostIO.MOTORINT:
this._motors[portID]._position = int32ArrayToNumber(data.slice(4, 8)); this.motor(portID).position = int32ArrayToNumber(data.slice(4, 8));
break; break;
case BoostIO.CURRENT: case BoostIO.CURRENT:
case BoostIO.VOLTAGE: case BoostIO.VOLTAGE:
@ -924,17 +954,20 @@ class Boost {
case BoostMessage.PORT_OUTPUT_COMMAND_FEEDBACK: { case BoostMessage.PORT_OUTPUT_COMMAND_FEEDBACK: {
// TODO: Handle messages that contain feedback from more than one port. // TODO: Handle messages that contain feedback from more than one port.
const feedback = data[4]; const feedback = data[4];
if (this.motor(portID)) {
this.motor(portID)._status = feedback;
}
switch (feedback) { switch (feedback) {
case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_COMPLETED ^ BoostOutputCommandFeedback.IDLE: case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_COMPLETED ^ BoostOutputCommandFeedback.IDLE: {
case BoostOutputCommandFeedback.CURRENT_COMMAND_DISCARDED ^ BoostOutputCommandFeedback.IDLE: const motor = this.motor(portID);
if (this._motors[portID] && this._motors[portID].pendingPromiseFunction) { if (motor && motor.pendingPromiseFunction) {
this._motors[portID].pendingPromiseFunction(); motor.pendingPromiseFunction();
} }
break; break;
}
case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS: case BoostOutputCommandFeedback.BUFFER_EMPTY_COMMAND_IN_PROGRESS:
break;
default: default:
log.warn(`Did not find a motor on portID: ${portID}`); log.warn(`Feedback from ${portID}: ${feedback}`);
} }
break; break;
} }
@ -1039,7 +1072,8 @@ const BoostMotorLabel = {
B: 'B', B: 'B',
C: 'C', C: 'C',
D: 'D', D: 'D',
ALL: 'all motors' AB: 'AB',
ALL: 'ABCD'
}; };
/** /**
@ -1221,7 +1255,7 @@ class Scratch3BoostBlocks {
opcode: 'setMotorDirection', opcode: 'setMotorDirection',
text: formatMessage({ text: formatMessage({
id: 'boost.setMotorDirection', 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' description: 'set the motor\'s turn direction without turning it on'
}), }),
blockType: BlockType.COMMAND, blockType: BlockType.COMMAND,
@ -1242,7 +1276,7 @@ class Scratch3BoostBlocks {
opcode: 'getMotorPosition', opcode: 'getMotorPosition',
text: formatMessage({ text: formatMessage({
id: 'boost.getMotorPosition', id: 'boost.getMotorPosition',
default: 'motor position [MOTOR_REPORTER_ID]', default: 'motor [MOTOR_REPORTER_ID] position',
description: 'the position returned by the motor' description: 'the position returned by the motor'
}), }),
blockType: BlockType.REPORTER, blockType: BlockType.REPORTER,
@ -1258,7 +1292,7 @@ class Scratch3BoostBlocks {
opcode: 'whenColor', opcode: 'whenColor',
text: formatMessage({ text: formatMessage({
id: 'boost.whenColor', id: 'boost.whenColor',
default: 'when color [COLOR]', default: 'when [COLOR] color seen',
description: 'check for when color' description: 'check for when color'
}), }),
blockType: BlockType.HAT, 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', opcode: 'getTiltAngle',
text: formatMessage({ text: formatMessage({
@ -1342,21 +1360,6 @@ class Scratch3BoostBlocks {
defaultValue: 50 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: { menus: {
@ -1364,39 +1367,47 @@ class Scratch3BoostBlocks {
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorId.a', id: 'boost.motorId.a',
default: 'A', default: BoostMotorLabel.A,
description: 'label for motor A element in motor menu for LEGO Boost extension' description: `label for motor A element in motor menu for LEGO Boost extension`
}), }),
value: BoostMotorLabel.A value: BoostMotorLabel.A
}, },
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorId.b', id: 'boost.motorId.b',
default: 'B', default: BoostMotorLabel.B,
description: 'label for motor B element in motor menu for LEGO Boost extension' description: `label for motor B element in motor menu for LEGO Boost extension`
}), }),
value: BoostMotorLabel.B value: BoostMotorLabel.B
}, },
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorId.c', id: 'boost.motorId.c',
default: 'C', default: BoostMotorLabel.C,
description: 'label for motor C element in motor menu for LEGO Boost extension' description: `label for motor C element in motor menu for LEGO Boost extension`
}), }),
value: BoostMotorLabel.C value: BoostMotorLabel.C
}, },
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorId.d', id: 'boost.motorId.d',
default: 'D', default: BoostMotorLabel.D,
description: 'label for motor D element in motor menu for LEGO Boost extension' description: `label for motor D element in motor menu for LEGO Boost extension`
}), }),
value: BoostMotorLabel.D 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({ text: formatMessage({
id: 'boost.motorId.all', id: 'boost.motorId.all',
default: 'all motors', default: BoostMotorLabel.ALL,
description: 'label for all motors element in motor menu for LEGO Boost extension' description: 'label for all motors element in motor menu for LEGO Boost extension'
}), }),
value: BoostMotorLabel.ALL value: BoostMotorLabel.ALL
@ -1406,7 +1417,7 @@ class Scratch3BoostBlocks {
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorReporterId.a', id: 'boost.motorReporterId.a',
default: 'A', default: BoostMotorLabel.A,
description: 'label for motor A element in motor menu for LEGO Boost extension' description: 'label for motor A element in motor menu for LEGO Boost extension'
}), }),
value: BoostMotorLabel.A value: BoostMotorLabel.A
@ -1414,7 +1425,7 @@ class Scratch3BoostBlocks {
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorReporterId.b', id: 'boost.motorReporterId.b',
default: 'B', default: BoostMotorLabel.B,
description: 'label for motor B element in motor menu for LEGO Boost extension' description: 'label for motor B element in motor menu for LEGO Boost extension'
}), }),
value: BoostMotorLabel.B value: BoostMotorLabel.B
@ -1422,7 +1433,7 @@ class Scratch3BoostBlocks {
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorReporterId.c', id: 'boost.motorReporterId.c',
default: 'C', default: BoostMotorLabel.C,
description: 'label for motor C element in motor menu for LEGO Boost extension' description: 'label for motor C element in motor menu for LEGO Boost extension'
}), }),
value: BoostMotorLabel.C value: BoostMotorLabel.C
@ -1430,7 +1441,7 @@ class Scratch3BoostBlocks {
{ {
text: formatMessage({ text: formatMessage({
id: 'boost.motorReporterId.d', id: 'boost.motorReporterId.d',
default: 'D', default: BoostMotorLabel.D,
description: 'label for motor D element in motor menu for LEGO Boost extension' description: 'label for motor D element in motor menu for LEGO Boost extension'
}), }),
value: BoostMotorLabel.D value: BoostMotorLabel.D
@ -1639,7 +1650,7 @@ class Scratch3BoostBlocks {
const motor = this._peripheral.motor(motorIndex); const motor = this._peripheral.motor(motorIndex);
if (motor) { if (motor) {
motor.turnOnForDegrees(degrees, sign); motor.turnOnForDegrees(degrees, sign);
motor._pendingPromiseFunction = resolve; motor.pendingPromiseFunction = resolve;
} }
}); });
}); });
@ -1694,7 +1705,6 @@ class Scratch3BoostBlocks {
* @param {object} args - the block's arguments. * @param {object} args - the block's arguments.
* @property {MotorID} MOTOR_ID - the motor(s) to be affected. * @property {MotorID} MOTOR_ID - the motor(s) to be affected.
* @property {int} POWER - the new power level for the motor(s). * @property {int} POWER - the new power level for the motor(s).
* @return {Promise} - a Promise that resolves after some delay.
*/ */
setMotorPower (args) { setMotorPower (args) {
// TODO: cast args.MOTOR_ID? // TODO: cast args.MOTOR_ID?
@ -1702,13 +1712,15 @@ 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) {
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. * @param {object} args - the block's arguments.
* @property {MotorID} MOTOR_ID - the motor(s) to be affected. * @property {MotorID} MOTOR_ID - the motor(s) to be affected.
* @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s). * @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s).
* @return {Promise} - a Promise that resolves after some delay.
*/ */
setMotorDirection (args) { setMotorDirection (args) {
// TODO: cast args.MOTOR_ID? // TODO: cast args.MOTOR_ID?
@ -1743,16 +1754,13 @@ class Scratch3BoostBlocks {
if (motor.isOn) { if (motor.isOn) {
if (motor.pendingTimeoutDelay) { if (motor.pendingTimeoutDelay) {
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now()); 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: case BoostMotorLabel.D:
motors = [BoostPort.D]; motors = [BoostPort.D];
break; break;
case BoostMotorLabel.AB:
motors = [BoostPort.A, BoostPort.B];
break;
case BoostMotorLabel.ALL: case BoostMotorLabel.ALL:
motors = [BoostPort.A, BoostPort.B, BoostPort.C, BoostPort.D]; motors = [BoostPort.A, BoostPort.B, BoostPort.C, BoostPort.D];
break; break;