Skip to content

Commit

Permalink
Update pybricks implementation to newer version
Browse files Browse the repository at this point in the history
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/
  • Loading branch information
Debenben committed Oct 23, 2022
1 parent 8d9207e commit 10d29b2
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 31 deletions.
11 changes: 8 additions & 3 deletions examples/pybricks_inventorhub.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
3 changes: 2 additions & 1 deletion src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
74 changes: 47 additions & 27 deletions src/hubs/pybrickshub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -39,15 +42,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);
}

Expand All @@ -56,38 +64,50 @@ 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<boolean>((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<boolean>((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}`);
});
}

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);
}
}

Expand Down

0 comments on commit 10d29b2

Please sign in to comment.