1+ import os , sys
2+ import json
3+ import glob
4+
5+ hadError = False
6+ firmwares = set ()
7+
8+ def error (msg ):
9+ global hadError
10+ hadError = True
11+ print (msg )
12+
13+ def validate_stm32 (vendor , type , devname , device ):
14+ for method in device ['upload_methods' ]:
15+ if method not in ['stlink' , 'dfu' , 'uart' , 'wifi' , 'betaflight' ]:
16+ error (f'Invalid upload method "{ method } " for target "{ vendor } .{ type } .{ devname } "' )
17+ if 'stlink' not in device ['upload_methods' ]:
18+ error (f'STM32 based devices must always have "stlink" as an upload_method for target "{ vendor } .{ type } .{ devname } "' )
19+ if 'stlink' not in device :
20+ error (f'STM32 based devices must always have "stlink" attribute for target "{ vendor } .{ type } .{ devname } "' )
21+ else :
22+ stlink = device ['stlink' ]
23+ if 'cpus' not in stlink :
24+ error (f'The "stlink" attribute for target "{ vendor } .{ type } .{ devname } " must have a list of valid "cpus"' )
25+ if 'offset' not in stlink :
26+ error (f'The "stlink" attribute for target "{ vendor } .{ type } .{ devname } " must have a valid "offset"' )
27+ if 'bootloader' not in stlink :
28+ error (f'The "stlink" attribute for target "{ vendor } .{ type } .{ devname } " must have a valid "bootloader"' )
29+ # could check the existence of the bootloader file
30+
31+ def validate_esp (vendor , type , devname , device ):
32+ if 'lua_name' not in device :
33+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "lua_name" child element' )
34+ if len (device ['lua_name' ]) > 16 :
35+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "lua_name" of 16 characters or less' )
36+ # validate layout_file
37+ if 'layout_file' not in device :
38+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "layout_file" child element' )
39+ else :
40+ dir = 'hardware/' + ('RX/' if type .startswith ('rx' ) else 'TX/' )
41+ if not os .path .isfile (dir + device ['layout_file' ]):
42+ layout_file = device ['layout_file' ]
43+ error (f'File specified by layout_file "{ layout_file } " in target "{ vendor } .{ type } .{ devname } ", does not exist' )
44+ # could validate overlay
45+
46+ def validate_esp32 (vendor , type , devname , device ):
47+ for method in device ['upload_methods' ]:
48+ if method not in ['uart' , 'etx' , 'wifi' , 'betaflight' ]:
49+ error (f'Invalid upload method "{ method } " for target "{ vendor } .{ type } .{ devname } "' )
50+ validate_esp (vendor , type , devname , device )
51+
52+ def validate_esp8285 (vendor , type , devname , device ):
53+ for method in device ['upload_methods' ]:
54+ if method not in ['uart' , 'wifi' , 'betaflight' ]:
55+ error (f'Invalid upload method "{ method } " for target "{ vendor } .{ type } .{ devname } "' )
56+ validate_esp (vendor , type , devname , device )
57+
58+ def validate_devices (vendor , type , devname , device ):
59+ if devname != devname .lower ():
60+ error (f'device tag "{ devname } " should be lowercase' )
61+ if 'product_name' not in device :
62+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "product_name" child element' )
63+ if 'upload_methods' not in device :
64+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "upload_methods" child element' )
65+
66+ if 'firmware' not in device :
67+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "firmware" child element' )
68+ else :
69+ firmware = device ['firmware' ]
70+ if firmware not in firmwares :
71+ error (f'device "{ vendor } .{ type } .{ devname } " has an invalid firmware file "{ firmware } "' )
72+ elif (firmware .endswith ('_TX' ) and 'tx_' not in type ):
73+ error (f'device "{ vendor } .{ type } .{ devname } " has an invalid firmware file "{ firmware } ", it must be a TX target firmware' )
74+ elif (firmware .endswith ('_RX' ) and 'rx_' not in type ):
75+ error (f'device "{ vendor } .{ type } .{ devname } " has an invalid firmware file "{ firmware } ", it must be an RX target firmware' )
76+
77+ if 'platform' not in device :
78+ error (f'device "{ vendor } .{ type } .{ devname } " must have a "platform" child element' )
79+ else :
80+ platform = device ['platform' ]
81+ if platform == 'stm32' :
82+ validate_stm32 (vendor , type , devname , device )
83+ elif platform == 'esp32' :
84+ validate_esp32 (vendor , type , devname , device )
85+ elif platform == 'esp8285' :
86+ validate_esp8285 (vendor , type , devname , device )
87+ else :
88+ error (f'invalid platform "{ platform } " in device "{ vendor } .{ type } .{ devname } "' )
89+
90+ if 'features' in device :
91+ for feature in device ['features' ]:
92+ if feature not in ['buzzer' , 'unlock-higher-power' , 'fan' , 'sbus-uart' ]:
93+ error (f'features must contain one or more of [\' buzzer\' , \' unlock-higher-power\' , \' fan\' , \' sbus-uart\' ], if present in target "{ vendor } .{ type } .{ devname } "' )
94+
95+ def validate_vendor (name , types ):
96+ if name != name .lower ():
97+ error (f'vendor tag "{ vendor } " should be lowercase' )
98+
99+ if 'name' not in types :
100+ error (f'vendor "{ vendor } " must have a "name" child element' )
101+
102+ for type in types :
103+ if type not in ['rx_2400' , 'rx_900' , 'tx_2400' , 'tx_900' , 'name' ]:
104+ error (f'invalid tag "{ type } " in "{ vendor } "' )
105+ if type in ['rx_2400' , 'rx_900' , 'tx_2400' , 'tx_900' ]:
106+ for device in types [type ]:
107+ validate_devices (name , type , device , types [type ][device ])
108+
109+ if __name__ == '__main__' :
110+ targets = {}
111+ with open ('hardware/targets.json' ) as f :
112+ targets = json .load (f )
113+
114+ for inifile in glob .iglob ('targets/*.ini' ):
115+ with open (inifile ) as ini :
116+ for line in ini :
117+ if line .startswith ('[env:' ):
118+ try :
119+ firmware_file = line [5 :line .index ('_via_' )]
120+ firmwares .add (firmware_file )
121+ except ValueError :
122+ print (line )
123+ None
124+
125+ for vendor in targets :
126+ validate_vendor (vendor , targets [vendor ])
127+
128+ for inifile in glob .iglob ('targets/*.ini' ):
129+ with open (inifile ) as ini :
130+ for line in ini :
131+ if line .startswith ('[env:' ):
132+ target = line
133+ if line .startswith ('board_config' ):
134+ eq = line .find ('=' )
135+ board = line [eq + 1 :].strip ()
136+ parts = board .split ('.' )
137+ if len (parts ) != 3 :
138+ error (f'{ inifile } : board_config must have 3 parts' )
139+ else :
140+ vendor = parts [0 ]
141+ type = parts [1 ]
142+ device = parts [2 ]
143+ if vendor not in targets :
144+ error (f'{ inifile } : targets.json file does not contain vendor { vendor } ' )
145+ elif type not in targets [vendor ]:
146+ error (f'{ inifile } : targets.json "{ vendor } " does not contain type { type } ' )
147+ elif device not in targets [vendor ][type ]:
148+ error (f'{ inifile } : targets.json "{ vendor } .{ type } " does not contain device { device } ' )
149+ if hadError :
150+ sys .exit (1 )
151+ sys .exit (0 )
0 commit comments