@@ -183,142 +183,174 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
183183 default = False ,
184184 )
185185
186- async def run (self , args : argparse .Namespace ):
187-
188- async def stay_connected_menu (hub : PybricksHub ):
186+ async def stay_connected_menu (self , hub : PybricksHub , args : argparse .Namespace ):
189187
190- class ResponseOptions (IntEnum ):
191- RECOMPILE_RUN = 0
192- RECOMPILE_DOWNLOAD = 1
193- RUN_STORED = 2
194- CHANGE_TARGET_FILE = 3
195- EXIT = 4
188+ if args .conntype == "ble" :
189+ from pybricksdev .ble import find_device as find_ble
190+ from pybricksdev .connections .pybricks import PybricksHubBLE
191+ else :
192+ from usb .core import find as find_usb
196193
197- async def reconnect_hub ():
198- if not await questionary .confirm (
199- "\n The hub has been disconnected. Would you like to re-connect?"
200- ).ask_async ():
201- exit ()
194+ from pybricksdev .connections .pybricks import PybricksHubUSB
195+ from pybricksdev .usb import (
196+ EV3_USB_PID ,
197+ LEGO_USB_VID ,
198+ MINDSTORMS_INVENTOR_USB_PID ,
199+ NXT_USB_PID ,
200+ SPIKE_ESSENTIAL_USB_PID ,
201+ SPIKE_PRIME_USB_PID ,
202+ )
202203
203- if args .conntype == "ble" :
204- print (
205- f"Searching for { args .name or 'any hub with Pybricks service' } ..."
204+ def is_pybricks_usb (dev ):
205+ return (
206+ (dev .idVendor == LEGO_USB_VID )
207+ and (
208+ dev .idProduct
209+ in [
210+ NXT_USB_PID ,
211+ EV3_USB_PID ,
212+ SPIKE_PRIME_USB_PID ,
213+ SPIKE_ESSENTIAL_USB_PID ,
214+ MINDSTORMS_INVENTOR_USB_PID ,
215+ ]
206216 )
207- device_or_address = await find_ble (args .name )
208- hub = PybricksHubBLE (device_or_address )
209- elif args .conntype == "usb" :
210- device_or_address = find_usb (custom_match = is_pybricks_usb )
211- hub = PybricksHubUSB (device_or_address )
212-
213- await hub .connect ()
214- # re-enable echoing of the hub's stdout
215- hub ._enable_line_handler = True
216- hub .print_output = True
217- return hub
218-
219- response_options = [
220- "Recompile and Run" ,
221- "Recompile and Download" ,
222- "Run Stored Program" ,
223- "Change Target File" ,
224- "Exit" ,
225- ]
226- # the entry that is selected by default when the menu opens
227- # this is overridden after the user picks an option
228- # so that the default option is always the one that was last chosen
229- default_response_option = (
230- ResponseOptions .RECOMPILE_RUN
231- if args .start
232- else ResponseOptions .RECOMPILE_DOWNLOAD
233- )
217+ and dev .product .endswith ("Pybricks" )
218+ )
234219
235- while True :
236- try :
237- if args .file is sys .stdin :
238- await hub .race_disconnect (
239- hub .race_power_button_press (
240- questionary .press_any_key_to_continue (
241- "The hub will stay connected and echo its output to the terminal. Press any key to exit."
242- ).ask_async ()
243- )
244- )
245- return
246- response = await hub .race_disconnect (
220+ class ResponseOptions (IntEnum ):
221+ RECOMPILE_RUN = 0
222+ RECOMPILE_DOWNLOAD = 1
223+ RUN_STORED = 2
224+ CHANGE_TARGET_FILE = 3
225+ EXIT = 4
226+
227+ async def reconnect_hub ():
228+ if not await questionary .confirm (
229+ "\n The hub has been disconnected. Would you like to re-connect?"
230+ ).ask_async ():
231+ exit ()
232+
233+ if args .conntype == "ble" :
234+ print (
235+ f"Searching for { args .name or 'any hub with Pybricks service' } ..."
236+ )
237+ device_or_address = await find_ble (args .name )
238+ hub = PybricksHubBLE (device_or_address )
239+ elif args .conntype == "usb" :
240+ device_or_address = find_usb (custom_match = is_pybricks_usb )
241+ hub = PybricksHubUSB (device_or_address )
242+
243+ await hub .connect ()
244+ # re-enable echoing of the hub's stdout
245+ hub ._enable_line_handler = True
246+ hub .print_output = True
247+ return hub
248+
249+ response_options = [
250+ "Recompile and Run" ,
251+ "Recompile and Download" ,
252+ "Run Stored Program" ,
253+ "Change Target File" ,
254+ "Exit" ,
255+ ]
256+ # the entry that is selected by default when the menu opens
257+ # this is overridden after the user picks an option
258+ # so that the default option is always the one that was last chosen
259+ default_response_option = (
260+ ResponseOptions .RECOMPILE_RUN
261+ if args .start
262+ else ResponseOptions .RECOMPILE_DOWNLOAD
263+ )
264+
265+ while True :
266+ try :
267+ if args .file is sys .stdin :
268+ await hub .race_disconnect (
247269 hub .race_power_button_press (
248- questionary .select (
249- f"Would you like to re-compile { os .path .basename (args .file .name )} ?" ,
250- response_options ,
251- default = (response_options [default_response_option ]),
270+ questionary .press_any_key_to_continue (
271+ "The hub will stay connected and echo its output to the terminal. Press any key to exit."
252272 ).ask_async ()
253273 )
254274 )
275+ return
276+ response = await hub .race_disconnect (
277+ hub .race_power_button_press (
278+ questionary .select (
279+ f"Would you like to re-compile { os .path .basename (args .file .name )} ?" ,
280+ response_options ,
281+ default = (response_options [default_response_option ]),
282+ ).ask_async ()
283+ )
284+ )
255285
256- default_response_option = response_options .index (response )
286+ default_response_option = response_options .index (response )
257287
258- match response_options .index (response ):
288+ match response_options .index (response ):
259289
260- case ResponseOptions .RECOMPILE_RUN :
261- with _get_script_path (args .file ) as script_path :
262- await hub .run (script_path , wait = True )
290+ case ResponseOptions .RECOMPILE_RUN :
291+ with _get_script_path (args .file ) as script_path :
292+ await hub .run (script_path , wait = True )
263293
264- case ResponseOptions .RECOMPILE_DOWNLOAD :
265- with _get_script_path (args .file ) as script_path :
266- await hub .download (script_path )
294+ case ResponseOptions .RECOMPILE_DOWNLOAD :
295+ with _get_script_path (args .file ) as script_path :
296+ await hub .download (script_path )
267297
268- case ResponseOptions .RUN_STORED :
269- if hub .fw_version < Version ("3.2.0-beta.4" ):
270- print (
271- "Running a stored program remotely is only supported in the hub firmware version >= v3.2.0."
272- )
273- else :
274- await hub .start_user_program ()
275- await hub ._wait_for_user_program_stop ()
276-
277- case ResponseOptions .CHANGE_TARGET_FILE :
278- args .file .close ()
279- while True :
280- try :
281- args .file = open (
282- await hub .race_disconnect (
283- hub .race_power_button_press (
284- questionary .path (
285- "What file would you like to use?"
286- ).ask_async ()
287- )
298+ case ResponseOptions .RUN_STORED :
299+ if hub .fw_version < Version ("3.2.0-beta.4" ):
300+ print (
301+ "Running a stored program remotely is only supported in the hub firmware version >= v3.2.0."
302+ )
303+ else :
304+ await hub .start_user_program ()
305+ await hub ._wait_for_user_program_stop ()
306+
307+ case ResponseOptions .CHANGE_TARGET_FILE :
308+ args .file .close ()
309+ while True :
310+ try :
311+ args .file = open (
312+ await hub .race_disconnect (
313+ hub .race_power_button_press (
314+ questionary .path (
315+ "What file would you like to use?"
316+ ).ask_async ()
288317 )
289318 )
290- break
291- except FileNotFoundError :
292- print ("The file was not found. Please try again." )
293- # send the new target file to the hub
294- with _get_script_path (args .file ) as script_path :
295- await hub .download (script_path )
296-
297- case _:
298- return
299-
300- except SyntaxError as e :
301- print ()
302- print ("A syntax error occurred while parsing your program:" )
303- print (e )
304- print ()
305-
306- except HubPowerButtonPressedError :
307- # This means the user pressed the button on the hub to re-start the
308- # current program, so the menu was canceled and we are now printing
309- # the hub stdout until the user program ends on the hub.
310- try :
311- await hub ._wait_for_power_button_release ()
312- await hub ._wait_for_user_program_stop ()
313-
314- except HubDisconnectError :
315- hub = await reconnect_hub ()
319+ )
320+ break
321+ except FileNotFoundError :
322+ print ("The file was not found. Please try again." )
323+ # send the new target file to the hub
324+ with _get_script_path (args .file ) as script_path :
325+ await hub .download (script_path )
326+
327+ case _:
328+ return
329+
330+ except SyntaxError as e :
331+ print ()
332+ print ("A syntax error occurred while parsing your program:" )
333+ print (e )
334+ print ()
335+
336+ except HubPowerButtonPressedError :
337+ # This means the user pressed the button on the hub to re-start the
338+ # current program, so the menu was canceled and we are now printing
339+ # the hub stdout until the user program ends on the hub.
340+ try :
341+ await hub ._wait_for_power_button_release ()
342+ await hub ._wait_for_user_program_stop ()
316343
317344 except HubDisconnectError :
318- # let terminal cool off before making a new prompt
319- await asyncio .sleep (0.3 )
320345 hub = await reconnect_hub ()
321346
347+ except HubDisconnectError :
348+ # let terminal cool off before making a new prompt
349+ await asyncio .sleep (0.3 )
350+ hub = await reconnect_hub ()
351+
352+ async def run (self , args : argparse .Namespace ):
353+
322354 # Pick the right connection
323355 if args .conntype == "ble" :
324356 from pybricksdev .ble import find_device as find_ble
@@ -382,13 +414,15 @@ def is_pybricks_usb(dev):
382414 await hub .download (script_path )
383415
384416 if args .stay_connected :
385- await stay_connected_menu (hub )
417+ await self . stay_connected_menu (hub )
386418
387419 except SyntaxError as e :
388- print ("\n A syntax error occurred while parsing your program:" )
389- print (e , "\n " )
420+ print ()
421+ print ("A syntax error occurred while parsing your program:" )
422+ print (e )
423+ print ()
390424 if args .stay_connected :
391- await stay_connected_menu (hub )
425+ await self . stay_connected_menu (hub )
392426
393427 finally :
394428 await hub .disconnect ()
0 commit comments