def read_memory(self, address: int, n_bytes: int): logger(__name__).info(f'STM32: Read {n_bytes} fromo 0x{address:x}') assert 255 >= n_bytes > 0 self._txrx_command(0x11) self._txrx_command(struct.pack('>I', address)) self._txrx_command(n_bytes) return self.port.read(n_bytes)
def extended_erase_special(self, command: int): logger(__name__).info(f'STM32: Extended special erase: {command:x}') if not self.commands[6] == 0x44: raise IOError('Extended erase not supported on this device (only standard erase)') assert 0xfffd <= command <= 0xffff self._txrx_command(0x44) self._txrx_command(struct.pack('>H', command))
def write_memory(self, start_address: int, data: bytes): logger(__name__).info(f'STM32: Write {len(data)} to 0x{start_address:x}') assert 0 < len(data) <= 256 if len(data) % 4 != 0: data = data + (b'\0' * (4 - (len(data) % 4))) self._txrx_command(0x31) self._txrx_command(struct.pack('>I', start_address)) self._txrx_command(bytes([len(data) - 1, *data]))
def erase_memory(self, page_numbers: List[int]): logger(__name__).info(f'STM32: Erase pages: {page_numbers}') if not self.commands[6] == 0x43: raise VEXCommError('Standard erase not supported on this device (only extended erase)') assert 0 < len(page_numbers) <= 255 assert all([0 <= p <= 255 for p in page_numbers]) self._txrx_command(0x43) self._txrx_command(bytes([len(page_numbers) - 1, *page_numbers]))
def extended_erase(self, page_numbers: List[int]): logger(__name__).info(f'STM32: Extended Erase pages: {page_numbers}') if not self.commands[6] == 0x44: raise IOError('Extended erase not supported on this device (only standard erase)') assert 0 < len(page_numbers) < 0xfff0 assert all([0 <= p <= 0xffff for p in page_numbers]) self._txrx_command(0x44) self._txrx_command(bytes([len(page_numbers) - 1, *struct.pack(f'>{len(page_numbers)}H', *page_numbers)]))
def go(self, start_address: int): logger(__name__).info(f'STM32: Go 0x{start_address:x}') self._txrx_command(0x21) try: self._txrx_command(struct.pack('>I', start_address), timeout=5.) except VEXCommError: logger(__name__).warning('STM32 Bootloader did not acknowledge GO command. ' 'The program may take a moment to begin running ' 'or the device should be rebooted.')
def update_remote_templates(self, **_): import requests response = requests.get(self.location) if response.status_code == 200: self.remote_templates = jsonpickle.decode(response.text) else: logger(__name__).warning( f'Unable to access {self.name} ({self.location}): {response.status_code}' ) self.last_remote_update = datetime.now()
def notify(cls, uuid, event, *args, **kwargs): """ Triggers an Observable given its UUID. See arguments for Observable.trigger """ if isinstance(uuid, Observable): uuid = uuid.uuid if uuid in _uuid_table: _uuid_table[uuid].trigger(event, *args, **kwargs) else: logger(__name__).warning( f'Could not find an Observable to notify with UUID: {uuid}', sentry=True)
def create_serial_port(port_name: str, timeout: Optional[float] = 1.0) -> serial.Serial: try: logger(__name__).debug(f'Opening serial port {port_name}') port = serial.Serial(port_name, baudrate=115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE) port.timeout = timeout port.inter_byte_timeout = 0.2 return port except serial.SerialException as e: if any(msg in str(e) for msg in [ 'Access is denied', 'Errno 16', 'Errno 13' ]): tb = sys.exc_info()[2] raise dont_send(ConnectionRefusedException(port_name, e).with_traceback(tb)) else: raise e
def __init__(self, port: BasePort, must_initialize: bool = False, do_negoitate: bool = True): super().__init__(port) self.commands = bytes([0x00, 0x01, 0x02, 0x11, 0x21, 0x31, 0x43, 0x63, 0x73, 0x82, 0x92]) if do_negoitate: # self.port.write(b'\0' * 255) if must_initialize: self._txrx_command(0x7f, checksum=False) try: self.get(n_retries=0) except: logger(__name__).info('Sending bootloader initialization') time.sleep(0.01) self.port.rts = 0 for _ in itertools.repeat(None, times=3): time.sleep(0.01) self._txrx_command(0x7f, checksum=False) time.sleep(0.01) self.get()
def _txrx_command(self, command: Union[int, bytes], timeout: float = 0.01, checksum: bool = True): self.port.read_all() if isinstance(command, bytes): message = command + (bytes([reduce(operator.xor, command, 0x00)]) if checksum else bytes([])) elif isinstance(command, int): message = bytearray([command, ~command & 0xff] if checksum else [command]) else: raise ValueError(f'Expected command to be bytes or int but got {type(command)}') logger(__name__).debug(f'STM32 TX: {bytes_to_str(message)}') self.port.write(message) self.port.flush() start_time = time.time() while time.time() - start_time < timeout: data = self.port.read(1) if data and len(data) == 1: logger(__name__).debug(f'STM32 RX: {data[0]} =?= {self.ACK_BYTE}') if data[0] == self.ACK_BYTE: return raise VEXCommError(f"Device never ACK'd to {command}", command)
def read(self, n_bytes: int = 0) -> bytes: try: if n_bytes <= 0: self.buffer.extend(self.serial.read_all()) msg = bytes(self.buffer) self.buffer = bytearray() return msg else: if len(self.buffer) < n_bytes: self.buffer.extend(self.serial.read(n_bytes - len(self.buffer))) if len(self.buffer) < n_bytes: msg = bytes(self.buffer) self.buffer = bytearray() else: msg, self.buffer = bytes(self.buffer[:n_bytes]), self.buffer[n_bytes:] return msg except serial.SerialException as e: logger(__name__).debug(e) raise PortConnectionException(e)
def get_remote_templates(self, auto_check_freq: Optional[timedelta] = None, force_check: bool = False, **kwargs): if auto_check_freq is None: auto_check_freq = getattr(self, 'update_frequency', cli_config().update_frequency) logger(__name__).info( f'Last check of {self.name} was {self.last_remote_update} ' f'({datetime.now() - self.last_remote_update} vs {auto_check_freq}).' ) if force_check or datetime.now( ) - self.last_remote_update > auto_check_freq: with ui.Notification(): ui.echo(f'Updating {self.name}... ', nl=False) self.update_remote_templates(**kwargs) ui.echo('Done', color='green') for t in self.remote_templates: t.metadata['origin'] = self.name return self.remote_templates
def perform_upgrade(self) -> UpgradeResult: instructions: UpgradeInstruction = self.platform_instructions.get( self.platform, NothingInstruction()) logger(__name__).debug(self.__dict__) logger(__name__).debug(f'Platform: {self.platform}') logger(__name__).debug(instructions.__dict__) return instructions.perform_upgrade()
def test(): ui.echo('Hello World!') with ui.Notification(): ui.echo('Hello from another box') ui.echo('Back on the other one', nl=False) ui.echo('Whoops I missed a newline') with ui.Notification(): ui.echo('Yet another box') with ui.progressbar(range(20)) as bar: for _ in bar: time.sleep(0.1) ui.echo('more below the ', nl=False) ui.echo('progressbar') ui.echo('Back in the other notification') logger(__name__).warning('Hello') try: raise Exception('Hey') except Exception as e: logger(__name__).exception(e) ui.finalize('test', {'hello': 'world'}, human_prefix='Created ')
def get_manifest(self, force: bool = False) -> UpgradeManifestV1: if not force and not self.has_stale_manifest: return self._manifest ui.echo('Fetching upgrade manifest...') import requests import jsonpickle import json channel_url = f'https://purduesigbots.github.io/pros-mainline/{self.release_channel.value}' self._manifest = None manifest_urls = [ f"{channel_url}/{manifest.__name__}.json" for manifest in manifests ] for manifest_url in manifest_urls: resp = requests.get(manifest_url) if resp.status_code == 200: try: self._manifest = jsonpickle.decode(resp.text, keys=True) logger(__name__).debug(self._manifest) self._last_check = datetime.now() self.save() break except json.decoder.JSONDecodeError as e: logger(__name__).warning( f'Failed to decode {manifest_url}') logger(__name__).debug(e) else: logger(__name__).debug( f'Failed to get {manifest_url} ({resp.status_code})') if not self._manifest: manifest_list = "\n".join(manifest_urls) logger(__name__).warning( f'Could not access any upgrade manifests from any of:\n{manifest_list}' ) return self._manifest
def get(self): logger(__name__).info('STM32: Get') self._txrx_command(0x00) n_bytes = self.port.read(1)[0] assert n_bytes == 11 data = self.port.read(n_bytes + 1) logger(__name__).info(f'STM32 Bootloader version 0x{data[0]:x}') self.commands = data[1:] logger(__name__).debug(f'STM32 Bootloader commands are: {bytes_to_str(data[1:])}') assert self.port.read(1)[0] == self.ACK_BYTE
def has_stale_manifest(self): if self._manifest is None: logger(__name__).debug( 'Upgrade manager\'s manifest is nonexistent') if datetime.now() - self._last_check > cli_config().update_frequency: logger(__name__).debug( f'Upgrade manager\'s last check occured at {self._last_check}.' ) logger(__name__).debug( f'Was longer ago than update frequency ({cli_config().update_frequency}) allows.' ) return (self._manifest is None) or (datetime.now() - self._last_check > cli_config().update_frequency)
def trigger(self, event, *args, **kw): logger(__name__).debug( f'Triggered {self.uuid} ({type(self).__name__}) "{event}" event: {args} {kw}' ) return super().trigger(event, *args, **kw)
def destroy(self): logger(__name__).debug( f'Destroying {self.__class__.__name__} to {self.serial.name}') self.serial.close()
def list_all_comports(): ports = list_ports.comports() logger(__name__).debug('Connected: {}'.format(';'.join([str(p.__dict__) for p in ports]))) return ports
def erase_all(self): logger(__name__).info('STM32: Erase all pages') if not self.commands[6] == 0x43: raise VEXCommError('Standard erase not supported on this device (only extended erase)') self._txrx_command(0x43) self._txrx_command(0xff)
def commit(self, label: str = 'Committing transaction', remove_empty_directories: bool = True): with ui.progressbar(length=len(self._rm_files) + len(self._add_files), label=label) as pb: for file in sorted(self._rm_files, key=lambda p: p.count('/') + p.count('\\'), reverse=True): file_path = os.path.join(self.location, file) if os.path.isfile(file_path): logger(__name__).info(f'Removing {file}') os.remove(os.path.join(self.location, file)) else: logger(__name__).info(f'Not removing nonexistent {file}') pardir = os.path.abspath(os.path.join(file_path, os.pardir)) while remove_empty_directories and len( os.listdir(pardir)) == 0: logger(__name__).info( f'Removing {os.path.relpath(pardir, self.location)}') os.rmdir(pardir) pardir = os.path.abspath(os.path.join(pardir, os.pardir)) if pardir == self.location: # Don't try and recursively delete folders outside the scope of the # transaction directory break pb.update(1) for file in self._add_files: source = os.path.join(self._add_srcs[file], file) destination = os.path.join(self.location, file) if os.path.isfile(source): if not os.path.isdir(os.path.dirname(destination)): logger(__name__).debug( f'Creating directories: f{destination}') os.makedirs(os.path.dirname(destination), exist_ok=True) logger(__name__).info(f'Adding {file}') shutil.copy(os.path.join(self._add_srcs[file], file), os.path.join(self.location, file)) else: logger(__name__).info( f"Not copying {file} because {source} doesn't exist.") pb.update(1)
def get_read_protection_status(self): logger(__name__).info('STM32: Get ID & Read Protection Status') self._txrx_command(0x01) data = self.port.read(3) logger(__name__).debug(f'STM32 Bootloader Get Version & Read Protection Status is: {bytes_to_str(data)}') assert self.port.read(1)[0] == self.ACK_BYTE
def get_id(self): logger(__name__).info('STM32: Get PID') self._txrx_command(0x02) n_bytes = self.port.read(1)[0] pid = self.port.read(n_bytes + 1) logger(__name__).debug(f'STM32 Bootloader PID is {pid}')