mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Merge pull request #1285 from LLK/feature/device-connection
Device connection update for micro:bit and EV3 extensions
This commit is contained in:
commit
55944bc4b6
13 changed files with 1363 additions and 229 deletions
34
package-lock.json
generated
34
package-lock.json
generated
|
@ -3799,9 +3799,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "5.0.1",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.1.0.tgz",
|
||||||
"integrity": "sha512-D5nG2rErquLUstgUaxJlWB5+gu+U/3VDY0fk/Iuq8y9CUFy/7Y6oF4N2cR1tV8knzQvciIbfqfohd359xTLIKQ==",
|
"integrity": "sha512-DyH6JsoA1KzA5+OSWFjg56DFJT+sDLO0yokaPZ9qY0UEmYrPA1gEX/G1MnVkmRDsksG4H1foIVz2ZXXM3hHYvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.5.0",
|
"ajv": "^6.5.0",
|
||||||
|
@ -3811,6 +3811,7 @@
|
||||||
"debug": "^3.1.0",
|
"debug": "^3.1.0",
|
||||||
"doctrine": "^2.1.0",
|
"doctrine": "^2.1.0",
|
||||||
"eslint-scope": "^4.0.0",
|
"eslint-scope": "^4.0.0",
|
||||||
|
"eslint-utils": "^1.3.1",
|
||||||
"eslint-visitor-keys": "^1.0.0",
|
"eslint-visitor-keys": "^1.0.0",
|
||||||
"espree": "^4.0.0",
|
"espree": "^4.0.0",
|
||||||
"esquery": "^1.0.1",
|
"esquery": "^1.0.1",
|
||||||
|
@ -3818,7 +3819,7 @@
|
||||||
"file-entry-cache": "^2.0.0",
|
"file-entry-cache": "^2.0.0",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
"globals": "^11.5.0",
|
"globals": "^11.7.0",
|
||||||
"ignore": "^3.3.3",
|
"ignore": "^3.3.3",
|
||||||
"imurmurhash": "^0.1.4",
|
"imurmurhash": "^0.1.4",
|
||||||
"inquirer": "^5.2.0",
|
"inquirer": "^5.2.0",
|
||||||
|
@ -4138,6 +4139,12 @@
|
||||||
"estraverse": "^4.1.1"
|
"estraverse": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-utils": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
|
||||||
|
@ -13129,9 +13136,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scratch-blocks": {
|
"scratch-blocks": {
|
||||||
"version": "0.1.0-prerelease.1530135682",
|
"version": "0.1.0-prerelease.1531144787",
|
||||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.1530135682.tgz",
|
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.1531144787.tgz",
|
||||||
"integrity": "sha512-JadXtaqDLmebmitbk5s5RVtuApvWWaj+ECSIfXY01dY9X3JDZAopMDWZuP/8uNWg7S1xMBrIaD8Rv0r83lGdmA==",
|
"integrity": "sha512-fSS/C6pBh5kNkimFfHDO+XG8Ny6lzHePb5eeqxDkel2u5agJgkAbIgBgJnsqwJ+iJmKhmJgW29K/iM442MDYXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"exports-loader": "0.6.3",
|
"exports-loader": "0.6.3",
|
||||||
|
@ -16054,6 +16061,11 @@
|
||||||
"untildify": "^3.0.2"
|
"untildify": "^3.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||||
|
},
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
@ -16108,6 +16120,14 @@
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
||||||
|
|
|
@ -254,6 +254,8 @@ class Runtime extends EventEmitter {
|
||||||
video: new Video(this)
|
video: new Video(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.extensionDevices = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runtime profiler that records timed events for later playback to
|
* A runtime profiler that records timed events for later playback to
|
||||||
* diagnose Scratch performance.
|
* diagnose Scratch performance.
|
||||||
|
@ -394,6 +396,30 @@ class Runtime extends EventEmitter {
|
||||||
return 'EXTENSION_ADDED';
|
return 'EXTENSION_ADDED';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event name for updating the available set of peripheral devices.
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
static get PERIPHERAL_LIST_UPDATE () {
|
||||||
|
return 'PERIPHERAL_LIST_UPDATE';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event name for reporting that a peripheral has connected.
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
static get PERIPHERAL_CONNECTED () {
|
||||||
|
return 'PERIPHERAL_CONNECTED';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event name for reporting that a peripheral has encountered an error.
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
static get PERIPHERAL_ERROR () {
|
||||||
|
return 'PERIPHERAL_ERROR';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for reporting that blocksInfo was updated.
|
* Event name for reporting that blocksInfo was updated.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
|
@ -867,6 +893,36 @@ class Runtime extends EventEmitter {
|
||||||
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerExtensionDevice (extensionId, device) {
|
||||||
|
this.extensionDevices[extensionId] = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
startDeviceScan (extensionId) {
|
||||||
|
if (this.extensionDevices[extensionId]) {
|
||||||
|
this.extensionDevices[extensionId].startDeviceScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToPeripheral (extensionId, peripheralId) {
|
||||||
|
if (this.extensionDevices[extensionId]) {
|
||||||
|
this.extensionDevices[extensionId].connectDevice(peripheralId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectExtensionSession (extensionId) {
|
||||||
|
if (this.extensionDevices[extensionId]) {
|
||||||
|
this.extensionDevices[extensionId].disconnectSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeripheralIsConnected (extensionId) {
|
||||||
|
let isConnected = false;
|
||||||
|
if (this.extensionDevices[extensionId]) {
|
||||||
|
isConnected = this.extensionDevices[extensionId].getPeripheralIsConnected();
|
||||||
|
}
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the function associated with the given opcode.
|
* Retrieve the function associated with the given opcode.
|
||||||
* @param {!string} opcode The opcode to look up.
|
* @param {!string} opcode The opcode to look up.
|
||||||
|
|
|
@ -15,6 +15,7 @@ const Scratch3SpeakBlocks = require('../extensions/scratch3_speak');
|
||||||
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
|
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
|
||||||
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
|
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
|
||||||
const Scratch3SpeechBlocks = require('../extensions/scratch3_speech');
|
const Scratch3SpeechBlocks = require('../extensions/scratch3_speech');
|
||||||
|
const Scratch3Ev3Blocks = require('../extensions/scratch3_ev3');
|
||||||
|
|
||||||
const builtinExtensions = {
|
const builtinExtensions = {
|
||||||
pen: Scratch3PenBlocks,
|
pen: Scratch3PenBlocks,
|
||||||
|
@ -24,7 +25,8 @@ const builtinExtensions = {
|
||||||
speak: Scratch3SpeakBlocks,
|
speak: Scratch3SpeakBlocks,
|
||||||
translate: Scratch3TranslateBlocks,
|
translate: Scratch3TranslateBlocks,
|
||||||
videoSensing: Scratch3VideoSensingBlocks,
|
videoSensing: Scratch3VideoSensingBlocks,
|
||||||
speech: Scratch3SpeechBlocks
|
speech: Scratch3SpeechBlocks,
|
||||||
|
ev3: Scratch3Ev3Blocks
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
919
src/extensions/scratch3_ev3/index.js
Normal file
919
src/extensions/scratch3_ev3/index.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
const ArgumentType = require('../../extension-support/argument-type');
|
const ArgumentType = require('../../extension-support/argument-type');
|
||||||
const BlockType = require('../../extension-support/block-type');
|
const BlockType = require('../../extension-support/block-type');
|
||||||
const log = require('../../util/log');
|
const log = require('../../util/log');
|
||||||
const ScratchBLE = require('../../io/scratchBLE');
|
const BLESession = require('../../io/bleSession');
|
||||||
const Base64Util = require('../../util/base64-util');
|
const Base64Util = require('../../util/base64-util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,8 +50,9 @@ class MicroBit {
|
||||||
/**
|
/**
|
||||||
* Construct a MicroBit communication object.
|
* Construct a MicroBit communication object.
|
||||||
* @param {Runtime} runtime - the Scratch 3.0 runtime
|
* @param {Runtime} runtime - the Scratch 3.0 runtime
|
||||||
|
* @param {string} extensionId - the id of the extension
|
||||||
*/
|
*/
|
||||||
constructor (runtime) {
|
constructor (runtime, extensionId) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Scratch 3.0 runtime used to trigger the green flag button.
|
* The Scratch 3.0 runtime used to trigger the green flag button.
|
||||||
|
@ -61,11 +62,12 @@ class MicroBit {
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ScratchBLE connection session for reading/writing device data.
|
* The BluetoothLowEnergy connection session for reading/writing device data.
|
||||||
* @type {ScratchBLE}
|
* @type {BLESession}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._ble = new ScratchBLE();
|
this._ble = null;
|
||||||
|
this._runtime.registerExtensionDevice(extensionId, this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The most recently received value for each sensor.
|
* The most recently received value for each sensor.
|
||||||
|
@ -102,33 +104,60 @@ class MicroBit {
|
||||||
timeout: false
|
timeout: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Temporary until the gui requests a device connection
|
// TODO: keep here?
|
||||||
this._ble.waitForSocket()
|
/**
|
||||||
// TODO: remove pinging once no longer needed
|
* Called by the runtime when user wants to scan for a device.
|
||||||
.then(() => this._ble.sendRemoteRequest('pingMe'))
|
*/
|
||||||
.then(() => this._onBLEReady());
|
startDeviceScan () {
|
||||||
|
log.info('making a new BLE session');
|
||||||
|
this._ble = new BLESession(this._runtime, {
|
||||||
|
filters: [
|
||||||
|
{services: [BLEUUID.service]}
|
||||||
|
]
|
||||||
|
}, this._onSessionConnect.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add ScratchBLE 'disconnect' handling
|
// 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 () {
|
||||||
|
this._ble.disconnectSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeripheralIsConnected () {
|
||||||
|
let connected = false;
|
||||||
|
if (this._ble) {
|
||||||
|
connected = this._ble.getPeripheralIsConnected();
|
||||||
|
}
|
||||||
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} text - the text to display.
|
* @param {string} text - the text to display.
|
||||||
|
* @return {Promise} - a Promise that resolves when writing to device.
|
||||||
*/
|
*/
|
||||||
displayText (text) {
|
displayText (text) {
|
||||||
const output = new Uint8Array(text.length);
|
const output = new Uint8Array(text.length);
|
||||||
for (let i = 0; i < text.length; i++) {
|
for (let i = 0; i < text.length; i++) {
|
||||||
output[i] = text.charCodeAt(i);
|
output[i] = text.charCodeAt(i);
|
||||||
}
|
}
|
||||||
this._writeBLE(BLECommand.CMD_DISPLAY_TEXT, output);
|
return this._writeSessionData(BLECommand.CMD_DISPLAY_TEXT, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} matrix - the matrix to display.
|
* @param {Uint8Array} matrix - the matrix to display.
|
||||||
|
* @return {Promise} - a Promise that resolves when writing to device.
|
||||||
*/
|
*/
|
||||||
displayMatrix (matrix) {
|
displayMatrix (matrix) {
|
||||||
this._writeBLE(BLECommand.CMD_DISPLAY_LED, matrix);
|
return this._writeSessionData(BLECommand.CMD_DISPLAY_LED, matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,38 +210,20 @@ class MicroBit {
|
||||||
return this._sensors.touchPins[pin];
|
return this._sensors.touchPins[pin];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests connection to a device when BLE session is ready.
|
|
||||||
*/
|
|
||||||
_onBLEReady () {
|
|
||||||
this._ble.requestDevice({
|
|
||||||
filters: [
|
|
||||||
{services: [BLEUUID.service]}
|
|
||||||
]
|
|
||||||
}, this._onBLEConnect.bind(this), this._onBLEError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts reading data from device after BLE has connected to it.
|
* Starts reading data from device after BLE has connected to it.
|
||||||
*/
|
*/
|
||||||
_onBLEConnect () {
|
_onSessionConnect () {
|
||||||
const callback = this._processBLEData.bind(this);
|
const callback = this._processSessionData.bind(this);
|
||||||
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, callback);
|
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} e - Error from BLE session.
|
|
||||||
*/
|
|
||||||
_onBLEError (e) {
|
|
||||||
log.error(`BLE error: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the sensor data from the incoming BLE characteristic.
|
* Process the sensor data from the incoming BLE characteristic.
|
||||||
* @param {object} base64 - the incoming BLE data.
|
* @param {object} base64 - the incoming BLE data.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_processBLEData (base64) {
|
_processSessionData (base64) {
|
||||||
const data = Base64Util.base64ToUint8Array(base64);
|
const data = Base64Util.base64ToUint8Array(base64);
|
||||||
|
|
||||||
this._sensors.tiltX = data[1] | (data[0] << 8);
|
this._sensors.tiltX = data[1] | (data[0] << 8);
|
||||||
|
@ -234,16 +245,17 @@ class MicroBit {
|
||||||
* Write a message to the device BLE session.
|
* Write a message to the device BLE session.
|
||||||
* @param {number} command - the BLE command hex.
|
* @param {number} command - the BLE command hex.
|
||||||
* @param {Uint8Array} message - the message to write.
|
* @param {Uint8Array} message - the message to write.
|
||||||
|
* @return {Promise} - a Promise that resolves when writing to device.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_writeBLE (command, message) {
|
_writeSessionData (command, message) {
|
||||||
const output = new Uint8Array(message.length + 1);
|
const output = new Uint8Array(message.length + 1);
|
||||||
output[0] = command; // attach command to beginning of message
|
output[0] = command; // attach command to beginning of message
|
||||||
for (let i = 0; i < message.length; i++) {
|
for (let i = 0; i < message.length; i++) {
|
||||||
output[i + 1] = message[i];
|
output[i + 1] = message[i];
|
||||||
}
|
}
|
||||||
const b64enc = Base64Util.uint8ArrayToBase64(output);
|
const data = Base64Util.uint8ArrayToBase64(output);
|
||||||
this._ble.write(BLEUUID.service, BLEUUID.txChar, b64enc, 'base64');
|
return this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +330,7 @@ class Scratch3MicroBitBlocks {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
|
|
||||||
// Create a new MicroBit device instance
|
// Create a new MicroBit device instance
|
||||||
this._device = new MicroBit(this.runtime);
|
this._device = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -516,17 +528,18 @@ class Scratch3MicroBitBlocks {
|
||||||
/**
|
/**
|
||||||
* Display text on the 5x5 LED matrix.
|
* Display text on the 5x5 LED matrix.
|
||||||
* @param {object} args - the block's arguments.
|
* @param {object} args - the block's arguments.
|
||||||
|
* @return {Promise} - a Promise that resolves when writing to device.
|
||||||
* Note the limit is 19 characters
|
* Note the limit is 19 characters
|
||||||
*/
|
*/
|
||||||
displayText (args) {
|
displayText (args) {
|
||||||
const text = String(args.TEXT).substring(0, 19);
|
const text = String(args.TEXT).substring(0, 19);
|
||||||
this._device.displayText(text);
|
return this._device.displayText(text);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a predefined symbol on the 5x5 LED matrix.
|
* Display a predefined symbol on the 5x5 LED matrix.
|
||||||
* @param {object} args - the block's arguments.
|
* @param {object} args - the block's arguments.
|
||||||
|
* @return {Promise} - a Promise that resolves when writing to device.
|
||||||
*/
|
*/
|
||||||
displaySymbol (args) {
|
displaySymbol (args) {
|
||||||
const hex = symbols2hex[args.SYMBOL];
|
const hex = symbols2hex[args.SYMBOL];
|
||||||
|
@ -536,8 +549,7 @@ class Scratch3MicroBitBlocks {
|
||||||
this._device.ledMatrixState[2] = (hex >> 10) & 0x1F;
|
this._device.ledMatrixState[2] = (hex >> 10) & 0x1F;
|
||||||
this._device.ledMatrixState[3] = (hex >> 5) & 0x1F;
|
this._device.ledMatrixState[3] = (hex >> 5) & 0x1F;
|
||||||
this._device.ledMatrixState[4] = hex & 0x1F;
|
this._device.ledMatrixState[4] = hex & 0x1F;
|
||||||
this._device.displayMatrix(this._device.ledMatrixState);
|
return this._device.displayMatrix(this._device.ledMatrixState);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
146
src/io/bleSession.js
Normal file
146
src/io/bleSession.js
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
||||||
|
const log = require('../util/log');
|
||||||
|
const ScratchLinkWebSocket = 'ws://localhost:20110/scratch/ble';
|
||||||
|
|
||||||
|
class BLESession extends JSONRPCWebSocket {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A BLE device session object. It handles connecting, over web sockets, to
|
||||||
|
* BLE devices, 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} connectCallback - a callback for connection.
|
||||||
|
*/
|
||||||
|
constructor (runtime, deviceOptions, 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.onerror = this._sendError.bind(this, 'ws onerror');
|
||||||
|
this._ws.onclose = this._sendError.bind(this, 'ws onclose');
|
||||||
|
|
||||||
|
this._availablePeripherals = {};
|
||||||
|
this._connectCallback = connectCallback;
|
||||||
|
this._characteristicDidChangeCallback = null;
|
||||||
|
this._deviceOptions = deviceOptions;
|
||||||
|
this._runtime = runtime;
|
||||||
|
|
||||||
|
this._connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request connection to the device.
|
||||||
|
* If the web socket is not yet open, request when the socket promise resolves.
|
||||||
|
*/
|
||||||
|
requestDevice () {
|
||||||
|
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
||||||
|
// TODO: start a 'discover' timeout
|
||||||
|
this.sendRemoteRequest('discover', this._deviceOptions)
|
||||||
|
.catch(e => this._sendError(e)); // never reached?
|
||||||
|
}
|
||||||
|
// TODO: else?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
connectDevice (id) {
|
||||||
|
this.sendRemoteRequest('connect', {peripheralId: id})
|
||||||
|
.then(() => {
|
||||||
|
log.info('should have connected');
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
||||||
|
this._connected = true;
|
||||||
|
this._connectCallback();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this._sendError(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the websocket.
|
||||||
|
*/
|
||||||
|
disconnectSession () {
|
||||||
|
this._ws.close();
|
||||||
|
this._connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {bool} whether the peripheral is connected.
|
||||||
|
*/
|
||||||
|
getPeripheralIsConnected () {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
// TODO: cancel a discover timeout if one is active
|
||||||
|
break;
|
||||||
|
case 'characteristicDidChange':
|
||||||
|
this._characteristicDidChangeCallback(params.message);
|
||||||
|
break;
|
||||||
|
case 'ping':
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start reading from the specified ble service.
|
||||||
|
* @param {number} serviceId - the ble service to read.
|
||||||
|
* @param {number} characteristicId - the ble characteristic to read.
|
||||||
|
* @param {boolean} optStartNotifications - whether to start receiving characteristic change notifications.
|
||||||
|
* @param {object} onCharacteristicChanged - callback for characteristic change notifications.
|
||||||
|
* @return {Promise} - a promise from the remote read request.
|
||||||
|
*/
|
||||||
|
read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged) {
|
||||||
|
const params = {
|
||||||
|
serviceId,
|
||||||
|
characteristicId
|
||||||
|
};
|
||||||
|
if (optStartNotifications) {
|
||||||
|
params.startNotifications = true;
|
||||||
|
}
|
||||||
|
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
||||||
|
return this.sendRemoteRequest('read', params);
|
||||||
|
// TODO: handle error here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to the specified ble service.
|
||||||
|
* @param {number} serviceId - the ble service to write.
|
||||||
|
* @param {number} characteristicId - the ble characteristic to write.
|
||||||
|
* @param {string} message - the message to send.
|
||||||
|
* @param {string} encoding - the message encoding type.
|
||||||
|
* @return {Promise} - a promise from the remote send request.
|
||||||
|
*/
|
||||||
|
write (serviceId, characteristicId, message, encoding = null) {
|
||||||
|
const params = {serviceId, characteristicId, message};
|
||||||
|
if (encoding) {
|
||||||
|
params.encoding = encoding;
|
||||||
|
}
|
||||||
|
return this.sendRemoteRequest('write', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendError (e) {
|
||||||
|
this._connected = false;
|
||||||
|
log.error(`BLESession error: ${JSON.stringify(e)}`);
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BLESession;
|
116
src/io/btSession.js
Normal file
116
src/io/btSession.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
||||||
|
const log = require('../util/log');
|
||||||
|
const ScratchLinkWebSocket = 'ws://localhost:20110/scratch/bt';
|
||||||
|
|
||||||
|
class BTSession extends JSONRPCWebSocket {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A BT device session object. It handles connecting, over web sockets, to
|
||||||
|
* BT devices, 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} connectCallback - a callback for connection.
|
||||||
|
* @param {object} messageCallback - a callback for message sending.
|
||||||
|
*/
|
||||||
|
constructor (runtime, deviceOptions, 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.onerror = this._sendError.bind(this, 'ws onerror');
|
||||||
|
this._ws.onclose = this._sendError.bind(this, 'ws onclose');
|
||||||
|
|
||||||
|
this._availablePeripherals = {};
|
||||||
|
this._connectCallback = connectCallback;
|
||||||
|
this._characteristicDidChangeCallback = null;
|
||||||
|
this._deviceOptions = deviceOptions;
|
||||||
|
this._messageCallback = messageCallback;
|
||||||
|
this._runtime = runtime;
|
||||||
|
|
||||||
|
this._connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request connection to the device.
|
||||||
|
* If the web socket is not yet open, request when the socket promise resolves.
|
||||||
|
*/
|
||||||
|
requestDevice () {
|
||||||
|
if (this._ws.readyState === 1) { // is this needed since it's only called on ws.onopen?
|
||||||
|
// TODO: start a 'discover' timeout
|
||||||
|
this.sendRemoteRequest('discover', this._deviceOptions)
|
||||||
|
.catch(e => this._sendError(e)); // never reached?
|
||||||
|
}
|
||||||
|
// TODO: else?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
connectDevice (id) {
|
||||||
|
this.sendRemoteRequest('connect', {peripheralId: id})
|
||||||
|
.then(() => {
|
||||||
|
log.info('should have connected');
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
||||||
|
this._connected = true;
|
||||||
|
this._connectCallback();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this._sendError(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the websocket.
|
||||||
|
*/
|
||||||
|
disconnectSession () {
|
||||||
|
this._ws.close();
|
||||||
|
this._connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {bool} whether the peripheral is connected.
|
||||||
|
*/
|
||||||
|
getPeripheralIsConnected () {
|
||||||
|
return this._connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sendMessage (options) {
|
||||||
|
return this.sendRemoteRequest('send', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
);
|
||||||
|
// TODO: cancel a discover timeout if one is active
|
||||||
|
break;
|
||||||
|
case 'didReceiveMessage':
|
||||||
|
this._messageCallback(params); // TODO: refine?
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 'nah';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendError (e) {
|
||||||
|
log.error(`BTSession error: ${JSON.stringify(e)}`);
|
||||||
|
this._runtime.emit(this._runtime.constructor.PERIPHERAL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BTSession;
|
|
@ -1,38 +0,0 @@
|
||||||
class PeripheralChooser {
|
|
||||||
|
|
||||||
get chosenPeripheralId () {
|
|
||||||
return this._chosenPeripheralId;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor () {
|
|
||||||
this._availablePeripherals = []; // TODO for use in gui?
|
|
||||||
this._chosenPeripheralId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches a GUI menu to choose a peripheral.
|
|
||||||
* @return {Promise} - chosen peripheral promise.
|
|
||||||
*/
|
|
||||||
choosePeripheral () {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// TODO: Temporary: should launch gui instead.
|
|
||||||
this._tempPeripheralChosenCallback = resolve;
|
|
||||||
this._tempPeripheralChosenReject = reject;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the peripheral ID to list of available peripherals.
|
|
||||||
* @param {number} peripheralId - the id to add.
|
|
||||||
*/
|
|
||||||
addPeripheral (peripheralId) {
|
|
||||||
this._availablePeripherals.push(peripheralId);
|
|
||||||
|
|
||||||
// TODO: Temporary: calls chosen callback on whatever peripherals are added.
|
|
||||||
this._chosenPeripheralId = this._availablePeripherals[0];
|
|
||||||
this._tempPeripheralChosenCallback(this._chosenPeripheralId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = PeripheralChooser;
|
|
|
@ -1,104 +0,0 @@
|
||||||
const JSONRPCWebSocket = require('../util/jsonrpc-web-socket');
|
|
||||||
const PeripheralChooser = require('./peripheralChooser');
|
|
||||||
|
|
||||||
const ScratchLinkWebSocket = 'ws://localhost:20110/scratch/ble';
|
|
||||||
|
|
||||||
class ScratchBLE extends JSONRPCWebSocket {
|
|
||||||
constructor () {
|
|
||||||
const ws = new WebSocket(ScratchLinkWebSocket);
|
|
||||||
|
|
||||||
super(ws);
|
|
||||||
|
|
||||||
this._ws = ws;
|
|
||||||
this.peripheralChooser = new PeripheralChooser(); // TODO: finalize gui connection
|
|
||||||
this._characteristicDidChange = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a promise for when the web socket opens.
|
|
||||||
* @return {Promise} - a promise when BLE socket is open.
|
|
||||||
*/
|
|
||||||
waitForSocket () {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this._ws.onopen = resolve;
|
|
||||||
this._ws.onerror = reject;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a device with the device options and optional gui options.
|
|
||||||
* @param {object} deviceOptions - list of device guiOptions.
|
|
||||||
* @param {object} onConnect - on connect callback.
|
|
||||||
* @param {object} onError - on error callbackk.
|
|
||||||
*/
|
|
||||||
requestDevice (deviceOptions, onConnect, onError) {
|
|
||||||
this.sendRemoteRequest('discover', deviceOptions)
|
|
||||||
.then(() => this.peripheralChooser.choosePeripheral()) // TODO: use gui options?
|
|
||||||
.then(id => this.sendRemoteRequest(
|
|
||||||
'connect',
|
|
||||||
{peripheralId: id}
|
|
||||||
))
|
|
||||||
.then(
|
|
||||||
onConnect,
|
|
||||||
onError
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.peripheralChooser.addPeripheral(params.peripheralId);
|
|
||||||
break;
|
|
||||||
case 'characteristicDidChange':
|
|
||||||
this._characteristicDidChange(params.message);
|
|
||||||
break;
|
|
||||||
case 'ping':
|
|
||||||
return 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start reading from the specified ble service.
|
|
||||||
* @param {number} serviceId - the ble service to read.
|
|
||||||
* @param {number} characteristicId - the ble characteristic to read.
|
|
||||||
* @param {boolean} optStartNotifications - whether to start receiving characteristic change notifications.
|
|
||||||
* @param {object} onCharacteristicChanged - callback for characteristic change notifications.
|
|
||||||
* @return {Promise} - a promise from the remote read request.
|
|
||||||
*/
|
|
||||||
read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged) {
|
|
||||||
const params = {
|
|
||||||
serviceId,
|
|
||||||
characteristicId
|
|
||||||
};
|
|
||||||
if (optStartNotifications) {
|
|
||||||
params.startNotifications = true;
|
|
||||||
}
|
|
||||||
this._characteristicDidChange = onCharacteristicChanged;
|
|
||||||
return this.sendRemoteRequest('read', params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write data to the specified ble service.
|
|
||||||
* @param {number} serviceId - the ble service to write.
|
|
||||||
* @param {number} characteristicId - the ble characteristic to write.
|
|
||||||
* @param {string} message - the message to send.
|
|
||||||
* @param {string} encoding - the message encoding type.
|
|
||||||
* @return {Promise} - a promise from the remote send request.
|
|
||||||
*/
|
|
||||||
write (serviceId, characteristicId, message, encoding = null) {
|
|
||||||
const params = {serviceId, characteristicId, message};
|
|
||||||
if (encoding) {
|
|
||||||
params.encoding = encoding;
|
|
||||||
}
|
|
||||||
return this.sendRemoteRequest('write', params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ScratchBLE;
|
|
|
@ -1,37 +0,0 @@
|
||||||
const JSONRPCWebSocket = require('../util/jsonrpc');
|
|
||||||
|
|
||||||
const ScratchLinkWebSocket = 'ws://localhost:20110/scratch/bt';
|
|
||||||
|
|
||||||
class ScratchBT extends JSONRPCWebSocket {
|
|
||||||
constructor () {
|
|
||||||
super(new WebSocket(ScratchLinkWebSocket));
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDevice (options) {
|
|
||||||
return this.sendRemoteRequest('discover', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectDevice (options) {
|
|
||||||
return this.sendRemoteRequest('connect', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage (options) {
|
|
||||||
return this.sendRemoteRequest('send', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
didReceiveCall (method /* , params */) {
|
|
||||||
// TODO: Add peripheral 'undiscover' handling
|
|
||||||
switch (method) {
|
|
||||||
case 'didDiscoverPeripheral':
|
|
||||||
// TODO: do something on peripheral discovered
|
|
||||||
break;
|
|
||||||
case 'didReceiveMessage':
|
|
||||||
// TODO: do something on received message
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return 'nah';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ScratchBT;
|
|
|
@ -28,6 +28,21 @@ class Base64Util {
|
||||||
return base64;
|
return base64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array buffer to a base64 encoded string.
|
||||||
|
* @param {array} buffer - an array buffer to convert.
|
||||||
|
* @return {string} - the base64 encoded string.
|
||||||
|
*/
|
||||||
|
static arrayBufferToBase64 (buffer) {
|
||||||
|
let binary = '';
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
const len = bytes.byteLength;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[ i ]);
|
||||||
|
}
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Base64Util;
|
module.exports = Base64Util;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const JSONRPC = require('./jsonrpc');
|
const JSONRPC = require('./jsonrpc');
|
||||||
|
// const log = require('../util/log');
|
||||||
|
|
||||||
class JSONRPCWebSocket extends JSONRPC {
|
class JSONRPCWebSocket extends JSONRPC {
|
||||||
constructor (webSocket) {
|
constructor (webSocket) {
|
||||||
|
|
|
@ -106,6 +106,16 @@ class VirtualMachine extends EventEmitter {
|
||||||
this.emit(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);
|
||||||
|
});
|
||||||
|
this.runtime.on(Runtime.PERIPHERAL_CONNECTED, () =>
|
||||||
|
this.emit(Runtime.PERIPHERAL_CONNECTED)
|
||||||
|
);
|
||||||
|
this.runtime.on(Runtime.PERIPHERAL_ERROR, () =>
|
||||||
|
this.emit(Runtime.PERIPHERAL_ERROR)
|
||||||
|
);
|
||||||
|
|
||||||
this.extensionManager = new ExtensionManager(this.runtime);
|
this.extensionManager = new ExtensionManager(this.runtime);
|
||||||
|
|
||||||
this.blockListener = this.blockListener.bind(this);
|
this.blockListener = this.blockListener.bind(this);
|
||||||
|
@ -195,6 +205,22 @@ class VirtualMachine extends EventEmitter {
|
||||||
this.runtime.ioDevices.video.setProvider(videoProvider);
|
this.runtime.ioDevices.video.setProvider(videoProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startDeviceScan (extensionId) {
|
||||||
|
this.runtime.startDeviceScan(extensionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToPeripheral (extensionId, peripheralId) {
|
||||||
|
this.runtime.connectToPeripheral(extensionId, peripheralId);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectExtensionSession (extensionId) {
|
||||||
|
this.runtime.disconnectExtensionSession(extensionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeripheralIsConnected (extensionId) {
|
||||||
|
return this.runtime.getPeripheralIsConnected(extensionId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a Scratch project from a .sb, .sb2, .sb3 or json string.
|
* Load a Scratch project from a .sb, .sb2, .sb3 or json string.
|
||||||
* @param {string | object} input A json string, object, or ArrayBuffer representing the project to load.
|
* @param {string | object} input A json string, object, or ArrayBuffer representing the project to load.
|
||||||
|
|
Loading…
Reference in a new issue