Skip to content

Commit 24f7fe2

Browse files
author
Aleksey Pryahin
committed
Replaced bmlab-erase, bmlab-flash, bmlab-scan, bmlab-rtt by bmlab-cli. Added autocomplete
1 parent 2bbe441 commit 24f7fe2

File tree

9 files changed

+292
-256
lines changed

9 files changed

+292
-256
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ Toolkit for flashing and testing embedded devices.
1818
pip install bmlab-toolkit
1919
```
2020

21+
## Installation CLI autocomplete
22+
```bash
23+
activate-global-python-argcomplete
24+
eval "$(register-python-argcomplete bmlab-cli)"
25+
```
26+
2127
## Usage
2228

2329
### Command Line

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ dev = [
3535
]
3636

3737
[project.scripts]
38-
bmlab-flash = "bmlab_toolkit.flashing:main"
39-
bmlab-erase = "bmlab_toolkit.erase_cli:main"
40-
bmlab-rtt = "bmlab_toolkit.rtt_cli:main"
41-
bmlab-scan = "bmlab_toolkit.scan_cli:main"
38+
bmlab-cli = "bmlab_toolkit.cli:main"
4239

4340
[project.urls]
4441
Homepage = "https://github.com/BareMetalTestLab/bmlab-flash-toolkit"

src/bmlab_toolkit/cli.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import argparse
2+
import argcomplete
3+
4+
from . import rtt_cli
5+
from . import scan_cli
6+
from . import erase_cli
7+
from . import flash_cli
8+
from .constants import SUPPORTED_PROGRAMMERS, DEFAULT_PROGRAMMER, LOG_LEVELS, DEFAULT_LOG_LEVEL
9+
10+
11+
def create_scan_parser(subparser: argparse._SubParsersAction):
12+
scan_parser = subparser.add_parser(
13+
'scan',
14+
help='Scan and list available programmers'
15+
)
16+
17+
scan_parser.add_argument(
18+
'--network', '-n',
19+
type=str,
20+
default=None,
21+
help='Network to scan for JLink Remote Servers (e.g., 192.168.1.0/24)'
22+
)
23+
24+
scan_parser.add_argument(
25+
'--start-ip',
26+
type=int,
27+
default=None,
28+
help='Starting last octet for IP range (e.g., 100 for x.x.x.100)'
29+
)
30+
31+
scan_parser.add_argument(
32+
'--end-ip',
33+
type=int,
34+
default=None,
35+
help='Ending last octet for IP range (e.g., 150 for x.x.x.150)'
36+
)
37+
38+
scan_parser.add_argument(
39+
'--programmer', '-p',
40+
type=str,
41+
default=DEFAULT_PROGRAMMER,
42+
choices=SUPPORTED_PROGRAMMERS,
43+
help=f'Programmer type to scan for (default: {DEFAULT_PROGRAMMER})'
44+
)
45+
46+
scan_parser.add_argument(
47+
'--log-level', '-l',
48+
type=str,
49+
default=DEFAULT_LOG_LEVEL,
50+
choices= LOG_LEVELS,
51+
help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})'
52+
)
53+
54+
55+
def create_erase_parser(subparser: argparse._SubParsersAction):
56+
erase_parser = subparser.add_parser(
57+
'erase',
58+
help='Erase flash memory of embedded devices'
59+
)
60+
61+
erase_parser.add_argument(
62+
'--serial', '-s',
63+
type=int,
64+
nargs='+',
65+
default=None,
66+
help='JLink serial number(s) (can specify multiple for sequential erase, or leave empty for auto-detect)'
67+
)
68+
69+
erase_parser.add_argument(
70+
'--mcu', '-m',
71+
type=str,
72+
default=None,
73+
help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided.'
74+
)
75+
76+
erase_parser.add_argument(
77+
'--ip',
78+
type=str,
79+
nargs='+',
80+
default=None,
81+
help='JLink IP address(es) for network connection (can specify multiple for parallel erase)'
82+
)
83+
84+
erase_parser.add_argument(
85+
'--log-level', '-l',
86+
type=str,
87+
default=DEFAULT_LOG_LEVEL,
88+
choices=LOG_LEVELS,
89+
help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})'
90+
)
91+
92+
93+
def create_rtt_parser(subparser: argparse._SubParsersAction):
94+
95+
rtt_parser = subparser.add_parser(
96+
'rtt',
97+
help='Connect to RTT for real-time data transfer'
98+
)
99+
100+
rtt_parser.add_argument(
101+
'--serial', '-s',
102+
type=int, nargs='+',
103+
default=None,
104+
help='Programmer serial number(s) (auto-detect if not provided)'
105+
)
106+
107+
rtt_parser.add_argument(
108+
'--programmer', '-p',
109+
type=str, default=DEFAULT_PROGRAMMER,
110+
choices=SUPPORTED_PROGRAMMERS,
111+
help=f'Programmer type (default: {DEFAULT_PROGRAMMER})'
112+
)
113+
114+
rtt_parser.add_argument(
115+
'--ip',
116+
type=str,
117+
nargs='+',
118+
default=None,
119+
help='JLink IP address(es) for network connection (can specify multiple)'
120+
)
121+
122+
rtt_parser.add_argument(
123+
'--output-dir',
124+
type=str, default=None,
125+
help='Output directory for RTT logs (required for multiple devices)'
126+
)
127+
128+
rtt_parser.add_argument(
129+
'--mcu', '-m',
130+
type=str,
131+
default=None,
132+
help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided. Not used with --ip.'
133+
)
134+
135+
rtt_parser.add_argument(
136+
'--reset',
137+
dest='reset',
138+
action='store_true',
139+
default=True,
140+
help='Reset target after connection (default: True)'
141+
)
142+
143+
rtt_parser.add_argument(
144+
'--no-reset',
145+
dest='reset',
146+
action='store_false',
147+
help='Do not reset target after connection'
148+
)
149+
150+
rtt_parser.add_argument(
151+
'--timeout', '-t',
152+
type=float,
153+
default=10.0,
154+
help='Read timeout in seconds. 0 means read until interrupted (default: 10.0)'
155+
)
156+
157+
rtt_parser.add_argument(
158+
'--msg',
159+
type=str,
160+
default=None,
161+
help='Message to send via RTT after connection'
162+
)
163+
164+
rtt_parser.add_argument(
165+
'--msg-timeout',
166+
type=float,
167+
default=0.5,
168+
help='Delay in seconds before sending message (default: 0.5)'
169+
)
170+
171+
rtt_parser.add_argument(
172+
'--msg-retries',
173+
type=int,
174+
default=10,
175+
help='Number of retries for sending message (default: 10)'
176+
)
177+
178+
rtt_parser.add_argument(
179+
'--log-level', '-l',
180+
type=str,
181+
default=DEFAULT_LOG_LEVEL,
182+
choices=LOG_LEVELS,
183+
help=f'Set logging level (default: {DEFAULT_LOG_LEVEL})'
184+
)
185+
186+
187+
def create_flash_parser(subparser: argparse._SubParsersAction):
188+
flash_parser = subparser.add_parser(
189+
'flash',
190+
help='Flash embedded devices and manage programmers'
191+
)
192+
193+
flash_parser.add_argument(
194+
"firmware_file",
195+
type=str,
196+
help="Path to firmware file (.hex or .bin)"
197+
)
198+
199+
flash_parser.add_argument(
200+
"--serial", "-s",
201+
type=int,
202+
nargs='+',
203+
default=None,
204+
help="Programmer serial number(s) (can specify multiple for parallel flashing, or leave empty for auto-detect)"
205+
)
206+
207+
flash_parser.add_argument(
208+
"--ip",
209+
type=str,
210+
nargs='+',
211+
default=None,
212+
help="JLink IP address(es) for network connection (e.g., 192.168.1.100 or multiple: 192.168.1.100 192.168.1.101)"
213+
)
214+
215+
flash_parser.add_argument(
216+
"--mcu",
217+
type=str,
218+
default=None,
219+
help="MCU name (e.g., STM32F765ZG). If not provided, will auto-detect"
220+
)
221+
222+
flash_parser.add_argument(
223+
"--programmer", "-p",
224+
type=str,
225+
default=DEFAULT_PROGRAMMER,
226+
choices=SUPPORTED_PROGRAMMERS,
227+
help=f"Programmer type (default: {DEFAULT_PROGRAMMER})"
228+
)
229+
230+
flash_parser.add_argument(
231+
"--log-level", "-l",
232+
type=str,
233+
default=DEFAULT_LOG_LEVEL,
234+
choices=LOG_LEVELS,
235+
help=f"Set logging level (default: {DEFAULT_LOG_LEVEL})"
236+
)
237+
238+
239+
def main():
240+
241+
parser = argparse.ArgumentParser(description="BMLab-toolkit")
242+
subparser = parser.add_subparsers(title='Available commands', dest='command', metavar='Command', help='Command descriptions', required=True)
243+
create_rtt_parser(subparser)
244+
create_scan_parser(subparser)
245+
create_erase_parser(subparser)
246+
create_flash_parser(subparser)
247+
# flash_parser = subparsers.add_parser('flash', help='Flash embedded devices and manage programmers')
248+
249+
250+
# Включаем автодополнение
251+
argcomplete.autocomplete(parser)
252+
253+
# Парсим аргументы
254+
args = parser.parse_args()
255+
if args.command == 'rtt':
256+
rtt_cli.main(args)
257+
elif args.command == 'scan':
258+
scan_cli.main(args)
259+
elif args.command == 'erase':
260+
erase_cli.main(args)
261+
elif args.command == 'flash':
262+
flash_cli.main(args)
263+
else:
264+
print('Unknown command')

src/bmlab_toolkit/constants.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,19 @@
1111

1212
# Default programmer
1313
DEFAULT_PROGRAMMER = PROGRAMMER_JLINK
14+
15+
16+
17+
LOG_LEVEL_INFO = 'INFO'
18+
LOG_LEVEL_ERROR = 'ERROR'
19+
LOG_LEVEL_DEBUG = 'DEBUG'
20+
LOG_LEVEL_WARNING = 'WARNING'
21+
22+
LOG_LEVELS = [
23+
LOG_LEVEL_INFO,
24+
LOG_LEVEL_ERROR,
25+
LOG_LEVEL_DEBUG,
26+
LOG_LEVEL_WARNING
27+
]
28+
29+
DEFAULT_LOG_LEVEL = LOG_LEVEL_WARNING

src/bmlab_toolkit/erase_cli.py

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,7 @@
1212
from .jlink_programmer import JLinkProgrammer
1313

1414

15-
def main():
16-
"""Main entry point for bmlab-erase command."""
17-
parser = argparse.ArgumentParser(
18-
description='Erase flash memory of embedded devices',
19-
formatter_class=argparse.RawDescriptionHelpFormatter,
20-
epilog="""
21-
Examples:
22-
# Erase with auto-detected programmer and MCU
23-
bmlab-erase
24-
25-
# Specify JLink serial number
26-
bmlab-erase --serial 123456789
27-
28-
# Erase multiple devices by serial
29-
bmlab-erase --serial 123456 789012 345678
30-
31-
# Specify MCU explicitly
32-
bmlab-erase --mcu STM32F765ZG
33-
34-
# Use IP address for network JLink
35-
bmlab-erase --ip 192.168.1.100
36-
37-
# Erase multiple devices in parallel by IP
38-
bmlab-erase --ip 192.168.1.100 192.168.1.101 192.168.1.102
39-
"""
40-
)
41-
42-
parser.add_argument('--serial', '-s', type=int, nargs='+', default=None,
43-
help='JLink serial number(s) (can specify multiple for sequential erase, or leave empty for auto-detect)')
44-
45-
parser.add_argument('--mcu', '-m', type=str, default=None,
46-
help='MCU name (e.g., STM32F765ZG). Auto-detects if not provided.')
47-
48-
parser.add_argument('--ip', type=str, nargs='+', default=None,
49-
help='JLink IP address(es) for network connection (can specify multiple for parallel erase)')
50-
51-
parser.add_argument('--log-level', '-l', type=str, default='WARNING',
52-
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
53-
help='Set logging level (default: WARNING)')
54-
55-
args = parser.parse_args()
56-
15+
def main(args: argparse.Namespace):
5716
# Validate that --serial and --ip are mutually exclusive
5817
if args.serial and args.ip:
5918
print("Error: Cannot specify both --serial and --ip")

0 commit comments

Comments
 (0)