Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d1c1309
cli/__init__.py: catch a syntax error when re-compiling the target file
shaggysa Oct 31, 2025
8b5eb9c
cli/__init__.py: slightly modify the error message warning
shaggysa Oct 31, 2025
e95fc0e
cli/__init__.py: refactor to allow the catching of syntax errors on t…
shaggysa Nov 1, 2025
c3f3c20
cli/__init__.py: clean up an if statement
shaggysa Nov 1, 2025
709e6f1
CHANGELOG.md: add an entry for the new changes to the run command
shaggysa Nov 26, 2025
aedee30
cli/__init__.py: avoid using newline escape characters
shaggysa Nov 28, 2025
7a1ca00
cli/__init__.py: move stay_connected_menu to separate function
shaggysa Nov 28, 2025
4d72397
Merge branch 'master' into feature/catch-syntax-error
dlech Nov 29, 2025
9f71013
explicitly pass args to the stay-connected menu from the run function
shaggysa Nov 29, 2025
972b4a7
Merge remote-tracking branch 'origin/feature/catch-syntax-error' into…
shaggysa Nov 29, 2025
b31f93c
Merge branch 'pybricks:master' into feature/catch-syntax-error
shaggysa Nov 29, 2025
1e0e8bc
catch a CalledProcessError and decode stderr
shaggysa Nov 29, 2025
d7cd4bd
fix typo in except block
shaggysa Nov 30, 2025
681acee
add a few unit tests related to stay_connected_menu
shaggysa Dec 1, 2025
ec917ab
make the open() call in the "Change Target File" option explicitly sp…
shaggysa Dec 1, 2025
45fed26
fix leaking coroutines in test_stay_connected_menu_interruptions
shaggysa Dec 1, 2025
9577550
add a test for the `Run Stored Program` option
shaggysa Dec 4, 2025
4e3f4b8
rearrange test order
shaggysa Dec 4, 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- The `run` command now catches syntax errors in the input file. ([pybricksdev#126])

[pybricksdev#126]: https://github.com/pybricks/pybricksdev/pull/126

## [2.3.0] - 2025-10-31

### Added
Expand Down
141 changes: 77 additions & 64 deletions pybricksdev/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from pybricksdev.connections.pybricks import (
HubDisconnectError,
HubPowerButtonPressedError,
PybricksHub,
)

PROG_NAME = (
Expand Down Expand Up @@ -184,70 +185,7 @@ def add_parser(self, subparsers: argparse._SubParsersAction):

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

# Pick the right connection
if args.conntype == "ble":
from pybricksdev.ble import find_device as find_ble
from pybricksdev.connections.pybricks import PybricksHubBLE

# It is a Pybricks Hub with BLE. Device name or address is given.
print(f"Searching for {args.name or 'any hub with Pybricks service'}...")
device_or_address = await find_ble(args.name)
hub = PybricksHubBLE(device_or_address)
elif args.conntype == "usb":
from usb.core import find as find_usb

from pybricksdev.connections.pybricks import PybricksHubUSB
from pybricksdev.usb import (
EV3_USB_PID,
LEGO_USB_VID,
MINDSTORMS_INVENTOR_USB_PID,
NXT_USB_PID,
SPIKE_ESSENTIAL_USB_PID,
SPIKE_PRIME_USB_PID,
)

def is_pybricks_usb(dev):
return (
(dev.idVendor == LEGO_USB_VID)
and (
dev.idProduct
in [
NXT_USB_PID,
EV3_USB_PID,
SPIKE_PRIME_USB_PID,
SPIKE_ESSENTIAL_USB_PID,
MINDSTORMS_INVENTOR_USB_PID,
]
)
and dev.product.endswith("Pybricks")
)

device_or_address = find_usb(custom_match=is_pybricks_usb)

if device_or_address is None:
print("Pybricks Hub not found.", file=sys.stderr)
exit(1)

hub = PybricksHubUSB(device_or_address)
else:
raise ValueError(f"Unknown connection type: {args.conntype}")

# 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 or args.stay_connected)
else:
if args.stay_connected:
# if the user later starts the program by pressing the button on the hub,
# we still want the hub stdout to print to Python's stdout
hub.print_output = True
hub._enable_line_handler = True
await hub.download(script_path)

if not args.stay_connected:
return
async def stay_connected_menu(hub: PybricksHub):

class ResponseOptions(IntEnum):
RECOMPILE_RUN = 0
Expand Down Expand Up @@ -359,6 +297,10 @@ async def reconnect_hub():
case _:
return

except SyntaxError as e:
print("\nA syntax error occurred while parsing your program:")
print(e, "\n")

except HubPowerButtonPressedError:
# This means the user pressed the button on the hub to re-start the
# current program, so the menu was canceled and we are now printing
Expand All @@ -375,6 +317,77 @@ async def reconnect_hub():
await asyncio.sleep(0.3)
hub = await reconnect_hub()

# Pick the right connection
if args.conntype == "ble":
from pybricksdev.ble import find_device as find_ble
from pybricksdev.connections.pybricks import PybricksHubBLE

# It is a Pybricks Hub with BLE. Device name or address is given.
print(f"Searching for {args.name or 'any hub with Pybricks service'}...")
device_or_address = await find_ble(args.name)
hub = PybricksHubBLE(device_or_address)
elif args.conntype == "usb":
from usb.core import find as find_usb

from pybricksdev.connections.pybricks import PybricksHubUSB
from pybricksdev.usb import (
EV3_USB_PID,
LEGO_USB_VID,
MINDSTORMS_INVENTOR_USB_PID,
NXT_USB_PID,
SPIKE_ESSENTIAL_USB_PID,
SPIKE_PRIME_USB_PID,
)

def is_pybricks_usb(dev):
return (
(dev.idVendor == LEGO_USB_VID)
and (
dev.idProduct
in [
NXT_USB_PID,
EV3_USB_PID,
SPIKE_PRIME_USB_PID,
SPIKE_ESSENTIAL_USB_PID,
MINDSTORMS_INVENTOR_USB_PID,
]
)
and dev.product.endswith("Pybricks")
)

device_or_address = find_usb(custom_match=is_pybricks_usb)

if device_or_address is None:
print("Pybricks Hub not found.", file=sys.stderr)
exit(1)

hub = PybricksHubUSB(device_or_address)
else:
raise ValueError(f"Unknown connection type: {args.conntype}")

# 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 or args.stay_connected)
else:
if args.stay_connected:
# if the user later starts the program by pressing the button on the hub,
# we still want the hub stdout to print to Python's stdout
hub.print_output = True
hub._enable_line_handler = True
await hub.download(script_path)

if args.stay_connected:
await stay_connected_menu(hub)

except SyntaxError as e:
print("\nA syntax error occurred while parsing your program:")
print(e, "\n")
if args.stay_connected:
await stay_connected_menu(hub)

finally:
await hub.disconnect()

Expand Down