@@ -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+
239347class 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