Skip to content

Commit 10d29b2

Browse files
committed
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/
1 parent 8d9207e commit 10d29b2

File tree

3 files changed

+57
-31
lines changed

3 files changed

+57
-31
lines changed

examples/pybricks_inventorhub.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ poweredUP.on("discover", async (hub) => { // Wait to discover hubs
1919
// If the hub transmits something, show it in the console
2020
hub.on("recieve", (data) => { console.log(data.toString()) });
2121

22-
hub.stopUserProgram(); // Stop any running user program
23-
// The hub is now waiting for a user program to be uploaded which will then get executed
22+
// Stop any running user program
23+
await hub.stopUserProgram();
2424

25-
hub.startUserProgram(`
25+
// Compiles the python code and uploads it as __main__
26+
await hub.uploadUserProgram(`
2627
from pybricks.hubs import InventorHub
2728
hub = InventorHub() # We assume the connected hub is an Inventor hub
2829
hub.display.text("Hello node-poweredup!") # Show on the led matrix of the hub
2930
print("finished") # Transmit via bluetooth to the laptop
3031
`);
32+
33+
// Run the user program that was uploaded on the hub
34+
// Alternatively the user program can be started by pressing the button on the hub
35+
await hub.startUserProgram();
3136
}
3237
});

src/consts.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ export enum BLECharacteristic {
238238
WEDO2_MOTOR_VALUE_WRITE = "00001565-1212-efde-1523-785feabcd123", // "1565"
239239
WEDO2_NAME_ID = "00001524-1212-efde-1523-785feabcd123", // "1524"
240240
LPF2_ALL = "00001624-1212-efde-1623-785feabcd123",
241-
PYBRICKS_CONTROL = "c5f50002-8280-46da-89f4-6d8051e4aeef",
241+
PYBRICKS_COMMAND_EVENT = "c5f50002-8280-46da-89f4-6d8051e4aeef",
242+
PYBRICKS_CAPABILITIES = "c5f50003-8280-46da-89f4-6d8051e4aeef",
242243
PYBRICKS_NUS_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
243244
PYBRICKS_NUS_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
244245
}

src/hubs/pybrickshub.ts

+47-27
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ const debug = Debug("pybrickshub");
88

99

1010
/**
11-
* The PybricksHub is emitted if the discovered device is hub with pybricks firmware.
11+
* The PybricksHub is emitted if the discovered device is a hub with Pybricks firmware installed.
12+
* To flash your hub with Pybricks firmware, follow the instructions from https://pybricks.com.
13+
* The class supports hubs with Pybricks version 3.2.0 or newer.
1214
* @class PybricksHub
1315
* @extends BaseHub
1416
*/
1517
export class PybricksHub extends BaseHub {
16-
private _checkSumCallback: ((buffer: Buffer) => any) | undefined;
18+
private _maxCharSize: number = 100; // overwritten by value from capabilities characteristic
19+
private _maxUserProgramSize: number = 16000; // overwritten by value from capabilities characteristic
1720

1821
public static IsPybricksHub (peripheral: Peripheral) {
1922
return (
@@ -39,15 +42,20 @@ export class PybricksHub extends BaseHub {
3942
debug("Connect completed");
4043
this.emit("connect");
4144
resolve();
45+
this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.PYBRICKS_CAPABILITIES, (err, data) => {
46+
if (data) {
47+
this._maxCharSize = data.readUInt16LE(0);
48+
this._maxUserProgramSize = data.readUInt32LE(6);
49+
debug("Recieved capabilities ", data, " maxCharSize: ", this._maxCharSize, " maxUserProgramSize: ", this._maxUserProgramSize);
50+
}
51+
});
4252
this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_NUS_TX, this._parseMessage.bind(this));
53+
this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT, (data) => debug("Recieved command event ", data));
4354
});
4455
}
4556

4657
private _parseMessage (data?: Buffer) {
4758
debug("Received Message (PYBRICKS_NUS_TX)", data);
48-
if(this._checkSumCallback && data) {
49-
return this._checkSumCallback(data);
50-
}
5159
this.emit("recieve", data);
5260
}
5361

@@ -56,38 +64,50 @@ export class PybricksHub extends BaseHub {
5664
return this._bleDevice.writeToCharacteristic(uuid, message);
5765
}
5866

59-
public startUserProgram (pythonCode: string) {
67+
public uploadUserProgram (pythonCode: string) {
6068
debug("Compiling Python User Program", pythonCode);
61-
return compile("UserProgram.py", pythonCode).then(async (result) => {
69+
return compile('userProgram.py', pythonCode).then(async (result) => {
6270
if(result.mpy) {
63-
debug("Uploading Python User Program", result.mpy);
64-
const programLength = Buffer.alloc(4);
65-
programLength.writeUint32LE(result.mpy.byteLength);
66-
const checkSumPromise = new Promise<boolean>((resolve) => {
67-
const checkSum = programLength.reduce((a, b) => a ^ b);
68-
this._checkSumCallback = (data) => resolve(data[0] === checkSum);
69-
});
70-
this.send(programLength, Consts.BLECharacteristic.PYBRICKS_NUS_RX);
71-
await checkSumPromise;
72-
const chunkSize = 100;
73-
for (let i = 0; i < result.mpy.byteLength; i += chunkSize) {
74-
const chunk = result.mpy.slice(i, i + chunkSize);
75-
const checkSumPromise = new Promise<boolean>((resolve) => {
76-
const checkSum = chunk.reduce((a, b) => a ^ b);
77-
this._checkSumCallback = (data) => resolve(data[0] === checkSum);
78-
});
79-
this.send(Buffer.from(chunk), Consts.BLECharacteristic.PYBRICKS_NUS_RX);
80-
await checkSumPromise;
71+
const multiFileBlob = Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from('__main__\0'), result.mpy]);
72+
multiFileBlob.writeUInt32LE(result.mpy.length);
73+
if(multiFileBlob.length > this._maxUserProgramSize) {
74+
throw new Error(`User program size ${multiFileBlob.length} larger than maximum ${this._maxUserProgramSize}`);
8175
}
82-
this._checkSumCallback = undefined;
76+
debug("Uploading Python User Program", multiFileBlob);
77+
await this.writeUserProgramMeta(0);
78+
const chunkSize = this._maxCharSize - 5;
79+
for (let i = 0; i < multiFileBlob.length; i += chunkSize) {
80+
const chunk = multiFileBlob.slice(i, i + chunkSize);
81+
await this.writeUserRam(i, Buffer.from(chunk));
82+
}
83+
await this.writeUserProgramMeta(multiFileBlob.length);
8384
debug("Finished uploading");
8485
}
8586
else throw new Error(`Compiling Python User Program failed: ${result.err}`);
8687
});
8788
}
8889

8990
public stopUserProgram () {
90-
return this.send(Buffer.from([0]), Consts.BLECharacteristic.PYBRICKS_CONTROL);
91+
debug("Stopping Python User Program");
92+
return this.send(Buffer.from([0]), Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT);
93+
}
94+
95+
public startUserProgram () {
96+
debug("Starting Python User Program");
97+
return this.send(Buffer.from([1]), Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT);
98+
}
99+
100+
private writeUserProgramMeta (programLength: number) {
101+
const message = Buffer.alloc(5);
102+
message[0] = 3;
103+
message.writeUint32LE(programLength, 1);
104+
return this.send(message, Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT);
105+
}
106+
107+
private writeUserRam (offset: number, payload: Buffer) {
108+
const message = Buffer.concat([Buffer.from([4, 0, 0, 0, 0]), payload]);
109+
message.writeUint32LE(offset, 1);
110+
return this.send(message, Consts.BLECharacteristic.PYBRICKS_COMMAND_EVENT);
91111
}
92112
}
93113

0 commit comments

Comments
 (0)