Sync edgetx to Gitea
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Hardware Definitions
|
||||
|
||||
The Python scripts in this directory are responsible for generating different
|
||||
include files used to describe the hardware to the software.
|
||||
|
||||
## Data model
|
||||
|
||||
The data model supported is described `models.py`. A script
|
||||
(`json_validator.py`) is available that validates the JSON files based on this
|
||||
model.
|
||||
|
||||
## Code generator
|
||||
|
||||
The various include files are generated based on Jinja2 templates and `generator.py`
|
||||
provided with a JSON target definition.
|
||||
|
||||
`legacy_names.py` contains some hard-coded definitions providing additional
|
||||
information related to analog inputs (by target; mostly names and labels).
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
#from os import path
|
||||
|
||||
import argparse
|
||||
|
||||
from hal_json import parse_defines
|
||||
from generator import generate_from_template
|
||||
|
||||
# def eprint(*args, **kwargs):
|
||||
# print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='Process hardware definitions')
|
||||
parser.add_argument('filename', metavar='filename', nargs='+')
|
||||
parser.add_argument('-i', metavar='input', choices=['json','defines'], default='json')
|
||||
parser.add_argument('-t', metavar='template')
|
||||
parser.add_argument('-T', metavar='target')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.i == 'defines':
|
||||
for filename in args.filename:
|
||||
parse_defines(filename, args.T)
|
||||
|
||||
elif args.i == 'json':
|
||||
for filename in args.filename:
|
||||
generate_from_template(filename, args.t, args.T)
|
||||
@@ -0,0 +1,102 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import jinja2
|
||||
import pydantic
|
||||
|
||||
import legacy_names
|
||||
import json_index
|
||||
|
||||
from models import HardwareDefinition, KeyEnum
|
||||
|
||||
MAIN_CONTROL_LUT = {
|
||||
# 2 Gimbal radios
|
||||
"LH": {"str": "Rud", "local": "STR_STICK_NAMES0"},
|
||||
"LV": {"str": "Ele", "local": "STR_STICK_NAMES1"},
|
||||
"RV": {"str": "Thr", "local": "STR_STICK_NAMES2"},
|
||||
"RH": {"str": "Ail", "local": "STR_STICK_NAMES3"},
|
||||
# Surface radios
|
||||
"ST": {"str": "ST", "local": "STR_SURFACE_NAMES0"},
|
||||
"TH": {"str": "TH", "local": "STR_SURFACE_NAMES1"},
|
||||
}
|
||||
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def is_ext_input(input):
|
||||
if str(getattr(input, "type", "")) != "FLEX":
|
||||
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:
|
||||
raw_data = json_file.read()
|
||||
|
||||
# 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)
|
||||
|
||||
# Load remaining fields not yet in the Pydantic model
|
||||
root_obj = json.loads(raw_data)
|
||||
|
||||
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)
|
||||
|
||||
legacy_inputs = legacy_names.inputs_by_target(target)
|
||||
|
||||
template_dir = os.path.dirname(os.path.abspath(template_filename))
|
||||
template_name = os.path.basename(template_filename)
|
||||
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir),
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True,
|
||||
)
|
||||
|
||||
env.tests["ext_input"] = is_ext_input
|
||||
|
||||
template = env.get_template(template_name)
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set main_inputs = adc_inputs.inputs | selectattr('type', '==', 'STICK') | list %}
|
||||
{% set n_mains = main_inputs | count %}
|
||||
static const etx_hal_adc_input_t _main_inputs[] = {
|
||||
{% for input in main_inputs %}
|
||||
{% set label = main_labels[input.name] %}
|
||||
{ "{{ input.name }}", "{{ label.str }}", {{ label.local }} },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set flex_offset = n_mains %}
|
||||
{% set flex_inputs = adc_inputs.inputs | selectattr('type', '==', 'FLEX') | list %}
|
||||
{% set n_flex = flex_inputs | count %}
|
||||
static const etx_hal_adc_input_t _flex_inputs[] = {
|
||||
{% for input in flex_inputs %}
|
||||
{ "{{ input.name }}", "{{ input.label }}", "{{ input.short_label }}" },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set raw_offset = flex_offset + n_flex %}
|
||||
{% set raw_inputs = adc_inputs.inputs | selectattr('type', '==', 'RAW') | list %}
|
||||
{% set n_raw = raw_inputs | count %}
|
||||
static const etx_hal_adc_input_t _raw_inputs[] = {
|
||||
{% for input in raw_inputs %}
|
||||
{ "{{ input.name }}", "{{ input.label }}", "{{ input.short_label }}" },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set vbat_offset = raw_offset + n_raw %}
|
||||
{% set vbat_inputs = adc_inputs.inputs | selectattr('type', '==', 'VBAT') | list %}
|
||||
{% set n_vbat = vbat_inputs | count %}
|
||||
static const etx_hal_adc_input_t _vbat_inputs[] = {
|
||||
{% for input in vbat_inputs %}
|
||||
{ "{{ input.name }}", nullptr, nullptr },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set rtc_bat_offset = vbat_offset + n_vbat %}
|
||||
{% set rtc_bat_inputs = adc_inputs.inputs | selectattr('type', '==', 'RTC_BAT') | list %}
|
||||
{% set n_rtc_bat = rtc_bat_inputs | count %}
|
||||
static const etx_hal_adc_input_t _rtc_bat_inputs[] = {
|
||||
{% for input in rtc_bat_inputs %}
|
||||
{ "{{ input.name }}", nullptr, nullptr },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set lux_offset = rtc_bat_offset + n_rtc_bat %}
|
||||
{% set lux_inputs = adc_inputs.inputs | selectattr('type', '==', 'LUX') | list %}
|
||||
{% set n_lux = lux_inputs | count %}
|
||||
static const etx_hal_adc_input_t _lux_inputs[] = {
|
||||
{% for input in lux_inputs %}
|
||||
{ "{{ input.name }}", nullptr, nullptr },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% set n_inputs = lux_offset + n_lux %}
|
||||
static const etx_hal_adc_inputs_t _hal_inputs[] = {
|
||||
{ {{ n_mains }}, 0, _main_inputs },
|
||||
{ {{ n_flex }}, {{ flex_offset }}, _flex_inputs },
|
||||
{ {{ n_vbat }}, {{ vbat_offset }}, _vbat_inputs },
|
||||
{ {{ n_rtc_bat }}, {{ rtc_bat_offset }}, _rtc_bat_inputs },
|
||||
{ {{ n_lux }}, {{ lux_offset }}, _lux_inputs },
|
||||
{ {{ n_inputs }}, 0, nullptr },
|
||||
};
|
||||
|
||||
constexpr potconfig_t _pot_default_config = (0)
|
||||
{% for flex in flex_inputs %}
|
||||
{% if flex.default %}
|
||||
| ((potconfig_t)FLEX_{{ flex.default }} << ({{ loop.index0 }} * POT_CFG_BITS))
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
;
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
class CFS:
|
||||
|
||||
def __init__(self, cfs_rgb_led, cfs_groups):
|
||||
self.rgb_led = cfs_rgb_led
|
||||
self.groups = cfs_groups
|
||||
|
||||
def parse_cfs(hw_defs):
|
||||
|
||||
cfs_rgb_led = 0
|
||||
cfs_groups = 0
|
||||
|
||||
fcfs = f'FUNCTION_SWITCHES'
|
||||
frgb = f'FUNCTION_SWITCHES_RGB_LEDS'
|
||||
|
||||
if fcfs in hw_defs:
|
||||
if frgb in hw_defs:
|
||||
cfs_rgb_led = 1
|
||||
if f'RADIO_GX12' in hw_defs:
|
||||
cfs_groups = 4
|
||||
elif f'RADIO_PA01' in hw_defs:
|
||||
cfs_groups = 2
|
||||
else:
|
||||
cfs_groups = 3
|
||||
|
||||
return CFS(cfs_rgb_led, cfs_groups)
|
||||
@@ -0,0 +1,78 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
#
|
||||
# Return a file handle or STDIN
|
||||
#
|
||||
def open_file(filename):
|
||||
if filename and not filename == "-":
|
||||
return open(filename)
|
||||
else:
|
||||
return sys.stdin
|
||||
|
||||
|
||||
#
|
||||
# Read lines of defines into a dictionary
|
||||
#
|
||||
def parse_hw_defs(filename):
|
||||
hw_defs = {}
|
||||
|
||||
with open_file(filename) as file:
|
||||
for line in file.readlines():
|
||||
m = re.match(r"#define ([^\s]*)\s*(.*)", line.rstrip())
|
||||
if m:
|
||||
name = m.group(1)
|
||||
value = m.group(2)
|
||||
if value.isnumeric():
|
||||
value = int(value)
|
||||
elif not value:
|
||||
value = None
|
||||
hw_defs[name] = value
|
||||
|
||||
return hw_defs
|
||||
|
||||
|
||||
def prune_dict(d):
|
||||
# ret = {}
|
||||
# for k, v in d.items():
|
||||
# if v is not None:
|
||||
# ret[k] = v
|
||||
# return ret
|
||||
return d
|
||||
|
||||
|
||||
# class DictEncoder(json.JSONEncoder):
|
||||
# def default(self, o):
|
||||
# if isinstance(o, Switch):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, ADCInput):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, SPI_ADCInput):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, ADC):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, Trim):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, Key):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, Display):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, CFS):
|
||||
# return prune_dict(o.__dict__)
|
||||
# if isinstance(o, Misc):
|
||||
# return prune_dict(o.__dict__)
|
||||
|
||||
# # Let the base class default method raise the TypeError
|
||||
# return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
#
|
||||
# Parse HAL defines into JSON
|
||||
#
|
||||
def parse_defines(filename, target):
|
||||
# hw_defs = LoggingDict(parse_hw_defs(filename))
|
||||
# out_defs = {}
|
||||
#
|
||||
# print(json.dumps(out_defs, cls=DictEncoder, indent=2))
|
||||
pass
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
const char* const _key_labels[] = {
|
||||
{% for k in key_index %}
|
||||
{% set key = keys | selectattr("key","==",k) | first %}
|
||||
{% if key %}
|
||||
"{{ key.label }}",
|
||||
{% else %}
|
||||
nullptr,
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
constexpr uint32_t _defined_keys = 0
|
||||
{% for key in keys %}
|
||||
| (1 << {{ key.key }})
|
||||
{% endfor %}
|
||||
;
|
||||
|
||||
constexpr uint8_t _n_keys = {{ keys | length }};
|
||||
constexpr uint8_t _n_trims = {{ trims | length }};
|
||||
|
||||
static_assert(DIM(_key_labels) == MAX_KEYS, "wrong size _key_labels[]");
|
||||
|
||||
static_assert(_n_keys <= MAX_KEYS, "too many keys defined");
|
||||
static_assert(_n_trims <= MAX_TRIMS, "too many trims defined");
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
class Display:
|
||||
|
||||
def __init__(self, w, h, phys_w, phys_h, depth, color, oled, bl_color):
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.phys_w = phys_w
|
||||
self.phys_h = phys_h
|
||||
self.depth = depth
|
||||
self.color = color
|
||||
self.oled = oled
|
||||
self.backlight_color = bl_color
|
||||
|
||||
def parse_lcd(hw_defs):
|
||||
|
||||
fw = f'LCD_W'
|
||||
fh = f'LCD_H'
|
||||
fphys_w = f'LCD_PHYS_W'
|
||||
fphys_h = f'LCD_PHYS_H'
|
||||
fdepth = f'LCD_DEPTH'
|
||||
foled = f'OLED_SCREEN'
|
||||
fbl_color = f'HAS_BACKLIGHT_COLOR'
|
||||
|
||||
w = hw_defs[fw]
|
||||
h = hw_defs[fh]
|
||||
if fphys_w in hw_defs:
|
||||
phys_w = hw_defs[fphys_w]
|
||||
else:
|
||||
phys_w = w
|
||||
if fphys_h in hw_defs:
|
||||
phys_h = hw_defs[fphys_h]
|
||||
else:
|
||||
phys_h = h
|
||||
depth = hw_defs[fdepth]
|
||||
|
||||
if phys_w == fw:
|
||||
phys_w = w
|
||||
if phys_w == fh:
|
||||
phys_w = h
|
||||
if phys_h == fh:
|
||||
phys_h = h
|
||||
if phys_h == fw:
|
||||
phys_h = w
|
||||
|
||||
if foled in hw_defs:
|
||||
oled = 1
|
||||
else:
|
||||
oled = 0
|
||||
|
||||
if depth < 16:
|
||||
color = 0
|
||||
else:
|
||||
color = 1
|
||||
|
||||
if fbl_color in hw_defs:
|
||||
bl_color = 1
|
||||
else:
|
||||
bl_color = 0
|
||||
|
||||
return Display(w, h, phys_w, phys_h, depth, color, oled, bl_color)
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
class Misc:
|
||||
|
||||
def __init__(self, has_audio_mute, has_bling_leds, has_ext_module_support, has_int_module_support, sport_max_baudrate, surface, cpu, cpu_type):
|
||||
self.has_audio_mute = has_audio_mute
|
||||
self.has_bling_leds = has_bling_leds
|
||||
self.has_ext_module_support = has_ext_module_support
|
||||
self.has_int_module_support = has_int_module_support
|
||||
self.sport_max_baudrate = sport_max_baudrate
|
||||
self.surface = surface
|
||||
self.cpu = cpu
|
||||
self.cpu_type = cpu_type
|
||||
|
||||
def parse_misc(hw_defs):
|
||||
|
||||
if f'RADIO_MT12' in hw_defs:
|
||||
surface = 1
|
||||
else:
|
||||
surface = 0
|
||||
|
||||
if f'SPORT_MAX_BAUDRATE' in hw_defs:
|
||||
sport_max_baudrate = hw_defs[f'SPORT_MAX_BAUDRATE']
|
||||
else:
|
||||
sport_max_baudrate = 400000
|
||||
|
||||
if f'AUDIO_MUTE_GPIO' in hw_defs:
|
||||
has_audio_mute = 1
|
||||
else:
|
||||
has_audio_mute = 0
|
||||
|
||||
if f'BLING_LED_STRIP_LENGTH' in hw_defs:
|
||||
has_bling_leds = hw_defs[f'BLING_LED_STRIP_LENGTH']
|
||||
else:
|
||||
has_bling_leds = 0
|
||||
|
||||
if f'RADIO_T8' in hw_defs:
|
||||
has_ext_module_support = 0
|
||||
else:
|
||||
has_ext_module_support = 1
|
||||
|
||||
if f'PCBX9D' in hw_defs or f'PCBX9DP' in hw_defs or f'PCBX9E' in hw_defs:
|
||||
has_int_module_support = 0
|
||||
else:
|
||||
has_int_module_support = 1
|
||||
|
||||
if f'CPU_TYPE_FULL' in hw_defs:
|
||||
cpu = hw_defs[f'CPU_TYPE_FULL']
|
||||
else:
|
||||
cpu = f'Unknown'
|
||||
|
||||
if f'CPU_TYPE' in hw_defs:
|
||||
cpu_type = hw_defs[f'CPU_TYPE']
|
||||
else:
|
||||
cpu_type = f'Unknown'
|
||||
|
||||
return Misc(has_audio_mute, has_bling_leds, has_ext_module_support, has_int_module_support, sport_max_baudrate, surface, cpu, cpu_type)
|
||||
@@ -0,0 +1,92 @@
|
||||
#
|
||||
# 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:
|
||||
index[str(adc_input.name)] = i
|
||||
i = i + 1
|
||||
|
||||
return index
|
||||
|
||||
|
||||
def append_to_index(d, key, val):
|
||||
if key not in d:
|
||||
d[key] = []
|
||||
|
||||
d[key].append(val)
|
||||
|
||||
|
||||
def build_adc_gpio_port_index(adc_inputs):
|
||||
i = 0
|
||||
gpios = {}
|
||||
for adc_input in adc_inputs.inputs:
|
||||
if str(adc_input.adc) == "SPI":
|
||||
continue
|
||||
|
||||
gpio = getattr(adc_input, "gpio", None)
|
||||
if gpio is None:
|
||||
i = i + 1
|
||||
continue
|
||||
|
||||
if gpio not in gpios:
|
||||
gpios[gpio] = []
|
||||
|
||||
pin = {"pin": adc_input.pin, "idx": i}
|
||||
|
||||
gpios[gpio].append(pin)
|
||||
i = i + 1
|
||||
|
||||
return gpios
|
||||
|
||||
|
||||
def build_switch_gpio_port_index(switches):
|
||||
gpios = {}
|
||||
for switch in switches:
|
||||
sw_type = str(switch.type)
|
||||
|
||||
if sw_type == "2POS" or sw_type == "FSWITCH":
|
||||
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.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.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)
|
||||
|
||||
return gpios
|
||||
|
||||
|
||||
def build_trim_gpio_port_index(trims):
|
||||
def index_contact(gpios, contact):
|
||||
gpio = contact["gpio"]
|
||||
pin = contact["pin"]
|
||||
if gpio and pin:
|
||||
append_to_index(gpios, contact["gpio"], contact["pin"])
|
||||
|
||||
gpios = {}
|
||||
for trim in trims:
|
||||
dec = trim.get("dec")
|
||||
inc = trim.get("inc")
|
||||
if dec and inc:
|
||||
index_contact(gpios, dec)
|
||||
index_contact(gpios, inc)
|
||||
|
||||
return gpios
|
||||
|
||||
|
||||
def build_key_gpio_port_index(keys):
|
||||
gpios = {}
|
||||
for key in keys:
|
||||
append_to_index(gpios, key.gpio, key.pin)
|
||||
|
||||
return gpios
|
||||
@@ -0,0 +1,116 @@
|
||||
from json import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
from typing_extensions import Type
|
||||
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
|
||||
def validate_json_files(
|
||||
directory: str, model_class: Type[BaseModel]
|
||||
) -> Tuple[List[str], List[Tuple[str, str]]]:
|
||||
"""
|
||||
Validate all JSON files in a directory against a Pydantic model.
|
||||
|
||||
Args:
|
||||
directory: Path to the directory containing JSON files
|
||||
model_class: Pydantic model class to validate against
|
||||
|
||||
Returns:
|
||||
Tuple of (valid_files, invalid_files_with_errors)
|
||||
"""
|
||||
directory_path = Path(directory)
|
||||
|
||||
if not directory_path.exists():
|
||||
raise FileNotFoundError(f"Directory {directory} does not exist")
|
||||
|
||||
valid_files = []
|
||||
invalid_files = []
|
||||
|
||||
# Find all JSON files in the directory
|
||||
json_files = list(directory_path.glob("*.json"))
|
||||
|
||||
if not json_files:
|
||||
print(f"No JSON files found in {directory}")
|
||||
return valid_files, invalid_files
|
||||
|
||||
print(f"Found {len(json_files)} JSON files to validate")
|
||||
|
||||
for json_file in json_files:
|
||||
try:
|
||||
# Read the JSON file
|
||||
with open(json_file, "r", encoding="utf-8") as f:
|
||||
json_data = f.read()
|
||||
|
||||
# Validate against given model
|
||||
model_class.model_validate_json(json_data)
|
||||
|
||||
valid_files.append(str(json_file))
|
||||
print(f"✓ {json_file.name} - Valid")
|
||||
|
||||
except ValidationError as e:
|
||||
error_msg = str(e)
|
||||
invalid_files.append((str(json_file), error_msg))
|
||||
print(f"✗ {json_file.name} - Validation Error:")
|
||||
print(f" {error_msg}")
|
||||
|
||||
except JSONDecodeError as e:
|
||||
error_msg = f"Invalid JSON syntax: {e}"
|
||||
invalid_files.append((str(json_file), error_msg))
|
||||
print(f"✗ {json_file.name} - JSON Decode Error:")
|
||||
print(f" {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Unexpected error: {e}"
|
||||
invalid_files.append((str(json_file), error_msg))
|
||||
print(f"✗ {json_file.name} - Unexpected Error:")
|
||||
print(f" {error_msg}")
|
||||
|
||||
return valid_files, invalid_files
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the validation"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate JSON files against HardwareDefinition model"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", help="Show detailed error messages"
|
||||
)
|
||||
parser.add_argument(
|
||||
"directory", help="Path to directory containing JSON files to validate"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
from models import HardwareDefinition
|
||||
|
||||
valid_files, invalid_files = validate_json_files(
|
||||
args.directory, HardwareDefinition
|
||||
)
|
||||
|
||||
print("\n=== Validation Summary ===")
|
||||
print(f"Total files processed: {len(valid_files) + len(invalid_files)}")
|
||||
print(f"Valid files: {len(valid_files)}")
|
||||
print(f"Invalid files: {len(invalid_files)}")
|
||||
|
||||
if invalid_files:
|
||||
print("\nInvalid files:")
|
||||
for file_path, error in invalid_files:
|
||||
if args.verbose:
|
||||
print(f" - {Path(file_path).name}: {error}")
|
||||
else:
|
||||
print(f" - {Path(file_path).name}")
|
||||
|
||||
return 0 if not invalid_files else 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during validation: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
from typing import Any, Dict, Iterator, KeysView, Optional, ValuesView, ItemsView
|
||||
|
||||
|
||||
class LoggingDict:
|
||||
"""A dictionary wrapper that logs all lookups to stderr."""
|
||||
|
||||
def __init__(self, data: Optional[Dict[Any, Any]] = None):
|
||||
self._data = data if data is not None else {}
|
||||
|
||||
def __getitem__(self, key: Any) -> Any:
|
||||
"""Log the lookup and return the value."""
|
||||
print(f"{str(key)}", file=sys.stderr)
|
||||
return self._data[key]
|
||||
|
||||
def __setitem__(self, key: Any, value: Any) -> None:
|
||||
"""Set an item in the dictionary."""
|
||||
self._data[key] = value
|
||||
|
||||
def __delitem__(self, key: Any) -> None:
|
||||
"""Delete an item from the dictionary."""
|
||||
del self._data[key]
|
||||
|
||||
def __contains__(self, key: Any) -> bool:
|
||||
"""Check if key exists (this is also a lookup, so log it)."""
|
||||
print(f"{str(key)}", file=sys.stderr)
|
||||
return key in self._data
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return the length of the dictionary."""
|
||||
return len(self._data)
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
"""Return an iterator over the keys."""
|
||||
return iter(self._data)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a string representation."""
|
||||
return f"LoggingDict({self._data})"
|
||||
|
||||
def get(self, key: Any, default: Any = None) -> Any:
|
||||
"""Get a value with optional default (logs the lookup)."""
|
||||
print(f"{str(key)}", file=sys.stderr)
|
||||
return self._data.get(key, default)
|
||||
|
||||
def keys(self) -> KeysView:
|
||||
"""Return dictionary keys."""
|
||||
return self._data.keys()
|
||||
|
||||
def values(self) -> ValuesView:
|
||||
"""Return dictionary values."""
|
||||
return self._data.values()
|
||||
|
||||
def items(self) -> ItemsView:
|
||||
"""Return dictionary items."""
|
||||
return self._data.items()
|
||||
|
||||
def update(self, other: Dict[Any, Any]) -> None:
|
||||
"""Update the dictionary with another dictionary."""
|
||||
self._data.update(other)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all items from the dictionary."""
|
||||
self._data.clear()
|
||||
|
||||
def pop(self, key: Any, default: Any = None) -> Any:
|
||||
"""Remove and return a value (logs the lookup)."""
|
||||
print(f"{str(key)}", file=sys.stderr)
|
||||
if default is None:
|
||||
return self._data.pop(key)
|
||||
return self._data.pop(key, default)
|
||||
|
||||
def setdefault(self, key: Any, default: Any = None) -> Any:
|
||||
"""Get a value or set/return default (logs the lookup)."""
|
||||
print(f"{str(key)}", file=sys.stderr)
|
||||
return self._data.setdefault(key, default)
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Create a logging dictionary
|
||||
d = LoggingDict({"a": 1, "b": 2, "c": 3})
|
||||
|
||||
# These operations will be logged to stderr
|
||||
print("Value of 'a':", d["a"])
|
||||
print("Value of 'b':", d.get("b"))
|
||||
print("Does 'c' exist?", "c" in d)
|
||||
print("Does 'd' exist?", "d" in d)
|
||||
|
||||
# Add new items
|
||||
d["d"] = 4
|
||||
d["e"] = 5
|
||||
|
||||
# More logged lookups
|
||||
print("Value of 'd':", d["d"])
|
||||
print("Popped 'e':", d.pop("e"))
|
||||
|
||||
print("Final dictionary:", d)
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% macro lua_input(first, offset, input) %}
|
||||
{% set li = legacy_inputs[input.name] %}
|
||||
{ {{ first }} + {{ offset }}, "{{ li.lua }}", "{{ li.description }}" },
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro lua_inputs(first, inputs) %}
|
||||
{% for input in inputs %}
|
||||
{{ lua_input(first, loop.index0, input) }}
|
||||
{% endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
static const LuaSingleField _lua_inputs[] = {
|
||||
// main inputs
|
||||
{% set main_inputs = adc_inputs.inputs | selectattr('type', '==', 'STICK') | list %}
|
||||
{{ lua_inputs('MIXSRC_FIRST_STICK', main_inputs) }}
|
||||
// flex inputs
|
||||
{% set inputs = adc_inputs.inputs | selectattr('type', '==', 'FLEX') | list %}
|
||||
{{ lua_inputs('MIXSRC_FIRST_POT', inputs) }}
|
||||
// switches
|
||||
{% set inputs = switches | selectattr('type','!=','FSWITCH') | list %}
|
||||
{% set sw_count = inputs | count %}
|
||||
{% for n in range(sw_count) %}
|
||||
{% set sw = switches[loop.index0] %}
|
||||
{ MIXSRC_FIRST_SWITCH + {{ loop.index0 }}, "{{ sw.name }}", "{{ sw.name }}" },
|
||||
{% endfor %}
|
||||
// trims
|
||||
{% set trims_count = trims | count %}
|
||||
{% for n in range(trims_count) %}
|
||||
{% if n < (main_inputs | length) %}
|
||||
{% set input = main_inputs[loop.index0] %}
|
||||
{% set li = legacy_inputs[input.name] %}
|
||||
{ MIXSRC_FIRST_TRIM + {{ loop.index0 }}, "trim-{{ li.lua }}", "{{ li.description}} trim" },
|
||||
{% else %}
|
||||
{ MIXSRC_FIRST_TRIM + {{ loop.index0 }}, "trim-t{{ loop.index }}", "Aux trim T{{ loop.index }}" },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set key_plus = keys | selectattr("key","==","KEY_PLUS") | first %}
|
||||
{% set key_up = keys | selectattr("key","==","KEY_UP") | first %}
|
||||
{% set key_shift = keys | selectattr("key","==","KEY_SHIFT") | first %}
|
||||
{% set key_pageup = keys | selectattr("key","==","KEY_PAGEUP") | first %}
|
||||
{% set key_pagedn = keys | selectattr("key","==","KEY_PAGEDN") | first %}
|
||||
{% set key_model = keys | selectattr("key","==","KEY_MODEL") | first %}
|
||||
{% set key_menu = keys | selectattr("key","==","KEY_MENU") | first %}
|
||||
{% set key_tele = keys | selectattr("key","==","KEY_TELE") | first %}
|
||||
{% set key_sys = keys | selectattr("key","==","KEY_SYS") | first %}
|
||||
{% set key_hats_as_keys = not key_pagedn and not key_menu and not key_up %}
|
||||
|
||||
{% if key_plus %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV, EVT_KEY_FIRST(KEY_PLUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV_REPT, EVT_KEY_REPT(KEY_PLUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT, EVT_KEY_FIRST(KEY_MINUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT_REPT, EVT_KEY_REPT(KEY_MINUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_DEC, EVT_KEY_FIRST(KEY_MINUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_DEC_REPT, EVT_KEY_REPT(KEY_MINUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_INC, EVT_KEY_FIRST(KEY_PLUS) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_INC_REPT, EVT_KEY_REPT(KEY_PLUS) )
|
||||
{% elif key_up %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV, EVT_KEY_FIRST(KEY_UP) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV_REPT, EVT_KEY_REPT(KEY_UP) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT, EVT_KEY_FIRST(KEY_DOWN) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT_REPT, EVT_KEY_REPT(KEY_DOWN) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_DEC, EVT_KEY_FIRST(KEY_DOWN) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_DEC_REPT, EVT_KEY_REPT(KEY_DOWN) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_INC, EVT_KEY_FIRST(KEY_UP) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_INC_REPT, EVT_KEY_REPT(KEY_UP) )
|
||||
{% else %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV, EVT_ROTARY_LEFT )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT, EVT_ROTARY_RIGHT )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_DEC, EVT_ROTARY_LEFT )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_INC, EVT_ROTARY_RIGHT )
|
||||
LROT_NUMENTRY( ROTENC_LOWSPEED, ROTENC_LOWSPEED )
|
||||
LROT_NUMENTRY( ROTENC_MIDSPEED, ROTENC_MIDSPEED )
|
||||
LROT_NUMENTRY( ROTENC_HIGHSPEED, ROTENC_HIGHSPEED )
|
||||
{% endif %}
|
||||
|
||||
{% if key_shift %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV_PAGE, EVT_KEY_LONG(KEY_LEFT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT_PAGE, EVT_KEY_LONG(KEY_RIGHT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU, EVT_KEY_BREAK(KEY_SHIFT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU_LONG, EVT_KEY_LONG(KEY_SHIFT) )
|
||||
{% elif key_up %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV_PAGE, EVT_KEY_LONG(KEY_LEFT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT_PAGE, EVT_KEY_BREAK(KEY_LEFT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU, EVT_KEY_BREAK(KEY_RIGHT) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU_LONG, EVT_KEY_LONG(KEY_RIGHT) )
|
||||
{% else %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_PREV_PAGE, EVT_KEY_BREAK(KEY_PAGEUP) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_NEXT_PAGE, EVT_KEY_BREAK(KEY_PAGEDN) )
|
||||
|
||||
{% if key_model or key_hats_as_keys %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU, EVT_KEY_BREAK(KEY_MODEL) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU_LONG, EVT_KEY_LONG(KEY_MODEL) )
|
||||
{% else %}
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU, EVT_KEY_BREAK(KEY_MENU) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_MENU_LONG, EVT_KEY_LONG(KEY_MENU) )
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_ENTER, EVT_KEY_BREAK(KEY_ENTER) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_ENTER_LONG, EVT_KEY_LONG(KEY_ENTER) )
|
||||
LROT_NUMENTRY( EVT_VIRTUAL_EXIT, EVT_KEY_BREAK(KEY_EXIT) )
|
||||
|
||||
LROT_NUMENTRY( EVT_EXIT_BREAK, EVT_KEY_BREAK(KEY_EXIT) )
|
||||
|
||||
{% macro key_event(name, key) %}
|
||||
LROT_NUMENTRY( EVT_{{ name }}_FIRST, EVT_KEY_FIRST({{ key }}) )
|
||||
LROT_NUMENTRY( EVT_{{ name }}_BREAK, EVT_KEY_BREAK({{ key }}) )
|
||||
LROT_NUMENTRY( EVT_{{ name }}_LONG, EVT_KEY_LONG({{ key }}) )
|
||||
LROT_NUMENTRY( EVT_{{ name }}_REPT, EVT_KEY_REPT({{ key }}) )
|
||||
{% endmacro %}
|
||||
|
||||
{{ key_event('ENTER','KEY_ENTER') }}
|
||||
|
||||
{% if key_menu %}
|
||||
{{ key_event('MENU','KEY_MENU') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_tele or key_hats_as_keys %}
|
||||
{{ key_event('TELEM','KEY_TELE') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_model or key_hats_as_keys %}
|
||||
{{ key_event('MODEL','KEY_MODEL') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_sys or key_hats_as_keys %}
|
||||
{{ key_event('SYS','KEY_SYS') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_up %}
|
||||
{{ key_event('UP', 'KEY_UP') }}
|
||||
{{ key_event('DOWN', 'KEY_DOWN') }}
|
||||
{{ key_event('LEFT', 'KEY_LEFT') }}
|
||||
{{ key_event('RIGHT', 'KEY_RIGHT') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_pageup or key_hats_as_keys %}
|
||||
{{ key_event('PAGEUP', 'KEY_PAGEUP') }}
|
||||
{{ key_event('PAGEDN', 'KEY_PAGEDN') }}
|
||||
{% elif key_pagedn %}
|
||||
{% if key_menu %}
|
||||
LROT_NUMENTRY( EVT_PAGE_FIRST, EVT_KEY_FIRST(KEY_PAGEDN) )
|
||||
LROT_NUMENTRY( EVT_PAGE_BREAK, EVT_KEY_BREAK(KEY_PAGEDN) )
|
||||
LROT_NUMENTRY( EVT_PAGE_LONG, EVT_KEY_BREAK(KEY_PAGEUP) )
|
||||
LROT_NUMENTRY( EVT_PAGE_REPT, EVT_KEY_REPT(KEY_PAGEDN) )
|
||||
{% else %}
|
||||
LROT_NUMENTRY( EVT_PAGEDN_FIRST, EVT_KEY_FIRST(KEY_PAGEDN) )
|
||||
LROT_NUMENTRY( EVT_PAGEDN_BREAK, EVT_KEY_BREAK(KEY_PAGEDN) )
|
||||
LROT_NUMENTRY( EVT_PAGEDN_LONG, EVT_KEY_BREAK(KEY_PAGEUP) )
|
||||
LROT_NUMENTRY( EVT_PAGEDN_REPT, EVT_KEY_REPT(KEY_PAGEDN) )
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if key_plus %}
|
||||
{{ key_event('PLUS', 'KEY_PLUS') }}
|
||||
{{ key_event('MINUS', 'KEY_MINUS') }}
|
||||
{% endif %}
|
||||
|
||||
{% if key_shift %}
|
||||
{{ key_event('SHIFT', 'KEY_SHIFT') }}
|
||||
{% endif %}
|
||||
|
||||
{% if (not key_plus) and (not key_up) %}
|
||||
{{ key_event('ROT', 'KEY_ENTER') }}
|
||||
LROT_NUMENTRY( EVT_ROT_LEFT, EVT_ROTARY_LEFT )
|
||||
LROT_NUMENTRY( EVT_ROT_RIGHT, EVT_ROTARY_RIGHT )
|
||||
{% endif %}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set main_inputs = adc_inputs.inputs | selectattr('type', '==', 'STICK') | list %}
|
||||
{% for input in main_inputs %}
|
||||
{% set label = main_labels[input.name] %}
|
||||
LROT_NUMENTRY( MIXSRC_{{ label.str }}, MIXSRC_FIRST_STICK + {{ loop.index0 }} )
|
||||
{% endfor %}
|
||||
|
||||
{% set regular_switches = switches | selectattr('type','!=','FSWITCH') | list %}
|
||||
{% for switch in regular_switches %}
|
||||
LROT_NUMENTRY( MIXSRC_{{ switch.name }}, MIXSRC_FIRST_SWITCH + {{ loop.index0 }} )
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,315 @@
|
||||
from enum import Enum
|
||||
from typing_extensions import List, Literal, Optional, Tuple, Union
|
||||
|
||||
from pydantic import BaseModel, model_validator
|
||||
from pydantic_core import from_json, PydanticCustomError
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
@staticmethod
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
return name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
ADCNameType = Literal["MAIN", "EXT", "SPI"]
|
||||
|
||||
|
||||
class ADC(BaseModel):
|
||||
name: ADCNameType
|
||||
adc: str
|
||||
sample_time: Optional[str] = None
|
||||
dma: Optional[str] = None
|
||||
dma_channel: Optional[str] = None
|
||||
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(
|
||||
"StickEnum",
|
||||
(
|
||||
# 2 Gimbal radios
|
||||
"LH",
|
||||
"LV",
|
||||
"RV",
|
||||
"RH",
|
||||
# Surface radios
|
||||
"ST",
|
||||
"TH",
|
||||
),
|
||||
)
|
||||
|
||||
MAX_POTS = 8
|
||||
MAX_SLIDERS = 8
|
||||
MAX_EXT = 16
|
||||
MAX_RAW = 8
|
||||
|
||||
FlexInputEnum = StrEnum(
|
||||
"FlexInputEnum",
|
||||
(
|
||||
[f"P{i}" for i in range(1, MAX_POTS)]
|
||||
+ [f"SL{i}" for i in range(1, MAX_SLIDERS)]
|
||||
+ [f"EXT{i}" for i in range(1, MAX_EXT)]
|
||||
+ ["JSx", "JSy"]
|
||||
),
|
||||
)
|
||||
|
||||
SwitchInputEnum = StrEnum(
|
||||
"SwitchInputEnum",
|
||||
list([f"SW{chr(i)}" for i in range(ord("A"), ord("Z") + 1)]),
|
||||
)
|
||||
|
||||
RawInputEnum = StrEnum("RawInputEnum", list([f"RAW{i}" for i in range(1, MAX_RAW)]))
|
||||
|
||||
InputNameType = Union[StickEnum, FlexInputEnum, SwitchInputEnum, RawInputEnum]
|
||||
|
||||
InputType = StrEnum(
|
||||
"InputType",
|
||||
(
|
||||
"STICK",
|
||||
"FLEX",
|
||||
"SWITCH",
|
||||
"VBAT",
|
||||
"RTC_BAT",
|
||||
"RAW",
|
||||
"LUX",
|
||||
),
|
||||
)
|
||||
|
||||
FlexType = StrEnum(
|
||||
"FlexType",
|
||||
(
|
||||
"NONE",
|
||||
"POT",
|
||||
"POT_CENTER",
|
||||
"SLIDER",
|
||||
"MULTIPOS",
|
||||
"AXIS_X",
|
||||
"AXIS_Y",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class StickInput(BaseModel):
|
||||
name: StickEnum
|
||||
type: Literal["STICK"]
|
||||
adc: Optional[ADCNameType] = None
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
channel: Optional[Union[str, int]] = None
|
||||
inverted: Optional[bool] = False
|
||||
pwm_channel: Optional[int] = None
|
||||
|
||||
|
||||
class FlexInput(BaseModel):
|
||||
name: FlexInputEnum
|
||||
type: Literal["FLEX"]
|
||||
adc: ADCNameType
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
channel: Optional[Union[str, int]] = None
|
||||
inverted: Optional[bool] = False
|
||||
default: Optional[FlexType] = None
|
||||
label: Optional[str] = None
|
||||
short_label: Optional[str] = None
|
||||
|
||||
|
||||
class SwitchInput(BaseModel):
|
||||
name: SwitchInputEnum
|
||||
type: Literal["SWITCH"]
|
||||
adc: ADCNameType
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
channel: Optional[Union[str, int]] = None
|
||||
inverted: Optional[bool] = False
|
||||
|
||||
|
||||
class RawInput(BaseModel):
|
||||
name: RawInputEnum
|
||||
type: Literal["RAW"]
|
||||
adc: ADCNameType
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
channel: Optional[Union[str, int]] = None
|
||||
|
||||
|
||||
class VBatInput(BaseModel):
|
||||
name: Literal["VBAT"]
|
||||
type: Literal["VBAT"]
|
||||
adc: ADCNameType
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
channel: Optional[Union[str, int]] = None
|
||||
|
||||
|
||||
class RTCBatInput(BaseModel):
|
||||
name: Literal["RTC_BAT"]
|
||||
type: Literal["RTC_BAT"]
|
||||
adc: Literal["MAIN", "EXT"]
|
||||
channel: str
|
||||
|
||||
|
||||
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):
|
||||
adcs: List[ADC]
|
||||
inputs: List[Input]
|
||||
|
||||
|
||||
# MUST be the same order as 'EnumKeys'
|
||||
KeyEnum = StrEnum(
|
||||
"KeyEnum",
|
||||
(
|
||||
"KEY_MENU",
|
||||
"KEY_EXIT",
|
||||
"KEY_ENTER",
|
||||
"KEY_PAGEUP",
|
||||
"KEY_PAGEDN",
|
||||
"KEY_UP",
|
||||
"KEY_DOWN",
|
||||
"KEY_LEFT",
|
||||
"KEY_RIGHT",
|
||||
"KEY_PLUS",
|
||||
"KEY_MINUS",
|
||||
"KEY_MODEL",
|
||||
"KEY_TELE",
|
||||
"KEY_SYS",
|
||||
"KEY_SHIFT",
|
||||
"KEY_BIND",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Key(BaseModel):
|
||||
name: str
|
||||
label: str
|
||||
key: KeyEnum
|
||||
active_low: Optional[bool] = False
|
||||
gpio: Optional[str] = None
|
||||
pin: Optional[str] = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_hardware(self: "Key") -> "Key":
|
||||
if bool(self.gpio) != bool(self.pin):
|
||||
raise PydanticCustomError(
|
||||
"KeyHardwareError",
|
||||
"Key missing either 'gpio' or 'pin'",
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
SwitchHardwareTypeEnum = StrEnum(
|
||||
"SwitchTypeEnum",
|
||||
(
|
||||
"2POS",
|
||||
"3POS",
|
||||
"ADC",
|
||||
"FSWITCH",
|
||||
),
|
||||
)
|
||||
|
||||
SwitchTypeEnum = StrEnum(
|
||||
"SwitchTypeEnum",
|
||||
(
|
||||
"2POS",
|
||||
"3POS",
|
||||
"TOGGLE",
|
||||
"NONE",
|
||||
),
|
||||
)
|
||||
|
||||
SwitchDisplayType = Union[Tuple[int, int], Tuple]
|
||||
|
||||
|
||||
class Switch(BaseModel):
|
||||
name: str
|
||||
type: SwitchHardwareTypeEnum
|
||||
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
|
||||
gpio_high: Optional[str] = None
|
||||
pin_high: Optional[str] = None
|
||||
gpio_low: Optional[str] = None
|
||||
pin_low: Optional[str] = None
|
||||
display: Optional[SwitchDisplayType] = None
|
||||
|
||||
def _uses_single_gpio(self: "Switch") -> bool:
|
||||
return bool(self.pin)
|
||||
|
||||
def _uses_two_gpios(self: "Switch") -> bool:
|
||||
return bool(self.pin_high or self.pin_low)
|
||||
|
||||
def _uses_gpio(self: "Switch") -> bool:
|
||||
return bool(self.gpio_high or self.gpio_low or self.gpio)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_hardware(self: "Switch") -> "Switch":
|
||||
if bool(self.adc_input) == self._uses_gpio():
|
||||
if self.adc_input:
|
||||
raise PydanticCustomError(
|
||||
"SwitchHardwareError",
|
||||
"A switch is either using ADC or it's own GPIO pin(s)",
|
||||
)
|
||||
|
||||
if self._uses_gpio() and (self._uses_single_gpio() == self._uses_two_gpios()):
|
||||
raise PydanticCustomError(
|
||||
"SwitchHardwareError",
|
||||
"A switch based on GPIO pin(s) uses either a single or two GPIOs",
|
||||
)
|
||||
|
||||
if self._uses_single_gpio():
|
||||
if str(self.type) not in ["2POS", "FSWITCH"]:
|
||||
raise PydanticCustomError(
|
||||
"SwitchHardwareError",
|
||||
"Single GPIO switch type is either '2POS' or 'FSWITCH'",
|
||||
)
|
||||
# TODO: check 'default' as well?
|
||||
|
||||
if self._uses_two_gpios():
|
||||
if not self.pin_high or not self.pin_low:
|
||||
raise PydanticCustomError(
|
||||
"SwitchHardwareError",
|
||||
"Switch missing 'pin_high' or 'pin_low'",
|
||||
)
|
||||
if str(self.type) != "3POS":
|
||||
raise PydanticCustomError(
|
||||
"SwitchHardwareError",
|
||||
"Dual GPIO switch type is always '3POS",
|
||||
)
|
||||
# TODO: check 'default' as well?
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class HardwareDefinition(BaseModel):
|
||||
adc_inputs: ADCInputs
|
||||
switches: List[Switch]
|
||||
keys: List[Key]
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: Union[str, bytes, bytearray]) -> "HardwareDefinition":
|
||||
return HardwareDefinition.model_validate(from_json(data))
|
||||
@@ -0,0 +1,11 @@
|
||||
const hw_key_def _hw_key_defs[] = {
|
||||
{% for key in keys %}
|
||||
{ "{{ key.name }}", {{ key.key }} },
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
const char* const _hw_trim_defs[] = {
|
||||
{% for trim in trims %}
|
||||
"{{ trim.name }}",
|
||||
{% endfor %}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set regular_switches = switches | selectattr('is_cfs','==',False) | list %}
|
||||
{% set display_switches = regular_switches | selectattr('display','!=',None) | list %}
|
||||
|
||||
const hw_switch_def _switch_defs[] = {
|
||||
{% for switch in switches %}
|
||||
{% if switch.is_cfs %}
|
||||
{ "{{ switch.name }}", SWITCH_HW_{{ switch.type }}, SWITCH_{{ switch.default }}, true, {{ switch.cfs_idx }} },
|
||||
{% else %}
|
||||
{ "{{ switch.name }}", SWITCH_HW_{{ switch.type }}, SWITCH_{{ switch.default }} },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
constexpr uint8_t n_switches = {{ switches | count }};
|
||||
|
||||
{% if display_switches | count > 0 %}
|
||||
const switch_display_pos_t _switch_display[] = {
|
||||
{% for sw in switches %}
|
||||
{% if sw.display %}
|
||||
{ {{ sw.display[0] }}, {{ sw.display[1] }} },
|
||||
{% else %}
|
||||
{ 0, 0 },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
{% endif %}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
#include "stm32_gpio.h"
|
||||
|
||||
static const stm32_adc_input_t _ADC_inputs[] = {
|
||||
{% for input in adc_inputs.inputs %}
|
||||
{
|
||||
// {{ input.name }}
|
||||
// ADC_INPUT_{{ input.type }},
|
||||
{{ input.gpio if input.gpio else 'nullptr' }},
|
||||
{{ input.pin if input.gpio else '0' }},
|
||||
{{ input.channel if input.channel else '0' }},
|
||||
{{ '1 // inverted' if input.inverted else '0 // normal' }}
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% for adc in adc_inputs.adcs %}
|
||||
{% set inputs = adc_inputs.inputs | selectattr('adc', '==', adc.name) | selectattr('channel','defined') %}
|
||||
static const uint8_t _ADC_{{ adc.name }}_channels[] = {
|
||||
{% for input in inputs %}
|
||||
{{ adc_index[input.name] }}, // {{ input.name }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endfor %}
|
||||
{% set offset = namespace(value=0) %}
|
||||
static const stm32_adc_t _ADC_adc[] = {
|
||||
{% for adc in adc_inputs.adcs | selectattr('name', '!=', 'SPI') %}
|
||||
{% set inputs = adc_inputs.inputs | selectattr('adc', '==', adc.name) | selectattr('channel','defined') %}
|
||||
{% set input_count = inputs | list | count %}
|
||||
{
|
||||
{{ adc.adc }},
|
||||
{{ adc.dma if adc.dma else 'nullptr' }},
|
||||
{{ adc.dma_channel if adc.dma else '0' }},
|
||||
{{ adc.dma_stream if adc.dma else '0' }},
|
||||
{{ adc.dma_stream_irq if adc.dma else '(IRQn_Type)-1' }},
|
||||
_ADC_{{ adc.name }}_channels,
|
||||
{{ offset.value }},
|
||||
{{ input_count }},
|
||||
{{ adc.sample_time if adc.sample_time else '0' }}
|
||||
},
|
||||
{% set offset.value = offset.value + input_count %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
static const stm32_spi_adc_t _ADC_spi[] = {
|
||||
{% for adc in adc_inputs.adcs | selectattr('name', '==', 'SPI') %}
|
||||
{% set inputs = adc_inputs.inputs | selectattr('adc', '==', adc.name) | selectattr('channel','defined') %}
|
||||
{% set input_count = inputs | list | count %}
|
||||
{
|
||||
.spi = {
|
||||
{{ adc.adc }},
|
||||
{{ adc.gpio_pin_sck }},
|
||||
{{ adc.gpio_pin_miso }},
|
||||
{{ adc.gpio_pin_mosi }},
|
||||
{{ adc.gpio_pin_cs }},
|
||||
},
|
||||
_ADC_{{ adc.name }}_channels,
|
||||
{{ input_count }}
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% for adc in adc_inputs.adcs | selectattr('name', '!=', 'SPI') %}
|
||||
{% if adc.dma %}
|
||||
extern "C" void {{ adc.dma_stream_irq_handler }} (void)
|
||||
{
|
||||
stm32_hal_adc_dma_isr(&_ADC_adc[{{ loop.index0 }}]);
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
extern "C" void ADC_IRQHandler()
|
||||
{
|
||||
{% for adc in adc_inputs.adcs | selectattr('name', '!=', 'SPI') %}
|
||||
stm32_hal_adc_isr(&_ADC_adc[{{ loop.index0 }}]);
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
#if defined(STM32H7)
|
||||
extern "C" void ADC3_IRQHandler()
|
||||
{
|
||||
{% for adc in adc_inputs.adcs | selectattr('name', '!=', 'SPI') %}
|
||||
stm32_hal_adc_isr(&_ADC_adc[{{ loop.index0 }}]);
|
||||
{% endfor %}
|
||||
}
|
||||
#endif
|
||||
|
||||
{% for adc_gpio, adc_inputs in adc_gpios.items() | sort %}
|
||||
static const uint32_t _ADC_{{ adc_gpio }}_pins[] = {
|
||||
{% for input in adc_inputs %}
|
||||
{{ input.pin }},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endfor %}
|
||||
static const stm32_adc_gpio_t _ADC_GPIOs[] = {
|
||||
{% for adc_gpio, adc_inputs in adc_gpios.items() | sort %}
|
||||
{ {{ adc_gpio }}, _ADC_{{ adc_gpio }}_pins, {{ adc_inputs|count }} },
|
||||
{% endfor %}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set hw_keys = keys | rejectattr('pin', 'none') | rejectattr('gpio', 'none') | list %}
|
||||
static inline void _init_keys()
|
||||
{
|
||||
{% for key_gpio, pins in key_gpios | dictsort %}
|
||||
{% if key_gpio %}
|
||||
stm32_gpio_enable_clock({{ key_gpio }});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
LL_GPIO_InitTypeDef pinInit;
|
||||
LL_GPIO_StructInit(&pinInit);
|
||||
pinInit.Mode = LL_GPIO_MODE_INPUT;
|
||||
{% for key in hw_keys %}
|
||||
pinInit.Pin = {{ key.pin }};
|
||||
pinInit.Pull = {{ 'LL_GPIO_PULL_UP' if key.active_low else 'LL_GPIO_PULL_DOWN' }};
|
||||
LL_GPIO_Init({{ key.gpio }}, &pinInit);
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
static inline uint32_t _read_keys()
|
||||
{
|
||||
uint32_t keys = 0;
|
||||
{% for key in hw_keys %}
|
||||
if ({{'!' if key.active_low }}LL_GPIO_IsInputPinSet({{ key.gpio }}, {{ key.pin }}))
|
||||
keys |= (1 << {{ key.key }});
|
||||
{% endfor %}
|
||||
return keys;
|
||||
}
|
||||
|
||||
{% set hw_trims = trims | selectattr('dec', 'defined') | selectattr('inc', 'defined') | list %}
|
||||
static inline void _init_trims()
|
||||
{
|
||||
{% for trim_gpio, pins in trim_gpios | dictsort %}
|
||||
stm32_gpio_enable_clock({{ trim_gpio }});
|
||||
{% endfor %}
|
||||
LL_GPIO_InitTypeDef pinInit;
|
||||
LL_GPIO_StructInit(&pinInit);
|
||||
pinInit.Mode = LL_GPIO_MODE_INPUT;
|
||||
{% for trim in hw_trims %}
|
||||
pinInit.Pin = {{ trim.dec.pin }};
|
||||
pinInit.Pull = {{ 'LL_GPIO_PULL_UP' if trim.dec.active_low else 'LL_GPIO_PULL_DOWN' }};
|
||||
LL_GPIO_Init({{ trim.dec.gpio }}, &pinInit);
|
||||
pinInit.Pin = {{ trim.inc.pin }};
|
||||
pinInit.Pull = {{ 'LL_GPIO_PULL_UP' if trim.inc.active_low else 'LL_GPIO_PULL_DOWN' }};
|
||||
LL_GPIO_Init({{ trim.inc.gpio }}, &pinInit);
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
static inline uint32_t _read_trims()
|
||||
{
|
||||
uint32_t trims = 0;
|
||||
{% for trim in hw_trims %}
|
||||
if ({{'!' if trim.dec.active_low }}LL_GPIO_IsInputPinSet({{ trim.dec.gpio }}, {{ trim.dec.pin }}))
|
||||
trims |= (1 << {{ loop.index0 * 2 }});
|
||||
if ({{'!' if trim.inc.active_low }}LL_GPIO_IsInputPinSet({{ trim.inc.gpio }}, {{ trim.inc.pin }}))
|
||||
trims |= (1 << {{ loop.index0 * 2 + 1 }});
|
||||
{% endfor %}
|
||||
return trims;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% 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 %}
|
||||
{
|
||||
// ADC_INPUT_{{ input.type }}, {{ input.name }}
|
||||
{{ input.pwm_channel }}, // channel
|
||||
{{ '1 // inverted' if input.inverted else '0 // normal' }}
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set regular_switches = switches | selectattr('is_cfs','==',False) | list %}
|
||||
{% set display_switches = regular_switches | selectattr('display','!=',None) | list %}
|
||||
|
||||
static const stm32_switch_t _switch_defs[] = {
|
||||
{% for switch in switches %}
|
||||
{% if switch.type == '2POS' %}
|
||||
{
|
||||
"{{ switch.name }}",
|
||||
{{ switch.gpio or "nullptr" }}, {{ switch.pin or 0 }},
|
||||
nullptr, 0,
|
||||
SWITCH_HW_2POS, {{ 'SWITCH_HW_INVERTED' if switch.inverted else 0 }}, SWITCH_{{ switch.default }},
|
||||
{% if switch.is_cfs %}
|
||||
true, {{ switch.cfs_idx }}
|
||||
{% endif %}
|
||||
},
|
||||
{% elif switch.type == '3POS' %}
|
||||
{
|
||||
"{{ switch.name }}",
|
||||
{{ switch.gpio_high or "nullptr" }}, {{ switch.pin_high or 0 }},
|
||||
{{ switch.gpio_low or "nullptr" }}, {{ switch.pin_low or 0 }},
|
||||
SWITCH_HW_3POS, {{ 'SWITCH_HW_INVERTED' if switch.inverted else 0 }}, SWITCH_{{ switch.default }},
|
||||
},
|
||||
{% elif switch.type == 'ADC' %}
|
||||
{
|
||||
"{{ switch.name }}",
|
||||
nullptr, {{ adc_index[switch.adc_input] }},
|
||||
nullptr, 0,
|
||||
SWITCH_HW_ADC, {{ 'SWITCH_HW_INVERTED' if switch.inverted else 0 }}, SWITCH_{{ switch.default }},
|
||||
},
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
static inline void _init_switches()
|
||||
{
|
||||
{% if switch_gpios|count > 0 %}
|
||||
LL_GPIO_InitTypeDef pinInit;
|
||||
LL_GPIO_StructInit(&pinInit);
|
||||
pinInit.Mode = LL_GPIO_MODE_INPUT;
|
||||
pinInit.Pull = LL_GPIO_PULL_UP;
|
||||
{% for sw_gpio, pins in switch_gpios.items() | sort %}
|
||||
{% if sw_gpio %}
|
||||
stm32_gpio_enable_clock({{ sw_gpio }});
|
||||
pinInit.Pin = {{ pins | join('|') }};
|
||||
LL_GPIO_Init({{ sw_gpio }}, &pinInit);
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
constexpr uint8_t n_switches = {{ switches | count }};
|
||||
|
||||
{% if display_switches | count > 0 %}
|
||||
const switch_display_pos_t _switch_display[] = {
|
||||
{% for sw in switches %}
|
||||
{% if sw.display %}
|
||||
{ {{ sw.display[0] }}, {{ sw.display[1] }} },
|
||||
{% else %}
|
||||
{ 0, 0 },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
{% endif %}
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Test that all templates render successfully for all board hw_defs.
|
||||
|
||||
Runs all .jinja templates against all .json board definitions in a single
|
||||
process, accumulating failures rather than stopping at the first one.
|
||||
|
||||
Usage:
|
||||
python3 test_templates.py <hw_defs_dir>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from generator import generate_from_template
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Test all templates x all boards")
|
||||
parser.add_argument("hw_defs_dir", help="Directory containing board .json files")
|
||||
args = parser.parse_args()
|
||||
|
||||
hw_defs_dir = Path(args.hw_defs_dir)
|
||||
if not hw_defs_dir.is_dir():
|
||||
print(f"ERROR: {hw_defs_dir} is not a directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
script_dir = Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
templates = sorted(script_dir.glob("*.jinja"))
|
||||
json_files = sorted(hw_defs_dir.glob("*.json"))
|
||||
|
||||
if not templates:
|
||||
print("ERROR: no .jinja templates found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not json_files:
|
||||
print(f"ERROR: no .json files found in {hw_defs_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Testing {len(json_files)} boards x {len(templates)} templates "
|
||||
f"= {len(json_files) * len(templates)} combinations")
|
||||
|
||||
failures = []
|
||||
|
||||
for json_file in json_files:
|
||||
board = json_file.stem
|
||||
for tmpl in templates:
|
||||
try:
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
generate_from_template(str(json_file), str(tmpl), board)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
failures.append((board, tmpl.name))
|
||||
|
||||
if failures:
|
||||
print(f"\nFAILED ({len(failures)}):", file=sys.stderr)
|
||||
for board, tmpl in failures:
|
||||
print(f" board={board} template={tmpl}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"All combinations passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// WARNING: DO NOT EDIT THIS FILE
|
||||
// This file has been generated from the target's JSON hardware description
|
||||
//
|
||||
|
||||
{% set main_inputs = adc_inputs.inputs | selectattr('type', '==', 'STICK') | list %}
|
||||
{% set flex_inputs = adc_inputs.inputs | selectattr('type', '==', 'FLEX') | list %}
|
||||
|
||||
struct legacy_input_t {
|
||||
const char* legacy;
|
||||
uint16_t src_raw;
|
||||
};
|
||||
|
||||
static const legacy_input_t _legacy_inputs[] = {
|
||||
// main inputs
|
||||
{% for input in main_inputs %}
|
||||
{% set li = legacy_inputs[input.name] %}
|
||||
{ "{{ li.yaml }}", MIXSRC_FIRST_STICK + {{ loop.index0 }} },
|
||||
{% endfor %}
|
||||
// flex inputs
|
||||
{% for input in flex_inputs %}
|
||||
{% set li = legacy_inputs[input.name] %}
|
||||
{ "{{ li.yaml }}", MIXSRC_FIRST_POT + {{ loop.index0 }} },
|
||||
{% endfor %}
|
||||
// trims
|
||||
{% set trims_count = trims | count %}
|
||||
{% for n in range(trims_count) %}
|
||||
{% if n < (main_inputs | length) %}
|
||||
{% set input = main_inputs[loop.index0] %}
|
||||
{% set li = legacy_inputs[input.name] %}
|
||||
{ "Trim{{ li.yaml }}", MIXSRC_FIRST_TRIM + {{ loop.index0 }} },
|
||||
{% else %}
|
||||
{ "TrimT{{ loop.index }}", MIXSRC_FIRST_TRIM + {{ loop.index0 }} },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
Reference in New Issue
Block a user