Sync edgetx to Gitea

This commit is contained in:
2026-08-03 16:37:20 +08:00
commit 14eb5a3fb9
5364 changed files with 2835009 additions and 0 deletions
+18
View File
@@ -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).
+28
View File
@@ -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)
+102
View File
@@ -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)
+76
View File
@@ -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 %}
;
+26
View File
@@ -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)
+78
View File
@@ -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
+29
View File
@@ -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");
+60
View File
@@ -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)
+56
View File
@@ -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)
+92
View File
@@ -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
+116
View File
@@ -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
+98
View File
@@ -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)
+42
View File
@@ -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 %}
};
+136
View File
@@ -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 %}
+15
View File
@@ -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 %}
+315
View File
@@ -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))
+11
View File
@@ -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 %}
};
+31
View File
@@ -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 %}
+105
View File
@@ -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 %}
};
+63
View File
@@ -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;
}
+15
View File
@@ -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 %}
};
+68
View File
@@ -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 %}
+67
View File
@@ -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()
+36
View File
@@ -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 %}
};