Files
SequencerIO/arnold/module_types.py
2026-03-02 17:48:55 -05:00

290 lines
14 KiB
Python

"""
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