Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -0,0 +1,62 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
namespace adas_core.module.Relays.Devices;
internal class FakeRelay(Relay relay, RelaySettings relaySettings) : RelayDevice(relay, relaySettings)
{
public override bool CheckDevice()
{
return true;
}
public override bool GetStatusRelay()
{
return true;
}
public override void PowerOffRelay(int outletId)
{
}
public override void PowerOnRelay(int outletId)
{
}
public override void PowerOffAll()
{
}
public override void PowerOnAll()
{
}
public override RelayEnum.Status GetStatusRelay(int outletId)
{
const string status = "00000000";
if (status.Length == 0) return RelayEnum.Status.On;
return status.Substring(outletId - 1, 1) == "0" ? RelayEnum.Status.Off : RelayEnum.Status.On;
}
public override void RebootOutlet(int outletId)
{
}
public override void Reboot()
{
PowerOffAll();
if (Relay.RebootDelay is > 0) Thread.Sleep(Relay.RebootDelay!.Value);
PowerOnAll();
}
public override Task<bool> GetStatusRelayAsync()
{
return Task.FromResult(true);
}
public override void CheckStatus()
{
}
}
@@ -0,0 +1,209 @@
using System.Net.Sockets;
using System.Text;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
using Serilog;
namespace adas_core.module.Relays.Devices;
public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
: RelayDevice(relay, relaySettings)
{
private UdpClient? _udpClient;
public bool DeviceStatus { get; set; }
public override bool CheckDevice()
{
CheckStatus();
var result = GetStatusRelay(1);
return result != RelayEnum.Status.Unknown;
}
public override bool GetStatusRelay()
{
var status = Send("FF0000");
//while (!status.IsCompleted)
//{
//}
//return !string.IsNullOrEmpty(status.Result);
return !string.IsNullOrEmpty(status);
}
public override RelayEnum.Status GetStatusRelay(int outletId)
{
var status = Send("FF0000");
//while (!status.IsCompleted)
//{
//}
//if (!string.IsNullOrEmpty(status.Result))
//{
// if (status.Result.Substring(outlet.outlet - 1, 1) == "0")
// {
// return true;
// }
//}
if (!string.IsNullOrEmpty(status))
try
{
if (status.Substring(outletId - 1, 1) == "0")
//return RelayStatus.Off;
return Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn
? RelayEnum.Status.On
: RelayEnum.Status.Off;
}
catch (ArgumentOutOfRangeException e)
{
Log.Error(
"There isn't an outlet {Outlet} in the rele {Ip}. Please check the configuration.\nException: {Ex}",
outletId, Relay.Ip, e.Message);
}
//return RelayStatus.On;
return Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn ? RelayEnum.Status.Off : RelayEnum.Status.On;
}
public override void PowerOffRelay(int outletId)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) OpenRelay(outletId);
else CloseRelay(outletId);
}
public void OpenRelay(int outletId)
{
//Activa el relé por lo que corta la corriente del dispositivo conectado
Send("FF0" + outletId + "01");
}
public override void PowerOnRelay(int outletId)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) CloseRelay(outletId);
else OpenRelay(outletId);
}
public void CloseRelay(int outletId)
{
//Desactiva el relé por lo que deja pasar la corriente del dispositivo conectado
Send("FF0" + outletId + "00");
}
public override void PowerOffAll()
{
//Activa el relé por lo que corta la corriente de todos los dispositivo conectados
Send("FFE0FF");
}
public override void PowerOnAll()
{
//Desactiva el relé por lo que deja pasar la corriente a todos dispositivo conectados
Send("FFE000");
}
public override void CheckStatus()
{
DeviceStatus = GetStatusRelay();
}
public override void RebootOutlet(int outletId)
{
//Reinicia el relé del dispositivo conectado
PowerOnOffRelay(outletId, 2000);
}
public override void Reboot()
{
PowerOffAll();
if (Relay.RebootDelay is > 0) Thread.Sleep(Relay.RebootDelay!.Value);
PowerOnAll();
}
public override Task<bool> GetStatusRelayAsync()
{
throw new NotImplementedException();
}
protected virtual void Connect()
{
if (_udpClient == null)
try
{
if (!string.IsNullOrEmpty(Relay.Ip)) _udpClient = new UdpClient(Relay.Ip, Relay.Port);
//var ipaddress = IPAddress.Parse(RelayConfig.Ip);
//sourceipendpoint = new IPEndPoint(ipaddress, port); //source port IPEndpoint
}
catch (Exception e)
{
Log.Debug("UdpClient not connect {Host}: {Port} Ex: {Ex}", Relay.Ip, Relay.Port, e.Message);
try
{
if (_udpClient != null)
{
_udpClient.Close();
_udpClient = null;
}
}
catch
{
// ignored
}
}
else
try
{
_udpClient.Close();
_udpClient = null;
}
catch
{
// ignored
}
}
protected virtual string Send(string datagrama)
{
if (_udpClient == null) Connect();
if (_udpClient == null) return string.Empty;
var command = Encoding.ASCII.GetBytes(datagrama.ToUpper());
_udpClient.Send(command, command.Length);
var returnData = string.Empty;
try
{
var t = Task.Run(() =>
{
var taskReceiveBytes = _udpClient.ReceiveAsync();
returnData = Encoding.ASCII.GetString(taskReceiveBytes.Result.Buffer);
});
var ts = TimeSpan.FromSeconds(5);
if (!t.Wait(ts))
{
if (!t.IsCompletedSuccessfully) returnData = string.Empty;
if (t.Exception != null) throw t.Exception;
}
if (t.Status == TaskStatus.RanToCompletion) t.Dispose();
}
catch (Exception ex)
{
Log.Debug("UdpClient not connect {Host}: {Port} Ex: {Ex}", Relay.Ip, Relay.Port, ex.Message);
returnData = string.Empty;
}
if (returnData == string.Empty)
Log.Debug("UdpClient not connect {Host}: {Port}", Relay.Ip, Relay.Port);
return returnData;
}
}
@@ -0,0 +1,187 @@
using System.Net;
using System.Xml;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
using adas_core.module.Relays.Utils;
using Serilog;
namespace adas_core.module.Relays.Devices;
public class KmTronicV2Relay : RelayDevice
{
public KmTronicV2Relay(Relay relay, RelaySettings relaySettings) : base(relay, relaySettings)
{
CheckDevice();
}
public sealed override bool CheckDevice()
{
CheckStatus();
var result = GetStatusRelay(1);
return result != RelayEnum.Status.Unknown;
}
public override void CheckStatus()
{
try
{
var httpResponse = SendRequest("status.xml", HttpMethod.Get);
if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
{
var responseContent = httpResponse.Content;
var responseString = responseContent.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(responseString))
{
var response = new XmlDocument();
response.LoadXml(responseString);
foreach (XmlNode node in response.SelectNodes("//response")!)
foreach (XmlNode childNode in node.ChildNodes)
{
if (!childNode.Name.StartsWith("relay") || string.IsNullOrEmpty(childNode.InnerText)) continue;
var id = int.Parse(childNode.Name[5..]);
if (id <= 0 || id > Relay.Total) continue;
var value = int.Parse(childNode.InnerText);
var relayStatus = value.Equals(0) ? RelayEnum.Status.Off :
value.Equals(1) ? RelayEnum.Status.On : RelayEnum.Status.Unknown;
Tuple.Create(Relay.Ip, Relay.Port, id);
SetStatusRelay(id, relayStatus);
}
}
Log.Debug(responseContent.ReadAsStringAsync().Result);
}
else
{
Log.Debug("Status code = " + httpResponse.StatusCode);
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
Enumerable.Range(1, Relay.Total).ToList().ForEach(outletId =>
{
SetStatusRelay(outletId, RelayEnum.Status.Unknown);
});
}
}
public override bool GetStatusRelay()
{
return GetStatusRelay(1) == RelayEnum.Status.On;
}
public override Task<bool> GetStatusRelayAsync()
{
var result = GetStatusRelay();
return Task.FromResult(result);
}
public override void PowerOnRelay(int outletId)
{
PowerRelay(outletId, RelayEnum.Status.On);
}
public override void PowerOffRelay(int outletId)
{
PowerRelay(outletId, RelayEnum.Status.Off);
}
private Uri GetUri(string path, string? query = null)
{
var uriBuilder = new UriBuilder
{ Scheme = "http", Host = Relay.Ip, Port = Relay.Port, Path = path };
if (!string.IsNullOrEmpty(query)) uriBuilder.Query = query;
return uriBuilder.Uri;
}
private HttpRequestMessage GetRequest(string path, HttpMethod method, string? query = null)
{
var uri = GetUri(path, query);
var request = new HttpRequestMessage
{
RequestUri = uri,
Method = method
};
if (!string.IsNullOrEmpty(Relay.Username) && !string.IsNullOrEmpty(Relay.Password))
HttpUtils.AddBasicAuth(Relay.Username, Relay.Password, request.Headers);
return request;
}
private HttpResponseMessage SendRequest(string path, HttpMethod method, string? query = null)
{
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(15);
try
{
return client.Send(GetRequest(path, method, query));
}
catch (Exception e)
{
Log.Warning("Error sending request to kmtronic ip {IP} port {Port}: {Message}", Relay.Ip,
Relay.Port, e.Message);
return new HttpResponseMessage(HttpStatusCode.Gone);
}
}
private void PowerRelay(int outletId, RelayEnum.Status toState, bool checkStatus = true)
{
if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn)
toState = toState == RelayEnum.Status.Off ? RelayEnum.Status.On : RelayEnum.Status.Off;
var status = GetStatusRelay(outletId);
if (status == toState) return;
//No entiendo esto así, siempre enciende/apaga el mismo uriBuilder.Query = $"relay={outlet.outlet}";
//me quedo con el nombre y solo con el número del rele que es la llamada que el hace realmente desde la web
//No uso una regex porque es mucho más lento
try
{
SendRequest("relays.cgi", HttpMethod.Post, $"relay={outletId}");
}
catch (Exception ex)
{
Log.Debug(ex.Message);
}
finally
{
if (checkStatus) CheckStatus();
}
}
public override void PowerOnAll()
{
PowerAll(RelayEnum.Status.On);
}
public override void PowerOffAll()
{
PowerAll(RelayEnum.Status.Off);
}
private void PowerAll(RelayEnum.Status toState)
{
Enumerable.Range(1, Relay.Total).ToList().ForEach(outletId => { PowerRelay(outletId, toState, false); });
CheckStatus();
}
public override void Reboot()
{
PowerOffAll();
if (Relay.RebootDelay is > 0) Thread.Sleep(Relay.RebootDelay!.Value);
PowerOnAll();
}
public override void RebootOutlet(int outletId)
{
PowerOffRelay(outletId);
Thread.Sleep(30000);
PowerOnRelay(outletId);
}
}
@@ -0,0 +1,86 @@
using System.Collections.Concurrent;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
using Timer = System.Timers.Timer;
namespace adas_core.module.Relays.Devices;
public abstract class RelayDevice
{
private readonly ConcurrentDictionary<int, RelayEnum.Status> _relaysStatus = new();
private readonly Timer? _timer;
public readonly Relay Relay;
public readonly RelaySettings RelaySettings;
//string host, int port, int relays, int? refreshTime, EventHandler refreshEvent
protected RelayDevice(Relay relay, RelaySettings relaySettings)
{
Relay = relay;
RelaySettings = relaySettings;
if (relaySettings.RefreshTime is > 0)
{
_timer = new Timer();
_timer.Interval = relaySettings.RefreshTime.Value * 1000;
_timer.Elapsed += delegate
{
_timer.Stop();
CheckStatus();
_timer.Start();
};
_timer.Start();
}
}
public event EventHandler<int>? RelayStatusChanged;
public abstract bool CheckDevice();
public abstract void CheckStatus();
public abstract bool GetStatusRelay();
public abstract Task<bool> GetStatusRelayAsync();
public virtual RelayEnum.Status GetStatusRelay(int outletId)
{
lock (_relaysStatus)
{
return _relaysStatus.GetValueOrDefault(outletId, RelayEnum.Status.Unknown);
}
}
public virtual void SetStatusRelay(int outletId, RelayEnum.Status status)
{
lock (_relaysStatus)
{
if (!_relaysStatus.ContainsKey(outletId) || _relaysStatus[outletId] != status)
RelayStatusChanged?.Invoke(this, outletId);
_relaysStatus[outletId] = status;
}
}
public virtual void PowerOnOffRelay(int outletId, int milliseconds)
{
Task.Run(() =>
{
PowerOffRelay(outletId);
Task.Delay(milliseconds);
PowerOnRelay(outletId);
});
}
public virtual void Refresh()
{
}
public abstract void PowerOnRelay(int outletId);
public abstract void PowerOffRelay(int outletId);
public abstract void PowerOnAll();
public abstract void PowerOffAll();
public abstract void RebootOutlet(int outletId);
public abstract void Reboot();
}
@@ -0,0 +1,24 @@
using adas_core.Domain.Enums;
namespace adas_core.module.Relays.Models;
public class PowerOutletsConfig
{
public bool PowerOffEnabled = false;
public bool ResetEnabled = true;
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Outlet { get; set; }
public string Type { get; set; } = RelayEnum.OutletType.Pdu.ToString();
public string? IconType { get; set; }
public bool ShowPowerOutlet { get; set; } = false;
public override string ToString()
{
return "PowerOutletsConfig id" + Id +
"name=" + Name +
" outlet=" + Outlet +
" type=" + Type;
}
}
@@ -0,0 +1,8 @@
namespace adas_core.module.Relays.Models;
public class RelaySettings
{
public bool Cache { get; set; }
public int? RefreshTime { get; set; }
public string? RecordingOrApiUrl { get; set; }
}
@@ -0,0 +1,15 @@
using System.Net.Http.Headers;
using System.Text;
namespace adas_core.module.Relays.Utils;
public static class HttpUtils
{
public static HttpRequestHeaders AddBasicAuth(string username, string password, HttpRequestHeaders headers)
{
var auth = Encoding.ASCII.GetBytes($"{username}:{password}");
var clientAuthorizationHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(auth));
headers.Add("Authorization", clientAuthorizationHeader.ToString());
return headers;
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>adas_core.module.Relays</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AuditLogs" Version="1.0.59" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\adas-core.Domain\adas-core.Domain.csproj" />
</ItemGroup>
</Project>