mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-08 14:01:58 -05:00
Refactor for hardware extensions (#1555)
* Beginning refactor: renaming 'device' to 'peripheral', shortening function names, reordering functions, etc. * Continuing refactoring: renaming some functions to be more verbose in the runtime, adding JSDocs, etc. * Changing 'device' to 'peripheral', etc. * Changing 'session' to 'socket'. * Fixing EV3 menus and menu arg validation, reordering functions, etc. * Add _send, add some references to documentation, etc. * Factored out _outputCommand and _inputCommand, renamed some enums, etc. * Fixed _outputCommand, some other minor cleanup. * Make _outputCommand and _inputCommand public. * Added TODO. * Renamed BLE UUID enums to be clearer. * Change WeDo2 in comments to WeDo 2.0, etc. * Changed some WeDo2Motor command names, cleaned up some JSDocs. * Beginning a major EV3 refactor. * WeDo2 formatting and comment changes. * Motor refactoring in EV3: motorTurnClockwise and motorTurnCounterClockwise initial working state. * Add reminders to possibly cast motor menu args in WeDo2. * Continue to move motor commands in EV3 to EV3Motor class, don't create new EV3Motor on every poll cycle, etc. * Factoring EV3 polling value commands, etc. * Fixing EV3 motor power, position and button pressed, and some commenting, etc. * Move EV3 motor position parsing to EV3Motor class, move directCommand and directCompoundCommand functions, some commenting, etc. * Changed WeDo2 motor label enum name. * Removed some EV3 motor functions that aren't needed, changed menu label enum names, moved some opcodes up to enums. * Fixing comments and documentation. * Some commenting. * Adding further documentation and references to PDFs, changed reply check to be safer, etc. * Some comment changes. * Moving some functions around in EV3 and WeDo2 to match. * Commenting, etc. * Some renaming of session, etc. * Fix stopAllMotors in EV3. * Fixing clearing of motors in EV3. * Some comment changes. * Change runtime .extensions/registerExtension to .peripheralExtensions/registerPeripheralExtension. * Renaming outputCommand/inputCommand to generateOutputCommand/generateInputCommand, etc. * Moved motorCommandIDs to EV3Motor class, renamed directCommand to generateCommand, etc. * Adding a reminder to rename something. * JSDoc fix in EV3Motor class. * Fixing microbit function name. * Adding a todo item. * Changing Ev3 menu formats to be backwards compatible, moving a BLE function up. * Fixing EV3 ports again, and button pressed returning a boolean. * Fixing menu value to be a string in EV3.
This commit is contained in:
parent
b41423bdfa
commit
ec432e3b2f
9 changed files with 1476 additions and 1234 deletions
|
@ -264,7 +264,10 @@ class Runtime extends EventEmitter {
|
|||
video: new Video(this)
|
||||
};
|
||||
|
||||
this.extensionDevices = {};
|
||||
/**
|
||||
* A list of extensions, used to manage hardware connection.
|
||||
*/
|
||||
this.peripheralExtensions = {};
|
||||
|
||||
/**
|
||||
* A runtime profiler that records timed events for later playback to
|
||||
|
@ -928,32 +931,56 @@ class Runtime extends EventEmitter {
|
|||
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
||||
}
|
||||
|
||||
registerExtensionDevice (extensionId, device) {
|
||||
this.extensionDevices[extensionId] = device;
|
||||
/**
|
||||
* Register an extension that communications with a hardware peripheral by id,
|
||||
* to have access to it and its peripheral functions in the future.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
* @param {object} extension - the extension to register.
|
||||
*/
|
||||
registerPeripheralExtension (extensionId, extension) {
|
||||
this.peripheralExtensions[extensionId] = extension;
|
||||
}
|
||||
|
||||
startDeviceScan (extensionId) {
|
||||
if (this.extensionDevices[extensionId]) {
|
||||
this.extensionDevices[extensionId].startDeviceScan();
|
||||
/**
|
||||
* Tell the specified extension to scan for a peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
*/
|
||||
scanForPeripheral (extensionId) {
|
||||
if (this.peripheralExtensions[extensionId]) {
|
||||
this.peripheralExtensions[extensionId].scan();
|
||||
}
|
||||
}
|
||||
|
||||
connectToPeripheral (extensionId, peripheralId) {
|
||||
if (this.extensionDevices[extensionId]) {
|
||||
this.extensionDevices[extensionId].connectDevice(peripheralId);
|
||||
/**
|
||||
* Connect to the extension's specified peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
* @param {number} peripheralId - the id of the peripheral.
|
||||
*/
|
||||
connectPeripheral (extensionId, peripheralId) {
|
||||
if (this.peripheralExtensions[extensionId]) {
|
||||
this.peripheralExtensions[extensionId].connect(peripheralId);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectExtensionSession (extensionId) {
|
||||
if (this.extensionDevices[extensionId]) {
|
||||
this.extensionDevices[extensionId].disconnectSession();
|
||||
/**
|
||||
* Disconnect from the extension's connected peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
*/
|
||||
disconnectPeripheral (extensionId) {
|
||||
if (this.peripheralExtensions[extensionId]) {
|
||||
this.peripheralExtensions[extensionId].disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extension has a currently connected peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
* @return {boolean} - whether the extension has a connected peripheral.
|
||||
*/
|
||||
getPeripheralIsConnected (extensionId) {
|
||||
let isConnected = false;
|
||||
if (this.extensionDevices[extensionId]) {
|
||||
isConnected = this.extensionDevices[extensionId].getPeripheralIsConnected();
|
||||
if (this.peripheralExtensions[extensionId]) {
|
||||
isConnected = this.peripheralExtensions[extensionId].isConnected();
|
||||
}
|
||||
return isConnected;
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,7 +3,7 @@ const BlockType = require('../../extension-support/block-type');
|
|||
const log = require('../../util/log');
|
||||
const cast = require('../../util/cast');
|
||||
const formatMessage = require('format-message');
|
||||
const BLESession = require('../../io/bleSession');
|
||||
const BLE = require('../../io/ble');
|
||||
const Base64Util = require('../../util/base64-util');
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,8 @@ const BLECommand = {
|
|||
CMD_DISPLAY_LED: 0x82
|
||||
};
|
||||
|
||||
const BLETimeout = 4500; // TODO: might need tweaking based on how long the device takes to start sending data
|
||||
// TODO: Needs comment
|
||||
const BLETimeout = 4500; // TODO: might need tweaking based on how long the peripheral takes to start sending data
|
||||
|
||||
/**
|
||||
* A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
|
||||
|
@ -46,7 +47,7 @@ const BLEUUID = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Manage communication with a MicroBit device over a Scrath Link client socket.
|
||||
* Manage communication with a MicroBit peripheral over a Scrath Link client socket.
|
||||
*/
|
||||
class MicroBit {
|
||||
|
||||
|
@ -65,12 +66,12 @@ class MicroBit {
|
|||
this._runtime = runtime;
|
||||
|
||||
/**
|
||||
* The BluetoothLowEnergy connection session for reading/writing device data.
|
||||
* @type {BLESession}
|
||||
* The BluetoothLowEnergy connection socket for reading/writing peripheral data.
|
||||
* @type {BLE}
|
||||
* @private
|
||||
*/
|
||||
this._ble = null;
|
||||
this._runtime.registerExtensionDevice(extensionId, this);
|
||||
this._runtime.registerPeripheralExtension(extensionId, this);
|
||||
|
||||
/**
|
||||
* The most recently received value for each sensor.
|
||||
|
@ -116,7 +117,7 @@ class MicroBit {
|
|||
this._timeoutID = null;
|
||||
|
||||
/**
|
||||
* A flag that is true while we are busy sending data to the BLE session.
|
||||
* A flag that is true while we are busy sending data to the BLE socket.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
|
@ -127,60 +128,30 @@ class MicroBit {
|
|||
* true for a long time.
|
||||
*/
|
||||
this._busyTimeoutID = null;
|
||||
}
|
||||
|
||||
// TODO: keep here?
|
||||
/**
|
||||
* Called by the runtime when user wants to scan for a device.
|
||||
*/
|
||||
startDeviceScan () {
|
||||
this._ble = new BLESession(this._runtime, {
|
||||
filters: [
|
||||
{services: [BLEUUID.service]}
|
||||
]
|
||||
}, this._onSessionConnect.bind(this));
|
||||
}
|
||||
|
||||
// TODO: keep here?
|
||||
/**
|
||||
* Called by the runtime when user wants to connect to a certain device.
|
||||
* @param {number} id - the id of the device to connect to.
|
||||
*/
|
||||
connectDevice (id) {
|
||||
this._ble.connectDevice(id);
|
||||
}
|
||||
|
||||
disconnectSession () {
|
||||
window.clearInterval(this._timeoutID);
|
||||
this._ble.disconnectSession();
|
||||
}
|
||||
|
||||
getPeripheralIsConnected () {
|
||||
let connected = false;
|
||||
if (this._ble) {
|
||||
connected = this._ble.getPeripheralIsConnected();
|
||||
}
|
||||
return connected;
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
this._onConnect = this._onConnect.bind(this);
|
||||
this._onMessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text - the text to display.
|
||||
* @return {Promise} - a Promise that resolves when writing to device.
|
||||
* @return {Promise} - a Promise that resolves when writing to peripheral.
|
||||
*/
|
||||
displayText (text) {
|
||||
const output = new Uint8Array(text.length);
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
output[i] = text.charCodeAt(i);
|
||||
}
|
||||
return this._writeSessionData(BLECommand.CMD_DISPLAY_TEXT, output);
|
||||
return this.send(BLECommand.CMD_DISPLAY_TEXT, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} matrix - the matrix to display.
|
||||
* @return {Promise} - a Promise that resolves when writing to device.
|
||||
* @return {Promise} - a Promise that resolves when writing to peripheral.
|
||||
*/
|
||||
displayMatrix (matrix) {
|
||||
return this._writeSessionData(BLECommand.CMD_DISPLAY_LED, matrix);
|
||||
return this.send(BLECommand.CMD_DISPLAY_LED, matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,20 +197,87 @@ class MicroBit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {number} pin - the pin to check touch state.
|
||||
* @return {number} - the latest value received for the touch pin states.
|
||||
* Called by the runtime when user wants to scan for a peripheral.
|
||||
*/
|
||||
_checkPinState (pin) {
|
||||
return this._sensors.touchPins[pin];
|
||||
scan () {
|
||||
this._ble = new BLE(this._runtime, {
|
||||
filters: [
|
||||
{services: [BLEUUID.service]}
|
||||
]
|
||||
}, this._onConnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts reading data from device after BLE has connected to it.
|
||||
* Called by the runtime when user wants to connect to a certain peripheral.
|
||||
* @param {number} id - the id of the peripheral to connect to.
|
||||
*/
|
||||
_onSessionConnect () {
|
||||
const callback = this._processSessionData.bind(this);
|
||||
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, callback);
|
||||
this._timeoutID = window.setInterval(this.disconnectSession.bind(this), BLETimeout);
|
||||
connect (id) {
|
||||
this._ble.connectPeripheral(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the micro:bit.
|
||||
*/
|
||||
disconnect () {
|
||||
window.clearInterval(this._timeoutID);
|
||||
this._ble.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if connected to the micro:bit.
|
||||
* @return {boolean} - whether the micro:bit is connected.
|
||||
*/
|
||||
isConnected () {
|
||||
let connected = false;
|
||||
if (this._ble) {
|
||||
connected = this._ble.isConnected();
|
||||
}
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the peripheral BLE socket.
|
||||
* @param {number} command - the BLE command hex.
|
||||
* @param {Uint8Array} message - the message to write
|
||||
*/
|
||||
send (command, message) {
|
||||
if (!this.isConnected()) return;
|
||||
if (this._busy) return;
|
||||
|
||||
// Set a busy flag so that while we are sending a message and waiting for
|
||||
// the response, additional messages are ignored.
|
||||
this._busy = true;
|
||||
|
||||
// Set a timeout after which to reset the busy flag. This is used in case
|
||||
// a BLE message was sent for which we never received a response, because
|
||||
// e.g. the peripheral was turned off after the message was sent. We reset
|
||||
// the busy flag after a while so that it is possible to try again later.
|
||||
this._busyTimeoutID = window.setTimeout(() => {
|
||||
this._busy = false;
|
||||
}, 5000);
|
||||
|
||||
const output = new Uint8Array(message.length + 1);
|
||||
output[0] = command; // attach command to beginning of message
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
output[i + 1] = message[i];
|
||||
}
|
||||
const data = Base64Util.uint8ArrayToBase64(output);
|
||||
|
||||
this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64', true).then(
|
||||
() => {
|
||||
this._busy = false;
|
||||
window.clearTimeout(this._busyTimeoutID);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts reading data from peripheral after BLE has connected to it.
|
||||
* @private
|
||||
*/
|
||||
_onConnect () {
|
||||
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage);
|
||||
this._timeoutID = window.setInterval(this.disconnect, BLETimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,7 +285,7 @@ class MicroBit {
|
|||
* @param {object} base64 - the incoming BLE data.
|
||||
* @private
|
||||
*/
|
||||
_processSessionData (base64) {
|
||||
_onMessage (base64) {
|
||||
// parse data
|
||||
const data = Base64Util.base64ToUint8Array(base64);
|
||||
|
||||
|
@ -267,44 +305,16 @@ class MicroBit {
|
|||
|
||||
// cancel disconnect timeout and start a new one
|
||||
window.clearInterval(this._timeoutID);
|
||||
this._timeoutID = window.setInterval(this.disconnectSession.bind(this), BLETimeout);
|
||||
this._timeoutID = window.setInterval(this.disconnect, BLETimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the device BLE session.
|
||||
* @param {number} command - the BLE command hex.
|
||||
* @param {Uint8Array} message - the message to write
|
||||
* @param {number} pin - the pin to check touch state.
|
||||
* @return {number} - the latest value received for the touch pin states.
|
||||
* @private
|
||||
*/
|
||||
_writeSessionData (command, message) {
|
||||
if (!this.getPeripheralIsConnected()) return;
|
||||
if (this._busy) return;
|
||||
|
||||
// Set a busy flag so that while we are sending a message and waiting for
|
||||
// the response, additional messages are ignored.
|
||||
this._busy = true;
|
||||
|
||||
// Set a timeout after which to reset the busy flag. This is used in case
|
||||
// a BLE message was sent for which we never received a response, because
|
||||
// e.g. the device was turned off after the message was sent. We reset
|
||||
// the busy flag after a while so that it is possible to try again later.
|
||||
this._busyTimeoutID = window.setTimeout(() => {
|
||||
this._busy = false;
|
||||
}, 5000);
|
||||
|
||||
const output = new Uint8Array(message.length + 1);
|
||||
output[0] = command; // attach command to beginning of message
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
output[i + 1] = message[i];
|
||||
}
|
||||
const data = Base64Util.uint8ArrayToBase64(output);
|
||||
|
||||
this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64', true).then(
|
||||
() => {
|
||||
this._busy = false;
|
||||
window.clearTimeout(this._busyTimeoutID);
|
||||
}
|
||||
);
|
||||
_checkPinState (pin) {
|
||||
return this._sensors.touchPins[pin];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,7 +364,7 @@ const PinState = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Scratch 3.0 blocks to interact with a MicroBit device.
|
||||
* Scratch 3.0 blocks to interact with a MicroBit peripheral.
|
||||
*/
|
||||
class Scratch3MicroBitBlocks {
|
||||
|
||||
|
@ -527,8 +537,8 @@ class Scratch3MicroBitBlocks {
|
|||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
// Create a new MicroBit device instance
|
||||
this._device = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);
|
||||
// Create a new MicroBit peripheral instance
|
||||
this._peripheral = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -725,11 +735,11 @@ class Scratch3MicroBitBlocks {
|
|||
*/
|
||||
whenButtonPressed (args) {
|
||||
if (args.BTN === 'any') {
|
||||
return this._device.buttonA | this._device.buttonB;
|
||||
return this._peripheral.buttonA | this._peripheral.buttonB;
|
||||
} else if (args.BTN === 'A') {
|
||||
return this._device.buttonA;
|
||||
return this._peripheral.buttonA;
|
||||
} else if (args.BTN === 'B') {
|
||||
return this._device.buttonB;
|
||||
return this._peripheral.buttonB;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -741,11 +751,11 @@ class Scratch3MicroBitBlocks {
|
|||
*/
|
||||
isButtonPressed (args) {
|
||||
if (args.BTN === 'any') {
|
||||
return this._device.buttonA | this._device.buttonB;
|
||||
return this._peripheral.buttonA | this._peripheral.buttonB;
|
||||
} else if (args.BTN === 'A') {
|
||||
return this._device.buttonA;
|
||||
return this._peripheral.buttonA;
|
||||
} else if (args.BTN === 'B') {
|
||||
return this._device.buttonB;
|
||||
return this._peripheral.buttonB;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -758,11 +768,11 @@ class Scratch3MicroBitBlocks {
|
|||
whenGesture (args) {
|
||||
const gesture = cast.toString(args.GESTURE);
|
||||
if (gesture === 'moved') {
|
||||
return (this._device.gestureState >> 2) & 1;
|
||||
return (this._peripheral.gestureState >> 2) & 1;
|
||||
} else if (gesture === 'shaken') {
|
||||
return this._device.gestureState & 1;
|
||||
return this._peripheral.gestureState & 1;
|
||||
} else if (gesture === 'jumped') {
|
||||
return (this._device.gestureState >> 1) & 1;
|
||||
return (this._peripheral.gestureState >> 1) & 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -780,12 +790,12 @@ class Scratch3MicroBitBlocks {
|
|||
};
|
||||
const hex = symbol.split('').reduce(reducer, 0);
|
||||
if (hex !== null) {
|
||||
this._device.ledMatrixState[0] = hex & 0x1F;
|
||||
this._device.ledMatrixState[1] = (hex >> 5) & 0x1F;
|
||||
this._device.ledMatrixState[2] = (hex >> 10) & 0x1F;
|
||||
this._device.ledMatrixState[3] = (hex >> 15) & 0x1F;
|
||||
this._device.ledMatrixState[4] = (hex >> 20) & 0x1F;
|
||||
this._device.displayMatrix(this._device.ledMatrixState);
|
||||
this._peripheral.ledMatrixState[0] = hex & 0x1F;
|
||||
this._peripheral.ledMatrixState[1] = (hex >> 5) & 0x1F;
|
||||
this._peripheral.ledMatrixState[2] = (hex >> 10) & 0x1F;
|
||||
this._peripheral.ledMatrixState[3] = (hex >> 15) & 0x1F;
|
||||
this._peripheral.ledMatrixState[4] = (hex >> 20) & 0x1F;
|
||||
this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
|
@ -803,7 +813,7 @@ class Scratch3MicroBitBlocks {
|
|||
*/
|
||||
displayText (args) {
|
||||
const text = String(args.TEXT).substring(0, 19);
|
||||
if (text.length > 0) this._device.displayText(text);
|
||||
if (text.length > 0) this._peripheral.displayText(text);
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
|
@ -818,9 +828,9 @@ class Scratch3MicroBitBlocks {
|
|||
*/
|
||||
displayClear () {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this._device.ledMatrixState[i] = 0;
|
||||
this._peripheral.ledMatrixState[i] = 0;
|
||||
}
|
||||
this._device.displayMatrix(this._device.ledMatrixState);
|
||||
this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
|
@ -868,8 +878,8 @@ class Scratch3MicroBitBlocks {
|
|||
_isTilted (direction) {
|
||||
switch (direction) {
|
||||
case TiltDirection.ANY:
|
||||
return (Math.abs(this._device.tiltX / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD) ||
|
||||
(Math.abs(this._device.tiltY / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD);
|
||||
return (Math.abs(this._peripheral.tiltX / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD) ||
|
||||
(Math.abs(this._peripheral.tiltY / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD);
|
||||
default:
|
||||
return this._getTiltAngle(direction) >= Scratch3MicroBitBlocks.TILT_THRESHOLD;
|
||||
}
|
||||
|
@ -884,13 +894,13 @@ class Scratch3MicroBitBlocks {
|
|||
_getTiltAngle (direction) {
|
||||
switch (direction) {
|
||||
case TiltDirection.FRONT:
|
||||
return Math.round(this._device.tiltY / -10);
|
||||
return Math.round(this._peripheral.tiltY / -10);
|
||||
case TiltDirection.BACK:
|
||||
return Math.round(this._device.tiltY / 10);
|
||||
return Math.round(this._peripheral.tiltY / 10);
|
||||
case TiltDirection.LEFT:
|
||||
return Math.round(this._device.tiltX / -10);
|
||||
return Math.round(this._peripheral.tiltX / -10);
|
||||
case TiltDirection.RIGHT:
|
||||
return Math.round(this._device.tiltX / 10);
|
||||
return Math.round(this._peripheral.tiltX / 10);
|
||||
default:
|
||||
log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);
|
||||
}
|
||||
|
@ -905,7 +915,7 @@ class Scratch3MicroBitBlocks {
|
|||
const pin = parseInt(args.PIN, 10);
|
||||
if (isNaN(pin)) return;
|
||||
if (pin < 0 || pin > 2) return false;
|
||||
return this._device._checkPinState(pin);
|
||||
return this._peripheral._checkPinState(pin);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,22 @@
|
|||
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
||||
// const log = require('../util/log');
|
||||
const ScratchLinkWebSocket = 'wss://device-manager.scratch.mit.edu:20110/scratch/ble';
|
||||
// const log = require('../util/log');
|
||||
|
||||
class BLESession extends JSONRPCWebSocket {
|
||||
class BLE extends JSONRPCWebSocket {
|
||||
|
||||
/**
|
||||
* A BLE device session object. It handles connecting, over web sockets, to
|
||||
* BLE devices, and reading and writing data to them.
|
||||
* A BLE peripheral socket object. It handles connecting, over web sockets, to
|
||||
* BLE peripherals, and reading and writing data to them.
|
||||
* @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
|
||||
* @param {object} deviceOptions - the list of options for device discovery.
|
||||
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
||||
* @param {object} connectCallback - a callback for connection.
|
||||
*/
|
||||
constructor (runtime, deviceOptions, connectCallback) {
|
||||
constructor (runtime, peripheralOptions, connectCallback) {
|
||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
||||
super(ws);
|
||||
|
||||
this._ws = ws;
|
||||
this._ws.onopen = this.requestDevice.bind(this); // only call request device after socket opens
|
||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
||||
this._ws.onerror = this._sendError.bind(this, 'ws onerror');
|
||||
this._ws.onclose = this._sendError.bind(this, 'ws onclose');
|
||||
|
||||
|
@ -24,21 +24,23 @@ class BLESession extends JSONRPCWebSocket {
|
|||
this._connectCallback = connectCallback;
|
||||
this._connected = false;
|
||||
this._characteristicDidChangeCallback = null;
|
||||
this._deviceOptions = deviceOptions;
|
||||
this._peripheralOptions = peripheralOptions;
|
||||
this._discoverTimeoutID = null;
|
||||
this._runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request connection to the device.
|
||||
* Request connection to the peripheral.
|
||||
* If the web socket is not yet open, request when the socket promise resolves.
|
||||
*/
|
||||
requestDevice () {
|
||||
requestPeripheral () {
|
||||
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
||||
this._availablePeripherals = {};
|
||||
this._discoverTimeoutID = window.setTimeout(this._sendDiscoverTimeout.bind(this), 15000);
|
||||
this.sendRemoteRequest('discover', this._deviceOptions)
|
||||
.catch(e => this._sendError(e)); // never reached?
|
||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||
.catch(e => {
|
||||
this._sendError(e);
|
||||
}); // never reached?
|
||||
}
|
||||
// TODO: else?
|
||||
}
|
||||
|
@ -48,7 +50,7 @@ class BLESession extends JSONRPCWebSocket {
|
|||
* callback if connection is successful.
|
||||
* @param {number} id - the id of the peripheral to connect to
|
||||
*/
|
||||
connectDevice (id) {
|
||||
connectPeripheral (id) {
|
||||
this.sendRemoteRequest('connect', {peripheralId: id})
|
||||
.then(() => {
|
||||
this._connected = true;
|
||||
|
@ -63,7 +65,7 @@ class BLESession extends JSONRPCWebSocket {
|
|||
/**
|
||||
* Close the websocket.
|
||||
*/
|
||||
disconnectSession () {
|
||||
disconnect () {
|
||||
this._ws.close();
|
||||
this._connected = false;
|
||||
}
|
||||
|
@ -71,37 +73,10 @@ class BLESession extends JSONRPCWebSocket {
|
|||
/**
|
||||
* @return {bool} whether the peripheral is connected.
|
||||
*/
|
||||
getPeripheralIsConnected () {
|
||||
isConnected () {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received call from the socket.
|
||||
* @param {string} method - a received method label.
|
||||
* @param {object} params - a received list of parameters.
|
||||
* @return {object} - optional return value.
|
||||
*/
|
||||
didReceiveCall (method, params) {
|
||||
switch (method) {
|
||||
case 'didDiscoverPeripheral':
|
||||
this._availablePeripherals[params.peripheralId] = params;
|
||||
this._runtime.emit(
|
||||
this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
|
||||
this._availablePeripherals
|
||||
);
|
||||
if (this._discoverTimeoutID) {
|
||||
// TODO: window?
|
||||
window.clearTimeout(this._discoverTimeoutID);
|
||||
}
|
||||
break;
|
||||
case 'characteristicDidChange':
|
||||
this._characteristicDidChangeCallback(params.message);
|
||||
break;
|
||||
case 'ping':
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start receiving notifications from the specified ble service.
|
||||
* @param {number} serviceId - the ble service to read.
|
||||
|
@ -167,9 +142,36 @@ class BLESession extends JSONRPCWebSocket {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received call from the socket.
|
||||
* @param {string} method - a received method label.
|
||||
* @param {object} params - a received list of parameters.
|
||||
* @return {object} - optional return value.
|
||||
*/
|
||||
didReceiveCall (method, params) {
|
||||
switch (method) {
|
||||
case 'didDiscoverPeripheral':
|
||||
this._availablePeripherals[params.peripheralId] = params;
|
||||
this._runtime.emit(
|
||||
this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
|
||||
this._availablePeripherals
|
||||
);
|
||||
if (this._discoverTimeoutID) {
|
||||
// TODO: window?
|
||||
window.clearTimeout(this._discoverTimeoutID);
|
||||
}
|
||||
break;
|
||||
case 'characteristicDidChange':
|
||||
this._characteristicDidChangeCallback(params.message);
|
||||
break;
|
||||
case 'ping':
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
_sendError (/* e */) {
|
||||
this.disconnectSession();
|
||||
// log.error(`BLESession error: ${JSON.stringify(e)}`);
|
||||
this.disconnect();
|
||||
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_ERROR);
|
||||
}
|
||||
|
||||
|
@ -178,4 +180,4 @@ class BLESession extends JSONRPCWebSocket {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = BLESession;
|
||||
module.exports = BLE;
|
|
@ -1,23 +1,23 @@
|
|||
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
||||
// const log = require('../util/log');
|
||||
const ScratchLinkWebSocket = 'wss://device-manager.scratch.mit.edu:20110/scratch/bt';
|
||||
// const log = require('../util/log');
|
||||
|
||||
class BTSession extends JSONRPCWebSocket {
|
||||
class BT extends JSONRPCWebSocket {
|
||||
|
||||
/**
|
||||
* A BT device session object. It handles connecting, over web sockets, to
|
||||
* BT devices, and reading and writing data to them.
|
||||
* A BT peripheral socket object. It handles connecting, over web sockets, to
|
||||
* BT peripherals, and reading and writing data to them.
|
||||
* @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
|
||||
* @param {object} deviceOptions - the list of options for device discovery.
|
||||
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
||||
* @param {object} connectCallback - a callback for connection.
|
||||
* @param {object} messageCallback - a callback for message sending.
|
||||
*/
|
||||
constructor (runtime, deviceOptions, connectCallback, messageCallback) {
|
||||
constructor (runtime, peripheralOptions, connectCallback, messageCallback) {
|
||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
||||
super(ws);
|
||||
|
||||
this._ws = ws;
|
||||
this._ws.onopen = this.requestDevice.bind(this); // only call request device after socket opens
|
||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
||||
this._ws.onerror = this._sendError.bind(this, 'ws onerror');
|
||||
this._ws.onclose = this._sendError.bind(this, 'ws onclose');
|
||||
|
||||
|
@ -25,21 +25,21 @@ class BTSession extends JSONRPCWebSocket {
|
|||
this._connectCallback = connectCallback;
|
||||
this._connected = false;
|
||||
this._characteristicDidChangeCallback = null;
|
||||
this._deviceOptions = deviceOptions;
|
||||
this._peripheralOptions = peripheralOptions;
|
||||
this._discoverTimeoutID = null;
|
||||
this._messageCallback = messageCallback;
|
||||
this._runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request connection to the device.
|
||||
* Request connection to the peripheral.
|
||||
* If the web socket is not yet open, request when the socket promise resolves.
|
||||
*/
|
||||
requestDevice () {
|
||||
requestPeripheral () {
|
||||
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
||||
this._availablePeripherals = {};
|
||||
this._discoverTimeoutID = window.setTimeout(this._sendDiscoverTimeout.bind(this), 15000);
|
||||
this.sendRemoteRequest('discover', this._deviceOptions)
|
||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||
.catch(e => this._sendError(e)); // never reached?
|
||||
}
|
||||
// TODO: else?
|
||||
|
@ -50,7 +50,7 @@ class BTSession extends JSONRPCWebSocket {
|
|||
* callback if connection is successful.
|
||||
* @param {number} id - the id of the peripheral to connect to
|
||||
*/
|
||||
connectDevice (id) {
|
||||
connectPeripheral (id) {
|
||||
this.sendRemoteRequest('connect', {peripheralId: id})
|
||||
.then(() => {
|
||||
this._connected = true;
|
||||
|
@ -65,7 +65,7 @@ class BTSession extends JSONRPCWebSocket {
|
|||
/**
|
||||
* Close the websocket.
|
||||
*/
|
||||
disconnectSession () {
|
||||
disconnect () {
|
||||
this._ws.close();
|
||||
this._connected = false;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class BTSession extends JSONRPCWebSocket {
|
|||
/**
|
||||
* @return {bool} whether the peripheral is connected.
|
||||
*/
|
||||
getPeripheralIsConnected () {
|
||||
isConnected () {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
|
@ -113,8 +113,8 @@ class BTSession extends JSONRPCWebSocket {
|
|||
}
|
||||
|
||||
_sendError (/* e */) {
|
||||
this.disconnectSession();
|
||||
// log.error(`BTSession error: ${JSON.stringify(e)}`);
|
||||
this.disconnect();
|
||||
// log.error(`BT error: ${JSON.stringify(e)}`);
|
||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_ERROR);
|
||||
}
|
||||
|
||||
|
@ -123,4 +123,4 @@ class BTSession extends JSONRPCWebSocket {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = BTSession;
|
||||
module.exports = BT;
|
|
@ -105,7 +105,6 @@ class VirtualMachine extends EventEmitter {
|
|||
this.runtime.on(Runtime.BLOCKSINFO_UPDATE, blocksInfo => {
|
||||
this.emit(Runtime.BLOCKSINFO_UPDATE, blocksInfo);
|
||||
});
|
||||
|
||||
this.runtime.on(Runtime.PERIPHERAL_LIST_UPDATE, info => {
|
||||
this.emit(Runtime.PERIPHERAL_LIST_UPDATE, info);
|
||||
});
|
||||
|
@ -213,18 +212,36 @@ class VirtualMachine extends EventEmitter {
|
|||
this.runtime.ioDevices.video.setProvider(videoProvider);
|
||||
}
|
||||
|
||||
startDeviceScan (extensionId) {
|
||||
this.runtime.startDeviceScan(extensionId);
|
||||
/**
|
||||
* Tell the specified extension to scan for a peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
*/
|
||||
scanForPeripheral (extensionId) {
|
||||
this.runtime.scanForPeripheral(extensionId);
|
||||
}
|
||||
|
||||
connectToPeripheral (extensionId, peripheralId) {
|
||||
this.runtime.connectToPeripheral(extensionId, peripheralId);
|
||||
/**
|
||||
* Connect to the extension's specified peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
* @param {number} peripheralId - the id of the peripheral.
|
||||
*/
|
||||
connectPeripheral (extensionId, peripheralId) {
|
||||
this.runtime.connectPeripheral(extensionId, peripheralId);
|
||||
}
|
||||
|
||||
disconnectExtensionSession (extensionId) {
|
||||
this.runtime.disconnectExtensionSession(extensionId);
|
||||
/**
|
||||
* Disconnect from the extension's connected peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
*/
|
||||
disconnectPeripheral (extensionId) {
|
||||
this.runtime.disconnectPeripheral(extensionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extension has a currently connected peripheral.
|
||||
* @param {string} extensionId - the id of the extension.
|
||||
* @return {boolean} - whether the extension has a connected peripheral.
|
||||
*/
|
||||
getPeripheralIsConnected (extensionId) {
|
||||
return this.runtime.getPeripheralIsConnected(extensionId);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ test('waitForSocket', t => {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('requestDevice', t => {
|
||||
test('requestPeripheral', t => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ test('constructor', t => {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('requestDevice', t => {
|
||||
test('requestPeripheral', t => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('connectDevice', t => {
|
||||
test('connectPeripheral', t => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue