using IoTClient; using IoTClient.Clients.PLC; using IoTClient.Common.Enums; using IoTClient.Enums; using Net462DllTest.Enums; using Net462DllTest.Model; using Net462DllTest.Signal; using Net462DllTest.Utils; using Serein.Library; using Serein.Library.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; namespace Net462DllTest.Trigger { [AutoRegister] public class SiemensPlcDevice : FlowTrigger { public SiemensClient Client { get; set; } public SiemensVersion Version { get; set; } public string IP { get; set; } = "127.0.0.1"; public int Port { get; set; } = 102; public PlcState State { get; set; } = PlcState.PowerOff; public bool IsTimedRefresh { get; set; } = false; // 是否定时刷新 public IGSModel Model { get; } private readonly object _lockObj = new object(); // 防止多次初始化读取任务 private readonly ConcurrentBag TimedRefreshTask = new ConcurrentBag(); // 定时读取任务 private readonly ConcurrentBag VarInfos = new ConcurrentBag(); // 所有变量信息 private readonly ConcurrentBag OnRefreshs = new ConcurrentBag(); // 数据变更后需要通知触发器的变量信息 private readonly ConcurrentBag OnChangeds = new ConcurrentBag(); // 读取读取后需要通知触发器的变量信息 public SiemensPlcDevice(PlcVarModel model) { this.Model = new GSModel(model); LoadVarInfos(); } /// /// 加载变量信息 /// private void LoadVarInfos() { foreach (var property in typeof(PlcVarModel).GetProperties()) { var attribute = property.GetCustomAttribute(); if (attribute?.Value is PlcVarName varName) { var varInfo = varName.ToVarInfo(); VarInfos.Add(varInfo); if (varInfo.IsTimingRead) // 是否定时刷新 { switch (varInfo.NotificationType) { case PlcVarInfo.OnNotificationType.OnRefresh: OnRefreshs.Add(varInfo); // 变量读取时通知触发器的变量 break; case PlcVarInfo.OnNotificationType.OnChanged: OnChangeds.Add(varInfo); // 变量变更时才需要通知触发器的变量 break; } } } } } public void Init(SiemensVersion version, string ip, int port) { Client = new SiemensClient(version, ip, port); Version = version; IP = ip; Port = port; Client.Open(); } public void Close() { Client?.Close(); Client = null; } public void Write(PlcVarName varName, object value) { var varInfo = varName.ToVarInfo(); if (this.State == PlcState.Runing) { if (varInfo.IsReadOnly) { throw new Exception($"PLC变量{varInfo}当前禁止写入"); } else { Client.WriteVar(varInfo, value); // 尝试写入PLC Model.Set(varName, value); Console.WriteLine($"PLC变量{varInfo}写入数据:{value}"); } } else { throw new Exception($"PLC处于非预期状态{this.State}"); } } public object Read(PlcVarName varName) { var varInfo = varName.ToVarInfo(); var result = Client.ReadVar(varInfo);// 尝试读取数据 Model.Set(varName, result); // 缓存读取的数据 return result; } public void BatchRefresh() { foreach(var varInfo in VarInfos) { Read(varInfo.Name); // 无条件批量读取 } } /// /// 开启定时批量读取任务 /// /// public async Task OpenTimedRefreshAsync() { if (TimedRefreshTask.IsEmpty) { InitTimedRefreshTask(); } IsTimedRefresh = true; await Task.WhenAll(TimedRefreshTask.ToArray()); } /// /// 关闭定时任务 /// public void CloseTimedRefresh() => IsTimedRefresh = false; /// /// 初始化定时刷新任务 /// public void InitTimedRefreshTask() { Console.WriteLine("开始初始化刷新任务"); lock (_lockObj) { foreach (var varInfo in OnChangeds) { Console.WriteLine($"添加监视任务(OnChanged):{varInfo.Name}"); ScheduleTask(varInfo); } foreach (var varInfo in OnRefreshs) { Console.WriteLine($"添加监视任务(OnRefresh):{varInfo.Name}"); ScheduleTask(varInfo); } } } /// /// 定时读取PLC变量,并刷新Model的值 /// /// private void ScheduleTask(PlcVarInfo varInfo) { var task = Task.Run(async () => { var signal = varInfo.Name; object oldData; object newData; bool isNotification; while (true) { await Task.Delay(varInfo.Interval); if (!IsTimedRefresh || Client is null) break; oldData = Model.Get(signal); // 暂存旧数据 newData = Read(signal); // 获取新数据 if (varInfo.NotificationType == PlcVarInfo.OnNotificationType.OnRefresh) { isNotification = true; // 无条件触发通知 } else { isNotification = !oldData.Equals(newData); // 变更时才会触发通知 } if (isNotification) { Console.WriteLine($"VarName: {signal}\t\tOld Data: {oldData}\tNew Data: {newData}"); Trigger(signal, newData); } } }); TimedRefreshTask.Add(task); } public override string ToString() { return $"西门子Plc[{this.Version}-{this.IP}:{this.Port}]"; } } /// /// 拓展方法 /// public static class MyPlcExtension { /// /// 缓存变量信息 /// private static readonly Dictionary VarInfoDict = new Dictionary(); /// /// 获取变量信息 /// /// /// /// public static PlcVarInfo ToVarInfo(this PlcVarName plcVarEnum) { if (VarInfoDict.ContainsKey(plcVarEnum)) { return VarInfoDict[plcVarEnum]; } var plcValue = EnumHelper.GetBoundValue(plcVarEnum, attr => attr.Info) ?? throw new Exception($"获取变量异常:{plcVarEnum},没有标记PlcValueAttribute"); if (string.IsNullOrEmpty(plcValue.Address)) { throw new Exception($"获取变量异常:{plcVarEnum},变量地址为空"); } VarInfoDict.Add(plcVarEnum, plcValue); plcValue.Name = plcVarEnum; return plcValue; } /// /// 读取PLC /// public static object ReadVar(this SiemensClient client, PlcVarInfo varInfo) { if (client is null) { throw new ArgumentNullException($"PLC尚未初始化"); } object resultvalue; switch (varInfo.DataType) { case DataTypeEnum.String: var resultString = client.ReadString(varInfo.Address); if (!resultString.IsSucceed) throw new Exception(resultString.Err); resultvalue = resultString.Value; break; case DataTypeEnum.Bool: var resultBool = client.ReadBoolean(varInfo.Address); if (!resultBool.IsSucceed) throw new Exception(resultBool.Err); resultvalue = resultBool.Value; break; case DataTypeEnum.Float: var resultFloat = client.ReadFloat(varInfo.Address); if (!resultFloat.IsSucceed) throw new Exception(resultFloat.Err); resultvalue = resultFloat.Value; break; case DataTypeEnum.Double: var resultDouble = client.ReadDouble(varInfo.Address); if (!resultDouble.IsSucceed) throw new Exception(resultDouble.Err); resultvalue = resultDouble.Value; break; case DataTypeEnum.Byte: var resultByte = client.ReadByte(varInfo.Address); if (!resultByte.IsSucceed) throw new Exception(resultByte.Err); resultvalue = resultByte.Value; break; case DataTypeEnum.Int16: var resultInt16 = client.ReadInt16(varInfo.Address); if (!resultInt16.IsSucceed) throw new Exception(resultInt16.Err); resultvalue = resultInt16.Value; break; case DataTypeEnum.UInt16: var resultUint16 = client.ReadUInt16(varInfo.Address); if (!resultUint16.IsSucceed) throw new Exception(resultUint16.Err); resultvalue = resultUint16.Value; break; case DataTypeEnum.Int32: var resultInt32 = client.ReadInt32(varInfo.Address); if (!resultInt32.IsSucceed) throw new Exception(resultInt32.Err); resultvalue = resultInt32.Value; break; case DataTypeEnum.UInt32: var resultUInt32 = client.ReadUInt32(varInfo.Address); if (!resultUInt32.IsSucceed) throw new Exception(resultUInt32.Err); resultvalue = resultUInt32.Value; break; case DataTypeEnum.Int64: var resultInt64 = client.ReadInt64(varInfo.Address); if (!resultInt64.IsSucceed) throw new Exception(resultInt64.Err); resultvalue = resultInt64.Value; break; case DataTypeEnum.UInt64: var resultUInt64 = client.ReadUInt64(varInfo.Address); if (!resultUInt64.IsSucceed) throw new Exception(resultUInt64.Err); resultvalue = resultUInt64.Value; break; default: throw new NotImplementedException($"变量为指定数据类型,或是非预期的数据类型{varInfo}"); } return resultvalue; } /// /// 转换数据类型,写入PLC /// public static object WriteVar(this SiemensClient client, PlcVarInfo varInfo, object value) { if (client is null) { throw new ArgumentNullException($"PLC尚未初始化"); } DataTypeEnum dataType = varInfo.DataType; object convertValue; Result result; switch (dataType) { case DataTypeEnum.String: var @string = value.ToString(); convertValue = @string; result = client.Write(varInfo.Address, @string); break; case DataTypeEnum.Bool: var @bool = bool.Parse(value.ToString()); convertValue = @bool; result = client.Write(varInfo.Address, @bool); break; case DataTypeEnum.Float: var @float = float.Parse(value.ToString()); convertValue = @float; result = client.Write(varInfo.Address, @float); break; case DataTypeEnum.Double: var @double = double.Parse(value.ToString()); convertValue = @double; result = client.Write(varInfo.Address, @double); break; case DataTypeEnum.Byte: var @byte = byte.Parse(value.ToString()); convertValue = @byte; result = client.Write(varInfo.Address, @byte); break; case DataTypeEnum.Int16: var @short = short.Parse(value.ToString()); convertValue = @short; result = client.Write(varInfo.Address, @short); break; case DataTypeEnum.UInt16: var @ushort = ushort.Parse(value.ToString()); convertValue = @ushort; result = client.Write(varInfo.Address, @ushort); break; case DataTypeEnum.Int32: var @int = int.Parse(value.ToString()); convertValue = @int; result = client.Write(varInfo.Address, @int); break; case DataTypeEnum.UInt32: var @uint = uint.Parse(value.ToString()); convertValue = @uint; result = client.Write(varInfo.Address, @uint); break; case DataTypeEnum.Int64: var @long = long.Parse(value.ToString()); convertValue = @long; result = client.Write(varInfo.Address, @long); break; case DataTypeEnum.UInt64: var @ulong = ulong.Parse(value.ToString()); convertValue = @ulong; result = client.Write(varInfo.Address, @ulong); break; default: throw new NotImplementedException($"变量为指定数据类型,或是非预期的数据类型{varInfo}"); } if (result.IsSucceed) { return convertValue; } else { throw new Exception(result.Err); } } /// /// 类型转换 /// /// /// /// //public static Type ToDataType(this DataTypeEnum dataType) //{ // Type plcDataType; // switch (dataType) // { // case DataTypeEnum.String: // plcDataType = typeof(string); // break; // case DataTypeEnum.Bool: // plcDataType = typeof(bool); // break; // case DataTypeEnum.Float: // plcDataType = typeof(float); // break; // case DataTypeEnum.Double: // plcDataType = typeof(double); // break; // case DataTypeEnum.Byte: // plcDataType = typeof(byte); // break; // case DataTypeEnum.Int16: // plcDataType = typeof(short); // break; // case DataTypeEnum.UInt16: // plcDataType = typeof(ushort); // break; // case DataTypeEnum.Int32: // plcDataType = typeof(int); // break; // case DataTypeEnum.UInt32: // plcDataType = typeof(uint); // break; // case DataTypeEnum.Int64: // plcDataType = typeof(long); // break; // case DataTypeEnum.UInt64: // plcDataType = typeof(ulong); // break; // default: // throw new NotImplementedException(); // } // return plcDataType; //} } }