diff options
Diffstat (limited to 'lib/python/qmk/info.py')
-rw-r--r-- | lib/python/qmk/info.py | 343 |
1 files changed, 102 insertions, 241 deletions
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index cc81f7a08..2accaba9e 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py | |||
@@ -5,57 +5,18 @@ from collections.abc import Mapping | |||
5 | from glob import glob | 5 | from glob import glob |
6 | from pathlib import Path | 6 | from pathlib import Path |
7 | 7 | ||
8 | import hjson | ||
8 | import jsonschema | 9 | import jsonschema |
10 | from dotty_dict import dotty | ||
9 | from milc import cli | 11 | from milc import cli |
10 | 12 | ||
11 | from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS | 13 | from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS |
12 | from qmk.c_parse import find_layouts | 14 | from qmk.c_parse import find_layouts |
13 | from qmk.keyboard import config_h, rules_mk | 15 | from qmk.keyboard import config_h, rules_mk |
14 | from qmk.keymap import list_keymaps | 16 | from qmk.keymap import list_keymaps |
15 | from qmk.makefile import parse_rules_mk_file | 17 | from qmk.makefile import parse_rules_mk_file |
16 | from qmk.math import compute | 18 | from qmk.math import compute |
17 | 19 | ||
18 | led_matrix_properties = { | ||
19 | 'driver_count': 'LED_DRIVER_COUNT', | ||
20 | 'driver_addr1': 'LED_DRIVER_ADDR_1', | ||
21 | 'driver_addr2': 'LED_DRIVER_ADDR_2', | ||
22 | 'driver_addr3': 'LED_DRIVER_ADDR_3', | ||
23 | 'driver_addr4': 'LED_DRIVER_ADDR_4', | ||
24 | 'led_count': 'LED_DRIVER_LED_COUNT', | ||
25 | 'timeout': 'ISSI_TIMEOUT', | ||
26 | 'persistence': 'ISSI_PERSISTENCE' | ||
27 | } | ||
28 | |||
29 | rgblight_properties = { | ||
30 | 'led_count': ('RGBLED_NUM', int), | ||
31 | 'pin': ('RGB_DI_PIN', str), | ||
32 | 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int), | ||
33 | 'hue_steps': ('RGBLIGHT_HUE_STEP', int), | ||
34 | 'saturation_steps': ('RGBLIGHT_SAT_STEP', int), | ||
35 | 'brightness_steps': ('RGBLIGHT_VAL_STEP', int) | ||
36 | } | ||
37 | |||
38 | rgblight_toggles = { | ||
39 | 'sleep': 'RGBLIGHT_SLEEP', | ||
40 | 'split': 'RGBLIGHT_SPLIT', | ||
41 | } | ||
42 | |||
43 | rgblight_animations = { | ||
44 | 'all': 'RGBLIGHT_ANIMATIONS', | ||
45 | 'alternating': 'RGBLIGHT_EFFECT_ALTERNATING', | ||
46 | 'breathing': 'RGBLIGHT_EFFECT_BREATHING', | ||
47 | 'christmas': 'RGBLIGHT_EFFECT_CHRISTMAS', | ||
48 | 'knight': 'RGBLIGHT_EFFECT_KNIGHT', | ||
49 | 'rainbow_mood': 'RGBLIGHT_EFFECT_RAINBOW_MOOD', | ||
50 | 'rainbow_swirl': 'RGBLIGHT_EFFECT_RAINBOW_SWIRL', | ||
51 | 'rgb_test': 'RGBLIGHT_EFFECT_RGB_TEST', | ||
52 | 'snake': 'RGBLIGHT_EFFECT_SNAKE', | ||
53 | 'static_gradient': 'RGBLIGHT_EFFECT_STATIC_GRADIENT', | ||
54 | 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' | ||
55 | } | ||
56 | |||
57 | usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} | ||
58 | |||
59 | true_values = ['1', 'on', 'yes'] | 20 | true_values = ['1', 'on', 'yes'] |
60 | false_values = ['0', 'off', 'no'] | 21 | false_values = ['0', 'off', 'no'] |
61 | 22 | ||
@@ -101,7 +62,6 @@ def info_json(keyboard): | |||
101 | except jsonschema.ValidationError as e: | 62 | except jsonschema.ValidationError as e: |
102 | json_path = '.'.join([str(p) for p in e.absolute_path]) | 63 | json_path = '.'.join([str(p) for p in e.absolute_path]) |
103 | cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) | 64 | cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) |
104 | print(dir(e)) | ||
105 | exit() | 65 | exit() |
106 | 66 | ||
107 | # Make sure we have at least one layout | 67 | # Make sure we have at least one layout |
@@ -132,7 +92,7 @@ def _json_load(json_file): | |||
132 | Note: file must be a Path object. | 92 | Note: file must be a Path object. |
133 | """ | 93 | """ |
134 | try: | 94 | try: |
135 | return json.load(json_file.open()) | 95 | return hjson.load(json_file.open()) |
136 | 96 | ||
137 | except json.decoder.JSONDecodeError as e: | 97 | except json.decoder.JSONDecodeError as e: |
138 | cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) | 98 | cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) |
@@ -172,65 +132,6 @@ def keyboard_api_validate(data): | |||
172 | return validator(data) | 132 | return validator(data) |
173 | 133 | ||
174 | 134 | ||
175 | def _extract_debounce(info_data, config_c): | ||
176 | """Handle debounce. | ||
177 | """ | ||
178 | if 'debounce' in info_data and 'DEBOUNCE' in config_c: | ||
179 | _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.') | ||
180 | |||
181 | if 'DEBOUNCE' in config_c: | ||
182 | info_data['debounce'] = int(config_c['DEBOUNCE']) | ||
183 | |||
184 | return info_data | ||
185 | |||
186 | |||
187 | def _extract_diode_direction(info_data, config_c): | ||
188 | """Handle the diode direction. | ||
189 | """ | ||
190 | if 'diode_direction' in info_data and 'DIODE_DIRECTION' in config_c: | ||
191 | _log_warning(info_data, 'Diode direction is specified in both info.json and config.h, the config.h value wins.') | ||
192 | |||
193 | if 'DIODE_DIRECTION' in config_c: | ||
194 | info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') | ||
195 | |||
196 | return info_data | ||
197 | |||
198 | |||
199 | def _extract_indicators(info_data, config_c): | ||
200 | """Find the LED indicator information. | ||
201 | """ | ||
202 | for json_key, config_key in LED_INDICATORS.items(): | ||
203 | if json_key in info_data.get('indicators', []) and config_key in config_c: | ||
204 | _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') | ||
205 | |||
206 | if 'indicators' not in info_data: | ||
207 | info_data['indicators'] = {} | ||
208 | |||
209 | if config_key in config_c: | ||
210 | if 'indicators' not in info_data: | ||
211 | info_data['indicators'] = {} | ||
212 | |||
213 | info_data['indicators'][json_key] = config_c.get(config_key) | ||
214 | |||
215 | return info_data | ||
216 | |||
217 | |||
218 | def _extract_community_layouts(info_data, rules): | ||
219 | """Find the community layouts in rules.mk. | ||
220 | """ | ||
221 | community_layouts = rules['LAYOUTS'].split() if 'LAYOUTS' in rules else [] | ||
222 | |||
223 | if 'community_layouts' in info_data: | ||
224 | for layout in community_layouts: | ||
225 | if layout not in info_data['community_layouts']: | ||
226 | community_layouts.append(layout) | ||
227 | |||
228 | else: | ||
229 | info_data['community_layouts'] = community_layouts | ||
230 | |||
231 | return info_data | ||
232 | |||
233 | |||
234 | def _extract_features(info_data, rules): | 135 | def _extract_features(info_data, rules): |
235 | """Find all the features enabled in rules.mk. | 136 | """Find all the features enabled in rules.mk. |
236 | """ | 137 | """ |
@@ -267,78 +168,6 @@ def _extract_features(info_data, rules): | |||
267 | return info_data | 168 | return info_data |
268 | 169 | ||
269 | 170 | ||
270 | def _extract_led_drivers(info_data, rules): | ||
271 | """Find all the LED drivers set in rules.mk. | ||
272 | """ | ||
273 | if 'LED_MATRIX_DRIVER' in rules: | ||
274 | if 'led_matrix' not in info_data: | ||
275 | info_data['led_matrix'] = {} | ||
276 | |||
277 | if info_data['led_matrix'].get('driver'): | ||
278 | _log_warning(info_data, 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
279 | |||
280 | info_data['led_matrix']['driver'] = rules['LED_MATRIX_DRIVER'] | ||
281 | |||
282 | return info_data | ||
283 | |||
284 | |||
285 | def _extract_led_matrix(info_data, config_c): | ||
286 | """Handle the led_matrix configuration. | ||
287 | """ | ||
288 | led_matrix = info_data.get('led_matrix', {}) | ||
289 | |||
290 | for json_key, config_key in led_matrix_properties.items(): | ||
291 | if config_key in config_c: | ||
292 | if json_key in led_matrix: | ||
293 | _log_warning(info_data, 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) | ||
294 | |||
295 | led_matrix[json_key] = config_c[config_key] | ||
296 | |||
297 | |||
298 | def _extract_rgblight(info_data, config_c): | ||
299 | """Handle the rgblight configuration. | ||
300 | """ | ||
301 | rgblight = info_data.get('rgblight', {}) | ||
302 | animations = rgblight.get('animations', {}) | ||
303 | |||
304 | if 'RGBLED_SPLIT' in config_c: | ||
305 | raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip() | ||
306 | rgblight['split_count'] = [int(i) for i in raw_split.split(',')] | ||
307 | |||
308 | for json_key, config_key_type in rgblight_properties.items(): | ||
309 | config_key, config_type = config_key_type | ||
310 | |||
311 | if config_key in config_c: | ||
312 | if json_key in rgblight: | ||
313 | _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) | ||
314 | |||
315 | try: | ||
316 | rgblight[json_key] = config_type(config_c[config_key]) | ||
317 | except ValueError as e: | ||
318 | cli.log.error('%s: config.h: Could not convert "%s" to %s: %s', info_data['keyboard_folder'], config_c[config_key], config_type.__name__, e) | ||
319 | |||
320 | for json_key, config_key in rgblight_toggles.items(): | ||
321 | if config_key in config_c and json_key in rgblight: | ||
322 | _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key) | ||
323 | |||
324 | rgblight[json_key] = config_key in config_c | ||
325 | |||
326 | for json_key, config_key in rgblight_animations.items(): | ||
327 | if config_key in config_c: | ||
328 | if json_key in animations: | ||
329 | _log_warning(info_data, 'RGB Light: animations: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) | ||
330 | |||
331 | animations[json_key] = config_c[config_key] | ||
332 | |||
333 | if animations: | ||
334 | rgblight['animations'] = animations | ||
335 | |||
336 | if rgblight: | ||
337 | info_data['rgblight'] = rgblight | ||
338 | |||
339 | return info_data | ||
340 | |||
341 | |||
342 | def _pin_name(pin): | 171 | def _pin_name(pin): |
343 | """Returns the proper representation for a pin. | 172 | """Returns the proper representation for a pin. |
344 | """ | 173 | """ |
@@ -426,34 +255,59 @@ def _extract_matrix_info(info_data, config_c): | |||
426 | return info_data | 255 | return info_data |
427 | 256 | ||
428 | 257 | ||
429 | def _extract_usb_info(info_data, config_c): | 258 | def _extract_config_h(info_data): |
430 | """Populate the USB information. | 259 | """Pull some keyboard information from existing config.h files |
431 | """ | 260 | """ |
432 | if 'usb' not in info_data: | 261 | config_c = config_h(info_data['keyboard_folder']) |
433 | info_data['usb'] = {} | ||
434 | 262 | ||
435 | for info_name, config_name in usb_properties.items(): | 263 | # Pull in data from the json map |
436 | if config_name in config_c: | 264 | dotty_info = dotty(info_data) |
437 | if info_name in info_data['usb']: | 265 | info_config_map = _json_load(Path('data/mappings/info_config.json')) |
438 | _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) | ||
439 | 266 | ||
440 | info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper() | 267 | for config_key, info_dict in info_config_map.items(): |
268 | info_key = info_dict['info_key'] | ||
269 | key_type = info_dict.get('value_type', 'str') | ||
441 | 270 | ||
442 | return info_data | 271 | try: |
272 | if config_key in config_c and info_dict.get('to_json', True): | ||
273 | if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): | ||
274 | _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key)) | ||
443 | 275 | ||
276 | if key_type.startswith('array'): | ||
277 | if '.' in key_type: | ||
278 | key_type, array_type = key_type.split('.', 1) | ||
279 | else: | ||
280 | array_type = None | ||
444 | 281 | ||
445 | def _extract_config_h(info_data): | 282 | config_value = config_c[config_key].replace('{', '').replace('}', '').strip() |
446 | """Pull some keyboard information from existing config.h files | 283 | |
447 | """ | 284 | if array_type == 'int': |
448 | config_c = config_h(info_data['keyboard_folder']) | 285 | dotty_info[info_key] = list(map(int, config_value.split(','))) |
286 | else: | ||
287 | dotty_info[info_key] = config_value.split(',') | ||
288 | |||
289 | elif key_type == 'bool': | ||
290 | dotty_info[info_key] = config_c[config_key] in true_values | ||
291 | |||
292 | elif key_type == 'hex': | ||
293 | dotty_info[info_key] = '0x' + config_c[config_key][2:].upper() | ||
449 | 294 | ||
450 | _extract_debounce(info_data, config_c) | 295 | elif key_type == 'list': |
451 | _extract_diode_direction(info_data, config_c) | 296 | dotty_info[info_key] = config_c[config_key].split() |
452 | _extract_indicators(info_data, config_c) | 297 | |
298 | elif key_type == 'int': | ||
299 | dotty_info[info_key] = int(config_c[config_key]) | ||
300 | |||
301 | else: | ||
302 | dotty_info[info_key] = config_c[config_key] | ||
303 | |||
304 | except Exception as e: | ||
305 | _log_warning(info_data, f'{config_key}->{info_key}: {e}') | ||
306 | |||
307 | info_data.update(dotty_info) | ||
308 | |||
309 | # Pull data that easily can't be mapped in json | ||
453 | _extract_matrix_info(info_data, config_c) | 310 | _extract_matrix_info(info_data, config_c) |
454 | _extract_usb_info(info_data, config_c) | ||
455 | _extract_led_matrix(info_data, config_c) | ||
456 | _extract_rgblight(info_data, config_c) | ||
457 | 311 | ||
458 | return info_data | 312 | return info_data |
459 | 313 | ||
@@ -462,21 +316,66 @@ def _extract_rules_mk(info_data): | |||
462 | """Pull some keyboard information from existing rules.mk files | 316 | """Pull some keyboard information from existing rules.mk files |
463 | """ | 317 | """ |
464 | rules = rules_mk(info_data['keyboard_folder']) | 318 | rules = rules_mk(info_data['keyboard_folder']) |
465 | mcu = rules.get('MCU', info_data.get('processor')) | 319 | info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) |
466 | 320 | ||
467 | if mcu in CHIBIOS_PROCESSORS: | 321 | if info_data['processor'] in CHIBIOS_PROCESSORS: |
468 | arm_processor_rules(info_data, rules) | 322 | arm_processor_rules(info_data, rules) |
469 | 323 | ||
470 | elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: | 324 | elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS: |
471 | avr_processor_rules(info_data, rules) | 325 | avr_processor_rules(info_data, rules) |
472 | 326 | ||
473 | else: | 327 | else: |
474 | cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) | 328 | cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor'])) |
475 | unknown_processor_rules(info_data, rules) | 329 | unknown_processor_rules(info_data, rules) |
476 | 330 | ||
477 | _extract_community_layouts(info_data, rules) | 331 | # Pull in data from the json map |
332 | dotty_info = dotty(info_data) | ||
333 | info_rules_map = _json_load(Path('data/mappings/info_rules.json')) | ||
334 | |||
335 | for rules_key, info_dict in info_rules_map.items(): | ||
336 | info_key = info_dict['info_key'] | ||
337 | key_type = info_dict.get('value_type', 'str') | ||
338 | |||
339 | try: | ||
340 | if rules_key in rules and info_dict.get('to_json', True): | ||
341 | if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): | ||
342 | _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) | ||
343 | |||
344 | if key_type.startswith('array'): | ||
345 | if '.' in key_type: | ||
346 | key_type, array_type = key_type.split('.', 1) | ||
347 | else: | ||
348 | array_type = None | ||
349 | |||
350 | rules_value = rules[rules_key].replace('{', '').replace('}', '').strip() | ||
351 | |||
352 | if array_type == 'int': | ||
353 | dotty_info[info_key] = list(map(int, rules_value.split(','))) | ||
354 | else: | ||
355 | dotty_info[info_key] = rules_value.split(',') | ||
356 | |||
357 | elif key_type == 'list': | ||
358 | dotty_info[info_key] = rules[rules_key].split() | ||
359 | |||
360 | elif key_type == 'bool': | ||
361 | dotty_info[info_key] = rules[rules_key] in true_values | ||
362 | |||
363 | elif key_type == 'hex': | ||
364 | dotty_info[info_key] = '0x' + rules[rules_key][2:].upper() | ||
365 | |||
366 | elif key_type == 'int': | ||
367 | dotty_info[info_key] = int(rules[rules_key]) | ||
368 | |||
369 | else: | ||
370 | dotty_info[info_key] = rules[rules_key] | ||
371 | |||
372 | except Exception as e: | ||
373 | _log_warning(info_data, f'{rules_key}->{info_key}: {e}') | ||
374 | |||
375 | info_data.update(dotty_info) | ||
376 | |||
377 | # Merge in config values that can't be easily mapped | ||
478 | _extract_features(info_data, rules) | 378 | _extract_features(info_data, rules) |
479 | _extract_led_drivers(info_data, rules) | ||
480 | 379 | ||
481 | return info_data | 380 | return info_data |
482 | 381 | ||
@@ -565,23 +464,7 @@ def arm_processor_rules(info_data, rules): | |||
565 | info_data['processor_type'] = 'arm' | 464 | info_data['processor_type'] = 'arm' |
566 | info_data['protocol'] = 'ChibiOS' | 465 | info_data['protocol'] = 'ChibiOS' |
567 | 466 | ||
568 | if 'MCU' in rules: | 467 | if 'bootloader' not in info_data: |
569 | if 'processor' in info_data: | ||
570 | _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
571 | |||
572 | info_data['processor'] = rules['MCU'] | ||
573 | |||
574 | elif 'processor' not in info_data: | ||
575 | info_data['processor'] = 'unknown' | ||
576 | |||
577 | if 'BOOTLOADER' in rules: | ||
578 | # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first | ||
579 | # if 'bootloader' in info_data: | ||
580 | # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
581 | |||
582 | info_data['bootloader'] = rules['BOOTLOADER'] | ||
583 | |||
584 | else: | ||
585 | if 'STM32' in info_data['processor']: | 468 | if 'STM32' in info_data['processor']: |
586 | info_data['bootloader'] = 'stm32-dfu' | 469 | info_data['bootloader'] = 'stm32-dfu' |
587 | else: | 470 | else: |
@@ -594,12 +477,6 @@ def arm_processor_rules(info_data, rules): | |||
594 | elif 'ARM_ATSAM' in rules: | 477 | elif 'ARM_ATSAM' in rules: |
595 | info_data['platform'] = 'ARM_ATSAM' | 478 | info_data['platform'] = 'ARM_ATSAM' |
596 | 479 | ||
597 | if 'BOARD' in rules: | ||
598 | if 'board' in info_data: | ||
599 | _log_warning(info_data, 'Board is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
600 | |||
601 | info_data['board'] = rules['BOARD'] | ||
602 | |||
603 | return info_data | 480 | return info_data |
604 | 481 | ||
605 | 482 | ||
@@ -607,26 +484,10 @@ def avr_processor_rules(info_data, rules): | |||
607 | """Setup the default info for an AVR board. | 484 | """Setup the default info for an AVR board. |
608 | """ | 485 | """ |
609 | info_data['processor_type'] = 'avr' | 486 | info_data['processor_type'] = 'avr' |
610 | info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu' | ||
611 | info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' | 487 | info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' |
612 | info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' | 488 | info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' |
613 | 489 | ||
614 | if 'MCU' in rules: | 490 | if 'bootloader' not in info_data: |
615 | if 'processor' in info_data: | ||
616 | _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
617 | |||
618 | info_data['processor'] = rules['MCU'] | ||
619 | |||
620 | elif 'processor' not in info_data: | ||
621 | info_data['processor'] = 'unknown' | ||
622 | |||
623 | if 'BOOTLOADER' in rules: | ||
624 | # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first | ||
625 | # if 'bootloader' in info_data: | ||
626 | # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
627 | |||
628 | info_data['bootloader'] = rules['BOOTLOADER'] | ||
629 | else: | ||
630 | info_data['bootloader'] = 'atmel-dfu' | 491 | info_data['bootloader'] = 'atmel-dfu' |
631 | 492 | ||
632 | # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: | 493 | # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: |