|
|
@@ -0,0 +1,703 @@
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using System.Net;
|
|
|
+using System.Net.Sockets;
|
|
|
+using TwinCAT.Ads;
|
|
|
+using TwinCAT.PlcOpen;
|
|
|
+using YSAI.Core.data;
|
|
|
+using YSAI.Core.@interface;
|
|
|
+using YSAI.Core.virtualAddress;
|
|
|
+using YSAI.Unility;
|
|
|
+using TwinCAT.TypeSystem;
|
|
|
+using System.Net.Http.Headers;
|
|
|
+using Newtonsoft.Json.Linq;
|
|
|
+using System.Reflection.Metadata;
|
|
|
+using TwinCAT.Ads.TypeSystem;
|
|
|
+using TwinCAT;
|
|
|
+
|
|
|
+namespace YSAI.Beckhoff.client
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 倍福PLC
|
|
|
+ /// </summary>
|
|
|
+ public class BeckhoffClientOperate : IBaseAbstract, IDaq
|
|
|
+ {
|
|
|
+ protected override string TAG => "BeckhoffOperate";
|
|
|
+ /// <summary>
|
|
|
+ /// 锁
|
|
|
+ /// </summary>
|
|
|
+ private static readonly object Lock = new object();
|
|
|
+ /// <summary>
|
|
|
+ /// 自身对象集合
|
|
|
+ /// </summary>
|
|
|
+ private static List<BeckhoffClientOperate> ThisObjList = new List<BeckhoffClientOperate>();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 单例模式
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static BeckhoffClientOperate Instance(BeckhoffClientData.Basics basics)
|
|
|
+ {
|
|
|
+ if (ThisObjList.Count >= MaxInstanceCount)
|
|
|
+ {
|
|
|
+ throw new Exception(ExceedMaxInstanceCountTips);
|
|
|
+ }
|
|
|
+ BeckhoffClientOperate? exp = ThisObjList.FirstOrDefault(c => c.basics.Comparer(basics).result);
|
|
|
+ if (exp == null)
|
|
|
+ {
|
|
|
+ lock (Lock)
|
|
|
+ {
|
|
|
+ if (ThisObjList.Count(c => c.basics.Comparer(basics).result) > 0)
|
|
|
+ {
|
|
|
+ return ThisObjList.First(c => c.basics.Comparer(basics).result);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ BeckhoffClientOperate exp2 = new BeckhoffClientOperate(basics);
|
|
|
+ ThisObjList.Add(exp2);
|
|
|
+ return exp2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return exp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构造函数
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="basics"></param>
|
|
|
+ public BeckhoffClientOperate(BeckhoffClientData.Basics basics)
|
|
|
+ {
|
|
|
+ this.basics = basics;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 基础数据
|
|
|
+ /// </summary>
|
|
|
+ private BeckhoffClientData.Basics basics { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 虚拟地址
|
|
|
+ /// </summary>
|
|
|
+ private VirtualAddressManage VAM = new VirtualAddressManage();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 全局令牌
|
|
|
+ /// </summary>
|
|
|
+ private CancellationTokenSource Cts = new CancellationTokenSource();
|
|
|
+ /// <summary>
|
|
|
+ /// ads客户端
|
|
|
+ /// </summary>
|
|
|
+ private AdsClient adsClient;
|
|
|
+ /// <summary>
|
|
|
+ /// 通知句柄容器
|
|
|
+ /// </summary>
|
|
|
+ private ConcurrentDictionary<uint, AddressDetails> NotificationHandleIoc = new ConcurrentDictionary<uint, AddressDetails>();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 任务集合
|
|
|
+ /// </summary>
|
|
|
+ private ConcurrentDictionary<CancellationTokenSource, Task> TaskArray;
|
|
|
+ /// <summary>
|
|
|
+ /// 数据队列
|
|
|
+ /// </summary>
|
|
|
+ private ConcurrentQueue<AdsNotificationExEventArgs> DataQueue;
|
|
|
+ /// <summary>
|
|
|
+ /// 任务处理
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="token">任务令牌</param>
|
|
|
+ /// <returns>任务</returns>
|
|
|
+ private Task TaskHandle(CancellationTokenSource token)
|
|
|
+ {
|
|
|
+ //起个新线程处理
|
|
|
+ return Task.Factory.StartNew(() =>
|
|
|
+ {
|
|
|
+ while (!token.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ //队列数据
|
|
|
+ AdsNotificationExEventArgs? queueData;
|
|
|
+ while (DataQueue.TryDequeue(out queueData))
|
|
|
+ {
|
|
|
+ if (queueData != null && !token.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //地址详情
|
|
|
+ AddressDetails addressDetails = queueData.UserData as AddressDetails;
|
|
|
+ //得到的数据
|
|
|
+ string value = queueData.Value.ToString();
|
|
|
+ //设置参数
|
|
|
+ ConcurrentDictionary<string, AddressValue> param = new ConcurrentDictionary<string, AddressValue>();
|
|
|
+ //处理数据
|
|
|
+ AddressValue addressValue = YSAI.Core.handler.AddressHandler.ExecuteDispose(addressDetails, value); //数据
|
|
|
+ //添加或更新
|
|
|
+ param.AddOrUpdate(addressDetails.AddressName, addressValue, (k, v) => addressValue);
|
|
|
+
|
|
|
+ //响应
|
|
|
+ OnEventHandler(this, new EventResult(true, $"点位数据更新", param, Core.@enum.ResultType.KeyValue));
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ OnEventHandler(this, new EventResult(false, $"异常:{ex.Message}"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //队列里面的数据处理完休息一下
|
|
|
+ Thread.Sleep(basics.TaskHandleInterval);
|
|
|
+ }
|
|
|
+ }, token.Token);
|
|
|
+ }
|
|
|
+
|
|
|
+ #region 事件
|
|
|
+ /// <summary>
|
|
|
+ /// 当符号版本发生变化时发生。
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_AdsSymbolVersionChanged(object? sender, AdsSymbolVersionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "版本更新", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当从AdsServer收到通知取消注册/无效时发生
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_AdsNotificationsInvalidated(object? sender, AdsNotificationsInvalidatedEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "订阅/取消订阅", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当ADS设备向客户端发送通知时发生
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_AdsNotificationEx(object? sender, AdsNotificationExEventArgs e)
|
|
|
+ {
|
|
|
+ DataQueue.Enqueue(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 数据变更通知
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender"></param>
|
|
|
+ /// <param name="e"></param>
|
|
|
+ //private void Symbol_ValueChanged(object? sender, ValueChangedEventArgs e)
|
|
|
+ //{
|
|
|
+
|
|
|
+ //}
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 在通知管理期间发生异常时发生
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_AdsNotificationError(object? sender, AdsNotificationErrorEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "订阅发生异常", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// ADS状态改变时发生
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_AdsStateChanged(object? sender, AdsStateChangedEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "状态已改变", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// 当连接状态已更改时发生
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_ConnectionStateChanged(object? sender, TwinCAT.ConnectionStateChangedEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "连接状态已改变", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 路由器状态改变事件
|
|
|
+ /// </summary>
|
|
|
+ private void AdsClient_RouterStateChanged(object? sender, AmsRouterNotificationEventArgs e)
|
|
|
+ {
|
|
|
+ OnEventHandler(sender, new EventResult(true, "路由器状态改变事件", e, Core.@enum.ResultType.Object));
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 释放
|
|
|
+ /// </summary>
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ Off();
|
|
|
+ GC.Collect();
|
|
|
+ GC.SuppressFinalize(this);
|
|
|
+ ThisObjList.Remove(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult GetStatus()
|
|
|
+ {
|
|
|
+ if (adsClient != null && adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(Depart("GetStatus"),true,"已连接");
|
|
|
+ }
|
|
|
+ return Break(Depart("GetStatus"), false, "未连接");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> GetStatusAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(()=> GetStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Off()
|
|
|
+ {
|
|
|
+ string SN = Depart("Off");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient == null || !adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+ if (Cts != null)
|
|
|
+ {
|
|
|
+ Cts.Cancel();
|
|
|
+ }
|
|
|
+ if (NotificationHandleIoc.Count > 0)
|
|
|
+ {
|
|
|
+ NotificationHandleIoc.Clear();
|
|
|
+ }
|
|
|
+ //任务清空
|
|
|
+ if (TaskArray != null)
|
|
|
+ {
|
|
|
+ foreach (var item in TaskArray)
|
|
|
+ {
|
|
|
+ item.Key.Cancel();
|
|
|
+ }
|
|
|
+ foreach (var item in TaskArray)
|
|
|
+ {
|
|
|
+ item.Value.Wait();
|
|
|
+ item.Value.Dispose();
|
|
|
+ }
|
|
|
+ TaskArray.Clear();
|
|
|
+ TaskArray = null;
|
|
|
+ }
|
|
|
+ //队列数据清空
|
|
|
+ if (DataQueue != null)
|
|
|
+ {
|
|
|
+ DataQueue.Clear();
|
|
|
+ DataQueue = null;
|
|
|
+ }
|
|
|
+ adsClient.Close();
|
|
|
+ adsClient.Dispose();
|
|
|
+ adsClient = null;
|
|
|
+ return Break(SN, true);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> OffAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => Off());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult On()
|
|
|
+ {
|
|
|
+ string SN = Depart("On");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient != null && adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "已连接");
|
|
|
+ }
|
|
|
+
|
|
|
+ //当队列为空,初始化队列
|
|
|
+ if (DataQueue == null)
|
|
|
+ {
|
|
|
+ DataQueue = new ConcurrentQueue<AdsNotificationExEventArgs>();
|
|
|
+ }
|
|
|
+
|
|
|
+ //任务为空创建任务
|
|
|
+ if (TaskArray == null)
|
|
|
+ {
|
|
|
+ TaskArray = new ConcurrentDictionary<CancellationTokenSource, Task>();
|
|
|
+ //创建任务
|
|
|
+ for (int i = 0; i < basics.TaskNumber; i++)
|
|
|
+ {
|
|
|
+ CancellationTokenSource token = new CancellationTokenSource();
|
|
|
+ TaskArray.TryAdd(token, TaskHandle(token));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //实例化对象
|
|
|
+ adsClient = new AdsClient();
|
|
|
+ //连接到服务器
|
|
|
+ adsClient.Connect(new AmsNetId(basics.AmsNetID), basics.Port);
|
|
|
+ //事件注册
|
|
|
+ adsClient.AdsNotificationError += AdsClient_AdsNotificationError;
|
|
|
+ adsClient.AdsNotificationEx += AdsClient_AdsNotificationEx;
|
|
|
+ adsClient.AdsNotificationsInvalidated += AdsClient_AdsNotificationsInvalidated;
|
|
|
+ adsClient.AdsStateChanged+= AdsClient_AdsStateChanged;
|
|
|
+ adsClient.AdsSymbolVersionChanged+= AdsClient_AdsSymbolVersionChanged;
|
|
|
+ adsClient.ConnectionStateChanged+= AdsClient_ConnectionStateChanged;
|
|
|
+ adsClient.RouterStateChanged += AdsClient_RouterStateChanged;
|
|
|
+
|
|
|
+ return Break(SN,true);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> OnAsync()
|
|
|
+ {
|
|
|
+ return Task.Run(() => On());
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Read(Address address)
|
|
|
+ {
|
|
|
+ string SN = Depart("Read");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient == null || !adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+ //节点数据
|
|
|
+ ConcurrentDictionary<string, AddressValue> param = new ConcurrentDictionary<string, AddressValue>();
|
|
|
+ //遍历
|
|
|
+ foreach (var item in address.AddressArray)
|
|
|
+ {
|
|
|
+ //是不是虚拟地址
|
|
|
+ bool IsVA = false;
|
|
|
+ //初始化虚拟地址
|
|
|
+ VAM.InitVirtualAddress(item, out IsVA);
|
|
|
+ //值
|
|
|
+ string? Value = string.Empty;
|
|
|
+
|
|
|
+ if (IsVA)
|
|
|
+ {
|
|
|
+ Value = VAM.Read(item);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //先获取句柄
|
|
|
+ ResultHandle handle = adsClient.CreateVariableHandleAsync(item.AddressName, Cts.Token).Result;
|
|
|
+ //获取到的值
|
|
|
+ ResultAnyValue resultAnyValue = null;
|
|
|
+ //时间格式读取
|
|
|
+ byte[] readBuffer = new byte[8];
|
|
|
+ //时间格式返回值
|
|
|
+ ResultRead resultRead = null;
|
|
|
+ //判断是否正常
|
|
|
+ if (handle.Succeeded)
|
|
|
+ {
|
|
|
+ //获取到了句柄
|
|
|
+ switch (item.AddressDataType)
|
|
|
+ {
|
|
|
+ case Core.@enum.DataType.Bool:
|
|
|
+ resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(bool), Cts.Token).Result;
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.String:
|
|
|
+ case Core.@enum.DataType.Char:
|
|
|
+ resultAnyValue = adsClient.ReadAnyStringAsync(handle.Handle, byte.MaxValue, StringMarshaler.DefaultEncoding, Cts.Token).Result;
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Double:
|
|
|
+ case Core.@enum.DataType.Float:
|
|
|
+ resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(double), Cts.Token).Result;
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Int:
|
|
|
+ resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(uint), Cts.Token).Result;
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.DateTime:
|
|
|
+ resultAnyValue = adsClient.ReadAnyAsync(handle.Handle, typeof(DateTime), Cts.Token).Result;
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Time:
|
|
|
+ resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TIME.MarshalSize), Cts.Token).Result;
|
|
|
+ if (resultRead.Succeeded)
|
|
|
+ {
|
|
|
+ TIME plcTime = null;
|
|
|
+ PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TIME.MarshalSize), out plcTime);
|
|
|
+ if (plcTime != null)
|
|
|
+ {
|
|
|
+ Value = plcTime.Time.ToString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Date:
|
|
|
+ resultRead = adsClient.ReadAsync(handle.Handle, readBuffer.AsMemory(0, TIME.MarshalSize), Cts.Token).Result;
|
|
|
+ if (resultRead.Succeeded)
|
|
|
+ {
|
|
|
+ DATE plcDate = null;
|
|
|
+ PrimitiveTypeMarshaler.Default.Unmarshal(readBuffer.AsSpan(0, TIME.MarshalSize), out plcDate);
|
|
|
+ if (plcDate != null)
|
|
|
+ {
|
|
|
+ Value = plcDate.ToString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ Value = $"不支持{item.AddressDataType}类型读取";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Value = $"未获取到句柄ID";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (resultAnyValue != null)
|
|
|
+ {
|
|
|
+ if (resultAnyValue.Succeeded)
|
|
|
+ {
|
|
|
+ Value = resultAnyValue.Value.ToString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //数据处理
|
|
|
+ AddressValue addressValue = Core.handler.AddressHandler.ExecuteDispose(item, Value);
|
|
|
+
|
|
|
+ //数据添加
|
|
|
+ param.AddOrUpdate(item.AddressName, addressValue, (k, v) => addressValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (param.Count > 0)
|
|
|
+ {
|
|
|
+ //返回读取的数据
|
|
|
+ return Break(SN, true, RData: param, RType: Core.@enum.ResultType.KeyValue);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return Break(SN, false, "读取失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> ReadAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Read(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Subscribe(Address address)
|
|
|
+ {
|
|
|
+ string SN = Depart("Subscribe");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient == null || !adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+ if (address == null || address.AddressArray?.Count <= 0)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "参数不能为空");
|
|
|
+ }
|
|
|
+ //失败消息
|
|
|
+ List<string> FailMessage = new List<string>();
|
|
|
+ foreach (var item in address.AddressArray)
|
|
|
+ {
|
|
|
+ //异步通知结果
|
|
|
+ ResultHandle resultHandle = null;
|
|
|
+ Type type = null;
|
|
|
+ switch (item.AddressDataType)
|
|
|
+ {
|
|
|
+ case Core.@enum.DataType.Bool:
|
|
|
+ type = typeof(bool);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.String:
|
|
|
+ case Core.@enum.DataType.Char:
|
|
|
+ type = typeof(string);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Double:
|
|
|
+ case Core.@enum.DataType.Float:
|
|
|
+ type = typeof(double);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Int:
|
|
|
+ type = typeof(uint);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.DateTime:
|
|
|
+ type = typeof(DateTime);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Date:
|
|
|
+ type = typeof(DateTimeOffset);
|
|
|
+ break;
|
|
|
+ case Core.@enum.DataType.Time:
|
|
|
+ type = typeof(TimeSpan);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ resultHandle = adsClient.AddDeviceNotificationExAsync(item.AddressName, NotificationSettings.ImmediatelyOnChange, item, type, new int[] { byte.MaxValue }, Cts.Token).Result;
|
|
|
+ if (!resultHandle.Succeeded)
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{item.AddressName} 订阅添加失败,{resultHandle.ErrorCode}");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ NotificationHandleIoc.AddOrUpdate(resultHandle.Handle, item, (k, v) => item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (FailMessage.Count > 0)
|
|
|
+ {
|
|
|
+ return Break(SN, false, FailMessage.ToJson());
|
|
|
+ }
|
|
|
+ return Break(SN, true, "订阅成功");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public Task<OperateResult> SubscribeAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Subscribe(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult UnSubscribe(Address address)
|
|
|
+ {
|
|
|
+ string SN = Depart("UnSubscribe");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient == null || !adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+ if (address == null || address.AddressArray?.Count <= 0)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "参数不能为空");
|
|
|
+ }
|
|
|
+ //失败消息
|
|
|
+ List<string> FailMessage = new List<string>();
|
|
|
+
|
|
|
+ foreach (var item in address.AddressArray)
|
|
|
+ {
|
|
|
+ //通知句柄
|
|
|
+ uint Handle = NotificationHandleIoc.FirstOrDefault(c => c.Value.Comparer(item).result).Key;
|
|
|
+
|
|
|
+ //判断是否正常
|
|
|
+ if (!Handle.Equals(0u))
|
|
|
+ {
|
|
|
+ //异步通知结果
|
|
|
+ ResultAds resultAds = adsClient.DeleteDeviceNotificationAsync(Handle, Cts.Token).Result;
|
|
|
+ if (!resultAds.Succeeded)
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{item.AddressName},订阅移除失败,{resultAds.ErrorCode}");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //移除容器中的此此句柄
|
|
|
+ NotificationHandleIoc.Remove(Handle, out _);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{item.AddressName},未获取到句柄ID");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (FailMessage.Count > 0)
|
|
|
+ {
|
|
|
+ return Break(SN, false, FailMessage.ToJson());
|
|
|
+ }
|
|
|
+ return Break(SN, true, "移除订阅成功");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> UnSubscribeAsync(Address address)
|
|
|
+ {
|
|
|
+ return Task.Run(() => UnSubscribe(address));
|
|
|
+ }
|
|
|
+
|
|
|
+ public OperateResult Write<V>(ConcurrentDictionary<string, V> Values)
|
|
|
+ {
|
|
|
+ string SN = Depart("Write");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (adsClient == null || !adsClient.IsConnected)
|
|
|
+ {
|
|
|
+ return Break(SN, false, "未连接");
|
|
|
+ }
|
|
|
+
|
|
|
+ //失败消息
|
|
|
+ List<string> FailMessage = new List<string>();
|
|
|
+
|
|
|
+ foreach (var item in Values)
|
|
|
+ {
|
|
|
+ KeyValuePair<string, V> Param = item;
|
|
|
+ //判断是不是虚拟点
|
|
|
+ if (VAM.IsVirtualAddress(Param.Key))
|
|
|
+ {
|
|
|
+ if (!VAM.Write(Param.Key, Param.Value.ToString()))
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //先获取句柄
|
|
|
+ ResultHandle handle = adsClient.CreateVariableHandleAsync(Param.Key, Cts.Token).Result;
|
|
|
+ //异步写入结果
|
|
|
+ ResultWrite resultWrite = null;
|
|
|
+ //判断是否正常
|
|
|
+ if (handle.Succeeded)
|
|
|
+ {
|
|
|
+ if (typeof(V).Name.Contains("String"))
|
|
|
+ {
|
|
|
+ PrimitiveTypeMarshaler converter = new PrimitiveTypeMarshaler(StringMarshaler.DefaultEncoding);
|
|
|
+ byte[] writeBuffer = new byte[System.Text.Encoding.Default.GetByteCount(Param.Value.ToString()) * 2];
|
|
|
+ int byteCount = converter.Marshal(Param.Value, writeBuffer);
|
|
|
+ if (byteCount > 0)
|
|
|
+ {
|
|
|
+ resultWrite = adsClient.WriteAsync(handle.Handle, writeBuffer, Cts.Token).Result;
|
|
|
+ if (!resultWrite.Succeeded)
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败,{resultWrite.ErrorCode}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败,字节数小于等于零");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (typeof(V).Name.Contains("TimeSpan") || typeof(V).Name.Contains("DateTimeOffset") || typeof(V).Name.Contains("DateTime")) //时间||日期||时间日期
|
|
|
+ {
|
|
|
+ byte[] writeBuffer = new byte[8];
|
|
|
+ PrimitiveTypeMarshaler.Default.Marshal(Param.Value, writeBuffer.AsSpan());
|
|
|
+ resultWrite = adsClient.WriteAsync(handle.Handle, writeBuffer.AsMemory(0, TIME.MarshalSize), Cts.Token).Result;
|
|
|
+ if (!resultWrite.Succeeded)
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败,{resultWrite.ErrorCode}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ resultWrite = adsClient.WriteAnyAsync(handle.Handle, Param.Value, Cts.Token).Result;
|
|
|
+ if (!resultWrite.Succeeded)
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败,{resultWrite.ErrorCode}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ FailMessage.Add($"{Param.Key},写入失败,未获取到句柄ID");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (FailMessage.Count > 0)
|
|
|
+ {
|
|
|
+ return Break(SN, false, FailMessage.ToJson());
|
|
|
+ }
|
|
|
+ return Break(SN, true, "写入成功");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ return Break(SN, false, ex.Message, Exception: ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Task<OperateResult> WriteAsync<V>(ConcurrentDictionary<string, V> Values)
|
|
|
+ {
|
|
|
+ return Task.Run(() => Write(Values));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|