using System.Collections.Concurrent; using System.Text; 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.Filter; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Responses; using adas_core.Domain.Utils; using adas_core.module.LightBeacons.Devices; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using LightBeaconDevice = adas_core.module.LightBeacons.Devices.LightBeacon; using LightBeacon = adas_core.Domain.Models.MongoModels.LightBeacon; namespace adas_core.module.LightBeacons.Services; public class LightBeaconService : ILightBeaconService { private readonly IClientMessageService _clientMessageService; private readonly ConcurrentDictionary _locationsWithColor = new(); private readonly ILogger _logger; private readonly IPointOfCareService _pointOfCareService; private readonly ILightBeaconRepository _lightBeaconRepository; private readonly ISubscribersService _subscribersService; public LightBeaconService( IOptions apiSettings, ILogger logger, IClientMessageService clientMessageService, ISubscribersService subscribersService, IPointOfCareService pointOfCareService, ILightBeaconRepository lightBeaconRepository) { _logger = logger; _clientMessageService = clientMessageService; _subscribersService = subscribersService; _pointOfCareService = pointOfCareService; _lightBeaconRepository = lightBeaconRepository; try { Url = apiSettings.Value.BalizaUrl; } catch (Exception ex) { _logger.LogError("Error BalizaService apiSettings values: {exMessage} ", ex); } //_ = SetLocationsWithColor(); } private string? Url { get; } public async Task PowerOffLed(ObjectId pocId) { var poc = await _pointOfCareService.FindById(pocId); if (poc == null) { _logger.LogError("Power off led did not find poc by id: {PocId}", pocId); return; } _ = PowerOffLed(poc); } public async Task InsertOne(LightBeacon beacon) { var beaconFound = await _lightBeaconRepository.GetByName(beacon.Name); if(beaconFound != null) throw new Exception($"Light beacon with name {beacon.Name} already exists"); return await _lightBeaconRepository.InsertOneAsyncAndReturn(beacon); } public async Task> GetPaginatedBeacons(PaginationFilter filter) { var usedRelayIds = await _pointOfCareService.FindAllIdBeaconsInUse(); var fluentQuery = _lightBeaconRepository.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> GetSearchByName(string textToSearch) { return await _lightBeaconRepository.GetSearchByName(textToSearch); } public async Task UpdateOne(LightBeacon beacon) { await _lightBeaconRepository.UpdateOneAsync(beacon.Id, beacon); return await _lightBeaconRepository.GetById(beacon.Id); } public async Task PowerOffLed(PointOfCare poc) { if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor) && lightBeaconColor == LightBeaconColor.Off) return; var beaconConfig = poc.Configuration?.BeaconIdList; if (beaconConfig == null || !beaconConfig.Any()) return; foreach (var beacon in beaconConfig.Select(GetBeacon)) if (beacon != null) await SendColor(poc.Id, LightBeaconColor.Off); lock (_locationsWithColor) { if (_locationsWithColor.ContainsKey(poc.Id)) _locationsWithColor[poc.Id] = LightBeaconColor.Off; else _locationsWithColor.TryAdd(poc.Id, LightBeaconColor.Off); } _logger.LogDebug("Locations with color cached: {dct}", DictionaryToString(_locationsWithColor)); } public void GenerateColorAlert(PatientObservation obs) { try { LightBeaconColor colorEnum; var colorNumbers = new char[3]; switch (obs.Status) { case StatusEnum.Type.Warning: if (obs.WarnColor == null) return; colorEnum = (LightBeaconColor)Enum.Parse(typeof(LightBeaconColor), obs.WarnColor); var colorValue = (int)colorEnum; var valuesChart = colorValue.ToString().ToCharArray(); if (valuesChart.Length is < 3 and < 2) { var c = valuesChart[0]; colorNumbers[0] = '0'; colorNumbers[1] = '0'; colorNumbers[2] = c; } else if (valuesChart.Length < 3) { var c = valuesChart[0]; var c2 = valuesChart[1]; colorNumbers[0] = '0'; colorNumbers[1] = c; colorNumbers[2] = c2; } break; case StatusEnum.Type.Alert: if (obs.AlertColor == null) return; colorEnum = (LightBeaconColor)Enum.Parse(typeof(LightBeaconColor), obs.AlertColor); var value = (int)colorEnum; var vchart = value.ToString().ToCharArray(); if (vchart.Length is < 3 and < 2) { var c = vchart[0]; colorNumbers[0] = '0'; colorNumbers[1] = '0'; colorNumbers[2] = c; } else if (vchart.Length < 3) { var c = vchart[0]; var c2 = vchart[1]; colorNumbers[0] = '0'; colorNumbers[1] = c; colorNumbers[2] = c2; } break; default: colorNumbers[0] = '0'; colorNumbers[1] = '0'; colorNumbers[2] = '0'; break; } var passwordEncoded = Uri.EscapeDataString("password"); var body = $"access_password={passwordEncoded}&58|12|1|1|0={Uri.EscapeDataString(colorNumbers[0].ToString())}&58|1|3|1|2={Uri.EscapeDataString(colorNumbers[1].ToString())}&58|1|2|1|2={Uri.EscapeDataString(colorNumbers[2].ToString())}"; _ = SendMessage(body); } catch (Exception ex) { _logger.LogError("Error GenerateColorAlert {obs}: {exMessage}", obs, ex); } } public async Task SendColor(ObjectId pocId, LightBeaconColor color) { var poc = await _pointOfCareService.FindById(pocId); if (poc == null) return; _ = SendColor(poc, color); } public async Task SendColor(PointOfCare poc, LightBeaconColor color) { try { if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor) && lightBeaconColor == color) return; var beaconConfig = poc.Configuration?.BeaconIdList; if (beaconConfig == null || !beaconConfig.Any()) return; foreach (var beaconId in beaconConfig) { var beacon = await GetBeacon(beaconId); switch (color) { case LightBeaconColor.Red: beacon?.RedCode(); break; case LightBeaconColor.Blue: beacon?.BlueCode(); break; case LightBeaconColor.Yellow: beacon?.YellowCode(); break; case LightBeaconColor.Off: beacon?.PowerOffLed(); break; } await SendBeaconBroadcast(poc, color); lock (_locationsWithColor) { if (_locationsWithColor.ContainsKey(poc.Id)) _locationsWithColor[poc.Id] = color; else _locationsWithColor.TryAdd(poc.Id, color); } } _logger.LogDebug("Locations with color cached: {dct}", DictionaryToString(_locationsWithColor)); } catch (Exception e) { _logger.LogError("An error occurred on sending color {color} while sending the Beacon code: {Message}", color.ToString(), e.Message); } } public async Task GetColor(ObjectId pocId) { var poc = await _pointOfCareService.FindById(pocId); if (poc == null) return LightBeaconColor.Off; return await GetColor(poc); } public async Task GetColor(PointOfCare poc) { try { if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor)) return lightBeaconColor; var lightBeaconConfig = poc.Configuration?.BeaconIdList; if (lightBeaconConfig == null || !lightBeaconConfig.Any()) return LightBeaconColor.Off; foreach (var beaconId in lightBeaconConfig) { var beacon = await GetBeacon(beaconId); if (beacon != null) return await beacon.GetBeaconColor(); } return LightBeaconColor.Off; } catch (Exception ex) { _logger.LogError("Exception getting color from beacon patient poc Id: {poc} Exception: {ex}", poc.Id, ex); return LightBeaconColor.Off; } } public async Task SendBeaconBroadcast(PointOfCare poc, LightBeaconColor color) { var subscribers = _subscribersService.GetSubscribers() .Where(s => s.LocationIds.Any(lid => lid == poc.Id)) .ToList(); BeaconResponse bc = new(color.ToString(), poc.Id, poc.UnitId); foreach (var subscriber in subscribers) await _clientMessageService.SendAsync(subscriber.Id, OperationType.Beacon, bc); } public async Task SendBeaconBroadcast(ObjectId pocId, LightBeaconColor color) { var poc = await _pointOfCareService.FindById(pocId); if (poc == null) return; _ = SendBeaconBroadcast(poc, color); } private async Task SendMessage(string body) { if (string.IsNullOrEmpty(Url)) { _logger.LogError("Url string is null or empty. Message:{body}", body); return; } HttpClient client = new(); var content = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"); try { var response = await client.PostAsync(Url, content); if (response.IsSuccessStatusCode) _logger.LogDebug("Message sent successfully: {body}", body); else _logger.LogError("Error SendMessage {body}: {responseStatusCode}", body, response.StatusCode); } catch (Exception ex) { _logger.LogError("Error SendMessage {body}: {exMessage}", body, ex); } } private async Task GetBeacon(ObjectId lightBeaconId) { var cfg = await _lightBeaconRepository.GetById(lightBeaconId); if (cfg == null) return null; if (string.IsNullOrEmpty(cfg.Options.Url) || cfg.Options.Port == null) return null; switch (cfg.Type) { case "TURKTBENS2": return new Turktbens2LightBeacon(new EquatableDictionary { { "url", cfg.Options.Url }, { "port", cfg.Options.Port }, { "password", cfg.Options.Password ?? "password" }, { "emulate", cfg.Options.Emulate } }); } return null; } public static string DictionaryToString(ConcurrentDictionary dictionary) { var builder = new StringBuilder(); foreach (var pair in dictionary) builder.AppendLine($"Location: {pair.Key}, Color: {pair.Value}"); return builder.ToString(); } //TODO:Almacenar en memoria los estados de los colores de la baliza //Array con el puerto y color }