diff --git a/radio/util/hw_defs/generator.py b/radio/util/hw_defs/generator.py index a9ba622bf..3c00857c7 100644 --- a/radio/util/hw_defs/generator.py +++ b/radio/util/hw_defs/generator.py @@ -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) diff --git a/radio/util/hw_defs/json_index.py b/radio/util/hw_defs/json_index.py index ccbcb8695..739aa3e7e 100644 --- a/radio/util/hw_defs/json_index.py +++ b/radio/util/hw_defs/json_index.py @@ -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 \ No newline at end of file + return gpios diff --git a/radio/util/hw_defs/models.py b/radio/util/hw_defs/models.py index 0210bb02f..02752b05b 100644 --- a/radio/util/hw_defs/models.py +++ b/radio/util/hw_defs/models.py @@ -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 diff --git a/radio/util/hw_defs/stm32_pwm_inputs.jinja b/radio/util/hw_defs/stm32_pwm_inputs.jinja index f2fcfd177..a79525240 100644 --- a/radio/util/hw_defs/stm32_pwm_inputs.jinja +++ b/radio/util/hw_defs/stm32_pwm_inputs.jinja @@ -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 %} {