You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
8.9KB

  1. """Functions that help us generate and use info.json files.
  2. """
  3. import json
  4. from glob import glob
  5. from pathlib import Path
  6. from milc import cli
  7. from qmk.constants import ARM_PROCESSORS, AVR_PROCESSORS, VUSB_PROCESSORS
  8. from qmk.c_parse import find_layouts
  9. from qmk.keyboard import config_h, rules_mk
  10. from qmk.math import compute
  11. def info_json(keyboard):
  12. """Generate the info.json data for a specific keyboard.
  13. """
  14. info_data = {
  15. 'keyboard_name': str(keyboard),
  16. 'keyboard_folder': str(keyboard),
  17. 'layouts': {},
  18. 'maintainer': 'qmk',
  19. }
  20. for layout_name, layout_json in _find_all_layouts(keyboard).items():
  21. if not layout_name.startswith('LAYOUT_kc'):
  22. info_data['layouts'][layout_name] = layout_json
  23. info_data = merge_info_jsons(keyboard, info_data)
  24. info_data = _extract_config_h(info_data)
  25. info_data = _extract_rules_mk(info_data)
  26. return info_data
  27. def _extract_config_h(info_data):
  28. """Pull some keyboard information from existing rules.mk files
  29. """
  30. config_c = config_h(info_data['keyboard_folder'])
  31. row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
  32. col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
  33. direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
  34. info_data['diode_direction'] = config_c.get('DIODE_DIRECTION')
  35. info_data['matrix_size'] = {
  36. 'rows': compute(config_c.get('MATRIX_ROWS', '0')),
  37. 'cols': compute(config_c.get('MATRIX_COLS', '0')),
  38. }
  39. info_data['matrix_pins'] = {}
  40. if row_pins:
  41. info_data['matrix_pins']['rows'] = row_pins.split(',')
  42. if col_pins:
  43. info_data['matrix_pins']['cols'] = col_pins.split(',')
  44. if direct_pins:
  45. direct_pin_array = []
  46. for row in direct_pins.split('},{'):
  47. if row.startswith('{'):
  48. row = row[1:]
  49. if row.endswith('}'):
  50. row = row[:-1]
  51. direct_pin_array.append([])
  52. for pin in row.split(','):
  53. if pin == 'NO_PIN':
  54. pin = None
  55. direct_pin_array[-1].append(pin)
  56. info_data['matrix_pins']['direct'] = direct_pin_array
  57. info_data['usb'] = {
  58. 'vid': config_c.get('VENDOR_ID'),
  59. 'pid': config_c.get('PRODUCT_ID'),
  60. 'device_ver': config_c.get('DEVICE_VER'),
  61. 'manufacturer': config_c.get('MANUFACTURER'),
  62. 'product': config_c.get('PRODUCT'),
  63. }
  64. return info_data
  65. def _extract_rules_mk(info_data):
  66. """Pull some keyboard information from existing rules.mk files
  67. """
  68. rules = rules_mk(info_data['keyboard_folder'])
  69. mcu = rules.get('MCU')
  70. if mcu in ARM_PROCESSORS:
  71. arm_processor_rules(info_data, rules)
  72. elif mcu in AVR_PROCESSORS:
  73. avr_processor_rules(info_data, rules)
  74. else:
  75. cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu))
  76. unknown_processor_rules(info_data, rules)
  77. return info_data
  78. def _find_all_layouts(keyboard):
  79. """Looks for layout macros associated with this keyboard.
  80. """
  81. layouts = {}
  82. rules = rules_mk(keyboard)
  83. keyboard_path = Path(rules.get('DEFAULT_FOLDER', keyboard))
  84. # Pull in all layouts defined in the standard files
  85. current_path = Path('keyboards/')
  86. for directory in keyboard_path.parts:
  87. current_path = current_path / directory
  88. keyboard_h = '%s.h' % (directory,)
  89. keyboard_h_path = current_path / keyboard_h
  90. if keyboard_h_path.exists():
  91. layouts.update(find_layouts(keyboard_h_path))
  92. if not layouts:
  93. # If we didn't find any layouts above we widen our search. This is error
  94. # prone which is why we want to encourage people to follow the standard above.
  95. cli.log.warning('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
  96. for file in glob('keyboards/%s/*.h' % keyboard):
  97. if file.endswith('.h'):
  98. these_layouts = find_layouts(file)
  99. if these_layouts:
  100. layouts.update(these_layouts)
  101. if 'LAYOUTS' in rules:
  102. # Match these up against the supplied layouts
  103. supported_layouts = rules['LAYOUTS'].strip().split()
  104. for layout_name in sorted(layouts):
  105. if not layout_name.startswith('LAYOUT_'):
  106. continue
  107. layout_name = layout_name[7:]
  108. if layout_name in supported_layouts:
  109. supported_layouts.remove(layout_name)
  110. if supported_layouts:
  111. cli.log.error('%s: Missing LAYOUT() macro for %s' % (keyboard, ', '.join(supported_layouts)))
  112. return layouts
  113. def arm_processor_rules(info_data, rules):
  114. """Setup the default info for an ARM board.
  115. """
  116. info_data['processor_type'] = 'arm'
  117. info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'unknown'
  118. info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown'
  119. info_data['protocol'] = 'ChibiOS'
  120. if info_data['bootloader'] == 'unknown':
  121. if 'STM32' in info_data['processor']:
  122. info_data['bootloader'] = 'stm32-dfu'
  123. elif info_data.get('manufacturer') == 'Input Club':
  124. info_data['bootloader'] = 'kiibohd-dfu'
  125. if 'STM32' in info_data['processor']:
  126. info_data['platform'] = 'STM32'
  127. elif 'MCU_SERIES' in rules:
  128. info_data['platform'] = rules['MCU_SERIES']
  129. elif 'ARM_ATSAM' in rules:
  130. info_data['platform'] = 'ARM_ATSAM'
  131. return info_data
  132. def avr_processor_rules(info_data, rules):
  133. """Setup the default info for an AVR board.
  134. """
  135. info_data['processor_type'] = 'avr'
  136. info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu'
  137. info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown'
  138. info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown'
  139. info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA'
  140. # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk:
  141. # info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA'
  142. return info_data
  143. def unknown_processor_rules(info_data, rules):
  144. """Setup the default keyboard info for unknown boards.
  145. """
  146. info_data['bootloader'] = 'unknown'
  147. info_data['platform'] = 'unknown'
  148. info_data['processor'] = 'unknown'
  149. info_data['processor_type'] = 'unknown'
  150. info_data['protocol'] = 'unknown'
  151. return info_data
  152. def merge_info_jsons(keyboard, info_data):
  153. """Return a merged copy of all the info.json files for a keyboard.
  154. """
  155. for info_file in find_info_json(keyboard):
  156. # Load and validate the JSON data
  157. with info_file.open('r') as info_fd:
  158. new_info_data = json.load(info_fd)
  159. if not isinstance(new_info_data, dict):
  160. cli.log.error("Invalid file %s, root object should be a dictionary.", str(info_file))
  161. continue
  162. # Copy whitelisted keys into `info_data`
  163. for key in ('keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'):
  164. if key in new_info_data:
  165. info_data[key] = new_info_data[key]
  166. # Merge the layouts in
  167. if 'layouts' in new_info_data:
  168. for layout_name, json_layout in new_info_data['layouts'].items():
  169. # Only pull in layouts we have a macro for
  170. if layout_name in info_data['layouts']:
  171. if info_data['layouts'][layout_name]['key_count'] != len(json_layout['layout']):
  172. cli.log.error('%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s', info_data['keyboard_folder'], layout_name, len(json_layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout']))
  173. else:
  174. for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
  175. key.update(json_layout['layout'][i])
  176. return info_data
  177. def find_info_json(keyboard):
  178. """Finds all the info.json files associated with a keyboard.
  179. """
  180. # Find the most specific first
  181. base_path = Path('keyboards')
  182. keyboard_path = base_path / keyboard
  183. keyboard_parent = keyboard_path.parent
  184. info_jsons = [keyboard_path / 'info.json']
  185. # Add DEFAULT_FOLDER before parents, if present
  186. rules = rules_mk(keyboard)
  187. if 'DEFAULT_FOLDER' in rules:
  188. info_jsons.append(Path(rules['DEFAULT_FOLDER']) / 'info.json')
  189. # Add in parent folders for least specific
  190. for _ in range(5):
  191. info_jsons.append(keyboard_parent / 'info.json')
  192. if keyboard_parent.parent == base_path:
  193. break
  194. keyboard_parent = keyboard_parent.parent
  195. # Return a list of the info.json files that actually exist
  196. return [info_json for info_json in info_jsons if info_json.exists()]