Пример #1
0
    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
Пример #2
0
 def get_connected_device(self) -> SystemDevice:
     logger(__name__).info('Interrogating Cortex...')
     stm32 = STM32Device(self.port, do_negoitate=False)
     try:
         stm32.get(n_retries=1)
         return stm32
     except VEXCommError:
         return self
Пример #3
0
 def expose_bootloader(self):
     logger(__name__).info('Exposing bootloader')
     for _ in itertools.repeat(None, 5):
         self._tx_packet(0x25)
         time.sleep(0.1)
     self.port.read_all()
     time.sleep(0.3)
     return STM32Device(self.port, must_initialize=True)
Пример #4
0
    def write_program(self, file: typing.BinaryIO, **kwargs):
        logger(__name__).info('Writing program to Cortex')
        status = self.query_system()
        logger(__name__).info(status)
        if not status.flags | self.SystemStatusFlags.TETH_USB and not status.flags | self.SystemStatusFlags.DL_MODE:
            self.send_to_download_channel()

        bootloader = self.expose_bootloader()
        return bootloader.write_program(file, **kwargs)
Пример #5
0
 def stop(self, *args):
     if not self.alive.is_set():
         logger(__name__).warning('Stopping terminal')
         self.alive.set()
         self.device.destroy()
         if threading.current_thread() != self.transmitter_thread and self.transmitter_thread.is_alive():
             self.console.cleanup()
             self.console.cancel()
         logger(__name__).info('All done!')
Пример #6
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')
Пример #7
0
 def _txrx_ack_packet(self, command: int, timeout=0.1):
     """
             Goes through a send/receive cycle with a VEX device.
             Transmits the command with the optional additional payload, then reads and parses the outer layer
             of the response
             :param command: Command to send the device
             :param retries: Number of retries to attempt to parse the output before giving up and raising an error
             :return: Returns a dictionary containing the received command field and the payload. Correctly computes
             the payload length even if the extended command (0x56) is used (only applies to the V5).
             """
     tx = self._tx_packet(command)
     self._rx_ack(timeout=timeout)
     logger(__name__).debug('TX: {}'.format(bytes_to_str(tx)))
Пример #8
0
def main():
    try:
        ctx_obj = {}
        click_handler = pros.common.ui.log.PROSLogHandler(ctx_obj=ctx_obj)
        ctx_obj['click_handler'] = click_handler
        formatter = pros.common.ui.log.PROSLogFormatter(
            '%(levelname)s - %(name)s:%(funcName)s - %(message)s', ctx_obj)
        click_handler.setFormatter(formatter)
        logging.basicConfig(level=logging.WARNING, handlers=[click_handler])
        cli.main(prog_name='pros', obj=ctx_obj)
    except KeyboardInterrupt:
        click.echo('Aborted!')
    except Exception as e:
        logger(__name__).exception(e)
Пример #9
0
    def fetch_template(self, template: BaseTemplate, destination: str,
                       **kwargs) -> Template:
        if 'location' not in kwargs:
            logger(__name__).debug(
                f"Template not specified. Provided arguments: {kwargs}")
            raise KeyError('Location of local template must be specified.')
        location = kwargs['location']
        if os.path.isdir(location):
            location_dir = location
            if not os.path.isfile(os.path.join(location_dir, 'template.pros')):
                raise ConfigNotFoundException(
                    f'A template.pros file was not found in {location_dir}.')
            template_file = os.path.join(location_dir, 'template.pros')
        elif zipfile.is_zipfile(location):
            with zipfile.ZipFile(location) as zf:
                with click.progressbar(
                        length=len(zf.namelist()),
                        label=f"Extracting {location}") as progress_bar:
                    for file in zf.namelist():
                        zf.extract(file, path=destination)
                        progress_bar.update(1)
            template_file = os.path.join(destination, 'template.pros')
            location_dir = destination
        elif os.path.isfile(location):
            location_dir = os.path.dirname(location)
            template_file = location
        elif isinstance(template, ExternalTemplate):
            location_dir = template.directory
            template_file = template.save_file
        else:
            raise ValueError(
                f"The specified location was not a file or directory ({location})."
            )
        if location_dir != destination:
            n_files = len([
                os.path.join(dp, f) for dp, dn, fn in os.walk(location_dir)
                for f in fn
            ])
            with click.progressbar(length=n_files,
                                   label='Copying to local cache') as pb:

                def my_copy(*args):
                    pb.update(1)
                    shutil.copy2(*args)

                shutil.copytree(location_dir,
                                destination,
                                copy_function=my_copy)
        return ExternalTemplate(file=template_file)
Пример #10
0
 def read(self) -> Tuple[bytes, bytes]:
     msg = None, None
     while msg[0] is None or (msg[0] not in self.topics and not self._accept_all):
         while b'\0' not in self.buffer:
             self.buffer.extend(self.port.read(1))
             self.buffer.extend(self.port.read(-1))
         assert b'\0' in self.buffer
         msg, self.buffer = self.buffer.split(b'\0', 1)
         try:
             msg = cobs.decode(msg)
         except cobs.DecodeError:
             logger(__name__).warning(f'Could not decode bytes: {msg.hex()}')
         assert len(msg) >= 4
         msg = bytes(msg[:4]), bytes(msg[4:])
     return msg
Пример #11
0
    def write_program(self, file: typing.BinaryIO, **kwargs):
        action_string = ''
        if hasattr(file, 'name'):
            action_string += f' {Path(file.name).name}'
        action_string += f' to Cortex on {self.port}'
        ui.echo(f'Uploading {action_string}')

        logger(__name__).info('Writing program to Cortex')
        status = self.query_system()
        logger(__name__).info(status)
        if not status.flags | self.SystemStatusFlags.TETH_USB and not status.flags | self.SystemStatusFlags.DL_MODE:
            self.send_to_download_channel()

        bootloader = self.expose_bootloader()
        rv = bootloader.write_program(file, **kwargs)

        ui.finalize('upload', f'Finished uploading {action_string}')
        return rv
Пример #12
0
 def transmitter(self):
     try:
         while not self.alive.is_set() and self._transmitter_alive:
             try:
                 c = self.console.getkey()
             except KeyboardInterrupt:
                 c = '\x03'
             if self.alive.is_set():
                 break
             if c == '\x03' or not self.no_sigint:
                 self.stop()
                 break
             else:
                 self.device.write(c.encode(encoding='utf-8'))
                 self.console.write(c)
     except Exception as e:
         if not self.alive.is_set():
             logger(__name__).exception(e)
         else:
             logger(__name__).debug(e)
         self.stop()
     logger(__name__).info('Terminal transmitter dying')
Пример #13
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()
Пример #14
0
 def reader(self):
     if self.request_banner:
         try:
             self.device.write(b'pRb')
         except Exception as e:
             logger(__name__).exception(e)
     try:
         while not self.alive.is_set() and self._reader_alive:
             data = self.device.read()
             if not data:
                 continue
             if data[0] == b'sout':
                 text = decode_bytes_to_str(data[1])
             elif data[0] == b'serr':
                 text = '{}{}{}'.format(colorama.Fore.RED, decode_bytes_to_str(data[1]), colorama.Style.RESET_ALL)
             elif data[0] == b'kdbg':
                 text = '{}\n\nKERNEL DEBUG:\t{}{}\n'.format(colorama.Back.GREEN + colorama.Style.BRIGHT,
                                                             decode_bytes_to_str(data[1]),
                                                             colorama.Style.RESET_ALL)
             elif data[0] != b'':
                 text = '{}{}'.format(decode_bytes_to_str(data[0]), decode_bytes_to_str(data[1]))
             else:
                 text = "{}".format(decode_bytes_to_str(data[1]))
             self.console.write(text)
     except UnicodeError as e:
         logger(__name__).exception(e)
     except PortConnectionException:
         logger(__name__).warning(f'Connection to {self.device.name} broken')
         if not self.alive.is_set():
             self.stop()
     except Exception as e:
         if not self.alive.is_set():
             logger(__name__).exception(e)
         else:
             logger(__name__).debug(e)
         self.stop()
     logger(__name__).info('Terminal receiver dying')
Пример #15
0
 def query_system(self) -> SystemStatus:
     logger(__name__).info('Querying system information')
     rx = self._txrx_simple_struct(0x21, "<8B2x")
     status = CortexDevice.SystemStatus(rx)
     ui.finalize('cortex-status', status)
     return status
Пример #16
0
 def send_to_download_channel(self):
     logger(__name__).info('Sending to download channel')
     self._txrx_ack_packet(0x35, timeout=1.0)
Пример #17
0
 def perform_upgrade(self) -> UpgradeResult:
     logger(__name__).debug(self.__dict__)
     from click import launch
     return UpgradeResult(launch(self.info_url) == 0)