fix(hw_defs): improve template error reporting in code generator (#7190)

This commit is contained in:
Raphael Coeffic
2026-03-21 20:35:00 +10:00
committed by GitHub
parent 4c4cb2c31b
commit b87932cbc8
4 changed files with 102 additions and 60 deletions
+65 -40
View File
@@ -1,10 +1,13 @@
import json
import os
import sys
import jinja2
import pydantic
import legacy_names
import json_index
from models import KeyEnum
from models import HardwareDefinition, KeyEnum
MAIN_CONTROL_LUT = {
# 2 Gimbal radios
@@ -17,61 +20,83 @@ MAIN_CONTROL_LUT = {
"TH": {"str": "TH", "local": "STR_SURFACE_NAMES1"},
}
# def eprint(*args, **kwargs):
# from sys import stderr
# print(*args, file=stderr, **kwargs)
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def is_ext_input(input):
if input.get("type") != "FLEX":
if str(getattr(input, "type", "")) != "FLEX":
return False
name = input.get("name")
if name and name.startswith("EXT"):
return True
return False
name = str(getattr(input, "name", ""))
return name.startswith("EXT")
def generate_from_template(json_filename, template_filename, target):
with open(json_filename) as json_file:
with open(template_filename) as template_file:
root_obj = json.load(json_file)
raw_data = json_file.read()
adc_inputs = root_obj.get("adc_inputs")
adc_index = json_index.build_adc_index(adc_inputs)
adc_gpios = json_index.build_adc_gpio_port_index(adc_inputs)
# Validate using Pydantic model
try:
hw_def = HardwareDefinition.from_json(raw_data)
except pydantic.ValidationError as e:
eprint(f"ERROR validating '{json_filename}' (target={target}):")
for err in e.errors():
loc = " -> ".join(str(l) for l in err["loc"])
eprint(f" {loc}: {err['msg']}")
sys.exit(1)
switches = root_obj.get("switches")
switch_gpios = json_index.build_switch_gpio_port_index(switches)
# Load remaining fields not yet in the Pydantic model
root_obj = json.loads(raw_data)
keys = root_obj.get("keys")
key_gpios = json_index.build_key_gpio_port_index(keys)
adc_index = json_index.build_adc_index(hw_def.adc_inputs)
adc_gpios = json_index.build_adc_gpio_port_index(hw_def.adc_inputs)
switch_gpios = json_index.build_switch_gpio_port_index(hw_def.switches)
key_gpios = json_index.build_key_gpio_port_index(hw_def.keys)
trims = root_obj.get("trims")
trim_gpios = json_index.build_trim_gpio_port_index(trims)
trims = root_obj.get("trims")
trim_gpios = json_index.build_trim_gpio_port_index(trims)
legacy_inputs = legacy_names.inputs_by_target(target)
legacy_inputs = legacy_names.inputs_by_target(target)
env = jinja2.Environment(lstrip_blocks=True, trim_blocks=True)
template_dir = os.path.dirname(os.path.abspath(template_filename))
template_name = os.path.basename(template_filename)
env.tests["ext_input"] = is_ext_input
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_dir),
lstrip_blocks=True,
trim_blocks=True,
)
template_str = template_file.read()
template = env.from_string(template_str)
env.tests["ext_input"] = is_ext_input
key_index = list([str(i) for i in KeyEnum])
template = env.get_template(template_name)
print(
template.render(
root_obj,
adc_index=adc_index,
adc_gpios=adc_gpios,
switch_gpios=switch_gpios,
key_gpios=key_gpios,
trim_gpios=trim_gpios,
legacy_inputs=legacy_inputs,
main_labels=MAIN_CONTROL_LUT,
key_index=key_index,
)
)
key_index = list([str(i) for i in KeyEnum])
context = dict(
adc_inputs=hw_def.adc_inputs,
switches=hw_def.switches,
keys=hw_def.keys,
trims=trims,
adc_index=adc_index,
adc_gpios=adc_gpios,
switch_gpios=switch_gpios,
key_gpios=key_gpios,
trim_gpios=trim_gpios,
legacy_inputs=legacy_inputs,
main_labels=MAIN_CONTROL_LUT,
key_index=key_index,
)
try:
print(template.render(context))
except jinja2.UndefinedError as e:
eprint(f"ERROR rendering template '{template_name}'"
f" with json='{json_filename}', target='{target}':")
eprint(f" {e}")
none_keys = [k for k, v in context.items() if v is None]
if none_keys:
eprint(f" None-valued context keys: {none_keys}")
sys.exit(1)
+16 -17
View File
@@ -1,12 +1,11 @@
#
# These methods are used to build helper indexes on JSON structures
# These methods are used to build helper indexes on data structures
#
def build_adc_index(adc_inputs):
i = 0
index = {}
for adc_input in adc_inputs["inputs"]:
name = adc_input["name"]
index[name] = i
for adc_input in adc_inputs.inputs:
index[str(adc_input.name)] = i
i = i + 1
return index
@@ -22,11 +21,11 @@ def append_to_index(d, key, val):
def build_adc_gpio_port_index(adc_inputs):
i = 0
gpios = {}
for adc_input in adc_inputs["inputs"]:
if adc_input["adc"] == "SPI":
for adc_input in adc_inputs.inputs:
if str(adc_input.adc) == "SPI":
continue
gpio = adc_input["gpio"]
gpio = getattr(adc_input, "gpio", None)
if gpio is None:
i = i + 1
continue
@@ -34,7 +33,7 @@ def build_adc_gpio_port_index(adc_inputs):
if gpio not in gpios:
gpios[gpio] = []
pin = {"pin": adc_input["pin"], "idx": i}
pin = {"pin": adc_input.pin, "idx": i}
gpios[gpio].append(pin)
i = i + 1
@@ -45,22 +44,22 @@ def build_adc_gpio_port_index(adc_inputs):
def build_switch_gpio_port_index(switches):
gpios = {}
for switch in switches:
sw_type = switch["type"]
sw_type = str(switch.type)
if sw_type == "2POS" or sw_type == "FSWITCH":
gpio = switch.get("gpio")
pin = switch.get("pin")
gpio = switch.gpio
pin = switch.pin
if gpio is not None and pin is not None:
append_to_index(gpios, gpio, pin)
elif sw_type == "3POS":
gpio_high = switch.get("gpio_high")
pin_high = switch.get("pin_high")
gpio_high = switch.gpio_high
pin_high = switch.pin_high
if gpio_high is not None and pin_high is not None:
append_to_index(gpios, gpio_high, pin_high)
gpio_low = switch.get("gpio_low")
pin_low = switch.get("pin_low")
gpio_low = switch.gpio_low
pin_low = switch.pin_low
if gpio_low is not None and pin_low is not None:
append_to_index(gpios, gpio_low, pin_low)
@@ -88,6 +87,6 @@ def build_trim_gpio_port_index(trims):
def build_key_gpio_port_index(keys):
gpios = {}
for key in keys:
append_to_index(gpios, key["gpio"], key["pin"])
append_to_index(gpios, key.gpio, key.pin)
return gpios
return gpios
+20 -2
View File
@@ -26,6 +26,11 @@ class ADC(BaseModel):
dma_stream: Optional[str] = None
dma_stream_irq: Optional[str] = None
dma_stream_irq_handler: Optional[str] = None
# SPI ADC fields
gpio_pin_sck: Optional[str] = None
gpio_pin_miso: Optional[str] = None
gpio_pin_mosi: Optional[str] = None
gpio_pin_cs: Optional[str] = None
StickEnum = StrEnum(
@@ -75,6 +80,7 @@ InputType = StrEnum(
"VBAT",
"RTC_BAT",
"RAW",
"LUX",
),
)
@@ -100,6 +106,7 @@ class StickInput(BaseModel):
pin: Optional[str] = None
channel: Optional[Union[str, int]] = None
inverted: Optional[bool] = False
pwm_channel: Optional[int] = None
class FlexInput(BaseModel):
@@ -110,7 +117,7 @@ class FlexInput(BaseModel):
pin: Optional[str] = None
channel: Optional[Union[str, int]] = None
inverted: Optional[bool] = False
default: Optional[FlexType] = FlexType.NONE
default: Optional[FlexType] = None
label: Optional[str] = None
short_label: Optional[str] = None
@@ -150,7 +157,16 @@ class RTCBatInput(BaseModel):
channel: str
Input = Union[StickInput, FlexInput, SwitchInput, RawInput, VBatInput, RTCBatInput]
class LuxInput(BaseModel):
name: Literal["LUX"]
type: Literal["LUX"]
adc: ADCNameType
gpio: Optional[str] = None
pin: Optional[str] = None
channel: Optional[Union[str, int]] = None
Input = Union[StickInput, FlexInput, SwitchInput, RawInput, VBatInput, RTCBatInput, LuxInput]
class ADCInputs(BaseModel):
@@ -230,6 +246,8 @@ class Switch(BaseModel):
default: Optional[SwitchTypeEnum] = SwitchTypeEnum.NONE
flags: Optional[int] = 0
inverted: Optional[bool] = False
is_cfs: Optional[bool] = False
cfs_idx: Optional[int] = None
adc_input: Optional[str] = None
gpio: Optional[str] = None
pin: Optional[str] = None
+1 -1
View File
@@ -3,7 +3,7 @@
// This file has been generated from the target's JSON hardware description
//
{% set pwm_inputs = adc_inputs.inputs | selectattr('pwm_channel', 'defined') | list %}
{% set pwm_inputs = adc_inputs.inputs | selectattr('type', '==', 'STICK') | selectattr('pwm_channel', 'ne', none) | list %}
static const stick_pwm_input_t _PWM_inputs[] = {
{% for input in pwm_inputs %}
{