soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
6.82 kB
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;