Source code for omnitiles.hardware

"""Hardware constants shared by the SDK and any presentation layer."""

import tomllib
from dataclasses import dataclass
from pathlib import Path


[docs] @dataclass(frozen=True, slots=True) class ActuatorConfig: """Physical configuration for a linear actuator.""" name: str stroke_mm: float buffer_bottom_mm: float buffer_top_mm: float @property def min_position_mm(self) -> float: return self.buffer_bottom_mm @property def max_position_mm(self) -> float: return self.stroke_mm - self.buffer_top_mm
M1_CONFIG = ActuatorConfig( name="P16 Linear Actuator", stroke_mm=150.0, buffer_bottom_mm=20.0, buffer_top_mm=35.0, ) M2_CONFIG = ActuatorConfig( name="T16 Track Actuator", stroke_mm=100.0, buffer_bottom_mm=25.0, buffer_top_mm=15.0, ) DEFAULT_ANCHOR_POSITIONS: tuple[tuple[float, float], ...] = ( (0.0, 0.0), (1.778, 0.0), (-0.508, 1.7018), (3.0861, 1.7018), ) """Default UWB anchor coordinates in meters. Override per test-setup."""
[docs] def load_anchor_positions( path: str | Path, ) -> tuple[tuple[float, float], ...]: """Load UWB anchor positions from a TOML file. The file must contain a top-level ``positions`` array of ``[x, y]`` pairs in meters, ordered by ``CONFIG_ANCHOR_ID``. If the file does not exist, returns :data:`DEFAULT_ANCHOR_POSITIONS`. """ p = Path(path) if not p.is_file(): return DEFAULT_ANCHOR_POSITIONS with p.open("rb") as f: data = tomllib.load(f) raw = data.get("positions") if not isinstance(raw, list) or not raw: raise ValueError(f"{p}: missing or empty 'positions' array") result: list[tuple[float, float]] = [] for i, entry in enumerate(raw): if not isinstance(entry, list) or len(entry) != 2: raise ValueError(f"{p}: positions[{i}] must be [x, y]") result.append((float(entry[0]), float(entry[1]))) return tuple(result)
ADC_MAX = 4095 """Maximum 12-bit ADC reading used for raw→mm conversion."""