mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 06:23:37 -05:00
Merge pull request #1763 from evhan55/extensions/disconnect-errors
Various fixes to extension disconnect errors
This commit is contained in:
commit
0b251adace
7 changed files with 182 additions and 62 deletions
|
@ -558,6 +558,8 @@ class Runtime extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for updating the available set of peripheral devices.
|
* Event name for updating the available set of peripheral devices.
|
||||||
|
* This causes the peripheral connection modal to update a list of
|
||||||
|
* available peripherals.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
static get PERIPHERAL_LIST_UPDATE () {
|
static get PERIPHERAL_LIST_UPDATE () {
|
||||||
|
@ -566,14 +568,25 @@ class Runtime extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for reporting that a peripheral has connected.
|
* Event name for reporting that a peripheral has connected.
|
||||||
|
* This causes the status button in the blocks menu to indicate 'connected'.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
static get PERIPHERAL_CONNECTED () {
|
static get PERIPHERAL_CONNECTED () {
|
||||||
return 'PERIPHERAL_CONNECTED';
|
return 'PERIPHERAL_CONNECTED';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event name for reporting that a peripheral has been intentionally disconnected.
|
||||||
|
* This causes the status button in the blocks menu to indicate 'disconnected'.
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
static get PERIPHERAL_DISCONNECTED () {
|
||||||
|
return 'PERIPHERAL_DISCONNECTED';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for reporting that a peripheral has encountered a request error.
|
* Event name for reporting that a peripheral has encountered a request error.
|
||||||
|
* This causes the peripheral connection modal to switch to an error state.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
static get PERIPHERAL_REQUEST_ERROR () {
|
static get PERIPHERAL_REQUEST_ERROR () {
|
||||||
|
@ -581,15 +594,17 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for reporting that a peripheral has encountered a disconnect error.
|
* Event name for reporting that a peripheral connection has been lost.
|
||||||
|
* This causes a 'peripheral connection lost' error alert to display.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
static get PERIPHERAL_DISCONNECT_ERROR () {
|
static get PERIPHERAL_CONNECTION_LOST_ERROR () {
|
||||||
return 'PERIPHERAL_DISCONNECT_ERROR';
|
return 'PERIPHERAL_CONNECTION_LOST_ERROR';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for reporting that a peripheral has not been discovered.
|
* Event name for reporting that a peripheral has not been discovered.
|
||||||
|
* This causes the peripheral connection modal to show a timeout state.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
static get PERIPHERAL_SCAN_TIMEOUT () {
|
static get PERIPHERAL_SCAN_TIMEOUT () {
|
||||||
|
|
|
@ -476,6 +476,7 @@ class EV3 {
|
||||||
this._bt = null;
|
this._bt = null;
|
||||||
this._runtime.registerPeripheralExtension(extensionId, this);
|
this._runtime.registerPeripheralExtension(extensionId, this);
|
||||||
|
|
||||||
|
this.disconnect = this.disconnect.bind(this);
|
||||||
this._onConnect = this._onConnect.bind(this);
|
this._onConnect = this._onConnect.bind(this);
|
||||||
this._onMessage = this._onMessage.bind(this);
|
this._onMessage = this._onMessage.bind(this);
|
||||||
this._pollValues = this._pollValues.bind(this);
|
this._pollValues = this._pollValues.bind(this);
|
||||||
|
@ -561,7 +562,7 @@ class EV3 {
|
||||||
this._bt = new BT(this._runtime, this._extensionId, {
|
this._bt = new BT(this._runtime, this._extensionId, {
|
||||||
majorDeviceClass: 8,
|
majorDeviceClass: 8,
|
||||||
minorDeviceClass: 1
|
minorDeviceClass: 1
|
||||||
}, this._onConnect, this._onMessage);
|
}, this._onConnect, this.disconnect, this._onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,8 +25,12 @@ const BLECommand = {
|
||||||
CMD_DISPLAY_LED: 0x82
|
CMD_DISPLAY_LED: 0x82
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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) before reporting to the BLE socket
|
||||||
|
* that data has stopped coming from the peripheral.
|
||||||
|
*/
|
||||||
|
const BLETimeout = 4500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
|
* A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
|
||||||
|
@ -34,6 +38,12 @@ const BLETimeout = 4500; // TODO: might need tweaking based on how long the peri
|
||||||
*/
|
*/
|
||||||
const BLESendInterval = 100;
|
const BLESendInterval = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string to report to the BLE socket when the micro:bit has stopped receiving data.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const BLEDataStoppedError = 'micro:bit extension stopped receiving data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for micro:bit protocol.
|
* Enum for micro:bit protocol.
|
||||||
* https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md
|
* https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md
|
||||||
|
@ -212,7 +222,7 @@ class MicroBit {
|
||||||
filters: [
|
filters: [
|
||||||
{services: [BLEUUID.service]}
|
{services: [BLEUUID.service]}
|
||||||
]
|
]
|
||||||
}, this._onConnect);
|
}, this._onConnect, this.disconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -289,7 +299,10 @@ class MicroBit {
|
||||||
*/
|
*/
|
||||||
_onConnect () {
|
_onConnect () {
|
||||||
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage);
|
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage);
|
||||||
this._timeoutID = window.setInterval(this.disconnect, BLETimeout);
|
this._timeoutID = window.setInterval(
|
||||||
|
() => this._ble.handleDisconnectError(BLEDataStoppedError),
|
||||||
|
BLETimeout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -317,7 +330,10 @@ class MicroBit {
|
||||||
|
|
||||||
// cancel disconnect timeout and start a new one
|
// cancel disconnect timeout and start a new one
|
||||||
window.clearInterval(this._timeoutID);
|
window.clearInterval(this._timeoutID);
|
||||||
this._timeoutID = window.setInterval(this.disconnect, BLETimeout);
|
this._timeoutID = window.setInterval(
|
||||||
|
() => this._ble.handleDisconnectError(BLEDataStoppedError),
|
||||||
|
BLETimeout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,11 +40,18 @@ const BLEService = {
|
||||||
*/
|
*/
|
||||||
const BLECharacteristic = {
|
const BLECharacteristic = {
|
||||||
ATTACHED_IO: '00001527-1212-efde-1523-785feabcd123',
|
ATTACHED_IO: '00001527-1212-efde-1523-785feabcd123',
|
||||||
|
LOW_VOLTAGE_ALERT: '00001528-1212-efde-1523-785feabcd123',
|
||||||
INPUT_VALUES: '00001560-1212-efde-1523-785feabcd123',
|
INPUT_VALUES: '00001560-1212-efde-1523-785feabcd123',
|
||||||
INPUT_COMMAND: '00001563-1212-efde-1523-785feabcd123',
|
INPUT_COMMAND: '00001563-1212-efde-1523-785feabcd123',
|
||||||
OUTPUT_COMMAND: '00001565-1212-efde-1523-785feabcd123'
|
OUTPUT_COMMAND: '00001565-1212-efde-1523-785feabcd123'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A time interval to wait (in milliseconds) in between battery check calls.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
const BLEBatteryCheckInterval = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
|
* A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
@ -421,8 +428,17 @@ class WeDo2 {
|
||||||
*/
|
*/
|
||||||
this._rateLimiter = new RateLimiter(BLESendRateMax);
|
this._rateLimiter = new RateLimiter(BLESendRateMax);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interval id for the battery check interval.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._batteryLevelIntervalId = null;
|
||||||
|
|
||||||
|
this.disconnect = this.disconnect.bind(this);
|
||||||
this._onConnect = this._onConnect.bind(this);
|
this._onConnect = this._onConnect.bind(this);
|
||||||
this._onMessage = this._onMessage.bind(this);
|
this._onMessage = this._onMessage.bind(this);
|
||||||
|
this._checkBatteryLevel = this._checkBatteryLevel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -578,7 +594,7 @@ class WeDo2 {
|
||||||
services: [BLEService.DEVICE_SERVICE]
|
services: [BLEService.DEVICE_SERVICE]
|
||||||
}],
|
}],
|
||||||
optionalServices: [BLEService.IO_SERVICE]
|
optionalServices: [BLEService.IO_SERVICE]
|
||||||
}, this._onConnect);
|
}, this._onConnect, this.disconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -606,6 +622,11 @@ class WeDo2 {
|
||||||
if (this._ble) {
|
if (this._ble) {
|
||||||
this._ble.disconnect();
|
this._ble.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._batteryLevelIntervalId) {
|
||||||
|
window.clearInterval(this._batteryLevelIntervalId);
|
||||||
|
this._batteryLevelIntervalId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -711,6 +732,7 @@ class WeDo2 {
|
||||||
BLECharacteristic.ATTACHED_IO,
|
BLECharacteristic.ATTACHED_IO,
|
||||||
this._onMessage
|
this._onMessage
|
||||||
);
|
);
|
||||||
|
this._batteryLevelIntervalId = window.setInterval(this._checkBatteryLevel, BLEBatteryCheckInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -757,6 +779,19 @@ class WeDo2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the battery level on the WeDo 2.0. If the WeDo 2.0 has disconnected
|
||||||
|
* for some reason, the BLE socket will get an error back and automatically
|
||||||
|
* close the socket.
|
||||||
|
*/
|
||||||
|
_checkBatteryLevel () {
|
||||||
|
this._ble.read(
|
||||||
|
BLEService.DEVICE_SERVICE,
|
||||||
|
BLECharacteristic.LOW_VOLTAGE_ALERT,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new sensor or motor connected at a port. Store the type of
|
* Register a new sensor or motor connected at a port. Store the type of
|
||||||
* sensor or motor internally, and then register for notifications on input
|
* sensor or motor internally, and then register for notifications on input
|
||||||
|
|
|
@ -11,23 +11,25 @@ class BLE extends JSONRPCWebSocket {
|
||||||
* @param {string} extensionId - the id of the extension using this socket.
|
* @param {string} extensionId - the id of the extension using this socket.
|
||||||
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
||||||
* @param {object} connectCallback - a callback for connection.
|
* @param {object} connectCallback - a callback for connection.
|
||||||
|
* @param {object} disconnectCallback - a callback for disconnection.
|
||||||
*/
|
*/
|
||||||
constructor (runtime, extensionId, peripheralOptions, connectCallback) {
|
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null) {
|
||||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
const ws = new WebSocket(ScratchLinkWebSocket);
|
||||||
super(ws);
|
super(ws);
|
||||||
|
|
||||||
this._ws = ws;
|
this._ws = ws;
|
||||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
||||||
this._ws.onerror = this._sendRequestError.bind(this, 'ws onerror');
|
this._ws.onerror = this._handleRequestError.bind(this, 'ws onerror');
|
||||||
this._ws.onclose = this._sendDisconnectError.bind(this, 'ws onclose');
|
this._ws.onclose = this.handleDisconnectError.bind(this, 'ws onclose');
|
||||||
|
|
||||||
this._availablePeripherals = {};
|
this._availablePeripherals = {};
|
||||||
this._connectCallback = connectCallback;
|
this._connectCallback = connectCallback;
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
this._characteristicDidChangeCallback = null;
|
this._characteristicDidChangeCallback = null;
|
||||||
|
this._disconnectCallback = disconnectCallback;
|
||||||
|
this._discoverTimeoutID = null;
|
||||||
this._extensionId = extensionId;
|
this._extensionId = extensionId;
|
||||||
this._peripheralOptions = peripheralOptions;
|
this._peripheralOptions = peripheralOptions;
|
||||||
this._discoverTimeoutID = null;
|
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +43,10 @@ class BLE extends JSONRPCWebSocket {
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
this._discoverTimeoutID = window.setTimeout(this._sendDiscoverTimeout.bind(this), 15000);
|
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
||||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendRequestError(e);
|
this._handleRequestError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// TODO: else?
|
// TODO: else?
|
||||||
|
@ -63,7 +65,7 @@ class BLE extends JSONRPCWebSocket {
|
||||||
this._connectCallback();
|
this._connectCallback();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendRequestError(e);
|
this._handleRequestError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,10 +73,15 @@ class BLE extends JSONRPCWebSocket {
|
||||||
* Close the websocket.
|
* Close the websocket.
|
||||||
*/
|
*/
|
||||||
disconnect () {
|
disconnect () {
|
||||||
|
if (!this._connected) return;
|
||||||
|
|
||||||
this._ws.close();
|
this._ws.close();
|
||||||
|
this._connected = false;
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,7 +106,7 @@ class BLE extends JSONRPCWebSocket {
|
||||||
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
||||||
return this.sendRemoteRequest('startNotifications', params)
|
return this.sendRemoteRequest('startNotifications', params)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendDisconnectError(e);
|
this.handleDisconnectError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +126,12 @@ class BLE extends JSONRPCWebSocket {
|
||||||
if (optStartNotifications) {
|
if (optStartNotifications) {
|
||||||
params.startNotifications = true;
|
params.startNotifications = true;
|
||||||
}
|
}
|
||||||
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
if (onCharacteristicChanged) {
|
||||||
|
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
||||||
|
}
|
||||||
return this.sendRemoteRequest('read', params)
|
return this.sendRemoteRequest('read', params)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendDisconnectError(e);
|
this.handleDisconnectError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +154,7 @@ class BLE extends JSONRPCWebSocket {
|
||||||
}
|
}
|
||||||
return this.sendRemoteRequest('write', params)
|
return this.sendRemoteRequest('write', params)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendDisconnectError(e);
|
this.handleDisconnectError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,14 +177,45 @@ class BLE extends JSONRPCWebSocket {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'characteristicDidChange':
|
case 'characteristicDidChange':
|
||||||
this._characteristicDidChangeCallback(params.message);
|
if (this._characteristicDidChangeCallback) {
|
||||||
|
this._characteristicDidChangeCallback(params.message);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'ping':
|
case 'ping':
|
||||||
return 42;
|
return 42;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendRequestError (/* e */) {
|
/**
|
||||||
|
* Handle an error resulting from losing connection to a peripheral.
|
||||||
|
*
|
||||||
|
* This could be due to:
|
||||||
|
* - battery depletion
|
||||||
|
* - going out of bluetooth range
|
||||||
|
* - being powered down
|
||||||
|
*
|
||||||
|
* Disconnect the socket, and if the extension using this socket has a
|
||||||
|
* disconnect callback, call it. Finally, emit an error to the runtime.
|
||||||
|
*/
|
||||||
|
handleDisconnectError (/* e */) {
|
||||||
|
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
||||||
|
|
||||||
|
if (!this._connected) return;
|
||||||
|
|
||||||
|
// TODO: Fix branching by splitting up cleanup/disconnect in extension
|
||||||
|
if (this._disconnectCallback) {
|
||||||
|
this._disconnectCallback(); // must call disconnect()
|
||||||
|
} else {
|
||||||
|
this.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
|
||||||
|
message: `Scratch lost connection to`,
|
||||||
|
extensionId: this._extensionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleRequestError (/* e */) {
|
||||||
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
||||||
|
|
||||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
||||||
|
@ -184,20 +224,7 @@ class BLE extends JSONRPCWebSocket {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendDisconnectError (/* e */) {
|
_handleDiscoverTimeout () {
|
||||||
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
|
||||||
|
|
||||||
if (!this._connected) return;
|
|
||||||
|
|
||||||
this._connected = false;
|
|
||||||
|
|
||||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECT_ERROR, {
|
|
||||||
message: `Scratch lost connection to`,
|
|
||||||
extensionId: this._extensionId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendDiscoverTimeout () {
|
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
69
src/io/bt.js
69
src/io/bt.js
|
@ -11,24 +11,26 @@ class BT extends JSONRPCWebSocket {
|
||||||
* @param {string} extensionId - the id of the extension using this socket.
|
* @param {string} extensionId - the id of the extension using this socket.
|
||||||
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
||||||
* @param {object} connectCallback - a callback for connection.
|
* @param {object} connectCallback - a callback for connection.
|
||||||
|
* @param {object} disconnectCallback - a callback for disconnection.
|
||||||
* @param {object} messageCallback - a callback for message sending.
|
* @param {object} messageCallback - a callback for message sending.
|
||||||
*/
|
*/
|
||||||
constructor (runtime, extensionId, peripheralOptions, connectCallback, messageCallback) {
|
constructor (runtime, extensionId, peripheralOptions, connectCallback, disconnectCallback = null, messageCallback) {
|
||||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
const ws = new WebSocket(ScratchLinkWebSocket);
|
||||||
super(ws);
|
super(ws);
|
||||||
|
|
||||||
this._ws = ws;
|
this._ws = ws;
|
||||||
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
this._ws.onopen = this.requestPeripheral.bind(this); // only call request peripheral after socket opens
|
||||||
this._ws.onerror = this._sendRequestError.bind(this, 'ws onerror');
|
this._ws.onerror = this._handleRequestError.bind(this, 'ws onerror');
|
||||||
this._ws.onclose = this._sendDisconnectError.bind(this, 'ws onclose');
|
this._ws.onclose = this.handleDisconnectError.bind(this, 'ws onclose');
|
||||||
|
|
||||||
this._availablePeripherals = {};
|
this._availablePeripherals = {};
|
||||||
this._connectCallback = connectCallback;
|
this._connectCallback = connectCallback;
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
this._characteristicDidChangeCallback = null;
|
this._characteristicDidChangeCallback = null;
|
||||||
|
this._disconnectCallback = disconnectCallback;
|
||||||
|
this._discoverTimeoutID = null;
|
||||||
this._extensionId = extensionId;
|
this._extensionId = extensionId;
|
||||||
this._peripheralOptions = peripheralOptions;
|
this._peripheralOptions = peripheralOptions;
|
||||||
this._discoverTimeoutID = null;
|
|
||||||
this._messageCallback = messageCallback;
|
this._messageCallback = messageCallback;
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
}
|
}
|
||||||
|
@ -43,10 +45,10 @@ class BT extends JSONRPCWebSocket {
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
this._discoverTimeoutID = window.setTimeout(this._sendDiscoverTimeout.bind(this), 15000);
|
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
||||||
this.sendRemoteRequest('discover', this._peripheralOptions)
|
this.sendRemoteRequest('discover', this._peripheralOptions)
|
||||||
.catch(
|
.catch(
|
||||||
e => this._sendRequestError(e)
|
e => this._handleRequestError(e)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: else?
|
// TODO: else?
|
||||||
|
@ -65,7 +67,7 @@ class BT extends JSONRPCWebSocket {
|
||||||
this._connectCallback();
|
this._connectCallback();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendRequestError(e);
|
this._handleRequestError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +75,15 @@ class BT extends JSONRPCWebSocket {
|
||||||
* Close the websocket.
|
* Close the websocket.
|
||||||
*/
|
*/
|
||||||
disconnect () {
|
disconnect () {
|
||||||
|
if (!this._connected) return;
|
||||||
|
|
||||||
this._ws.close();
|
this._ws.close();
|
||||||
|
this._connected = false;
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +96,7 @@ class BT extends JSONRPCWebSocket {
|
||||||
sendMessage (options) {
|
sendMessage (options) {
|
||||||
return this.sendRemoteRequest('send', options)
|
return this.sendRemoteRequest('send', options)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this._sendDisconnectError(e);
|
this.handleDisconnectError(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +127,36 @@ class BT extends JSONRPCWebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendRequestError (/* e */) {
|
/**
|
||||||
|
* Handle an error resulting from losing connection to a peripheral.
|
||||||
|
*
|
||||||
|
* This could be due to:
|
||||||
|
* - battery depletion
|
||||||
|
* - going out of bluetooth range
|
||||||
|
* - being powered down
|
||||||
|
*
|
||||||
|
* Disconnect the socket, and if the extension using this socket has a
|
||||||
|
* disconnect callback, call it. Finally, emit an error to the runtime.
|
||||||
|
*/
|
||||||
|
handleDisconnectError (/* e */) {
|
||||||
|
// log.error(`BT error: ${JSON.stringify(e)}`);
|
||||||
|
|
||||||
|
if (!this._connected) return;
|
||||||
|
|
||||||
|
// TODO: Fix branching by splitting up cleanup/disconnect in extension
|
||||||
|
if (this._disconnectCallback) {
|
||||||
|
this._disconnectCallback(); // must call disconnect()
|
||||||
|
} else {
|
||||||
|
this.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
|
||||||
|
message: `Scratch lost connection to`,
|
||||||
|
extensionId: this._extensionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleRequestError (/* e */) {
|
||||||
// log.error(`BT error: ${JSON.stringify(e)}`);
|
// log.error(`BT error: ${JSON.stringify(e)}`);
|
||||||
|
|
||||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
||||||
|
@ -129,20 +165,7 @@ class BT extends JSONRPCWebSocket {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendDisconnectError (/* e */) {
|
_handleDiscoverTimeout () {
|
||||||
// log.error(`BT error: ${JSON.stringify(e)}`);
|
|
||||||
|
|
||||||
if (!this._connected) return;
|
|
||||||
|
|
||||||
this._connected = false;
|
|
||||||
|
|
||||||
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECT_ERROR, {
|
|
||||||
message: `Scratch lost connection to`,
|
|
||||||
extensionId: this._extensionId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendDiscoverTimeout () {
|
|
||||||
if (this._discoverTimeoutID) {
|
if (this._discoverTimeoutID) {
|
||||||
window.clearTimeout(this._discoverTimeoutID);
|
window.clearTimeout(this._discoverTimeoutID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,8 +124,11 @@ class VirtualMachine extends EventEmitter {
|
||||||
this.runtime.on(Runtime.PERIPHERAL_REQUEST_ERROR, () =>
|
this.runtime.on(Runtime.PERIPHERAL_REQUEST_ERROR, () =>
|
||||||
this.emit(Runtime.PERIPHERAL_REQUEST_ERROR)
|
this.emit(Runtime.PERIPHERAL_REQUEST_ERROR)
|
||||||
);
|
);
|
||||||
this.runtime.on(Runtime.PERIPHERAL_DISCONNECT_ERROR, data =>
|
this.runtime.on(Runtime.PERIPHERAL_DISCONNECTED, () =>
|
||||||
this.emit(Runtime.PERIPHERAL_DISCONNECT_ERROR, data)
|
this.emit(Runtime.PERIPHERAL_DISCONNECTED)
|
||||||
|
);
|
||||||
|
this.runtime.on(Runtime.PERIPHERAL_CONNECTION_LOST_ERROR, data =>
|
||||||
|
this.emit(Runtime.PERIPHERAL_CONNECTION_LOST_ERROR, data)
|
||||||
);
|
);
|
||||||
this.runtime.on(Runtime.PERIPHERAL_SCAN_TIMEOUT, () =>
|
this.runtime.on(Runtime.PERIPHERAL_SCAN_TIMEOUT, () =>
|
||||||
this.emit(Runtime.PERIPHERAL_SCAN_TIMEOUT)
|
this.emit(Runtime.PERIPHERAL_SCAN_TIMEOUT)
|
||||||
|
|
Loading…
Reference in a new issue