Files
serein-flow/Net462DllTest/Trigger/SiemensPlcDevice.cs

464 lines
17 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 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 : TaskFlowTrigger<PlcVarName>
{
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<PlcVarName, PlcVarModel> Model { get; }
private readonly object _lockObj = new object(); // 防止多次初始化读取任务
private readonly ConcurrentBag<Task> TimedRefreshTask = new ConcurrentBag<Task>(); // 定时读取任务
private readonly ConcurrentBag<PlcVarInfo> VarInfos = new ConcurrentBag<PlcVarInfo>(); // 所有变量信息
private readonly ConcurrentBag<PlcVarInfo> OnRefreshs = new ConcurrentBag<PlcVarInfo>(); // 数据变更后需要通知触发器的变量信息
private readonly ConcurrentBag<PlcVarInfo> OnChangeds = new ConcurrentBag<PlcVarInfo>(); // 读取读取后需要通知触发器的变量信息
public SiemensPlcDevice(PlcVarModel model)
{
this.Model = new GSModel<PlcVarName, PlcVarModel>(model);
LoadVarInfos();
}
/// <summary>
/// 加载变量信息
/// </summary>
private void LoadVarInfos()
{
foreach (var property in typeof(PlcVarModel).GetProperties())
{
var attribute = property.GetCustomAttribute<BindValueAttribute>();
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); // 无条件批量读取
}
}
/// <summary>
/// 开启定时批量读取任务
/// </summary>
/// <returns></returns>
public async Task OpenTimedRefreshAsync()
{
if (TimedRefreshTask.IsEmpty)
{
InitTimedRefreshTask();
}
IsTimedRefresh = true;
await Task.WhenAll(TimedRefreshTask.ToArray());
}
/// <summary>
/// 关闭定时任务
/// </summary>
public void CloseTimedRefresh() => IsTimedRefresh = false;
/// <summary>
/// 初始化定时刷新任务
/// </summary>
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);
}
}
}
/// <summary>
/// 定时读取PLC变量并刷新Model的值
/// </summary>
/// <param name="varInfo"></param>
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}");
await InvokeTriggerAsync(signal, newData);
}
}
});
TimedRefreshTask.Add(task);
}
public override string ToString()
{
return $"西门子Plc[{this.Version}-{this.IP}:{this.Port}]";
}
}
/// <summary>
/// 拓展方法
/// </summary>
public static class MyPlcExtension
{
/// <summary>
/// 缓存变量信息
/// </summary>
private static readonly Dictionary<PlcVarName, PlcVarInfo> VarInfoDict = new Dictionary<PlcVarName, PlcVarInfo>();
/// <summary>
/// 获取变量信息
/// </summary>
/// <param name="plcVarEnum"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static PlcVarInfo ToVarInfo(this PlcVarName plcVarEnum)
{
if (VarInfoDict.ContainsKey(plcVarEnum))
{
return VarInfoDict[plcVarEnum];
}
var plcValue = EnumHelper.GetAttributeValue<PlcVarName, PlcVarInfoAttribute, PlcVarInfo>(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;
}
/// <summary>
/// 读取PLC
/// </summary>
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;
}
/// <summary>
/// 转换数据类型写入PLC
/// </summary>
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);
}
}
/// <summary>
/// 类型转换
/// </summary>
/// <param name="dataType"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
//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;
//}
}
}