437 lines
13 KiB
Python
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,
|
|
)
|