RobotNet/RobotNet.RobotManager/Services/OpenACS/OpenACSPublisher.cs
2025-10-15 15:15:53 +07:00

172 lines
7.6 KiB
C#

using RobotNet.RobotShares.VDA5050.State;
namespace RobotNet.RobotManager.Services.OpenACS;
public class OpenACSPublisher(IConfiguration configuration,
LoggerController<OpenACSPublisher> Logger,
RobotManager RobotManager,
OpenACSManager OpenACSManager) : BackgroundService
{
public int PublishCount { get; private set; }
private WatchTimerAsync<OpenACSPublisher>? 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<double>("ACSStatusConfig:ExtendX");
private readonly double ACSExtendY = configuration.GetValue<double>("ACSStatusConfig:ExtendY");
private readonly double ACSExtendTheta = configuration.GetValue<double>("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<ACSStatusResponse>();
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;
}
}