using RobotNet.RobotShares.VDA5050.State; namespace RobotNet.RobotManager.Services.OpenACS; public class OpenACSPublisher(IConfiguration configuration, LoggerController Logger, RobotManager RobotManager, OpenACSManager OpenACSManager) : BackgroundService { public int PublishCount { get; private set; } private WatchTimerAsync? Timer; private readonly string ACSSiteCode = configuration["ACSStatusConfig:SiteCode"] ?? "VN03"; private readonly string ACSAreaCode = configuration["ACSStatusConfig:AreaCode"] ?? "DA3_FL1"; private readonly string ACSAreaName = configuration["ACSStatusConfig:AreaName"] ?? "DA3_WM"; private readonly double ACSExtendX = configuration.GetValue("ACSStatusConfig:ExtendX"); private readonly double ACSExtendY = configuration.GetValue("ACSStatusConfig:ExtendY"); private readonly double ACSExtendTheta = configuration.GetValue("ACSStatusConfig:ExtendTheta"); private async Task TimerHandler() { if (OpenACSManager.PublishEnable && !string.IsNullOrEmpty(OpenACSManager.PublishURL)) { try { using var HttpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }; var robotIds = RobotManager.RobotSerialNumbers.ToArray(); foreach (var robotId in robotIds) { var startTime = DateTime.Now; var robot = RobotManager[robotId]; if (robot == null || robot.StateMsg == null || robot.StateMsg.AgvPosition == null) continue; if (!DateTime.TryParse(robot.StateMsg.Timestamp, out DateTime lastTimeUpdate)) continue; if ((startTime - lastTimeUpdate).TotalMilliseconds > OpenACSManager.PublishInterval) continue; int batLevel = (int)robot.StateMsg.BatteryState.BatteryHealth; if (batLevel <= 0) batLevel = 85; int batVol = (int)robot.StateMsg.BatteryState.BatteryVoltage; if (batVol <= 0) batVol = 24; var status = new RobotPublishStatusV2() { Header = new("AGV_STATUS", lastTimeUpdate.ToString("yyyy-MM-dd HH:mm:ss.fff")), Body = new() { Id = robot.SerialNumber, Location = new() { X = (robot.StateMsg.AgvPosition.X + ACSExtendX).ToString(), Y = (robot.StateMsg.AgvPosition.Y + ACSExtendY).ToString(), Z = "0", Direction = (robot.StateMsg.AgvPosition.Theta + ACSExtendTheta).ToString(), }, SiteCode = ACSSiteCode, AreaCode = ACSAreaCode, AreaName = ACSAreaName, MarkerId = string.IsNullOrEmpty(robot.StateMsg.LastNodeId) ? null : robot.StateMsg.LastNodeId, BatteryId = null, BatteryLevel = batLevel.ToString(), BatteryVoltage = batVol.ToString(), BatterySOH = null, BatteryCurrent = "1.0", BatteryTemprature = "30", StationId = null, Loading = robot.StateMsg.Loads.Length != 0 ? "1" : "0", ErrorCode = GetErrorCode(robot.StateMsg.Errors ?? [])?.ToString() ?? null, State = GetStatus(robot.StateMsg).ToString(), } }; var response = await HttpClient.PostAsJsonAsync(OpenACSManager.PublishURL, status); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(); if (result == null) { Logger.Error("Failed to convert response.Content to ACSStatusResponse"); } else if (result.Header.MessageName == "AGV_STATUS_ACK" && result.Body.Result == "OK") { PublishCount++; } else { Logger.Warning($"ACS response is not OK: {System.Text.Json.JsonSerializer.Serialize(result)}"); } } else { Logger.Warning($"Quá trình xuất bản tới {OpenACSManager.PublishURL} không thành công: {response.StatusCode}"); } } } catch (Exception ex) { Logger.Warning($"Quá trình xuất bản tới {OpenACSManager.PublishURL} có lỗi xảy ra: {ex.Message}"); } } } private static int GetStatus(StateMsg state) { if (GetError(state) == ErrorLevel.FATAL || GetError(state) == ErrorLevel.WARNING) return (int)AGVState.Error; else if (state.BatteryState.Charging) return (int)AGVState.Charging; else if (state.Paused) return (int)AGVState.Pause; else if (IsIdle(state)) return (int)AGVState.Idle; else if (IsWorking(state)) return (int)AGVState.Run; else return (int)AGVState.Stop; } private static string? GetErrorCode(Error[] errors) { var error = errors.FirstOrDefault(); if (error is not null && int.TryParse(error.ErrorType, out int errorCode)) return errorCode.ToString(); return null; } private static bool IsIdle(StateMsg state) { if (state.NodeStates.Length != 0 || state.EdgeStates.Length != 0) return false; return true; } private static bool IsWorking(StateMsg state) { if (state.NodeStates.Length != 0 || state.EdgeStates.Length != 0) return true; return false; } private static ErrorLevel GetError(StateMsg state) { if (state.Errors is not null) { if (state.Errors.Any(error => error.ErrorLevel == ErrorLevel.FATAL.ToString())) return ErrorLevel.FATAL; if (state.Errors.Any(error => error.ErrorLevel == ErrorLevel.WARNING.ToString())) return ErrorLevel.WARNING; } return ErrorLevel.NONE; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Yield(); while (!stoppingToken.IsCancellationRequested) { try { OpenACSManager.PublishIntervalChanged += UpdateInterval; Timer = new(OpenACSManager.PublishInterval <= 500 ? 500 : OpenACSManager.PublishInterval, TimerHandler, Logger); Timer.Start(); break; } catch (Exception ex) { Logger.Warning($"Publisher ACS: Quá trình khởi tạo có lỗi xảy ra: {ex.Message}"); await Task.Delay(2000, stoppingToken); } } } public void UpdateInterval() { Timer?.Dispose(); Timer = new(OpenACSManager.PublishInterval <= 500 ? 500 : OpenACSManager.PublishInterval, TimerHandler, Logger); Timer.Start(); } public override Task StopAsync(CancellationToken cancellationToken) { Timer?.Dispose(); return Task.CompletedTask; } }