Skip to content

Commit 14f4716

Browse files
afaragodlech
andauthored
Implemented pybricks protocol v1.4.0 (#2317)
* Update for changed StartUserProgram command semantics * Added new capability flags and fixed-purpose slot definitions * Update for new start REPL command * Updated StartUserProgram command and Status event for slots * Added new AppData command and event --------- Co-authored-by: David Lechner <[email protected]>
1 parent 41a397a commit 14f4716

File tree

15 files changed

+514
-123
lines changed

15 files changed

+514
-123
lines changed

src/ble-pybricks-service/actions.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2021-2023 The Pybricks Authors
2+
// Copyright (c) 2021-2024 The Pybricks Authors
33
//
44
// Actions for Bluetooth Low Energy Pybricks service
55

@@ -59,22 +59,36 @@ export const sendStopUserProgramCommand = createAction((id: number) => ({
5959
* Action that requests a start user program to be sent.
6060
* @param id Unique identifier for this transaction.
6161
*
62-
* @since Pybricks Profile v1.2.0
62+
* @since Pybricks Profile v1.2.0 - removed in v1.4.0
6363
*/
64-
export const sendStartUserProgramCommand = createAction((id: number) => ({
65-
type: 'blePybricksServiceCommand.action.sendStartUserProgram',
64+
export const sendLegacyStartUserProgramCommand = createAction((id: number) => ({
65+
type: 'blePybricksServiceCommand.action.sendLegacyStartUserProgram',
6666
id,
6767
}));
6868

6969
/**
7070
* Action that requests a start interactive REPL to be sent.
7171
* @param id Unique identifier for this transaction.
7272
*
73-
* @since Pybricks Profile v1.2.0
73+
* @since Pybricks Profile v1.2.0 - removed in v1.4.0
74+
*
75+
*/
76+
export const sendLegacyStartReplCommand = createAction((id: number) => ({
77+
type: 'blePybricksServiceCommand.action.sendLegacyStartRepl',
78+
id,
79+
}));
80+
81+
/**
82+
* Action that requests a start user program to be sent.
83+
* @param id Unique identifier for this transaction.
84+
* @param slot The slot number of the user program to start.
85+
*
86+
* @since Pybricks Profile v1.4.0
7487
*/
75-
export const sendStartReplCommand = createAction((id: number) => ({
76-
type: 'blePybricksServiceCommand.action.sendStartRepl',
88+
export const sendStartUserProgramCommand = createAction((id: number, slot: number) => ({
89+
type: 'blePybricksServiceCommand.action.sendStartUserProgram',
7790
id,
91+
slot,
7892
}));
7993

8094
/**
@@ -124,6 +138,23 @@ export const sendWriteStdinCommand = createAction(
124138
}),
125139
);
126140

141+
/**
142+
* Action that requests to write to AppData.
143+
* @param id Unique identifier for this transaction.
144+
* @param offset offset: The offset from the buffer base address
145+
* @param payload The bytes to write.
146+
*
147+
* @since Pybricks Profile v1.4.0.
148+
*/
149+
export const sendWriteAppDataCommand = createAction(
150+
(id: number, offset: number, payload: ArrayBuffer) => ({
151+
type: 'blePybricksServiceCommand.action.sendWriteAppDataCommand',
152+
id,
153+
offset,
154+
payload,
155+
}),
156+
);
157+
127158
/**
128159
* Action that indicates that a command was successfully sent.
129160
* @param id Unique identifier for the transaction from the corresponding "send" command.
@@ -149,15 +180,19 @@ export const didFailToSendCommand = createAction((id: number, error: Error) => (
149180
/**
150181
* Action that represents a status report event received from the hub.
151182
* @param statusFlags The status flags.
183+
* @param slot The slot number of the user program that is running.
152184
*/
153-
export const didReceiveStatusReport = createAction((statusFlags: number) => ({
154-
type: 'blePybricksServiceEvent.action.didReceiveStatusReport',
155-
statusFlags,
156-
}));
185+
export const didReceiveStatusReport = createAction(
186+
(statusFlags: number, slot: number) => ({
187+
type: 'blePybricksServiceEvent.action.didReceiveStatusReport',
188+
statusFlags,
189+
slot,
190+
}),
191+
);
157192

158193
/**
159194
* Action that represents a status report event received from the hub.
160-
* @param statusFlags The status flags.
195+
* @param payload The piece of message received.
161196
*
162197
* @since Pybricks Profile v1.3.0
163198
*/
@@ -166,6 +201,17 @@ export const didReceiveWriteStdout = createAction((payload: ArrayBuffer) => ({
166201
payload,
167202
}));
168203

204+
/**
205+
* Action that represents a write to a buffer that is pre-allocated by a user program received from the hub.
206+
* @param payload The piece of message received.
207+
*
208+
* @since Pybricks Profile v1.4.0
209+
*/
210+
export const didReceiveWriteAppData = createAction((payload: ArrayBuffer) => ({
211+
type: 'blePybricksServiceEvent.action.didReceiveWriteAppData',
212+
payload,
213+
}));
214+
169215
/**
170216
* Pseudo-event = actionCreator((not received from hub) indicating that there was a protocol error.
171217
* @param error The error that was caught.

src/ble-pybricks-service/protocol.ts

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2020-2023 The Pybricks Authors
2+
// Copyright (c) 2020-2024 The Pybricks Authors
33
//
44
// Definitions related to the Pybricks Bluetooth low energy GATT service.
55

@@ -25,13 +25,13 @@ export enum CommandType {
2525
/**
2626
* Request to start the user program.
2727
*
28-
* @since Pybricks Profile v1.2.0
28+
* @since Pybricks Profile v1.2.0 - changed in v1.4.0
2929
*/
3030
StartUserProgram = 1,
3131
/**
3232
* Request to start the interactive REPL.
3333
*
34-
* @since Pybricks Profile v1.2.0
34+
* @since Pybricks Profile v1.2.0 - removed in v1.4.0
3535
*/
3636
StartRepl = 2,
3737
/**
@@ -58,6 +58,43 @@ export enum CommandType {
5858
* @since Pybricks Profile v1.3.0
5959
*/
6060
WriteStdin = 6,
61+
/**
62+
* Requests to write to a buffer that is pre-allocated by a user program.
63+
*
64+
* Parameters:
65+
* - offset: The offset from the buffer base address (16-bit little-endian
66+
* unsigned integer).
67+
* - payload: The data to write.
68+
*
69+
* @since Pybricks Profile v1.4.0
70+
*/
71+
WriteAppData = 7,
72+
}
73+
74+
/**
75+
* Built-in program ID's for use with {@link CommandType.StartUserProgram}.
76+
*
77+
* @since Pybricks Profile v1.4.0
78+
*/
79+
export enum BuiltinProgramId {
80+
/**
81+
* Requests to start the built-in REPL on stdio.
82+
*
83+
* @since Pybricks Profile v1.4.0
84+
*/
85+
REPL = 0x80,
86+
/**
87+
* Requests to start the built-in sensor port view monitoring program.
88+
*
89+
* @since Pybricks Profile v1.4.0
90+
*/
91+
PortView = 0x81,
92+
/**
93+
* Requests to start the built-in IMU calibration program.
94+
*
95+
* @since Pybricks Profile v1.4.0
96+
*/
97+
IMUCalibration = 0x82,
6198
}
6299

63100
/**
@@ -74,20 +111,38 @@ export function createStopUserProgramCommand(): Uint8Array {
74111
/**
75112
* Creates a {@link CommandType.StartUserProgram} message.
76113
*
77-
* @since Pybricks Profile v1.2.0
114+
* Parameters:
115+
* - slot: Program identifier (one byte). Slots 0--127 are reserved for
116+
* downloaded user programs. Slots 128--255 are for builtin user programs.
117+
*
118+
* @since Pybricks Profile v1.4.0
78119
*/
79-
export function createStartUserProgramCommand(): Uint8Array {
120+
export function createStartUserProgramCommand(
121+
slot: number | BuiltinProgramId,
122+
): Uint8Array {
123+
const msg = new Uint8Array(2);
124+
msg[0] = CommandType.StartUserProgram;
125+
msg[1] = slot;
126+
return msg;
127+
}
128+
129+
/**
130+
* Creates a legacy {@link CommandType.StartUserProgram} message.
131+
*
132+
* @since Pybricks Profile v1.2.0 - removed in v1.4.0
133+
*/
134+
export function createLegacyStartUserProgramCommand(): Uint8Array {
80135
const msg = new Uint8Array(1);
81136
msg[0] = CommandType.StartUserProgram;
82137
return msg;
83138
}
84139

85140
/**
86-
* Creates a {@link CommandType.StartRepl} message.
141+
* Creates a legacy {@link CommandType.StartRepl} message.
87142
*
88-
* @since Pybricks Profile v1.2.0
143+
* @since Pybricks Profile v1.2.0 - removed in v1.4.0
89144
*/
90-
export function createStartReplCommand(): Uint8Array {
145+
export function createLegacyStartReplCommand(): Uint8Array {
91146
const msg = new Uint8Array(1);
92147
msg[0] = CommandType.StartRepl;
93148
return msg;
@@ -140,6 +195,25 @@ export function createWriteStdinCommand(payload: ArrayBuffer): Uint8Array {
140195
return msg;
141196
}
142197

198+
/**
199+
* Creates a {@link CommandType.WriteAppData} message.
200+
* @param offset The offset from the buffer base address
201+
* @param payload The bytes to write.
202+
*
203+
* @since Pybricks Profile v1.4.0.
204+
*/
205+
export function createWriteAppDataCommand(
206+
offset: number,
207+
payload: ArrayBuffer,
208+
): Uint8Array {
209+
const msg = new Uint8Array(1 + 2 + payload.byteLength);
210+
const view = new DataView(msg.buffer);
211+
view.setUint8(0, CommandType.WriteAppData);
212+
view.setUint16(1, offset & 0xffff, true);
213+
msg.set(new Uint8Array(payload), 3);
214+
return msg;
215+
}
216+
143217
/** Events are notifications received from the hub. */
144218
export enum EventType {
145219
/**
@@ -156,6 +230,12 @@ export enum EventType {
156230
* @since Pybricks Profile v1.3.0
157231
*/
158232
WriteStdout = 1,
233+
/**
234+
* Hub wrote to AppData event.
235+
*
236+
* @since Pybricks Profile v1.4.0
237+
*/
238+
WriteAppData = 2,
159239
}
160240

161241
/** Status indications received by Event.StatusReport */
@@ -223,13 +303,16 @@ export function getEventType(msg: DataView): EventType {
223303
/**
224304
* Parses the payload of a status report message.
225305
* @param msg The raw message data.
226-
* @returns The status as bit flags.
306+
* @returns The status as bit flags and the slot number of the running program.
227307
*
228-
* @since Pybricks Profile v1.0.0
308+
* @since Pybricks Profile v1.0.0 - changed in v1.4.0
229309
*/
230-
export function parseStatusReport(msg: DataView): number {
310+
export function parseStatusReport(msg: DataView): { flags: number; slot: number } {
231311
assert(msg.getUint8(0) === EventType.StatusReport, 'expecting status report event');
232-
return msg.getUint32(1, true);
312+
return {
313+
flags: msg.getUint32(1, true),
314+
slot: msg.byteLength > 5 ? msg.getUint8(5) : 0,
315+
};
233316
}
234317

235318
/**
@@ -244,6 +327,18 @@ export function parseWriteStdout(msg: DataView): ArrayBuffer {
244327
return msg.buffer.slice(1);
245328
}
246329

330+
/**
331+
* Parses the payload of a app data message.
332+
* @param msg The raw message data.
333+
* @returns The bytes that were written.
334+
*
335+
* @since Pybricks Profile v1.4.0
336+
*/
337+
export function parseWriteAppData(msg: DataView): ArrayBuffer {
338+
assert(msg.getUint8(0) === EventType.WriteAppData, 'expecting write appdata event');
339+
return msg.buffer.slice(1);
340+
}
341+
247342
/**
248343
* Protocol error. Thrown e.g. when there is a malformed message.
249344
*/
@@ -266,7 +361,9 @@ export class ProtocolError extends Error {
266361
*/
267362
export enum HubCapabilityFlag {
268363
/**
269-
* Hub has an interactive REPL.
364+
* Hub supports {@link CommandType.StartUserProgram} command with
365+
* {@link BuiltinProgramId.REPL} for protocol v1.4.0 and later or hub
366+
* supports {@link CommandType.StartRepl}
270367
*
271368
* @since Pybricks Profile v1.2.0
272369
*/
@@ -285,6 +382,22 @@ export enum HubCapabilityFlag {
285382
* @since Pybricks Profile v1.3.0
286383
*/
287384
UserProgramMultiMpy6Native6p1 = 1 << 2,
385+
386+
/**
387+
* Hub supports {@link CommandType.StartUserProgram} command with
388+
* {@link BuiltinProgramId.PortView}.
389+
*
390+
* @since Pybricks Profile v1.4.0.
391+
*/
392+
HasPortView = 1 << 3,
393+
394+
/**
395+
* Hub supports {@link CommandType.StartUserProgram} command with
396+
* {@link BuiltinProgramId.IMUCalibration}.
397+
*
398+
* @since Pybricks Profile v1.4.0.
399+
*/
400+
HasIMUCalibration = 1 << 4,
288401
}
289402

290403
/** Supported user program file formats. */

0 commit comments

Comments
 (0)