aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk/info.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk/info.py')
-rw-r--r--lib/python/qmk/info.py284
1 files changed, 248 insertions, 36 deletions
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 5fc14dc85..7f3aabdc3 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -9,7 +9,7 @@ from milc import cli
9 9
10from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS 10from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
11from qmk.c_parse import find_layouts 11from qmk.c_parse import find_layouts
12from qmk.json_schema import deep_update, json_load, keyboard_validate, keyboard_api_validate 12from qmk.json_schema import deep_update, json_load, validate
13from qmk.keyboard import config_h, rules_mk 13from qmk.keyboard import config_h, rules_mk
14from qmk.keymap import list_keymaps 14from qmk.keymap import list_keymaps
15from qmk.makefile import parse_rules_mk_file 15from qmk.makefile import parse_rules_mk_file
@@ -49,7 +49,7 @@ def info_json(keyboard):
49 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} 49 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'}
50 50
51 # Populate layout data 51 # Populate layout data
52 layouts, aliases = _find_all_layouts(info_data, keyboard) 52 layouts, aliases = _search_keyboard_h(keyboard)
53 53
54 if aliases: 54 if aliases:
55 info_data['layout_aliases'] = aliases 55 info_data['layout_aliases'] = aliases
@@ -61,12 +61,15 @@ def info_json(keyboard):
61 61
62 # Merge in the data from info.json, config.h, and rules.mk 62 # Merge in the data from info.json, config.h, and rules.mk
63 info_data = merge_info_jsons(keyboard, info_data) 63 info_data = merge_info_jsons(keyboard, info_data)
64 info_data = _extract_config_h(info_data)
65 info_data = _extract_rules_mk(info_data) 64 info_data = _extract_rules_mk(info_data)
65 info_data = _extract_config_h(info_data)
66
67 # Ensure that we have matrix row and column counts
68 info_data = _matrix_size(info_data)
66 69
67 # Validate against the jsonschema 70 # Validate against the jsonschema
68 try: 71 try:
69 keyboard_api_validate(info_data) 72 validate(info_data, 'qmk.api.keyboard.v1')
70 73
71 except jsonschema.ValidationError as e: 74 except jsonschema.ValidationError as e:
72 json_path = '.'.join([str(p) for p in e.absolute_path]) 75 json_path = '.'.join([str(p) for p in e.absolute_path])
@@ -75,6 +78,9 @@ def info_json(keyboard):
75 78
76 # Make sure we have at least one layout 79 # Make sure we have at least one layout
77 if not info_data.get('layouts'): 80 if not info_data.get('layouts'):
81 _find_missing_layouts(info_data, keyboard)
82
83 if not info_data.get('layouts'):
78 _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.') 84 _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.')
79 85
80 # Filter out any non-existing community layouts 86 # Filter out any non-existing community layouts
@@ -90,6 +96,9 @@ def info_json(keyboard):
90 if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}): 96 if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}):
91 _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) 97 _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name))
92 98
99 # Check that the reported matrix size is consistent with the actual matrix size
100 _check_matrix(info_data)
101
93 return info_data 102 return info_data
94 103
95 104
@@ -143,10 +152,7 @@ def _pin_name(pin):
143 elif pin == 'NO_PIN': 152 elif pin == 'NO_PIN':
144 return None 153 return None
145 154
146 elif pin[0] in 'ABCDEFGHIJK' and pin[1].isdigit(): 155 return pin
147 return pin
148
149 raise ValueError(f'Invalid pin: {pin}')
150 156
151 157
152def _extract_pins(pins): 158def _extract_pins(pins):
@@ -155,10 +161,9 @@ def _extract_pins(pins):
155 return [_pin_name(pin) for pin in pins.split(',')] 161 return [_pin_name(pin) for pin in pins.split(',')]
156 162
157 163
158def _extract_direct_matrix(info_data, direct_pins): 164def _extract_direct_matrix(direct_pins):
159 """ 165 """
160 """ 166 """
161 info_data['matrix_pins'] = {}
162 direct_pin_array = [] 167 direct_pin_array = []
163 168
164 while direct_pins[-1] != '}': 169 while direct_pins[-1] != '}':
@@ -182,12 +187,157 @@ def _extract_direct_matrix(info_data, direct_pins):
182 return direct_pin_array 187 return direct_pin_array
183 188
184 189
190def _extract_audio(info_data, config_c):
191 """Populate data about the audio configuration
192 """
193 audio_pins = []
194
195 for pin in 'B5', 'B6', 'B7', 'C4', 'C5', 'C6':
196 if config_c.get(f'{pin}_AUDIO'):
197 audio_pins.append(pin)
198
199 if audio_pins:
200 info_data['audio'] = {'pins': audio_pins}
201
202
203def _extract_split_main(info_data, config_c):
204 """Populate data about the split configuration
205 """
206 # Figure out how the main half is determined
207 if config_c.get('SPLIT_HAND_PIN') is True:
208 if 'split' not in info_data:
209 info_data['split'] = {}
210
211 if 'main' in info_data['split']:
212 _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_PIN) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
213
214 info_data['split']['main'] = 'pin'
215
216 if config_c.get('SPLIT_HAND_MATRIX_GRID'):
217 if 'split' not in info_data:
218 info_data['split'] = {}
219
220 if 'main' in info_data['split']:
221 _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_MATRIX_GRID) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
222
223 info_data['split']['main'] = 'matrix_grid'
224 info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID'])
225
226 if config_c.get('EE_HANDS') is True:
227 if 'split' not in info_data:
228 info_data['split'] = {}
229
230 if 'main' in info_data['split']:
231 _log_warning(info_data, 'Split main hand is specified in both config.h (EE_HANDS) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
232
233 info_data['split']['main'] = 'eeprom'
234
235 if config_c.get('MASTER_RIGHT') is True:
236 if 'split' not in info_data:
237 info_data['split'] = {}
238
239 if 'main' in info_data['split']:
240 _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_RIGHT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
241
242 info_data['split']['main'] = 'right'
243
244 if config_c.get('MASTER_LEFT') is True:
245 if 'split' not in info_data:
246 info_data['split'] = {}
247
248 if 'main' in info_data['split']:
249 _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_LEFT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
250
251 info_data['split']['main'] = 'left'
252
253
254def _extract_split_transport(info_data, config_c):
255 # Figure out the transport method
256 if config_c.get('USE_I2C') is True:
257 if 'split' not in info_data:
258 info_data['split'] = {}
259
260 if 'transport' not in info_data['split']:
261 info_data['split']['transport'] = {}
262
263 if 'protocol' in info_data['split']['transport']:
264 _log_warning(info_data, 'Split transport is specified in both config.h (USE_I2C) and info.json (split.transport.protocol) (Value: %s), the config.h value wins.' % info_data['split']['transport'])
265
266 info_data['split']['transport']['protocol'] = 'i2c'
267
268 elif 'protocol' not in info_data.get('split', {}).get('transport', {}):
269 if 'split' not in info_data:
270 info_data['split'] = {}
271
272 if 'transport' not in info_data['split']:
273 info_data['split']['transport'] = {}
274
275 info_data['split']['transport']['protocol'] = 'serial'
276
277
278def _extract_split_right_pins(info_data, config_c):
279 # Figure out the right half matrix pins
280 row_pins = config_c.get('MATRIX_ROW_PINS_RIGHT', '').replace('{', '').replace('}', '').strip()
281 col_pins = config_c.get('MATRIX_COL_PINS_RIGHT', '').replace('{', '').replace('}', '').strip()
282 unused_pin_text = config_c.get('UNUSED_PINS_RIGHT')
283 unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None
284 direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1]
285
286 if row_pins and col_pins:
287 if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data:
288 _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
289
290 if 'split' not in info_data:
291 info_data['split'] = {}
292
293 if 'matrix_pins' not in info_data['split']:
294 info_data['split']['matrix_pins'] = {}
295
296 if 'right' not in info_data['split']['matrix_pins']:
297 info_data['split']['matrix_pins']['right'] = {}
298
299 info_data['split']['matrix_pins']['right'] = {
300 'cols': _extract_pins(col_pins),
301 'rows': _extract_pins(row_pins),
302 }
303
304 if direct_pins:
305 if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}):
306 _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
307
308 if 'split' not in info_data:
309 info_data['split'] = {}
310
311 if 'matrix_pins' not in info_data['split']:
312 info_data['split']['matrix_pins'] = {}
313
314 if 'right' not in info_data['split']['matrix_pins']:
315 info_data['split']['matrix_pins']['right'] = {}
316
317 info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins)
318
319 if unused_pins:
320 if 'split' not in info_data:
321 info_data['split'] = {}
322
323 if 'matrix_pins' not in info_data['split']:
324 info_data['split']['matrix_pins'] = {}
325
326 if 'right' not in info_data['split']['matrix_pins']:
327 info_data['split']['matrix_pins']['right'] = {}
328
329 info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins)
330
331
185def _extract_matrix_info(info_data, config_c): 332def _extract_matrix_info(info_data, config_c):
186 """Populate the matrix information. 333 """Populate the matrix information.
187 """ 334 """
188 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() 335 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
189 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() 336 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
337 unused_pin_text = config_c.get('UNUSED_PINS')
338 unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None
190 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] 339 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
340 info_snippet = {}
191 341
192 if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c: 342 if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
193 if 'matrix_size' in info_data: 343 if 'matrix_size' in info_data:
@@ -199,19 +349,35 @@ def _extract_matrix_info(info_data, config_c):
199 } 349 }
200 350
201 if row_pins and col_pins: 351 if row_pins and col_pins:
202 if 'matrix_pins' in info_data: 352 if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
203 _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') 353 _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
204 354
205 info_data['matrix_pins'] = { 355 info_snippet['cols'] = _extract_pins(col_pins)
206 'cols': _extract_pins(col_pins), 356 info_snippet['rows'] = _extract_pins(row_pins)
207 'rows': _extract_pins(row_pins),
208 }
209 357
210 if direct_pins: 358 if direct_pins:
211 if 'matrix_pins' in info_data: 359 if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:
212 _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') 360 _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
213 361
214 info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins) 362 info_snippet['direct'] = _extract_direct_matrix(direct_pins)
363
364 if unused_pins:
365 if 'matrix_pins' not in info_data:
366 info_data['matrix_pins'] = {}
367
368 info_snippet['unused'] = _extract_pins(unused_pins)
369
370 if config_c.get('CUSTOM_MATRIX', 'no') != 'no':
371 if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
372 _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.')
373
374 info_snippet['custom'] = True
375
376 if config_c['CUSTOM_MATRIX'] == 'lite':
377 info_snippet['custom_lite'] = True
378
379 if info_snippet:
380 info_data['matrix_pins'] = info_snippet
215 381
216 return info_data 382 return info_data
217 383
@@ -269,6 +435,10 @@ def _extract_config_h(info_data):
269 435
270 # Pull data that easily can't be mapped in json 436 # Pull data that easily can't be mapped in json
271 _extract_matrix_info(info_data, config_c) 437 _extract_matrix_info(info_data, config_c)
438 _extract_audio(info_data, config_c)
439 _extract_split_main(info_data, config_c)
440 _extract_split_transport(info_data, config_c)
441 _extract_split_right_pins(info_data, config_c)
272 442
273 return info_data 443 return info_data
274 444
@@ -341,12 +511,53 @@ def _extract_rules_mk(info_data):
341 return info_data 511 return info_data
342 512
343 513
344def _search_keyboard_h(path): 514def _matrix_size(info_data):
515 """Add info_data['matrix_size'] if it doesn't exist.
516 """
517 if 'matrix_size' not in info_data and 'matrix_pins' in info_data:
518 info_data['matrix_size'] = {}
519
520 if 'direct' in info_data['matrix_pins']:
521 info_data['matrix_size']['cols'] = len(info_data['matrix_pins']['direct'][0])
522 info_data['matrix_size']['rows'] = len(info_data['matrix_pins']['direct'])
523 elif 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
524 info_data['matrix_size']['cols'] = len(info_data['matrix_pins']['cols'])
525 info_data['matrix_size']['rows'] = len(info_data['matrix_pins']['rows'])
526
527 return info_data
528
529
530def _check_matrix(info_data):
531 """Check the matrix to ensure that row/column count is consistent.
532 """
533 if 'matrix_pins' in info_data and 'matrix_size' in info_data:
534 actual_col_count = info_data['matrix_size'].get('cols', 0)
535 actual_row_count = info_data['matrix_size'].get('rows', 0)
536 col_count = row_count = 0
537
538 if 'direct' in info_data['matrix_pins']:
539 col_count = len(info_data['matrix_pins']['direct'][0])
540 row_count = len(info_data['matrix_pins']['direct'])
541 elif 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
542 col_count = len(info_data['matrix_pins']['cols'])
543 row_count = len(info_data['matrix_pins']['rows'])
544
545 if col_count != actual_col_count and col_count != (actual_col_count / 2):
546 # FIXME: once we can we should detect if split is enabled to do the actual_col_count/2 check.
547 _log_error(info_data, f'MATRIX_COLS is inconsistent with the size of MATRIX_COL_PINS: {col_count} != {actual_col_count}')
548
549 if row_count != actual_row_count and row_count != (actual_row_count / 2):
550 # FIXME: once we can we should detect if split is enabled to do the actual_row_count/2 check.
551 _log_error(info_data, f'MATRIX_ROWS is inconsistent with the size of MATRIX_ROW_PINS: {row_count} != {actual_row_count}')
552
553
554def _search_keyboard_h(keyboard):
555 keyboard = Path(keyboard)
345 current_path = Path('keyboards/') 556 current_path = Path('keyboards/')
346 aliases = {} 557 aliases = {}
347 layouts = {} 558 layouts = {}
348 559
349 for directory in path.parts: 560 for directory in keyboard.parts:
350 current_path = current_path / directory 561 current_path = current_path / directory
351 keyboard_h = '%s.h' % (directory,) 562 keyboard_h = '%s.h' % (directory,)
352 keyboard_h_path = current_path / keyboard_h 563 keyboard_h_path = current_path / keyboard_h
@@ -361,27 +572,28 @@ def _search_keyboard_h(path):
361 return layouts, aliases 572 return layouts, aliases
362 573
363 574
364def _find_all_layouts(info_data, keyboard): 575def _find_missing_layouts(info_data, keyboard):
365 """Looks for layout macros associated with this keyboard. 576 """Looks for layout macros when they aren't found other places.
366 """
367 layouts, aliases = _search_keyboard_h(Path(keyboard))
368 577
369 if not layouts: 578 If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above.
370 # If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above. 579 """
371 info_data['parse_warnings'].append('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard)) 580 _log_warning(info_data, '%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
372 581
373 for file in glob('keyboards/%s/*.h' % keyboard): 582 for file in glob('keyboards/%s/*.h' % keyboard):
374 if file.endswith('.h'): 583 these_layouts, these_aliases = find_layouts(file)
375 these_layouts, these_aliases = find_layouts(file)
376 584
377 if these_layouts: 585 if these_layouts:
378 layouts.update(these_layouts) 586 for layout_name, layout_json in these_layouts.items():
587 if not layout_name.startswith('LAYOUT_kc'):
588 layout_json['c_macro'] = True
589 info_data['layouts'][layout_name] = layout_json
379 590
380 for alias, alias_text in these_aliases.items(): 591 for alias, alias_text in these_aliases.items():
381 if alias_text in layouts: 592 if alias_text in these_layouts:
382 aliases[alias] = alias_text 593 if 'layout_aliases' not in info_data:
594 info_data['layout_aliases'] = {}
383 595
384 return layouts, aliases 596 info_data['layout_aliases'][alias] = alias_text
385 597
386 598
387def _log_error(info_data, message): 599def _log_error(info_data, message):
@@ -460,7 +672,7 @@ def merge_info_jsons(keyboard, info_data):
460 continue 672 continue
461 673
462 try: 674 try:
463 keyboard_validate(new_info_data) 675 validate(new_info_data, 'qmk.keyboard.v1')
464 except jsonschema.ValidationError as e: 676 except jsonschema.ValidationError as e:
465 json_path = '.'.join([str(p) for p in e.absolute_path]) 677 json_path = '.'.join([str(p) for p in e.absolute_path])
466 cli.log.error('Not including data from file: %s', info_file) 678 cli.log.error('Not including data from file: %s', info_file)