mirror of
https://github.com/SlimeVR/SlimeVR-Tracker-ESP.git
synced 2026-04-06 02:01:57 +02:00
184 lines
6.8 KiB
Python
184 lines
6.8 KiB
Python
import json
|
|
import re
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Union, Optional, Dict, Any, List
|
|
|
|
Import("env")
|
|
|
|
try:
|
|
import jsonschema
|
|
except:
|
|
env.Execute(
|
|
env.VerboseAction(
|
|
'$PYTHONEXE -m pip install "jsonschema==4.22.0"',
|
|
"Installing jsonschema for validation",
|
|
)
|
|
)
|
|
|
|
from jsonschema import Draft202012Validator, exceptions as jsonschema_exceptions
|
|
|
|
def _load_json(maybe_path_or_dict: Union[str, Path, dict]) -> dict:
|
|
"""Load JSON file or accept dict directly."""
|
|
if isinstance(maybe_path_or_dict, dict):
|
|
return maybe_path_or_dict
|
|
p = Path(maybe_path_or_dict)
|
|
if not p.exists():
|
|
raise FileNotFoundError(f"File not found: {p}")
|
|
try:
|
|
return json.loads(p.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError as e:
|
|
raise ValueError(f"Invalid JSON file {p}: {e}")
|
|
|
|
|
|
def _format_raw_value(value: Any) -> str:
|
|
"""Format booleans for c/cpp, otherwise str(value)."""
|
|
if isinstance(value, bool):
|
|
return "true" if value else "false"
|
|
return str(value)
|
|
|
|
def format_value(val: Any, typ: str) -> str:
|
|
if typ == "pin":
|
|
if isinstance(val, str) and re.search(r"[AD]", val):
|
|
return f'{val}'
|
|
else:
|
|
return _format_raw_value(val)
|
|
elif typ == "string":
|
|
return f'{val}'
|
|
elif typ in ("raw", "number"):
|
|
return _format_raw_value(val)
|
|
else:
|
|
raise ValueError(f"Value type is not supported")
|
|
|
|
|
|
def _build_board_flags(defaults: dict, board_name: str) -> List[str]:
|
|
"""Construct list of -D flags for one board."""
|
|
if "defaults" not in defaults:
|
|
raise ValueError("Missing top-level 'defaults' key in defaults JSON.")
|
|
if board_name not in defaults["defaults"]:
|
|
raise ValueError(f"Invalid board selected - {board_name}")
|
|
|
|
board_defaults = defaults["defaults"][board_name]
|
|
values = board_defaults.get("values", {})
|
|
|
|
args: Dict[str, Dict[str, Any]] = {}
|
|
|
|
def add(key: str, value: Any, value_type: str):
|
|
if value is not None:
|
|
args[key] = {"value": value, "type": value_type}
|
|
|
|
add('BOARD', board_name, 'raw')
|
|
add('LED_PIN', values.get('LED').get('LED_PIN'), 'pin')
|
|
add('LED_INVERTED', values.get('LED').get('LED_INVERTED'), 'raw')
|
|
|
|
sensors = values.get('SENSORS')
|
|
if sensors:
|
|
sensor_list = []
|
|
|
|
add('PIN_IMU_SDA', 255, 'pin') # FIXME fix the I2C Scanner so it use the sensor list and not be called when no I2C sensor
|
|
add('PIN_IMU_SCL', 255, 'pin')
|
|
add('PIN_IMU_INT_2', 255, 'pin') # FIXME: fix the CONFIG serial command so it use the sensor list
|
|
|
|
for index, sensor in enumerate(sensors):
|
|
if sensor.get('protocol') == 'I2C':
|
|
params = [
|
|
format_value(sensor.get('imu'), 'raw'),
|
|
format_value(sensor.get('address', 'PRIMARY_IMU_ADDRESS_ONE'), 'number'),
|
|
format_value(sensor.get('rotation'), 'raw'),
|
|
f'DIRECT_WIRE({format_value(sensor.get('scl'), 'pin')}, {format_value(sensor.get('sda'), 'pin')})',
|
|
'false' if index == 0 else 'true',
|
|
f'DIRECT_PIN({format_value(sensor.get('int', 255), 'pin')})',
|
|
'0'
|
|
]
|
|
sensor_list.append(f'SENSOR_DESC_ENTRY({','.join(params)})')
|
|
add('PIN_IMU_SDA', sensor.get('sda'), 'pin')
|
|
add('PIN_IMU_SCL', sensor.get('scl'), 'pin')
|
|
|
|
if sensor.get('protocol') == 'SPI':
|
|
params = [
|
|
format_value(sensor.get('imu'), 'raw'),
|
|
f'DIRECT_PIN({format_value(sensor.get('cs'), 'pin')})',
|
|
format_value(sensor.get('rotation'), 'raw'),
|
|
"DIRECT_SPI(24'000'000, MSBFIRST, SPI_MODE3)",
|
|
'false' if index == 0 else 'true',
|
|
f'DIRECT_PIN({format_value(sensor.get('int', 255), 'pin')})',
|
|
'0'
|
|
]
|
|
sensor_list.append(f'SENSOR_DESC_ENTRY({','.join(params)})')
|
|
|
|
if index == 0: # FIXME: fix the CONFIG serial command so it use the sensor list
|
|
add('PIN_IMU_INT', sensor.get('int'), 'pin')
|
|
elif index == 1:
|
|
add('PIN_IMU_INT_2', sensor.get('int'), 'pin')
|
|
add('SENSOR_DESC_LIST', f"'{' '.join(sensor_list)}'", 'raw')
|
|
|
|
|
|
battery = values.get('BATTERY')
|
|
if battery:
|
|
add('BATTERY_MONITOR', battery.get('type'), 'raw')
|
|
add('PIN_BATTERY_LEVEL', battery.get('pin', 255), 'pin')
|
|
add('BATTERY_SHIELD_RESISTANCE', battery.get('shieldR', 180), 'number')
|
|
add('BATTERY_SHIELD_R1', battery.get('r1', 100), 'number')
|
|
add('BATTERY_SHIELD_R2', battery.get('r2', 220), 'number')
|
|
|
|
parts: List[str] = []
|
|
for key, meta in args.items():
|
|
val = meta["value"]
|
|
typ = meta["type"]
|
|
parts.append(f"-D{key}={format_value(val, typ)}")
|
|
|
|
return parts
|
|
|
|
|
|
def build_boards(
|
|
schema_obj,
|
|
defaults_obj,
|
|
board_name: Optional[str] = None,
|
|
) -> Dict[str, List[str]]:
|
|
"""
|
|
Validate defaults.json against board-defaults.schema.json using jsonschema,
|
|
and return { board_name: [list of -D flags] }.
|
|
"""
|
|
validator = Draft202012Validator(schema_obj)
|
|
errors = sorted(validator.iter_errors(defaults_obj), key=lambda e: e.path)
|
|
|
|
if errors:
|
|
print("✖ JSON Schema validation failed:")
|
|
for err in errors:
|
|
path = "/".join(map(str, err.path)) or "(root)"
|
|
print(f" • Path: {path}")
|
|
print(f" Error: {err.message}")
|
|
if err.context:
|
|
for ctx in err.context:
|
|
print(f" ↳ {ctx.message}")
|
|
raise ValueError(f"{len(errors)} schema validation errors found.")
|
|
|
|
out: Dict[str, List[str]] = {}
|
|
if board_name:
|
|
out[board_name] = _build_board_flags(defaults_obj, board_name)
|
|
else:
|
|
for name in defaults_obj.get("defaults", {}).keys():
|
|
out[name] = _build_board_flags(defaults_obj, name)
|
|
|
|
return out
|
|
|
|
schema_obj = _load_json("./board-defaults.schema.json")
|
|
defaults_obj = _load_json("./board-defaults.json")
|
|
slime_board = env.GetProjectOption("custom_slime_board", None)
|
|
if slime_board:
|
|
if 'SLIMEVR_OVERRIDE_DEFAULTS' in os.environ and slime_board in defaults_obj['defaults']:
|
|
print(">>> OVERIDING BOARD DEFAULTS ", os.environ['SLIMEVR_OVERRIDE_DEFAULTS'])
|
|
defaults_obj['defaults'][slime_board]['values'] = json.loads(os.environ['SLIMEVR_OVERRIDE_DEFAULTS'])
|
|
|
|
output_flags = build_boards(
|
|
schema_obj,
|
|
defaults_obj,
|
|
slime_board,
|
|
)
|
|
output_flags = output_flags.get(slime_board, []) if isinstance(output_flags, dict) else []
|
|
|
|
print(f">>> Appending build flags:\n {'\n '.join(output_flags)}")
|
|
env.Append(BUILD_FLAGS=output_flags)
|
|
else:
|
|
print(">>> custom_slime_board not set - skipping")
|