Skip to content

Commit 5314a99

Browse files
committed
Add a new CLI command push for uploading program without running it.
1 parent 5f44290 commit 5314a99

File tree

2 files changed

+406
-1
lines changed

2 files changed

+406
-1
lines changed

pybricksdev/cli/__init__.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,114 @@ def is_pybricks_usb(dev):
236236
await hub.disconnect()
237237

238238

239+
class Push(Tool):
240+
def add_parser(self, subparsers: argparse._SubParsersAction):
241+
parser = subparsers.add_parser(
242+
"push",
243+
help="upload a Pybricks program without running it",
244+
)
245+
parser.tool = self
246+
parser.add_argument(
247+
"conntype",
248+
metavar="<connection type>",
249+
help="connection type: %(choices)s",
250+
choices=["ble", "usb", "ssh"],
251+
)
252+
parser.add_argument(
253+
"file",
254+
metavar="<file>",
255+
help="path to a MicroPython script or `-` for stdin",
256+
type=argparse.FileType(),
257+
)
258+
parser.add_argument(
259+
"-n",
260+
"--name",
261+
metavar="<name>",
262+
required=False,
263+
help="hostname or IP address for SSH connection; "
264+
"Bluetooth device name or Bluetooth address for BLE connection; "
265+
"serial port name for USB connection",
266+
)
267+
268+
async def run(self, args: argparse.Namespace):
269+
# Pick the right connection
270+
if args.conntype == "ssh":
271+
from pybricksdev.connections.ev3dev import EV3Connection
272+
273+
# So it's an ev3dev
274+
if args.name is None:
275+
print("--name is required for SSH connections", file=sys.stderr)
276+
exit(1)
277+
278+
device_or_address = socket.gethostbyname(args.name)
279+
hub = EV3Connection(device_or_address)
280+
elif args.conntype == "ble":
281+
from pybricksdev.ble import find_device as find_ble
282+
from pybricksdev.connections.pybricks import PybricksHubBLE
283+
284+
# It is a Pybricks Hub with BLE. Device name or address is given.
285+
print(f"Searching for {args.name or 'any hub with Pybricks service'}...")
286+
device_or_address = await find_ble(args.name)
287+
hub = PybricksHubBLE(device_or_address)
288+
elif args.conntype == "usb":
289+
from usb.core import find as find_usb
290+
291+
from pybricksdev.connections.pybricks import PybricksHubUSB
292+
from pybricksdev.usb import (
293+
LEGO_USB_VID,
294+
MINDSTORMS_INVENTOR_USB_PID,
295+
SPIKE_ESSENTIAL_USB_PID,
296+
SPIKE_PRIME_USB_PID,
297+
)
298+
299+
def is_pybricks_usb(dev):
300+
return (
301+
(dev.idVendor == LEGO_USB_VID)
302+
and (
303+
dev.idProduct
304+
in [
305+
SPIKE_PRIME_USB_PID,
306+
SPIKE_ESSENTIAL_USB_PID,
307+
MINDSTORMS_INVENTOR_USB_PID,
308+
]
309+
)
310+
and dev.product.endswith("Pybricks")
311+
)
312+
313+
device_or_address = find_usb(custom_match=is_pybricks_usb)
314+
315+
if device_or_address is not None:
316+
hub = PybricksHubUSB(device_or_address)
317+
else:
318+
from pybricksdev.connections.lego import REPLHub
319+
320+
hub = REPLHub()
321+
else:
322+
raise ValueError(f"Unknown connection type: {args.conntype}")
323+
324+
# Connect to the address and upload the script without running it
325+
await hub.connect()
326+
try:
327+
with _get_script_path(args.file) as script_path:
328+
# For PybricksHub, we use download_user_program to just upload without running
329+
if args.conntype in ["ble", "usb"]:
330+
from pybricksdev.compile import compile_multi_file
331+
332+
# Compile the script to mpy format
333+
mpy = await compile_multi_file(
334+
script_path, hub._mpy_abi_version or 6
335+
)
336+
# Upload without running
337+
await hub.download_user_program(mpy)
338+
# For EV3Connection, we just use download
339+
elif args.conntype == "ssh":
340+
await hub.download(script_path)
341+
else:
342+
raise RuntimeError("Unsupported hub type for push command")
343+
finally:
344+
await hub.disconnect()
345+
346+
239347
class Flash(Tool):
240348
def add_parser(self, subparsers: argparse._SubParsersAction):
241349
parser = subparsers.add_parser(
@@ -459,7 +567,7 @@ def main():
459567
help="the tool to use",
460568
)
461569

462-
for tool in Compile(), Run(), Flash(), DFU(), OAD(), LWP3(), Udev():
570+
for tool in Compile(), Run(), Push(), Flash(), DFU(), OAD(), LWP3(), Udev():
463571
tool.add_parser(subparsers)
464572

465573
argcomplete.autocomplete(parser)

0 commit comments

Comments
 (0)