mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-14 18:46:17 +00:00
wire up flash button
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
// Ref: https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#lego-hub-boot-loader-service
|
||||
|
||||
/**
|
||||
* LEGO Powered Up Bootloader Service UUID.
|
||||
*/
|
||||
export const ServiceUUID = '00001625-1212-efde-1623-785feabcd123';
|
||||
|
||||
/**
|
||||
* LEGO Powered Up Bootloader Characteristic UUID.
|
||||
*/
|
||||
export const CharacteristicUUID = '00001626-1212-efde-1623-785feabcd123';
|
||||
|
||||
/**
|
||||
* The maximum message size that can be sent or received.
|
||||
*/
|
||||
export const MaxMessageSize = 20;
|
||||
|
||||
/**
|
||||
* LEGO Powered Up Hub IDs
|
||||
*/
|
||||
export enum HubType {
|
||||
MoveHub = 0x40,
|
||||
CityHub = 0x41,
|
||||
CPlusHub = 0x80,
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGO bootloader command bytecodes.
|
||||
*/
|
||||
export enum Command {
|
||||
EraseFlash = 0x11,
|
||||
ProgramFlash = 0x22,
|
||||
StartApp = 0x33,
|
||||
InitLoader = 0x44,
|
||||
GetInfo = 0x55,
|
||||
GetChecksum = 0x66,
|
||||
GetFlashState = 0x77,
|
||||
Disconnect = 0x88,
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message bytecode.
|
||||
*/
|
||||
export type ErrorMessage = 0x05;
|
||||
export const ErrorBytecode: ErrorMessage = 0x05;
|
||||
|
||||
enum ErrorCode {
|
||||
UnknownCommand = 0x05,
|
||||
}
|
||||
|
||||
/**
|
||||
* Result status.
|
||||
*/
|
||||
export enum Result {
|
||||
OK = 0x00,
|
||||
Error = 0xff,
|
||||
}
|
||||
|
||||
/**
|
||||
* The largest allowable size for the payload of the ProgramFlash command.
|
||||
*/
|
||||
export const MaxProgramFlashSize = 14;
|
||||
|
||||
/**
|
||||
* Flash memory protection level.
|
||||
*
|
||||
* Refer to STM32 technical reference.
|
||||
*/
|
||||
export enum ProtectionLevel {
|
||||
None = 0x00,
|
||||
Level1 = 0x01,
|
||||
Level2 = 0x02,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to erase the flash memory.
|
||||
*/
|
||||
export function createEraseFlashRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.EraseFlash);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to program the flash memory.
|
||||
* @param address The starting address.
|
||||
* @param payload The data (14 bytes max)
|
||||
*/
|
||||
export function createProgramFlashRequest(
|
||||
address: number,
|
||||
payload: ArrayBuffer,
|
||||
): Uint8Array {
|
||||
const size = payload.byteLength;
|
||||
if (size > MaxProgramFlashSize) {
|
||||
throw Error('payload is bigger than MaxProgramFlashSize');
|
||||
}
|
||||
const msg = new Uint8Array(size + 6);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.ProgramFlash);
|
||||
view.setUint8(1, size + 4);
|
||||
view.setUint32(2, address, true);
|
||||
const payloadView = new DataView(payload);
|
||||
for (let i = 0; i < size; i++) {
|
||||
view.setUint8(6 + i, payloadView.getUint8(i));
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to reboot an start the new firmware.
|
||||
*/
|
||||
export function createStartAppRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.StartApp);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to prepare the bootloader to receive a new firmware.
|
||||
* @param fwSize The total size of the firmware to be flashed.
|
||||
*/
|
||||
export function createInitLoaderRequest(fwSize: number): Uint8Array {
|
||||
const msg = new Uint8Array(5);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.InitLoader);
|
||||
view.setUint32(1, fwSize, true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to get bootloader and device info.
|
||||
*/
|
||||
export function createGetInfoRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.GetInfo);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to get the current checksum.
|
||||
*/
|
||||
export function createGetChecksumRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.GetChecksum);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to get the flash memory protection state.
|
||||
*
|
||||
* This command is not implemented on some devices.
|
||||
*/
|
||||
export function createGetFlashStateRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.GetFlashState);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message to disconnect the connection.
|
||||
*/
|
||||
export function createDisconnectRequest(): Uint8Array {
|
||||
const msg = new Uint8Array(1);
|
||||
const view = new DataView(msg.buffer);
|
||||
view.setUint8(0, Command.Disconnect);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of message.
|
||||
* @param msg The raw message data.
|
||||
*/
|
||||
export function getMessageType(msg: DataView): Command | ErrorMessage {
|
||||
// Technically, the first byte of an error message is the length, but it
|
||||
// is always 0x05 which is the same as the error message bytecode.
|
||||
return msg.getUint8(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an error message.
|
||||
* @param msg The raw message data.
|
||||
*/
|
||||
export function parseErrorResponse(msg: DataView): Command {
|
||||
// Error responses are ordered differently compared to command responses.
|
||||
if (msg.getUint8(2) === ErrorBytecode) {
|
||||
throw Error('expecting error');
|
||||
}
|
||||
if (msg.getUint8(0) !== 5) {
|
||||
throw Error('unexpected length');
|
||||
}
|
||||
if (msg.getUint8(4) !== ErrorCode.UnknownCommand) {
|
||||
// "command not recognized" is only possible error code
|
||||
throw Error('unexpected error code');
|
||||
}
|
||||
const command = msg.getUint8(3);
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an erase flash response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The result of the erase operation.
|
||||
*/
|
||||
export function parseEraseFlashResponse(msg: DataView): Result {
|
||||
if (msg.getUint8(0) !== Command.EraseFlash) {
|
||||
throw Error('expecting erase flash command');
|
||||
}
|
||||
const result = msg.getUint8(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a program flash response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The final checksum and the number of bytes written.
|
||||
*/
|
||||
export function parseProgramFlashResponse(msg: DataView): [number, number] {
|
||||
if (msg.getUint8(0) !== Command.ProgramFlash) {
|
||||
throw Error('expecting program flash command');
|
||||
}
|
||||
const checksum = msg.getUint8(1);
|
||||
const count = msg.getUint32(2, true);
|
||||
return [checksum, count];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an initialization response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The result of the initialization.
|
||||
*/
|
||||
export function parseInitLoaderResponse(msg: DataView): Result {
|
||||
if (msg.getUint8(0) !== Command.InitLoader) {
|
||||
throw Error('expecting init loader command');
|
||||
}
|
||||
const result = msg.getUint8(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an information response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The bootloader software version, the starting and ending addresses
|
||||
* of where firmware can be flashed, and the hub type identifier.
|
||||
*/
|
||||
export function parseGetInfoResponse(msg: DataView): [number, number, number, HubType] {
|
||||
if (msg.getUint8(0) !== Command.GetInfo) {
|
||||
throw Error('expecting get info command');
|
||||
}
|
||||
const version = msg.getUint32(1, true);
|
||||
const startAddress = msg.getUint32(5, true);
|
||||
const endAddress = msg.getUint32(9, true);
|
||||
const hubType = msg.getUint8(13);
|
||||
return [version, startAddress, endAddress, hubType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a checksum response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The checksum of the data that has been flashed so far.
|
||||
*/
|
||||
export function parseGetChecksumResponse(msg: DataView): number {
|
||||
if (msg.getUint8(0) !== Command.GetChecksum) {
|
||||
throw Error('expecting get checksum command');
|
||||
}
|
||||
const checksum = msg.getUint8(1);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a flash protection state response message.
|
||||
* @param msg The raw message data.
|
||||
* @returns The protection level
|
||||
*/
|
||||
export function parseGetFlashStateResponse(msg: DataView): ProtectionLevel {
|
||||
if (msg.getUint8(0) !== Command.GetFlashState) {
|
||||
throw Error('expecting get flash state command');
|
||||
}
|
||||
const level = msg.getUint8(1);
|
||||
return level;
|
||||
}
|
||||
Reference in New Issue
Block a user