using System.Collections.Concurrent; using System.Net; using System.Text; using System.Web; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Exceptions; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Responses; using adas_core.Infrastructure.Utils; using adas_core.module.Relays.Devices; using adas_core.module.Relays.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Quartz.Util; namespace adas_core.Infrastructure.Services; public class RelayService : IRelayService { private readonly List _devices = []; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly IPointOfCareService _pointOfCareService; private readonly RelaySettings _relaySettings; private readonly IRelayRepository _relayRepository; private readonly ConcurrentDictionary, RelayEnum.Status> _relayWithStatus = new(); private readonly string _url; private UriBuilder? _builder; public RelayService( ILogger logger, IHttpClientFactory httpClientFactory, IOptions relaySettings, IPointOfCareService pointOfCareService, IRelayRepository relayRepository) { _httpClientFactory = httpClientFactory; _pointOfCareService = pointOfCareService; _relayRepository = relayRepository; _logger = logger; _relaySettings = relaySettings.Value; _url = _relaySettings.RecordingOrApiUrl ?? string.Empty; Task.Run(async () => await InitRelayWithStatus()); } public async Task CheckRelayStatus(Relay relay) { var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber); try { if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { if (_relayWithStatus.TryGetValue(cacheKey, out var status) && status != RelayEnum.Status.NotInitialized) return status; _logger.LogDebug("Relay status not found in cache: {Dct}", DictionaryToString(_relayWithStatus)); } RelayDevice? relayDevice = null; try { relayDevice = GetRelayDevice(relay); } catch (Exception e) { _logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace); } if (relayDevice == null && !string.IsNullOrEmpty(_url)) return await GetRelayStatusByOr(relay); if (relayDevice != null) return relayDevice.GetStatusRelay(relay.RelayNumber); } catch (Exception e) { _logger.LogError("Error CheckRelayStatus relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message, e.StackTrace); lock (_relayWithStatus) { _relayWithStatus[cacheKey] = RelayEnum.Status.Unknown; } } return RelayEnum.Status.Unknown; } public async Task CheckRelayStatus(ObjectId relayId) { var relay = await _relayRepository.GetById(relayId); if(relay == null) return RelayEnum.Status.Unknown; return await CheckRelayStatus(relay); } public async Task PowerOff(Relay relay) { var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber); if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { if (_relayWithStatus.Any() && _relayWithStatus[cacheKey] == RelayEnum.Status.Off) return; } RelayDevice? relayDevice = null; try { relayDevice = GetRelayDevice(relay); } catch (Exception e) { _logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace); } if (relayDevice == null && !string.IsNullOrEmpty(_url)) { _builder = new UriBuilder(_url) { Path = $"Relay/{relay.RelayNumber}/powerOff" }; _logger.LogDebug("Send PowerOff relay {Relay}", relay); await PowerRelay(relay, _builder); } relayDevice?.PowerOffRelay(relay.RelayNumber); if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { _relayWithStatus[cacheKey] = RelayEnum.Status.Off; _logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus)); } } public async Task PowerOn(Relay relay) { if (string.IsNullOrEmpty(relay.Driver) || string.IsNullOrEmpty(relay.Ip)) return; var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber); if (_relaySettings.Cache && relay.Cache) if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { if (_relayWithStatus.Any() && _relayWithStatus[cacheKey] == RelayEnum.Status.On) return; } RelayDevice? relayDevice = null; try { relayDevice = GetRelayDevice(relay); } catch (Exception e) { _logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace); } if (relayDevice == null && !string.IsNullOrEmpty(_url)) { _builder = new UriBuilder(_url) { Path = $"Relay/{relay.RelayNumber}/powerOn" }; _logger.LogDebug("Send PowerOn relay {Relay}", relay); await PowerRelay(relay, _builder); } relayDevice?.PowerOnRelay(relay.RelayNumber); if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { _relayWithStatus[cacheKey] = RelayEnum.Status.On; _logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus)); } } public async Task SetManualRelay(RelayEnum.Status status, ObjectId pocId, RelayEnum.Type type) { try { var poc = await _pointOfCareService.FindById(pocId); if (poc is { Configuration.RelayIdList: not null }) { var relay = _relayRepository.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault(); if (relay != null) relay.ManualRelayStatus = status; await _pointOfCareService.UpdateRelayConfig(poc); _logger.LogDebug("SetManualRelay: {Status}, pocId: {Location}, bed: {Bed}", status, poc.Id, poc.Bed); } } catch (Exception ex) { _logger.LogError("Error settings manual relay. Exception: {ex}", ex); throw; } } public Task GetById(ObjectId relay) { return _relayRepository.GetById(relay); } public List GetRelayInList(List? relayList) { if(relayList == null) return new List(); return _relayRepository.GetRelayInList(relayList); } public List GetRelayByTypeInList(List? configurationRelayList, RelayEnum.Type type) { if(configurationRelayList == null) return new List(); return _relayRepository.GetRelayByTypeInList(configurationRelayList, type); } public async Task> GetPaginatedRelays(PaginationFilter filter) { var usedRelayIds = await _pointOfCareService.FindAllIdRelaysInUse(); var fluentQuery = _relayRepository.GetPaginatedRelays(filter); if (filter.FilteredRequest?.InUse != null) { bool filterInUse = filter.FilteredRequest.InUse.Value; var filterBuilder = Builders.Filter; var idFilter = filterInUse ? filterBuilder.In(c => c.Id, usedRelayIds) : filterBuilder.Not(filterBuilder.In(c => c.Id, usedRelayIds)); fluentQuery.Filter = filterBuilder.And(fluentQuery.Filter, idFilter); } var count = await fluentQuery.CountDocumentsAsync(); var data = await fluentQuery .Skip((filter.PageNumber - 1) * filter.PageSize) .Limit(filter.PageSize) .ToListAsync(); if (data == null) return new PaginationResponse([], filter.PageNumber, filter.PageSize, count); foreach (var camera in data) { if (camera == null) continue; bool isInUse = usedRelayIds.Contains(camera.Id); // Asignación mediante reflexión para el private set camera.GetType().GetProperty(nameof(Relay.InUse)) ?.SetValue(camera, isInUse); } return new PaginationResponse(data, filter.PageNumber, filter.PageSize, count); } public async Task InsertRelay(Relay request) { var relayExist = await _relayRepository.GetByName(request.RelayName); if(relayExist != null) throw new Exception($"Relay with name {request.RelayName} already exists"); return await _relayRepository.InsertOneRelayAsync(request); } public Task UpdateRelayById(ObjectId objectId, Relay relay) { return _relayRepository.UpdateRelayAsync(objectId, relay); } public event EventHandler>? RelayStatusChanged; private async Task InitRelayWithStatus() { try { var pocConfigList = await _pointOfCareService.GetAllConfigs(); var relayTasks = new List(); pocConfigList.ForEach(poc => { if (poc.Configuration?.RelayList == null) return; relayTasks.AddRange(from relayConf in poc.Configuration.RelayList let statusTask = CheckRelayStatus(relayConf) select statusTask.ContinueWith(task => { if (task.IsCompletedSuccessfully) { var cackeKey = Tuple.Create(relayConf.Ip, relayConf.Port, relayConf.RelayNumber); if (!relayConf.Cache || !relayConf.Cache) return; lock (_relayWithStatus) { _relayWithStatus[cackeKey] = task.Result; } } else if (task.IsFaulted) { // Manejar la excepción si la llamada asincrónica falla _logger.LogError("Error al obtener el estado del relé {RelayParsedIp}: {TaskException}", relayConf.Ip, task.Exception.Message); } })); }); await Task.WhenAll(relayTasks); lock (_relayWithStatus) { _logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus)); } } catch (Exception ex) { Console.WriteLine("Exception setting relay with status: " + ex.Message); } } private RelayDevice? GetRelayDevice(Relay relay) { if(relay.Ip.IsNullOrWhiteSpace() || relay.Port == 0) return null; var device = _devices.FirstOrDefault(d => d.Relay.Ip == relay.Ip && d.Relay.Port == relay.Port); if (device != null) return device; var type = TypesUtils.GetDriver("Relay", relay.Driver ?? string.Empty); var constructor = type.GetConstructor([typeof(Relay), typeof(RelaySettings)]); if (constructor == null) throw new AdasException("Constructor not found for relay device"); var relayDevice = (RelayDevice)constructor.Invoke([relay, _relaySettings]); if (relayDevice == null) throw new AdasException("Relay device not found"); relayDevice.RelayStatusChanged += (_, outletId) => { var cacheKey = Tuple.Create(relay.Ip, relay.Port, outletId); var value = relayDevice.GetStatusRelay(outletId); lock (_relayWithStatus) { _relayWithStatus[cacheKey] = value; } _logger.LogDebug("Relay status changed for {Key} with value {Value}", cacheKey, value); RelayStatusChanged?.Invoke(this, Tuple.Create(relayDevice, outletId)); }; _devices.Add(relayDevice); return relayDevice; } private async Task PowerRelay(Relay relay, UriBuilder builder) { var query = HttpUtility.ParseQueryString(builder.Query); query["driver"] = relay.Driver; query["host"] = relay.Ip; query["port"] = relay.Port.ToString(); query["relayName"] = relay.RelayName; query["relays"] = relay.Total.ToString(); query["username"] = relay.Username; query["password"] = relay.Password; builder.Query = query.ToString(); HttpRequestMessage request = new() { RequestUri = builder.Uri, Method = HttpMethod.Post }; try { using var client = _httpClientFactory.CreateClient(); var httpResponse = await client.SendAsync(request); if (!httpResponse.StatusCode.Equals(HttpStatusCode.OK)) _logger.LogDebug("Send PowerRelay relay {Relay}", relay); } catch (Exception e) { _logger.LogError("Error PowerRelay relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message, e.StackTrace); } } private static string DictionaryToString(ConcurrentDictionary, RelayEnum.Status> dictionary) { var builder = new StringBuilder(); foreach (var pair in dictionary) builder.AppendLine($"Device ID: {pair.Key.Item1}, Port: {pair.Key.Item2}, Status: {pair.Value}"); return builder.ToString(); } private async Task GetRelayStatusByOr(Relay relay) { var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber); _builder = new UriBuilder(_url) { Path = $"Relay/{relay.RelayNumber}/status" }; var query = HttpUtility.ParseQueryString(_builder.Query); query["driver"] = relay.Driver; query["host"] = relay.Ip; query["port"] = relay.Port.ToString(); query["relayName"] = relay.RelayName; query["relays"] = relay.Total.ToString(); query["username"] = relay.Username; query["password"] = relay.Password; query["mode"] = relay.Mode.ToString(); query["refreshTime"] = "0"; _builder.Query = query.ToString(); HttpRequestMessage request = new() { RequestUri = _builder.Uri, Method = HttpMethod.Get }; using var client = _httpClientFactory.CreateClient(); client.Timeout = new TimeSpan(0, 0, 3); var httpResponse = await client.SendAsync(request); if (httpResponse.StatusCode.Equals(HttpStatusCode.OK)) { var responseContent = httpResponse.Content; var response = await responseContent.ReadAsStringAsync(); response = response.Replace("\"", ""); var relayStatusParsed = (RelayEnum.Status)Enum.Parse(typeof(RelayEnum.Status), response); if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { _relayWithStatus[cacheKey] = relayStatusParsed; _logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus)); } return relayStatusParsed; } if (_relaySettings.Cache && relay.Cache) lock (_relayWithStatus) { _relayWithStatus[cacheKey] = RelayEnum.Status.Unknown; _logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus)); } return RelayEnum.Status.Unknown; } }