diff options
Diffstat (limited to 'lib/python')
-rw-r--r-- | lib/python/qmk/c_parse.py | 2 | ||||
-rw-r--r-- | lib/python/qmk/cli/__init__.py | 72 | ||||
-rw-r--r-- | lib/python/qmk/cli/config.py | 116 | ||||
-rw-r--r-- | lib/python/qmk/cli/console.py | 302 | ||||
-rw-r--r-- | lib/python/qmk/commands.py | 1 | ||||
-rw-r--r-- | lib/python/qmk/constants.py | 2 | ||||
-rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 10 |
7 files changed, 350 insertions, 155 deletions
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index d4f39c883..991373d56 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py | |||
@@ -8,7 +8,7 @@ from milc import cli | |||
8 | from qmk.comment_remover import comment_remover | 8 | from qmk.comment_remover import comment_remover |
9 | 9 | ||
10 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} | 10 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} |
11 | single_comment_regex = re.compile(r' */[/*].*$') | 11 | single_comment_regex = re.compile(r'\s+/[/*].*$') |
12 | multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE) | 12 | multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE) |
13 | 13 | ||
14 | 14 | ||
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 6fe769fe7..02b721f34 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py | |||
@@ -12,6 +12,20 @@ from subprocess import run | |||
12 | from milc import cli, __VERSION__ | 12 | from milc import cli, __VERSION__ |
13 | from milc.questions import yesno | 13 | from milc.questions import yesno |
14 | 14 | ||
15 | import_names = { | ||
16 | # A mapping of package name to importable name | ||
17 | 'pep8-naming': 'pep8ext_naming', | ||
18 | 'pyusb': 'usb.core', | ||
19 | } | ||
20 | |||
21 | safe_commands = [ | ||
22 | # A list of subcommands we always run, even when the module imports fail | ||
23 | 'clone', | ||
24 | 'config', | ||
25 | 'env', | ||
26 | 'setup', | ||
27 | ] | ||
28 | |||
15 | 29 | ||
16 | def _run_cmd(*command): | 30 | def _run_cmd(*command): |
17 | """Run a command in a subshell. | 31 | """Run a command in a subshell. |
@@ -50,8 +64,8 @@ def _find_broken_requirements(requirements): | |||
50 | module_import = module_name.replace('-', '_') | 64 | module_import = module_name.replace('-', '_') |
51 | 65 | ||
52 | # Not every module is importable by its own name. | 66 | # Not every module is importable by its own name. |
53 | if module_name == "pep8-naming": | 67 | if module_name in import_names: |
54 | module_import = "pep8ext_naming" | 68 | module_import = import_names[module_name] |
55 | 69 | ||
56 | if not find_spec(module_import): | 70 | if not find_spec(module_import): |
57 | broken_modules.append(module_name) | 71 | broken_modules.append(module_name) |
@@ -107,32 +121,31 @@ if int(milc_version[0]) < 2 and int(milc_version[1]) < 3: | |||
107 | 121 | ||
108 | # Check to make sure we have all our dependencies | 122 | # Check to make sure we have all our dependencies |
109 | msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.' | 123 | msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.' |
110 | 124 | args = sys.argv[1:] | |
111 | if _broken_module_imports('requirements.txt'): | 125 | while args and args[0][0] == '-': |
112 | if yesno('Would you like to install the required Python modules?'): | 126 | del args[0] |
113 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt') | 127 | |
114 | else: | 128 | if not args or args[0] not in safe_commands: |
115 | print() | 129 | if _broken_module_imports('requirements.txt'): |
116 | print(msg_install % (str(Path('requirements.txt').resolve()),)) | 130 | if yesno('Would you like to install the required Python modules?'): |
117 | print() | 131 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt') |
118 | exit(1) | 132 | else: |
119 | 133 | print() | |
120 | if cli.config.user.developer: | 134 | print(msg_install % (str(Path('requirements.txt').resolve()),)) |
121 | args = sys.argv[1:] | 135 | print() |
122 | while args and args[0][0] == '-': | 136 | exit(1) |
123 | del args[0] | 137 | |
124 | if not args or args[0] != 'config': | 138 | if cli.config.user.developer and _broken_module_imports('requirements-dev.txt'): |
125 | if _broken_module_imports('requirements-dev.txt'): | 139 | if yesno('Would you like to install the required developer Python modules?'): |
126 | if yesno('Would you like to install the required developer Python modules?'): | 140 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements-dev.txt') |
127 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements-dev.txt') | 141 | elif yesno('Would you like to disable developer mode?'): |
128 | elif yesno('Would you like to disable developer mode?'): | 142 | _run_cmd(sys.argv[0], 'config', 'user.developer=None') |
129 | _run_cmd(sys.argv[0], 'config', 'user.developer=None') | 143 | else: |
130 | else: | 144 | print() |
131 | print() | 145 | print(msg_install % (str(Path('requirements-dev.txt').resolve()),)) |
132 | print(msg_install % (str(Path('requirements-dev.txt').resolve()),)) | 146 | print('You can also turn off developer mode: qmk config user.developer=None') |
133 | print('You can also turn off developer mode: qmk config user.developer=None') | 147 | print() |
134 | print() | 148 | exit(1) |
135 | exit(1) | ||
136 | 149 | ||
137 | # Import our subcommands | 150 | # Import our subcommands |
138 | from . import c2json # noqa | 151 | from . import c2json # noqa |
@@ -140,7 +153,8 @@ from . import cformat # noqa | |||
140 | from . import chibios # noqa | 153 | from . import chibios # noqa |
141 | from . import clean # noqa | 154 | from . import clean # noqa |
142 | from . import compile # noqa | 155 | from . import compile # noqa |
143 | from . import config # noqa | 156 | from milc.subcommand import config # noqa |
157 | from . import console # noqa | ||
144 | from . import docs # noqa | 158 | from . import docs # noqa |
145 | from . import doctor # noqa | 159 | from . import doctor # noqa |
146 | from . import fileformat # noqa | 160 | from . import fileformat # noqa |
diff --git a/lib/python/qmk/cli/config.py b/lib/python/qmk/cli/config.py deleted file mode 100644 index e17d8bb9b..000000000 --- a/lib/python/qmk/cli/config.py +++ /dev/null | |||
@@ -1,116 +0,0 @@ | |||
1 | """Read and write configuration settings | ||
2 | """ | ||
3 | from milc import cli | ||
4 | |||
5 | |||
6 | def print_config(section, key): | ||
7 | """Print a single config setting to stdout. | ||
8 | """ | ||
9 | cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key]) | ||
10 | |||
11 | |||
12 | def show_config(): | ||
13 | """Print the current configuration to stdout. | ||
14 | """ | ||
15 | for section in cli.config: | ||
16 | for key in cli.config[section]: | ||
17 | print_config(section, key) | ||
18 | |||
19 | |||
20 | def parse_config_token(config_token): | ||
21 | """Split a user-supplied configuration-token into its components. | ||
22 | """ | ||
23 | section = option = value = None | ||
24 | |||
25 | if '=' in config_token and '.' not in config_token: | ||
26 | cli.log.error('Invalid configuration token, the key must be of the form <section>.<option>: %s', config_token) | ||
27 | return section, option, value | ||
28 | |||
29 | # Separate the key (<section>.<option>) from the value | ||
30 | if '=' in config_token: | ||
31 | key, value = config_token.split('=') | ||
32 | else: | ||
33 | key = config_token | ||
34 | |||
35 | # Extract the section and option from the key | ||
36 | if '.' in key: | ||
37 | section, option = key.split('.', 1) | ||
38 | else: | ||
39 | section = key | ||
40 | |||
41 | return section, option, value | ||
42 | |||
43 | |||
44 | def set_config(section, option, value): | ||
45 | """Set a config key in the running config. | ||
46 | """ | ||
47 | log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s' | ||
48 | if cli.args.read_only: | ||
49 | log_string += ' {fg_red}(change not written)' | ||
50 | |||
51 | cli.echo(log_string, section, option, cli.config[section][option], value) | ||
52 | |||
53 | if not cli.args.read_only: | ||
54 | if value == 'None': | ||
55 | del cli.config[section][option] | ||
56 | else: | ||
57 | cli.config[section][option] = value | ||
58 | |||
59 | |||
60 | @cli.argument('-ro', '--read-only', arg_only=True, action='store_true', help='Operate in read-only mode.') | ||
61 | @cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.') | ||
62 | @cli.subcommand("Read and write configuration settings.") | ||
63 | def config(cli): | ||
64 | """Read and write config settings. | ||
65 | |||
66 | This script iterates over the config_tokens supplied as argument. Each config_token has the following form: | ||
67 | |||
68 | section[.key][=value] | ||
69 | |||
70 | If only a section (EG 'compile') is supplied all keys for that section will be displayed. | ||
71 | |||
72 | If section.key is supplied the value for that single key will be displayed. | ||
73 | |||
74 | If section.key=value is supplied the value for that single key will be set. | ||
75 | |||
76 | If section.key=None is supplied the key will be deleted. | ||
77 | |||
78 | No validation is done to ensure that the supplied section.key is actually used by qmk scripts. | ||
79 | """ | ||
80 | if not cli.args.configs: | ||
81 | return show_config() | ||
82 | |||
83 | # Process config_tokens | ||
84 | save_config = False | ||
85 | |||
86 | for argument in cli.args.configs: | ||
87 | # Split on space in case they quoted multiple config tokens | ||
88 | for config_token in argument.split(' '): | ||
89 | section, option, value = parse_config_token(config_token) | ||
90 | |||
91 | # Validation | ||
92 | if option and '.' in option: | ||
93 | cli.log.error('Config keys may not have more than one period! "%s" is not valid.', config_token) | ||
94 | return False | ||
95 | |||
96 | # Do what the user wants | ||
97 | if section and option and value: | ||
98 | # Write a configuration option | ||
99 | set_config(section, option, value) | ||
100 | if not cli.args.read_only: | ||
101 | save_config = True | ||
102 | |||
103 | elif section and option: | ||
104 | # Display a single key | ||
105 | print_config(section, option) | ||
106 | |||
107 | elif section: | ||
108 | # Display an entire section | ||
109 | for key in cli.config[section]: | ||
110 | print_config(section, key) | ||
111 | |||
112 | # Ending actions | ||
113 | if save_config: | ||
114 | cli.save_config() | ||
115 | |||
116 | return True | ||
diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py new file mode 100644 index 000000000..45ff0c8be --- /dev/null +++ b/lib/python/qmk/cli/console.py | |||
@@ -0,0 +1,302 @@ | |||
1 | """Acquire debugging information from usb hid devices | ||
2 | |||
3 | cli implementation of https://www.pjrc.com/teensy/hid_listen.html | ||
4 | """ | ||
5 | from pathlib import Path | ||
6 | from threading import Thread | ||
7 | from time import sleep, strftime | ||
8 | |||
9 | import hid | ||
10 | import usb.core | ||
11 | |||
12 | from milc import cli | ||
13 | |||
14 | LOG_COLOR = { | ||
15 | 'next': 0, | ||
16 | 'colors': [ | ||
17 | '{fg_blue}', | ||
18 | '{fg_cyan}', | ||
19 | '{fg_green}', | ||
20 | '{fg_magenta}', | ||
21 | '{fg_red}', | ||
22 | '{fg_yellow}', | ||
23 | ], | ||
24 | } | ||
25 | |||
26 | KNOWN_BOOTLOADERS = { | ||
27 | # VID , PID | ||
28 | ('03EB', '2FEF'): 'atmel-dfu: ATmega16U2', | ||
29 | ('03EB', '2FF0'): 'atmel-dfu: ATmega32U2', | ||
30 | ('03EB', '2FF3'): 'atmel-dfu: ATmega16U4', | ||
31 | ('03EB', '2FF4'): 'atmel-dfu: ATmega32U4', | ||
32 | ('03EB', '2FF9'): 'atmel-dfu: AT90USB64', | ||
33 | ('03EB', '2FFA'): 'atmel-dfu: AT90USB162', | ||
34 | ('03EB', '2FFB'): 'atmel-dfu: AT90USB128', | ||
35 | ('03EB', '6124'): 'Microchip SAM-BA', | ||
36 | ('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER', | ||
37 | ('16C0', '05DC'): 'USBasp: USBaspLoader', | ||
38 | ('16C0', '05DF'): 'bootloadHID: HIDBoot', | ||
39 | ('16C0', '0478'): 'halfkay: Teensy Halfkay', | ||
40 | ('1B4F', '9203'): 'caterina: Pro Micro 3.3V', | ||
41 | ('1B4F', '9205'): 'caterina: Pro Micro 5V', | ||
42 | ('1B4F', '9207'): 'caterina: LilyPadUSB', | ||
43 | ('1C11', 'B007'): 'kiibohd: Kiibohd DFU Bootloader', | ||
44 | ('1EAF', '0003'): 'stm32duino: Maple 003', | ||
45 | ('1FFB', '0101'): 'caterina: Polou A-Star 32U4 Bootloader', | ||
46 | ('2341', '0036'): 'caterina: Arduino Leonardo', | ||
47 | ('2341', '0037'): 'caterina: Arduino Micro', | ||
48 | ('239A', '000C'): 'caterina: Adafruit Feather 32U4', | ||
49 | ('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v', | ||
50 | ('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v', | ||
51 | ('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v', | ||
52 | ('2A03', '0036'): 'caterina: Arduino Leonardo', | ||
53 | ('2A03', '0037'): 'caterina: Arduino Micro', | ||
54 | ('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode' | ||
55 | } | ||
56 | |||
57 | |||
58 | class MonitorDevice(object): | ||
59 | def __init__(self, hid_device, numeric): | ||
60 | self.hid_device = hid_device | ||
61 | self.numeric = numeric | ||
62 | self.device = hid.Device(path=hid_device['path']) | ||
63 | self.current_line = '' | ||
64 | |||
65 | cli.log.info('Console Connected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device) | ||
66 | |||
67 | def read(self, size, encoding='ascii', timeout=1): | ||
68 | """Read size bytes from the device. | ||
69 | """ | ||
70 | return self.device.read(size, timeout).decode(encoding) | ||
71 | |||
72 | def read_line(self): | ||
73 | """Read from the device's console until we get a \n. | ||
74 | """ | ||
75 | while '\n' not in self.current_line: | ||
76 | self.current_line += self.read(32).replace('\x00', '') | ||
77 | |||
78 | lines = self.current_line.split('\n', 1) | ||
79 | self.current_line = lines[1] | ||
80 | |||
81 | return lines[0] | ||
82 | |||
83 | def run_forever(self): | ||
84 | while True: | ||
85 | try: | ||
86 | message = {**self.hid_device, 'text': self.read_line()} | ||
87 | identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string']) | ||
88 | message['identifier'] = ':'.join(identifier) | ||
89 | message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else '' | ||
90 | |||
91 | cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message) | ||
92 | |||
93 | except hid.HIDException: | ||
94 | break | ||
95 | |||
96 | |||
97 | class FindDevices(object): | ||
98 | def __init__(self, vid, pid, index, numeric): | ||
99 | self.vid = vid | ||
100 | self.pid = pid | ||
101 | self.index = index | ||
102 | self.numeric = numeric | ||
103 | |||
104 | def run_forever(self): | ||
105 | """Process messages from our queue in a loop. | ||
106 | """ | ||
107 | live_devices = {} | ||
108 | live_bootloaders = {} | ||
109 | |||
110 | while True: | ||
111 | try: | ||
112 | for device in list(live_devices): | ||
113 | if not live_devices[device]['thread'].is_alive(): | ||
114 | cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) | ||
115 | del live_devices[device] | ||
116 | |||
117 | for device in self.find_devices(): | ||
118 | if device['path'] not in live_devices: | ||
119 | device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']] | ||
120 | LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) | ||
121 | live_devices[device['path']] = device | ||
122 | |||
123 | try: | ||
124 | monitor = MonitorDevice(device, self.numeric) | ||
125 | device['thread'] = Thread(target=monitor.run_forever, daemon=True) | ||
126 | |||
127 | device['thread'].start() | ||
128 | except Exception as e: | ||
129 | device['e'] = e | ||
130 | device['e_name'] = e.__class__.__name__ | ||
131 | cli.log.error("Could not connect to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s:%(vendor_id)04X:%(product_id)04X:%(index)d): %(e_name)s: %(e)s", device) | ||
132 | if cli.config.general.verbose: | ||
133 | cli.log.exception(e) | ||
134 | del live_devices[device['path']] | ||
135 | |||
136 | if cli.args.bootloaders: | ||
137 | for device in self.find_bootloaders(): | ||
138 | if device.address in live_bootloaders: | ||
139 | live_bootloaders[device.address]._qmk_found = True | ||
140 | else: | ||
141 | name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] | ||
142 | cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) | ||
143 | device._qmk_found = True | ||
144 | live_bootloaders[device.address] = device | ||
145 | |||
146 | for device in list(live_bootloaders): | ||
147 | if live_bootloaders[device]._qmk_found: | ||
148 | live_bootloaders[device]._qmk_found = False | ||
149 | else: | ||
150 | name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] | ||
151 | cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) | ||
152 | del live_bootloaders[device] | ||
153 | |||
154 | sleep(.1) | ||
155 | |||
156 | except KeyboardInterrupt: | ||
157 | break | ||
158 | |||
159 | def is_bootloader(self, hid_device): | ||
160 | """Returns true if the device in question matches a known bootloader vid/pid. | ||
161 | """ | ||
162 | return (int2hex(hid_device.idVendor), int2hex(hid_device.idProduct)) in KNOWN_BOOTLOADERS | ||
163 | |||
164 | def is_console_hid(self, hid_device): | ||
165 | """Returns true when the usage page indicates it's a teensy-style console. | ||
166 | """ | ||
167 | return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074 | ||
168 | |||
169 | def is_filtered_device(self, hid_device): | ||
170 | """Returns True if the device should be included in the list of available consoles. | ||
171 | """ | ||
172 | return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid | ||
173 | |||
174 | def find_devices_by_report(self, hid_devices): | ||
175 | """Returns a list of available teensy-style consoles by doing a brute-force search. | ||
176 | |||
177 | Some versions of linux don't report usage and usage_page. In that case we fallback to reading the report (possibly inaccurately) ourselves. | ||
178 | """ | ||
179 | devices = [] | ||
180 | |||
181 | for device in hid_devices: | ||
182 | path = device['path'].decode('utf-8') | ||
183 | |||
184 | if path.startswith('/dev/hidraw'): | ||
185 | number = path[11:] | ||
186 | report = Path(f'/sys/class/hidraw/hidraw{number}/device/report_descriptor') | ||
187 | |||
188 | if report.exists(): | ||
189 | rp = report.read_bytes() | ||
190 | |||
191 | if rp[1] == 0x31 and rp[3] == 0x09: | ||
192 | devices.append(device) | ||
193 | |||
194 | return devices | ||
195 | |||
196 | def find_bootloaders(self): | ||
197 | """Returns a list of available bootloader devices. | ||
198 | """ | ||
199 | return list(filter(self.is_bootloader, usb.core.find(find_all=True))) | ||
200 | |||
201 | def find_devices(self): | ||
202 | """Returns a list of available teensy-style consoles. | ||
203 | """ | ||
204 | hid_devices = hid.enumerate() | ||
205 | devices = list(filter(self.is_console_hid, hid_devices)) | ||
206 | |||
207 | if not devices: | ||
208 | devices = self.find_devices_by_report(hid_devices) | ||
209 | |||
210 | if self.vid and self.pid: | ||
211 | devices = list(filter(self.is_filtered_device, devices)) | ||
212 | |||
213 | # Add index numbers | ||
214 | device_index = {} | ||
215 | for device in devices: | ||
216 | id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id']))) | ||
217 | |||
218 | if id not in device_index: | ||
219 | device_index[id] = 0 | ||
220 | |||
221 | device_index[id] += 1 | ||
222 | device['index'] = device_index[id] | ||
223 | |||
224 | return devices | ||
225 | |||
226 | |||
227 | def int2hex(number): | ||
228 | """Returns a string representation of the number as hex. | ||
229 | """ | ||
230 | return "%04X" % number | ||
231 | |||
232 | |||
233 | def list_devices(device_finder): | ||
234 | """Show the user a nicely formatted list of devices. | ||
235 | """ | ||
236 | devices = device_finder.find_devices() | ||
237 | |||
238 | if devices: | ||
239 | cli.log.info('Available devices:') | ||
240 | for dev in devices: | ||
241 | color = LOG_COLOR['colors'][LOG_COLOR['next']] | ||
242 | LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) | ||
243 | cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) | ||
244 | |||
245 | if cli.args.bootloaders: | ||
246 | bootloaders = device_finder.find_bootloaders() | ||
247 | |||
248 | if bootloaders: | ||
249 | cli.log.info('Available Bootloaders:') | ||
250 | |||
251 | for dev in bootloaders: | ||
252 | cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]) | ||
253 | |||
254 | |||
255 | @cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.') | ||
256 | @cli.argument('-d', '--device', help='Device to select - uses format <pid>:<vid>[:<index>].') | ||
257 | @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') | ||
258 | @cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.') | ||
259 | @cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.') | ||
260 | @cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)") | ||
261 | @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) | ||
262 | def console(cli): | ||
263 | """Acquire debugging information from usb hid devices | ||
264 | """ | ||
265 | vid = None | ||
266 | pid = None | ||
267 | index = 1 | ||
268 | |||
269 | if cli.config.console.device: | ||
270 | device = cli.config.console.device.split(':') | ||
271 | |||
272 | if len(device) == 2: | ||
273 | vid, pid = device | ||
274 | |||
275 | elif len(device) == 3: | ||
276 | vid, pid, index = device | ||
277 | |||
278 | if not index.isdigit(): | ||
279 | cli.log.error('Device index must be a number! Got "%s" instead.', index) | ||
280 | exit(1) | ||
281 | |||
282 | index = int(index) | ||
283 | |||
284 | if index < 1: | ||
285 | cli.log.error('Device index must be greater than 0! Got %s', index) | ||
286 | exit(1) | ||
287 | |||
288 | else: | ||
289 | cli.log.error('Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "%s".', cli.config.console.device) | ||
290 | cli.print_help() | ||
291 | exit(1) | ||
292 | |||
293 | vid = vid.upper() | ||
294 | pid = pid.upper() | ||
295 | |||
296 | device_finder = FindDevices(vid, pid, index, cli.args.numeric) | ||
297 | |||
298 | if cli.args.list: | ||
299 | return list_devices(device_finder) | ||
300 | |||
301 | print('Looking for devices...', flush=True) | ||
302 | device_finder.run_forever() | ||
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index ee049e8af..3a35c1103 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py | |||
@@ -201,6 +201,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va | |||
201 | f'VERBOSE={verbose}', | 201 | f'VERBOSE={verbose}', |
202 | f'COLOR={color}', | 202 | f'COLOR={color}', |
203 | 'SILENT=false', | 203 | 'SILENT=false', |
204 | f'QMK_BIN={"bin/qmk" if "DEPRECATED_BIN_QMK" in os.environ else "qmk"}', | ||
204 | ]) | 205 | ]) |
205 | 206 | ||
206 | return make_command | 207 | return make_command |
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 3ed69f3bf..49e5e0eb4 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py | |||
@@ -10,7 +10,7 @@ QMK_FIRMWARE = Path.cwd() | |||
10 | MAX_KEYBOARD_SUBFOLDERS = 5 | 10 | MAX_KEYBOARD_SUBFOLDERS = 5 |
11 | 11 | ||
12 | # Supported processor types | 12 | # Supported processor types |
13 | CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F411', 'STM32G431', 'STM32G474' | 13 | CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66F18', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L433', 'STM32L443' |
14 | LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None | 14 | LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None |
15 | VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' | 15 | VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' |
16 | 16 | ||
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index d13e42d40..afdbc8142 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py | |||
@@ -7,7 +7,7 @@ is_windows = 'windows' in platform.platform().lower() | |||
7 | 7 | ||
8 | 8 | ||
9 | def check_subcommand(command, *args): | 9 | def check_subcommand(command, *args): |
10 | cmd = ['bin/qmk', command, *args] | 10 | cmd = ['qmk', command, *args] |
11 | result = cli.run(cmd, stdin=DEVNULL, combined_output=True) | 11 | result = cli.run(cmd, stdin=DEVNULL, combined_output=True) |
12 | return result | 12 | return result |
13 | 13 | ||
@@ -16,7 +16,7 @@ def check_subcommand_stdin(file_to_read, command, *args): | |||
16 | """Pipe content of a file to a command and return output. | 16 | """Pipe content of a file to a command and return output. |
17 | """ | 17 | """ |
18 | with open(file_to_read, encoding='utf-8') as my_file: | 18 | with open(file_to_read, encoding='utf-8') as my_file: |
19 | cmd = ['bin/qmk', command, *args] | 19 | cmd = ['qmk', command, *args] |
20 | result = cli.run(cmd, stdin=my_file, combined_output=True) | 20 | result = cli.run(cmd, stdin=my_file, combined_output=True) |
21 | return result | 21 | return result |
22 | 22 | ||
@@ -61,12 +61,6 @@ def test_flash_bootloaders(): | |||
61 | check_returncode(result, [1]) | 61 | check_returncode(result, [1]) |
62 | 62 | ||
63 | 63 | ||
64 | def test_config(): | ||
65 | result = check_subcommand('config') | ||
66 | check_returncode(result) | ||
67 | assert 'general.color' in result.stdout | ||
68 | |||
69 | |||
70 | def test_kle2json(): | 64 | def test_kle2json(): |
71 | result = check_subcommand('kle2json', 'lib/python/qmk/tests/kle.txt', '-f') | 65 | result = check_subcommand('kle2json', 'lib/python/qmk/tests/kle.txt', '-f') |
72 | check_returncode(result) | 66 | check_returncode(result) |