using MQTTnet; using MQTTnet.Protocol; using RobotNet.RobotManager.Data; using RobotNet.RobotManager.Services.Robot; using RobotNet.RobotManager.Services.Simulation; using RobotNet.RobotManager.Services.Traffic; using RobotNet.RobotShares.Dtos; using RobotNet.RobotShares.OpenACS; using RobotNet.RobotShares.VDA5050; using RobotNet.RobotShares.VDA5050.Connection; using RobotNet.RobotShares.VDA5050.Factsheet; using RobotNet.RobotShares.VDA5050.FactsheetExtend; using RobotNet.RobotShares.VDA5050.State; using RobotNet.RobotShares.VDA5050.Visualization; using RobotNet.Script.Expressions; using RobotNet.Shares; using System.Linq.Expressions; using System.Text; using System.Text.Json; namespace RobotNet.RobotManager.Services; public class RobotManager : BackgroundService { public IEnumerable RobotSerialNumbers => RobotControllers.Keys; private readonly VDA5050Setting VDA5050Setting = new(); private Dictionary RobotControllers { get; } = []; private readonly IServiceProvider ServiceProvider; private IMqttClient? MQTTClient; private readonly LoggerController Logger; public RobotManager(IConfiguration configuration, IServiceProvider serviceProvider, LoggerController logger) { configuration.Bind("VDA5050Setting", VDA5050Setting); ServiceProvider = serviceProvider; Logger = logger; } public IRobotController? this[string robotid] => RobotControllers.TryGetValue(robotid, out IRobotController? value) ? value : null; private RobotController? AddRobotController(string robotId) { using var scope = ServiceProvider.CreateScope(); var ApplicationDb = scope.ServiceProvider.GetRequiredService(); var robotdb = ApplicationDb.Robots.FirstOrDefault(robot => robot.RobotId == robotId); if (robotdb is null) return null; var robotModel = ApplicationDb.RobotModels.FirstOrDefault(model => model.Id == robotdb.ModelId); if (robotModel is null) return null; var robotController = new RobotController(robotId, VDA5050Setting.Manufacturer, VDA5050Setting.Version, robotModel.NavigationType, ServiceProvider, PublishMQTT); RobotControllers.Add(robotId, robotController); return robotController; } public void AddRobotSimulation(IEnumerable robotIds) { using var scope = ServiceProvider.CreateScope(); var ApplicationDb = scope.ServiceProvider.GetRequiredService(); var Traffic = scope.ServiceProvider.GetRequiredService(); foreach (var robotId in robotIds) { if (RobotControllers.TryGetValue(robotId, out _)) continue; var robotdb = ApplicationDb.Robots.FirstOrDefault(robot => robot.RobotId == robotId); if (robotdb is null) return; var robotModel = ApplicationDb.RobotModels.FirstOrDefault(model => model.Id == robotdb.ModelId); if (robotModel is null) return; var robotController = new RobotSimulation(robotId, robotModel, ServiceProvider); RobotControllers.Add(robotId, robotController); } } public bool DeleteRobot(string robotId) { try { if (RobotControllers.TryGetValue(robotId, out var robotcontroller)) { RobotControllers.Remove(robotId); } return true; } catch (Exception ex) { Logger.Warning($"Hệ thống xảy ra lỗi khi xóa bỏ robot: {ex.Message}"); return false; } } public IEnumerable GetRobotInfo(Guid mapId) { try { using var scope = ServiceProvider.CreateScope(); var RobotDb = scope.ServiceProvider.GetRequiredService(); List RobotInfos = []; foreach (var robotcontroller in RobotControllers.Values.ToList()) { var robotDb = RobotDb.Robots.FirstOrDefault(r => robotcontroller.SerialNumber == r.RobotId); if (robotDb is null || robotDb.MapId != mapId) continue; RobotInfos.Add(new() { RobotId = robotcontroller.SerialNumber, Name = robotDb?.Name, MapId = robotDb is null ? Guid.Empty : robotDb.MapId, Battery = new() { BatteryHealth = robotcontroller.StateMsg.BatteryState.BatteryHealth, BatteryCharge = robotcontroller.StateMsg.BatteryState.BatteryCharge, BatteryVoltage = robotcontroller.StateMsg.BatteryState.BatteryVoltage, Charging = robotcontroller.StateMsg.BatteryState.Charging, }, Errors = [], Infomations = [], Navigation = new() { NavigationState = robotcontroller.State, RobotPath = robotcontroller.FullPath, RobotBasePath = robotcontroller.BasePath, }, AgvPosition = new() { X = robotcontroller.VisualizationMsg.AgvPosition.X, Y = robotcontroller.VisualizationMsg.AgvPosition.Y, Theta = robotcontroller.VisualizationMsg.AgvPosition.Theta, PositionInitialized = robotcontroller.VisualizationMsg.AgvPosition.PositionInitialized, LocalizationScore = robotcontroller.VisualizationMsg.AgvPosition.LocalizationScore, DeviationRange = robotcontroller.VisualizationMsg.AgvPosition.DeviationRange, }, AgvVelocity = new() { Vx = robotcontroller.VisualizationMsg.Velocity.Vx, Vy = robotcontroller.VisualizationMsg.Velocity.Vy, Omega = robotcontroller.VisualizationMsg.Velocity.Omega, }, Loads = robotcontroller.StateMsg.Loads, }); } return RobotInfos; } catch (Exception ex) { Logger.Warning($"Get Robot Info Error: - {ex}"); return []; } } public RobotACSLockedDto[] GetRobotACSLocked() { try { List robotACSLockedDtos = []; foreach(var robot in RobotControllers.Values) { robotACSLockedDtos.Add(new() { RobotId = robot.SerialNumber, ZoneIds = robot.CurrentZones, }); } return [..robotACSLockedDtos]; } catch (Exception ex) { Logger.Warning($"Get Robot ACS Locked Error: - {ex}"); return []; } } private static Script.Expressions.RobotState ConvertIRobotControllerToRobotState(IRobotController robotController) { bool isReady = robotController.IsOnline && !robotController.OrderState.IsProcessing && robotController.StateMsg.Errors.Length == 0; if (robotController.ActionStates.Length > 0) { isReady = isReady && robotController.ActionStates.All(a => !a.IsProcessing); } return new RobotState(isReady, robotController.StateMsg.BatteryState.BatteryVoltage, robotController.StateMsg.Loads.Length != 0, robotController.StateMsg.BatteryState.Charging, robotController.StateMsg.AgvPosition.X, robotController.StateMsg.AgvPosition.Y, robotController.StateMsg.AgvPosition.Theta); } /// /// Tìm kiếm robot theo tên model, tên bản đồ /// /// /// /// public async Task> SearchRobot(string? robotModelName, string? mapName, Func funcSearch) { List robotIds = []; try { using var scope = ServiceProvider.CreateScope(); var RobotDbContext = scope.ServiceProvider.GetRequiredService(); var MapManager = scope.ServiceProvider.GetRequiredService(); var robotModel = RobotDbContext.RobotModels.FirstOrDefault(m => m.ModelName == robotModelName); if (robotModel is null) return new(false, $"Robot model name {robotModelName} không tồn tại"); var map = await MapManager.GetMapInfo(mapName ?? ""); if (!map.IsSuccess && !string.IsNullOrEmpty(mapName)) return new(false, map.Message); if (map is null || map.Data is null) return new(false, $"Map name {mapName} không tồn tại"); foreach (var robot in RobotControllers.Values) { if (!robot.IsOnline) continue; var robotDb = RobotDbContext.Robots.FirstOrDefault(r => r.RobotId == robot.SerialNumber); if (robotDb is null) continue; var robotDbModel = RobotDbContext.RobotModels.FirstOrDefault(m => m.Id == robotDb.ModelId); if (robotDbModel is null) continue; var robotDbMap = await MapManager.GetMapInfo(robotDb.MapId); if (robotDbMap is null || !robotDbMap.IsSuccess) continue; if (robotDbMap.Data is null) continue; bool isRobotModelMatch = string.IsNullOrEmpty(robotModelName) || robotDbModel.Id == robotModel.Id; bool isMapMatch = string.IsNullOrEmpty(mapName) || (map is not null && robotDbMap.Data.Id == map.Data.Id); bool isExpressionMatch = funcSearch(ConvertIRobotControllerToRobotState(robot)); if (isRobotModelMatch && isMapMatch && isExpressionMatch) robotIds.Add(robot.SerialNumber); } return new(true, robotIds.Count > 0 ? "Tìm thấy robot" : "Không tìm thấy robot nào thỏa mãn điều kiện") { Data = [.. robotIds], }; } catch (Exception ex) { Logger.Warning($"Find Robot Error: {ex}"); return new(false, "Hệ thống có lỗi xảy ra"); } } public RobotVDA5050StateDto? GetRobotVDA5050State(string robotId) { try { using var scope = ServiceProvider.CreateScope(); var robotDb = scope.ServiceProvider.GetRequiredService(); if (RobotControllers.TryGetValue(robotId, out IRobotController? robotController) && robotController is not null) { var robot = robotDb.Robots.FirstOrDefault(r => r.RobotId == robotId); if (robot is null) return null; return new() { RobotId = robotId, Name = robot.Name, MapId = robot.MapId, Online = robotController.IsOnline, State = robotController.StateMsg, OrderState = robotController.OrderState, IsWorking = robotController.IsWorking, Visualization = robotController.VisualizationMsg, }; } return null; } catch (Exception ex) { Logger.Warning($"Get VDA State Error: {robotId} - {ex}"); return null; } } public IEnumerable GetRobotOnlineState() { try { using var scope = ServiceProvider.CreateScope(); var robotDb = scope.ServiceProvider.GetRequiredService(); var robots = robotDb.Robots.ToList(); return [.. robots.Select(robot => { var robotOnline = RobotControllers.TryGetValue(robot.RobotId, out IRobotController? robotController); return new RobotOnlineStateDto() { RobotId = robot.RobotId, State = !robotOnline || robotController is null ? "OFFLINE" : robotController.State, IsOnline = robotOnline && robotController is not null && robotController.IsOnline, Battery = !robotOnline || robotController is null ? 0 : robotController.StateMsg.BatteryState.BatteryHealth, }; })]; } catch (Exception ex) { Logger.Warning($"Get Robot Online State Error: {ex}"); return []; } } private bool PublishMQTT(string topic, string data) { var repeat = VDA5050Setting.Repeat; while (repeat-- > 0) { try { var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(data) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) .Build(); if (MQTTClient is null) return false; var publish = MQTTClient.PublishAsync(applicationMessage, CancellationToken.None); publish.Wait(); if (!publish.Result.IsSuccess) continue; return publish.Result.IsSuccess; } catch { return false; } } return false; } private void ConnectionChanged(string data) { try { var msg = JsonSerializer.Deserialize(data); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber)) return; IRobotController robotController; if (RobotControllers.TryGetValue(msg.SerialNumber, out IRobotController? value)) { value.IsOnline = msg.ConnectionState == ConnectionState.ONLINE.ToString(); robotController = value; } else { var addRobtoControllerTask = AddRobotController(msg.SerialNumber); if (addRobtoControllerTask is null) return; robotController = addRobtoControllerTask; RobotControllers[msg.SerialNumber].IsOnline = msg.ConnectionState == ConnectionState.ONLINE.ToString(); } robotController.RobotUpdated.Set(); } catch (Exception ex) { Logger.Warning($"Robot Manager ConnectionChanged xảy ra lỗi: {ex.Message} - {ex.StackTrace}"); } } private void StateChanged(string data) { try { var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Write); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber)) return; if (msg.AgvPosition is null) return; msg.AgvPosition.Theta = msg.AgvPosition.Theta * 180 / Math.PI; IRobotController robotController; if (RobotControllers.TryGetValue(msg.SerialNumber, out IRobotController? value)) { value.StateMsg = msg; robotController = value; } else { var addRobtoControllerTask = AddRobotController(msg.SerialNumber); if (addRobtoControllerTask is null) return; robotController = addRobtoControllerTask; RobotControllers[msg.SerialNumber].StateMsg = msg; } robotController.IsOnline = true; robotController.RobotUpdated.Set(); } catch (Exception ex) { Logger.Warning($"Robot Manager StateChanged xảy ra lỗi: {ex.Message} - {ex.StackTrace}"); } } private void VisualizationChanged(string data) { try { var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Write); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber)) return; if (msg.AgvPosition is null) return; msg.AgvPosition.Theta = msg.AgvPosition.Theta * 180 / Math.PI; IRobotController robotController; if (RobotControllers.TryGetValue(msg.SerialNumber, out IRobotController? value)) { value.VisualizationMsg = msg; robotController = value; } else { var addRobtoControllerTask = AddRobotController(msg.SerialNumber); if (addRobtoControllerTask is null) return; robotController = addRobtoControllerTask; RobotControllers[msg.SerialNumber].VisualizationMsg = msg; } robotController.IsOnline = true; robotController.RobotUpdated.Set(); } catch (Exception ex) { Logger.Warning($"Robot Manager VisualizationChanged xảy ra lỗi: {ex.Message} - {ex.StackTrace}"); } } private void FactsheetChanged(string data) { try { var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Write); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber)) return; IRobotController robotController; if (RobotControllers.TryGetValue(msg.SerialNumber, out IRobotController? value)) { value.FactSheetMsg = msg; robotController = value; } else { var addRobtoControllerTask = AddRobotController(msg.SerialNumber); if (addRobtoControllerTask is null) return; robotController = addRobtoControllerTask; RobotControllers[msg.SerialNumber].FactSheetMsg = msg; } robotController.IsOnline = true; robotController.RobotUpdated.Set(); } catch (Exception ex) { Logger.Warning($"Robot Manager FactsheetChanged xảy ra lỗi: {ex.Message} - {ex.StackTrace}"); } } private void FactsheetExtendChanged(string data) { try { var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Write); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber)) return; IRobotController robotController; if (RobotControllers.TryGetValue(msg.SerialNumber, out IRobotController? value)) { value.FactsheetExtendMsg = msg; robotController = value; } else { var addRobtoControllerTask = AddRobotController(msg.SerialNumber); if (addRobtoControllerTask is null) return; robotController = addRobtoControllerTask; RobotControllers[msg.SerialNumber].FactsheetExtendMsg = msg; } robotController.IsOnline = true; robotController.RobotUpdated.Set(); } catch (Exception ex) { Logger.Warning($"Robot Manager FactsheetExtendChanged xảy ra lỗi: {ex.Message} - {ex.StackTrace}"); } } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Yield(); while (!stoppingToken.IsCancellationRequested) { try { var mqttFactory = new MqttClientFactory(); MQTTClient = mqttFactory.CreateMqttClient(); var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer(VDA5050Setting.HostServer, VDA5050Setting.Port) .WithClientId("FleetManager") .WithCredentials(VDA5050Setting.UserName, VDA5050Setting.Password) .Build(); if ((await MQTTClient.ConnectAsync(mqttClientOptions, stoppingToken)).ResultCode != MqttClientConnectResultCode.Success) { await Task.Delay(1000, stoppingToken); continue; } Logger.Info("Fleet Manager kết nối tới broker thành công"); var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() .WithTopicFilter(f => { f.WithTopic(VDA5050Topic.Connection); }) .WithTopicFilter(f => { f.WithTopic(VDA5050Topic.Visualization); }) .WithTopicFilter(f => { f.WithTopic(VDA5050Topic.State); }) .WithTopicFilter(f => { f.WithTopic(VDA5050Topic.Factsheet); }) .WithTopicFilter(f => { f.WithTopic(VDA5050Topic.FactsheetExtend); }) .Build(); var response = await MQTTClient.SubscribeAsync(mqttSubscribeOptions, stoppingToken); Logger.Info("Fleet Manager Subscribe thành công"); MQTTClient.ApplicationMessageReceivedAsync += delegate (MqttApplicationMessageReceivedEventArgs args) { var stringData = Encoding.Default.GetString(args.ApplicationMessage.Payload); switch (args.ApplicationMessage.Topic) { case VDA5050Topic.Connection: ConnectionChanged(stringData); break; case VDA5050Topic.Visualization: VisualizationChanged(stringData); break; case VDA5050Topic.State: StateChanged(stringData); break; case VDA5050Topic.Factsheet: FactsheetChanged(stringData); break; case VDA5050Topic.FactsheetExtend: FactsheetExtendChanged(stringData); break; default: break; } return Task.CompletedTask; }; Logger.Info("Fleet Manager Subscribe event thành công"); break; } catch (Exception ex) { Logger.Warning($"Kết nối tới broker xảy ra lỗi: {ex.Message}"); await Task.Delay(3000, stoppingToken); } } Logger.Info("Fleet Manager bắt đầu kiểm tra kết nối tới robot"); while (!stoppingToken.IsCancellationRequested) { try { foreach (var robotController in RobotControllers) { if (!robotController.Value.RobotUpdated.WaitOne(TimeSpan.FromSeconds(1)) && robotController.Value.IsOnline) { robotController.Value.Dispose(); continue; } robotController.Value.RobotUpdated.Reset(); } await Task.Delay(TimeSpan.FromSeconds(VDA5050Setting.CheckingRobotMsgTimout), stoppingToken); } catch (Exception ex) { Logger.Warning($"Kiểm tra kết nối tới robot xảy ra lỗi: {ex.Message}"); await Task.Delay(3000, stoppingToken); } } } }