316 lines
7.5 KiB
Python
316 lines
7.5 KiB
Python
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))
|