From cedb5bfe811a490688b0798d240b4ccede646857 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Fri, 11 Jun 2021 22:19:09 +0200 Subject: [PATCH] Rewrite eepprog.py using the Click framework --- client/eepprog.py | 167 ++++++++--------------------- client/helpers/CliContext.py | 10 ++ client/helpers/EepromProgrammer.py | 52 +++++++++ client/helpers/Logger.py | 57 ++++++++++ client/helpers/__init__.py | 3 + client/requirements.txt | 2 + 6 files changed, 171 insertions(+), 120 deletions(-) create mode 100644 client/helpers/CliContext.py create mode 100644 client/helpers/EepromProgrammer.py create mode 100644 client/helpers/Logger.py create mode 100644 client/helpers/__init__.py create mode 100644 client/requirements.txt diff --git a/client/eepprog.py b/client/eepprog.py index c127fbf..58837d0 100755 --- a/client/eepprog.py +++ b/client/eepprog.py @@ -1,137 +1,64 @@ #!/usr/bin/env python3 -import sys -import getopt -import serial +import click -# Global variables for program parameters -verbose = False -serial_device = "/dev/ttyUSB0" -serial_baudrate = 9600 -command = "" - -# Valid commands -valid_commands = ("test") +from helpers import CliContext, Logger, EepromProgrammer -def usage(): - """Prints help text.""" +@click.group() +@click.option('--device', '-d', default='/dev/ttyUSB0', show_default=True, + metavar='DEVICE', help="Set the serial device") +@click.option('--baud', '-b', default=38400, show_default=True, + metavar='BAUDRATE', help="Set the baud rate of the serial device") +@click.option('--verbose', '-v', is_flag=True, help='Print debug output') +@click.pass_context +def eepprog(ctx: click.Context, device: str, baud: int, verbose: bool): + # Create dependencies + logger = Logger(verbose=verbose) + eeprom_programmer = EepromProgrammer(logger=logger, device=device, baudrate=baud) - print(f""" -Usage: {sys.argv[0]} [OPTIONS] COMMAND [ARGS] + logger.debug("Creating CLI context (device: {}, bauds: {})".format(device, baud)) -Commands: - test first test command... + # Create CLI context + ctx.obj = CliContext( + logger=logger, + eeprom_programmer=eeprom_programmer, + ) -Options: - -h, --help show this help message and exit - -v, --verbose be verbose - -d DEVICE, --device=DEVICE use DEVICE as serial device - (default: /dev/ttyUSB0) -"""[1:-1]) + # Define clean up functions + ctx.call_on_close(eeprom_programmer.close) -def parse_args(): - """Parse command line arguments and set global variables.""" +@eepprog.command('hello', short_help='Say hello. :)') +@click.pass_obj +def hello(context: CliContext): + """ + Say hello. :) - # Global variables for program options (yes, yes, I know...) - global verbose, serial_device, serial_baudrate, command - - try: - # Try to parse argument strings - optlist, args = getopt.gnu_getopt( - sys.argv[1:], - "hvd:", - ["help", "verbose", "device="] - ) - except getopt.GetoptError as err: - # Invalid option, print help and exit - print(err, "\n") - usage() - sys.exit(2) - - # Parse all the options - for opt, val in optlist: - if opt == "-h" or opt == "--help": - # Print help - usage() - sys.exit() - - elif opt == "-v" or opt == "--verbose": - # Verbose - verbose = True - - elif opt == "-d" or opt == "--device": - # Set device filename - serial_device = val - - else: - assert False, "unhandled option" - - # Parse command argument - if len(args) == 0: - print("missing command\n") - usage() - sys.exit(2) - else: - # Get command and remove argument - command = args.pop(0) - - # Check if command is valid - if command not in valid_commands: - print("invalid command '" + command + "'\n") - usage() - sys.exit(2) - - # Check if command has valid arguments - # TODO + Just a test command that tests the logger. + """ + context.logger.debug('hello hello', 413, 'hellooo') + context.logger.info('hello!') + context.logger.info('the same', click.style('but in green!', fg='green')) + context.logger.warn('this is fine') + context.logger.error('ohno') + context.logger.error('error, but a friendly one :)', fg='green') + context.logger.success('yay!') -def setup_serial(): - """Setup serial device.""" +@eepprog.command('test', short_help="Send INIT command to the programmer and read answer") +@click.pass_obj +def test(context: CliContext): + """ + Send an 'INIT' command to the programmer and read the answer. - if verbose: - print(f"setting up serial device '{serial_device}' with baudrate " - f"{serial_baudrate}") + Test command for debugging. + """ + context.eeprom_programmer.test() - # Setup serial device - ser = serial.Serial(serial_device, serial_baudrate) - return ser +# TODO command: list-devices -> serial.tools.list_ports +# TODO shell: Run an interactive shell -def command_test(ser): - """Command 'test': Does some testing.""" - - if verbose: - print("running command 'test' ...") - - # Write a test command - # TODO do a HELLO first - ser.write(b"TESTREAD\n") - - # Just read some stuff - while True: - print("read: ", ser.readline(80)) - - -def main(): - """Main function. Does the thing.""" - - # Parse program arguments - parse_args() - - # Setup serial device - ser = setup_serial() - - # Run command - if command == "test": - command_test(ser) - else: - assert False, "unhandled command" - - # Close serial device - ser.close() - - -# Run program -main() +if __name__ == '__main__': + eepprog() diff --git a/client/helpers/CliContext.py b/client/helpers/CliContext.py new file mode 100644 index 0000000..a463124 --- /dev/null +++ b/client/helpers/CliContext.py @@ -0,0 +1,10 @@ +from . import Logger, EepromProgrammer + + +class CliContext: + logger: Logger + eeprom_programmer: EepromProgrammer + + def __init__(self, logger: Logger, eeprom_programmer: EepromProgrammer): + self.logger = logger + self.eeprom_programmer = eeprom_programmer diff --git a/client/helpers/EepromProgrammer.py b/client/helpers/EepromProgrammer.py new file mode 100644 index 0000000..d529ddc --- /dev/null +++ b/client/helpers/EepromProgrammer.py @@ -0,0 +1,52 @@ +from serial import Serial +from typing import Optional + +from . import Logger + + +class EepromProgrammer: + # Dependencies + logger: Logger + serial: Optional[Serial] + + # Settings + _device_file: str + _baudrate: int + + def __init__(self, logger: Logger, device: str, baudrate): + self.logger = logger + self.serial = None + + self._device_file = device + self._baudrate = baudrate + + # TODO + def open(self): + """ + Open and setup serial port. + """ + assert self.serial is None, 'Serial port is already opened!' + + self.logger.debug("Setting up serial device '{}' with baudrate {}".format(self._device_file, self._baudrate)) + self.serial = Serial(self._device_file, self._baudrate) + + def close(self): + if self.serial is None: + self.logger.debug("Serial port is already closed") + return + + self.logger.debug("Closing serial port") + self.serial.close() + + # TODO + def test(self): + # Open serial port + if self.serial is None: + self.open() + + # Write a test command + self.logger.info("Sending 'INIT' ...") + self.serial.write(b"INIT\n") + + # Just read some stuff + self.logger.info("Read line: ", self.serial.readline(80)) diff --git a/client/helpers/Logger.py b/client/helpers/Logger.py new file mode 100644 index 0000000..f7c9382 --- /dev/null +++ b/client/helpers/Logger.py @@ -0,0 +1,57 @@ +from typing import Any +import click + + +class Logger: + verbose: bool + + def __init__(self, verbose: bool = False): + self.verbose = verbose + + @staticmethod + def _log(prefix: str = 'LOG', message: Any = '', nl: bool = True, **kwargs) -> None: + text = '{}: {}'.format(prefix, message) if message else '' + click.secho(text, nl=nl, **kwargs) + + @staticmethod + def _join(*args, delimiter: str = ' ') -> str: + return delimiter.join([str(arg) for arg in args]) + + def debug(self, *args, **kwargs) -> None: + """ + Write a message to stdout only if the '--verbose' option is set (in gray). + """ + if self.verbose: + kwargs.setdefault('fg', 'bright_black') + self._log('DEBUG', self._join(*args), **kwargs) + + def info(self, *args, **kwargs) -> None: + """ + Write a message to stdout (in white). + """ + kwargs.setdefault('fg', 'white') + self._log('INFO', self._join(*args), **kwargs) + + def warn(self, *args, **kwargs) -> None: + """ + Write a warning message to stderr (in yellow). + """ + kwargs.setdefault('fg', 'yellow') + self._log('WARNING', self._join(*args), **kwargs) + + def error(self, *args, **kwargs) -> None: + """ + Write an error message to stderr (in red). + """ + kwargs.setdefault('fg', 'red') + self._log('ERROR', self._join(*args), err=True, **kwargs) + + def success(self, *args, **kwargs) -> None: + """ + Write a success message stdout (in green). + """ + kwargs.setdefault('fg', 'green') + self._log('SUCCESS', self._join(*args), **kwargs) + + +logger = Logger() diff --git a/client/helpers/__init__.py b/client/helpers/__init__.py new file mode 100644 index 0000000..7643789 --- /dev/null +++ b/client/helpers/__init__.py @@ -0,0 +1,3 @@ +from .CliContext import CliContext +from .Logger import Logger +from .EepromProgrammer import EepromProgrammer diff --git a/client/requirements.txt b/client/requirements.txt new file mode 100644 index 0000000..e3ad0f1 --- /dev/null +++ b/client/requirements.txt @@ -0,0 +1,2 @@ +click~=7.0 +pyserial~=3.4