first commit -push
This commit is contained in:
16
RobotNet.RobotManager/Services/OpenACS/ACSHeader.cs
Normal file
16
RobotNet.RobotManager/Services/OpenACS/ACSHeader.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class ACSHeader(string messageName, string time)
|
||||
{
|
||||
[JsonPropertyName("msgname")]
|
||||
[Required]
|
||||
public string MessageName { get; set; } = messageName;
|
||||
|
||||
[JsonPropertyName("time")]
|
||||
[Required]
|
||||
public string Time { get; set; } = time;
|
||||
|
||||
}
|
||||
24
RobotNet.RobotManager/Services/OpenACS/ACSStatusResponse.cs
Normal file
24
RobotNet.RobotManager/Services/OpenACS/ACSStatusResponse.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class ACSStatusBodyResponse
|
||||
{
|
||||
[JsonPropertyName("result")]
|
||||
[Required]
|
||||
public string Result { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
|
||||
public class ACSStatusResponse
|
||||
{
|
||||
[JsonPropertyName("header")]
|
||||
[Required]
|
||||
public ACSHeader Header { get; set; } = new("", DateTime.MinValue.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
[Required]
|
||||
public ACSStatusBodyResponse Body { get; set; } = new();
|
||||
}
|
||||
23
RobotNet.RobotManager/Services/OpenACS/AGVLocation.cs
Normal file
23
RobotNet.RobotManager/Services/OpenACS/AGVLocation.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class AGVLocation
|
||||
{
|
||||
[JsonPropertyName("world_x")]
|
||||
[Required]
|
||||
public string X { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("world_y")]
|
||||
[Required]
|
||||
public string Y { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("world_z")]
|
||||
[Required]
|
||||
public string Z { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("direction")]
|
||||
[Required]
|
||||
public string Direction { get; set; } = "0";
|
||||
}
|
||||
15
RobotNet.RobotManager/Services/OpenACS/AGVState.cs
Normal file
15
RobotNet.RobotManager/Services/OpenACS/AGVState.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public enum AGVState
|
||||
{
|
||||
Offline = -1,
|
||||
Error = 0,
|
||||
Idle = 1,
|
||||
Processing = 2,
|
||||
Pause = 3,
|
||||
DockingFail = 4,
|
||||
NoPose = 5,
|
||||
Charging = 6,
|
||||
Run = 7,
|
||||
Stop = 8,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class OpenACSException : Exception
|
||||
{
|
||||
public OpenACSException() { }
|
||||
public OpenACSException(string message) : base(message) { }
|
||||
public OpenACSException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
102
RobotNet.RobotManager/Services/OpenACS/OpenACSManager.cs
Normal file
102
RobotNet.RobotManager/Services/OpenACS/OpenACSManager.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class OpenACSManager : BackgroundService
|
||||
{
|
||||
public bool TrafficEnable => Config.TrafficEnable;
|
||||
public string TrafficURL => Config.TrafficURL ?? "";
|
||||
public string[] TrafficURLUsed => [..Config.TrafficURLUsed ?? []];
|
||||
public bool PublishEnable => Config.PublishEnable;
|
||||
public string PublishURL => Config.PublishURL ?? "";
|
||||
public string[] PublishURLUsed => [..Config.PublishURLUsed ?? []];
|
||||
public int PublishInterval => Config.PublishInterval;
|
||||
public event Action? PublishIntervalChanged;
|
||||
|
||||
private ConfigData Config;
|
||||
private const string DataPath = "openACSConfig.json";
|
||||
private struct ConfigData
|
||||
{
|
||||
public string PublishURL { get; set; }
|
||||
public List<string> PublishURLUsed { get; set; }
|
||||
public bool PublishEnable { get; set; }
|
||||
public string TrafficURL { get; set; }
|
||||
public List<string> TrafficURLUsed { get; set; }
|
||||
public bool TrafficEnable { get; set; }
|
||||
public int PublishInterval { get; set; }
|
||||
}
|
||||
|
||||
public async Task UpdateTrafficURL(string url)
|
||||
{
|
||||
if (url == Config.TrafficURL) return;
|
||||
|
||||
Config.TrafficURL = url;
|
||||
Config.TrafficURLUsed ??= [];
|
||||
var urlUsed = Config.TrafficURLUsed.FirstOrDefault(u => u.Equals(url, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (urlUsed is not null)
|
||||
{
|
||||
Config.TrafficURLUsed.Remove(urlUsed);
|
||||
}
|
||||
else if (Config.PublishURLUsed.Count >= 10) Config.TrafficURLUsed.Remove(Config.TrafficURLUsed.Last());
|
||||
Config.TrafficURLUsed.Insert(0, url);
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config));
|
||||
}
|
||||
|
||||
public async Task UpdateTrafficEnable(bool enable)
|
||||
{
|
||||
if (enable == Config.TrafficEnable) return;
|
||||
|
||||
Config.TrafficEnable = enable;
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config));
|
||||
}
|
||||
|
||||
public async Task UpdatePublishURL(string url)
|
||||
{
|
||||
if (url == Config.PublishURL) return;
|
||||
|
||||
Config.PublishURL = url;
|
||||
Config.PublishURLUsed ??= [];
|
||||
var urlUsed = Config.PublishURLUsed.FirstOrDefault(u => u.Equals(url, StringComparison.OrdinalIgnoreCase));
|
||||
if (urlUsed is not null)
|
||||
{
|
||||
Config.PublishURLUsed.Remove(urlUsed);
|
||||
}
|
||||
else if (Config.PublishURLUsed.Count >= 10) Config.PublishURLUsed.Remove(Config.PublishURLUsed.Last());
|
||||
Config.PublishURLUsed.Insert(0, url);
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config));
|
||||
}
|
||||
|
||||
public async Task UpdatePublishEnable(bool enable)
|
||||
{
|
||||
if (enable == Config.PublishEnable) return;
|
||||
|
||||
Config.PublishEnable = enable;
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config));
|
||||
}
|
||||
|
||||
public async Task UpdatePublishInterval(int interval)
|
||||
{
|
||||
if (interval == Config.PublishInterval) return;
|
||||
|
||||
Config.PublishInterval = interval;
|
||||
PublishIntervalChanged?.Invoke();
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config));
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
if (File.Exists(DataPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Config = JsonSerializer.Deserialize<ConfigData>(await File.ReadAllTextAsync(DataPath, CancellationToken.None));
|
||||
PublishIntervalChanged?.Invoke();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
else await File.WriteAllTextAsync(DataPath, JsonSerializer.Serialize(Config), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
171
RobotNet.RobotManager/Services/OpenACS/OpenACSPublisher.cs
Normal file
171
RobotNet.RobotManager/Services/OpenACS/OpenACSPublisher.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
15
RobotNet.RobotManager/Services/OpenACS/RobotPublishStatus.cs
Normal file
15
RobotNet.RobotManager/Services/OpenACS/RobotPublishStatus.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class RobotPublishStatusV2
|
||||
{
|
||||
[JsonPropertyName("header")]
|
||||
[Required]
|
||||
public ACSHeader Header { get; set; } = new("AGV_STATUS", DateTime.Today.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
[Required]
|
||||
public RobotPublishStatusBody Body { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class RobotPublishStatusBody
|
||||
{
|
||||
[JsonPropertyName("agv_id")]
|
||||
[Required]
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
[Required]
|
||||
public string State { get; set; } = "-1";
|
||||
|
||||
[JsonPropertyName("site_code")]
|
||||
[Required]
|
||||
public string SiteCode { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("area_code")]
|
||||
[Required]
|
||||
public string AreaCode { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("area_name")]
|
||||
[Required]
|
||||
public string AreaName { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("location")]
|
||||
[Required]
|
||||
public AGVLocation Location { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("marker_id")]
|
||||
[Required]
|
||||
public string? MarkerId { get; set; }
|
||||
|
||||
[JsonPropertyName("battery_level")]
|
||||
[Required]
|
||||
public string BatteryLevel { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("battery_voltage")]
|
||||
[Required]
|
||||
public string BatteryVoltage { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("battery_current")]
|
||||
[Required]
|
||||
public string BatteryCurrent { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("battery_temperature")]
|
||||
[Required]
|
||||
public string BatteryTemprature { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("battery_id")]
|
||||
[Required]
|
||||
public string? BatteryId { get; set; }
|
||||
|
||||
[JsonPropertyName("battery_soh")]
|
||||
[Required]
|
||||
public string? BatterySOH { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("loading")]
|
||||
[Required]
|
||||
public string Loading { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("error_code")]
|
||||
[Required]
|
||||
public string? ErrorCode { get; set; }
|
||||
|
||||
[JsonPropertyName("station_id")]
|
||||
[Required]
|
||||
public string? StationId { get; set; }
|
||||
}
|
||||
108
RobotNet.RobotManager/Services/OpenACS/TrafficACS.cs
Normal file
108
RobotNet.RobotManager/Services/OpenACS/TrafficACS.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using NLog.Targets;
|
||||
using RobotNet.MapShares.Dtos;
|
||||
using RobotNet.RobotShares.OpenACS;
|
||||
using RobotNet.Shares;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class TrafficACS(OpenACSManager OpenACSManager, IConfiguration Configuration, LoggerController<TrafficACS> Logger)
|
||||
{
|
||||
public bool Enable => OpenACSManager.TrafficEnable;
|
||||
private readonly double TrafficCheckingDistanceMin = Configuration.GetValue("TrafficConfig:CheckingDistanceMin", 3);
|
||||
public readonly double DeviationDistance = Configuration.GetValue("TrafficConfig:DeviationDistance", 0.5);
|
||||
private static readonly JsonSerializerOptions jsonSerializeOptions = new() {
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
public async Task<MessageResult<bool>> RequestIn(string robotId, string zoneId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!OpenACSManager.TrafficEnable) return new(true, "Kết nối với hệ thống traffic ACS không được bật") { Data = true };
|
||||
using var HttpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) };
|
||||
|
||||
var model = new TrafficACSRequestV2(robotId, zoneId, TrafficRequestType.IN.ToInOutString());
|
||||
|
||||
var response = await (await HttpClient.PostAsJsonAsync(OpenACSManager.TrafficURL, model)).Content.ReadFromJsonAsync<TrafficACSResponseV2>() ??
|
||||
throw new OpenACSException("Lỗi giao tiếp với hệ thống traffic ACS");
|
||||
if (response.Body.AgvId != robotId) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS agv_id trả về {response.Body.AgvId} không trùng với dữ liệu gửi đi {robotId}");
|
||||
if (response.Body.TrafficZoneId != zoneId) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS traffic_zone_id trả về {response.Body.TrafficZoneId} không trùng với dữ liệu gửi đi {zoneId}");
|
||||
if (response.Body.InOut != TrafficRequestType.IN.ToInOutString()) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS inout trả về {response.Body.InOut} không trùng với dữ liệu gửi đi in");
|
||||
Logger.Info($"{robotId} yêu cầu vào traffic zone {zoneId} \nRequest: {JsonSerializer.Serialize(model, jsonSerializeOptions)}\n trả về kết quả: {JsonSerializer.Serialize(response, jsonSerializeOptions)}");
|
||||
return new(true, "Yêu cầu vào traffic zone thành công") { Data = response.Body.Result == TrafficACSResult.GO };
|
||||
}
|
||||
catch (OpenACSException ex)
|
||||
{
|
||||
Logger.Warning($"{robotId} request in xảy ra lỗi: {ex.Message}");
|
||||
return new(false, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"{robotId} request In xảy ra lỗi: {ex.Message}");
|
||||
return new(false, "Lỗi giao tiếp với hệ thống traffic ACS");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MessageResult<bool>> RequestOut(string robotId, string zoneId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!OpenACSManager.TrafficEnable) return new(true, "Kết nối với hệ thống traffic ACS không được bật") { Data = true};
|
||||
using var HttpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) };
|
||||
var model = new TrafficACSRequestV2(robotId, zoneId, TrafficRequestType.OUT.ToInOutString());
|
||||
var response = await (await HttpClient.PostAsJsonAsync(OpenACSManager.TrafficURL, model)).Content.ReadFromJsonAsync<TrafficACSResponseV2>() ??
|
||||
throw new OpenACSException("Lỗi giao tiếp với hệ thống traffic ACS");
|
||||
if (response.Body.AgvId != robotId) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS agv_id trả về {response.Body.AgvId} không trùng với dữ liệu gửi đi {robotId}");
|
||||
if (response.Body.TrafficZoneId != zoneId) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS traffic_zone_id trả về {response.Body.TrafficZoneId} không trùng với dữ liệu gửi đi {zoneId}");
|
||||
if (response.Body.InOut != TrafficRequestType.OUT.ToInOutString()) throw new OpenACSException($"Dữ liệu hệ thống traffic ACS inout trả về {response.Body.InOut} không trùng với dữ liệu gửi đi out");
|
||||
Logger.Info($"{robotId} yêu cầu ra khỏi traffic zone {zoneId} \nRequest: {JsonSerializer.Serialize(model, jsonSerializeOptions)}\n trả về kết quả: {JsonSerializer.Serialize(response, jsonSerializeOptions)}");
|
||||
return new(true, "Yêu cầu ra khỏi traffic zone thành công") { Data = response.Body.Result == TrafficACSResult.GO };
|
||||
}
|
||||
catch (OpenACSException ex)
|
||||
{
|
||||
Logger.Warning($"{robotId} request out xảy ra lỗi: {ex.Message}");
|
||||
return new(false, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"{robotId} request Out xảy ra lỗi: {ex.Message}");
|
||||
return new(false, "Lỗi giao tiếp với hệ thống traffic ACS");
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Guid, ZoneDto[]> GetZones(Guid inNodeId, NodeDto[] nodes, Dictionary<Guid, ZoneDto[]> zones)
|
||||
{
|
||||
int inNodeIndex = Array.FindIndex(nodes, n => n.Id == inNodeId);
|
||||
if (inNodeId == Guid.Empty || (inNodeIndex != -1 && inNodeIndex < nodes.Length - 1))
|
||||
{
|
||||
List<NodeDto> baseNodes = [];
|
||||
List<ZoneDto> basezones = [];
|
||||
double distance = 0;
|
||||
int index = inNodeIndex != -1 ? inNodeIndex + 1 : 1;
|
||||
for (; index < nodes.Length; index++)
|
||||
{
|
||||
baseNodes.Add(nodes[index]);
|
||||
distance += Math.Sqrt(Math.Pow(nodes[index].X - nodes[index - 1].X, 2) + Math.Pow(nodes[index].Y - nodes[index - 1].Y, 2));
|
||||
if (distance > TrafficCheckingDistanceMin) break;
|
||||
}
|
||||
|
||||
Dictionary<Guid, ZoneDto[]> nodeZones = [];
|
||||
|
||||
foreach (var node in baseNodes)
|
||||
{
|
||||
if (zones.TryGetValue(node.Id, out ZoneDto[]? zone) && zone is not null && zone.Length > 0) nodeZones.Add(node.Id, zone);
|
||||
else nodeZones.Add(node.Id, []);
|
||||
}
|
||||
|
||||
return nodeZones;
|
||||
}
|
||||
else if (inNodeIndex == nodes.Length - 1) return [];
|
||||
else
|
||||
{
|
||||
Logger.Warning($"Không tìm thấy node {inNodeId} trong danh sách nodes hoặc node này.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
42
RobotNet.RobotManager/Services/OpenACS/TrafficACSRequest.cs
Normal file
42
RobotNet.RobotManager/Services/OpenACS/TrafficACSRequest.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
|
||||
public class TrafficACSRequestBody(string agvId, string trafficZoneId, string inOut)
|
||||
{
|
||||
[JsonPropertyName("agv_id")]
|
||||
[Required]
|
||||
public string AgvId { get; set; } = agvId;
|
||||
|
||||
[JsonPropertyName("traffic_zone_id")]
|
||||
[Required]
|
||||
public string TrafficZoneId { get; set; } = trafficZoneId;
|
||||
|
||||
[JsonPropertyName("inout")]
|
||||
[Required]
|
||||
public string InOut { get; set; } = inOut;
|
||||
}
|
||||
|
||||
public class TrafficACSRequestV2
|
||||
{
|
||||
[JsonPropertyName("header")]
|
||||
[Required]
|
||||
public ACSHeader Header { get; set; } = new("TRAFFIC_REQ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
[Required]
|
||||
public TrafficACSRequestBody Body { get; set; }
|
||||
|
||||
public TrafficACSRequestV2(string agvId, string trafficZoneId, string inOut)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(agvId))
|
||||
throw new ArgumentException("AGV ID không thể rỗng.", nameof(agvId));
|
||||
if (string.IsNullOrWhiteSpace(trafficZoneId))
|
||||
throw new ArgumentException("Traffic Zone ID không thể rỗng.", nameof(trafficZoneId));
|
||||
if (string.IsNullOrWhiteSpace(inOut))
|
||||
throw new ArgumentException("In OUT không thể rỗng.", nameof(inOut));
|
||||
|
||||
Body = new (agvId, trafficZoneId, inOut);
|
||||
}
|
||||
}
|
||||
62
RobotNet.RobotManager/Services/OpenACS/TrafficACSResponse.cs
Normal file
62
RobotNet.RobotManager/Services/OpenACS/TrafficACSResponse.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.RobotManager.Services.OpenACS;
|
||||
public class TrafficACSResult
|
||||
{
|
||||
public static string GO => "go";
|
||||
public static string NO => "no";
|
||||
}
|
||||
public class TrafficACSResponse
|
||||
{
|
||||
[JsonPropertyName("time")]
|
||||
public string Time { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
|
||||
[JsonPropertyName("agv_id")]
|
||||
public string AgvId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("traffic_zone_id")]
|
||||
[Required]
|
||||
public string TrafficZoneId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("inout")]
|
||||
[Required]
|
||||
public string InOut { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
[Required]
|
||||
public string Result { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public class TrafficACSResponseBody
|
||||
{
|
||||
|
||||
[JsonPropertyName("agv_id")]
|
||||
[Required]
|
||||
public string AgvId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("traffic_zone_id")]
|
||||
[Required]
|
||||
public string TrafficZoneId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("inout")]
|
||||
[Required]
|
||||
public string InOut { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
[Required]
|
||||
public string Result { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class TrafficACSResponseV2
|
||||
{
|
||||
|
||||
[JsonPropertyName("header")]
|
||||
[Required]
|
||||
public ACSHeader Header { get; set; } = new("TRAFFIC_RES", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
[Required]
|
||||
public TrafficACSResponseBody Body { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user