Files
libretiny/docs/scripts/write_boards.py
2023-06-22 18:30:14 +02:00

437 lines
13 KiB
Python

# Copyright (c) Kuba Szczodrzyński 2022-05-31.
import re
from os.path import dirname, isfile, join
import colorama
from colorama import Fore, Style
from ltchiptool import Board, Family
from ltchiptool.util.fileio import readjson, readtext
from ltchiptool.util.misc import sizeof
from markdown import Markdown
OUTPUT = join(dirname(__file__), "..", "status")
def load_chip_type_h() -> str:
code = readtext(
join(
dirname(__file__),
"..",
"..",
"cores",
"common",
"base",
"lt_types.h",
)
)
code = re.sub(r"//.+", "", code)
code = re.sub(r"/\*.+\*/", "", code)
return code
def parse_enum(code: str, name: str) -> dict[str, str]:
code = code.replace("\t", " ")
code = code.partition(f"{name};")[0]
code = code.rpartition("{")[2]
code = code.strip().strip("{}").strip()
code = [line.strip().strip(",").strip() for line in code.split("\n")]
code = filter(None, code)
code = [line.partition("=") for line in code]
code = {key.strip(): value.strip() for key, _, value in code}
return code
def get_families_json() -> dict[str, int]:
return {
family.short_name: family.id for family in Family.get_all() if family.is_chip
}
def get_mcus_boards(boards: list[Board]) -> dict[str, str]:
out = {}
for board in boards:
mcu_name: str = board["build.mcu"].upper()
family_name: str = board.family.short_name
if mcu_name in out and out[mcu_name] != family_name:
print(
Fore.RED
+ f"ERROR: MCU '{mcu_name}' of board '{board.name}' belongs to multiple families: '{out[mcu_name]}' and '{family_name}'"
+ Style.RESET_ALL
)
continue
out[mcu_name] = family_name
return out
def get_families_enum(code: str) -> dict[str, int]:
return {
family[2:]: int(family_id, 16)
for family, family_id in parse_enum(code, "lt_cpu_family_t").items()
}
def get_mcus_enum(code: str) -> tuple[dict[str, str], dict[str, str]]:
mcus = {}
aliases = {}
enum = parse_enum(code, "lt_cpu_model_t")
for mcu, mcu_id in enum.items():
while mcu_id in enum:
aliases[mcu] = mcu_id
mcu_id = enum[mcu_id]
mcus[mcu] = mcu_id.split("(")[1].split(",")[0][2:]
return mcus, aliases
def get_readme_family_link(family: Family) -> str | None:
for f in family.inheritance[::-1]:
path = f"../platform/{f.name}/README.md"
if isfile(join(dirname(__file__), path)):
return path
return None
def get_readme_board_link(board: Board) -> str:
return f"../../boards/{board.name}/README.md"
def board_json_sort(tpl):
return tpl[1]["mcu"], tpl[0]
def board_obj_sort(board: Board):
generic = board.is_generic
vendor = board.vendor
if vendor == "N/A":
vendor = "\xff"
generic = False
return (
not generic, # reverse
vendor,
board["build.mcu"],
board["mcu"],
board.name,
)
def get_board_symbol(board: Board) -> str:
return board.symbol or board.generic_name or board.name.upper()
def write_chips(mcus: dict[str, Family], aliases: dict[str, str]):
md = Markdown(OUTPUT, "supported_chips")
chips = []
clones = []
for mcu, family in sorted(mcus.items()):
docs = get_readme_family_link(family)
target = chips
if mcu in aliases:
mcu = f"{mcu} ({aliases[mcu]})"
target = clones
if docs:
target.append(md.get_link(mcu, docs))
else:
target.append(mcu)
md.add_list(*chips, *clones)
md.write()
def write_boards(boards: list[Board]):
md = Markdown(OUTPUT, "supported_boards")
header = [
"Name",
"MCU",
"Flash",
"RAM",
"Pins*",
"Wi-Fi",
"BLE",
"ZigBee",
"Family name",
]
rows = []
vendor_prev = ""
for board in boards:
# add board vendor as a row
vendor = board["vendor"]
if vendor_prev != vendor:
rows.append([f"**{vendor}**"])
vendor_prev = vendor
# count total pin count & IO count
pins = "-"
pinout: dict[str, dict] = board["pcb.pinout"]
if pinout:
pinout = [pin for name, pin in pinout.items() if name.isnumeric()]
pins_total = len(pinout)
pins_io = sum(1 for pin in pinout if "ARD" in pin)
pins = f"{pins_total} ({pins_io} I/O)"
# format row values
symbol = get_board_symbol(board)
docs = get_readme_board_link(board)
board_url = f"[{symbol}]({docs})"
row = [
board_url,
board["build.mcu"].upper(),
sizeof(board["upload.flash_size"]),
sizeof(board["upload.maximum_ram_size"]),
pins,
"✔️" if "wifi" in board["connectivity"] else "",
"✔️" if "ble" in board["connectivity"] else "",
"✔️" if "zigbee" in board["connectivity"] else "",
f"`{board.family.name}`",
]
rows.append(row)
md.add_table(header, *rows)
md.write()
def write_unsupported_boards(
series: dict[str, dict[str, dict]],
name: str,
supported: list[str],
mcus: dict[str, Family],
generics: dict[str, list[Board]],
):
md = Markdown(OUTPUT, name)
header = [
"Name",
"MCU",
"Flash",
"RAM",
"Pins",
"Wi-Fi",
"BLE",
"ZigBee",
]
rows = []
for series_name in sorted(series):
series_rows = []
series_rows.append([f"**{series_name.upper()} Series**"])
boards = series[series_name]
for board_name, board in sorted(boards.items(), key=board_json_sort):
if board_name in supported:
continue
board_text = board_name.upper()
mcu_text = mcu = board["mcu"].upper()
if mcu in generics:
generic = generics[mcu][0]
board_text += f' :material-information-outline:{{ title="You can use {generic.name} board instead" }}'
if mcu in mcus:
docs = get_readme_family_link(mcus[mcu])
if docs:
mcu_text = md.get_link(mcu, docs)
row = [
board_text,
mcu_text,
sizeof(board["flash"]) if board["flash"] else "?",
sizeof(board["ram"]) if board["ram"] else "?",
str(board["pins_total"]),
"✔️" if "wifi" in board["connectivity"] else "",
"✔️" if "ble" in board["connectivity"] else "",
"✔️" if "zigbee" in board["connectivity"] else "",
]
series_rows.append(row)
if series_rows:
rows += series_rows
md.add_table(header, *rows)
md.write()
def write_families(supported: list[Family]):
md = Markdown(OUTPUT, "supported_families")
header = [
"Title",
"Name",
"Code",
"Short name & ID",
"Supported?",
"Source SDK",
]
rows = []
for family in Family.get_all():
# TODO update the table to support parent-child relationship
if not family.is_chip:
continue
docs = get_readme_family_link(family)
row = [
# Title
"[{}]({})".format(
family.description,
docs,
)
if docs
else family.description,
# Name
family.is_supported and f"`{family.name}`" or "`-`",
# Code
family.is_supported and f"`{family.code}`" or "`-`",
# Short name & ID
"`{}` (0x{:X})".format(
family.short_name,
family.id,
),
# Arduino Core
"✔️"
if family in supported and family.is_supported and family.has_arduino_core
else "",
# Source SDK
"[`{}`]({})".format(
family.target_package,
f"https://github.com/libretiny-eu/{family.target_package}",
)
if family.target_package
else "-",
]
rows.append(row)
md.add_table(header, *rows)
md.write()
def write_boards_list(boards: list[Board]):
md = Markdown(join(dirname(__file__), ".."), join("..", "boards", "SUMMARY"))
items = []
for board in boards:
symbol = get_board_symbol(board)
if board.is_generic:
symbol = board["name"]
items.append(f"[{symbol}](../boards/{board.name}/README.md)")
md.add_list(*items)
md.write()
if __name__ == "__main__":
colorama.init()
boards = map(Board, Board.get_list())
boards = sorted(boards, key=board_obj_sort)
code = load_chip_type_h()
errors = False
for board in boards:
if board.name != board["source"]:
print(
Fore.RED
+ f"ERROR: Invalid build.variant of '{board['source']}': '{board.name}'"
+ Style.RESET_ALL
)
errors = True
families_json = get_families_json()
families_enum = get_families_enum(code)
families_json_keys = set(families_json.keys())
families_enum_keys = set(families_enum.keys())
mcus_boards = get_mcus_boards(boards)
mcus_enum, mcu_aliases = get_mcus_enum(code)
mcus_boards_keys = set(mcus_boards.keys())
mcus_enum_keys = set(mcus_enum.keys())
mcus_missing_in_boards = mcus_enum_keys - mcus_boards_keys
mcus_missing_in_enum = mcus_boards_keys - mcus_enum_keys
# check if all families are defined in lt_types.h and families.json
if families_json_keys != families_enum_keys:
print(Fore.RED + f"ERROR: Inconsistent lt_types.h vs families.json:")
print(
"- Missing in JSON: " + ", ".join(families_enum_keys - families_json_keys)
)
print(
"- Missing in enum: " + ", ".join(families_json_keys - families_enum_keys)
)
print(Style.RESET_ALL, end="")
errors = True
# verify that family IDs match
for family in families_json_keys.union(families_enum_keys):
if (
family in families_json
and family in families_enum
and families_json[family] != families_enum[family]
):
print(
Fore.RED
+ f"ERROR: Family ID mismatch for '{family}': 0x{families_json[family]:08X} vs 0x{families_enum[family]:08X}"
+ Style.RESET_ALL
)
errors = True
# warn if any enum MCUs are unused in boards
if mcus_missing_in_boards:
print(
Fore.YELLOW
+ f"NOTICE: Unused MCUs: "
+ ", ".join(mcus_missing_in_boards)
+ Style.RESET_ALL
)
# fail if any board MCUs are undefined in enum
if mcus_missing_in_enum:
print(
Fore.RED
+ f"ERROR: Undefined MCUs in lt_types.h: "
+ ", ".join(mcus_missing_in_enum)
+ Style.RESET_ALL
)
errors = True
# check if all MCUs belong to the correct family
for mcu in mcus_boards_keys.union(mcus_enum_keys):
if (
mcu in mcus_boards
and mcu in mcus_enum
and mcus_boards[mcu] != mcus_enum[mcu]
):
print(
Fore.RED
+ f"ERROR: MCU family mismatch for '{mcu}': '{mcus_boards[mcu]}' vs '{mcus_enum[mcu]}'"
+ Style.RESET_ALL
)
errors = True
if errors:
exit(1)
# check all supported families by MCU presence in enum
families_supported = sorted(families_enum_keys.intersection(mcus_enum.values()))
families_supported = [Family.get(f) for f in families_supported]
# filter out MCUs of unsupported families
mcus_boards.update(mcus_enum)
mcus_all = {}
for mcu, family in mcus_boards.items():
family = Family.get(family)
if family not in families_supported:
continue
mcus_all[mcu] = family
# remove boards of unsupported families
boards = [board for board in boards if board.family in families_supported]
# find generic variants of boards
generics: dict[str, list[Board]] = {}
for board in boards:
if not board.is_generic:
continue
mcu: str = board["build.mcu"].upper()
if mcu not in generics:
generics[mcu] = []
generics[mcu].append(board)
write_chips(mcus_all, mcu_aliases)
write_boards(boards)
write_boards_list(boards)
write_families(families_supported)
board_lists = [
"boards_tuya_all",
]
for name in board_lists:
file = join(dirname(__file__), "..", f"{name}.json")
data = readjson(file)
write_unsupported_boards(
series=data,
name=f"unsupported_{name}",
supported=[board.name for board in boards],
mcus=mcus_all,
generics=generics,
)