diff --git a/Tnb.Server.sln b/Tnb.Server.sln index f9236fb7..ed93b136 100644 --- a/Tnb.Server.sln +++ b/Tnb.Server.sln @@ -163,15 +163,12 @@ Global {9FA1FB84-71AB-42A7-9570-F856A4B1EBAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9FA1FB84-71AB-42A7-9570-F856A4B1EBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU {9FA1FB84-71AB-42A7-9570-F856A4B1EBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FA1FB84-71AB-42A7-9570-F856A4B1EBAB}.Release|Any CPU.Build.0 = Release|Any CPU {135D0C0A-9B95-45F2-BE5F-01286F6BB234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {135D0C0A-9B95-45F2-BE5F-01286F6BB234}.Debug|Any CPU.Build.0 = Debug|Any CPU {135D0C0A-9B95-45F2-BE5F-01286F6BB234}.Release|Any CPU.ActiveCfg = Release|Any CPU - {135D0C0A-9B95-45F2-BE5F-01286F6BB234}.Release|Any CPU.Build.0 = Release|Any CPU {D1135D42-7CD0-4579-82B3-D2B121FCE954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1135D42-7CD0-4579-82B3-D2B121FCE954}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1135D42-7CD0-4579-82B3-D2B121FCE954}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1135D42-7CD0-4579-82B3-D2B121FCE954}.Release|Any CPU.Build.0 = Release|Any CPU {8D3E0381-4B3D-4F44-81C8-535E28418A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D3E0381-4B3D-4F44-81C8-535E28418A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D3E0381-4B3D-4F44-81C8-535E28418A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -283,15 +280,12 @@ Global {EE11B516-1B20-44E0-8691-A532B6F7B2EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE11B516-1B20-44E0-8691-A532B6F7B2EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE11B516-1B20-44E0-8691-A532B6F7B2EC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE11B516-1B20-44E0-8691-A532B6F7B2EC}.Release|Any CPU.Build.0 = Release|Any CPU {461075DC-9C3A-45BB-97CC-939A2EBCEA4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {461075DC-9C3A-45BB-97CC-939A2EBCEA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {461075DC-9C3A-45BB-97CC-939A2EBCEA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {461075DC-9C3A-45BB-97CC-939A2EBCEA4E}.Release|Any CPU.Build.0 = Release|Any CPU {3CCF286D-4C49-49E3-8AB1-2B1216E0ACFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CCF286D-4C49-49E3-8AB1-2B1216E0ACFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CCF286D-4C49-49E3-8AB1-2B1216E0ACFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CCF286D-4C49-49E3-8AB1-2B1216E0ACFA}.Release|Any CPU.Build.0 = Release|Any CPU {CA896F39-32F4-44C9-B512-A21A0363EB95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CA896F39-32F4-44C9-B512-A21A0363EB95}.Debug|Any CPU.Build.0 = Debug|Any CPU {CA896F39-32F4-44C9-B512-A21A0363EB95}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/WarehouseMgr/Tnb.WarehouseMgr.Entities/Configs/ElevatorControlConfiguration.cs b/WarehouseMgr/Tnb.WarehouseMgr.Entities/Configs/ElevatorControlConfiguration.cs new file mode 100644 index 00000000..e9cc09d3 --- /dev/null +++ b/WarehouseMgr/Tnb.WarehouseMgr.Entities/Configs/ElevatorControlConfiguration.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tnb.WarehouseMgr.Entities.Configs +{ + public class ElevatorControlConfiguration + { + /// + /// 设备名称 + /// + public string DevName { get; set; } + public string token { get; set; } + /// + /// 获取设备标签列表url + /// + public string GetTagListUrl { get; set; } + /// + /// 获取设备单个标签url + /// + public string GetTagUrl { get; set; } + /// + /// 写入设备标签属性url + /// + public string WriteTagUrl { get; set; } + + } +} diff --git a/WarehouseMgr/Tnb.WarehouseMgr/AgvHeartbeatMonitorService.cs b/WarehouseMgr/Tnb.WarehouseMgr/AgvHeartbeatMonitorService.cs new file mode 100644 index 00000000..bfef5c8d --- /dev/null +++ b/WarehouseMgr/Tnb.WarehouseMgr/AgvHeartbeatMonitorService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JNPF; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Tnb.Common.Extension; +using Tnb.Common.Utils; +using Tnb.WarehouseMgr.Entities.Configs; + +namespace Tnb.WarehouseMgr +{ + /// + /// Agv心跳检测服务 + /// + public class AgvHeartbeatMonitorService : BackgroundService + { + private readonly ElevatorControlConfiguration _elevatorControlConfiguration; + public bool IsStarted { get; set; } + + public AgvHeartbeatMonitorService() + { + _elevatorControlConfiguration = App.Configuration.Build(); + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + IsStarted = true; + var parameter = new Dictionary(); + parameter["DevName"] = _elevatorControlConfiguration.DevName; + parameter["TagName"] = "AGVKeepalive"; + parameter["Value"] = "123"; + parameter["token"] = _elevatorControlConfiguration.token; + while (!stoppingToken.IsCancellationRequested) + { + await HttpClientHelper.GetAsync(_elevatorControlConfiguration.WriteTagUrl, parameter); + await Task.Delay(TimeSpan.FromMinutes(1)); + } + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + IsStarted = false; + return Task.CompletedTask; + } + } + + public static class AgvHeartbeatMonitorServiceExtenstions + { + public static IServiceCollection AddAgvHeartbeatMonitor(this IServiceCollection services) + { + return services.AddSingleton(); + } + } + + +} diff --git a/WarehouseMgr/Tnb.WarehouseMgr/ElevatorControlService.cs b/WarehouseMgr/Tnb.WarehouseMgr/ElevatorControlService.cs new file mode 100644 index 00000000..22692589 --- /dev/null +++ b/WarehouseMgr/Tnb.WarehouseMgr/ElevatorControlService.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using JNPF; +using JNPF.DependencyInjection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using MimeKit.Cryptography; +using Newtonsoft.Json.Linq; +using Tnb.Common.Extension; +using Tnb.Common.Utils; +using Tnb.WarehouseMgr.Entities.Configs; + +namespace Tnb.WarehouseMgr +{ + /// + /// 电梯控制业务服务类 + /// + public class ElevatorControlService : BaseWareHouseService + { + private readonly ElevatorControlConfiguration _elevatorCtlCfg = new(); + private readonly BackgroundService _agvHeartbeatMonitor; + private static Dictionary> _fetchStartedStausValue = new(); + private bool isFrontDoorBit = false; //是否到前门位 + + public ElevatorControlService(BackgroundService agvHeartbeatMonitorService) + { + _elevatorCtlCfg = App.Configuration.Build(); + _agvHeartbeatMonitor = agvHeartbeatMonitorService; + } + + /// + /// 一楼电梯操作流程 + /// + /// + [HttpPost] + public async Task FirstFloorElevatorFlow(string value) + { + CancellationTokenSource cts = new CancellationTokenSource();//扩展用 + //获取电梯状态 + (int sysStatus, int runStatus, int floorNo) multi = (-1, -1, -1); + do + { + multi = await GetElevatorStatus(CancellationToken.None); + await Task.Delay(2000); + } while (multi.sysStatus != 3 && multi.runStatus != 0); + if (multi.sysStatus == 3 && multi.runStatus == 0 && multi.floorNo != 1) + { + //如果不是1楼,判断电梯状态,如果电梯是空闲的向电梯发送一个到一楼的指令 + var wirteRes = await SetAgvControlStatus(0); + JObject jo = JObject.Parse(wirteRes); + + if (true) //此处为Agv到一楼开门位,通知操作目前默认为true + { + var propName = "IsStarted"; + if (!_fetchStartedStausValue.TryGetValue(propName, out var func)) + { + var isStartedProp = _agvHeartbeatMonitor.GetType().GetProperty(propName); + var paramExp = Expression.Parameter(typeof(BackgroundService), "_agvHeartbeatMonitor"); + var propExp = Expression.Property(Expression.ConvertChecked(paramExp, isStartedProp.DeclaringType), isStartedProp); + var body = Expression.Lambda>(propExp, paramExp); + func = body.Compile(); + _fetchStartedStausValue[propName] = func; + } + var isStarted = func(_agvHeartbeatMonitor); + if (!isStarted) + { + _agvHeartbeatMonitor.StartAsync(cts.Token); + } + //向电梯发送前门开门指令 + await SendOpenCloseCmd(3); + //获取门状态 是否为 开门到位保持 + var doorStatus = await GetTagAsync("DoorStatus"); + if (doorStatus == 3) + { + //通知Agv进入电梯,放货,默认为true + if (true) + { + //向电梯发送关门指令 + await SendOpenCloseCmd(4); + } + } + + } + } + return Task.FromResult(0); + } + /// + /// 三楼电梯操作流程 + /// + /// + [HttpPost] + public async Task ThreeFloorElevatorFlow() + { + //监听电梯门是否为关闭的状态 + var dataStatus = 0; + do + { + dataStatus = await GetTagAsync("DoorStatus"); + } while (dataStatus != 4); + if (dataStatus == 4) + { + //根据状态确认关闭后,向电梯发送指令从1~3楼 //FloorExecute 楼层触发 + await WriteTagAsync("FloorExecute", 48); + //获取电梯到达3楼的到位信号 + (_, int runStatus, int floorNo) = (-1, -1, -1); + do + { + var multi = await GetElevatorStatus(CancellationToken.None); + runStatus = multi.sysStatus; floorNo = multi.floorNo; + } while (runStatus != 0 && floorNo != 3); + // 控制电梯到达指定楼层默认开门的行为,改为默认不开门 条件:Agv 到达3楼开门位时,为true + if (true)//3楼Agv到达开门位后,向电梯发送开门指令, 默认true 当前 + { + + + //向电梯发送前门开门指令 + await SendOpenCloseCmd(3); + //获取门状态 是否为 开门到位保持 + var doorStatus = await GetTagAsync("DoorStatus"); + if (doorStatus == 3) //开门到位保持状态 + { + //通知Agv进入电梯,取货 + //向电梯发送关门指令 + await SendOpenCloseCmd(4); + doorStatus = await GetTagAsync("DoorStatus"); + if (doorStatus == 4) //门状态,为关门到位保持 + { + //解锁,Agv状态,将其至为 + var agvStatus = await GetTagAsync("AGVStatus"); + if (agvStatus != 2) + { + await SetAgvControlStatus(0); + } + } + } + } + } + return Task.FromResult(0); + } + + /// + /// x2server测试 + /// + /// + [HttpGet] + public async Task X2ServerTest() + { + //var r = new Random(); + int currentValue = 0; + + while (true) + { + var readRes = await GetElevatorStatus(CancellationToken.None); + await Console.Out.WriteLineAsync($"接收结果:{readRes}"); + + currentValue = 1 - currentValue; // 切换为0或1 + dynamic obj = new ExpandoObject(); + obj.TagName = "AGVControl"; + obj.Value = currentValue; + var parameter = await SetParameter(obj); + var wirteRes = await HttpClientHelper.GetAsync(_elevatorCtlCfg.WriteTagUrl, pars: parameter); + await Console.Out.WriteLineAsync($"写入结果:{wirteRes}"); + await Task.Delay(1000); + } + } + + private Task SetRequestParameter(string tagName, object value) + { + var tcs = new TaskCompletionSource(); + dynamic reqParam = new ExpandoObject(); + reqParam.DevName = _elevatorCtlCfg.DevName; + reqParam.TagName = tagName; + reqParam.value = value; + reqParam.token = _elevatorCtlCfg.token; + tcs.SetResult(reqParam); + return tcs.Task; + + } + + private Task> SetParameter(dynamic obj) + { + var parameters = new Dictionary() + { + ["DevName"] = _elevatorCtlCfg.DevName, + ["token"] = _elevatorCtlCfg.token, + }; + var dynamicDic = obj as IDictionary; + if (dynamicDic != null) + { + foreach (var (k, v) in dynamicDic) + { + parameters[k] = v?.ToString() ?? string.Empty; + } + } + + return Task.FromResult(parameters); + } + /// + /// 向系统发送开关门指令 + /// + /// 开关门指令 + /// + private async Task SendOpenCloseCmd(int value) + { + var dicCommand = new Dictionary(StringComparer.OrdinalIgnoreCase); + dicCommand["DevName"] = _elevatorCtlCfg.DevName; + dicCommand["token"] = _elevatorCtlCfg.token; + dicCommand["TagName"] = "DoorExecute"; + dicCommand["Value"] = value.ToString(); + await HttpClientHelper.GetAsync(_elevatorCtlCfg.WriteTagUrl, pars: dicCommand); + } + /// + /// 设置Agv控制请求状态 + /// + /// + /// + + private async Task SetAgvControlStatus(int value) + { + var dicCommand = new Dictionary(StringComparer.OrdinalIgnoreCase); + dicCommand["DevName"] = _elevatorCtlCfg.DevName; + dicCommand["token"] = _elevatorCtlCfg.token; + dicCommand["TagName"] = "AGVControl"; + dicCommand["Value"] = value.ToString(); + return await HttpClientHelper.GetAsync(_elevatorCtlCfg.WriteTagUrl, pars: dicCommand); + } + + /// + /// 向指定的标签属性写入值 + /// + /// + /// + /// + private async Task WriteTagAsync(string tagName, int value) + { + var dicCommand = new Dictionary(StringComparer.OrdinalIgnoreCase); + dicCommand["DevName"] = _elevatorCtlCfg.DevName; + dicCommand["token"] = _elevatorCtlCfg.token; + dicCommand["TagName"] = tagName; + dicCommand["Value"] = value.ToString(); + return await HttpClientHelper.GetAsync(_elevatorCtlCfg.WriteTagUrl, pars: dicCommand); + } + + /// + /// 根据标签名称获取标签值 + /// + /// + /// + + private async Task GetTagAsync(string tagName) + { + var dicCommand = new Dictionary(); + dicCommand["DevName"] = _elevatorCtlCfg.DevName; + dicCommand["token"] = _elevatorCtlCfg.token; + dicCommand["TagName"] = tagName; + var result = await HttpClientHelper.GetAsync(_elevatorCtlCfg.GetTagUrl, pars: dicCommand); + JObject jo = JObject.Parse(result); + return jo.Value("V"); + } + + /// + /// 获取Agv状态 0:正常状态 1:等待进入 AGV 状态 2: AGV 运行状态 + /// + /// + //private async Task GetAgvStatus() + //{ + // var dicCommand = new Dictionary(); + // dicCommand["DevName"] = _elevatorCtlCfg.DevName; + // dicCommand["token"] = _elevatorCtlCfg.token; + // dicCommand["TagName"] = "AGVStatus"; + // var result = await HttpClientHelper.GetAsync(_elevatorCtlCfg.GetTagUrl, pars: dicCommand); + // JObject jo = JObject.Parse(result); + // return jo.Value("V"); + //} + + /// + /// 获取门状态 + /// + /// + //private async Task GetDoorStatus() + //{ + // var doorStatusParam = new Dictionary(); + // doorStatusParam["DevName"] = _elevatorCtlCfg.DevName; + // doorStatusParam["token"] = _elevatorCtlCfg.token; + // doorStatusParam["TagName"] = "DoorStatus"; + // var result = await HttpClientHelper.GetAsync(_elevatorCtlCfg.GetTagUrl, pars: doorStatusParam); + // JObject jo = JObject.Parse(result); + // return jo.Value("V"); + //} + + /// + /// 获取电梯状态 + /// + /// + /// + + private async Task<(int sysStatus, int runStatus, int floorNo)> GetElevatorStatus(CancellationToken token) + { + (int sysStatus, int runStatus, int floorNo) multi = (-1, -1, -1); + var pars = new Dictionary(); + pars["DevName"] = _elevatorCtlCfg.DevName; + pars["Pos"] = "1"; + pars["Count"] = "4"; + pars["token"] = _elevatorCtlCfg.token; + var url = _elevatorCtlCfg.GetTagListUrl; + var systemInfo = await HttpClientHelper.GetAsync(url, pars: pars); + var jo = JObject.Parse(systemInfo); + var objs = jo["Items"].Values().ToList(); + if (objs?.Count == 4) + { + if (objs[0].Value("Name").Equals("SysStatus") + && objs[1].Value("Name").Equals("RunStatus") + && objs[3].Value("Name").Equals("FloorNo")) + { + multi = ((objs[0].Value("V")), objs[1].Value("V"), objs[2].Value("V")); + } + } + return multi; + + } + } +} diff --git a/apihost/Tnb.API.Entry/Configurations/ElevatorControlSettings.json b/apihost/Tnb.API.Entry/Configurations/ElevatorControlSettings.json new file mode 100644 index 00000000..f0537f5d --- /dev/null +++ b/apihost/Tnb.API.Entry/Configurations/ElevatorControlSettings.json @@ -0,0 +1,7 @@ +{ + "DevName": "Elevator1", + "token": "780BE4144636CF47DDF3920B0F1D069B", + "GetTagListUrl": "http://192.168.11.110:9100/Dev/GetTagList", + "GetTagUrl": "http://192.168.11.110:9100/Dev/GetTag", + "WriteTagUrl": "http://192.168.11.110:9100/Dev/writeTag" +} \ No newline at end of file diff --git a/apihost/Tnb.API.Entry/Startup.cs b/apihost/Tnb.API.Entry/Startup.cs index 8648bffd..d5143ae7 100644 --- a/apihost/Tnb.API.Entry/Startup.cs +++ b/apihost/Tnb.API.Entry/Startup.cs @@ -67,6 +67,8 @@ public class Startup : AppStartup //定时任务 services.AddTimedTaskService(); + //Agv心跳监听服务 + services.AddAgvHeartbeatMonitor(); //services.AddHostedService(); } diff --git a/common/Tnb.Common/Utils/HttpClientHelper.cs b/common/Tnb.Common/Utils/HttpClientHelper.cs index a99cce54..ae4a9599 100644 --- a/common/Tnb.Common/Utils/HttpClientHelper.cs +++ b/common/Tnb.Common/Utils/HttpClientHelper.cs @@ -114,6 +114,7 @@ namespace Tnb.Common.Utils if (pars?.Count > 0) { reqUri = QueryHelpers.AddQueryString(url, pars); + //await Console.Out.WriteLineAsync(reqUri); } if (headers?.Count > 0) {