| 1313 | |
| 1314 | |
| 1315 | def create_board(board_name: str, no_project_options: bool = False) -> Board: |
| 1316 | board: Board | None = None |
| 1317 | if no_project_options: |
| 1318 | board = Board(board_name=board_name, add_board_to_all=False) |
| 1319 | elif board_name in _BOARD_MAP: |
| 1320 | # Direct match on board_name (primary lookup) |
| 1321 | board = _BOARD_MAP[board_name] |
| 1322 | else: |
| 1323 | # Try reverse lookup by real_board_name (alias support) |
| 1324 | board = None |
| 1325 | for candidate in ALL: |
| 1326 | if candidate.real_board_name == board_name: |
| 1327 | board = candidate |
| 1328 | break |
| 1329 | |
| 1330 | # Case-insensitive fallback: some boards registered in `_BOARD_MAP` |
| 1331 | # use camelCase (`ATtiny1604`, `ATtiny1616`) while CI workflows and |
| 1332 | # docs frequently spell them lowercase. Resolving by lowercase |
| 1333 | # before falling through to a generic Board avoids |
| 1334 | # `UndefinedEnvPlatformError` at PlatformIO env-resolution time |
| 1335 | # (FastLED #2779). |
| 1336 | if board is None: |
| 1337 | target = board_name.lower() |
| 1338 | for candidate in ALL: |
| 1339 | if candidate.board_name.lower() == target: |
| 1340 | board = candidate |
| 1341 | break |
| 1342 | |
| 1343 | if board is None: |
| 1344 | # No match found - create generic board without special overrides |
| 1345 | # Assume platformio will know what to do with it |
| 1346 | board = Board(board_name=board_name, add_board_to_all=False) |
| 1347 | |
| 1348 | assert board is not None |
| 1349 | return board.clone() |