fix: Omit program slot ID when not specified.

We don't have a UI for it, so we should assume no slot is chosen rather than default to the first slot.

This allows the user to select the slot on the hub without a front end UI.

This is compatible with the existing protocol.
This commit is contained in:
Laurens Valk
2025-05-19 09:50:32 +02:00
parent 0aeb53d1f6
commit 4ba8ed62ea
6 changed files with 26 additions and 16 deletions
+8 -6
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021-2024 The Pybricks Authors
// Copyright (c) 2021-2025 The Pybricks Authors
//
// Actions for Bluetooth Low Energy Pybricks service
@@ -85,11 +85,13 @@ export const sendLegacyStartReplCommand = createAction((id: number) => ({
*
* @since Pybricks Profile v1.4.0
*/
export const sendStartUserProgramCommand = createAction((id: number, slot: number) => ({
type: 'blePybricksServiceCommand.action.sendStartUserProgram',
id,
slot,
}));
export const sendStartUserProgramCommand = createAction(
(id: number, slot: number | null) => ({
type: 'blePybricksServiceCommand.action.sendStartUserProgram',
id,
slot,
}),
);
/**
* Action that requests to write user program metadata.
+10 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2024 The Pybricks Authors
// Copyright (c) 2020-2025 The Pybricks Authors
//
// Definitions related to the Pybricks Bluetooth low energy GATT service.
@@ -114,12 +114,20 @@ export function createStopUserProgramCommand(): Uint8Array {
* Parameters:
* - slot: Program identifier (one byte). Slots 0--127 are reserved for
* downloaded user programs. Slots 128--255 are for builtin user programs.
* If null, the hub will start the program slot selected on the hub.
*
* @since Pybricks Profile v1.4.0
*/
export function createStartUserProgramCommand(
slot: number | BuiltinProgramId,
slot: number | BuiltinProgramId | null,
): Uint8Array {
// Omit optional slot id to start currently active slot.
if (slot === null) {
const msg = new Uint8Array(1);
msg[0] = CommandType.StartUserProgram;
return msg;
}
const msg = new Uint8Array(2);
msg[0] = CommandType.StartUserProgram;
msg[1] = slot;
+2 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2024 The Pybricks Authors
// Copyright (c) 2020-2025 The Pybricks Authors
import { createAction } from '../actions';
import { FileFormat } from '../ble-pybricks-service/protocol';
@@ -26,7 +26,7 @@ export const downloadAndRun = createAction(
fileFormat: FileFormat | null,
useLegacyDownload: boolean,
useLegacyStartUserProgram: boolean,
slot: number,
slot: number | null,
) => ({
type: 'hub.action.downloadAndRun',
fileFormat,
+2 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2024 The Pybricks Authors
// Copyright (c) 2020-2025 The Pybricks Authors
import { AsyncSaga } from '../../test';
import { alertsShowAlert } from '../alerts/actions';
@@ -38,7 +38,7 @@ describe('downloadAndRun', () => {
saga.updateState({ editor: { isReady: true } });
saga.put(downloadAndRun(FileFormat.Mpy5, true, true, 0));
saga.put(downloadAndRun(FileFormat.Mpy5, true, true, null));
// first, it gets the value from the current editor
const editorValueAction = await saga.take();
+2 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2024 The Pybricks Authors
// Copyright (c) 2022-2025 The Pybricks Authors
import { act, cleanup } from '@testing-library/react';
import React from 'react';
@@ -43,7 +43,7 @@ test.each([
FileFormat.MultiMpy6,
legacyDownload,
legacyStartUserProgram,
0,
null,
),
);
},
+2 -2
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2024 The Pybricks Authors
// Copyright (c) 2020-2025 The Pybricks Authors
import React from 'react';
import { useDispatch } from 'react-redux';
@@ -48,7 +48,7 @@ const RunButton: React.FunctionComponent<RunButtonProps> = ({ id }) => {
preferredFileFormat,
useLegacyDownload,
useLegacyStartUserProgram,
0, // No slot UI yet
null, // No slot UI yet, downloading to active hub slot
),
)
}