diff --git a/README.md b/README.md index 18f0b96..d09d939 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ Toolkit for flashing and testing embedded devices. pip install bmlab-toolkit ``` +## Installation CLI autocomplete +```bash +activate-global-python-argcomplete +eval "$(register-python-argcomplete bmlab-cli)" +``` + ## Usage ### Command Line diff --git a/pyproject.toml b/pyproject.toml index 1927085..603a754 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,10 +35,7 @@ dev = [ ] [project.scripts] -bmlab-flash = "bmlab_toolkit.flashing:main" -bmlab-erase = "bmlab_toolkit.erase_cli:main" -bmlab-rtt = "bmlab_toolkit.rtt_cli:main" -bmlab-scan = "bmlab_toolkit.scan_cli:main" +bmlab-cli = "bmlab_toolkit.cli:main" [project.urls] Homepage = "https://github.com/BareMetalTestLab/bmlab-flash-toolkit" diff --git a/src/bmlab_toolkit/cli.py b/src/bmlab_toolkit/cli.py new file mode 100644 index 0000000..de3bd02 --- /dev/null +++ b/src/bmlab_toolkit/cli.py @@ -0,0 +1,264 @@ +import argparse +import argcomplete + +from . import rtt_cli +from . import scan_cli +from . import erase_cli +from . import flash_cli +from .constants import SUPPORTED_PROGRAMMERS, DEFAULT_PROGRAMMER, LOG_LEVELS, DEFAULT_LOG_LEVEL + + +def create_scan_parser(subparser: argparse._SubParsersAction): + scan_parser = subparser.add_parser( + 'scan', + help='Scan and list available programmers' + ) + + scan_parser.add_argument( + '--network', '-n', + type=str, + default=None, + help='Network to scan for JLink Remote Servers (e.g., 192.168.1.0/24)' + ) + + scan_parser.add_argument( + '--start-ip', + type=int, + default=None, + help='Starting last octet for IP range (e.g., 100 for x.x.x.100)' + ) + + scan_parser.add_argument( + '--end-ip', + type=int, + default=None, + help='Ending last octet for IP range (e.g., 150 for x.x.x.150)' + ) + + scan_parser.add_argument( + '--programmer', '-p', + type=str, + default=DEFAULT_PROGRAMMER, + choices=SUPPORTED_PROGRAMMERS, + help=f'Programmer type to scan for (default: {DEFAULT_PROGRAMMER})' + ) + + scan_parser.add_argument( + '--log-level', '-l', + type=str, + default=DEFAULT_LOG_LEVEL, + choices= LOG_LEVELS, + help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})' + ) + + +def create_erase_parser(subparser: argparse._SubParsersAction): + erase_parser = subparser.add_parser( + 'erase', + help='Erase flash memory of embedded devices' + ) + + erase_parser.add_argument( + '--serial', '-s', + type=int, + nargs='+', + default=None, + help='JLink serial number(s) (can specify multiple for sequential erase, or leave empty for auto-detect)' + ) + + erase_parser.add_argument( + '--mcu', '-m', + type=str, + default=None, + help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided.' + ) + + erase_parser.add_argument( + '--ip', + type=str, + nargs='+', + default=None, + help='JLink IP address(es) for network connection (can specify multiple for parallel erase)' + ) + + erase_parser.add_argument( + '--log-level', '-l', + type=str, + default=DEFAULT_LOG_LEVEL, + choices=LOG_LEVELS, + help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})' + ) + + +def create_rtt_parser(subparser: argparse._SubParsersAction): + + rtt_parser = subparser.add_parser( + 'rtt', + help='Connect to RTT for real-time data transfer' + ) + + rtt_parser.add_argument( + '--serial', '-s', + type=int, nargs='+', + default=None, + help='Programmer serial number(s) (auto-detect if not provided)' + ) + + rtt_parser.add_argument( + '--programmer', '-p', + type=str, default=DEFAULT_PROGRAMMER, + choices=SUPPORTED_PROGRAMMERS, + help=f'Programmer type (default: {DEFAULT_PROGRAMMER})' + ) + + rtt_parser.add_argument( + '--ip', + type=str, + nargs='+', + default=None, + help='JLink IP address(es) for network connection (can specify multiple)' + ) + + rtt_parser.add_argument( + '--output-dir', + type=str, default=None, + help='Output directory for RTT logs (required for multiple devices)' + ) + + rtt_parser.add_argument( + '--mcu', '-m', + type=str, + default=None, + help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided. Not used with --ip.' + ) + + rtt_parser.add_argument( + '--reset', + dest='reset', + action='store_true', + default=True, + help='Reset target after connection (default: True)' + ) + + rtt_parser.add_argument( + '--no-reset', + dest='reset', + action='store_false', + help='Do not reset target after connection' + ) + + rtt_parser.add_argument( + '--timeout', '-t', + type=float, + default=10.0, + help='Read timeout in seconds. 0 means read until interrupted (default: 10.0)' + ) + + rtt_parser.add_argument( + '--msg', + type=str, + default=None, + help='Message to send via RTT after connection' + ) + + rtt_parser.add_argument( + '--msg-timeout', + type=float, + default=0.5, + help='Delay in seconds before sending message (default: 0.5)' + ) + + rtt_parser.add_argument( + '--msg-retries', + type=int, + default=10, + help='Number of retries for sending message (default: 10)' + ) + + rtt_parser.add_argument( + '--log-level', '-l', + type=str, + default=DEFAULT_LOG_LEVEL, + choices=LOG_LEVELS, + help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})' + ) + + +def create_flash_parser(subparser: argparse._SubParsersAction): + flash_parser = subparser.add_parser( + 'flash', + help='Flash embedded devices and manage programmers' + ) + + flash_parser.add_argument( + "firmware_file", + type=str, + help="Path to firmware file (.hex or .bin)" + ) + + flash_parser.add_argument( + "--serial", "-s", + type=int, + nargs='+', + default=None, + help="Programmer serial number(s) (can specify multiple for parallel flashing, or leave empty for auto-detect)" + ) + + flash_parser.add_argument( + "--ip", + type=str, + nargs='+', + default=None, + help="JLink IP address(es) for network connection (e.g., 192.168.1.100 or multiple: 192.168.1.100 192.168.1.101)" + ) + + flash_parser.add_argument( + "--mcu", + type=str, + default=None, + help="MCU name (e.g., STM32F765ZG). If not provided, will auto-detect" + ) + + flash_parser.add_argument( + "--programmer", "-p", + type=str, + default=DEFAULT_PROGRAMMER, + choices=SUPPORTED_PROGRAMMERS, + help=f"Programmer type (default: {DEFAULT_PROGRAMMER})" + ) + + flash_parser.add_argument( + "--log-level", "-l", + type=str, + default=DEFAULT_LOG_LEVEL, + choices=LOG_LEVELS, + help=f"Set logging level (default: {DEFAULT_LOG_LEVEL})" + ) + + +def main(): + + parser = argparse.ArgumentParser(description="BMLab-toolkit") + subparser = parser.add_subparsers(title='Available commands', dest='command', metavar='Command', help='Command descriptions', required=True) + create_rtt_parser(subparser) + create_scan_parser(subparser) + create_erase_parser(subparser) + create_flash_parser(subparser) + # flash_parser = subparsers.add_parser('flash', help='Flash embedded devices and manage programmers') + + + # Включаем автодополнение + argcomplete.autocomplete(parser) + + # Парсим аргументы + args = parser.parse_args() + if args.command == 'rtt': + rtt_cli.main(args) + elif args.command == 'scan': + scan_cli.main(args) + elif args.command == 'erase': + erase_cli.main(args) + elif args.command == 'flash': + flash_cli.main(args) + else: + print('Unknown command') diff --git a/src/bmlab_toolkit/constants.py b/src/bmlab_toolkit/constants.py index ba08644..a39aa71 100644 --- a/src/bmlab_toolkit/constants.py +++ b/src/bmlab_toolkit/constants.py @@ -11,3 +11,19 @@ # Default programmer DEFAULT_PROGRAMMER = PROGRAMMER_JLINK + + + +LOG_LEVEL_INFO = 'INFO' +LOG_LEVEL_ERROR = 'ERROR' +LOG_LEVEL_DEBUG = 'DEBUG' +LOG_LEVEL_WARNING = 'WARNING' + +LOG_LEVELS = [ + LOG_LEVEL_INFO, + LOG_LEVEL_ERROR, + LOG_LEVEL_DEBUG, + LOG_LEVEL_WARNING +] + +DEFAULT_LOG_LEVEL = LOG_LEVEL_WARNING \ No newline at end of file diff --git a/src/bmlab_toolkit/erase_cli.py b/src/bmlab_toolkit/erase_cli.py index 05a7270..60ab16e 100644 --- a/src/bmlab_toolkit/erase_cli.py +++ b/src/bmlab_toolkit/erase_cli.py @@ -12,48 +12,7 @@ from .jlink_programmer import JLinkProgrammer -def main(): - """Main entry point for bmlab-erase command.""" - parser = argparse.ArgumentParser( - description='Erase flash memory of embedded devices', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - # Erase with auto-detected programmer and MCU - bmlab-erase - - # Specify JLink serial number - bmlab-erase --serial 123456789 - - # Erase multiple devices by serial - bmlab-erase --serial 123456 789012 345678 - - # Specify MCU explicitly - bmlab-erase --mcu STM32F765ZG - - # Use IP address for network JLink - bmlab-erase --ip 192.168.1.100 - - # Erase multiple devices in parallel by IP - bmlab-erase --ip 192.168.1.100 192.168.1.101 192.168.1.102 - """ - ) - - parser.add_argument('--serial', '-s', type=int, nargs='+', default=None, - help='JLink serial number(s) (can specify multiple for sequential erase, or leave empty for auto-detect)') - - parser.add_argument('--mcu', '-m', type=str, default=None, - help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided.') - - parser.add_argument('--ip', type=str, nargs='+', default=None, - help='JLink IP address(es) for network connection (can specify multiple for parallel erase)') - - parser.add_argument('--log-level', '-l', type=str, default='WARNING', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], - help='Set logging level (default: WARNING)') - - args = parser.parse_args() - +def main(args: argparse.Namespace): # Validate that --serial and --ip are mutually exclusive if args.serial and args.ip: print("Error: Cannot specify both --serial and --ip") diff --git a/src/bmlab_toolkit/flashing.py b/src/bmlab_toolkit/flash_cli.py similarity index 71% rename from src/bmlab_toolkit/flashing.py rename to src/bmlab_toolkit/flash_cli.py index 288ae32..e808f05 100644 --- a/src/bmlab_toolkit/flashing.py +++ b/src/bmlab_toolkit/flash_cli.py @@ -10,82 +10,7 @@ from .jlink_programmer import JLinkProgrammer -def main(): - """Main CLI entry point.""" - parser = argparse.ArgumentParser( - description="Flash embedded devices and manage programmers", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - # List connected programmers - bmlab-scan - - # Flash with auto-detected JLink (first available) - bmlab-flash firmware.hex - - # Flash with specific JLink serial - bmlab-flash firmware.hex --serial 123456 - - # Flash via JLink IP address - bmlab-flash firmware.hex --ip 192.168.1.100 - - # Flash multiple devices in parallel - bmlab-flash firmware.hex --ip 192.168.1.100 192.168.1.101 192.168.1.102 - - # Flash with specific MCU - bmlab-flash firmware.hex --mcu STM32F765ZG - - # Specify programmer explicitly - bmlab-flash firmware.hex --programmer jlink --serial 123456 - """ - ) - - parser.add_argument( - "firmware_file", - type=str, - help="Path to firmware file (.hex or .bin)" - ) - - parser.add_argument( - "--serial", "-s", - type=int, - nargs='+', - default=None, - help="Programmer serial number(s) (can specify multiple for parallel flashing, or leave empty for auto-detect)" - ) - - parser.add_argument( - "--ip", - type=str, - nargs='+', - default=None, - help="JLink IP address(es) for network connection (e.g., 192.168.1.100 or multiple: 192.168.1.100 192.168.1.101)" - ) - - parser.add_argument( - "--mcu", - type=str, - default=None, - help="MCU name (e.g., STM32F765ZG). If not provided, will auto-detect" - ) - - parser.add_argument( - "--programmer", "-p", - type=str, - default=DEFAULT_PROGRAMMER, - choices=SUPPORTED_PROGRAMMERS, - help=f"Programmer type (default: {DEFAULT_PROGRAMMER})" - ) - - parser.add_argument( - "--log-level", "-l", - type=str, - default="WARNING", - choices=["DEBUG", "INFO", "WARNING", "ERROR"], - help="Set logging level (default: WARNING)" - ) - - args = parser.parse_args() +def main(args: argparse.Namespace): # Validate that --serial and --ip are mutually exclusive if args.serial and args.ip: diff --git a/src/bmlab_toolkit/rtt_cli.py b/src/bmlab_toolkit/rtt_cli.py index 52bda82..83b1b5b 100644 --- a/src/bmlab_toolkit/rtt_cli.py +++ b/src/bmlab_toolkit/rtt_cli.py @@ -17,84 +17,7 @@ from .jlink_programmer import JLinkProgrammer -def main(): - """Main entry point for bmlab-rtt command.""" - parser = argparse.ArgumentParser( - description='Connect to RTT for real-time data transfer', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - # Connect with auto-detect and read for 10 seconds - bmlab-rtt - - # Specify JLink serial number - bmlab-rtt --serial 123456789 - - # Connect via IP address (MCU not needed) - bmlab-rtt --ip 192.168.1.100 - - # Connect to multiple devices in parallel (writes to files) - bmlab-rtt --ip 192.168.1.100 192.168.1.101 192.168.1.102 --output-dir ./rtt_logs - - # Specify MCU explicitly - bmlab-rtt --mcu STM32F765ZG - - # Read indefinitely until Ctrl+C - bmlab-rtt -t 0 - - # Send message after connection - bmlab-rtt --msg "hello\\n" - - # Send message after 2 seconds delay - bmlab-rtt --msg "test" --msg-timeout 2.0 - - # No reset on connection - bmlab-rtt --no-reset - - # Specify programmer explicitly (default: jlink) - bmlab-rtt --programmer jlink --serial 123456 - """ - ) - - parser.add_argument('--serial', '-s', type=int, nargs='+', default=None, - help='Programmer serial number(s) (auto-detect if not provided)') - - parser.add_argument('--programmer', '-p', type=str, default=DEFAULT_PROGRAMMER, - choices=SUPPORTED_PROGRAMMERS, - help=f'Programmer type (default: {DEFAULT_PROGRAMMER})') - - parser.add_argument('--ip', type=str, nargs='+', default=None, - help='JLink IP address(es) for network connection (can specify multiple)') - - parser.add_argument('--output-dir', type=str, default=None, - help='Output directory for RTT logs (required for multiple devices)') - - parser.add_argument('--mcu', '-m', type=str, default=None, - help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided. Not used with --ip.') - - parser.add_argument('--reset', dest='reset', action='store_true', default=True, - help='Reset target after connection (default: True)') - - parser.add_argument('--no-reset', dest='reset', action='store_false', - help='Do not reset target after connection') - - parser.add_argument('--timeout', '-t', type=float, default=10.0, - help='Read timeout in seconds. 0 means read until interrupted (default: 10.0)') - - parser.add_argument('--msg', type=str, default=None, - help='Message to send via RTT after connection') - - parser.add_argument('--msg-timeout', type=float, default=0.5, - help='Delay in seconds before sending message (default: 0.5)') - - parser.add_argument('--msg-retries', type=int, default=10, - help='Number of retries for sending message (default: 10)') - - parser.add_argument('--log-level', '-l', type=str, default='WARNING', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], - help='Set logging level (default: WARNING)') - - args = parser.parse_args() +def main(args: argparse.Namespace): # Validate that --serial and --ip are mutually exclusive if args.serial and args.ip: diff --git a/src/bmlab_toolkit/scan_cli.py b/src/bmlab_toolkit/scan_cli.py index 256ff34..bd92467 100644 --- a/src/bmlab_toolkit/scan_cli.py +++ b/src/bmlab_toolkit/scan_cli.py @@ -75,62 +75,8 @@ def scan_network_ip(ip_str, log_level): pass -def main(): +def main(args: argparse.Namespace): """Main entry point for bmlab-scan command.""" - parser = argparse.ArgumentParser( - description='Scan and list available programmers', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - # Scan for USB JLink programmers - bmlab-scan - - # Scan network for JLink Remote Servers - bmlab-scan --network 192.168.1.0/24 - - # Scan with debug output - bmlab-scan --log-level DEBUG - """ - ) - - parser.add_argument( - '--network', '-n', - type=str, - default=None, - help='Network to scan for JLink Remote Servers (e.g., 192.168.1.0/24)' - ) - - parser.add_argument( - '--start-ip', - type=int, - default=None, - help='Starting last octet for IP range (e.g., 100 for x.x.x.100)' - ) - - parser.add_argument( - '--end-ip', - type=int, - default=None, - help='Ending last octet for IP range (e.g., 150 for x.x.x.150)' - ) - - parser.add_argument( - '--programmer', '-p', - type=str, - default=DEFAULT_PROGRAMMER, - choices=SUPPORTED_PROGRAMMERS, - help=f'Programmer type to scan for (default: {DEFAULT_PROGRAMMER})' - ) - - parser.add_argument( - '--log-level', '-l', - type=str, - default='WARNING', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], - help='Set logging level (default: WARNING)' - ) - - args = parser.parse_args() try: # Convert log level string to logging constant diff --git a/tests/test_flashing.py b/tests/test_flashing.py index 96a7c8b..d29476d 100644 --- a/tests/test_flashing.py +++ b/tests/test_flashing.py @@ -1,7 +1,7 @@ """Tests for flashing module.""" import pytest -from bmlab_toolkit.flashing import flash_device_by_usb +from bmlab_toolkit.flash_cli import flash_device_by_usb def test_unsupported_programmer():