diff --git a/src/extensions/scratch3_wedo2/index.js b/src/extensions/scratch3_wedo2/index.js index a158269e5..667071438 100644 --- a/src/extensions/scratch3_wedo2/index.js +++ b/src/extensions/scratch3_wedo2/index.js @@ -24,6 +24,12 @@ const UUID = { OUTPUT_COMMAND: '00001565-1212-efde-1523-785feabcd123' }; +/** + * A time interval to wait (in milliseconds) while a block that sends a BLE message is running. + * @type {number} + */ +const BLESendInterval = 100; + /** * Enum for WeDo2 sensor and output types. * @readonly @@ -340,6 +346,19 @@ class WeDo2 { distance: 0 }; + /** + * A flag that is true while we are busy sendng data to the BLE session. + * @type {boolean} + * @private + */ + this._sending = false; + + /** + * ID for a timeout which is used to clear the sending flag if it has been + * true for a long time. + */ + this._sendingTimeoutID = null; + /** * The Bluetooth connection session for reading/writing device data. * @type {BLESession} @@ -396,7 +415,7 @@ class WeDo2 { /** * Set the WeDo 2.0 hub's LED to a specific color. * @param {int} rgb - a 24-bit RGB color in 0xRRGGBB format. - * @return {Promise} - a promise of the set led send operation. + * @return {Promise} - a promise of the completion of the set led send operation. */ setLED (rgb) { const cmd = new Uint8Array(6); @@ -412,6 +431,7 @@ class WeDo2 { /** * Switch off the LED on the WeDo2. + * @return {Promise} - a promise of the completion of the stop led send operation. */ stopLED () { const cmd = new Uint8Array(6); @@ -422,13 +442,14 @@ class WeDo2 { cmd[4] = 0x000000; cmd[5] = 0x000000; - this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); + return this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); } /** * Play a tone from the WeDo 2.0 hub for a specific amount of time. * @param {int} tone - the pitch of the tone, in Hz. * @param {int} milliseconds - the duration of the note, in milliseconds. + * @return {Promise} - a promise of the completion of the play tone send operation. */ playTone (tone, milliseconds) { const cmd = new Uint8Array(7); @@ -440,18 +461,19 @@ class WeDo2 { cmd[5] = milliseconds; cmd[6] = milliseconds >> 8; - this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); + return this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); } /** * Stop the tone playing from the WeDo 2.0 hub, if any. + * @return {Promise} - a promise that the command sent. */ stopTone () { const cmd = new Uint8Array(2); cmd[0] = WeDo2ConnectIDs.PIEZO; // connect id cmd[1] = WeDo2Commands.STOP_TONE; // command - this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); + return this._send(UUID.OUTPUT_COMMAND, Base64Util.uint8ArrayToBase64(cmd)); } /** @@ -533,7 +555,19 @@ class WeDo2 { */ _send (uuid, message) { if (!this.getPeripheralIsConnected()) return; - return this._ble.write(UUID.IO_SERVICE, uuid, message, 'base64'); + if (this._sending) return; + + this._sending = true; + + this._sendingTimeoutID = window.setTimeout(() => { + this._sending = false; + }, 5000); + + return this._ble.write(UUID.IO_SERVICE, uuid, message, 'base64') + .then(() => { + this._sending = false; + window.clearTimeout(this._sendingTimeoutID); + }); } /** @@ -665,9 +699,10 @@ class WeDo2 { */ _stopAll () { if (!this.getPeripheralIsConnected()) return; - this.stopTone(); - this.stopAllMotors(); - // this.stopLED(); + this.stopTone() + .then(() => { + this.stopAllMotors(); + }); } } @@ -996,6 +1031,7 @@ class Scratch3WeDo2Blocks { * Turn specified motor(s) on indefinitely. * @param {object} args - the block's arguments. * @property {MotorID} MOTOR_ID - the motor(s) to activate. + * @return {Promise} - a Promise that resolves after some delay. */ motorOn (args) { this._forEachMotor(args.MOTOR_ID, motorIndex => { @@ -1004,12 +1040,19 @@ class Scratch3WeDo2Blocks { motor.setMotorOn(); } }); + + return new Promise(resolve => { + window.setTimeout(() => { + resolve(); + }, BLESendInterval); + }); } /** * Turn specified motor(s) off. * @param {object} args - the block's arguments. * @property {MotorID} MOTOR_ID - the motor(s) to deactivate. + * @return {Promise} - a Promise that resolves after some delay. */ motorOff (args) { this._forEachMotor(args.MOTOR_ID, motorIndex => { @@ -1018,6 +1061,12 @@ class Scratch3WeDo2Blocks { motor.setMotorOff(); } }); + + return new Promise(resolve => { + window.setTimeout(() => { + resolve(); + }, BLESendInterval); + }); } /** @@ -1025,6 +1074,7 @@ class Scratch3WeDo2Blocks { * @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. */ startMotorPower (args) { this._forEachMotor(args.MOTOR_ID, motorIndex => { @@ -1034,6 +1084,12 @@ class Scratch3WeDo2Blocks { motor.setMotorOn(); } }); + + return new Promise(resolve => { + window.setTimeout(() => { + resolve(); + }, BLESendInterval); + }); } /** @@ -1042,6 +1098,7 @@ class Scratch3WeDo2Blocks { * @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) { this._forEachMotor(args.MOTOR_ID, motorIndex => { @@ -1071,12 +1128,19 @@ class Scratch3WeDo2Blocks { } } }); + + return new Promise(resolve => { + window.setTimeout(() => { + resolve(); + }, BLESendInterval); + }); } /** * Set the LED's hue. * @param {object} args - the block's arguments. * @property {number} HUE - the hue to set, in the range [0,100]. + * @return {Promise} - a Promise that resolves after some delay. */ setLightHue (args) { // Convert from [0,100] to [0,360] @@ -1089,6 +1153,12 @@ class Scratch3WeDo2Blocks { const rgbDecimal = color.rgbToDecimal(rgbObject); this._device.setLED(rgbDecimal); + + return new Promise(resolve => { + window.setTimeout(() => { + resolve(); + }, BLESendInterval); + }); } /**