def update_slots(self): assert self.project.target == 'v5' if self.port.is_valid() and bool(self.port.value): from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort device = V5Device(DirectPort(self.port.value)) slot_options = [ f'{slot}' + ('' if program is None else f' (Currently: {program})') for slot, program in device.used_slots().items() ] else: slot_options = [str(i) for i in range(1, 9)] project_name = self.advanced_options['name'].value if 'slot' in self.project.upload_options: # first, see if the project has it specified in its upload options selected = slot_options[self.project.upload_options['slot'] - 1] else: # otherwise, try to do a name match matched_slots = [ i for i, slot in enumerate(slot_options) if slot.endswith(f'{project_name})') ] if len(matched_slots) > 0: selected = slot_options[matched_slots[0]] elif 'slot' in self.advanced_options: # or whatever the last value was selected = slot_options[ int(self.advanced_options['slot'].value[0]) - 1] else: # or just slot 1 selected = slot_options[0] self.advanced_options['slot'] = parameters.OptionParameter( selected, slot_options)
def cat_metadata(file_name: str, port: str, vid: int): """ Print metadata for a file """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) print(device.get_file_metadata_by_name(file_name, vid=vid))
def rm_file(file_name: str, port: str, vid: int, erase_all: bool): """ Remove a file from the flash filesystem """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) device.erase_file(file_name, vid=vid, erase_all=erase_all)
def write_file(file, port: str, remote_file: str, **kwargs): """ Write a file to the V5. """ from pros.serial.ports import DirectPort from pros.serial.devices.vex import V5Device port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) device.write_file(file=file, remote_file=remote_file or os.path.basename(file.name), **kwargs)
def stop(port: str): """ Stops a V5 program If FILE is unspecified or is a directory, then attempts to find the correct filename based on the PROS project """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) device.execute_program_file('', run=False)
def read_file(file_name: str, port: str, vid: int, source: str): """ Read file on the flash filesystem to stdout """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) device.read_file(file=click.get_binary_stream('stdout'), remote_file=file_name, vid=vid, target=source)
def ls_files(port: str, vid: int, options: int): """ List files on the flash filesystem """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) c = device.get_dir_count(vid=vid, options=options) for i in range(0, c): print(device.get_file_metadata_by_idx(i))
def rm_file(slot: int, port: str, vid: int): """ Remove a program from the flash filesystem """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 base_name = 'slot_' + str(slot) ser = DirectPort(port) device = V5Device(ser) device.erase_file(base_name + '.ini', vid=vid) device.erase_file(base_name + '.bin', vid=vid)
def run(slot: str, port: str): """ Run a V5 program """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort file = f'slot_{slot}.bin' import re if not re.match(r'[\w\.]{1,24}', file): logger(__name__).error('file must be a valid V5 filename') return 1 port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) device.execute_program_file(file, run=True)
def rm_file(file_name: str, port: str, vid: int): """ Remove a file from the V5 """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) c = device.get_dir_count(vid=vid) if file_name != None: device.erase_file(file_name, vid=vid) else: print('Invalid or no filename given')
def capture(file_name: str, port: str, force: bool = False): """ Take a screen capture of the display """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort import png import os port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) i_data, width, height = device.capture_screen() if i_data is None: print('Failed to capture screen from connected brain.') return -1 # Sanity checking and default values for filenames if file_name is None: import time time_s = time.strftime('%Y-%m-%d-%H%M%S') file_name = f'{time_s}_{width}x{height}_pros_capture.png' if file_name == '-': # Send the data to stdout to allow for piping print(i_data, end='') return if not file_name.endswith('.png'): file_name += '.png' if not force and os.path.exists(file_name): print(f'{file_name} already exists. Refusing to overwrite!') print( 'Re-run this command with the --force argument to overwrite existing files.' ) return -1 with open(file_name, 'wb') as file_: w = png.Writer(width, height, greyscale=False) w.write(file_, i_data) print(f'Saved screen capture to {file_name}')
def rm_all(port: str, vid: int): """ Remove all user files from the V5 """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) c = device.get_dir_count(vid=vid) files = [] for i in range(0, c): files.append(device.get_file_metadata_by_idx(i)['filename']) for file in files: device.erase_file(file, vid=vid)
def status(port: str): """ Print system information for the V5 """ from pros.serial.devices.vex import V5Device from pros.serial.ports import DirectPort port = resolve_v5_port(port, 'system') if not port: return -1 ser = DirectPort(port) device = V5Device(ser) if ismachineoutput(): print(device.status) else: print('Connected to V5 on {}'.format(port)) print('System version:', device.status['system_version']) print('CPU0 F/W version:', device.status['cpu0_version']) print('CPU1 SDK version:', device.status['cpu1_version']) print('System ID: 0x{:x}'.format(device.status['system_id']))
def upload(path: str, project: Optional[c.Project], port: str, **kwargs): """ Upload a binary to a microcontroller. [PATH] may be a directory or file. If a directory, finds a PROS project root and uploads the binary for the correct target automatically. If a file, then the file is uploaded. Note that --target must be specified in this case. [PORT] may be any valid communication port file, such as COM1 or /dev/ttyACM0. If left blank, then a port is automatically detected based on the target (or as supplied by the PROS project) """ import pros.serial.devices.vex as vex from pros.serial.ports import DirectPort args = [] if path is None or os.path.isdir(path): if project is None: project_path = c.Project.find_project(path or os.getcwd()) if project_path is None: raise click.UsageError( 'Specify a file to upload or set the cwd inside a PROS project' ) project = c.Project(project_path) path = os.path.join(project.location, project.output) if project.target == 'v5' and not kwargs['name']: kwargs['name'] = project.name # apply upload_options as a template options = dict(**project.upload_options) options.update(kwargs) kwargs = options kwargs[ 'target'] = project.target # enforce target because uploading to the wrong uC is VERY bad if 'program-version' in kwargs: kwargs['version'] = kwargs['program-version'] if 'name' not in kwargs: kwargs['name'] = project.name if 'target' not in kwargs: logger(__name__).debug( f'Target not specified. Arguments provided: {kwargs}') raise click.UsageError( 'Target not specified. specify a project (using the file argument) or target manually' ) if kwargs['target'] == 'v5': port = resolve_v5_port(port, 'system') elif kwargs['target'] == 'cortex': port = resolve_cortex_port(port) else: logger(__name__).debug(f"Invalid target provided: {kwargs['target']}") logger(__name__).debug('Target should be one of ("v5" or "cortex").') if not port: return -1 if kwargs['target'] == 'v5': if kwargs['name'] is None: kwargs['name'] = os.path.splitext(os.path.basename(path))[0] args.append(kwargs.pop('name').replace('@', '_')) kwargs['slot'] -= 1 if kwargs['run_after'] and kwargs['run_screen']: kwargs['run_after'] = vex.V5Device.FTCompleteOptions.RUN_SCREEN elif kwargs['run_after'] and not kwargs['run_screen']: kwargs[ 'run_after'] = vex.V5Device.FTCompleteOptions.RUN_IMMEDIATELY else: kwargs['run_after'] = vex.V5Device.FTCompleteOptions.DONT_RUN elif kwargs['target'] == 'cortex': pass # print what was decided ui.echo('Uploading {} to {} device on {}'.format(path, kwargs['target'], port), nl=False) if kwargs['target'] == 'v5': ui.echo(f' as {args[0]} to slot {kwargs["slot"] + 1}', nl=False) ui.echo('') logger(__name__).debug('Arguments: {}'.format(str(kwargs))) if not os.path.isfile(path) and path is not '-': logger(__name__).error( '{} is not a valid file! Make sure it exists (e.g. by building your project)' .format(path)) return -1 # Do the actual uploading! try: ser = DirectPort(port) device = None if kwargs['target'] == 'v5': device = vex.V5Device(ser) elif kwargs['target'] == 'cortex': device = vex.CortexDevice(ser).get_connected_device() with click.open_file(path, mode='rb') as pf: device.write_program(pf, *args, **kwargs) except Exception as e: logger(__name__).exception(e, exc_info=True) exit(1) ui.finalize('upload', f'Finished uploading {path} to {kwargs["target"]} on {port}')
def terminal(port: str, backend: str, **kwargs): """ Open a terminal to a serial port There are two possible backends for the terminal: "share" or "solo". In "share" mode, a server/bridge is created so that multiple PROS processes (such as another terminal or flash command) may communicate with the device. In the simpler solo mode, only one PROS process may communicate with the device. The default mode is "share", but "solo" may be preferred when "share" doesn't perform adequately. Note: share backend is not yet implemented. """ from pros.serial.devices.vex.v5_user_device import V5UserDevice from pros.serial.terminal import Terminal is_v5_user_joystick = False if port == 'default': project_path = c.Project.find_project(os.getcwd()) if project_path is None: v5_port, is_v5_user_joystick = resolve_v5_port(None, 'user', quiet=True) cortex_port = resolve_cortex_port(None, quiet=True) if ((v5_port is None) ^ (cortex_port is None)) or (v5_port is not None and v5_port == cortex_port): port = v5_port or cortex_port else: raise click.UsageError( 'You must be in a PROS project directory to enable default port selecting' ) else: project = c.Project(project_path) port = project.target if port == 'v5': port = None port, is_v5_user_joystick = resolve_v5_port(port, 'user') elif port == 'cortex': port = None port = resolve_cortex_port(port) kwargs['raw'] = True if not port: return -1 if backend == 'share': raise NotImplementedError('Share backend is not yet implemented') # ser = SerialSharePort(port) elif is_v5_user_joystick: logger(__name__).debug("it's a v5 joystick") ser = V5WirelessPort(port) else: logger(__name__).debug("not a v5 joystick") ser = DirectPort(port) if kwargs.get('raw', False): device = devices.RawStreamDevice(ser) else: device = devices.vex.V5UserDevice(ser) term = Terminal(device, request_banner=kwargs.pop('request_banner', True)) signal.signal(signal.SIGINT, term.stop) term.start() while not term.alive.is_set(): time.sleep(0.005) term.join() logger(__name__).info('CLI Main Thread Dying')
def upload(path: Optional[str], project: Optional[c.Project], port: str, **kwargs): """ Upload a binary to a microcontroller. [PATH] may be a directory or file. If a directory, finds a PROS project root and uploads the binary for the correct target automatically. If a file, then the file is uploaded. Note that --target must be specified in this case. [PORT] may be any valid communication port file, such as COM1 or /dev/ttyACM0. If left blank, then a port is automatically detected based on the target (or as supplied by the PROS project) """ import pros.serial.devices.vex as vex from pros.serial.ports import DirectPort if path is None or os.path.isdir(path): if project is None: project_path = c.Project.find_project(path or os.getcwd()) if project_path is None: raise click.UsageError( 'Specify a file to upload or set the cwd inside a PROS project' ) project = c.Project(project_path) path = os.path.join(project.location, project.output) if project.target == 'v5' and not kwargs['remote_name']: kwargs['remote_name'] = project.name # apply upload_options as a template options = dict(**project.upload_options) if 'slot' in options and kwargs.get('slot', None) is None: kwargs.pop('slot') elif kwargs.get('slot', None) is None: kwargs['slot'] = 1 options.update(kwargs) kwargs = options kwargs[ 'target'] = project.target # enforce target because uploading to the wrong uC is VERY bad if 'program-version' in kwargs: kwargs['version'] = kwargs['program-version'] if 'remote_name' not in kwargs: kwargs['remote_name'] = project.name if 'target' not in kwargs or kwargs['target'] is None: logger(__name__).debug( f'Target not specified. Arguments provided: {kwargs}') raise click.UsageError( 'Target not specified. specify a project (using the file argument) or target manually' ) if kwargs['target'] == 'v5': port = resolve_v5_port(port, 'system')[0] elif kwargs['target'] == 'cortex': port = resolve_cortex_port(port) else: logger(__name__).debug(f"Invalid target provided: {kwargs['target']}") logger(__name__).debug('Target should be one of ("v5" or "cortex").') if not port: raise dont_send( click.UsageError( 'No port provided or located. Make sure to specify --target if needed.' )) if kwargs['target'] == 'v5': if kwargs['remote_name'] is None: kwargs['remote_name'] = os.path.splitext(os.path.basename(path))[0] kwargs['remote_name'] = kwargs['remote_name'].replace('@', '_') kwargs['slot'] -= 1 if kwargs['run_after'] and kwargs['run_screen']: kwargs['run_after'] = vex.V5Device.FTCompleteOptions.RUN_SCREEN elif kwargs['run_after'] and not kwargs['run_screen']: kwargs[ 'run_after'] = vex.V5Device.FTCompleteOptions.RUN_IMMEDIATELY else: kwargs['run_after'] = vex.V5Device.FTCompleteOptions.DONT_RUN kwargs.pop('run_screen') elif kwargs['target'] == 'cortex': pass logger(__name__).debug('Arguments: {}'.format(str(kwargs))) # Do the actual uploading! try: ser = DirectPort(port) device = None if kwargs['target'] == 'v5': device = vex.V5Device(ser) elif kwargs['target'] == 'cortex': device = vex.CortexDevice(ser).get_connected_device() if project is not None: device.upload_project(project, **kwargs) else: with click.open_file(path, mode='rb') as pf: device.write_program(pf, **kwargs) except Exception as e: logger(__name__).exception(e, exc_info=True) exit(1)