Files
AI_Printer/tools/esptool-factory/factory_gui/config.py

298 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from pathlib import Path
from typing import Optional
from factory_private_config import get_optional, get_required, load_private_config
from .models import DeviceCheckConfig, FlashPrivateConfig
_ALLOWED_FLASH_MODE = {"qio", "qout", "dio", "dout", "keep"}
_ALLOWED_FLASH_FREQ = {
"keep",
"80m",
"60m",
"48m",
"40m",
"30m",
"26m",
"24m",
"20m",
"16m",
"15m",
"12m",
}
_ALLOWED_FLASH_SIZE = {
"keep",
"detect",
"256kb",
"512kb",
"1mb",
"2mb",
"2mb-c1",
"4mb",
"4mb-c1",
"8mb",
"16mb",
"32mb",
"64mb",
"128mb",
}
_ALLOWED_FLASH_BEFORE = {"default-reset", "usb-reset", "no-reset", "no-reset-no-sync"}
_ALLOWED_FLASH_AFTER = {
"default-reset",
"hard-reset",
"soft-reset",
"watchdog-reset",
"no-reset",
"no-reset-stub",
"noreset",
"none",
"no",
}
def _coerce_str(value: object, label: str) -> str:
if isinstance(value, (int, float)):
return str(value)
if isinstance(value, str) and value.strip():
return value.strip()
raise ValueError(f"私有配置无效: {label}")
def _optional_str(value: object) -> Optional[str]:
if isinstance(value, str):
text = value.strip()
return text if text else None
return None
def _optional_bool_or_none(value: object) -> Optional[bool]:
if value is None:
return None
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(int(value))
if isinstance(value, str):
text = value.strip().lower()
if text in {"1", "true", "yes", "y", "on"}:
return True
if text in {"0", "false", "no", "n", "off"}:
return False
return None
def _coerce_positive_float(value: Optional[float], fallback: float) -> float:
if value is None:
return fallback
try:
parsed = float(value)
except (TypeError, ValueError):
return fallback
return max(1.0, parsed)
def _coerce_positive_int(value: Optional[int], fallback: int) -> int:
if value is None:
return fallback
try:
parsed = int(value)
except (TypeError, ValueError):
return fallback
return max(1, parsed)
def _ui_text(ui_cfg: dict, key: str, fallback: str) -> str:
value = ui_cfg.get(key)
if isinstance(value, str) and value.strip():
return value.strip()
return fallback
def _coerce_offset_int(value: str, label: str) -> int:
try:
return int(value, 0)
except ValueError as exc:
raise ValueError(f"私有配置无效: {label}") from exc
def _validate_allowed(
value: str,
label: str,
allowed: set[str],
*,
normalize_dash: bool = False,
) -> str:
normalized = value.strip().lower()
if normalize_dash:
normalized = normalized.replace("_", "-")
if normalized not in allowed:
allowed_text = ", ".join(sorted(allowed))
raise ValueError(f"私有配置无效: {label}={value}(允许: {allowed_text}")
return normalized
def _validate_app_layout_binding(
layout: list[tuple[str, str]],
app_bin_name: str,
app_partition_offset: str,
) -> None:
app_offsets = [offset for offset, name in layout if name == app_bin_name]
if not app_offsets:
raise ValueError("私有配置无效: flash.app_bin_name 必须出现在 flash.layout")
app_layout_offsets = {_coerce_offset_int(offset, "flash.layout.offset") for offset in app_offsets}
if len(app_layout_offsets) != 1:
raise ValueError("私有配置无效: flash.layout 中 app_bin_name 对应多个偏移地址")
app_offset = _coerce_offset_int(app_partition_offset, "flash.app_partition_offset")
if app_offset not in app_layout_offsets:
layout_offset = app_offsets[0]
raise ValueError(
"私有配置无效: flash.app_partition_offset 与 flash.layout 中 app_bin_name 的 offset 不一致"
f"app_partition_offset={app_partition_offset}, layout_offset={layout_offset}"
)
def _load_flash_private_config(
base_dir: Path, raw: Optional[dict] = None
) -> FlashPrivateConfig:
if raw is None:
raw = load_private_config(base_dir)
flash_cfg = get_required(raw, ("flash",), (dict,))
chip = _coerce_str(get_required(flash_cfg, ("chip",), (str, int)), "flash.chip")
baud = _coerce_str(get_required(flash_cfg, ("baud",), (str, int)), "flash.baud")
bin_dir_raw = _coerce_str(get_required(flash_cfg, ("bin_dir",), (str,)), "flash.bin_dir")
bin_dir = Path(bin_dir_raw)
bin_enc_raw = get_optional(flash_cfg, ("bin_encryption",), None)
if bin_enc_raw is not None:
raise ValueError("私有配置无效: flash.bin_encryption 已移除。")
layout_raw = get_required(flash_cfg, ("layout",), (list,))
layout: list[tuple[str, str]] = []
for entry in layout_raw:
offset: Optional[str] = None
name: Optional[str] = None
if isinstance(entry, (list, tuple)) and len(entry) == 2:
offset = _coerce_str(entry[0], "flash.layout.offset")
name = _coerce_str(entry[1], "flash.layout.name")
elif isinstance(entry, dict):
offset = _coerce_str(entry.get("offset"), "flash.layout.offset")
name = _coerce_str(entry.get("name"), "flash.layout.name")
else:
raise ValueError("私有配置无效: flash.layout")
if name.endswith(".enc"):
raise ValueError("私有配置无效: flash.layout 不应包含 .enc 后缀。")
layout.append((offset, name))
if not layout:
raise ValueError("私有配置无效: flash.layout 不能为空")
app_bin_name = _coerce_str(
get_required(flash_cfg, ("app_bin_name",), (str,)), "flash.app_bin_name"
)
if app_bin_name.endswith(".enc"):
raise ValueError("私有配置无效: flash.app_bin_name 不应包含 .enc 后缀。")
app_partition_offset = _coerce_str(
get_required(flash_cfg, ("app_partition_offset",), (str, int)),
"flash.app_partition_offset",
)
_validate_app_layout_binding(layout, app_bin_name, app_partition_offset)
flash_args_raw = get_required(flash_cfg, ("flash_args",), (dict,))
use_stub_value = _optional_bool_or_none(get_optional(flash_args_raw, ("use_stub",), None))
flash_args = {
"flash_mode": _validate_allowed(
_coerce_str(
get_required(flash_args_raw, ("flash_mode",), (str, int)),
"flash.flash_args.flash_mode",
),
"flash.flash_args.flash_mode",
_ALLOWED_FLASH_MODE,
),
"flash_freq": _validate_allowed(
_coerce_str(
get_required(flash_args_raw, ("flash_freq",), (str, int)),
"flash.flash_args.flash_freq",
),
"flash.flash_args.flash_freq",
_ALLOWED_FLASH_FREQ,
),
"flash_size": _validate_allowed(
_coerce_str(
get_required(flash_args_raw, ("flash_size",), (str, int)),
"flash.flash_args.flash_size",
),
"flash.flash_args.flash_size",
_ALLOWED_FLASH_SIZE,
),
"before": _validate_allowed(
_coerce_str(
get_required(flash_args_raw, ("before",), (str, int)),
"flash.flash_args.before",
),
"flash.flash_args.before",
_ALLOWED_FLASH_BEFORE,
normalize_dash=True,
),
"after": _validate_allowed(
_coerce_str(
get_required(flash_args_raw, ("after",), (str, int)),
"flash.flash_args.after",
),
"flash.flash_args.after",
_ALLOWED_FLASH_AFTER,
normalize_dash=True,
),
"use_stub": True if use_stub_value is None else use_stub_value,
}
check_cfg = get_required(raw, ("device_check",), (dict,))
crypt_cnt_fields_raw = get_required(check_cfg, ("crypt_cnt_fields",), (list,))
crypt_cnt_fields = tuple(_coerce_str(item, "device_check.crypt_cnt_fields") for item in crypt_cnt_fields_raw)
crypt_cnt_expect = _optional_str(get_optional(check_cfg, ("crypt_cnt_expect",), None))
if crypt_cnt_expect:
expect_norm = crypt_cnt_expect.strip().lower()
if expect_norm not in {"odd", "even", "zero", "any"}:
raise ValueError("私有配置无效: device_check.crypt_cnt_expect")
crypt_cnt_expect = expect_norm
secure_boot_expected = _optional_bool_or_none(
get_optional(check_cfg, ("secure_boot_expected",), None)
)
secure_boot_field = _coerce_str(
get_required(check_cfg, ("secure_boot_field",), (str,)), "device_check.secure_boot_field"
)
flash_key_purpose_match = _optional_str(
get_optional(check_cfg, ("flash_key_purpose_match",), None)
)
secure_boot_key_purpose_match = _optional_str(
get_optional(check_cfg, ("secure_boot_key_purpose_match",), None)
)
key_read_disable_field = _coerce_str(
get_required(check_cfg, ("key_read_disable_field",), (str,)),
"device_check.key_read_disable_field",
)
ui_cfg = get_optional(raw, ("ui",), {})
if not isinstance(ui_cfg, dict):
ui_cfg = {}
return FlashPrivateConfig(
chip=chip,
baud=baud,
bin_dir=bin_dir,
layout=layout,
app_bin_name=app_bin_name,
app_partition_offset=app_partition_offset,
flash_args=flash_args,
device_check=DeviceCheckConfig(
crypt_cnt_fields=crypt_cnt_fields,
secure_boot_field=secure_boot_field,
secure_boot_expected=secure_boot_expected,
crypt_cnt_expect=crypt_cnt_expect,
flash_key_purpose_match=flash_key_purpose_match,
secure_boot_key_purpose_match=secure_boot_key_purpose_match,
key_read_disable_field=key_read_disable_field,
),
ui=ui_cfg,
)