using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.GroupedObservations; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Utils; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using Serilog; using Patient = adas_core.Domain.Models.MongoModels.Patient; namespace adas_core.Application.Services; public class AlarmService : IAlarmService { private readonly IAlarmRepository _alarmRepository; private readonly IOptions _apiSettings; private readonly ILocalAuditService _auditService; private readonly Lazy _calculatedObservationsService; private readonly IClientMessageService _clientMessageService; private readonly IConfigObservationService _configObservationService; private readonly IHttpContextAccessor _httpContextAccessor; private readonly Lazy _lightBeaconService; private readonly ILogger _logger; private readonly Lazy _observationService; private readonly IPatientService _patientService; private readonly IPointOfCareService _pocService; private readonly Lazy _recordingService; private readonly List _relayAlarmList = []; private readonly Lazy _relayService; private readonly SemaphoreSlim _semaphore = new(1, 1); // Semáforo para evitar la ejecución simultánea del temporizador private readonly ISubscribersService _subscribersService; private readonly IUnitService _unitService; //private readonly string _url; private List _beaconAlarmList = []; private TimeSpan _interval; public AlarmService(IAlarmRepository alarmRepository, ILogger logger, IPatientService patientService, IConfigObservationService configObservationService, Lazy observationService, IClientMessageService clientMessageService, ISubscribersService subscribersService, Lazy calculatedObservationsService, Lazy lightBeaconService, Lazy recordingService, Lazy relayService, IOptions apiSettings, IUnitService unitService, IPointOfCareService pocService, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, bool startTimer = true ) { _alarmRepository = alarmRepository; _logger = logger; _patientService = patientService; _configObservationService = configObservationService; _observationService = observationService; _clientMessageService = clientMessageService; _subscribersService = subscribersService; _calculatedObservationsService = calculatedObservationsService; _lightBeaconService = lightBeaconService; _recordingService = recordingService; _relayService = relayService; _apiSettings = apiSettings; _unitService = unitService; _pocService = pocService; _httpContextAccessor = httpContextAccessor; _auditService = auditService; if (apiSettings == null) throw new Exception("ApiSettings must be defined"); if (startTimer) StartTimer(); } public async Task SaveRequest(ApiRequest apiRequest) { if ( string.IsNullOrEmpty(apiRequest.PatientNumber) && string.IsNullOrEmpty(apiRequest.Location?.UnitName) ) { _logger.LogDebug("Patient and PointOfCare are nulls"); throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestMissingParameters); } var patient = await _patientService.FindPatientByApiRequest(apiRequest); if (patient == null) { // NO PATIENTS OR LOCATIONS WERE FOUND _logger.LogWarning( "Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring", apiRequest.PatientNumber, apiRequest.Location); return; } var unitConfig = await _unitService.FindById(patient.UnitId); if (!Hl7Utils.ManageAutoAdt(unitConfig, null, _logger, "ORU Alarm")) return; switch (apiRequest.Type) { /* * ORU_R40 - Unsolicited transmission of an alert observation message */ case "ORU_R40": // UNSOLICITED ALERT OBSERVATION // OBSERVATIONS if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null) apiRequest.Observations = [apiRequest.Observation]; //var obrcode = apiRequest.ObservationData?.Code ?? ""; if (!apiRequest.Alarms.IsNullOrEmpty()) await ProcessAlarmObservations(apiRequest.Alarms ?? [], apiRequest.Observations ?? [], patient, apiRequest.ObservationData?.Time ?? apiRequest.MessageTime, apiRequest.ObservationData); break; default: _logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations", apiRequest.Type); throw new ApiRequestException("ApiRequest type " + apiRequest.Type + " is not valid for Observations"); } } public async Task SaveRequestAsync(ApiRequest apiRequest) { await SaveRequest(apiRequest); } public async Task MapObservation(PatientObservationAlarm obs, bool onlyByName = false) { try { _logger.LogTrace("Mapping config Observation obs: {obs} onlyByName: {onlyByName}", obs, onlyByName); var obs2 = await _configObservationService.Map(obs, onlyByName); if (obs2 == null) { _logger.LogTrace("Mapping obs2 {obs}: Ignored", obs); return null; } _logger.LogTrace("Mapping _configObservationService.Map obs2: {obs2}", obs2); var obs3 = await _calculatedObservationsService.Value.Map(obs2, onlyByName); if (obs3 == null) { _logger.LogTrace("Mapping obs3 {obs2}: Ignored", obs2); return null; } return obs3; } catch (Exception ex) { _logger.LogError(ex, "Error Mapping Observation, Ignoring Observation: {obs} Exception:{ex}", obs, ex.Message); return null; } } public async Task> FindLastObservationsByField(ObjectId patientId, List? filterObservations = null) { var result = await _alarmRepository.AggregatedPatientLastObservationsByField(patientId, filterObservations); return result; } public async Task> FindLastValuesNotExpired(ObjectId patientId, List filterObservations, List configAlarm) { var result = await _alarmRepository.AggregatedPatientNotExpiredObservationsByField(patientId, filterObservations, configAlarm); return result; } public async Task MapObservationsByName(PatientObservationAlarm obs) { return await _configObservationService.Map(obs, true); } public async Task ProcessAlarmObservations(List alarmObservations, List observations, Patient patient, DateTime messageTime, ObservationData? observationData = null) { _logger.LogDebug( "Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING {observationsCount} OBSERVATIONS", patient.Id, patient.UnitId, patient.PointOfCareId, messageTime, alarmObservations.Count); ParentDataClass? parentData = null; if (observationData != null) parentData = new ParentDataClass { Code = observationData.Code, CodingSystem = observationData.CodingSystem, Name = observationData.Text }; var listToInsert = new List(); foreach (var obs in alarmObservations) { obs.ParentData = parentData; obs.MessageTime = messageTime; obs.PatientId = patient.Id; obs.Patient = patient; obs.Id = ObjectId.GenerateNewId(); var intObsTime = new DateTimeOffset(obs.Time).ToUnixTimeSeconds(); if (obs.Time == DateTime.MinValue || intObsTime <= 10) obs.Time = DateTime.UtcNow; var intMessageTime = new DateTimeOffset(obs.MessageTime).ToUnixTimeSeconds(); if (obs.MessageTime == DateTime.MinValue || intMessageTime <= 10) obs.MessageTime = DateTime.UtcNow; if (obs.Value.ToString() == "System.Object") { obs.Value = obs.Event?.ToString()??string.Empty; } _logger.LogDebug( "Patient: {patientId} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING ALARM OBSERAVTION {obs}", patient.Id, patient.PointOfCare, patient.Bed, messageTime, obs); listToInsert.Add(obs); } //listToInsert.ForEach(async obs => await InsertObservation(obs)); foreach (var alarmToInsert in listToInsert) { var alarmData = observations.FirstOrDefault(obs => obs.Value.ToString() == alarmToInsert.Value.ToString()); if (alarmData != null) { alarmToInsert.Code = alarmData.Code; alarmToInsert.Name = alarmData.Code; alarmToInsert.CodingSystem = alarmData.CodingSystem; } else { alarmToInsert.CodingSystem = parentData?.CodingSystem?? "MDIL-ALARM"; alarmToInsert.Code = alarmToInsert.Priority.ToString(); alarmToInsert.Name = alarmToInsert.Priority switch { AlarmEnum.ObservationAlarmPriority.Ph => "RedAlarm_Ph", AlarmEnum.ObservationAlarmPriority.Pm => "YellowAlarm_Pm", AlarmEnum.ObservationAlarmPriority.Pl => "BlueAlarm_Pl", _ => "", }; } if (!alarmToInsert.Sources.IsNullOrEmpty()) { var apiRequestObs = new ApiRequest { Type = "ORU_R01", MessageTime = messageTime, ObservationData = observationData }; var obsToInsert = new List(); alarmToInsert.Sources?.ForEach(async void (c) => { try { var obs = new PatientObservation { Code = c.Code, Name = c.OriginalName, CodingSystem = c.CodeSystem, Units = c.Units, Value = c.Value?.ToString() ?? "No value", Time = alarmToInsert.Time, Result = c.Result }; var obs2 = await _calculatedObservationsService.Value .MapSourceAlarm(obs, alarmToInsert); obsToInsert.Add(obs2); } catch (Exception e) { _logger.LogError( "Error processing source observation {source} for alarm {alarm}. Exception: {ex}", c, alarmToInsert, e); } }); apiRequestObs.Observations = obsToInsert; apiRequestObs.Location = patient.Location; apiRequestObs.Patient = patient.Person; apiRequestObs.PatientNumber = patient.PatientNumber; await _observationService.Value.SaveRequestAsync(apiRequestObs); } await InsertObservation(alarmToInsert); } } private async Task InsertObservation(PatientObservationAlarm obs, bool persistObs = true, bool mapObs = true) { try { var obs2 = obs; //only will be false if the obs comes from the inner refactor job if (mapObs) obs2 = await MapObservation(obs2, onlyByName: true); if (obs2 == null) { _logger.LogDebug("Mapped observation returns null. Ignored {obs}", obs); } else { if (obs2.Persist.HasValue && !obs2.Persist.Value) persistObs = false; if (persistObs) { _logger.LogDebug("Mapped {obs2}", obs2); await _alarmRepository.InsertOneAsync(obs2); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2); } _logger.LogDebug("Inserted {obs2}", obs2); await SendObsBroadcast(obs2); } } catch (Exception ex) { _logger.LogError("Error Inserting observation {obs}. Excepcion; {ex} ", obs, ex); } } private async Task SendObsBroadcast(BasePatientObservation obs) { if (obs.Name == null) return; const OperationType type = OperationType.Alarm; var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId); if (patient == null) { _logger.LogDebug("Not patient on bd to sendOnBroadcastObs: {obspatientid}", obs.PatientId); return; } _logger.LogTrace( "sending obs name: {obsname} to patient id: {patientid}, PointOfCare: {patientpointOfCare} {patientbed}", obs.Name, patient.Id, patient.UnitId, patient.Bed); var subscribers = _subscribersService.GetSubscribers().Where(s => !s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c => c == patient.PointOfCareId )).ToList(); foreach (var subscriber in subscribers) { _logger.LogTrace("sending obs name: {obsname} to subscriber id: {subscriberId}", obs.Name, subscriber.Id); await _clientMessageService.SendAsync(subscriber.Id, type, obs); } } #region activación de alarmas con balizas, relé y grabaciones public async Task CheckObservationAlarm(PatientObservation obs) { //ConfigObservations var configs = await _configObservationService.Get(new PatientObservation { Name = obs.Name, PatientId = obs.PatientId } ); if (configs?.CreateObservation == null) return; var obsValue = obs.Value.ToString() ?? string.Empty; var observationsToCreate = configs.CreateObservation .Where(c => obsValue.ToUpper().Contains(c.RequiredValue?.ToString()?.ToUpper() ?? string.Empty)) .ToList(); foreach (var obsConfig in observationsToCreate) { var create = false; if (obsConfig.Preconditions == null) create = true; else foreach (var preCondition in obsConfig.Preconditions) { if (preCondition.Name == null) continue; var obsWithConditions = await _observationService.Value.FindLastObservations(obs.PatientId, 1, [preCondition.Name]); if (!obsWithConditions.Any()) continue; var foundObs = obsWithConditions.FirstOrDefault(); //Descartamos la observación si ha expirado if (foundObs == null || (obsConfig.Expires.HasValue && foundObs.Time.AddSeconds(obsConfig.Expires.Value) < DateTime.UtcNow)) continue; var foundObsStr = foundObs.Value.ToString(); var requiredValueStr = preCondition.RequiredValue?.ToString(); if (string.IsNullOrEmpty(requiredValueStr) || (foundObsStr != null && foundObsStr.Contains(requiredValueStr))) { create = true; break; } } if (create) { var newObservation = CreateNewObservation(obs, obsConfig, StatusEnum.Type.Alert); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, newObservation); newObservation = await CheckAlarmConfig(newObservation); var obsName = newObservation.Name ?? string.Empty; _ = SendAlarm(newObservation, obsName, null, AlarmEnum.Severity.None, AlarmEnum.Type.Auto); _ = _observationService.Value.InsertObservation(newObservation); } } } private static PatientObservation CreateNewObservation(PatientObservation obs, ConfigObservation config, StatusEnum.Type type) { return new PatientObservation { CodingSystem = config.CodingSystem, Code = config.Code, Name = config.Name, Value = obs.Value, PatientId = obs.PatientId, Time = obs.Time, Alarm = config.Alarm, Status = type }; } private async Task CheckAlarmConfig(PatientObservation pobs) { var configObs = await _configObservationService.Get(new PatientObservation { Name = pobs.Name, PatientId = pobs.PatientId }); pobs.Alarm = configObs?.Alarm ?? null; return pobs; } /// /// Sends a new alarm /// /// Observation to generate the alarm /// Name of the alarm /// Code of the alarm for the recording /// Severity of the alarm for the recording /// /// /// New alarm created public async Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Severity severity, AlarmEnum.Type type) { try { var poc = await _pocService.FindPoCByPatientId(obs.PatientId); switch (obs.Time.Kind) { // Convert obs.Time to UTC if it's not already case DateTimeKind.Local: obs.Time = obs.Time.ToUniversalTime(); break; case DateTimeKind.Unspecified: _logger.LogWarning("obs.Time has unspecified kind. Assuming it to be UTC."); obs.Time = DateTime.SpecifyKind(obs.Time, DateTimeKind.Utc); break; case DateTimeKind.Utc: break; default: throw new ArgumentOutOfRangeException(); } //ConfigObservations var configObs = await _configObservationService.Get(new PatientObservation { Name = obs.Name, PatientId = obs.PatientId } ); if (configObs == null) return; if (configObs is { Alarm.Enabled: true }) { obs.Alarm = configObs.Alarm; var now = DateTime.UtcNow; if (obs.Expired || (obs.Expires.HasValue && obs.Time.AddSeconds(obs.Expires.Value) < now)) return; //En la prioridad de las alarmas 1 máxima prioridad if (configObs.Alarm.Beacon is { Enabled: true }) try { if (obs.Time.AddSeconds(configObs.Alarm.Beacon.EndAfter) >= now) { var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId); if (patient != null) { _logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}", obs.PatientId, configObs.Alarm.Beacon.BeaconColor); if (!_beaconAlarmList.Any(o => o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority)) { _ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient); lock (_beaconAlarmList) { _beaconAlarmList.Add(obs); } } } } else { _logger.LogDebug( "PatientId: {nObsPatientid}.Beacon is Expired. EndAfter {endAfter} Time: {obsTime}", obs.PatientId, configObs.Alarm.Beacon.EndAfter, obs.Time); } } catch (Exception ex) { _logger.LogDebug("Exception sending beacon code for patient {patientId}. Exception: {ex}", obs.PatientId, ex); throw; } if (configObs.Alarm.Recording is { Enabled: true }) try { if (obs.Time.AddSeconds(configObs.Alarm.Recording.EndAfter) >= now) { //if(severity == AlarmSeverity.NONE) severity = configObs.Alarm.Recording.Severity; var strValue = obs.Value.ToString(); if (strValue == null) { _logger.LogError("Observation value to string is null observation:{nObs}", obs); return; } _logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId, obs.Name); if (code == null && Enum.TryParse(configObs.Alarm.Name, out var result)) code = result; _ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, code, severity, strValue, configObs.Alarm.Recording.EndAfter, type); } else { _logger.LogDebug( "PatientId: {nObsPatientid}.Recording is Expired. EndAfter {endAfter} Time: {obsTime}", obs.PatientId, configObs.Alarm.Recording.EndAfter, obs.Time); } } catch (Exception ex) { _logger.LogDebug( "Exception sending Alarm Recording for patient {patientId}. Exception: {ex}", obs.PatientId, ex); throw; } if (configObs.Alarm.OpenDoor is { Enabled: true }) try { if (obs.Time.AddSeconds(configObs.Alarm.OpenDoor.EndAfter) >= now) { _logger.LogDebug("PatientId: {nObsPatientid}. Open door observation {obsName}", obs.PatientId, obs.Name); lock (_relayAlarmList) { if (!_relayAlarmList.Any(o => o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority)) { PointOfCareConfiguration? poCSettings = null; if (poc is { Configuration.RelayIdList: not null }) poCSettings = poc.Configuration; var status = _relayService.Value.GetRelayByTypeInList(poCSettings?.RelayIdList, RelayEnum.Type.Door).FirstOrDefault()?.ManualRelayStatus; if (status is RelayEnum.Status.Off) _ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door); lock (_relayAlarmList) { _relayAlarmList.Add(obs); } } } } else { _logger.LogDebug( "PatientId: {nObsPatientid}.Open door is Expired. EndAfter {endAfter} Time: {obsTime}", obs.PatientId, configObs.Alarm.OpenDoor.EndAfter, obs.Time); } } catch (Exception ex) { _logger.LogDebug("Exception opening door for patient {patientId}. Exception: {ex}", obs.PatientId, ex); throw; } } } catch (Exception ex) { Log.Error("Exception sending alarm: {exMessage}", ex.Message); } } public async Task CalculateAlarmTest(BasePatientObservationValue source, string name) { if (source is not PatientObservation obs) return; //ConfigObservations var configObs = await _configObservationService.Get(source, true); if (configObs is { Alarm.Enabled: true }) { obs.Alarm = configObs.Alarm; string? alarmSeverityStr = null; if (configObs.Alarm.Beacon is { Enabled: true }) { var patient = await _patientService.FindById(obs.PatientId); if (patient == null) return; _logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}", obs.PatientId, configObs.Alarm.Beacon.BeaconColor); _ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient); } if (configObs.Alarm.Recording is { Enabled: true }) { _logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId, obs.Name); if (!string.IsNullOrEmpty(alarmSeverityStr) && Enum.TryParse(alarmSeverityStr, out AlarmEnum.Severity alarmSeverity)) _ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test, alarmSeverity, "test description", configObs.Alarm.Recording.EndAfter); else _ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test, AlarmEnum.Severity.Yellow, "test description", configObs.Alarm.Recording.EndAfter); } if (configObs.Alarm.OpenDoor is { Enabled: true }) { _logger.LogDebug("PatientId: {nObsPatientid}. Open door", obs.PatientId); _ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door); } } } private Task SendBeaconCode(AlarmEnum.BeaconColor color, Patient patient) { if (!patient.PointOfCareId.HasValue) { _logger.LogError("Try to sen beacon code, but no PointOfCareId id is present in the Patient {Patient}", patient.ToString()); return Task.CompletedTask; } switch (color) { case AlarmEnum.BeaconColor.Blue: _lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Blue); break; case AlarmEnum.BeaconColor.Yellow: _lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Yellow); break; case AlarmEnum.BeaconColor.Red: _lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Red); break; case AlarmEnum.BeaconColor.None: _lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Off); break; } return Task.CompletedTask; } /// /// /// /// Hora de la observación /// /// /// /// /// /// private async Task StartRecording(ObjectId patientId, DateTime eventTime, AlarmItem? recording, AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription, int? endAfter, AlarmEnum.Type type = AlarmEnum.Type.Manual) { try { var patient = await _patientService.FindById(patientId); if (patient is not { PointOfCareId: not null }) return; var poc = await _pocService.FindByIdAllConfig(patient.PointOfCareId.Value); if (poc == null) return; //30 minutos antes y después de la fecha de la observación var startTime = recording != null ? eventTime.AddSeconds(-recording.StartBefore) : eventTime; var endDate = endAfter.HasValue ? eventTime.AddSeconds(endAfter.Value) : (DateTime?)null; await _recordingService.Value.SendRecordingDataToQueue(patient, poc, startTime, endDate, eventTime, alarmName, severity, alarmDescription, true, type); } catch (Exception ex) { _logger.LogError( "Error Starting recording from patientId: {patientId}. eventTime: {eventTime}. AlarmItem: {recording}. alarmSeverity: {alarmSeverity} Error: {ex}", patientId, eventTime, recording, severity, ex); } } private async Task RelayPowerOn(ObjectId patientId, RelayEnum.Type type) { try { var patient = await _patientService.FindById(patientId); if (patient is not { PointOfCareId: not null }) { Log.Error("can not power on relay because patient: {patientId} not found", patientId); return; } var poc = await _pocService.FindById(patient.PointOfCareId.Value); if (poc?.Configuration == null) return; var relayConfig = _relayService.Value.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault(); if (relayConfig != null) await _relayService.Value.PowerOn(relayConfig); } catch (Exception ex) { _logger.LogError("Error Relay Power On from patientId: {patientId}. Error: {ex}", patientId, ex); } } private async void StartTimer() { try { var pocList = await _pocService.GetAllLocationInfo(); _interval = TimeSpan.FromSeconds(_apiSettings.Value.ExpireAlertIntervalSeconds); _ = new Timer(async void (_) => { try { await _semaphore.WaitAsync(); // Esperar a adquirir el semáforo antes de ejecutar el temporizador await CheckExpiredAlarms(pocList); } catch (Exception e) { _logger.LogError("Error in timer execution: {message}", e.Message); //throw new Exception("Error in timer execution", e); } finally { _semaphore.Release(); // Liberar el semáforo después de ejecutar el temporizador } }, null, TimeSpan.Zero, _interval); } catch (Exception e) { _logger.LogError("Error starting timer: {message}", e.Message); //throw new Exception("Error starting timer", e); } } private async Task CheckExpiredAlarms(List pocList) { _logger.LogTrace("Checking Expired Alarms Started"); // Iniciar ambas tareas de forma asincrónica var checkBeaconsTask = CheckExpiredBeaconsAsync(pocList); var checkRelayTask = CheckExpiredRelayAsync(); // Esperar a que ambas tareas completen await Task.WhenAll(checkBeaconsTask, checkRelayTask); _logger.LogTrace("Checking Expired Alarms Finished"); } private readonly SemaphoreSlim _beaconListSemaphore = new(1, 1); private async Task CheckExpiredBeaconsAsync(List pocList) { await _beaconListSemaphore.WaitAsync(); try { if (!_beaconAlarmList.Any()) //var pocList = await _pocService.GetAllLocationInfo(); if (pocList.Any()) { var tasks = pocList.Select(async poc => { await _lightBeaconService.Value.SendColor(poc, LightBeaconColor.Off); }); await Task.WhenAll(tasks); return; } } finally { _beaconListSemaphore.Release(); } var now = DateTime.UtcNow; List updatedList = []; List ledTasks = []; await _beaconListSemaphore.WaitAsync(); try { foreach (var obsGroup in _beaconAlarmList.GroupBy(o => o.PatientId)) { var nonExpiredObs = obsGroup.Where(obs => obs.Alarm is { Beacon: not null } && (obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime()) .AddSeconds(obs.Alarm.Beacon.EndAfter) >= now ).ToList(); if (!nonExpiredObs.Any()) { var patient = obsGroup.FirstOrDefault()?.Patient; if (patient is { PointOfCareId: not null }) ledTasks.Add(_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Off)); } else { updatedList.AddRange(nonExpiredObs); } } _beaconAlarmList = updatedList; } finally { _beaconListSemaphore.Release(); } await Task.WhenAll(ledTasks); } private async Task CheckExpiredRelayAsync() { var now = DateTime.UtcNow; IEnumerable> groupedByPatientId; lock (_relayAlarmList) { groupedByPatientId = _relayAlarmList.GroupBy(o => o.PatientId); } foreach (var obsGroup in groupedByPatientId) { var patient = await _patientService.FindById(obsGroup.Key); if (patient is not { PointOfCareId: null }) continue; var nonExpiredObs = obsGroup.Where(obs => obs.Alarm is { OpenDoor: not null } && (obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime()).AddSeconds( obs.Alarm.OpenDoor.EndAfter) >= now ).ToList(); // Apagar el LED si no hay observaciones no expiradas if (!nonExpiredObs.Any()) { //buscamos en PoCSettings si está activado de forma manual var pocSettings = await _pocService.FindById(patient.PointOfCareId!.Value); var relays = _relayService.Value.GetRelayInList(pocSettings?.Configuration?.RelayIdList); foreach (var relay in relays) { // Verificamos si NO tiene un estado manual activo (On) // Si el estado es null o es diferente de On, lo apagamos if (relay.ManualRelayStatus != null && relay.ManualRelayStatus != RelayEnum.Status.On) { await _relayService.Value.PowerOff(relay); } } } // Reemplazar la lista original con las observaciones no expiradas lock (_relayAlarmList) { _relayAlarmList.RemoveAll(obs => obs.PatientId == obsGroup.Key); _relayAlarmList.AddRange(nonExpiredObs); } } } } #endregion