Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
07cacfc
change code sending script to stay connected to robot indefinitely
shaggysa Aug 27, 2025
9dfd844
add a menu to select options
shaggysa Aug 30, 2025
018af5a
move code resending to optional argument
shaggysa Aug 31, 2025
e1683b1
disable code resending when using usb
shaggysa Aug 31, 2025
aa342f4
bring longdemo.py back to original form
shaggysa Aug 31, 2025
1180a0d
fix poetry lock file
shaggysa Aug 31, 2025
e83e1e9
implement all suggestions
shaggysa Aug 31, 2025
004b3f5
move import statement to more suitable location
shaggysa Aug 31, 2025
4e878ee
move import statement to be in alphabetical order
shaggysa Aug 31, 2025
41e7a77
change unit tests to use the stay_connected arg
shaggysa Aug 31, 2025
dc1ecac
fix formatting issues
shaggysa Aug 31, 2025
c4e0745
fix additional formatting problem
shaggysa Aug 31, 2025
f76600c
Merge remote-tracking branch 'origin/master'
shaggysa Aug 31, 2025
3a595e4
fix the stdout echoing issue
shaggysa Sep 8, 2025
87ce5bc
fix some linting issues
shaggysa Sep 8, 2025
39d6675
fix more linting issues
shaggysa Sep 8, 2025
31561dc
linter fix V3
shaggysa Sep 8, 2025
888aa70
linter fix V4
shaggysa Sep 8, 2025
2b2ace0
Merge pull request #1 from shaggysa/stay-connected
shaggysa Sep 8, 2025
5bc6a3b
refactor and change the time to wait after power button is pressed
shaggysa Sep 9, 2025
f4dede9
make custom errors for the hub disconnect and power button press events
shaggysa Sep 9, 2025
52f055f
add proper handling of the new hub errors in the stay-connected code
shaggysa Sep 9, 2025
745a588
minor fixes and cleanup
shaggysa Sep 10, 2025
0633bfd
change uses of the exit function to return instead when possible
shaggysa Sep 10, 2025
476a1cd
Merge branch 'pybricks:master' into master
shaggysa Sep 10, 2025
2fa86cb
fix a windows specific issue where the wait_for_user_program_stop fun…
shaggysa Sep 10, 2025
3bec955
add a special case in the stay-connected implementation for when the …
shaggysa Sep 10, 2025
f088c1a
minor stability changes to the behavior when using the stay-connected…
shaggysa Sep 10, 2025
7429c39
remove unnecessary parameters from the hub wait_for methods
shaggysa Sep 10, 2025
76a0b89
CHANGELOG.md: Add entry for the --stay-connected arg
shaggysa Sep 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions pybricksdev/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import ContextManager, TextIO

import argcomplete
import questionary
from argcomplete.completers import FilesCompleter

from pybricksdev import __name__ as MODULE_NAME
Expand Down Expand Up @@ -160,6 +161,13 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
default=True,
)

parser.add_argument(
"--stay-connected",
help="Add a menu option to resend the code with bluetooth instead of disconnecting from the robot after the program ends.",
action=argparse.BooleanOptionalAction,
default=False,
)

async def run(self, args: argparse.Namespace):

# Pick the right connection
Expand Down Expand Up @@ -213,16 +221,32 @@ def is_pybricks_usb(dev):
# Connect to the address and run the script
await hub.connect()
try:
with _get_script_path(args.file) as script_path:
if args.start:
await hub.run(script_path, args.wait)
else:
await hub.download(script_path)
while True:
with _get_script_path(args.file) as script_path:
if args.start:
await hub.run(script_path, args.wait)
else:
await hub.download(script_path)

if not args.wait or not args.stay_connected:
break

resend = await questionary.select(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use race_disconnect() here to cancel the menu if the hub becomes disconnected rather than waiting for the user to respond.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would be a much cleaner option. Since we would be properly handling the disconnect event, maybe it would be a good idea to prompt the user to re-connect?

Copy link
Member

@dlech dlech Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like it would make the implementation quite a bit more complex. So I would say no. Or at least save that for implementing later.

After the program exits because of disconnect, the user should be able to just press up to get the command from the history in their terminal and run it again to reconnect.

"Would you like to resend your code?", choices=["Resend", "Exit"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Resend" sounds like it would resend exactly the same program again. But in that case, the program is already on the hub, so there is no reason to send it again. We can just send a command to start the program again without taking the time to send it.

But if the user locally modified their source code and we need to compile and download again, that is a different case. So perhaps we should have 3 menu items?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the case of compiling and downloading a changed program, we would want to catch the error if compiling fails (e.g. a syntax error) so that we don't disconnect in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current setup, it will automatically recompile whatever is at the file path. I think it makes sense to have that as the only option, but the text choice could be something more like "Recompile and Run". Maybe there could be a separate option for "Recompile and Download?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose. As long as the default matches the original command line option for --start/--no-start.

).ask_async()

if resend == "Exit":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if resend == "Exit":
if response == "Exit":

Would make more sense to me if the variable name wasn't one of the options.

break

except RuntimeError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is disconnection really the only possible thing that could happen to cause a RuntimeError?

print("The hub is no longer connected.")

finally:
await hub.disconnect()


class Flash(Tool):

def add_parser(self, subparsers: argparse._SubParsersAction):
parser = subparsers.add_parser(
"flash", help="flash firmware on a LEGO Powered Up device"
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ typing-extensions = ">=4.3.0"
reactivex = {version = ">=4.0.4", python = "<4"}
hidapi = ">=0.14.0"
pybricks = {version = ">=3", allow-prereleases = true, python = "<4"}
questionary = {version = ">=2.1.1", python = "<4"}

[tool.poetry.group.lint.dependencies]
black = ">=23,<25"
Expand Down
8 changes: 8 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ async def test_download_ble(self):
name="MyHub",
start=False,
wait=False,
stay_connected=False,
)

mock_hub_class = stack.enter_context(
Expand Down Expand Up @@ -135,6 +136,7 @@ async def test_download_usb(self):
name=None,
start=False,
wait=False,
stay_connected=False,
)

mock_hub_class = stack.enter_context(
Expand Down Expand Up @@ -175,6 +177,7 @@ async def test_download_stdin(self):
name="MyHub",
start=False,
wait=False,
stay_connected=False,
)

# Set up mocks using ExitStack
Expand Down Expand Up @@ -227,6 +230,7 @@ async def test_download_connection_error(self):
name="MyHub",
start=False,
wait=False,
stay_connected=False,
)

stack.enter_context(
Expand Down Expand Up @@ -273,6 +277,7 @@ async def test_run_ble(self):
name="MyHub",
start=True,
wait=True,
stay_connected=False,
)

mock_hub_class = stack.enter_context(
Expand Down Expand Up @@ -321,6 +326,7 @@ async def test_run_usb(self):
name=None,
start=True,
wait=True,
stay_connected=False,
)

mock_hub_class = stack.enter_context(
Expand Down Expand Up @@ -360,6 +366,7 @@ async def test_run_stdin(self):
name="MyHub",
start=True,
wait=True,
stay_connected=False,
)

# Set up mocks using ExitStack
Expand Down Expand Up @@ -414,6 +421,7 @@ async def test_run_connection_error(self):
name="MyHub",
start=False,
wait=True,
stay_connected=False,
)

stack.enter_context(
Expand Down