4
4
from glob import glob
5
5
from pathlib import Path
6
6
7
+ import jsonschema
7
8
from milc import cli
8
9
9
10
from qmk .constants import CHIBIOS_PROCESSORS , LUFA_PROCESSORS , VUSB_PROCESSORS , LED_INDICATORS
13
14
from qmk .makefile import parse_rules_mk_file
14
15
from qmk .math import compute
15
16
17
+ led_matrix_properties = {
18
+ 'driver_count' : 'LED_DRIVER_COUNT' ,
19
+ 'driver_addr1' : 'LED_DRIVER_ADDR_1' ,
20
+ 'driver_addr2' : 'LED_DRIVER_ADDR_2' ,
21
+ 'driver_addr3' : 'LED_DRIVER_ADDR_3' ,
22
+ 'driver_addr4' : 'LED_DRIVER_ADDR_4' ,
23
+ 'led_count' : 'LED_DRIVER_LED_COUNT' ,
24
+ 'timeout' : 'ISSI_TIMEOUT' ,
25
+ 'persistence' : 'ISSI_PERSISTENCE'
26
+ }
27
+
16
28
rgblight_properties = {
17
29
'led_count' : 'RGBLED_NUM' ,
18
30
'pin' : 'RGB_DI_PIN' ,
@@ -80,6 +92,15 @@ def info_json(keyboard):
80
92
info_data = _extract_config_h (info_data )
81
93
info_data = _extract_rules_mk (info_data )
82
94
95
+ # Validate against the jsonschema
96
+ try :
97
+ keyboard_api_validate (info_data )
98
+
99
+ except jsonschema .ValidationError as e :
100
+ cli .log .error ('Invalid info.json data: %s' , e .message )
101
+ print (dir (e ))
102
+ exit ()
103
+
83
104
# Make sure we have at least one layout
84
105
if not info_data .get ('layouts' ):
85
106
_log_error (info_data , 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.' )
@@ -102,14 +123,58 @@ def info_json(keyboard):
102
123
return info_data
103
124
104
125
126
+ def _json_load (json_file ):
127
+ """Load a json file from disk.
128
+
129
+ Note: file must be a Path object.
130
+ """
131
+ try :
132
+ return json .load (json_file .open ())
133
+
134
+ except json .decoder .JSONDecodeError as e :
135
+ cli .log .error ('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n \t {fg_red}%s' , json_file , e )
136
+ exit (1 )
137
+
138
+
139
+ def _jsonschema (schema_name ):
140
+ """Read a jsonschema file from disk.
141
+ """
142
+ schema_path = Path (f'data/schemas/{ schema_name } .jsonschema' )
143
+
144
+ if not schema_path .exists ():
145
+ schema_path = Path ('data/schemas/false.jsonschema' )
146
+
147
+ return _json_load (schema_path )
148
+
149
+
150
+ def keyboard_validate (data ):
151
+ """Validates data against the keyboard jsonschema.
152
+ """
153
+ schema = _jsonschema ('keyboard' )
154
+ validator = jsonschema .Draft7Validator (schema ).validate
155
+
156
+ return validator (data )
157
+
158
+
159
+ def keyboard_api_validate (data ):
160
+ """Validates data against the api_keyboard jsonschema.
161
+ """
162
+ base = _jsonschema ('keyboard' )
163
+ relative = _jsonschema ('api_keyboard' )
164
+ resolver = jsonschema .RefResolver .from_schema (base )
165
+ validator = jsonschema .Draft7Validator (relative , resolver = resolver ).validate
166
+
167
+ return validator (data )
168
+
169
+
105
170
def _extract_debounce (info_data , config_c ):
106
171
"""Handle debounce.
107
172
"""
108
173
if 'debounce' in info_data and 'DEBOUNCE' in config_c :
109
174
_log_warning (info_data , 'Debounce is specified in both info.json and config.h, the config.h value wins.' )
110
175
111
176
if 'DEBOUNCE' in config_c :
112
- info_data ['debounce' ] = config_c . get ( 'DEBOUNCE' )
177
+ info_data ['debounce' ] = int ( config_c [ 'DEBOUNCE' ] )
113
178
114
179
return info_data
115
180
@@ -181,8 +246,36 @@ def _extract_features(info_data, rules):
181
246
return info_data
182
247
183
248
249
+ def _extract_led_drivers (info_data , rules ):
250
+ """Find all the LED drivers set in rules.mk.
251
+ """
252
+ if 'LED_MATRIX_DRIVER' in rules :
253
+ if 'led_matrix' not in info_data :
254
+ info_data ['led_matrix' ] = {}
255
+
256
+ if info_data ['led_matrix' ].get ('driver' ):
257
+ _log_warning (info_data , 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.' )
258
+
259
+ info_data ['led_matrix' ]['driver' ] = rules ['LED_MATRIX_DRIVER' ]
260
+
261
+ return info_data
262
+
263
+
264
+ def _extract_led_matrix (info_data , config_c ):
265
+ """Handle the led_matrix configuration.
266
+ """
267
+ led_matrix = info_data .get ('led_matrix' , {})
268
+
269
+ for json_key , config_key in led_matrix_properties .items ():
270
+ if config_key in config_c :
271
+ if json_key in led_matrix :
272
+ _log_warning (info_data , 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key ,))
273
+
274
+ led_matrix [json_key ] = config_c [config_key ]
275
+
276
+
184
277
def _extract_rgblight (info_data , config_c ):
185
- """Handle the rgblight configuration
278
+ """Handle the rgblight configuration.
186
279
"""
187
280
rgblight = info_data .get ('rgblight' , {})
188
281
animations = rgblight .get ('animations' , {})
@@ -303,6 +396,7 @@ def _extract_config_h(info_data):
303
396
_extract_indicators (info_data , config_c )
304
397
_extract_matrix_info (info_data , config_c )
305
398
_extract_usb_info (info_data , config_c )
399
+ _extract_led_matrix (info_data , config_c )
306
400
_extract_rgblight (info_data , config_c )
307
401
308
402
return info_data
@@ -326,6 +420,7 @@ def _extract_rules_mk(info_data):
326
420
327
421
_extract_community_layouts (info_data , rules )
328
422
_extract_features (info_data , rules )
423
+ _extract_led_drivers (info_data , rules )
329
424
330
425
return info_data
331
426
@@ -412,13 +507,28 @@ def arm_processor_rules(info_data, rules):
412
507
"""Setup the default info for an ARM board.
413
508
"""
414
509
info_data ['processor_type' ] = 'arm'
415
- info_data ['bootloader' ] = rules ['BOOTLOADER' ] if 'BOOTLOADER' in rules else 'unknown'
416
- info_data ['processor' ] = rules ['MCU' ] if 'MCU' in rules else 'unknown'
417
510
info_data ['protocol' ] = 'ChibiOS'
418
511
419
- if info_data ['bootloader' ] == 'unknown' :
512
+ if 'MCU' in rules :
513
+ if 'processor' in info_data :
514
+ _log_warning (info_data , 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.' )
515
+
516
+ info_data ['processor' ] = rules ['MCU' ]
517
+
518
+ elif 'processor' not in info_data :
519
+ info_data ['processor' ] = 'unknown'
520
+
521
+ if 'BOOTLOADER' in rules :
522
+ if 'bootloader' in info_data :
523
+ _log_warning (info_data , 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.' )
524
+
525
+ info_data ['bootloader' ] = rules ['BOOTLOADER' ]
526
+
527
+ else :
420
528
if 'STM32' in info_data ['processor' ]:
421
529
info_data ['bootloader' ] = 'stm32-dfu'
530
+ else :
531
+ info_data ['bootloader' ] = 'unknown'
422
532
423
533
if 'STM32' in info_data ['processor' ]:
424
534
info_data ['platform' ] = 'STM32'
@@ -436,9 +546,25 @@ def avr_processor_rules(info_data, rules):
436
546
info_data ['processor_type' ] = 'avr'
437
547
info_data ['bootloader' ] = rules ['BOOTLOADER' ] if 'BOOTLOADER' in rules else 'atmel-dfu'
438
548
info_data ['platform' ] = rules ['ARCH' ] if 'ARCH' in rules else 'unknown'
439
- info_data ['processor' ] = rules ['MCU' ] if 'MCU' in rules else 'unknown'
440
549
info_data ['protocol' ] = 'V-USB' if rules .get ('MCU' ) in VUSB_PROCESSORS else 'LUFA'
441
550
551
+ if 'MCU' in rules :
552
+ if 'processor' in info_data :
553
+ _log_warning (info_data , 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.' )
554
+
555
+ info_data ['processor' ] = rules ['MCU' ]
556
+
557
+ elif 'processor' not in info_data :
558
+ info_data ['processor' ] = 'unknown'
559
+
560
+ if 'BOOTLOADER' in rules :
561
+ if 'bootloader' in info_data :
562
+ _log_warning (info_data , 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.' )
563
+
564
+ info_data ['bootloader' ] = rules ['BOOTLOADER' ]
565
+ else :
566
+ info_data ['bootloader' ] = 'atmel-dfu'
567
+
442
568
# FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk:
443
569
# info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA'
444
570
@@ -463,10 +589,13 @@ def merge_info_jsons(keyboard, info_data):
463
589
for info_file in find_info_json (keyboard ):
464
590
# Load and validate the JSON data
465
591
try :
466
- new_info_data = json .load (info_file .open ('r' ))
467
- except Exception as e :
468
- _log_error (info_data , "Invalid JSON in file %s: %s: %s" % (str (info_file ), e .__class__ .__name__ , e ))
469
- new_info_data = {}
592
+ new_info_data = _json_load (info_file )
593
+ keyboard_validate (new_info_data )
594
+
595
+ except jsonschema .ValidationError as e :
596
+ cli .log .error ('Invalid info.json data: %s' , e .message )
597
+ cli .log .error ('Not including file %s' , info_file )
598
+ continue
470
599
471
600
if not isinstance (new_info_data , dict ):
472
601
_log_error (info_data , "Invalid file %s, root object should be a dictionary." % (str (info_file ),))
@@ -479,7 +608,7 @@ def merge_info_jsons(keyboard, info_data):
479
608
480
609
# Deep merge certain keys
481
610
# FIXME(skullydazed/anyone): this should be generalized more so that we can inteligently merge more than one level deep. It would be nice if we could filter on valid keys too. That may have to wait for a future where we use openapi or something.
482
- for key in ('features' , 'layout_aliases' , 'matrix_pins' , 'rgblight' , 'usb' ):
611
+ for key in ('features' , 'layout_aliases' , 'led_matrix' , ' matrix_pins' , 'rgblight' , 'usb' ):
483
612
if key in new_info_data :
484
613
if key not in info_data :
485
614
info_data [key ] = {}
0 commit comments