# adas-core.module.Relays — Relay Module > A **functional module** of the ADAS Core platform. > Encapsulates all logic for controlling and monitoring **relay hardware devices** (PDU units). This module operates as an independent satellite that the Host registers at startup, providing a unified abstraction over heterogeneous relay hardware. --- ## Table of Contents 1. [Overview](#overview) 2. [Responsibilities](#responsibilities) 3. [Project Structure](#project-structure) 4. [Dependencies](#dependencies) 5. [Device Abstraction](#device-abstraction) 6. [Relay Control Flow](#relay-control-flow) 7. [Design Rules](#design-rules) --- ## Overview `adas-core.module.Relays` is a modular satellite project that encapsulates every concern related to relay (PDU) hardware within the ADAS Core ecosystem. Relays control power to medical/IoT peripherals — turning outlets on/off, rebooting devices, and reporting outlet status per Point of Care. Key characteristics: - **Modular Monolith Pattern** — Self-contained module with device drivers, models, utilities, and settings. Other modules do not depend on it. - **Polymorphic Devices** — Multiple relay hardware families via `RelayDevice` abstract base. Adding a new family requires only a new derived class. - **Dual Protocol Support** — `KmTronicRelay` speaks raw UDP hex commands; `KmTronicV2Relay` uses HTTP/XML with Basic Auth. - **Background Polling** — `RelayDevice` optionally spawns a `Timer` that calls `CheckStatus()` at a configurable refresh interval. - **Outlet-Level Control** — Per-outlet power on/off, bulk on/off, outlet reboot, and full-device reboot with configurable delay. - **Mode Awareness** — Supports both "OpenedOffClosedOn" (NC) and standard (NO) wiring modes. - **Fake/Test Device** — `FakeRelay` provides no-op implementations for unit testing and CI pipelines. --- ## Responsibilities | Concern | What this project does | |---------|----------------------| | **Device Abstraction** | Defines `RelayDevice` abstract base with common state, timers, events, and outlet status tracking. | | **Hardware Families** | Concrete drivers for KMTronic (UDP hex) and KMTronic V2 (HTTP/XML + Basic Auth). | | **Outlet Control** | Per-outlet power on, power off, toggle, and timed reboot. | | **Bulk Control** | Power on/off all outlets simultaneously. | | **Device Reboot** | Full-device reboot with a configurable `RebootDelay`. | | **Status Polling** | Optional periodic background refresh via `System.Timers.Timer`. | | **Status Tracking** | `ConcurrentDictionary`-backed outlet status with `RelayStatusChanged` event. | | **Mode Adaptation** | Inverts on/off semantics when `Relay.Mode == OpenedOffClosedOn`. | | **Test Double** | `FakeRelay` no-op driver for isolated testing. | | **Auth Utilities** | `HttpUtils.AddBasicAuth()` for Base64-encoded Basic authentication headers. | | **Settings Model** | `RelaySettings` (cache, refresh interval, recording/API URL) consumed by drivers. | --- ## Project Structure ``` adas-core.module.Relays/ ├── Models/ │ ├── RelaySettings.cs # Driver tuning: cache, refresh time, recording/API URL │ └── PowerOutletsConfig.cs # Per-outlet UI configuration (name, icon, visibility, etc.) │ ├── Devices/ │ ├── RelayDevice.cs # Abstract base: state, timer, events, outlet status │ ├── KMTronicRelay.cs # UDP hex-protocol driver (legacy KMTronic) │ ├── KMTronicV2Relay.cs # HTTP/XML driver with Basic Auth (modern KMTronic) │ └── FakeRelay.cs # No-op test double │ └── Utils/ └── HttpUtils.cs # Basic-auth header helper for HTTP drivers ``` | File | Role | |------|------| | `RelaySettings.cs` | Configuration model: `Cache` flag, `RefreshTime` (seconds), `RecordingOrApiUrl`. | | `PowerOutletsConfig.cs` | UI-facing model: `Id`, `Name`, `Outlet`, `Type`, `IconType`, `ShowPowerOutlet`, `PowerOffEnabled`, `ResetEnabled`. | | `RelayDevice.cs` | Abstract base holding `Relay`/`RelaySettings`, `_relaysStatus` (`ConcurrentDictionary`), optional polling `Timer`, and the `RelayStatusChanged` event. | | `KMTronicRelay.cs` | UDP-based driver. Sends hex datagrams (`FF0x01` open, `FF0x00` close) over `UdpClient` with 5-second receive timeout. | | `KMTronicV2Relay.cs` | HTTP/XML driver. Polls `status.xml`, sends `relays.cgi?relay=N` commands. Uses `HttpUtils` for Basic Auth. 15-second HTTP timeout. | | `FakeRelay.cs` | Internal no-op driver. Returns canned status. Useful for tests and demo environments. | | `HttpUtils.cs` | Static helper: `AddBasicAuth(username, password, headers)` -> injects `Authorization: Basic ...` into `HttpRequestHeaders`. | --- ## Dependencies ### Downstream References | Project | Role | |---------|------| | `adas-core.Domain` | Uses `Relay`, `RelayEnum` (`Status`, `Mode`, `OutletType`), and shared domain exceptions. | ### Upstream References (projects that depend on this) | Project | Reason | |---------|--------| | `adas-core` (Host) | Registers `IRelayService` (implemented in `adas-core.Infrastructure`) which consumes this module's device classes. Controllers trigger outlet commands. | | `adas-core.Application` | `IRelayService` is defined here; the Host/Application orchestrates relay workflows. | | `adas-core.Infrastructure` | `RelayService` (in Infrastructure) instantiates and coordinates `RelayDevice` instances from this module. | ### NuGet Packages | Package | Version | Purpose | |---------|---------|---------| | `Microsoft.Extensions.Hosting` | 10.0.8 | Hosting abstractions for timer-scoped background operations. | | `AuditLogs` | 1.0.59 | Audit trail for relay state changes and outlet commands. | --- ## Device Abstraction ### `RelayDevice` Abstract Base All drivers extend `RelayDevice` and inherit: | Member | Purpose | |--------|---------| | `Relay` / `RelaySettings` | Bound configuration and settings objects. | | `_relaysStatus` | `ConcurrentDictionary` tracking each outlet. | | `RelayStatusChanged` | Event fired when an outlet transitions to a new status. | | `CheckStatus()` | Periodic refresh of outlet states (abstract). | | `GetStatusRelay(outletId)` | Reads cached status (with `lock` on dictionary). | | `SetStatusRelay(outletId, status)` | Writes status and raises event if changed (with `lock`). | | `PowerOnOffRelay(outletId, ms)` | Fire-and-forget: power off, delay, power on. | | `PowerOnRelay(outletId)` / `PowerOffRelay(outletId)` | Abstract outlet-level commands. | | `PowerOnAll()` / `PowerOffAll()` | Abstract bulk commands. | | `RebootOutlet(outletId)` / `Reboot()` | Abstract reboot sequences. | | `Timer` | Optional background polling when `RelaySettings.RefreshTime > 0`. | ### `KMTronicRelay` — UDP Hex Driver - **Transport** — `UdpClient` to `Relay.Ip:Relay.Port`. - **Command Format** — Hex strings sent as ASCII bytes (`FF0N01` = open, `FF0N00` = close). - **Bulk** — `FFE0FF` (open all), `FFE000` (close all). - **Status Query** — `FF0000` returns an 8-character bitmask; each char maps to one outlet. - **Mode Inversion** — When `Relay.Mode == OpenedOffClosedOn`, "open" means ON and "closed" means OFF. - **Receive Timeout** — 5-second bounded wait on `_udpClient.ReceiveAsync()`; empty result treated as offline. - **Connection Resilience** — Re-creates `UdpClient` on null or fault; catches and logs socket errors. ### `KMTronicV2Relay` — HTTP/XML Driver - **Transport** — `HttpClient` to `http://{Relay.Ip}:{Relay.Port}/`. - **Status Endpoint** — GET `status.xml` -> XML document with `...` nodes. - **Control Endpoint** — POST `relays.cgi?relay=N` toggles the specified outlet. - **Authentication** — `HttpUtils.AddBasicAuth()` injects `Authorization: Basic ...` when `Relay.Username`/`Relay.Password` are set. - **Polling** — Same `CheckStatus()` contract; parses XML into `_relaysStatus` dictionary. - **Bulk** — Iterates outlets 1..`Relay.Total` and fires per-outlet POSTs, then refreshes status. - **Timeout** — 15-second HTTP request timeout; faults return `HttpStatusCode.Gone` to the caller. ### `FakeRelay` — Test Double - No network calls; all commands are no-ops. - `GetStatusRelay()` returns a hardcoded `"00000000"` bitmask (all off). - `CheckDevice()` always returns `true`. - Internal visibility (`internal class`) — intended for test or DI overrides only. --- ## Relay Control Flow ### Outlet Power Cycle ``` [Host Controller] --> [RelayService.PowerOffRelay(relayId, outletId)] | v [Resolve RelayDevice from cache/settings] | v [device.PowerOffRelay(outletId)] | +---------+---------+ | | [KMTronicRelay] [KMTronicV2Relay] | | [Send UDP "FF0N01"] [POST relays.cgi?relay=N] | | v v [Receive bitmask] [Parse HTTP response] | | v v [SetStatusRelay(N, Off)] | v [Raise RelayStatusChanged] | v [Return to controller] ``` ### Periodic Status Refresh 1. `Timer` fires every `RefreshTime` seconds (if configured). 2. `CheckStatus()` delegates to the concrete driver. 3. Driver queries hardware (UDP bitmask or HTTP XML). 4. Results are written to `_relaysStatus` via `SetStatusRelay()`. 5. Any changed outlet triggers `RelayStatusChanged`, which the Host can subscribe to for real-time dashboards. ### Full Device Reboot 1. `Reboot()` -> `PowerOffAll()`. 2. Thread sleeps for `Relay.RebootDelay` milliseconds (if > 0). 3. `PowerOnAll()` restores power to all outlets. --- ## Design Rules 1. **Module Isolation** — Must not reference `adas-core.Application`, `adas-core.Infrastructure`, `adas-core.Authentication`, or other modules directly. Communicates through `Relay`/`RelaySettings` models from `Domain`. 2. **Polymorphic Devices** — New relay families are added by inheriting `RelayDevice`. No changes to existing drivers or the Host (Open/Closed). 3. **Thread-Safe State** — `_relaysStatus` is a `ConcurrentDictionary` but writes are additionally locked to ensure atomic event raising alongside status updates. 4. **Timer Ownership** — Each `RelayDevice` owns its `Timer`. The timer lifecycle is tied to the device instance. Stopping/starting is handled internally. 5. **Fail-Safe Defaults** — Any receive timeout, parse error, or connection failure results in `RelayEnum.Status.Unknown` for affected outlets. The Host decides how to surface this. 6. **No Business Logic** — This module controls hardware. It does not decide *when* to turn an outlet on/off. Those decisions belong to `Application` or the Host. 7. **Lazy UDP Connection** — `KMTronicRelay.Connect()` is called only on first `Send()`. The `UdpClient` is recreated on failure to recover from transient network issues. 8. **Auth Separation** — `HttpUtils` is a static utility, not tied to any specific driver. Basic auth logic is reusable by future HTTP-based devices. 9. **Fake Driver for Tests** — `FakeRelay` must remain `internal` and must not be referenced from production Host code. It exists solely for unit tests and CI. 10. **Audit Every Mutation** — Every outlet state change (on/off/reboot/bulk) and every status poll emits an audit event via `AuditLogs`. ---

Back to adas-core Root README