mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Merge branch 'develop' into extensions/ev3-rate-limiting
This commit is contained in:
commit
b6a33cc3ed
9 changed files with 235 additions and 144 deletions
|
@ -16,6 +16,7 @@ const maybeFormatMessage = require('../util/maybe-format-message');
|
||||||
const StageLayering = require('./stage-layering');
|
const StageLayering = require('./stage-layering');
|
||||||
const Variable = require('./variable');
|
const Variable = require('./variable');
|
||||||
const xmlEscape = require('../util/xml-escape');
|
const xmlEscape = require('../util/xml-escape');
|
||||||
|
const ScratchLinkWebSocket = require('../util/scratch-link-websocket');
|
||||||
|
|
||||||
// Virtual I/O devices.
|
// Virtual I/O devices.
|
||||||
const Clock = require('../io/clock');
|
const Clock = require('../io/clock');
|
||||||
|
@ -1289,6 +1290,34 @@ class Runtime extends EventEmitter {
|
||||||
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a scratch link socket.
|
||||||
|
* @param {string} type Either BLE or BT
|
||||||
|
* @returns {ScratchLinkSocket} The scratch link socket.
|
||||||
|
*/
|
||||||
|
getScratchLinkSocket (type) {
|
||||||
|
const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory;
|
||||||
|
return factory(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure how ScratchLink sockets are created. Factory must consume a "type" parameter
|
||||||
|
* either BT or BLE.
|
||||||
|
* @param {Function} factory The new factory for creating ScratchLink sockets.
|
||||||
|
*/
|
||||||
|
configureScratchLinkSocketFactory (factory) {
|
||||||
|
this._linkSocketFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default scratch link socket creator, using websockets to the installed device manager.
|
||||||
|
* @param {string} type Either BLE or BT
|
||||||
|
* @returns {ScratchLinkSocket} The new scratch link socket (a WebSocket object)
|
||||||
|
*/
|
||||||
|
_defaultScratchLinkSocketFactory (type) {
|
||||||
|
return new ScratchLinkWebSocket(type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an extension that communications with a hardware peripheral by id,
|
* Register an extension that communications with a hardware peripheral by id,
|
||||||
* to have access to it and its peripheral functions in the future.
|
* to have access to it and its peripheral functions in the future.
|
||||||
|
|
|
@ -20,7 +20,6 @@ const builtinExtensions = {
|
||||||
text2speech: () => require('../extensions/scratch3_text2speech'),
|
text2speech: () => require('../extensions/scratch3_text2speech'),
|
||||||
translate: () => require('../extensions/scratch3_translate'),
|
translate: () => require('../extensions/scratch3_translate'),
|
||||||
videoSensing: () => require('../extensions/scratch3_video_sensing'),
|
videoSensing: () => require('../extensions/scratch3_video_sensing'),
|
||||||
speech2text: () => require('../extensions/scratch3_speech2text'),
|
|
||||||
ev3: () => require('../extensions/scratch3_ev3'),
|
ev3: () => require('../extensions/scratch3_ev3'),
|
||||||
makeymakey: () => require('../extensions/scratch3_makeymakey'),
|
makeymakey: () => require('../extensions/scratch3_makeymakey'),
|
||||||
boost: () => require('../extensions/scratch3_boost'),
|
boost: () => require('../extensions/scratch3_boost'),
|
||||||
|
|
|
@ -332,36 +332,36 @@ class BoostMotor {
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._pendingTimeoutId = null;
|
this._pendingDurationTimeoutId = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The starting time for the pending timeout.
|
* The starting time for the pending duration timeout.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._pendingTimeoutStartTime = null;
|
this._pendingDurationTimeoutStartTime = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The delay/duration of the pending timeout.
|
* The delay/duration of the pending duration timeout.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._pendingTimeoutDelay = null;
|
this._pendingDurationTimeoutDelay = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The target position of a turn-based command.
|
* The target position of a turn-based command.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._pendingPositionDestination = null;
|
this._pendingRotationDestination = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the motor has been turned on run for a specific duration,
|
* If the motor has been turned on run for a specific rotation, this is the function
|
||||||
* this is the function that will be called once Scratch VM gets a notification from the Move Hub.
|
* that will be called once Scratch VM gets a notification from the Move Hub.
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._pendingPromiseFunction = null;
|
this._pendingRotationPromise = null;
|
||||||
|
|
||||||
this.turnOff = this.turnOff.bind(this);
|
this.turnOff = this.turnOff.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -432,43 +432,43 @@ class BoostMotor {
|
||||||
*/
|
*/
|
||||||
set status (value) {
|
set status (value) {
|
||||||
this._clearRotationState();
|
this._clearRotationState();
|
||||||
this._clearTimeout();
|
this._clearDurationTimeout();
|
||||||
this._status = value;
|
this._status = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number} - time, in milliseconds, of when the pending timeout began.
|
* @return {number} - time, in milliseconds, of when the pending duration timeout began.
|
||||||
*/
|
*/
|
||||||
get pendingTimeoutStartTime () {
|
get pendingDurationTimeoutStartTime () {
|
||||||
return this._pendingTimeoutStartTime;
|
return this._pendingDurationTimeoutStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number} - delay, in milliseconds, of the pending timeout.
|
* @return {number} - delay, in milliseconds, of the pending duration timeout.
|
||||||
*/
|
*/
|
||||||
get pendingTimeoutDelay () {
|
get pendingDurationTimeoutDelay () {
|
||||||
return this._pendingTimeoutDelay;
|
return this._pendingDurationTimeoutDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number} - delay, in milliseconds, of the pending timeout.
|
* @return {number} - target position, in degrees, of the pending rotation.
|
||||||
*/
|
*/
|
||||||
get pendingPositionDestination () {
|
get pendingRotationDestination () {
|
||||||
return this._pendingPositionDestination;
|
return this._pendingRotationDestination;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {boolean} - true if this motor is currently moving, false if this motor is off or braking.
|
* @return {Promise} - the Promise function for the pending rotation.
|
||||||
*/
|
*/
|
||||||
get pendingPromiseFunction () {
|
get pendingRotationPromise () {
|
||||||
return this._pendingPromiseFunction;
|
return this._pendingRotationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {function} func - function to resolve promise
|
* @param {function} func - function to resolve pending rotation Promise
|
||||||
*/
|
*/
|
||||||
set pendingPromiseFunction (func) {
|
set pendingRotationPromise (func) {
|
||||||
this._pendingPromiseFunction = func;
|
this._pendingRotationPromise = func;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -505,7 +505,7 @@ class BoostMotor {
|
||||||
milliseconds = Math.max(0, milliseconds);
|
milliseconds = Math.max(0, milliseconds);
|
||||||
this.status = BoostMotorState.ON_FOR_TIME;
|
this.status = BoostMotorState.ON_FOR_TIME;
|
||||||
this._turnOn();
|
this._turnOn();
|
||||||
this._setNewTimeout(this.turnOff, milliseconds);
|
this._setNewDurationTimeout(this.turnOff, milliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -530,7 +530,7 @@ class BoostMotor {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.status = BoostMotorState.ON_FOR_ROTATION;
|
this.status = BoostMotorState.ON_FOR_ROTATION;
|
||||||
this._pendingPositionDestination = this.position + (degrees * this.direction * direction);
|
this._pendingRotationDestination = this.position + (degrees * this.direction * direction);
|
||||||
this._parent.send(BoostBLE.characteristic, cmd);
|
this._parent.send(BoostBLE.characteristic, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,12 +558,12 @@ class BoostMotor {
|
||||||
* Clear the motor action timeout, if any. Safe to call even when there is no pending timeout.
|
* Clear the motor action timeout, if any. Safe to call even when there is no pending timeout.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_clearTimeout () {
|
_clearDurationTimeout () {
|
||||||
if (this._pendingTimeoutId !== null) {
|
if (this._pendingDurationTimeoutId !== null) {
|
||||||
clearTimeout(this._pendingTimeoutId);
|
clearTimeout(this._pendingDurationTimeoutId);
|
||||||
this._pendingTimeoutId = null;
|
this._pendingDurationTimeoutId = null;
|
||||||
this._pendingTimeoutStartTime = null;
|
this._pendingDurationTimeoutStartTime = null;
|
||||||
this._pendingTimeoutDelay = null;
|
this._pendingDurationTimeoutDelay = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,19 +573,19 @@ class BoostMotor {
|
||||||
* @param {int} delay - wait this many milliseconds before calling the callback.
|
* @param {int} delay - wait this many milliseconds before calling the callback.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_setNewTimeout (callback, delay) {
|
_setNewDurationTimeout (callback, delay) {
|
||||||
this._clearTimeout();
|
this._clearDurationTimeout();
|
||||||
const timeoutID = setTimeout(() => {
|
const timeoutID = setTimeout(() => {
|
||||||
if (this._pendingTimeoutId === timeoutID) {
|
if (this._pendingDurationTimeoutId === timeoutID) {
|
||||||
this._pendingTimeoutId = null;
|
this._pendingDurationTimeoutId = null;
|
||||||
this._pendingTimeoutStartTime = null;
|
this._pendingDurationTimeoutStartTime = null;
|
||||||
this._pendingTimeoutDelay = null;
|
this._pendingDurationTimeoutDelay = null;
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
}, delay);
|
}, delay);
|
||||||
this._pendingTimeoutId = timeoutID;
|
this._pendingDurationTimeoutId = timeoutID;
|
||||||
this._pendingTimeoutStartTime = Date.now();
|
this._pendingDurationTimeoutStartTime = Date.now();
|
||||||
this._pendingTimeoutDelay = delay;
|
this._pendingDurationTimeoutDelay = delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -594,11 +594,11 @@ class BoostMotor {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_clearRotationState () {
|
_clearRotationState () {
|
||||||
if (this._pendingPromiseFunction !== null) {
|
if (this._pendingRotationPromise !== null) {
|
||||||
this._pendingPromiseFunction();
|
this._pendingRotationPromise();
|
||||||
this._pendingPromiseFunction = null;
|
this._pendingRotationPromise = null;
|
||||||
}
|
}
|
||||||
this._pendingPositionDestination = null;
|
this._pendingRotationDestination = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1669,7 +1669,7 @@ class Scratch3BoostBlocks {
|
||||||
if (motor.power === 0) return Promise.resolve();
|
if (motor.power === 0) return Promise.resolve();
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
motor.turnOnForDegrees(degrees, sign);
|
motor.turnOnForDegrees(degrees, sign);
|
||||||
motor.pendingPromiseFunction = resolve;
|
motor.pendingRotationPromise = resolve;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1739,7 +1739,8 @@ class Scratch3BoostBlocks {
|
||||||
motor.turnOnForever();
|
motor.turnOnForever();
|
||||||
break;
|
break;
|
||||||
case BoostMotorState.ON_FOR_TIME:
|
case BoostMotorState.ON_FOR_TIME:
|
||||||
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
|
motor.turnOnFor(motor.pendingDurationTimeoutStartTime +
|
||||||
|
motor.pendingDurationTimeoutDelay - Date.now());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1785,7 +1786,8 @@ class Scratch3BoostBlocks {
|
||||||
motor.turnOnForever();
|
motor.turnOnForever();
|
||||||
break;
|
break;
|
||||||
case BoostMotorState.ON_FOR_TIME:
|
case BoostMotorState.ON_FOR_TIME:
|
||||||
motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());
|
motor.turnOnFor(motor.pendingDurationTimeoutStartTime +
|
||||||
|
motor.pendingDurationTimeoutDelay - Date.now());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,6 @@
|
||||||
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
const JSONRPC = require('../util/jsonrpc');
|
||||||
const ScratchLinkWebSocket = 'wss://device-manager.scratch.mit.edu:20110/scratch/ble';
|
|
||||||
// const log = require('../util/log');
|
|
||||||
|
|
||||||
class BLE extends JSONRPCWebSocket {
|
class BLE extends JSONRPC {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A BLE peripheral socket object. It handles connecting, over web sockets, to
|
* A BLE peripheral socket object. It handles connecting, over web sockets, to
|
||||||
|
@ -14,13 +12,15 @@ class BLE extends JSONRPCWebSocket {
|
||||||
* @param {object} disconnectCallback - a callback for disconnection.
|
* @param {object} disconnectCallback - a callback for disconnection.
|
||||||
*/
|
*/
|
||||||
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null) {
|
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null) {
|
||||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
super();
|
||||||
super(ws);
|
|
||||||
|
|
||||||
this._ws = ws;
|
this._socket = runtime.getScratchLinkSocket('BLE');
|
||||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
this._socket.setOnOpen(this.requestPeripheral.bind(this));
|
||||||
this._ws.onerror = this._handleRequestError.bind(this, 'ws onerror');
|
this._socket.setOnClose(this.handleDisconnectError.bind(this));
|
||||||
this._ws.onclose = this.handleDisconnectError.bind(this, 'ws onclose');
|
this._socket.setOnError(this._handleRequestError.bind(this));
|
||||||
|
this._socket.setHandleMessage(this._handleMessage.bind(this));
|
||||||
|
|
||||||
|
this._sendMessage = this._socket.sendMessage.bind(this._socket);
|
||||||
|
|
||||||
this._availablePeripherals = {};
|
this._availablePeripherals = {};
|
||||||
this._connectCallback = connectCallback;
|
this._connectCallback = connectCallback;
|
||||||
|
@ -31,6 +31,8 @@ class BLE extends JSONRPCWebSocket {
|
||||||
this._extensionId = extensionId;
|
this._extensionId = extensionId;
|
||||||
this._peripheralOptions = peripheralOptions;
|
this._peripheralOptions = peripheralOptions;
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
|
|
||||||
|
this._socket.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,18 +40,15 @@ class BLE extends JSONRPCWebSocket {
|
||||||
* If the web socket is not yet open, request when the socket promise resolves.
|
* If the web socket is not yet open, request when the socket promise resolves.
|
||||||
*/
|
*/
|
||||||
requestPeripheral () {
|
requestPeripheral () {
|
||||||
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
this._availablePeripherals = {};
|
||||||
this._availablePeripherals = {};
|
if (this._discoverTimeoutID) {
|
||||||
if (this._discoverTimeoutID) {
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
|
||||||
}
|
|
||||||
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
|
||||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
|
||||||
.catch(e => {
|
|
||||||
this._handleRequestError(e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// TODO: else?
|
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
||||||
|
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||||
|
.catch(e => {
|
||||||
|
this._handleRequestError(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,14 +72,14 @@ class BLE extends JSONRPCWebSocket {
|
||||||
* Close the websocket.
|
* Close the websocket.
|
||||||
*/
|
*/
|
||||||
disconnect () {
|
disconnect () {
|
||||||
if (this._ws.readyState === this._ws.OPEN) {
|
|
||||||
this._ws.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._connected) {
|
if (this._connected) {
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._socket.isOpen()) {
|
||||||
|
this._socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
58
src/io/bt.js
58
src/io/bt.js
|
@ -1,8 +1,6 @@
|
||||||
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
const JSONRPC = require('../util/jsonrpc');
|
||||||
const ScratchLinkWebSocket = 'wss://device-manager.scratch.mit.edu:20110/scratch/bt';
|
|
||||||
// const log = require('../util/log');
|
|
||||||
|
|
||||||
class BT extends JSONRPCWebSocket {
|
class BT extends JSONRPC {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A BT peripheral socket object. It handles connecting, over web sockets, to
|
* A BT peripheral socket object. It handles connecting, over web sockets, to
|
||||||
|
@ -15,13 +13,15 @@ class BT extends JSONRPCWebSocket {
|
||||||
* @param {object} messageCallback - a callback for message sending.
|
* @param {object} messageCallback - a callback for message sending.
|
||||||
*/
|
*/
|
||||||
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null, messageCallback) {
|
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null, messageCallback) {
|
||||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
super();
|
||||||
super(ws);
|
|
||||||
|
|
||||||
this._ws = ws;
|
this._socket = runtime.getScratchLinkSocket('BT');
|
||||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
this._socket.setOnOpen(this.requestPeripheral.bind(this));
|
||||||
this._ws.onerror = this._handleRequestError.bind(this, 'ws onerror');
|
this._socket.setOnError(this._handleRequestError.bind(this));
|
||||||
this._ws.onclose = this.handleDisconnectError.bind(this, 'ws onclose');
|
this._socket.setOnClose(this.handleDisconnectError.bind(this));
|
||||||
|
this._socket.setHandleMessage(this._handleMessage.bind(this));
|
||||||
|
|
||||||
|
this._sendMessage = this._socket.sendMessage.bind(this._socket);
|
||||||
|
|
||||||
this._availablePeripherals = {};
|
this._availablePeripherals = {};
|
||||||
this._connectCallback = connectCallback;
|
this._connectCallback = connectCallback;
|
||||||
|
@ -33,6 +33,8 @@ class BT extends JSONRPCWebSocket {
|
||||||
this._peripheralOptions = peripheralOptions;
|
this._peripheralOptions = peripheralOptions;
|
||||||
this._messageCallback = messageCallback;
|
this._messageCallback = messageCallback;
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
|
|
||||||
|
this._socket.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,27 +42,29 @@ class BT extends JSONRPCWebSocket {
|
||||||
* If the web socket is not yet open, request when the socket promise resolves.
|
* If the web socket is not yet open, request when the socket promise resolves.
|
||||||
*/
|
*/
|
||||||
requestPeripheral () {
|
requestPeripheral () {
|
||||||
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
this._availablePeripherals = {};
|
||||||
this._availablePeripherals = {};
|
if (this._discoverTimeoutID) {
|
||||||
if (this._discoverTimeoutID) {
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
|
||||||
}
|
|
||||||
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
|
||||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
|
||||||
.catch(
|
|
||||||
e => this._handleRequestError(e)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// TODO: else?
|
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
||||||
|
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||||
|
.catch(
|
||||||
|
e => this._handleRequestError(e)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try connecting to the input peripheral id, and then call the connect
|
* Try connecting to the input peripheral id, and then call the connect
|
||||||
* callback if connection is successful.
|
* callback if connection is successful.
|
||||||
* @param {number} id - the id of the peripheral to connect to
|
* @param {number} id - the id of the peripheral to connect to
|
||||||
|
* @param {string} pin - an optional pin for pairing
|
||||||
*/
|
*/
|
||||||
connectPeripheral (id) {
|
connectPeripheral (id, pin = null) {
|
||||||
this.sendRemoteRequest('connect', {peripheralId: id})
|
const params = {peripheralId: id};
|
||||||
|
if (pin) {
|
||||||
|
params.pin = pin;
|
||||||
|
}
|
||||||
|
this.sendRemoteRequest('connect', params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._connected = true;
|
this._connected = true;
|
||||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
||||||
|
@ -75,14 +79,14 @@ class BT extends JSONRPCWebSocket {
|
||||||
* Close the websocket.
|
* Close the websocket.
|
||||||
*/
|
*/
|
||||||
disconnect () {
|
disconnect () {
|
||||||
if (this._ws.readyState === this._ws.OPEN) {
|
|
||||||
this._ws.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._connected) {
|
if (this._connected) {
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._socket.isOpen()) {
|
||||||
|
this._socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
const JSONRPC = require('./jsonrpc');
|
|
||||||
// const log = require('../util/log');
|
|
||||||
|
|
||||||
class JSONRPCWebSocket extends JSONRPC {
|
|
||||||
constructor (webSocket) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._ws = webSocket;
|
|
||||||
this._ws.onmessage = e => this._onSocketMessage(e);
|
|
||||||
this._ws.onopen = e => this._onSocketOpen(e);
|
|
||||||
this._ws.onclose = e => this._onSocketClose(e);
|
|
||||||
this._ws.onerror = e => this._onSocketError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose () {
|
|
||||||
this._ws.close();
|
|
||||||
this._ws = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketOpen () {
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketClose () {
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketError () {
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSocketMessage (e) {
|
|
||||||
const json = JSON.parse(e.data);
|
|
||||||
this._handleMessage(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendMessage (message) {
|
|
||||||
const messageText = JSON.stringify(message);
|
|
||||||
this._ws.send(messageText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = JSONRPCWebSocket;
|
|
84
src/util/scratch-link-websocket.js
Normal file
84
src/util/scratch-link-websocket.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* This class provides a ScratchLinkSocket implementation using WebSockets,
|
||||||
|
* attempting to connect with the locally installed Scratch-Link.
|
||||||
|
*
|
||||||
|
* To connect with ScratchLink without WebSockets, you must implement all of the
|
||||||
|
* public methods in this class.
|
||||||
|
* - open()
|
||||||
|
* - close()
|
||||||
|
* - setOn[Open|Close|Error]
|
||||||
|
* - setHandleMessage
|
||||||
|
* - sendMessage(msgObj)
|
||||||
|
* - isOpen()
|
||||||
|
*/
|
||||||
|
class ScratchLinkWebSocket {
|
||||||
|
constructor (type) {
|
||||||
|
this._type = type;
|
||||||
|
this._onOpen = null;
|
||||||
|
this._onClose = null;
|
||||||
|
this._onError = null;
|
||||||
|
this._handleMessage = null;
|
||||||
|
|
||||||
|
this._ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
open () {
|
||||||
|
switch (this._type) {
|
||||||
|
case 'BLE':
|
||||||
|
this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/ble');
|
||||||
|
break;
|
||||||
|
case 'BT':
|
||||||
|
this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/bt');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown ScratchLink socket Type: ${this._type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._onOpen && this._onClose && this._onError && this._handleMessage) {
|
||||||
|
this._ws.onopen = this._onOpen;
|
||||||
|
this._ws.onclose = this._onClose;
|
||||||
|
this._ws.onerror = this._onError;
|
||||||
|
} else {
|
||||||
|
throw new Error('Must set open, close, message and error handlers before calling open on the socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._ws.onmessage = this._onMessage.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
this._ws.close();
|
||||||
|
this._ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage (message) {
|
||||||
|
const messageText = JSON.stringify(message);
|
||||||
|
this._ws.send(messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnOpen (fn) {
|
||||||
|
this._onOpen = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnClose (fn) {
|
||||||
|
this._onClose = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnError (fn) {
|
||||||
|
this._onError = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHandleMessage (fn) {
|
||||||
|
this._handleMessage = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen () {
|
||||||
|
return this._ws && this._ws.readyState === this._ws.OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMessage (e) {
|
||||||
|
const json = JSON.parse(e.data);
|
||||||
|
this._handleMessage(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ScratchLinkWebSocket;
|
|
@ -1525,6 +1525,14 @@ class VirtualMachine extends EventEmitter {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow VM consumer to configure the ScratchLink socket creator.
|
||||||
|
* @param {Function} factory The custom ScratchLink socket factory.
|
||||||
|
*/
|
||||||
|
configureScratchLinkSocketFactory (factory) {
|
||||||
|
this.runtime.configureScratchLinkSocketFactory(factory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = VirtualMachine;
|
module.exports = VirtualMachine;
|
||||||
|
|
Loading…
Reference in a new issue