using Cowain.Bake.Common.Core; using Cowain.Bake.Communication.Interface; using Cowain.Bake.Model.Entity; using HslCommunication; using HslCommunication.Core; using Newtonsoft.Json; using OpcUaHelper; using Prism.Mvvm; using Prism.Services.Dialogs; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Unity; using Cowain.Bake.Common.Models; using Cowain.Bake.Model.Models; using Cowain.Bake.Common; using Opc.Ua; using Cowain.Bake.BLL; using Cowain.Bake.Common.Enums; namespace Cowain.Bake.Communication.PLC { public abstract class PLCBase : BindableBase, IPLCDevice { protected IUnityContainer _unityContainer; protected IDialogService _dialogService; public virtual IReadWriteNet PLC { get; set; } public OpcUaClient OPC { get; set; } public IByteTransform ByteTransform { get; set; } public virtual string IpAddress { get; set; } = "127.0.0.1"; public virtual int Port { get; set; } public virtual byte Slot { get; set; } public virtual string ConnectString { get; set; } CancellationTokenSource cts = new CancellationTokenSource(); protected PLCBase(IUnityContainer unityContainer, IDialogService dialogService) { _unityContainer = unityContainer; _dialogService = dialogService; } private int readUseTime; public int ReadUseTime { get { return readUseTime; } set { SetProperty(ref readUseTime, value); } } private bool isConnect; public bool IsConnect { get { return isConnect; } set { SetProperty(ref isConnect, value); } } public int Id { get; set; } public string Name { get; set; } public string DeviceName { get; set; } public Dictionary VariableDic { get; set; } = new Dictionary(); public ObservableCollection VariableList { get; set; } = new ObservableCollection(); //数组遍历器 public List TagList { get; set; } = new List(); public List Storages { get; set; } = new List(); //1个PLC一个,首地址及所对应的标签信息 public abstract void Close(); public abstract void Connect(); public abstract void GetJsonParam(string param); bool? lastConnect = null; public virtual void StartRead() { Task.Run(async () => { await Task.Delay(2000); //等待OPC初始化完成 Stopwatch sw = new Stopwatch(); while (true) { if (Global.AppExit) { return; } try { if (!IsConnect) { if (VariableList != null && VariableList.Count > 0) { foreach (var tag in VariableList) { tag.Quality = false; } } Connect(); } else { if (IsConnect) { sw.Restart(); ReadStorageArea(); sw.Stop(); ReadUseTime = (int)sw.ElapsedMilliseconds; sw.Reset(); if (ReadUseTime > 3000) { LogHelper.Instance.Warn($"{ConnectString}:刷新所有数据时间:" + ReadUseTime + "ms"); } } } } catch(Exception ex) { LogHelper.Instance.Error($"全局读取数据失败:{ex.Message}"); } if (lastConnect != IsConnect) { lastConnect = IsConnect; //_unityContainer.Resolve().PLCStatus = IsConnect; //_unityContainer.Resolve().UpdateStatus(this.Name, IsConnect); _unityContainer.Resolve().UpdateStatus(this.Id, IsConnect); } // 触发 GC GC.Collect(); GC.WaitForPendingFinalizers(); await Task.Delay(1000); } }); } public virtual void Heartbeat() { Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { if (Global.AppExit) { return; } await Task.Delay(Global.HEARTBEAT_INTERVAL_TIME); if (null == PLC || !IsConnect) { continue; } var node = (from secondaryList in Storages from item in secondaryList.VariableList where item.VarDesc == "心跳" select item).FirstOrDefault(); if (null == node) { LogHelper.Instance.Warn("查询心跳节点失败"); continue; } OperateResult result = Write(node.Address + node.VarName, 1); //只写一个心跳就行了 if (!result.IsSuccess) { //LogHelper.Instance.Warn($"DeviceId:{Storages[0].DeviceId}:写心跳节点失败"); } } }, cts.Token); } public virtual int GetReadMaxLength(Variable variable) { return 101; } public virtual void AddressSplit(TagEntity tag) { //按modbus tcp解析 格式:x=3;0 string[] ss = tag.Address.Split(';'); if (ss.Length == 2) { VariableDic[(int)tag.Id].AddressType = ss[0]; VariableDic[(int)tag.Id].Address = ss[1]; } } /// /// 赋值类型长度 /// /// public virtual void SetReadLength(TagEntity tag) { string varType = tag.VarType; Match m = Regex.Match(tag.VarType, @"(?<=\[)([0-9]+)(?=\])", RegexOptions.Singleline); if (m.Success) { //说明是数组[] //VariableDic[tag.VarName].ReadLength = Convert.ToInt32(m.Value); 这是个BUG VariableDic[(int)tag.Id].ArrayLength = Convert.ToInt32(m.Value); Match mType = Regex.Match(tag.VarType, @"(.*)(?=\[)", RegexOptions.Singleline); varType = mType.Value; } switch (varType) { case "BOOL": case "BYTE": case "INT16": case "UINT16": VariableDic[(int)tag.Id].ReadLength = 1; break; case "INT32": case "UINT32": case "FLOAT": VariableDic[(int)tag.Id].ReadLength = 2; break; default: LogHelper.Instance.Error("不支持的数据类型:" + tag.VarType); break; } } public virtual void AnalysisAddress() { foreach (var tag in TagList) { AddressSplit(tag); SetReadLength(tag); } } public virtual void GetStorageArea() { Storages.Clear(); var query = (from v in VariableList //query = List> //按地址类型分组:如x=3一组 orderby Convert.ToInt16(v.Address), v.ReadLength group v by v.AddressType into a where a.Count() > 0 orderby a.Key select a).ToList(); //modbus地址类型分组 /* modbus地址分类 0x01 读取线圈的操作, 0x02 读取离散的操作, 0x03 读取寄存器的值, 0x04 读取输入寄存器的值 0x05 写一个线圈操作, 0x06 写一个寄存器值, 0x0F 批量写线圈操作, 0x10 批量写寄存器值, 0x16 掩码写入的寄存器(大部分的modbus设备不支持) */ int k = query.Count; foreach (var g in query) { int k1 = g.Count(); var first = g.First();//first:Variable,g:Dictionary> var totalLengthQuery1 = (from a in g //分块读数据 group a by (Convert.ToInt16(a.Address) - Convert.ToInt16(first.Address) + a.ReadLength) / GetReadMaxLength(a)).ToList(); var totalLengthQuery = (from a in g //分块读数据,100个数据分一组,在地址类型上,再按100为一组,分第二次组 group a by (Convert.ToInt16(a.Address) - Convert.ToInt16(first.Address) + a.ReadLength) / GetReadMaxLength(a) into b select new { VarList = b, //Regex.Replace(a.VarType, @"\[.*\]", string.Empty) Id = b.Key, //(Convert.ToInt16(a.Address) - Convert.ToInt16(first.Address) + a.ReadLength) / GetReadMaxLength(a) AddressType = b.First().AddressType, //AddressType类型是Variable StartAddress = b.First().Address, GroupReadLength = (Convert.ToInt16(b.Last().Address) - Convert.ToInt16(b.First().Address) + b.Last().ReadLength) }).ToList(); totalLengthQuery.ForEach(x => { List variables = x.VarList.ToList(); foreach (var variable in variables) { //计算offset,字节偏移 variable.Offset = (Convert.ToInt16(variable.Address) - Convert.ToInt16(x.StartAddress)) * 2; } Storages.Add(new StorageArea() { //Id = x.Id, AddressType = x.AddressType, StartAddress = x.StartAddress, Len = x.GroupReadLength, VariableList = variables }); }); } GetReadAddress(); } public virtual void GetReadAddress() { foreach (var storage in Storages) { storage.ReadAddress = storage.AddressType + storage.StartAddress; } } public virtual OperateResult Read(string tag) { return null; } public virtual OperateResult GetValue(string paramName) { return null; } public virtual OperateResult GetValue(string paramName, int machineId, int number = 0) { return null; } public virtual Variable GetVariable(string paramName, int machineId, int number = 0) { return null; } public virtual OperateResult Write(string paramName, int machineId, int number, T data) { return null; } public virtual void ReadStorageArea() { try { foreach (var item in Storages) { bool readError = false; for (int i = 0; i < Global.MAX_READS; i++) { var read = PLC.Read(item.ReadAddress, (ushort)item.Len); if (read.IsSuccess) { IsConnect = true; readError = false; //读成功 if (item.VariableList != null) { foreach (var variable in item.VariableList) { AnalyseData(read.Content, variable); } } break; } else { foreach (var val in item.VariableList) { val.Quality = false; } LogHelper.Instance.Error($"读错误,,ErrorCode:{read.ErrorCode},失败:" + read.Message); } readError = true; Thread.Sleep(30); } Thread.Sleep(30); if (readError) { IsConnect = false; } } } catch(Exception ex) { IsConnect = false; LogHelper.Instance.Error(ex.Message); } } public virtual void AnalyseData(byte[] data, Variable variable) { if (data != null && data.Length > 0) { if (variable.ArrayLength > 1) { //说明是数组[] //variable.ArrayLength = Convert.ToInt32(m.Value); switch (variable.VarType) { case "BOOL": variable.CurValue = ByteTransform.TransBool(data, variable.Offset); variable.Quality = true; break; case "BYTE": variable.CurValue = ByteTransform.TransByte(data, variable.Offset, variable.ArrayLength); variable.Quality = true; break; case "INT16": variable.CurValue = Array.ConvertAll(ByteTransform.TransInt16(data, variable.Offset, variable.ArrayLength), ele => ele.ToString()); variable.Quality = true; break; case "UINT16": variable.CurValue = Array.ConvertAll(ByteTransform.TransUInt16(data, variable.Offset, variable.ArrayLength), ele => ele.ToString()); variable.Quality = true; break; case "INT32": variable.CurValue = Array.ConvertAll(ByteTransform.TransInt32(data, variable.Offset, variable.ArrayLength), ele => ele.ToString()); variable.Quality = true; break; case "UINT32": variable.Value = Array.ConvertAll(ByteTransform.TransUInt32(data, variable.Offset, variable.ArrayLength), ele => ele.ToString()); variable.Quality = true; break; case "FLOAT": variable.Value = Array.ConvertAll(ByteTransform.TransSingle(data, variable.Offset, variable.ArrayLength), ele => ele.ToString()); variable.Quality = true; break; //case "STR": // variable.CurValue = ByteTransform.TransString(data, variable.Offset, variable.ArrayLength, Encoding.Default); // variable.Quality = true; // break; default: LogHelper.Instance.Error("不支持的数据类型:" + variable.VarType); break; } } else { //值类型tostring不会装箱 switch (variable.VarType) { case "BOOL": variable.CurValue = ByteTransform.TransBool(data, 1).ToString(); variable.Quality = true; break; case "BYTE": variable.CurValue = ByteTransform.TransByte(data, variable.Offset).ToString(); variable.Quality = true; break; case "INT16": variable.CurValue = ByteTransform.TransInt16(data, variable.Offset).ToString(); variable.Quality = true; break; case "UINT16": data[0] = 33; data[1] = 251; variable.CurValue = ByteTransform.TransUInt16(data, variable.Offset).ToString(); variable.Quality = true; break; case "INT32": variable.CurValue = ByteTransform.TransInt32(data, variable.Offset).ToString(); variable.Quality = true; break; case "UINT32": variable.CurValue = ByteTransform.TransUInt32(data, variable.Offset).ToString(); variable.Quality = true; break; case "FLOAT": float f = ByteTransform.TransSingle(data, variable.Offset); variable.CurValue = ByteTransform.TransSingle(data, variable.Offset).ToString(); variable.Quality = true; break; default: LogHelper.Instance.Error("不支持的数据类型:" + variable.VarType); break; } //variable.Value = new string[] { variable.CurValue }; } if (variable.TrigEnable) { dynamic d = JsonConvert.DeserializeObject(variable.TrigJson); string fName = d.Func; string vals = d.Value; string key = d.Key; string desc = d.Desc; string serviceName = d.Service; if (string.IsNullOrEmpty(serviceName)) { serviceName = "TrigerService"; } if (!string.IsNullOrEmpty(variable.OldValue.ToString())) { if (string.Equals(variable.OldValue, variable.CurValue)) { if (vals == variable.CurValue.ToString()) { //异步执行 //this.TrigServiceAsync(serviceName, fName, VariableDic[tag.VarName].FuncJson); //int scriptId = variable.ScriptId; //if (scriptId > 0) //{ // var sm = _unityContainer.Resolve(); // var script = sm.ScriptList.Where(x => x.Id == scriptId).FirstOrDefault(); // if (script != null) // { // string funcJson = variable.Json; // if (!string.IsNullOrEmpty(funcJson)) // { // TrigScriptAsync(script, variable.Json); // } // } // else // { // LogHelper.Instance.Error($"脚本执行异常:没找到脚本-{scriptId}"); // } //} } } } } variable.OldValue = variable.CurValue; } } //private void CopyBytes(byte[] data, Variable variable, int len) //{ // variable.CurValue = new byte[len]; // Buffer.BlockCopy(data, variable.Offset, variable.CurValue, 0, len); //} public virtual OperateResult Writes(string[] tags, object[] values, int maxCount = Global.MAX_READS) { return null; } public virtual List Reads(NodeId[] nodeIds) { return null; } public abstract OperateResult Write(string address, T data, int maxCount = 5); public virtual OperateResult Write(dynamic plc, string address, T data, int maxCount = 5) { var res = Write(plc, address, data); for (int i = 0; i < maxCount; i++) { if (res.IsSuccess) { break; } Thread.Sleep(15); res = Write(plc, address, data); } return res; } public virtual OperateResult Write(dynamic plc, string address, T data) { if (!IsConnect) { return new OperateResult("未连接PLC"); } Type type = typeof(T); OperateResult resultdata = null; try { if (type == typeof(short)) { if (data is short one) { resultdata = plc.Write(address, one); } } else if (type == typeof(ushort)) { if (data is ushort one) { resultdata = plc.Write(address, one); } } else if (type == typeof(int)) { if (data is int one) { resultdata = plc.Write(address, one); } } else if (type == typeof(uint)) { if (data is uint one) { resultdata = plc.Write(address, one); } } else if (type == typeof(long)) { if (data is long one) { resultdata = plc.Write(address, one); } } else if (type == typeof(ulong)) { if (data is ulong one) { resultdata = plc.Write(address, one); } } else if (type == typeof(float)) { if (data is float one) { resultdata = plc.Write(address, one); } } else if (type == typeof(double)) { if (data is double one) { resultdata = plc.Write(address, one); } } else if (type == typeof(bool)) { if (data is bool state) { resultdata = plc.Write(address, state); } } else if (type == typeof(short[]) || type == typeof(List)) { if (data is short[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(ushort[]) || type == typeof(List)) { if (data is ushort[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(int[]) || type == typeof(List)) { if (data is int[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(uint[]) || type == typeof(List)) { if (data is uint[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(long[]) || type == typeof(List)) { if (data is long[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(ulong[]) || type == typeof(List)) { if (data is ulong[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(float[]) || type == typeof(List)) { if (data is float[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(double[]) || type == typeof(List)) { if (data is double[] arr) { resultdata = plc.Write(address, arr); } else if (data is List list) { //list resultdata = plc.Write(address, list.ToArray()); } } else if (type == typeof(bool[]) || type == typeof(List)) { if (data is bool[] arrb) { resultdata = plc.Write(address, arrb); } else if (data is List listb) { resultdata = plc.Write(address, listb.ToArray()); } } else if (type == typeof(byte[]) || type == typeof(List)) { if (data is byte[] arrby) { resultdata = plc.Write(address, arrby); } else if (data is List listb) { resultdata = plc.Write(address, listb.ToArray()); } } else if (type == typeof(string)) { if (data is string str) { resultdata = plc.Write(address, str, Encoding.ASCII); } } } catch (Exception ex) { IsConnect = false; LogHelper.Instance.Error($"写PLC数据失败:{address},{ex}"); } if (!resultdata.IsSuccess) { LogHelper.Instance.Error("写PLC数据失败:" + address); } return resultdata; } public bool CheckBytesEquals(byte[] a, byte[] b) { if (a == null || b == null) return false; if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) { return false; } } return true; } //public Task TrigScriptAsync(ScriptEntity script, string funcJson) //{ // //return Task.Run(() => // //{ // // return RunSrcipt(script, funcJson); // //}); //} //private OperateResult RunSrcipt(ScriptEntity script, string funcJson) //{ // Stopwatch watch = new Stopwatch(); // watch.Start(); // try // { // //OperateResult result = ScriptEngine.RunScript(_unityContainer, funcJson, script); // //LogHelper.Instance.Info($"脚本【{script.Name}】执行用时:" + watch.ElapsedMilliseconds + "ms"); // return null; // } // catch (Exception ex) // { // LogHelper.Instance.Error("脚本执行异常", ex); // return null; // } //} //public Task TrigServiceAsync(string serviceName, string fName, string funcJson) //{ // return Task.Run(() => // { // TrigService(serviceName, fName, funcJson); // }); //} public void TrigService(string serviceName, string fName, string funcJson) { Assembly asm = Assembly.GetExecutingAssembly(); Type type = asm.GetType($"Cowain.Bake.Communication.Service.{serviceName}"); var ts = _unityContainer.Resolve(type); object[] parameters = new object[1]; parameters[0] = funcJson; try { MethodInfo mt = ts.GetType().GetMethod(fName); if (mt != null) { mt.Invoke(ts, parameters); } } catch (Exception ex) { LogHelper.Instance.Error($"TrigService:{ex}"); } } } }