From 42b240a2bb8f4de461e515dbf839ce853b744d8d Mon Sep 17 00:00:00 2001 From: bene Date: Sun, 23 Oct 2022 15:43:52 +0200 Subject: [PATCH] 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, 58 insertions(+), 31 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..6f8777b 100644 --- a/src/hubs/pybrickshub.ts +++ b/src/hubs/pybrickshub.ts @@ -8,12 +8,16 @@ 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 ( @@ -39,15 +43,20 @@ export class PybricksHub extends BaseHub { debug("Connect completed"); this.emit("connect"); 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); + } + }); this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this)); + this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT, (data) => debug("Recieved command event ", data)); }); } 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 +65,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}`); } - this._checkSumCallback = undefined; + 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)); + } + await this.writeUserProgramMeta(multiFileBlob.length); debug("Finished uploading"); } else throw new Error(`Compiling Python User Program failed: ${result.err}`); @@ -87,7 +89,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); } }