""" arnold/module_types.py — Terminator I/O module type registry. Every T1H (full-size) and T1K (compact) module that the EBC100 supports is catalogued here as a frozen ModuleType dataclass. Categories ---------- digital_input FC02 (read discrete inputs) — coil address space digital_output FC01/FC05/FC15 (read/write coils) — coil address space relay_output Same Modbus behaviour as digital_output, relay contacts analog_input FC04 (read input registers) — register address space analog_output FC03/FC06/FC16 (read/write holding registers) — register space temperature_input FC04 (read input registers) — register address space Address spaces -------------- The EBC100 maintains TWO independent flat address spaces: coil space — digital modules only, 1-bit per point register space — analog + temperature modules, 16-bit per channel Digital and analog modules do NOT interfere with each other's address offsets. A digital-input module in slot 1 advances coil_offset but has zero effect on register_offset, and vice-versa. """ from __future__ import annotations from dataclasses import dataclass from typing import Literal # --------------------------------------------------------------------------- # Category type # --------------------------------------------------------------------------- Category = Literal[ "digital_input", "digital_output", "relay_output", "analog_input", "analog_output", "temperature_input", ] # --------------------------------------------------------------------------- # ModuleType dataclass # --------------------------------------------------------------------------- @dataclass(frozen=True) class ModuleType: part_number: str # e.g. "T1H-08TDS" series: Literal["T1H", "T1K"] # housing form factor category: Category points: int # I/O count (channels for analog) signal_type: str # human description of signal resolution_bits: int | None = None # None for digital, 12/15/16 for analog range_options: tuple[str, ...] = () # e.g. ("0-20mA","4-20mA","0-10V","±10V") max_current_per_point: str = "" # e.g. "0.3A", "1A@250VAC" # -- Derived properties -------------------------------------------------- @property def is_digital(self) -> bool: return self.category in ( "digital_input", "digital_output", "relay_output", ) @property def is_analog(self) -> bool: return self.category in ( "analog_input", "analog_output", "temperature_input", ) @property def direction(self) -> Literal["input", "output"]: if self.category in ("digital_input", "analog_input", "temperature_input"): return "input" return "output" @property def modbus_space(self) -> Literal["coil", "register"]: """Which EBC100 address space this module lives in.""" return "coil" if self.is_digital else "register" @property def value_type(self) -> Literal["bool", "int"]: """Python type of per-point values: bool for digital, int for analog.""" return "bool" if self.is_digital else "int" # --------------------------------------------------------------------------- # Full registry # --------------------------------------------------------------------------- _ALL_MODULES: list[ModuleType] = [ # ══════════════════════════════════════════════════════════════════════════ # DIGITAL INPUTS — T1H # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-08TDS", "T1H", "digital_input", 8, "24VDC sinking (NPN) input, 4.1mA/pt"), ModuleType("T1H-08ND3", "T1H", "digital_input", 8, "24VDC sinking/sourcing input"), ModuleType("T1H-08ND3P", "T1H", "digital_input", 8, "24VDC sourcing (PNP) input"), ModuleType("T1H-16ND3", "T1H", "digital_input", 16, "24VDC sinking/sourcing input"), ModuleType("T1H-08NA", "T1H", "digital_input", 8, "120VAC input"), ModuleType("T1H-16NA", "T1H", "digital_input", 16, "120VAC input"), # DIGITAL INPUTS — T1K ModuleType("T1K-08TDS", "T1K", "digital_input", 8, "24VDC sinking (NPN) input, 4.1mA/pt"), ModuleType("T1K-08ND3", "T1K", "digital_input", 8, "24VDC sinking/sourcing input"), ModuleType("T1K-16ND3", "T1K", "digital_input", 16, "24VDC sinking/sourcing input"), # ══════════════════════════════════════════════════════════════════════════ # DIGITAL OUTPUTS — T1H # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-08TD1", "T1H", "digital_output", 8, "24VDC sourcing transistor output", max_current_per_point="0.3A"), ModuleType("T1H-08TD2", "T1H", "digital_output", 8, "12-24VDC sinking transistor output", max_current_per_point="0.3A"), ModuleType("T1H-16TD1", "T1H", "digital_output", 16, "24VDC sourcing transistor output", max_current_per_point="0.1A"), ModuleType("T1H-16TD2", "T1H", "digital_output", 16, "12-24VDC sinking transistor output", max_current_per_point="0.1A"), ModuleType("T1H-08TA", "T1H", "digital_output", 8, "120/240VAC triac output", max_current_per_point="0.5A"), # DIGITAL OUTPUTS — T1K ModuleType("T1K-08TD1", "T1K", "digital_output", 8, "24VDC sourcing transistor output", max_current_per_point="0.3A"), ModuleType("T1K-08TD2", "T1K", "digital_output", 8, "12-24VDC sinking transistor output", max_current_per_point="0.3A"), ModuleType("T1K-16TD1", "T1K", "digital_output", 16, "24VDC sourcing transistor output", max_current_per_point="0.1A"), ModuleType("T1K-16TD2", "T1K", "digital_output", 16, "12-24VDC sinking transistor output", max_current_per_point="0.1A"), ModuleType("T1K-16TD2-1","T1K", "digital_output", 16, "12-24VDC sourcing transistor output", max_current_per_point="0.1A"), ModuleType("T1K-08TA", "T1K", "digital_output", 8, "120/240VAC triac output", max_current_per_point="0.5A"), # ══════════════════════════════════════════════════════════════════════════ # RELAY OUTPUTS # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-08TRS", "T1H", "relay_output", 8, "Relay output (Form A SPST-NO)", max_current_per_point="1A@250VAC"), ModuleType("T1H-16TRS", "T1H", "relay_output", 16, "Relay output (Form A SPST-NO)", max_current_per_point="1A@250VAC"), ModuleType("T1H-16TRS2", "T1H", "relay_output", 16, "Relay output (Form A SPST-NO)", max_current_per_point="2A@250VAC"), ModuleType("T1K-08TRS", "T1K", "relay_output", 8, "Relay output (Form A SPST-NO)", max_current_per_point="1A@250VAC"), # ══════════════════════════════════════════════════════════════════════════ # ANALOG INPUTS — T1H # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-08AD-1", "T1H", "analog_input", 8, "Voltage/current analog input, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V", "0-5V", "±5V")), ModuleType("T1H-08AD-2", "T1H", "analog_input", 8, "Voltage/current analog input, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V", "0-5V", "±5V")), ModuleType("T1H-16AD-1", "T1H", "analog_input", 16, "Voltage/current analog input, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1H-16AD-2", "T1H", "analog_input", 16, "Voltage/current analog input, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), # ANALOG INPUTS — T1K ModuleType("T1K-08AD-1", "T1K", "analog_input", 8, "Voltage/current analog input, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V", "0-5V", "±5V")), ModuleType("T1K-08AD-2", "T1K", "analog_input", 8, "Voltage/current analog input, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V", "0-5V", "±5V")), # ══════════════════════════════════════════════════════════════════════════ # ANALOG OUTPUTS — T1H # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-04DA-1", "T1H", "analog_output", 4, "Voltage/current analog output, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1H-04DA-2", "T1H", "analog_output", 4, "Voltage/current analog output, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1H-08DA-1", "T1H", "analog_output", 8, "Voltage/current analog output, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1H-08DA-2", "T1H", "analog_output", 8, "Voltage/current analog output, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1H-16DA-1", "T1H", "analog_output", 16, "Voltage/current analog output, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V")), ModuleType("T1H-16DA-2", "T1H", "analog_output", 16, "Voltage/current analog output, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V")), # ANALOG OUTPUTS — T1K ModuleType("T1K-04DA-1", "T1K", "analog_output", 4, "Voltage/current analog output, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1K-04DA-2", "T1K", "analog_output", 4, "Voltage/current analog output, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1K-08DA-1", "T1K", "analog_output", 8, "Voltage/current analog output, 12-bit", resolution_bits=12, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), ModuleType("T1K-08DA-2", "T1K", "analog_output", 8, "Voltage/current analog output, 15-bit", resolution_bits=15, range_options=("0-20mA", "4-20mA", "0-10V", "±10V")), # ══════════════════════════════════════════════════════════════════════════ # TEMPERATURE INPUTS # ══════════════════════════════════════════════════════════════════════════ ModuleType("T1H-08THM", "T1H", "temperature_input", 8, "Thermocouple input (J/K/E/T/R/S/N/B)", resolution_bits=16, range_options=("type J", "type K", "type E", "type T", "type R", "type S", "type N", "type B")), ModuleType("T1H-04RTD", "T1H", "temperature_input", 4, "RTD input (Pt100/Pt1000/Ni120)", resolution_bits=16, range_options=("Pt100", "Pt1000", "Ni120")), ModuleType("T1K-08THM", "T1K", "temperature_input", 8, "Thermocouple input (J/K/E/T/R/S/N/B)", resolution_bits=16, range_options=("type J", "type K", "type E", "type T", "type R", "type S", "type N", "type B")), ModuleType("T1K-04RTD", "T1K", "temperature_input", 4, "RTD input (Pt100/Pt1000/Ni120)", resolution_bits=16, range_options=("Pt100", "Pt1000", "Ni120")), ] # Build the lookup dict MODULE_REGISTRY: dict[str, ModuleType] = {m.part_number: m for m in _ALL_MODULES} def get_module_type(part_number: str) -> ModuleType: """Look up a module type by part number. Raises KeyError with helpful message.""" try: return MODULE_REGISTRY[part_number] except KeyError: known = ", ".join(sorted(MODULE_REGISTRY)) raise KeyError( f"Unknown module type {part_number!r}. " f"Known types: {known}" ) from None