rama creada apartir de master en j

This commit is contained in:
jrojas
2026-06-26 10:29:23 +02:00
parent 319fd3dfb0
commit c1517fda87
2810 changed files with 1927392 additions and 25392 deletions
@@ -4,34 +4,69 @@ using adas_core.module.Relays.Models;
namespace adas_core.module.Relays.Devices;
/// <summary>
/// This class represents a fake relay device used for testing purposes. It inherits from the RelayDevice class and overrides its methods to provide simulated behavior without interacting with actual hardware.
/// This allows developers to test the functionality of their code that interacts with relay devices without needing access to physical devices.
/// </summary>
/// <param name="relay">The Relay object representing the relay device.</param>
/// <param name="relaySettings">The RelaySettings object containing the configuration settings for the relay device.</param>
internal class FakeRelay(Relay relay, RelaySettings relaySettings) : RelayDevice(relay, relaySettings)
{
/// <summary>
/// Checks the status of the fake relay device. Since this is a simulated device, it always returns true, indicating that the device is functioning properly.
/// In a real implementation, this method would contain logic to check the actual status of the hardware device.
/// </summary>
/// <returns>True if the device is functioning properly, false otherwise.</returns>
public override bool CheckDevice()
{
return true;
}
/// <summary>
/// Gets the status of the fake relay device. Since this is a simulated device, it always returns true, indicating that the device is on.
/// </summary>
/// <returns>True if the device is on, false otherwise.</returns>
public override bool GetStatusRelay()
{
return true;
}
/// <summary>
/// Powers off the relay for the specified outlet ID. Since this is a simulated device, this method does not perform any actual operations.
/// In a real implementation, this method would contain logic to send a command to the hardware device to power off the specified outlet.
/// </summary>
/// <param name="outletId">The ID of the outlet to power off</param>
public override void PowerOffRelay(int outletId)
{
}
/// <summary>
/// Powers on the relay for the specified outlet ID. Since this is a simulated device, this method does not perform any actual operations.
/// </summary>
/// <param name="outletId">The ID of the outlet to power on.</param>
public override void PowerOnRelay(int outletId)
{
}
/// <summary>
/// Powers off all relays. Since this is a simulated device, this method does not perform any actual operations.
/// </summary>
public override void PowerOffAll()
{
}
/// <summary>
/// Powers on all relays. Since this is a simulated device, this method does not perform any actual operations.
/// </summary>
public override void PowerOnAll()
{
}
/// <summary>
/// Gets the status of the relay for the specified outlet ID. Since this is a simulated device, it returns a hardcoded status string "00000000", which indicates that all outlets are off.
/// </summary>
/// <param name="outletId">The ID of the outlet to get the status for.</param>
/// <returns>The status of the specified outlet.</returns>
public override RelayEnum.Status GetStatusRelay(int outletId)
{
const string status = "00000000";
@@ -40,10 +75,18 @@ internal class FakeRelay(Relay relay, RelaySettings relaySettings) : RelayDevice
return status.Substring(outletId - 1, 1) == "0" ? RelayEnum.Status.Off : RelayEnum.Status.On;
}
/// <summary>
/// Reboots the relay for the specified outlet ID. Since this is a simulated device, this method does not perform any actual operations.
/// </summary>
/// <param name="outletId">The ID of the outlet to reboot.</param>
public override void RebootOutlet(int outletId)
{
}
/// <summary>
/// Reboots all relays. Since this is a simulated device, this method does not perform any actual operations.
/// However, it includes a delay based on the RebootDelay property of the Relay object to simulate the time taken for a reboot process.
/// </summary>
public override void Reboot()
{
PowerOffAll();
@@ -51,11 +94,19 @@ internal class FakeRelay(Relay relay, RelaySettings relaySettings) : RelayDevice
PowerOnAll();
}
/// <summary>
/// Asynchronously gets the status of the relay for the specified outlet ID. Since this is a simulated device, it returns a hardcoded status string "00000000", which indicates that all outlets are off.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the status of the specified outlet.</returns>
public override Task<bool> GetStatusRelayAsync()
{
return Task.FromResult(true);
}
/// <summary>
/// Asynchronously gets the status of the relay for the specified outlet ID.
/// Since this is a simulated device, it returns a hardcoded status string "00000000", which indicates that all outlets are off.
/// </summary>
public override void CheckStatus()
{
}
@@ -7,13 +7,29 @@ using Serilog;
namespace adas_core.module.Relays.Devices;
/// <summary>
/// Class for controlling a KMTronic relay device.
/// </summary>
/// <param name="relay">The Relay object representing the relay device.</param>
/// <param name="relaySettings">The RelaySettings object containing the configuration settings for the relay device.</param>
public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
: RelayDevice(relay, relaySettings)
{
/// <summary>
/// UDP client used for communication with the KMTronic relay device. It is initialized when connecting to the device and is used to send commands and receive responses.
/// </summary>
private UdpClient? _udpClient;
/// <summary>
/// Indicates the current status of the KMTronic relay device. It is updated by the CheckStatus method and can be used to determine if the device is online or offline.
/// </summary>
public bool DeviceStatus { get; set; }
/// <summary>
/// Checks the connectivity and status of the KMTronic relay device by sending a command to retrieve the status of the first outlet. It updates the DeviceStatus property based on the response received from the device.
/// If the response indicates that the status is unknown, it returns false, indicating that the device is not reachable or not responding correctly. Otherwise, it returns true, indicating that the device is online and responsive.
/// </summary>
/// <returns>True if the device is online and responsive, false otherwise.</returns>
public override bool CheckDevice()
{
CheckStatus();
@@ -21,6 +37,11 @@ public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
return result != RelayEnum.Status.Unknown;
}
/// <summary>
/// Retrieves the status of the KMTronic relay device by sending a command to get the status of all outlets. It checks if the response is not null or empty, which indicates that the device is online and responsive.
/// If the response is null or empty, it indicates that the device is not reachable or not responding correctly, and it returns false. Otherwise, it returns true, indicating that the device is online and responsive.
/// </summary>
/// <returns>True if the device is online and responsive, false otherwise.</returns>
public override bool GetStatusRelay()
{
var status = Send("FF0000");
@@ -33,6 +54,12 @@ public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
return !string.IsNullOrEmpty(status);
}
/// <summary>
/// Retrieves the status of a specific outlet on the KMTronic relay device by sending a command to get the status of all outlets.
/// It checks if the response is not null or empty, and if it contains the expected format for the outlet status.
/// </summary>
/// <param name="outletId">The ID of the outlet to retrieve the status for.</param>
/// <returns>The status of the specified outlet.</returns>
public override RelayEnum.Status GetStatusRelay(int outletId)
{
var status = Send("FF0000");
@@ -71,54 +98,87 @@ public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
return Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn ? RelayEnum.Status.Off : RelayEnum.Status.On;
}
/// <summary>
/// Powers off a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
/// </summary>
/// <param name="outletId">The ID of the outlet to power off.</param>
public override void PowerOffRelay(int outletId)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) OpenRelay(outletId);
else CloseRelay(outletId);
}
/// <summary>
/// Powers on a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
/// </summary>
/// <param name="outletId">The ID of the outlet to power on.</param>
public void OpenRelay(int outletId)
{
//Activa el relé por lo que corta la corriente del dispositivo conectado
Send("FF0" + outletId + "01");
}
/// <summary>
/// Powers on a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
/// </summary>
/// <param name="outletId">The ID of the outlet to power on.</param>
public override void PowerOnRelay(int outletId)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) CloseRelay(outletId);
else OpenRelay(outletId);
}
/// <summary>
/// Closes a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
/// This method is used to power on the outlet by allowing the current to flow through it.
/// </summary>
/// <param name="outletId">The ID of the outlet to close.</param>
public void CloseRelay(int outletId)
{
//Desactiva el relé por lo que deja pasar la corriente del dispositivo conectado
Send("FF0" + outletId + "00");
}
/// <summary>
/// Powers off all outlets on the KMTronic relay device by sending a command to either open or close all relays, depending on the configured mode.
/// </summary>
public override void PowerOffAll()
{
//Activa el relé por lo que corta la corriente de todos los dispositivo conectados
Send("FFE0FF");
}
/// <summary>
/// Powers on all outlets on the KMTronic relay device by sending a command to either open or close all relays, depending on the configured mode.
/// </summary>
public override void PowerOnAll()
{
//Desactiva el relé por lo que deja pasar la corriente a todos dispositivo conectados
Send("FFE000");
}
/// <summary>
/// Checks the status of the KMTronic relay device by calling the GetStatusRelay method and updating the DeviceStatus property accordingly.
/// </summary>
public override void CheckStatus()
{
DeviceStatus = GetStatusRelay();
}
/// <summary>
/// Reboots a specific outlet on the KMTronic relay device by first powering it off, waiting for a specified delay, and then powering it back on. This method is used to reset the connected device by briefly cutting off its power supply.
/// </summary>
/// <param name="outletId">The ID of the outlet to reboot.</param>
public override void RebootOutlet(int outletId)
{
//Reinicia el relé del dispositivo conectado
PowerOnOffRelay(outletId, 2000);
}
/// <summary>
/// Reboots all outlets on the KMTronic relay device by first powering them off, waiting for a specified delay, and then powering them back on. This method is used to reset all connected devices by briefly cutting off their power supply.
/// </summary>
public override void Reboot()
{
PowerOffAll();
@@ -126,11 +186,19 @@ public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
PowerOnAll();
}
/// <summary>
/// Asynchronously retrieves the status of the KMTronic relay device. This method is not implemented in this class, and it throws a NotImplementedException when called.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="NotImplementedException"></exception>
public override Task<bool> GetStatusRelayAsync()
{
throw new NotImplementedException();
}
/// <summary>
/// Asynchronously retrieves the status of a specific outlet on the KMTronic relay device. This method is not implemented in this class, and it throws a NotImplementedException when called.
/// </summary>
protected virtual void Connect()
{
if (_udpClient == null)
@@ -169,6 +237,13 @@ public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
}
}
/// <summary>
/// Sends a command to the KMTronic relay device and waits for a response.
/// It first checks if the UDP client is initialized and connected, and if not, it attempts to connect to the device.
/// If the connection is successful, it sends the specified command to the device and waits for a response.
/// </summary>
/// <param name="datagrama">The command to send to the KMTronic relay device.</param>
/// <returns>The response from the KMTronic relay device.</returns>
protected virtual string Send(string datagrama)
{
if (_udpClient == null) Connect();
@@ -8,13 +8,27 @@ using Serilog;
namespace adas_core.module.Relays.Devices;
/// <summary>
/// Class representing a KMTronic V2 relay device. This class inherits from the RelayDevice class and provides specific implementations for checking the device status, getting the status of individual relays, and controlling the power state of the relays for a KMTronic V2 relay device.
/// </summary>
public class KmTronicV2Relay : RelayDevice
{
/// <summary>
/// Constructor for the KmTronicV2Relay class. It initializes the base RelayDevice class with the provided Relay and RelaySettings objects, and then checks the device status to ensure it is functioning correctly.
/// </summary>
/// <param name="relay">The Relay object representing the relay device.</param>
/// <param name="relaySettings">The RelaySettings object containing the configuration settings for the relay device.</param>
public KmTronicV2Relay(Relay relay, RelaySettings relaySettings) : base(relay, relaySettings)
{
CheckDevice();
}
/// <summary>
/// Checks the status of the KMTronic V2 relay device by sending a request to retrieve the status of the relays.
/// It updates the internal status of each relay based on the response received.
/// The method returns true if the device is responsive and provides a valid status, and false otherwise.
/// </summary>
/// <returns>True if the device is responsive and provides a valid status, false otherwise.</returns>
public sealed override bool CheckDevice()
{
CheckStatus();
@@ -22,6 +36,12 @@ public class KmTronicV2Relay : RelayDevice
return result != RelayEnum.Status.Unknown;
}
/// <summary>
/// Checks the status of the KMTronic V2 relay device by sending a request to retrieve the status of the relays.
/// </summary>
/// <remarks>
/// The method sends an HTTP GET request to the "status.xml" endpoint of the KMTronic V2 relay device and parses the XML response to update the internal status of each relay.
/// </remarks>
public override void CheckStatus()
{
try
@@ -67,28 +87,50 @@ public class KmTronicV2Relay : RelayDevice
}
}
/// <summary>
/// Gets the status of a specific relay outlet by sending a request to the KMTronic V2 relay device. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
/// </summary>
/// <returns>True if the specified relay outlet is On, false otherwise.</returns>
public override bool GetStatusRelay()
{
return GetStatusRelay(1) == RelayEnum.Status.On;
}
/// <summary>
/// Gets the status of a specific relay outlet by sending a request to the KMTronic V2 relay device. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
/// </summary>
/// <returns>True if the specified relay outlet is On, false otherwise.</returns>
public override Task<bool> GetStatusRelayAsync()
{
var result = GetStatusRelay();
return Task.FromResult(result);
}
/// <summary>
/// Gets the status of a specific relay outlet by retrieving the status from the internal status dictionary. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
/// </summary>
/// <param name="outletId">The ID of the relay outlet.</param>
public override void PowerOnRelay(int outletId)
{
PowerRelay(outletId, RelayEnum.Status.On);
}
/// <summary>
/// Gets the status of a specific relay outlet by retrieving the status from the internal status dictionary. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
/// </summary>
/// <param name="outletId">The ID of the relay outlet.</param>
public override void PowerOffRelay(int outletId)
{
PowerRelay(outletId, RelayEnum.Status.Off);
}
/// <summary>
/// Constructs a URI for sending requests to the KMTronic V2 relay device based on the specified path and query parameters.
/// The method builds the URI using the IP address, port, and path of the relay device, and includes any query parameters if provided.
/// </summary>
/// <param name="path">The path of the relay device.</param>
/// <param name="query">The query parameters for the request.</param>
/// <returns>The constructed URI.</returns>
private Uri GetUri(string path, string? query = null)
{
var uriBuilder = new UriBuilder
@@ -97,6 +139,13 @@ public class KmTronicV2Relay : RelayDevice
return uriBuilder.Uri;
}
/// <summary>
/// Constructs an HttpRequestMessage for sending requests to the KMTronic V2 relay device based on the specified path, HTTP method, and query parameters.
/// </summary>
/// <param name="path">The path of the relay device.</param>
/// <param name="method">The HTTP method for the request.</param>
/// <param name="query">The query parameters for the request.</param>
/// <returns>The constructed HttpRequestMessage.</returns>
private HttpRequestMessage GetRequest(string path, HttpMethod method, string? query = null)
{
var uri = GetUri(path, query);
@@ -111,6 +160,15 @@ public class KmTronicV2Relay : RelayDevice
return request;
}
/// <summary>
/// Sends an HTTP request to the KMTronic V2 relay device using the specified path, HTTP method, and query parameters.
/// The method constructs the request using the GetRequest method and sends it using an HttpClient.
/// It handles any exceptions that may occur during the request and returns the HttpResponseMessage received from the relay device.
/// </summary>
/// <param name="path">The path of the relay device.</param>
/// <param name="method">The HTTP method for the request.</param>
/// <param name="query">The query parameters for the request.</param>
/// <returns>The HttpResponseMessage received from the relay device.</returns>
private HttpResponseMessage SendRequest(string path, HttpMethod method, string? query = null)
{
using var client = new HttpClient();
@@ -127,6 +185,15 @@ public class KmTronicV2Relay : RelayDevice
}
}
/// <summary>
/// Controls the power state of a specific relay outlet by sending a request to the KMTronic V2 relay device.
/// The method checks the current status of the specified relay outlet and sends a request to change its state to the desired state (On or Off).
/// If the checkStatus parameter is set to true, it will also check the status of the device after sending the request to ensure that the state change was successful.
/// </summary>
/// <param name="outletId">The ID of the relay outlet to control.</param>
/// <param name="toState">The desired state for the relay outlet (On or Off).</param>
/// <param name="checkStatus">Whether to check the status of the device after sending the request.</param>
private void PowerRelay(int outletId, RelayEnum.Status toState, bool checkStatus = true)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn)
@@ -155,22 +222,37 @@ public class KmTronicV2Relay : RelayDevice
}
}
/// <summary>
/// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device.
/// The method iterates through all relay outlets and sends requests to change their state to the desired state (On or Off).
/// After sending the requests, it checks the status of the device to ensure that the state changes were successful.
/// </summary>
public override void PowerOnAll()
{
PowerAll(RelayEnum.Status.On);
}
/// <summary>
/// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device.
/// </summary>
public override void PowerOffAll()
{
PowerAll(RelayEnum.Status.Off);
}
/// <summary>
/// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device.
/// </summary>
/// <param name="toState">The desired state for all relay outlets (On or Off).</param>
private void PowerAll(RelayEnum.Status toState)
{
Enumerable.Range(1, Relay.Total).ToList().ForEach(outletId => { PowerRelay(outletId, toState, false); });
CheckStatus();
}
/// <summary>
/// Reboots the KMTronic V2 relay device by first powering off all relay outlets, then waiting for a specified delay (if configured), and finally powering on all relay outlets again.
/// </summary>
public override void Reboot()
{
PowerOffAll();
@@ -178,6 +260,10 @@ public class KmTronicV2Relay : RelayDevice
PowerOnAll();
}
/// <summary>
/// Reboots a specific relay outlet by first powering it off, then waiting for a specified delay (30 seconds in this case), and finally powering it on again.
/// </summary>
/// <param name="outletId">The ID of the relay outlet to reboot.</param>
public override void RebootOutlet(int outletId)
{
PowerOffRelay(outletId);
+91 -1
View File
@@ -6,14 +6,42 @@ using Timer = System.Timers.Timer;
namespace adas_core.module.Relays.Devices;
/// <summary>
/// Abstract class representing a relay device.
/// It provides methods to check the device status, get and set the status of individual relays, and control the power state of the relays.
/// The class also includes an event to notify when the status of a relay changes.
/// The implementation of the methods is left to the derived classes, which will provide specific functionality based on the type of relay device being used.
/// </summary>
public abstract class RelayDevice
{
/// <summary>
/// A thread-safe dictionary to store the status of each relay outlet.
/// </summary>
private readonly ConcurrentDictionary<int, RelayEnum.Status> _relaysStatus = new();
/// <summary>
/// A timer to periodically check the status of the relay device. The timer is initialized based on the refresh time specified in the relay settings.
/// If the refresh time is greater than 0, the timer will trigger the CheckStatus method at regular intervals to update the status of the relays.
/// </summary>
private readonly Timer? _timer;
/// <summary>
/// The Relay object representing the relay device. This object contains information about the relay, such as its name, type, and other relevant details.
/// </summary>
public readonly Relay Relay;
/// <summary>
/// The RelaySettings object containing the configuration settings for the relay device.
/// This includes parameters such as the refresh time for checking the status of the relays, and any other settings that may be relevant for the operation of the relay device.
/// </summary>
public readonly RelaySettings RelaySettings;
//string host, int port, int relays, int? refreshTime, EventHandler refreshEvent
/// <summary>
/// Constructor for the RelayDevice class. It initializes the Relay and RelaySettings properties, and sets up the timer for checking the status of the relays if a refresh time is specified in the relay settings.
/// </summary>
/// <param name="relay">The Relay object representing the relay device.</param>
/// <param name="relaySettings">The RelaySettings object containing the configuration settings for the relay device.</param>
protected RelayDevice(Relay relay, RelaySettings relaySettings)
{
Relay = relay;
@@ -32,14 +60,39 @@ public abstract class RelayDevice
}
}
/// <summary>
/// Event that is triggered when the status of a relay changes. The event handler receives the instance of the RelayDevice and the outlet ID of the relay that changed its status.
/// </summary>
public event EventHandler<int>? RelayStatusChanged;
/// <summary>
/// Checks the status of the relay device. This method is abstract and must be implemented by derived classes to provide specific functionality for checking the status of the relay device.
/// </summary>
/// <returns></returns>
public abstract bool CheckDevice();
/// <summary>
/// Checks the status of the relays. This method is abstract and must be implemented by derived classes to provide specific functionality for checking the status of the relays.
/// </summary>
public abstract void CheckStatus();
/// <summary>
/// Gets the status of the relay device. This method is abstract and must be implemented by derived classes to provide specific functionality for retrieving the status of the relay device.
/// </summary>
/// <returns></returns>
public abstract bool GetStatusRelay();
/// <summary>
/// Gets the status of the relays. This method is abstract and must be implemented by derived classes to provide specific functionality for retrieving the status of the relays.
/// </summary>
/// <returns></returns>
public abstract Task<bool> GetStatusRelayAsync();
/// <summary>
/// Gets the status of a specific relay outlet. This method retrieves the status of the relay outlet with the specified outlet ID from the _relaysStatus dictionary.
/// </summary>
/// <param name="outletId">The ID of the relay outlet.</param>
/// <returns>The status of the specified relay outlet.</returns>
public virtual RelayEnum.Status GetStatusRelay(int outletId)
{
lock (_relaysStatus)
@@ -48,6 +101,11 @@ public abstract class RelayDevice
}
}
/// <summary>
/// Sets the status of a specific relay outlet. This method updates the status of the relay outlet with the specified outlet ID in the _relaysStatus dictionary.
/// </summary>
/// <param name="outletId">The ID of the relay outlet.</param>
/// <param name="status">The new status of the relay outlet.</param>
public virtual void SetStatusRelay(int outletId, RelayEnum.Status status)
{
lock (_relaysStatus)
@@ -58,6 +116,13 @@ public abstract class RelayDevice
}
}
/// <summary>
/// Powers on or off a specific relay outlet for a specified duration.
/// This method first powers off the relay outlet with the specified outlet ID, then waits for the specified number of milliseconds, and finally powers on the relay outlet again.
/// The method runs asynchronously to avoid blocking the main thread while waiting for the specified duration.
/// </summary>
/// <param name="outletId">The ID of the relay outlet.</param>
/// <param name="milliseconds">The duration in milliseconds for which the relay outlet should be powered off.</param>
public virtual void PowerOnOffRelay(int outletId, int milliseconds)
{
Task.Run(() =>
@@ -68,19 +133,44 @@ public abstract class RelayDevice
});
}
/// <summary>
/// Refreshes the status of the relay device. This method is virtual and can be overridden by derived classes to provide specific functionality for refreshing the status of the relay device.
/// </summary>
public virtual void Refresh()
{
}
/// <summary>
/// Powers on a specific relay outlet. This method is abstract and must be implemented by derived classes to provide specific functionality for powering on a relay outlet with the specified outlet ID.
/// </summary>
/// <param name="outletId">The ID of the relay outlet to power on.</param>
public abstract void PowerOnRelay(int outletId);
/// <summary>
/// Powers off a specific relay outlet. This method is abstract and must be implemented by derived classes to provide specific functionality for powering off a relay outlet with the specified outlet ID.
/// </summary>
/// <param name="outletId">The ID of the relay outlet to power off.</param>
public abstract void PowerOffRelay(int outletId);
/// <summary>
/// Powers on all relay outlets. This method is abstract and must be implemented by derived classes to provide specific functionality for powering on all relay outlets.
/// </summary>
public abstract void PowerOnAll();
/// <summary>
/// Powers off all relay outlets. This method is abstract and must be implemented by derived classes to provide specific functionality for powering off all relay outlets.
/// </summary>
public abstract void PowerOffAll();
/// <summary>
/// Reboots a specific relay outlet. This method is abstract and must be implemented by derived classes to provide specific functionality for rebooting a relay outlet with the specified outlet ID.
/// </summary>
/// <param name="outletId"></param>
public abstract void RebootOutlet(int outletId);
/// <summary>
/// Reboots all relay outlets. This method is abstract and must be implemented by derived classes to provide specific functionality for rebooting all relay outlets.
/// </summary>
public abstract void Reboot();
}
@@ -2,18 +2,61 @@ using adas_core.Domain.Enums;
namespace adas_core.module.Relays.Models;
/// <summary>
/// Represents the configuration settings for power outlets in a relay system.
/// This class includes properties to enable or disable power off and reset functionalities,
/// as well as details about the outlet such as its ID, name, type, and whether to show the power outlet in the user interface.
/// </summary>
public class PowerOutletsConfig
{
/// <summary>
/// Indicates whether the power off functionality is enabled for the outlet.
/// If true, the outlet can be powered off remotely.
/// If false, the outlet will remain powered on and cannot be turned off remotely.
/// </summary>
public bool PowerOffEnabled = false;
/// <summary>
/// Indicates whether the reset functionality is enabled for the outlet.
/// </summary>
public bool ResetEnabled = true;
/// <summary>
/// The unique identifier for the power outlet configuration.
/// This ID is used to distinguish between different outlet configurations within the relay system.
/// </summary>
public int Id { get; set; }
/// <summary>
/// The name of the power outlet. This is a user-friendly name that can be displayed in the user interface to identify the outlet.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// The outlet number or identifier that corresponds to the physical outlet in the relay system.
/// </summary>
public int Outlet { get; set; }
/// <summary>
/// The type of the outlet, which can be used to specify the kind of device or functionality associated with the outlet.
/// </summary>
public string Type { get; set; } = RelayEnum.OutletType.Pdu.ToString();
/// <summary>
/// The type of icon to be displayed for the outlet in the user interface.
/// This can be used to visually represent the outlet based on its type or functionality.
/// </summary>
public string? IconType { get; set; }
/// <summary>
/// Indicates whether the power outlet should be shown in the user interface.
/// </summary>
public bool ShowPowerOutlet { get; set; } = false;
/// <summary>
/// String representation of the PowerOutletsConfig object, including its ID, name, outlet number, and type.
/// </summary>
/// <returns>A string representation of the PowerOutletsConfig object.</returns>
public override string ToString()
{
return "PowerOutletsConfig id" + Id +
@@ -1,8 +1,23 @@
namespace adas_core.module.Relays.Models;
/// <summary>
/// Represents the settings for a relay device, including caching behavior, refresh intervals, and data source URLs.
/// These settings allow for flexible configuration of the relay's operation, enabling it to optimize performance and ensure up-to-date information based on the specified parameters.
/// </summary>
public class RelaySettings
{
/// <summary>
/// Indicates whether the relay should cache the data it retrieves. If true, the relay will store the data locally to improve performance and reduce the number of requests to the source. If false, the relay will fetch fresh data from the source every time it is requested.
/// </summary>
public bool Cache { get; set; }
/// <summary>
/// The time interval, in seconds, at which the relay should refresh its cached data. If null, the relay will use a default refresh interval.
/// </summary>
public int? RefreshTime { get; set; }
/// <summary>
/// The URL from which the relay should retrieve data. This can be an API endpoint or a recording URL. If null, the relay will use a default source.
/// </summary>
public string? RecordingOrApiUrl { get; set; }
}
+222
View File
@@ -0,0 +1,222 @@
# 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<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
```
[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`.
---
<p align="center">
Back to <a href="../README.md">adas-core Root README</a>
</p>
@@ -3,8 +3,18 @@ using System.Text;
namespace adas_core.module.Relays.Utils;
/// <summary>
/// Utility class for handling HTTP-related operations, such as adding basic authentication headers to HTTP requests.
/// </summary>
public static class HttpUtils
{
/// <summary>
/// Adds basic authentication to the provided HttpRequestHeaders.
/// </summary>
/// <param name="username">The username for basic authentication.</param>
/// <param name="password">The password for basic authentication.</param>
/// <param name="headers">The HttpRequestHeaders to which the authentication will be added.</param>
/// <returns>The HttpRequestHeaders with the added basic authentication.</returns>
public static HttpRequestHeaders AddBasicAuth(string username, string password, HttpRequestHeaders headers)
{
var auth = Encoding.ASCII.GetBytes($"{username}:{password}");