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:
Evelyn Eastmond 2018-09-07 17:01:23 -04:00 committed by Eric Rosenbaum
parent b41423bdfa
commit ec432e3b2f
9 changed files with 1476 additions and 1234 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View file

@ -9,7 +9,7 @@ test('waitForSocket', t => {
t.end();
});
test('requestDevice', t => {
test('requestPeripheral', t => {
t.end();
});

View file

@ -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();
});