Ejemplo n.º 1
0
 def callback(ctx: click.Context, param: click.Parameter, value: str):
     import pros.conductor as c
     project_path = c.Project.find_project(value)
     if project_path is None:
         raise click.UsageError(
             f'{os.path.abspath(value or ".")} is not inside a PROS project. '
             f'Execute this command from within a PROS project or specify it '
             f'with --project project/path')
     return c.Project(project_path)
Ejemplo n.º 2
0
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
    if port == 'default':
        project_path = c.Project.find_project(os.getcwd())
        if project_path is None:
            v5_port = 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 = 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':
        ser = ports.SerialSharePort(port)
    else:
        ser = ports.DirectPort(port)
    if kwargs.get('raw', False):
        device = devices.RawStreamDevice(ser)
    else:
        device = 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')
Ejemplo n.º 3
0
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}')
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
def create_template(ctx, path: str, destination: str, do_zip: bool, **kwargs):
    """
    Create a template to be used in other projects

    Templates primarily consist of the following fields: name, version, and
    files to install.

    Templates have two types of files: system files and user files. User files
    are files in a template intended to be modified by users - they are not
    replaced during upgrades or removed by default when a library is uninstalled.
    System files are files that are for the "system." They get replaced every
    time the template is upgraded. The default PROS project is a template. The user
    files are files like src/opcontrol.c and src/initialize.c, and the system files
    are files like firmware/libpros.a and include/api.h.

    You should specify the --system and --user options multiple times to include
    more than one file. Both flags also accept glob patterns. When a glob pattern is
    provided and inside a PROS project, then all files that match the pattern that
    are NOT supplied by another template are included.


    Example usage:

    pros conduct create-template . libblrs 2.0.1 --system "firmware/*.a" --system "include/*.h"
    """
    project = c.Project.find_project(path, recurse_times=1)
    if project:
        project = c.Project(project)
        path = project.location
        if not kwargs['supported_kernels'] and kwargs['name'] != 'kernel':
            kwargs['supported_kernels'] = f'^{project.kernel}'
        kwargs['target'] = project.target
    if not destination:
        if os.path.isdir(path):
            destination = path
        else:
            destination = os.path.dirname(path)
    kwargs['system_files'] = list(kwargs['system_files'])
    kwargs['user_files'] = list(kwargs['user_files'])
    kwargs['metadata'] = {
        ctx.args[i][2:]: ctx.args[i + 1]
        for i in range(0,
                       int(len(ctx.args) / 2) * 2, 2)
    }

    def get_matching_files(globs: List[str]) -> Set[str]:
        matching_files: List[str] = []
        _path = os.path.normpath(path) + os.path.sep
        for g in [g for g in globs if glob.has_magic(g)]:
            files = glob.glob(f'{path}/{g}', recursive=True)
            files = filter(lambda f: os.path.isfile(f), files)
            files = [
                os.path.normpath(os.path.normpath(f).split(_path)[-1])
                for f in files
            ]
            matching_files.extend(files)

        # matches things like src/opcontrol.{c,cpp} so that we can expand to src/opcontrol.c and src/opcontrol.cpp
        pattern = re.compile(r'^([\w{}]+.){{((?:\w+,)*\w+)}}$'.format(
            os.path.sep.replace('\\', '\\\\')))
        for f in [os.path.normpath(f) for f in globs if not glob.has_magic(f)]:
            if re.match(pattern, f):
                matches = re.split(pattern, f)
                logger(__name__).debug(f'Matches on {f}: {matches}')
                matching_files.extend(
                    [f'{matches[1]}{ext}' for ext in matches[2].split(',')])
            else:
                matching_files.append(f)

        matching_files: Set[str] = set(matching_files)
        return matching_files

    matching_system_files: Set[str] = get_matching_files(
        kwargs['system_files'])
    matching_user_files: Set[str] = get_matching_files(kwargs['user_files'])

    matching_system_files: Set[
        str] = matching_system_files - matching_user_files

    # exclude existing project.pros and template.pros from the template,
    # and name@*.zip so that we don't redundantly include ZIPs
    exclude_files = {
        'project.pros', 'template.pros',
        *get_matching_files([f"{kwargs['name']}@*.zip"])
    }
    if project:
        exclude_files = exclude_files.union(project.list_template_files())
    matching_system_files = matching_system_files - exclude_files
    matching_user_files = matching_user_files - exclude_files

    def filename_remap(file_path: str) -> str:
        if os.path.dirname(file_path) == 'bin':
            return file_path.replace('bin', 'firmware', 1)
        return file_path

    kwargs['system_files'] = list(map(filename_remap, matching_system_files))
    kwargs['user_files'] = list(map(filename_remap, matching_user_files))

    if do_zip:
        if not os.path.isdir(
                destination) and os.path.splitext(destination)[-1] != '.zip':
            logger(__name__).error(
                f'{destination} must be a zip file or an existing directory.')
            return -1
        with tempfile.TemporaryDirectory() as td:
            template = ExternalTemplate(file=os.path.join(td, 'template.pros'),
                                        **kwargs)
            template.save()
            if os.path.isdir(destination):
                destination = os.path.join(destination,
                                           f'{template.identifier}.zip')
            with zipfile.ZipFile(destination, mode='w') as z:
                z.write(template.save_file, arcname='template.pros')

                for file in matching_user_files:
                    source_path = os.path.join(path, file)
                    dest_file = filename_remap(file)
                    if os.path.exists(source_path):
                        ui.echo(f'U: {file}' + (
                            f' -> {dest_file}' if file != dest_file else ''))
                        z.write(f'{path}/{file}', arcname=dest_file)
                for file in matching_system_files:
                    source_path = os.path.join(path, file)
                    dest_file = filename_remap(file)
                    if os.path.exists(source_path):
                        ui.echo(f'S: {file}' + (
                            f' -> {dest_file}' if file != dest_file else ''))
                        z.write(f'{path}/{file}', arcname=dest_file)
    else:
        if os.path.isdir(destination):
            destination = os.path.join(destination, 'template.pros')
        template = ExternalTemplate(file=destination, **kwargs)
        template.save()