Files
6098/Cowain.Bake.Communication/PLC/PLCBase.cs

831 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<int, Variable> VariableDic { get; set; } = new Dictionary<int, Variable>();
public ObservableCollection<Variable> VariableList { get; set; } = new ObservableCollection<Variable>(); //数组遍历器
public List<TagEntity> TagList { get; set; } = new List<TagEntity>();
public List<StorageArea> Storages { get; set; } = new List<StorageArea>(); //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<BasicInfoViewModel>().PLCStatus = IsConnect;
//_unityContainer.Resolve<DeviceConfigService>().UpdateStatus(this.Name, IsConnect);
_unityContainer.Resolve<DeviceConfigService>().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<Int16>(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];
}
}
/// <summary>
/// 赋值类型长度
/// </summary>
/// <param name="tag"></param>
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<IGrouping<string, Variable>> //按地址类型分组如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<string,List<Variable>>
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<Variable> 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<T> Read<T>(string tag)
{
return null;
}
public virtual OperateResult<T> GetValue<T>(string paramName)
{
return null;
}
public virtual OperateResult<T> GetValue<T>(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<T>(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<dynamic>(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<IScriptManage>();
// 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<DataValue> Reads(NodeId[] nodeIds)
{
return null;
}
public abstract OperateResult Write<T>(string address, T data, int maxCount = 5);
public virtual OperateResult Write<T>(dynamic plc, string address, T data, int maxCount = 5)
{
var res = Write<T>(plc, address, data);
for (int i = 0; i < maxCount; i++)
{
if (res.IsSuccess)
{
break;
}
Thread.Sleep(15);
res = Write<T>(plc, address, data);
}
return res;
}
public virtual OperateResult Write<T>(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<short>))
{
if (data is short[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<short> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(ushort[]) || type == typeof(List<ushort>))
{
if (data is ushort[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<ushort> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(int[]) || type == typeof(List<int>))
{
if (data is int[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<int> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(uint[]) || type == typeof(List<uint>))
{
if (data is uint[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<uint> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(long[]) || type == typeof(List<long>))
{
if (data is long[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<long> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(ulong[]) || type == typeof(List<ulong>))
{
if (data is ulong[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<ulong> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(float[]) || type == typeof(List<float>))
{
if (data is float[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<float> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(double[]) || type == typeof(List<double>))
{
if (data is double[] arr)
{
resultdata = plc.Write(address, arr);
}
else if (data is List<double> list)
{
//list
resultdata = plc.Write(address, list.ToArray());
}
}
else if (type == typeof(bool[]) || type == typeof(List<bool>))
{
if (data is bool[] arrb)
{
resultdata = plc.Write(address, arrb);
}
else if (data is List<bool> listb)
{
resultdata = plc.Write(address, listb.ToArray());
}
}
else if (type == typeof(byte[]) || type == typeof(List<byte>))
{
if (data is byte[] arrby)
{
resultdata = plc.Write(address, arrby);
}
else if (data is List<byte> 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}");
}
}
}
}