Files
2026-06-26 10:29:23 +02:00

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

  1. Overview
  2. Responsibilities
  3. Project Structure
  4. Dependencies
  5. Device Abstraction
  6. Relay Control Flow
  7. 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 SupportKmTronicRelay speaks raw UDP hex commands; KmTronicV2Relay uses HTTP/XML with Basic Auth.
  • Background PollingRelayDevice 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 DeviceFakeRelay 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<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

  • TransportUdpClient to Relay.Ip:Relay.Port.
  • Command Format — Hex strings sent as ASCII bytes (FF0N01 = open, FF0N00 = close).
  • BulkFFE0FF (open all), FFE000 (close all).
  • Status QueryFF0000 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

  • TransportHttpClient 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.
  • AuthenticationHttpUtils.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 ConnectionKMTronicRelay.Connect() is called only on first Send(). The UdpClient is recreated on failure to recover from transient network issues.
  8. Auth SeparationHttpUtils is a static utility, not tied to any specific driver. Basic auth logic is reusable by future HTTP-based devices.
  9. Fake Driver for TestsFakeRelay 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