From 8d9207ef1b1b17976ada535390e6d49b3e99af4b Mon Sep 17 00:00:00 2001 From: bene Date: Fri, 12 Aug 2022 17:52:44 +0200 Subject: [PATCH 1/4] Basic implementation to support pybricks hubs This commit adds a new hub type: PybricksHub. To use the new hub, you need to have Pybricks firmware installed on your hub, see https://pybricks.com for further instructions. For now, the hub is only available in the Node.js, not in the Web- Bluetooth implementation. Properties like firmware version are not implemented yet. You can connect, compile, upload and run Python code and send and recieve data via NUS with one connected Pybricks hub at a time. --- examples/pybricks_inventorhub.js | 32 +++++++++++ package.json | 3 +- src/consts.ts | 11 +++- src/hubs/pybrickshub.ts | 95 ++++++++++++++++++++++++++++++++ src/index-node.ts | 2 + src/poweredup-node.ts | 5 ++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 examples/pybricks_inventorhub.js create mode 100644 src/hubs/pybrickshub.ts diff --git a/examples/pybricks_inventorhub.js b/examples/pybricks_inventorhub.js new file mode 100644 index 0000000..8df2d98 --- /dev/null +++ b/examples/pybricks_inventorhub.js @@ -0,0 +1,32 @@ +/* + * + * This demonstrates connecting a Spike Prime / Mindstorms Inventor Hub with pybricks firmware. + * + */ + +const PoweredUP = require(".."); + +const poweredUP = new PoweredUP.PoweredUP(); +poweredUP.scan(); // Start scanning for hubs + +console.log("Looking for Hubs..."); + +poweredUP.on("discover", async (hub) => { // Wait to discover hubs + if(hub.type === PoweredUP.Consts.HubType.PYBRICKS_HUB) { + await hub.connect(); // If we found a hub with Pybricks firmware, connect to it + console.log(`Connected to ${hub.name}!`); + + // If the hub transmits something, show it in the console + hub.on("recieve", (data) => { console.log(data.toString()) }); + + hub.stopUserProgram(); // Stop any running user program + // The hub is now waiting for a user program to be uploaded which will then get executed + + hub.startUserProgram(` +from pybricks.hubs import InventorHub +hub = InventorHub() # We assume the connected hub is an Inventor hub +hub.display.text("Hello node-poweredup!") # Show on the led matrix of the hub +print("finished") # Transmit via bluetooth to the laptop + `); + } +}); diff --git a/package.json b/package.json index cb061d1..879e0cd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "dependencies": { "@abandonware/noble": "1.9.2-15", "compare-versions": "^4.1.3", - "debug": "^4.3.3" + "debug": "^4.3.3", + "@pybricks/mpy-cross-v6": "^2.0.0" }, "devDependencies": { "@types/debug": "4.1.7", diff --git a/src/consts.ts b/src/consts.ts index 268caa8..f64acaa 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -9,6 +9,7 @@ * @property {number} TECHNIC_MEDIUM_HUB 6 * @property {number} MARIO 7 * @property {number} TECHNIC_SMALL_HUB 8 + * @property {number} PYBRICKS_HUB 100 */ export enum HubType { UNKNOWN = 0, @@ -20,6 +21,7 @@ export enum HubType { TECHNIC_MEDIUM_HUB = 6, MARIO = 7, TECHNIC_SMALL_HUB = 8, + PYBRICKS_HUB = 100, } @@ -215,7 +217,9 @@ export enum BLEService { WEDO2_SMART_HUB_3 = "2a19", WEDO2_SMART_HUB_4 = "180f", WEDO2_SMART_HUB_5 = "180a", - LPF2_HUB = "00001623-1212-efde-1623-785feabcd123" + LPF2_HUB = "00001623-1212-efde-1623-785feabcd123", + PYBRICKS_HUB = "c5f50001-8280-46da-89f4-6d8051e4aeef", + PYBRICKS_NUS = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" } @@ -233,7 +237,10 @@ export enum BLECharacteristic { WEDO2_PORT_TYPE_WRITE = "00001563-1212-efde-1523-785feabcd123", // "1563" WEDO2_MOTOR_VALUE_WRITE = "00001565-1212-efde-1523-785feabcd123", // "1565" WEDO2_NAME_ID = "00001524-1212-efde-1523-785feabcd123", // "1524" - LPF2_ALL = "00001624-1212-efde-1623-785feabcd123" + LPF2_ALL = "00001624-1212-efde-1623-785feabcd123", + PYBRICKS_CONTROL = "c5f50002-8280-46da-89f4-6d8051e4aeef", + PYBRICKS_NUS_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e", + PYBRICKS_NUS_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" } diff --git a/src/hubs/pybrickshub.ts b/src/hubs/pybrickshub.ts new file mode 100644 index 0000000..7e4bb12 --- /dev/null +++ b/src/hubs/pybrickshub.ts @@ -0,0 +1,95 @@ +import { Peripheral } from "@abandonware/noble"; +import { compile } from "@pybricks/mpy-cross-v6"; +import { IBLEAbstraction } from "../interfaces"; +import { BaseHub } from "./basehub"; +import * as Consts from "../consts"; +import Debug = require("debug"); +const debug = Debug("pybrickshub"); + + +/** + * The PybricksHub is emitted if the discovered device is hub with pybricks firmware. + * @class PybricksHub + * @extends BaseHub + */ +export class PybricksHub extends BaseHub { + private _checkSumCallback: ((buffer: Buffer) => any) | undefined; + + public static IsPybricksHub (peripheral: Peripheral) { + return ( + peripheral.advertisement && + peripheral.advertisement.serviceUuids && + peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.PYBRICKS_HUB.replace(/-/g, "")) >= 0 + ); + } + + + constructor (device: IBLEAbstraction) { + super(device, PortMap, Consts.HubType.PYBRICKS_HUB); + debug("Discovered Pybricks Hub"); + } + + + public connect () { + return new Promise(async (resolve) => { + debug("Connecting to Pybricks Hub"); + await super.connect(); + await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_HUB); + await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_NUS); + debug("Connect completed"); + this.emit("connect"); + resolve(); + this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); + }); + } + + private _parseMessage (data?: Buffer) { + debug("Received Message (PYBRICKS_NUS_TX)", data); + if(this._checkSumCallback && data) { + return this._checkSumCallback(data); + } + this.emit("recieve", data); + } + + public send (message: Buffer, uuid: string = Consts.BLECharacteristic.PYBRICKS_NUS_RX) { + debug(`Send Message (${uuid})`, message); + return this._bleDevice.writeToCharacteristic(uuid, message); + } + + public startUserProgram (pythonCode: string) { + debug("Compiling Python User Program", pythonCode); + return compile("UserProgram.py", pythonCode).then(async (result) => { + if(result.mpy) { + debug("Uploading Python User Program", result.mpy); + const programLength = Buffer.alloc(4); + programLength.writeUint32LE(result.mpy.byteLength); + const checkSumPromise = new Promise((resolve) => { + const checkSum = programLength.reduce((a, b) => a ^ b); + this._checkSumCallback = (data) => resolve(data[0] === checkSum); + }); + this.send(programLength, Consts.BLECharacteristic.PYBRICKS_NUS_RX); + await checkSumPromise; + const chunkSize = 100; + for (let i = 0; i < result.mpy.byteLength; i += chunkSize) { + const chunk = result.mpy.slice(i, i + chunkSize); + const checkSumPromise = new Promise((resolve) => { + const checkSum = chunk.reduce((a, b) => a ^ b); + this._checkSumCallback = (data) => resolve(data[0] === checkSum); + }); + this.send(Buffer.from(chunk), Consts.BLECharacteristic.PYBRICKS_NUS_RX); + await checkSumPromise; + } + this._checkSumCallback = undefined; + debug("Finished uploading"); + } + else throw new Error(`Compiling Python User Program failed: ${result.err}`); + }); + } + + public stopUserProgram () { + return this.send(Buffer.from([0]), Consts.BLECharacteristic.PYBRICKS_CONTROL); + } +} + +export const PortMap: {[portName: string]: number} = { +}; diff --git a/src/index-node.ts b/src/index-node.ts index 59e3317..d0abffe 100644 --- a/src/index-node.ts +++ b/src/index-node.ts @@ -11,6 +11,7 @@ import { MoveHub } from "./hubs/movehub"; import { RemoteControl } from "./hubs/remotecontrol"; import { TechnicMediumHub } from "./hubs/technicmediumhub"; import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; +import { PybricksHub } from "./hubs/pybrickshub"; import { ColorDistanceSensor } from "./devices/colordistancesensor"; import { CurrentSensor } from "./devices/currentsensor"; @@ -58,6 +59,7 @@ export { Hub, RemoteControl, DuploTrainBase, + PybricksHub, Consts, Color, ColorDistanceSensor, diff --git a/src/poweredup-node.ts b/src/poweredup-node.ts index 14806a0..1e8741c 100644 --- a/src/poweredup-node.ts +++ b/src/poweredup-node.ts @@ -10,6 +10,7 @@ import { MoveHub } from "./hubs/movehub"; import { RemoteControl } from "./hubs/remotecontrol"; import { TechnicMediumHub } from "./hubs/technicmediumhub"; import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; +import { PybricksHub } from "./hubs/pybrickshub"; import * as Consts from "./consts"; @@ -25,6 +26,8 @@ let wantScan = false; const startScanning = () => { noble.startScanning([ + Consts.BLEService.PYBRICKS_HUB, + Consts.BLEService.PYBRICKS_HUB.replace(/-/g, ""), Consts.BLEService.LPF2_HUB, Consts.BLEService.LPF2_HUB.replace(/-/g, ""), Consts.BLEService.WEDO2_SMART_HUB, @@ -172,6 +175,8 @@ export class PoweredUP extends EventEmitter { hub = new TechnicMediumHub(device); } else if (Mario.IsMario(peripheral)) { hub = new Mario(device); + } else if (PybricksHub.IsPybricksHub(peripheral)) { + hub = new PybricksHub(device); } else { return; } From f29a9c6e2f1205b5ba786de5b1488737c02ab734 Mon Sep 17 00:00:00 2001 From: bene Date: Sun, 23 Oct 2022 15:43:52 +0200 Subject: [PATCH 2/4] Update pybricks implementation to newer version Changes to bluetooth communication according to https://github.com/pybricks/technical-info/blob/master/pybricks-ble-profile.md#profile-v120 Currently, compatible firmware can be installed from https://beta.pybricks.com/ --- examples/pybricks_inventorhub.js | 11 +++-- src/consts.ts | 3 +- src/hubs/pybrickshub.ts | 75 ++++++++++++++++++++------------ 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/examples/pybricks_inventorhub.js b/examples/pybricks_inventorhub.js index 8df2d98..3c0a21c 100644 --- a/examples/pybricks_inventorhub.js +++ b/examples/pybricks_inventorhub.js @@ -19,14 +19,19 @@ poweredUP.on("discover", async (hub) => { // Wait to discover hubs // If the hub transmits something, show it in the console hub.on("recieve", (data) => { console.log(data.toString()) }); - hub.stopUserProgram(); // Stop any running user program - // The hub is now waiting for a user program to be uploaded which will then get executed + // Stop any running user program + await hub.stopUserProgram(); - hub.startUserProgram(` + // Compiles the python code and uploads it as __main__ + await hub.uploadUserProgram(` from pybricks.hubs import InventorHub hub = InventorHub() # We assume the connected hub is an Inventor hub hub.display.text("Hello node-poweredup!") # Show on the led matrix of the hub print("finished") # Transmit via bluetooth to the laptop `); + + // Run the user program that was uploaded on the hub + // Alternatively the user program can be started by pressing the button on the hub + await hub.startUserProgram(); } }); diff --git a/src/consts.ts b/src/consts.ts index f64acaa..0f273cd 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -238,7 +238,8 @@ export enum BLECharacteristic { WEDO2_MOTOR_VALUE_WRITE = "00001565-1212-efde-1523-785feabcd123", // "1565" WEDO2_NAME_ID = "00001524-1212-efde-1523-785feabcd123", // "1524" LPF2_ALL = "00001624-1212-efde-1623-785feabcd123", - PYBRICKS_CONTROL = "c5f50002-8280-46da-89f4-6d8051e4aeef", + PYBRICKS_COMMAND_EVENT = "c5f50002-8280-46da-89f4-6d8051e4aeef", + PYBRICKS_CAPABILITIES = "c5f50003-8280-46da-89f4-6d8051e4aeef", PYBRICKS_NUS_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e", PYBRICKS_NUS_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" } diff --git a/src/hubs/pybrickshub.ts b/src/hubs/pybrickshub.ts index 7e4bb12..e9da58c 100644 --- a/src/hubs/pybrickshub.ts +++ b/src/hubs/pybrickshub.ts @@ -8,12 +8,15 @@ const debug = Debug("pybrickshub"); /** - * The PybricksHub is emitted if the discovered device is hub with pybricks firmware. + * The PybricksHub is emitted if the discovered device is a hub with Pybricks firmware installed. + * To flash your hub with Pybricks firmware, follow the instructions from https://pybricks.com. + * The class supports hubs with Pybricks version 3.2.0 or newer. * @class PybricksHub * @extends BaseHub */ export class PybricksHub extends BaseHub { - private _checkSumCallback: ((buffer: Buffer) => any) | undefined; + private _maxCharSize: number = 100; // overwritten by value from capabilities characteristic + private _maxUserProgramSize: number = 16000; // overwritten by value from capabilities characteristic public static IsPybricksHub (peripheral: Peripheral) { return ( @@ -36,18 +39,22 @@ export class PybricksHub extends BaseHub { await super.connect(); await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_HUB); await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_NUS); + await this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.PYBRICKS_CAPABILITIES, (err, data) => { + if (data) { + this._maxCharSize = data.readUInt16LE(0); + this._maxUserProgramSize = data.readUInt32LE(6); + debug("Recieved capabilities ", data, " maxCharSize: ", this._maxCharSize, " maxUserProgramSize: ", this._maxUserProgramSize); + } + }); + this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); debug("Connect completed"); this.emit("connect"); resolve(); - this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); }); } private _parseMessage (data?: Buffer) { debug("Received Message (PYBRICKS_NUS_TX)", data); - if(this._checkSumCallback && data) { - return this._checkSumCallback(data); - } this.emit("recieve", data); } @@ -56,30 +63,23 @@ export class PybricksHub extends BaseHub { return this._bleDevice.writeToCharacteristic(uuid, message); } - public startUserProgram (pythonCode: string) { + public uploadUserProgram (pythonCode: string) { debug("Compiling Python User Program", pythonCode); - return compile("UserProgram.py", pythonCode).then(async (result) => { + return compile('userProgram.py', pythonCode).then(async (result) => { if(result.mpy) { - debug("Uploading Python User Program", result.mpy); - const programLength = Buffer.alloc(4); - programLength.writeUint32LE(result.mpy.byteLength); - const checkSumPromise = new Promise((resolve) => { - const checkSum = programLength.reduce((a, b) => a ^ b); - this._checkSumCallback = (data) => resolve(data[0] === checkSum); - }); - this.send(programLength, Consts.BLECharacteristic.PYBRICKS_NUS_RX); - await checkSumPromise; - const chunkSize = 100; - for (let i = 0; i < result.mpy.byteLength; i += chunkSize) { - const chunk = result.mpy.slice(i, i + chunkSize); - const checkSumPromise = new Promise((resolve) => { - const checkSum = chunk.reduce((a, b) => a ^ b); - this._checkSumCallback = (data) => resolve(data[0] === checkSum); - }); - this.send(Buffer.from(chunk), Consts.BLECharacteristic.PYBRICKS_NUS_RX); - await checkSumPromise; + const multiFileBlob = Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from('__main__\0'), result.mpy]); + multiFileBlob.writeUInt32LE(result.mpy.length); + if(multiFileBlob.length > this._maxUserProgramSize) { + throw new Error(`User program size ${multiFileBlob.length} larger than maximum ${this._maxUserProgramSize}`); + } + debug("Uploading Python User Program", multiFileBlob); + await this.writeUserProgramMeta(0); + const chunkSize = this._maxCharSize - 5; + for (let i = 0; i < multiFileBlob.length; i += chunkSize) { + const chunk = multiFileBlob.slice(i, i + chunkSize); + await this.writeUserRam(i, Buffer.from(chunk)); } - this._checkSumCallback = undefined; + await this.writeUserProgramMeta(multiFileBlob.length); debug("Finished uploading"); } else throw new Error(`Compiling Python User Program failed: ${result.err}`); @@ -87,7 +87,26 @@ export class PybricksHub extends BaseHub { } public stopUserProgram () { - return this.send(Buffer.from([0]), Consts.BLECharacteristic.PYBRICKS_CONTROL); + debug("Stopping Python User Program"); + return this.send(Buffer.from([0]), Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT); + } + + public startUserProgram () { + debug("Starting Python User Program"); + return this.send(Buffer.from([1]), Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT); + } + + private writeUserProgramMeta (programLength: number) { + const message = Buffer.alloc(5); + message[0] = 3; + message.writeUInt32LE(programLength, 1); + return this.send(message, Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT); + } + + private writeUserRam (offset: number, payload: Buffer) { + const message = Buffer.concat([Buffer.from([4, 0, 0, 0, 0]), payload]); + message.writeUInt32LE(offset, 1); + return this.send(message, Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT); } } From fb97c7c6b1a8c601c86322add86d5d44b5a3ff26 Mon Sep 17 00:00:00 2001 From: bene Date: Sat, 26 Nov 2022 20:09:00 +0100 Subject: [PATCH 3/4] Rename generic bluetooth characteristics constants The characteristics are part of generic specifications such as https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf Some are also supported by pybricks --- src/consts.ts | 6 +++--- src/hubs/wedo2smarthub.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/consts.ts b/src/consts.ts index 0f273cd..df72d4c 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -216,7 +216,7 @@ export enum BLEService { WEDO2_SMART_HUB_2 = "00004f0e-1212-efde-1523-785feabcd123", WEDO2_SMART_HUB_3 = "2a19", WEDO2_SMART_HUB_4 = "180f", - WEDO2_SMART_HUB_5 = "180a", + STANDARD_DEVICE_INFORMATION = "180a", LPF2_HUB = "00001623-1212-efde-1623-785feabcd123", PYBRICKS_HUB = "c5f50001-8280-46da-89f4-6d8051e4aeef", PYBRICKS_NUS = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" @@ -224,8 +224,8 @@ export enum BLEService { export enum BLECharacteristic { - WEDO2_BATTERY = "2a19", - WEDO2_FIRMWARE_REVISION = "2a26", + STANDARD_BATTERY = "2a19", + STANDARD_FIRMWARE_REVISION = "2a26", WEDO2_BUTTON = "00001526-1212-efde-1523-785feabcd123", // "1526" WEDO2_PORT_TYPE = "00001527-1212-efde-1523-785feabcd123", // "1527" // Handles plugging and unplugging of devices on WeDo 2.0 Smart Hub WEDO2_LOW_VOLTAGE_ALERT = "00001528-1212-efde-1523-785feabcd123", // "1528" diff --git a/src/hubs/wedo2smarthub.ts b/src/hubs/wedo2smarthub.ts index d7ffa0c..f5ec114 100644 --- a/src/hubs/wedo2smarthub.ts +++ b/src/hubs/wedo2smarthub.ts @@ -49,7 +49,7 @@ export class WeDo2SmartHub extends BaseHub { if (!isWebBluetooth) { await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_3); await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_4); - await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_5); + await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.STANDARD_DEVICE_INFORMATION); } else { await this._bleDevice.discoverCharacteristicsForService("battery_service"); await this._bleDevice.discoverCharacteristicsForService("device_information"); @@ -61,8 +61,8 @@ export class WeDo2SmartHub extends BaseHub { this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_SENSOR_VALUE, this._parseSensorMessage.bind(this)); this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_BUTTON, this._parseSensorMessage.bind(this)); if (!isWebBluetooth) { - this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_BATTERY, this._parseBatteryMessage.bind(this)); - this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.WEDO2_BATTERY, (err, data) => { + this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.STANDARD_BATTERY, this._parseBatteryMessage.bind(this)); + this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.STANDARD_BATTERY, (err, data) => { if (data) { this._parseBatteryMessage(data); } @@ -77,7 +77,7 @@ export class WeDo2SmartHub extends BaseHub { } this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_HIGH_CURRENT_ALERT, this._parseHighCurrentAlert.bind(this)); if (!isWebBluetooth) { - this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.WEDO2_FIRMWARE_REVISION, (err, data) => { + this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.STANDARD_FIRMWARE_REVISION, (err, data) => { if (data) { this._parseFirmwareRevisionString(data); } From 54d0bc986e8852cb1593b7ccea689117e14bd0f8 Mon Sep 17 00:00:00 2001 From: bene Date: Sat, 26 Nov 2022 20:20:50 +0100 Subject: [PATCH 4/4] Read and check pybricks firmware version --- src/hubs/pybrickshub.ts | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/hubs/pybrickshub.ts b/src/hubs/pybrickshub.ts index e9da58c..e40aea9 100644 --- a/src/hubs/pybrickshub.ts +++ b/src/hubs/pybrickshub.ts @@ -1,4 +1,5 @@ import { Peripheral } from "@abandonware/noble"; +import compareVersion from "compare-versions"; import { compile } from "@pybricks/mpy-cross-v6"; import { IBLEAbstraction } from "../interfaces"; import { BaseHub } from "./basehub"; @@ -39,20 +40,36 @@ export class PybricksHub extends BaseHub { await super.connect(); await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_HUB); await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.PYBRICKS_NUS); - await this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.PYBRICKS_CAPABILITIES, (err, data) => { - if (data) { - this._maxCharSize = data.readUInt16LE(0); - this._maxUserProgramSize = data.readUInt32LE(6); - debug("Recieved capabilities ", data, " maxCharSize: ", this._maxCharSize, " maxUserProgramSize: ", this._maxUserProgramSize); - } - }); - this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); + await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.STANDARD_DEVICE_INFORMATION); + await new Promise(async (resolve) => this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.STANDARD_FIRMWARE_REVISION, (err, data) => { + if (data) { + this._firmwareVersion = data.toString(); + this._checkFirmware(this._firmwareVersion); + debug("Firmware version ", this._firmwareVersion); + return resolve(); + } + })); + await new Promise(async (resolve) => this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.PYBRICKS_CAPABILITIES, (err, data) => { + if (data) { + this._maxCharSize = data.readUInt16LE(0); + this._maxUserProgramSize = data.readUInt32LE(6); + debug("Recieved capabilities ", data, " maxCharSize: ", this._maxCharSize, " maxUserProgramSize: ", this._maxUserProgramSize); + return resolve(); + } + })); + await this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); debug("Connect completed"); this.emit("connect"); resolve(); }); } + protected _checkFirmware (version: string) { + if (compareVersion.validate(version) && compareVersion('3.2.0', version) === 1) { + throw new Error(`Your Hub's (${this.name}) firmware is out of date and unsupported by this library. Please update it via the official Pybricks website.`); + } + } + private _parseMessage (data?: Buffer) { debug("Received Message (PYBRICKS_NUS_TX)", data); this.emit("recieve", data);