12 KiB
12 KiB
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
RelayDeviceabstract base. Adding a new family requires only a new derived class. - Dual Protocol Support —
KmTronicRelayspeaks raw UDP hex commands;KmTronicV2Relayuses HTTP/XML with Basic Auth. - Background Polling —
RelayDeviceoptionally spawns aTimerthat callsCheckStatus()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 —
FakeRelayprovides 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<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 —
UdpClienttoRelay.Ip:Relay.Port. - Command Format — Hex strings sent as ASCII bytes (
FF0N01= open,FF0N00= close). - Bulk —
FFE0FF(open all),FFE000(close all). - Status Query —
FF0000returns 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
UdpClienton null or fault; catches and logs socket errors.
KMTronicV2Relay — HTTP/XML Driver
- Transport —
HttpClienttohttp://{Relay.Ip}:{Relay.Port}/. - Status Endpoint — GET
status.xml-> XML document with<relay1>...<relay8>nodes. - Control Endpoint — POST
relays.cgi?relay=Ntoggles the specified outlet. - Authentication —
HttpUtils.AddBasicAuth()injectsAuthorization: Basic ...whenRelay.Username/Relay.Passwordare set. - Polling — Same
CheckStatus()contract; parses XML into_relaysStatusdictionary. - Bulk — Iterates outlets 1..
Relay.Totaland fires per-outlet POSTs, then refreshes status. - Timeout — 15-second HTTP request timeout; faults return
HttpStatusCode.Goneto the caller.
FakeRelay — Test Double
- No network calls; all commands are no-ops.
GetStatusRelay()returns a hardcoded"00000000"bitmask (all off).CheckDevice()always returnstrue.- 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
Timerfires everyRefreshTimeseconds (if configured).CheckStatus()delegates to the concrete driver.- Driver queries hardware (UDP bitmask or HTTP XML).
- Results are written to
_relaysStatusviaSetStatusRelay(). - Any changed outlet triggers
RelayStatusChanged, which the Host can subscribe to for real-time dashboards.
Full Device Reboot
Reboot()->PowerOffAll().- Thread sleeps for
Relay.RebootDelaymilliseconds (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 throughRelay/RelaySettingsmodels fromDomain. - Polymorphic Devices — New relay families are added by inheriting
RelayDevice. No changes to existing drivers or the Host (Open/Closed). - Thread-Safe State —
_relaysStatusis aConcurrentDictionarybut writes are additionally locked to ensure atomic event raising alongside status updates. - Timer Ownership — Each
RelayDeviceowns itsTimer. 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.Unknownfor 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
Applicationor the Host. - Lazy UDP Connection —
KMTronicRelay.Connect()is called only on firstSend(). TheUdpClientis recreated on failure to recover from transient network issues. - Auth Separation —
HttpUtilsis a static utility, not tied to any specific driver. Basic auth logic is reusable by future HTTP-based devices. - Fake Driver for Tests —
FakeRelaymust remaininternaland 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