Spaces:
Build error
Build error
const JSONRPC = require('../util/jsonrpc'); | |
class BT extends JSONRPC { | |
/** | |
* 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 {string} extensionId - the id of the extension using this socket. | |
* @param {object} peripheralOptions - the list of options for peripheral discovery. | |
* @param {object} connectCallback - a callback for connection. | |
* @param {object} resetCallback - a callback for resetting extension state. | |
* @param {object} messageCallback - a callback for message sending. | |
*/ | |
constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null, messageCallback) { | |
super(); | |
this._socket = runtime.getScratchLinkSocket('BT'); | |
this._socket.setOnOpen(this.requestPeripheral.bind(this)); | |
this._socket.setOnError(this._handleRequestError.bind(this)); | |
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._connectCallback = connectCallback; | |
this._connected = false; | |
this._characteristicDidChangeCallback = null; | |
this._resetCallback = resetCallback; | |
this._discoverTimeoutID = null; | |
this._extensionId = extensionId; | |
this._peripheralOptions = peripheralOptions; | |
this._messageCallback = messageCallback; | |
this._runtime = runtime; | |
this._socket.open(); | |
} | |
/** | |
* Request connection to the peripheral. | |
* If the web socket is not yet open, request when the socket promise resolves. | |
*/ | |
requestPeripheral () { | |
this._availablePeripherals = {}; | |
if (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) | |
); | |
} | |
/** | |
* Try connecting to the input peripheral id, and then call the connect | |
* callback if connection is successful. | |
* @param {number} id - the id of the peripheral to connect to | |
* @param {string} pin - an optional pin for pairing | |
*/ | |
connectPeripheral (id, pin = null) { | |
const params = {peripheralId: id}; | |
if (pin) { | |
params.pin = pin; | |
} | |
this.sendRemoteRequest('connect', params) | |
.then(() => { | |
this._connected = true; | |
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED); | |
this._connectCallback(); | |
}) | |
.catch(e => { | |
this._handleRequestError(e); | |
}); | |
} | |
/** | |
* Close the websocket. | |
*/ | |
disconnect () { | |
if (this._connected) { | |
this._connected = false; | |
} | |
if (this._socket.isOpen()) { | |
this._socket.close(); | |
} | |
if (this._discoverTimeoutID) { | |
window.clearTimeout(this._discoverTimeoutID); | |
} | |
// Sets connection status icon to orange | |
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED); | |
} | |
/** | |
* @return {bool} whether the peripheral is connected. | |
*/ | |
isConnected () { | |
return this._connected; | |
} | |
sendMessage (options) { | |
return this.sendRemoteRequest('send', options) | |
.catch(e => { | |
this.handleDisconnectError(e); | |
}); | |
} | |
/** | |
* 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) { | |
// TODO: Add peripheral 'undiscover' handling | |
switch (method) { | |
case 'didDiscoverPeripheral': | |
this._availablePeripherals[params.peripheralId] = params; | |
this._runtime.emit( | |
this._runtime.constructor.PERIPHERAL_LIST_UPDATE, | |
this._availablePeripherals | |
); | |
if (this._discoverTimeoutID) { | |
window.clearTimeout(this._discoverTimeoutID); | |
} | |
break; | |
case 'userDidPickPeripheral': | |
this._availablePeripherals[params.peripheralId] = params; | |
this._runtime.emit( | |
this._runtime.constructor.USER_PICKED_PERIPHERAL, | |
this._availablePeripherals | |
); | |
if (this._discoverTimeoutID) { | |
window.clearTimeout(this._discoverTimeoutID); | |
} | |
break; | |
case 'userDidNotPickPeripheral': | |
this._runtime.emit( | |
this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT | |
); | |
if (this._discoverTimeoutID) { | |
window.clearTimeout(this._discoverTimeoutID); | |
} | |
break; | |
case 'didReceiveMessage': | |
this._messageCallback(params); // TODO: refine? | |
break; | |
default: | |
return 'nah'; | |
} | |
} | |
/** | |
* 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 | |
* reset callback, call it. Finally, emit an error to the runtime. | |
*/ | |
handleDisconnectError (/* e */) { | |
// log.error(`BT error: ${JSON.stringify(e)}`); | |
if (!this._connected) return; | |
this.disconnect(); | |
if (this._resetCallback) { | |
this._resetCallback(); | |
} | |
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)}`); | |
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, { | |
message: `Scratch lost connection to`, | |
extensionId: this._extensionId | |
}); | |
} | |
_handleDiscoverTimeout () { | |
if (this._discoverTimeoutID) { | |
window.clearTimeout(this._discoverTimeoutID); | |
} | |
this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT); | |
} | |
} | |
module.exports = BT; | |