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
- Overview
- Responsibilities
- Project Structure
- Dependencies
- Device Abstraction
- Relay Control Flow
- 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
| 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<int, RelayEnum.Status> 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 <relay1>...<relay8> 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
Periodic Status Refresh
Timer fires every RefreshTime seconds (if configured).
CheckStatus() delegates to the concrete driver.
- Driver queries hardware (UDP bitmask or HTTP XML).
- Results are written to
_relaysStatus via SetStatusRelay().
- Any changed outlet triggers
RelayStatusChanged, which the Host can subscribe to for real-time dashboards.
Full Device Reboot
Reboot() -> PowerOffAll().
- Thread sleeps for
Relay.RebootDelay milliseconds (if > 0).
PowerOnAll() restores power to all outlets.
Design Rules
- 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.
- Polymorphic Devices — New relay families are added by inheriting
RelayDevice. No changes to existing drivers or the Host (Open/Closed).
- Thread-Safe State —
_relaysStatus is a ConcurrentDictionary but writes are additionally locked to ensure atomic event raising alongside status updates.
- Timer Ownership — Each
RelayDevice owns its Timer. The timer lifecycle is tied to the device instance. Stopping/starting is handled internally.
- 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.
- 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.
- Lazy UDP Connection —
KMTronicRelay.Connect() is called only on first Send(). The UdpClient is recreated on failure to recover from transient network issues.
- Auth Separation —
HttpUtils is a static utility, not tied to any specific driver. Basic auth logic is reusable by future HTTP-based devices.
- 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.
- 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