1
+ # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ import json
6
+ import os
7
+ import re
8
+ import sys
9
+ from collections import defaultdict
10
+
11
+
12
+ def get_board_pins (pin_filename ):
13
+ records = []
14
+
15
+ with open (pin_filename , encoding = "utf-8" ) as pin_file :
16
+ for line in pin_file :
17
+ if line .strip ()[0 :2 ] == "//" :
18
+ continue
19
+
20
+ search = re .search (r"MP_ROM_QSTR\(MP_QSTR_(.*?)\), MP_ROM_PTR" , line )
21
+ if search is None :
22
+ search = re .search (
23
+ r"MP_OBJ_NEW_QSTR\(MP_QSTR_(.*?)\), MP_ROM_PTR" , line
24
+ )
25
+ if search is None :
26
+ continue
27
+
28
+ board_member = search .group (1 )
29
+
30
+ board_type_search = re .search (r"MP_ROM_PTR\(&pin_(.*?)\)" , line )
31
+ if board_type_search :
32
+ board_type = "pin"
33
+ else :
34
+ if board_type_search is None :
35
+ board_type_search = re .search (r"MP_ROM_PTR\(&(.*?)_obj" , line )
36
+ if board_type_search is None :
37
+ board_type_search = re .search (
38
+ r"MP_ROM_PTR\(&(.*?)\[0\].[display|epaper_display]" , line
39
+ )
40
+ if board_type_search is None :
41
+ records .append (["unmapped" , None , line ])
42
+ continue
43
+
44
+ board_type = board_type_search .group (1 )
45
+
46
+ extra_search = None
47
+ extra = None
48
+
49
+ if board_type == "pin" :
50
+ extra_search = re .search (r"&pin_(.*?)\)" , line )
51
+
52
+ elif board_type == "displays" :
53
+ extra_search = re .search (r"&displays\[0\].(.*?)\)" , line )
54
+
55
+ if extra_search :
56
+ extra = extra_search .group (1 )
57
+
58
+ records .append ([board_type , board_member , extra ])
59
+
60
+ return records
61
+
62
+
63
+ def create_board_stubs (board_id , records , mappings , board_filename ):
64
+ pins = []
65
+ members = []
66
+ unmapped = []
67
+
68
+ needs_busio = False
69
+ needs_displayio = False
70
+ needs_microcontroller = False
71
+
72
+ for board_type , board_member , extra in records :
73
+ if board_type == "pin" :
74
+ needs_microcontroller = True
75
+ comment = f" # { extra } "
76
+ pins .append (f"{ board_member } : microcontroller.Pin{ comment } " )
77
+
78
+ elif board_type == "board_i2c" :
79
+ needs_busio = True
80
+ import_name = "I2C"
81
+ class_name = f"busio.{ import_name } "
82
+ member_data = f"def { board_member } () -> { class_name } :\n "
83
+ member_data += f' """Returns the `{ class_name } ` object for the board\' s designated I2C bus(es).\n '
84
+ member_data += f" The object created is a singleton, and uses the default parameter values for `{ class_name } `.\n "
85
+ member_data += ' """\n '
86
+ members .append (member_data )
87
+
88
+ elif board_type == "board_spi" :
89
+ needs_busio = True
90
+ import_name = "SPI"
91
+ class_name = f"busio.{ import_name } "
92
+ member_data = f"def { board_member } () -> { class_name } :\n "
93
+ member_data += f' """Returns the `{ class_name } ` object for the board\' s designated SPI bus(es).\n '
94
+ member_data += f" The object created is a singleton, and uses the default parameter values for `{ class_name } `.\n "
95
+ member_data += ' """\n '
96
+ members .append (member_data )
97
+
98
+ elif board_type == "board_uart" :
99
+ needs_busio = True
100
+ import_name = "UART"
101
+ class_name = f"busio.{ import_name } "
102
+ member_data = f"def { board_member } () -> { class_name } :\n "
103
+ member_data += f' """Returns the `{ class_name } ` object for the board\' s designated UART bus(es).\n '
104
+ member_data += f" The object created is a singleton, and uses the default parameter values for `{ class_name } `.\n "
105
+ member_data += ' """\n '
106
+ members .append (member_data )
107
+
108
+ elif board_type == "displays" :
109
+ needs_displayio = True
110
+ if extra == "display" :
111
+ import_name = "Display"
112
+ elif extra == "epaper_display" :
113
+ import_name = "EPaperDisplay"
114
+ class_name = f"displayio.{ import_name } "
115
+ member_data = f'"""Returns the `{ class_name } ` object for the board\' s built in display.\n '
116
+ member_data += f"The object created is a singleton, and uses the default parameter values for `{ class_name } `.\n "
117
+ member_data += '"""\n '
118
+ member_data += f"{ board_member } : { class_name } \n "
119
+ members .append (member_data )
120
+
121
+ elif board_type == "unmapped" :
122
+ unmapped .append (extra )
123
+
124
+ libraries = mappings ["libraries" ]
125
+ included_modules = "Unknown"
126
+ frozen_libraries = "Unknown"
127
+ if "modules" in libraries :
128
+ included_modules = ", " .join (libraries ["modules" ])
129
+ if "frozen_libraries" in libraries :
130
+ frozen_libraries = ", " .join (libraries ["frozen_libraries" ])
131
+
132
+ with open (board_filename , "w" ) as boards_file :
133
+ boards_file .write (
134
+ "# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries\n "
135
+ )
136
+ boards_file .write ("#\n " )
137
+ boards_file .write ("# SPDX-License-Identifier: MIT\n " )
138
+ boards_file .write ('"""\n ' )
139
+ boards_file .write (f'Board stub for { mappings ["board_name" ]} \n ' )
140
+ boards_file .write (f' - port: { mappings ["port" ]} \n ' )
141
+ boards_file .write (f" - board_id: { board_id } \n " )
142
+ boards_file .write (f' - NVM size: { mappings ["nvm_size" ]} \n ' )
143
+ boards_file .write (f" - Included modules: { included_modules } \n " )
144
+ boards_file .write (f" - Frozen libraries: { frozen_libraries } \n " )
145
+ boards_file .write ('"""\n \n ' )
146
+ boards_file .write ("# Imports\n " )
147
+ if needs_busio :
148
+ boards_file .write ("import busio\n " )
149
+ if needs_displayio :
150
+ boards_file .write ("import displayio\n " )
151
+ if needs_microcontroller :
152
+ boards_file .write ("import microcontroller\n " )
153
+
154
+ boards_file .write ("\n \n " )
155
+ boards_file .write ("# Board Info:\n " )
156
+ boards_file .write ("board_id: str\n " )
157
+
158
+ boards_file .write ("\n \n " )
159
+ boards_file .write ("# Pins:\n " )
160
+ for pin in pins :
161
+ boards_file .write (f"{ pin } \n " )
162
+
163
+ boards_file .write ("\n \n " )
164
+ boards_file .write ("# Members:\n " )
165
+ for member in members :
166
+ boards_file .write (f"{ member } \n " )
167
+
168
+ boards_file .write ("\n " )
169
+ boards_file .write ("# Unmapped:\n " )
170
+ if not unmapped :
171
+ boards_file .write ("# none\n " )
172
+ for record in unmapped :
173
+ boards_file .write (f"# { record } " )
174
+
175
+
176
+ def process (board_mappings , export_dir ):
177
+ total_boards = 0
178
+ total_pins = 0
179
+ total_members = 0
180
+ total_unmapped = 0
181
+ skipped_boards = []
182
+ unmapped_boards = defaultdict (int )
183
+ unmapped_values = defaultdict (list )
184
+
185
+ for board_id , mappings in board_mappings .items ():
186
+ total_boards += 1
187
+
188
+ if "pins_filename" not in mappings :
189
+ skipped_boards .append (board_id )
190
+ continue
191
+
192
+ pin_filename = mappings ["pins_filename" ]
193
+
194
+ sub_dir = f"{ export_dir } /{ board_id } "
195
+ if not os .path .exists (sub_dir ):
196
+ os .makedirs (sub_dir )
197
+
198
+ try :
199
+ records = get_board_pins (pin_filename )
200
+ create_board_stubs (board_id , records , mappings , f"{ sub_dir } /__init__.pyi" )
201
+
202
+ for board_type , board_member , extra in records :
203
+ if board_type == "pin" :
204
+ total_pins += 1
205
+ elif board_type == "unmapped" :
206
+ unmapped_boards [board_id ] += 1
207
+ unmapped_values [extra ].append (board_id )
208
+ total_unmapped += 1
209
+ else :
210
+ total_members += 1
211
+
212
+ except Exception as e :
213
+ print (f" - { e } " )
214
+
215
+ unmapped_percent = total_unmapped / (total_pins + total_members + total_unmapped )
216
+
217
+ print ("\n Totals:" )
218
+ print (f" boards: { total_boards } " )
219
+ print (f" pins: { total_pins } " )
220
+ print (f" members: { total_members } " )
221
+ print (f" unmapped: { total_unmapped } ({ unmapped_percent :.5f} %)" )
222
+ print ("\n \n Skipped Boards" )
223
+ for board in skipped_boards :
224
+ print (f" { board } " )
225
+ print ("\n \n Boards with Unmapped Pins:" )
226
+ for board , total in unmapped_boards .items ():
227
+ print (f" { board } : { total } " )
228
+ print ("\n \n Unmapped Pins:" )
229
+ for unmapped , boards in unmapped_values .items ():
230
+ print (f" { unmapped .strip ()} " )
231
+ for board in boards :
232
+ print (f" - { board } " )
233
+
234
+
235
+ def build_stubs (circuitpython_dir , circuitpython_org_dir , export_dir , version = "8.2.9" ):
236
+ if circuitpython_dir [- 1 ] != "/" :
237
+ circuitpython_dir = circuitpython_dir + "/"
238
+
239
+ sys .path .append (circuitpython_dir )
240
+ from docs import shared_bindings_matrix
241
+
242
+ if not os .path .exists (export_dir ):
243
+ os .makedirs (export_dir )
244
+
245
+ libraries = {}
246
+ if circuitpython_org_dir is None :
247
+ libraries = shared_bindings_matrix .support_matrix_by_board (use_branded_name = False , withurl = False )
248
+ else :
249
+ with open (f"{ circuitpython_org_dir } /_data/files.json" ) as libraries_file :
250
+ libraries_list = json .load (libraries_file )
251
+ for library in libraries_list :
252
+ board = library ["id" ]
253
+ for version_data in library ["versions" ]:
254
+ if version_data ["version" ] == version :
255
+ libraries [board ] = version_data
256
+
257
+ aliases = {}
258
+ for board , renames in shared_bindings_matrix .ALIASES_BY_BOARD .items ():
259
+ for rename in renames :
260
+ aliases [rename ] = board
261
+
262
+ board_mappings = shared_bindings_matrix .get_board_mapping ()
263
+ for board , board_data in board_mappings .items ():
264
+ if board in aliases :
265
+ lookup = aliases [board ]
266
+ else :
267
+ lookup = board
268
+
269
+ port_path = f'{ circuitpython_dir } ports/{ board_data ["port" ]} /'
270
+ board_path = f"{ port_path } boards/{ lookup } /"
271
+ pins_path = f"{ board_path } pins.c"
272
+ if not os .path .isfile (pins_path ):
273
+ print (f"Could not find pin file for { lookup } " )
274
+ continue
275
+
276
+ board_mappings [board ]["pins_filename" ] = pins_path
277
+
278
+ nvm_size = "Unknown"
279
+ with open (f"{ port_path } /mpconfigport.h" ) as get_name :
280
+ port_contents = get_name .read ()
281
+ port_nvm_re = re .search (
282
+ r"(?<=#define CIRCUITPY_INTERNAL_NVM_SIZE)\s+(.+)" , port_contents
283
+ )
284
+ if port_nvm_re :
285
+ nvm_size = port_nvm_re .group (1 ).strip ()
286
+
287
+ board_name = board
288
+ with open (f"{ board_path } /mpconfigboard.h" ) as get_name :
289
+ board_contents = get_name .read ()
290
+ board_name_re = re .search (
291
+ r"(?<=MICROPY_HW_BOARD_NAME)\s+(.+)" , board_contents
292
+ )
293
+ if board_name_re :
294
+ board_name = board_name_re .group (1 ).strip ('"' )
295
+
296
+ port_nvm_re = re .search (
297
+ r"(?<=#define CIRCUITPY_INTERNAL_NVM_SIZE)\s+(.+)" , port_contents
298
+ )
299
+ if port_nvm_re :
300
+ nvm_size = port_nvm_re .group (1 ).strip ()
301
+
302
+ nvm_size_re = re .search (r"^[0-9\(\) *]*$" , nvm_size )
303
+ if nvm_size_re :
304
+ nvm_size = eval (nvm_size_re .group (0 ))
305
+
306
+ board_mappings [board ]["board_name" ] = board_name
307
+ board_mappings [board ]["nvm_size" ] = nvm_size
308
+ board_mappings [board ]["libraries" ] = libraries .get (board , None )
309
+
310
+ process (board_mappings , export_dir )
311
+
312
+ if __name__ == '__main__' :
313
+ build_stubs ("./" , None , "circuitpython-stubs/board_definitions/" )
0 commit comments