初始化上传
This commit is contained in:
587
常用工具集/Utility/Network/EIP/CIPHelper.cs
Normal file
587
常用工具集/Utility/Network/EIP/CIPHelper.cs
Normal file
@@ -0,0 +1,587 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace 欧姆龙EIP调试.EIP
|
||||
{
|
||||
/// <summary>
|
||||
/// CIP通信工具类
|
||||
/// </summary>
|
||||
public class CIPHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// IP地址
|
||||
/// </summary>
|
||||
public string IpAddress { get; set; }
|
||||
/// <summary>
|
||||
/// 通信端口号
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (client == null)
|
||||
return false;
|
||||
return client.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Socket client;
|
||||
public int SessionHandle = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 默认无参构造
|
||||
/// </summary>
|
||||
public CIPHelper()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法,传递通信IP与端口
|
||||
/// </summary>
|
||||
public CIPHelper(string ip, int port)
|
||||
{
|
||||
this.IpAddress = ip;
|
||||
this.Port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立连接
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
//连接
|
||||
try
|
||||
{
|
||||
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
client.Connect(new IPEndPoint(IPAddress.Parse(IpAddress), Port));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册会话
|
||||
/// </summary>
|
||||
public bool RegisterSession()
|
||||
{
|
||||
//注册会话
|
||||
if (client != null && client.Connected)
|
||||
{
|
||||
byte[] registerSessionBytes = { (byte)0x65, (byte)0x00, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
|
||||
client.Send(registerSessionBytes);
|
||||
Thread.Sleep(20);
|
||||
byte[] rev = new byte[1024];
|
||||
try
|
||||
{
|
||||
int length = client.Receive(rev);
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(rev, 0, data, 0, length);
|
||||
if (data[0] == 0x65)
|
||||
return GetSessionHandle(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标签值
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns></returns>
|
||||
public object ReadTag(string tag)
|
||||
{
|
||||
if (client == null)
|
||||
return null;
|
||||
if (!client.Connected)
|
||||
return null;
|
||||
if (SessionHandle == 0)
|
||||
return null;
|
||||
byte[] d = GetReadTagBytes(tag);
|
||||
client.Send(d);
|
||||
Thread.Sleep(20);
|
||||
byte[] rev = new byte[1024];
|
||||
try
|
||||
{
|
||||
int length = client.Receive(rev);
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(rev, 0, data, 0, length);
|
||||
if (data[0] == 0x6f)
|
||||
return ReadTagVal(data);
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteTag(string tag, object value)
|
||||
{
|
||||
if (client == null)
|
||||
return false;
|
||||
if (!client.Connected)
|
||||
return false;
|
||||
if (SessionHandle == 0)
|
||||
return false;
|
||||
byte[] d = GetWriteTagBytes(tag, value);
|
||||
client.Send(d);
|
||||
Thread.Sleep(20);
|
||||
byte[] rev = new byte[1024];
|
||||
try
|
||||
{
|
||||
int length = client.Receive(rev);
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(rev, 0, data, 0, length);
|
||||
if (data[0] == 0x6f)
|
||||
return WriteTagVal(data);
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注销会话
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool UnRegisterSession()
|
||||
{
|
||||
if (client == null)
|
||||
return false;
|
||||
if (!client.Connected)
|
||||
return false;
|
||||
if (SessionHandle == 0)
|
||||
return false;
|
||||
List<byte> UnregisterSession = new List<byte>();
|
||||
UnregisterSession.AddRange(new List<byte> { (byte)0x66, (byte)0x00, (byte)0x00, (byte)0x00 });
|
||||
UnregisterSession.AddRange(BitConverter.GetBytes(SessionHandle));
|
||||
UnregisterSession.AddRange(new List<byte> { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 });
|
||||
try
|
||||
{
|
||||
client.Send(UnregisterSession.ToArray());
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接
|
||||
/// </summary>
|
||||
public void DisConnect()
|
||||
{
|
||||
|
||||
if (client == null)
|
||||
return;
|
||||
//断开
|
||||
try
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
client.Close();
|
||||
}
|
||||
client = null;
|
||||
SessionHandle = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
client = null;
|
||||
SessionHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Private
|
||||
|
||||
/// <summary>
|
||||
/// 解析写标签数据
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool WriteTagVal(byte[] data)
|
||||
{
|
||||
int byte_index = 0;
|
||||
short var1 = BitConverter.ToInt16(data, 0);//命令码
|
||||
short var2 = BitConverter.ToInt16(data, 2);//去除Hearder后的长度
|
||||
if ((var2 + 24) != data.Length)//24是Hearder封装头的长度 封装头长度+后面报文长度 应该等于总长度 否则报文错误
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int sessionHandle = BitConverter.ToInt32(data, 4);
|
||||
if (sessionHandle != this.SessionHandle)
|
||||
{
|
||||
//如果会话句柄不一致 返回
|
||||
return false;
|
||||
}
|
||||
int state = BitConverter.ToInt32(data, 8);
|
||||
if (state != 0)
|
||||
{
|
||||
//会话状态不正确
|
||||
return false;
|
||||
}
|
||||
//00 00 00 00 01 00 02 00 00 00 00 00 B2 00 08 00 CC 00 00 00 C1 00 00 00
|
||||
//发送方描述
|
||||
byte_index = 12; //8
|
||||
//选项 4字节
|
||||
byte_index = 20;
|
||||
//接口句柄 4字节
|
||||
byte_index = 24;
|
||||
//超时 2字节
|
||||
byte_index = 28;
|
||||
//项数 2字节
|
||||
byte_index = 30;
|
||||
//连接的地址项 2字节
|
||||
byte_index = 32;
|
||||
//连接地址项长度 2字节
|
||||
byte_index = 34;
|
||||
//未连接数据项 2字节
|
||||
byte_index = 36;
|
||||
//连接长度 2字节
|
||||
byte_index = 38;
|
||||
//数据开始
|
||||
byte_index = 40;
|
||||
for (int i = byte_index; i < data.Length; i++)
|
||||
{
|
||||
if (data[i] != 0xCD)
|
||||
continue;
|
||||
//填充字节 1字节
|
||||
if (BitConverter.ToInt16(data, i + 2) != 0x00)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析读标签数据
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private object ReadTagVal(byte[] data)
|
||||
{
|
||||
int byte_index = 0;
|
||||
short var1 = BitConverter.ToInt16(data, 0);//命令码
|
||||
short var2 = BitConverter.ToInt16(data, 2);//去除Hearder后的长度
|
||||
if ((var2 + 24) != data.Length)//24是Hearder封装头的长度 封装头长度+后面报文长度 应该等于总长度 否则报文错误
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int sessionHandle = BitConverter.ToInt32(data, 4);
|
||||
if (sessionHandle != this.SessionHandle)
|
||||
{
|
||||
//如果会话句柄不一致 返回
|
||||
return null;
|
||||
}
|
||||
int state = BitConverter.ToInt32(data, 8);
|
||||
if (state != 0)
|
||||
{
|
||||
//会话状态不正确
|
||||
return null;
|
||||
}
|
||||
//发送方描述
|
||||
byte_index = 12; //8
|
||||
//选项 4字节
|
||||
byte_index = 20;
|
||||
//接口句柄 4字节
|
||||
byte_index = 24;
|
||||
//超时 2字节
|
||||
byte_index = 28;
|
||||
//项数 2字节
|
||||
byte_index = 30;
|
||||
//连接的地址项 2字节
|
||||
byte_index = 32;
|
||||
//连接地址项长度 2字节
|
||||
byte_index = 34;
|
||||
//未连接数据项 2字节
|
||||
byte_index = 36;
|
||||
//连接长度 2字节
|
||||
byte_index = 38;
|
||||
//数据开始
|
||||
byte_index = 40;
|
||||
for (int i = byte_index; i < data.Length; i++)
|
||||
{
|
||||
if (data[i] != 0xCC)
|
||||
continue;
|
||||
//服务标识 0xCC
|
||||
//填充字节 1字节
|
||||
if (BitConverter.ToInt16(data, i + 2) != 0x00) // data[var4] != 0x00 || data[var4 + 1] != 0x00)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//数据类型
|
||||
short dataType = BitConverter.ToInt16(data, i + 4);
|
||||
if (dataType == 0x00C1)//BOOL
|
||||
{
|
||||
bool value = Convert.ToBoolean(((short)data[i + 6] & 0x00ff) | ((short)data[i + 7] << 8));
|
||||
return value;
|
||||
}
|
||||
if (dataType == 0x00D1)//byte
|
||||
{
|
||||
byte value = data[i + 6];
|
||||
return value;
|
||||
}
|
||||
if (dataType == 0x00C3)//int
|
||||
{
|
||||
short value = BitConverter.ToInt16(data, i + 6);// ((short)data[i+6] & 0x00ff) | ((short)data[i+7] << 8);
|
||||
return value;
|
||||
}
|
||||
if (dataType == 0x00C4)//long型
|
||||
{
|
||||
int value = BitConverter.ToInt32(data, i + 6);
|
||||
return value;
|
||||
}
|
||||
if (dataType == 0x00CA)//float
|
||||
{
|
||||
float value = BitConverter.ToSingle(data, i + 6);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 写标签报文数据
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private byte[] GetWriteTagBytes(string tag, object value)
|
||||
{
|
||||
byte[] TagStringToAscii;
|
||||
byte[] Var4 = Encoding.Default.GetBytes(tag);
|
||||
if (Var4.Length % 2 == 0)
|
||||
{
|
||||
TagStringToAscii = new byte[Var4.Length];
|
||||
for (int i = 0; i < Var4.Length; i++)
|
||||
{
|
||||
TagStringToAscii[i] = Var4[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TagStringToAscii = new byte[Var4.Length + 1];
|
||||
for (int i = 0; i < Var4.Length; i++)
|
||||
{
|
||||
TagStringToAscii[i] = Var4[i];
|
||||
}
|
||||
TagStringToAscii[Var4.Length] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
//CIP协议数据 =================================================
|
||||
List<byte> Cip_Msg = new List<byte>();
|
||||
Cip_Msg.Add((byte)0x4D);//服务标识
|
||||
Cip_Msg.Add((byte)((TagStringToAscii.Length + 2) / 2));//CIP长度多少字 从标识开始 大读取标签长度结束
|
||||
Cip_Msg.Add((byte)0x91);//固定
|
||||
Cip_Msg.Add((byte)TagStringToAscii.Length);//PLC标签长度 多少个字节
|
||||
Cip_Msg.AddRange(TagStringToAscii);//添加标签
|
||||
//根据类型
|
||||
if (value is bool)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0xC1, 0x00 });
|
||||
if ((bool)value)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0x01, 0x00 });
|
||||
}
|
||||
else
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0x00, 0x00 });
|
||||
}
|
||||
}
|
||||
else if (value is byte)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0xD1, 0x00 });
|
||||
Cip_Msg.Add((byte)value);
|
||||
}
|
||||
else if (value is short)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0xC3, 0x00 });
|
||||
Cip_Msg.AddRange(BitConverter.GetBytes((short)value));
|
||||
}
|
||||
else if (value is int)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0xC4, 0x00 });
|
||||
Cip_Msg.AddRange(BitConverter.GetBytes((int)value));
|
||||
}
|
||||
else if (value is float)
|
||||
{
|
||||
Cip_Msg.AddRange(new byte[] { 0xCA, 0x00 });
|
||||
Cip_Msg.AddRange(BitConverter.GetBytes((float)value));
|
||||
}
|
||||
List<byte> Slot = new List<byte> { (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00 }; //槽号
|
||||
List<byte> Cip_Msg0 = new List<byte>();
|
||||
Cip_Msg0.Add((byte)0x52);//命令
|
||||
Cip_Msg0.Add((byte)0X02);//请求路径长度
|
||||
Cip_Msg0.AddRange(new byte[] { 0x20, 0x06, 0x24, 0x01 });//默认请求路径
|
||||
Cip_Msg0.AddRange(new byte[] { 0x0A, 0xF0 });//默认超时
|
||||
Cip_Msg0.AddRange(BitConverter.GetBytes((short)Cip_Msg.Count)); //后面报文长度 不包含PLC槽号
|
||||
Cip_Msg0.AddRange(Cip_Msg);//添加CIP报文
|
||||
|
||||
List<byte> C_S_D = new List<byte>();
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//接口句柄 CIP
|
||||
C_S_D.AddRange(new byte[] { 0x01, 0x00 });//超时时间 默认
|
||||
C_S_D.AddRange(new byte[] { 0x02, 0x00 });//项数 默认
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址项 默认
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址长度 默认
|
||||
C_S_D.AddRange(new byte[] { 0xB2, 0x00 });//未连接项 默认
|
||||
C_S_D.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + Slot.Count)));//CIP报文包的长度 包括槽号
|
||||
|
||||
|
||||
List<byte> Header = new List<byte>();
|
||||
Header.AddRange(new byte[] { 0x6F, 0x00 });//命令码
|
||||
Header.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + C_S_D.Count + Slot.Count)));//后面报文的长度 特定命令数据 和 CIP报文长度 和PLC槽号
|
||||
Header.AddRange(BitConverter.GetBytes(SessionHandle));//添加会话句柄
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//添加状态
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //发送方描述
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//选项默认
|
||||
|
||||
|
||||
|
||||
List<byte> EtherNet_IP_CIP_MSG = new List<byte>();//单标签读取完整报文
|
||||
EtherNet_IP_CIP_MSG.AddRange(Header);//添加Header 封装头
|
||||
EtherNet_IP_CIP_MSG.AddRange(C_S_D);//添加特定命令数据
|
||||
EtherNet_IP_CIP_MSG.AddRange(Cip_Msg0);//添加CIP报文
|
||||
EtherNet_IP_CIP_MSG.AddRange(Slot);//添加槽号
|
||||
return EtherNet_IP_CIP_MSG.ToArray();
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 读标签报文数据
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns></returns>
|
||||
private byte[] GetReadTagBytes(string tag)
|
||||
{
|
||||
byte[] TagStringToAscii;
|
||||
byte[] Var4 = Encoding.Default.GetBytes(tag);
|
||||
if (Var4.Length % 2 == 0)
|
||||
{
|
||||
TagStringToAscii = new byte[Var4.Length];
|
||||
for (int i = 0; i < Var4.Length; i++)
|
||||
{
|
||||
TagStringToAscii[i] = Var4[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TagStringToAscii = new byte[Var4.Length + 1];
|
||||
for (int i = 0; i < Var4.Length; i++)
|
||||
{
|
||||
TagStringToAscii[i] = Var4[i];
|
||||
}
|
||||
TagStringToAscii[Var4.Length] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
//CIP协议数据 =================================================
|
||||
List<byte> Cip_Msg = new List<byte>();
|
||||
Cip_Msg.Add((byte)0x4C);//服务标识
|
||||
Cip_Msg.Add((byte)((TagStringToAscii.Length + 2) / 2));//CIP长度多少字 从标识开始 大读取标签长度结束
|
||||
Cip_Msg.Add((byte)0x91);//固定
|
||||
Cip_Msg.Add((byte)TagStringToAscii.Length);//PLC标签长度 多少个字节
|
||||
Cip_Msg.AddRange(TagStringToAscii);//添加标签
|
||||
Cip_Msg.Add((byte)0x01);
|
||||
Cip_Msg.Add((byte)0x00); // 读取长度
|
||||
|
||||
List<byte> Slot = new List<byte> { (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00 }; //槽号
|
||||
|
||||
List<byte> Cip_Msg0 = new List<byte>();
|
||||
Cip_Msg0.Add((byte)0x52);//命令
|
||||
Cip_Msg0.Add((byte)0X02);//请求路径长度
|
||||
Cip_Msg0.AddRange(new byte[] { 0x20, 0x06, 0x24, 0x01 });//默认请求路径
|
||||
Cip_Msg0.AddRange(new byte[] { 0x0A, 0xF0 });//默认超时
|
||||
Cip_Msg0.AddRange(BitConverter.GetBytes((short)Cip_Msg.Count)); //后面报文长度 不包含PLC槽号
|
||||
Cip_Msg0.AddRange(Cip_Msg);//添加CIP报文
|
||||
|
||||
List<byte> C_S_D = new List<byte>();
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//接口句柄 CIP
|
||||
C_S_D.AddRange(new byte[] { 0x01, 0x00 });//超时时间 默认
|
||||
C_S_D.AddRange(new byte[] { 0x02, 0x00 });//项数 默认
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址项 默认
|
||||
C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址长度 默认
|
||||
C_S_D.AddRange(new byte[] { 0xB2, 0x00 });//未连接项 默认
|
||||
C_S_D.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + Slot.Count)));//CIP报文包的长度 包括槽号
|
||||
|
||||
|
||||
List<byte> Header = new List<byte>();
|
||||
Header.Add((byte)0x6F); Header.Add((byte)0x00);//命令码
|
||||
Header.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + C_S_D.Count + Slot.Count)));//后面报文的长度 特定命令数据 和 CIP报文长度 和PLC槽号
|
||||
Header.AddRange(BitConverter.GetBytes(SessionHandle));//添加会话句柄
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//添加状态
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //发送方描述
|
||||
Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//选项默认
|
||||
|
||||
|
||||
|
||||
List<byte> EtherNet_IP_CIP_MSG = new List<byte>();//单标签读取完整报文
|
||||
EtherNet_IP_CIP_MSG.AddRange(Header);//添加Header 封装头
|
||||
EtherNet_IP_CIP_MSG.AddRange(C_S_D);//添加特定命令数据
|
||||
EtherNet_IP_CIP_MSG.AddRange(Cip_Msg0);//添加CIP报文
|
||||
EtherNet_IP_CIP_MSG.AddRange(Slot);//添加槽号
|
||||
return EtherNet_IP_CIP_MSG.ToArray();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册会话结果解析
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool GetSessionHandle(byte[] data)
|
||||
{
|
||||
//short sessionLength = BitConverter.ToInt16(data, 2);// (Convert.ToInt16(data[3]) * 256) | Convert.ToInt16(data[2]);
|
||||
//byte[] var1 = new byte[4];
|
||||
//for (int i = 0; i < 4; i++)
|
||||
//{
|
||||
// var1[i] = data[sessionLength + 4 + i];
|
||||
//}
|
||||
int state = BitConverter.ToInt32(data, 8);
|
||||
if (state == 0)//判断报文状态
|
||||
{
|
||||
SessionHandle = BitConverter.ToInt32(data, 4);
|
||||
//SessionHandle = new byte[sessionLength];
|
||||
//for (int i = 0; i < sessionLength; i++)
|
||||
//{
|
||||
// SessionHandle[i] = data[4 + i];
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
601
常用工具集/Utility/Network/FtpHelper.cs
Normal file
601
常用工具集/Utility/Network/FtpHelper.cs
Normal file
@@ -0,0 +1,601 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Printing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace 常用工具集.Utility.Network
|
||||
{
|
||||
|
||||
public class FtpHelper
|
||||
{
|
||||
private string ip;
|
||||
private int port = 21;
|
||||
private string ftpUserId;
|
||||
private string ftpPassword;
|
||||
private Encoding encoding;
|
||||
private string FtpUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"ftp://{ip}:{port}/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FtpHelper(string ip) : this(ip, 21) { }
|
||||
|
||||
public FtpHelper(string ip, int port) : this(ip, 21, Encoding.GetEncoding("GB2312")) { }
|
||||
/// <summary>
|
||||
/// 匿名
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
public FtpHelper(string ip, Encoding encoding) : this(ip, 21, encoding) { }
|
||||
|
||||
public FtpHelper(string ip, int port, Encoding encoding) : this(ip, port, "anonymous", "", encoding) { }
|
||||
|
||||
|
||||
public FtpHelper(string ip, string userName, string password) : this(ip, 21, userName, password) { }
|
||||
|
||||
public FtpHelper(string ip, int port, string userName, string password) : this(ip, port, userName, password, Encoding.UTF8) { }
|
||||
/// <summary>
|
||||
/// 指定用户名密码
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="userName"></param>
|
||||
/// <param name="password"></param>
|
||||
|
||||
public FtpHelper(string ip, string userName, string password, Encoding encoding) : this(ip, 21, userName, password, encoding) { }
|
||||
|
||||
/// <summary>
|
||||
/// 指定用户名密码
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="userName"></param>
|
||||
/// <param name="password"></param>
|
||||
|
||||
public FtpHelper(string ip, int port, string userName, string password, Encoding encoding)
|
||||
{
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.ftpUserId = userName;
|
||||
this.ftpPassword = password;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获得文件的最后更新时间
|
||||
/// </summary>
|
||||
/// <param name="ftpPath"></param>
|
||||
/// <param name="ftpUserId"></param>
|
||||
/// <param name="ftpPassword"></param>
|
||||
/// <param name="relativeFilePath"></param>
|
||||
/// <returns></returns>
|
||||
public DateTime GetFileLastModified(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.UseBinary = true;
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
DateTime dt = response.LastModified;
|
||||
response.Close();
|
||||
response = null;
|
||||
request = null;
|
||||
return dt;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.GetFileLastModified 获得文件的最后更新时间 错误");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列出ftp目录下的所有文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath"></param>
|
||||
/// <param name="ftpUserId"></param>
|
||||
/// <param name="ftpPassword"></param>
|
||||
/// <returns></returns>
|
||||
public List<FtpFileInfo> GetList(string ftpPath)
|
||||
{
|
||||
List<string> fileList = GetFileDetail(ftpPath);
|
||||
List<FtpFileInfo> list = new List<FtpFileInfo>();
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.ListDirectory;
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
|
||||
string str = encoding.GetString(stream.ToArray());
|
||||
foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (line == "." || line == "..")
|
||||
continue;
|
||||
string detail = fileList.Where(it => it.EndsWith(line)).FirstOrDefault();
|
||||
if (detail == null || detail.Length == 0)
|
||||
{
|
||||
continue;
|
||||
|
||||
}
|
||||
if (detail.ToUpper().Contains("<DIR>") || detail.ToUpper().Contains("DR"))
|
||||
{
|
||||
list.Add(new FtpFileInfo { IsDirectory = true, Name = line });
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new FtpFileInfo { IsDirectory = false, Name = line });
|
||||
}
|
||||
}
|
||||
return list.OrderByDescending(it => it.IsDirectory).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.GetFile 列出ftp目录下的所有文件 错误");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<string> GetFileDetail(string ftpPath)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
|
||||
string str = encoding.GetString(stream.ToArray());
|
||||
foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
list.Add(line);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.GetFile 列出ftp目录下的所有文件 错误");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath"></param>
|
||||
/// <param name="ftpUserId"></param>
|
||||
/// <param name="ftpPassword"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsFileExist(string ftpPath, string fileName)
|
||||
{
|
||||
List<FtpFileInfo> list = GetList(ftpPath);
|
||||
if (list.Any(it => !it.IsDirectory && it.Name == fileName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#region 文件夹是否存在
|
||||
/// <summary>
|
||||
/// 文件夹是否存在
|
||||
/// </summary>
|
||||
/// <param name="ftpPath"></param>
|
||||
/// <param name="folderName"></param>
|
||||
/// <returns></returns>
|
||||
public bool FolderExists(string ftpPath, string folderName)
|
||||
{
|
||||
List<FtpFileInfo> list = GetList(ftpPath);
|
||||
if (list.Any(it => it.IsDirectory && it.Name == folderName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 创建文件夹
|
||||
/// <summary>
|
||||
/// 创建文件夹
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="folderName">文件夹名称</param>
|
||||
public bool CreateFolder(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "MKD";
|
||||
((FtpWebResponse)request.GetResponse()).Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 文件是否存在
|
||||
/// <summary>
|
||||
/// 文件是否存在
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="ftpUserId">用户名</param>
|
||||
/// <param name="ftpPassword">密码</param>
|
||||
/// <param name="relativeFilePath">文件相对路径</param>
|
||||
public bool FileExists(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
string folderName = Path.GetDirectoryName(ftpPath);
|
||||
string fileName = Path.GetFileName(ftpPath);
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "LIST";
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
|
||||
string str = encoding.GetString(stream.ToArray());
|
||||
foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!line.ToUpper().Contains("DIR") && !line.ToUpper().Contains("DR"))
|
||||
{
|
||||
int pos = line.LastIndexOf(" ");
|
||||
string strFileName = line.Substring(pos).Trim();
|
||||
if (strFileName == fileName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.FileExists 判断文件是否存在 错误");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 下载文件
|
||||
/// <summary>
|
||||
/// 下载文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="ftpUserId">用户名</param>
|
||||
/// <param name="ftpPassword">密码</param>
|
||||
/// <param name="relativeFilePath">文件相对路径</param>
|
||||
public MemoryStream DownloadFile(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "RETR";
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
|
||||
|
||||
return stream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.DownloadFile 下载文件 错误");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="ftpUserId">用户名</param>
|
||||
/// <param name="ftpPassword">密码</param>
|
||||
/// <param name="relativeFilePath">文件相对路径</param>
|
||||
public void DownloadFile(string ftpPath, string localFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "RETR";
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
FileStream stream = new FileStream(localFile, FileMode.OpenOrCreate, FileAccess.Write);
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
stream.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.DownloadFile 下载文件 错误");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 异步下载文件
|
||||
/// <summary>
|
||||
/// 异步下载文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
public async Task<MemoryStream> DownloadFileAsync(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "RETR";
|
||||
FtpWebResponse response = (FtpWebResponse)(await request.GetResponseAsync());
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
byte[] bArr = new byte[1024 * 1024];
|
||||
int size = await responseStream.ReadAsync(bArr, 0, (int)bArr.Length);
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
size = await responseStream.ReadAsync(bArr, 0, (int)bArr.Length);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
responseStream.Close();
|
||||
|
||||
return stream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FtpUtil.DownloadFileAsync 异步下载文件 错误");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 上传文件
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="relativeFilePath">文件相对路径</param>
|
||||
/// <param name="bArr">文件内容</param>
|
||||
public bool UploadFile(string ftpPath, byte[] bArr)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "STOR";
|
||||
Stream postStream = request.GetRequestStream();
|
||||
int pageSize = 1024 * 1024;
|
||||
int index = 0;
|
||||
while (index < bArr.Length)
|
||||
{
|
||||
if (bArr.Length - index > pageSize)
|
||||
{
|
||||
postStream.Write(bArr, index, pageSize);
|
||||
index += pageSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
postStream.Write(bArr, index, bArr.Length - index);
|
||||
index = index + (bArr.Length - index);
|
||||
}
|
||||
}
|
||||
postStream.Close();
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
|
||||
string result = sr.ReadToEnd();
|
||||
responseStream.Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="localFilePath">本地文件</param>
|
||||
/// <param name="ftpPath">远端路径</param>
|
||||
/// <returns></returns>
|
||||
public bool UploadFile(string localFilePath, string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
byte[] bytes = new byte[1024 * 1024];
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = "STOR";
|
||||
|
||||
FileStream stream = new FileStream(localFilePath, FileMode.Open);
|
||||
|
||||
Stream postStream = request.GetRequestStream();
|
||||
while (true)
|
||||
{
|
||||
int count = stream.Read(bytes, 0, bytes.Length);
|
||||
if (count == 0)
|
||||
break;
|
||||
postStream.Write(bytes, 0, count);
|
||||
}
|
||||
postStream.Close();
|
||||
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
|
||||
Stream responseStream = response.GetResponseStream();
|
||||
StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
|
||||
string result = sr.ReadToEnd();
|
||||
responseStream.Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 删除文件
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <param name="ftpPath">ftp路径</param>
|
||||
/// <param name="ftpUserId">用户名</param>
|
||||
/// <param name="ftpPassword">密码</param>
|
||||
/// <param name="relativeFilePath">文件相对路径</param>
|
||||
public bool DeleteFile(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.DeleteFile;
|
||||
((FtpWebResponse)request.GetResponse()).Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Rename(string ftpPath, string newName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.Rename;
|
||||
request.RenameTo = newName;
|
||||
((FtpWebResponse)request.GetResponse()).Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool DeleteDirectory(string ftpPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = Uri.EscapeUriString(FtpUrl + ftpPath);
|
||||
if (ftpPath.EndsWith("/"))
|
||||
ftpPath = ftpPath.Substring(0, ftpPath.Length - 1);
|
||||
FtpWebRequest request = null;
|
||||
request = (FtpWebRequest)WebRequest.Create(new Uri(url));
|
||||
request.Credentials = new NetworkCredential(ftpUserId, ftpPassword);
|
||||
request.Method = WebRequestMethods.Ftp.RemoveDirectory;
|
||||
((FtpWebResponse)request.GetResponse()).Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class FtpFileInfo
|
||||
{
|
||||
public bool IsDirectory { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
321
常用工具集/Utility/Network/HttpUtils.cs
Normal file
321
常用工具集/Utility/Network/HttpUtils.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
using MES.Utility.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace 常用工具集.Utility.Network
|
||||
{
|
||||
public class HttpUtils
|
||||
{
|
||||
public static bool DoGetFile(string url, string filePath, int? timeout)
|
||||
{
|
||||
var task = DoGetFileAsync(url, filePath);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
public static byte[] DoGetFile(string url, int? timeout)
|
||||
{
|
||||
var task = DoGetFileAsync(url);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
public static async Task<bool> DoGetFileAsync(string url, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
fileStream.Close();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> DoGetFileAsync(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
return await response.Content.ReadAsByteArrayAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Get请求数据
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parametersDict"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public static string DoGet(string url, Dictionary<string, string> parametersDict, int? timeout)
|
||||
{
|
||||
var task = DoGetAsync(url, parametersDict);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Get请求数据
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parametersDict"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DoGetAsync(string url, Dictionary<string, string> parametersDict)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!url.Contains("?"))
|
||||
url = url + "?";
|
||||
foreach (string key in parametersDict.Keys)
|
||||
{
|
||||
url = url + key + "=" + parametersDict[key] + "&";
|
||||
}
|
||||
url = url.Substring(0, url.Length - 1);
|
||||
var httpClient = new HttpClient();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
string str= await response.Content.ReadAsStringAsync();
|
||||
return str;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post上传文件:multipart/form-data
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parms"></param>
|
||||
/// <param name="fileParms"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public static string DoPostFile(string url, Dictionary<string, string> parms, Dictionary<string, string> fileParms, int? timeout)
|
||||
{
|
||||
var task = DoPostFileAsync(url, parms, fileParms);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post上传文件:multipart/form-data
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parms"></param>
|
||||
/// <param name="fileParms"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DoPostFileAsync(string url, Dictionary<string, string> parms, Dictionary<string, string> fileParms)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var content = new MultipartFormDataContent();
|
||||
if (parms != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> keyValue in parms)
|
||||
{
|
||||
content.Add(new StringContent(keyValue.Value), keyValue.Key);
|
||||
}
|
||||
}
|
||||
if (fileParms != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> keyValue in fileParms)
|
||||
{
|
||||
string filePath = keyValue.Value;
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
content.Add(new ByteArrayContent(File.ReadAllBytes(filePath)), keyValue.Key, fileName);
|
||||
}
|
||||
}
|
||||
var response = await httpClient.PostAsync(url, content);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parms"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public static string DoPostForm(string url, Dictionary<string, string> parms, int? timeout)
|
||||
{
|
||||
var task = DoPostFormAsync(url, parms);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="parms"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DoPostFormAsync(string url, Dictionary<string, string> parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
FormUrlEncodedContent formData;
|
||||
if (parms != null)
|
||||
{
|
||||
formData = new FormUrlEncodedContent(parms);
|
||||
}
|
||||
else
|
||||
{
|
||||
formData = new FormUrlEncodedContent(new Dictionary<string, string>());
|
||||
}
|
||||
var response = await httpClient.PostAsync(url, formData);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public static string DoPostJson(string url, string json, int? timeout)
|
||||
{
|
||||
var task = DoPostJsonAsync(url, json);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DoPostJsonAsync(string url, string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
StringContent jsonContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
var response = await httpClient.PostAsync(url, jsonContent);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public static string DoPostData(string url, object data, int? timeout)
|
||||
{
|
||||
var task = DoPostDataAsync(url, data);
|
||||
if (timeout != null)
|
||||
{
|
||||
if (!task.Wait(timeout.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Post请求数据: application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DoPostDataAsync(string url, object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
StringContent jsonContent = new StringContent(obj.ToJson(), Encoding.UTF8, "application/json");
|
||||
var response = await httpClient.PostAsync(url, jsonContent);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
234
常用工具集/Utility/Network/Mitsubishi/MC3EServer.cs
Normal file
234
常用工具集/Utility/Network/Mitsubishi/MC3EServer.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using McProtocol.Mitsubishi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace 常用工具集.Utility.Network.Mitsubishi
|
||||
{
|
||||
internal class MC3EServer
|
||||
{
|
||||
public delegate void DataChanged(int address);
|
||||
public delegate void LogChanged(string message);
|
||||
public event DataChanged DataChangedEvent;
|
||||
public event LogChanged LogChangedEvent;
|
||||
|
||||
private TcpListener _listener;
|
||||
private bool _isRunning;
|
||||
public static ushort[] _dataStorage = new ushort[65535]; // 初始化数据存储区; // 65535个地址数据,类型为ushort
|
||||
|
||||
|
||||
public MC3EServer(int port)
|
||||
{
|
||||
_listener = new TcpListener(IPAddress.Any, port);
|
||||
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
_isRunning = true;
|
||||
_listener.Start();
|
||||
LogChangedEvent?.Invoke("MC-3E Protocol Server started...");
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient client = _listener.AcceptTcpClient();
|
||||
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
|
||||
clientThread.Start(client);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_listener.Stop();
|
||||
}
|
||||
|
||||
private void HandleClient(object obj)
|
||||
{
|
||||
TcpClient client = (TcpClient)obj;
|
||||
try
|
||||
{
|
||||
NetworkStream stream = client.GetStream();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
|
||||
{
|
||||
LogChangedEvent?.Invoke($"Received {bytesRead} bytes from client.");
|
||||
// 解析MC-3E协议请求
|
||||
byte[] response = ProcessMc3ERequest(buffer, bytesRead, out bool write, out int address);
|
||||
// 发送响应
|
||||
stream.Write(response, 0, response.Length);
|
||||
if (write) Task.Run(() => DataChangedEvent?.Invoke(address));
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogChangedEvent?.Invoke(ex.Message);
|
||||
}
|
||||
client.Close();
|
||||
}
|
||||
private byte[] ProcessMc3ERequest(byte[] request, int length, out bool write, out int address)
|
||||
{
|
||||
// MC-3E协议帧格式示例:
|
||||
// 子头 (2字节) + 网络编号 (1字节) + PLC编号 (1字节) + 请求目标模块IO编号 (2字节) + 请求目标模块站号 (1字节)
|
||||
// + 请求数据长度 (2字节) + 请求数据 (N字节)
|
||||
// 解析请求数据
|
||||
if (length < 8)
|
||||
{
|
||||
LogChangedEvent?.Invoke($"Invalid request length.");
|
||||
write = false;
|
||||
address = 0;
|
||||
return BuildErrorResponse();
|
||||
}
|
||||
McFrame mcFrame = new McFrame();
|
||||
mcFrame.Frame = BitConverter.ToUInt16(request, 0); // 假设前两字节为帧类型 0x0050 80
|
||||
mcFrame.NetworkNumber = request[2]; // 假设第3字节为网络编号
|
||||
mcFrame.PcNumber = request[3]; // 假设第4字节为PLC编号
|
||||
mcFrame.IoNumber = BitConverter.ToUInt16(request, 4); // 假设第5-6字节为模块IO编号
|
||||
mcFrame.ChannelNumber = request[6]; // 假设第7字节为模块站号
|
||||
int dataLength = BitConverter.ToUInt16(request, 7); // 假设第8-9字节为数据长度
|
||||
mcFrame.CpuTimer = BitConverter.ToUInt16(request, 9); // 假设第10-11字节为CPU监视定时器
|
||||
int mainCommand = BitConverter.ToUInt16(request, 11); // 假设第12-13字节为主命令
|
||||
int subCommand = BitConverter.ToUInt16(request, 13); // 假设第14-15字节为子命令
|
||||
mcFrame.Address = request[17] << 16 | request[16] << 8 | request[15];
|
||||
mcFrame.Type = (PlcDeviceType)request[18];
|
||||
mcFrame.Size = BitConverter.ToUInt16(request, 19);
|
||||
byte[] data = new byte[mcFrame.Size * 2];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = request[21 + i];
|
||||
}
|
||||
mcFrame.Data = data;
|
||||
// 处理读请求
|
||||
if (mainCommand == 0x0401) //读命令
|
||||
{
|
||||
LogChangedEvent?.Invoke($"Read request: Address={mcFrame.Address},Type={mcFrame.Type} Count={mcFrame.Size}");
|
||||
write = false;
|
||||
address = 0;
|
||||
return HandleReadRequest(mcFrame);
|
||||
}
|
||||
// 处理写请求
|
||||
else if (mainCommand == 0x1401) // 写命令
|
||||
{
|
||||
LogChangedEvent?.Invoke($"Write request: Address={mcFrame.Address},Type={mcFrame.Type} Count={mcFrame.Size}");
|
||||
write = true;
|
||||
address = mcFrame.Address;
|
||||
return HandleWriteRequest(mcFrame); ;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogChangedEvent?.Invoke("Unknown command.");
|
||||
write = false;
|
||||
address = 0;
|
||||
return BuildErrorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
class McFrame
|
||||
{
|
||||
public ushort Frame { get; set; } // 头部
|
||||
public byte NetworkNumber { get; set; } // 网络号码
|
||||
public byte PcNumber { get; set; } // 电脑号码
|
||||
public ushort IoNumber { get; set; } // 请求单元 I/O 编号/O番号
|
||||
public byte ChannelNumber { get; set; } // 请求单位站号
|
||||
public ushort CpuTimer { get; set; } // 中央处理器监控计时器
|
||||
public int Address { get; set; } // 地址
|
||||
public PlcDeviceType Type { get; set; } // 类型
|
||||
|
||||
public uint Size { get; set; } // 大小
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
|
||||
private byte[] HandleReadRequest(McFrame mcFrame)
|
||||
{
|
||||
// 读取数据
|
||||
List<byte> responseData = new List<byte>(); // 每个ushort占2字节
|
||||
for (int i = 0; i < mcFrame.Size; i++)
|
||||
{
|
||||
byte[] valueBytes = BitConverter.GetBytes(_dataStorage[mcFrame.Address + i]);
|
||||
responseData.AddRange(valueBytes);
|
||||
}
|
||||
// 构建响应帧
|
||||
return BuildSuccessResponse(mcFrame, responseData.ToArray());
|
||||
}
|
||||
|
||||
private byte[] HandleWriteRequest(McFrame mcFrame)
|
||||
{
|
||||
for (int i = 0; i < mcFrame.Size; i++)
|
||||
{
|
||||
_dataStorage[mcFrame.Address + i] = BitConverter.ToUInt16(mcFrame.Data, i * 2);
|
||||
}
|
||||
LogChangedEvent?.Invoke($"Write successful: Address={mcFrame.Address}, Length={mcFrame.Size}");
|
||||
return BuildSuccessResponse(mcFrame, new byte[0]); // 写入成功,返回空响应
|
||||
}
|
||||
|
||||
private byte[] BuildSuccessResponse(McFrame mcFrame, byte[] responseData)
|
||||
{
|
||||
// 构建成功响应帧
|
||||
List<byte> response = new List<byte>();
|
||||
mcFrame.Frame = 0x00D0;
|
||||
response.AddRange(BitConverter.GetBytes(mcFrame.Frame));//0 1
|
||||
response.Add(mcFrame.NetworkNumber); //2
|
||||
response.Add(mcFrame.PcNumber); //3
|
||||
response.AddRange(BitConverter.GetBytes(mcFrame.IoNumber)); //4 5
|
||||
response.Add(mcFrame.ChannelNumber);//6
|
||||
//response.AddRange(BitConverter.GetBytes(mcFrame.CpuTimer));
|
||||
//数据长度
|
||||
response.AddRange(BitConverter.GetBytes((short)(responseData.Length + 2)));//7 8
|
||||
response.AddRange(BitConverter.GetBytes((short)0));//9 10结束符号
|
||||
response.AddRange(responseData);
|
||||
return response.ToArray();
|
||||
}
|
||||
|
||||
private byte[] BuildErrorResponse()
|
||||
{
|
||||
// 构建错误响应帧
|
||||
byte[] response = new byte[8];
|
||||
|
||||
// 子头 (2字节)
|
||||
response[0] = 0xD0;
|
||||
response[1] = 0x00;
|
||||
|
||||
// 网络编号 (1字节)
|
||||
response[2] = 0x00;
|
||||
|
||||
// PLC编号 (1字节)
|
||||
response[3] = 0xFF;
|
||||
|
||||
// 模块IO编号 (2字节)
|
||||
response[4] = 0x00;
|
||||
response[5] = 0x00;
|
||||
|
||||
// 模块站号 (1字节)
|
||||
response[6] = 0x00;
|
||||
|
||||
// 响应数据长度 (2字节)
|
||||
response[7] = 0x00;
|
||||
response[8] = 0x00;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
512
常用工具集/Utility/Network/Mitsubishi/McHelper.cs
Normal file
512
常用工具集/Utility/Network/Mitsubishi/McHelper.cs
Normal file
@@ -0,0 +1,512 @@
|
||||
using McProtocol.Mitsubishi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace McProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// MC协议通信帮助类
|
||||
/// </summary>
|
||||
public class McHelper : IDisposable
|
||||
{
|
||||
private McProtocolTcp plc;
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
public McHelper(string ip, int port)
|
||||
{
|
||||
plc = new McProtocolTcp(ip, port, McFrame.MC3E);
|
||||
}
|
||||
public McHelper(string ip, int port, McFrame mcFrame)
|
||||
{
|
||||
plc = new McProtocolTcp(ip, port, mcFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立连接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.Open();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出后销毁
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
plc.Dispose();
|
||||
}
|
||||
|
||||
|
||||
#region Int16读写操作
|
||||
/// <summary>
|
||||
/// 读取Int16数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址:例如 D10</param>
|
||||
/// <param name="value">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadInt16(string address, out short value)
|
||||
{
|
||||
|
||||
short[] values = new short[1];
|
||||
bool flag = ReadInts16(address, 1, out values);
|
||||
if (!flag)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = values[0];
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取Int16数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址:例如 D10</param>
|
||||
/// <param name="count">读取长度</param>
|
||||
/// <param name="values">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadInts16(string address, int count, out short[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
//地址解析
|
||||
values = plc.ReadInt16(address, count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteInt16(string address, short value)
|
||||
{
|
||||
short[] values = new short[1];
|
||||
values[0] = value;
|
||||
return WriteInts16(address, values);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="values">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteInts16(string address, short[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.WriteInt16(address, values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UInt16读写操作
|
||||
/// <summary>
|
||||
/// 读取UInt16数据
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="value">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadUInt16(string address, out ushort value)
|
||||
{
|
||||
ushort[] values = new ushort[1];
|
||||
bool flag = ReadUInts16(address, 1, out values);
|
||||
if (!flag)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = values[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取UInt16数据
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="count">读取数量</param>
|
||||
/// <param name="values">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadUInts16(string address, int count, out ushort[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
values = plc.ReadUInt16(address, count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteUInt16(string address, ushort value)
|
||||
{
|
||||
|
||||
ushort[] values = new ushort[1];
|
||||
values[0] = value;
|
||||
return WriteUInts16(address, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="values">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteUInts16(string address, ushort[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.WriteUInt16(address, values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Int32读写操作
|
||||
/// <summary>
|
||||
/// 读取Int32
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="value">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadInt32(string address, out int value)
|
||||
{
|
||||
int[] values;
|
||||
bool flag = ReadInts32(address, 1, out values);
|
||||
if (!flag)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = values[0];
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取Int32
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="count">读取数量</param>
|
||||
/// <param name="values">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadInts32(string address, int count, out int[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
//地址解析
|
||||
values = plc.ReadInt32(address, count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteInt32(string address, int value)
|
||||
{
|
||||
int[] values = new int[1];
|
||||
values[0] = value;
|
||||
return WriteInts32(address, values);
|
||||
}
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="values">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteInts32(string address, int[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.WriteInt32(address, values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UInt32读写操作
|
||||
/// <summary>
|
||||
/// 读取UInt32
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="value">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadUInt32(string address, out uint value)
|
||||
{
|
||||
|
||||
uint[] values = new uint[1];
|
||||
bool flag = ReadUInts32(address, 1, out values);
|
||||
if (!flag)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = values[0];
|
||||
return true;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 读取UInt32
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="count">读取数量</param>
|
||||
/// <param name="values">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadUInts32(string address, int count, out uint[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
//地址解析
|
||||
values = plc.ReadUInt32(address, count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteUInt32(string address, uint value)
|
||||
{
|
||||
|
||||
uint[] values = new uint[1];
|
||||
values[0] = value;
|
||||
return WriteUInts32(address, values);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="values">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteUInts32(string address, uint[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.WriteUInt32(address, values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Float读写操作
|
||||
/// <summary>
|
||||
/// 读取Float
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="value">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadFloat(string address, out float value)
|
||||
{
|
||||
float[] values;
|
||||
bool flag = ReadFloats(address, 1, out values);
|
||||
if (!flag)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = values[0];
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 读取Float
|
||||
/// </summary>
|
||||
/// <param name="address">例如 D10</param>
|
||||
/// <param name="count">读取数量</param>
|
||||
/// <param name="values">返回数据</param>
|
||||
/// <returns>读取成功还是失败</returns>
|
||||
public bool ReadFloats(string address, int count, out float[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
//地址解析
|
||||
values = plc.ReadFloat(address, count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteFloat(string address, float value)
|
||||
{
|
||||
float[] values = new float[1];
|
||||
values[0] = value;
|
||||
return WriteFloats(address, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="address">地址D100</param>
|
||||
/// <param name="values">写入数据</param>
|
||||
/// <returns>写入成功还是失败</returns>
|
||||
public bool WriteFloats(string address, float[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
plc.WriteFloat(address, values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region String读写操作
|
||||
/// <summary>
|
||||
/// 读取字符串
|
||||
/// </summary>
|
||||
/// <param name="address">地址,例如:D3200</param>
|
||||
/// <param name="length">此处为地址长度,例50,表示字符串长度100</param>
|
||||
/// <param name="str">读到的字符串</param>
|
||||
/// <returns>返回读取成功还是失败</returns>
|
||||
public bool ReadString(string address, int length, out string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
ushort[] value = new ushort[length];
|
||||
byte[] values = plc.ReadDeviceBlock(address, length, value);
|
||||
List<byte> bytes = new List<byte>();
|
||||
foreach (var item in value)
|
||||
{
|
||||
bytes.AddRange(BitConverter.GetBytes(item));
|
||||
}
|
||||
str = Encoding.GetEncoding("GB2312").GetString(bytes.ToArray());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
str = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入字符串
|
||||
/// </summary>
|
||||
/// <param name="address">地址,例如:D3200</param>
|
||||
/// <param name="length">此处为地址长度,例50,表示字符串长度100</param>
|
||||
/// <param name="str">写入的字符串</param>
|
||||
/// <returns>返回写入成功还是失败</returns>
|
||||
public bool WriteString(string address, int length, string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] Bytes = new byte[length * 2];
|
||||
byte[] bytes = Encoding.GetEncoding("GB2312").GetBytes(str);
|
||||
if (bytes.Length > Bytes.Length)
|
||||
{
|
||||
for (int i = 0; i < Bytes.Length; i++)
|
||||
{
|
||||
Bytes[i] = bytes[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
Bytes[i] = bytes[i];
|
||||
}
|
||||
}
|
||||
ushort[] value = new ushort[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
value[i] = BitConverter.ToUInt16(Bytes, i * 2);
|
||||
}
|
||||
plc.WriteDeviceBlock(address, value);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
1391
常用工具集/Utility/Network/Mitsubishi/McProtocolTcp.cs
Normal file
1391
常用工具集/Utility/Network/Mitsubishi/McProtocolTcp.cs
Normal file
File diff suppressed because it is too large
Load Diff
211
常用工具集/Utility/Network/Modbus/EasyModbus/Exceptions/Exceptions.cs
Normal file
211
常用工具集/Utility/Network/Modbus/EasyModbus/Exceptions/Exceptions.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright (c) 2018-2020 Rossmann-Engineering
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction,
|
||||
including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission
|
||||
notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace EasyModbus.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception to be thrown if serial port is not opened
|
||||
/// </summary>
|
||||
public class SerialPortNotOpenedException : ModbusException
|
||||
{
|
||||
public SerialPortNotOpenedException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public SerialPortNotOpenedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SerialPortNotOpenedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected SerialPortNotOpenedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if Connection to Modbus device failed
|
||||
/// </summary>
|
||||
public class ConnectionException : ModbusException
|
||||
{
|
||||
public ConnectionException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public ConnectionException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ConnectionException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected ConnectionException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if Modbus Server returns error code "Function code not supported"
|
||||
/// </summary>
|
||||
public class FunctionCodeNotSupportedException : ModbusException
|
||||
{
|
||||
public FunctionCodeNotSupportedException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public FunctionCodeNotSupportedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public FunctionCodeNotSupportedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected FunctionCodeNotSupportedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if Modbus Server returns error code "quantity invalid"
|
||||
/// </summary>
|
||||
public class QuantityInvalidException : ModbusException
|
||||
{
|
||||
public QuantityInvalidException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public QuantityInvalidException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public QuantityInvalidException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected QuantityInvalidException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if Modbus Server returns error code "starting adddress and quantity invalid"
|
||||
/// </summary>
|
||||
public class StartingAddressInvalidException : ModbusException
|
||||
{
|
||||
public StartingAddressInvalidException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public StartingAddressInvalidException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public StartingAddressInvalidException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected StartingAddressInvalidException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if Modbus Server returns error code "Function Code not executed (0x04)"
|
||||
/// </summary>
|
||||
public class ModbusException : Exception
|
||||
{
|
||||
public ModbusException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public ModbusException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ModbusException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected ModbusException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be thrown if CRC Check failed
|
||||
/// </summary>
|
||||
public class CRCCheckFailedException : ModbusException
|
||||
{
|
||||
public CRCCheckFailedException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public CRCCheckFailedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CRCCheckFailedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected CRCCheckFailedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
2878
常用工具集/Utility/Network/Modbus/EasyModbus/ModbusClient.cs
Normal file
2878
常用工具集/Utility/Network/Modbus/EasyModbus/ModbusClient.cs
Normal file
File diff suppressed because it is too large
Load Diff
2277
常用工具集/Utility/Network/Modbus/EasyModbus/ModbusServer.cs
Normal file
2277
常用工具集/Utility/Network/Modbus/EasyModbus/ModbusServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
120
常用工具集/Utility/Network/Modbus/EasyModbus/StoreLogData.cs
Normal file
120
常用工具集/Utility/Network/Modbus/EasyModbus/StoreLogData.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright (c) 2018-2020 Rossmann-Engineering
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction,
|
||||
including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission
|
||||
notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace EasyModbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Store Log-Data in a File
|
||||
/// </summary>
|
||||
public sealed class StoreLogData
|
||||
{
|
||||
private String filename = null;
|
||||
private static volatile StoreLogData instance;
|
||||
private static object syncObject = new Object();
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor; Ensures the access of the class only via "instance"
|
||||
/// </summary>
|
||||
private StoreLogData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the instance of the class (singleton)
|
||||
/// </summary>
|
||||
/// <returns>instance (Singleton)</returns>
|
||||
public static StoreLogData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new StoreLogData();
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store message in Log-File
|
||||
/// </summary>
|
||||
/// <param name="message">Message to append to the Log-File</param>
|
||||
public void Store(String message)
|
||||
{
|
||||
if (this.filename == null)
|
||||
return;
|
||||
|
||||
using (System.IO.StreamWriter file =
|
||||
new System.IO.StreamWriter(Filename, true))
|
||||
{
|
||||
file.WriteLine(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store message in Log-File including Timestamp
|
||||
/// </summary>
|
||||
/// <param name="message">Message to append to the Log-File</param>
|
||||
/// <param name="timestamp">Timestamp to add to the same Row</param>
|
||||
public void Store(String message, DateTime timestamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (System.IO.StreamWriter file =
|
||||
new System.IO.StreamWriter(Filename, true))
|
||||
{
|
||||
file.WriteLine(timestamp.ToString("dd.MM.yyyy H:mm:ss.ff ") + message);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Filename to Store Strings in a File
|
||||
/// </summary>
|
||||
public string Filename
|
||||
{
|
||||
get
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
set
|
||||
{
|
||||
filename = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1263
常用工具集/Utility/Network/Modbus/ModbusHelper.cs
Normal file
1263
常用工具集/Utility/Network/Modbus/ModbusHelper.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF01ReadCoils : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort count;
|
||||
|
||||
public byte Code { get { return 1; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort Count { get { return count; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } }
|
||||
|
||||
public ModbusF01ReadCoils(byte slave, ushort address, ushort count)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 1;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.High(count);
|
||||
request[offset + 5] = ModbusUtils.Low(count);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForBools(count);
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 1, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
|
||||
return ModbusUtils.DecodeBools(response, offset + 3, count);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return model.getDOs(slave, address, count);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForBools(count);
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 1;
|
||||
response[offset + 2] = bytes;
|
||||
var data = ModbusUtils.EncodeBools(value as bool[]);
|
||||
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF01ReadCoils Slave={0}, Address={1}, Count={2}]", slave, address, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF02ReadInputs : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort count;
|
||||
|
||||
public byte Code { get { return 2; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort Count { get { return count; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } }
|
||||
|
||||
public ModbusF02ReadInputs(byte slave, ushort address, ushort count)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 2;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.High(count);
|
||||
request[offset + 5] = ModbusUtils.Low(count);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForBools(count);
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 2, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
|
||||
return ModbusUtils.DecodeBools(response, offset + 3, count);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return model.getDIs(slave, address, count);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForBools(count);
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 2;
|
||||
response[offset + 2] = bytes;
|
||||
var data = ModbusUtils.EncodeBools(value as bool[]);
|
||||
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF02ReadInputs Slave={0}, Address={1}, Count={2}]", slave, address, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF03ReadHoldingRegisters : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort count;
|
||||
|
||||
public byte Code { get { return 3; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort Count { get { return count; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } }
|
||||
|
||||
public ModbusF03ReadHoldingRegisters(byte slave, ushort address, ushort count)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 3;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.High(count);
|
||||
request[offset + 5] = ModbusUtils.Low(count);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForWords(count);
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 3, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
|
||||
return ModbusUtils.DecodeWords(response, offset + 3, count);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return model.getWOs(slave, address, count);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForWords(count);
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 3;
|
||||
response[offset + 2] = bytes;
|
||||
var data = ModbusUtils.EncodeWords((ushort[])value);
|
||||
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF03ReadHoldingRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF04ReadInputRegisters : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort count;
|
||||
|
||||
public byte Code { get { return 4; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort Count { get { return count; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } }
|
||||
|
||||
public ModbusF04ReadInputRegisters(byte slave, ushort address, ushort count)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 4;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.High(count);
|
||||
request[offset + 5] = ModbusUtils.Low(count);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForWords(count);
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 4, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
|
||||
return ModbusUtils.DecodeWords(response, offset + 3, count);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return model.getWIs(slave, address, count);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
var bytes = ModbusUtils.BytesForWords(count);
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 4;
|
||||
response[offset + 2] = bytes;
|
||||
var data = ModbusUtils.EncodeWords(value as ushort[]);
|
||||
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF04ReadInputRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF05WriteCoil : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly bool value;
|
||||
|
||||
public byte Code { get { return 5; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public bool Value { get { return value; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 6; } }
|
||||
|
||||
public ModbusF05WriteCoil(byte slave, ushort address, bool state)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.value = state;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 5;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.EncodeBool(value);
|
||||
request[offset + 5] = 0;
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 5, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 4], ModbusUtils.EncodeBool(value), "Value mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 5], 0, "Pad mismatch {0} expected:{1}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
model.setDO(slave, address, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
FillRequest(response, offset);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF05WriteCoil Slave={0}, Address={1}, Value={2}]", slave, address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF06WriteRegister : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort value;
|
||||
|
||||
public byte Code { get { return 6; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort Value { get { return value; } }
|
||||
public int RequestLength { get { return 6; } }
|
||||
public int ResponseLength { get { return 6; } }
|
||||
|
||||
public ModbusF06WriteRegister(byte slave, ushort address, ushort value)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = slave;
|
||||
request[offset + 1] = 6;
|
||||
request[offset + 2] = ModbusUtils.High(address);
|
||||
request[offset + 3] = ModbusUtils.Low(address);
|
||||
request[offset + 4] = ModbusUtils.High(value);
|
||||
request[offset + 5] = ModbusUtils.Low(value);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 6, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), value, "Value mismatch got {0} expected {1}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
model.setWO(slave, address, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
FillRequest(response, offset);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF06WriteRegister Slave={0}, Address={1}, Value={2}]", slave, address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF15WriteCoils : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly bool[] values;
|
||||
|
||||
public byte Code { get { return 15; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public bool[] Values { get { return ModbusUtils.Clone(values); } }
|
||||
public int RequestLength { get { return 7 + ModbusUtils.BytesForBools(values.Length); } }
|
||||
public int ResponseLength { get { return 6; } }
|
||||
|
||||
public ModbusF15WriteCoils(byte slave, ushort address, bool[] values)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
FillResponse(request, offset, null);
|
||||
var bytes = ModbusUtils.EncodeBools(values);
|
||||
request[offset + 6] = (byte)bytes.Length;
|
||||
ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 15, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Coil count mismatch got {0} expected {1}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
model.setDOs(slave, address, values);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 15;
|
||||
response[offset + 2] = ModbusUtils.High(address);
|
||||
response[offset + 3] = ModbusUtils.Low(address);
|
||||
response[offset + 4] = ModbusUtils.High(values.Length);
|
||||
response[offset + 5] = ModbusUtils.Low(values.Length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF15WriteCoils Slave={0}, Address={1}, Values={2}]", slave, address, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusF16WriteRegisters : IModbusCommand
|
||||
{
|
||||
private readonly byte slave;
|
||||
private readonly ushort address;
|
||||
private readonly ushort[] values;
|
||||
|
||||
public byte Code { get { return 16; } }
|
||||
public byte Slave { get { return slave; } }
|
||||
public ushort Address { get { return address; } }
|
||||
public ushort[] Values { get { return ModbusUtils.Clone(values); } }
|
||||
public int RequestLength { get { return 7 + ModbusUtils.BytesForWords(values.Length); } }
|
||||
public int ResponseLength { get { return 6; } }
|
||||
|
||||
public ModbusF16WriteRegisters(byte slave, ushort address, ushort[] values)
|
||||
{
|
||||
this.slave = slave;
|
||||
this.address = address;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
FillResponse(request, offset, null);
|
||||
var bytes = ModbusUtils.EncodeWords(values);
|
||||
request[offset + 6] = (byte)bytes.Length;
|
||||
ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(response[offset + 1], 16, "Function mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Register count mismatch got {0} expected {1}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
model.setWOs(slave, address, values);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
response[offset + 0] = slave;
|
||||
response[offset + 1] = 16;
|
||||
response[offset + 2] = ModbusUtils.High(address);
|
||||
response[offset + 3] = ModbusUtils.Low(address);
|
||||
response[offset + 4] = ModbusUtils.High(values.Length);
|
||||
response[offset + 5] = ModbusUtils.Low(values.Length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusF16WriteRegisters Slave={0}, Address={1}, Values={2}]", slave, address, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusCommand.cs
Normal file
18
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusCommand
|
||||
{
|
||||
byte Code { get; }
|
||||
byte Slave { get; }
|
||||
ushort Address { get; }
|
||||
int RequestLength { get; }
|
||||
int ResponseLength { get; }
|
||||
void FillRequest(byte[] request, int offset);
|
||||
object ParseResponse(byte[] response, int offset);
|
||||
object ApplyTo(IModbusModel model);
|
||||
void FillResponse(byte[] response, int offset, object value);
|
||||
}
|
||||
}
|
||||
39
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusModel.cs
Normal file
39
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusModel.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusModel
|
||||
{
|
||||
void setDI(byte slave, ushort address, bool value);
|
||||
|
||||
void setDIs(byte slave, ushort address, bool[] values);
|
||||
|
||||
bool getDI(byte slave, ushort address);
|
||||
|
||||
bool[] getDIs(byte slave, ushort address, int count);
|
||||
|
||||
void setDO(byte slave, ushort address, bool value);
|
||||
|
||||
void setDOs(byte slave, ushort address, bool[] values);
|
||||
|
||||
bool getDO(byte slave, ushort address);
|
||||
|
||||
bool[] getDOs(byte slave, ushort address, int count);
|
||||
|
||||
void setWI(byte slave, ushort address, ushort value);
|
||||
|
||||
void setWIs(byte slave, ushort address, ushort[] values);
|
||||
|
||||
ushort getWI(byte slave, ushort address);
|
||||
|
||||
ushort[] getWIs(byte slave, ushort address, int count);
|
||||
|
||||
void setWO(byte slave, ushort address, ushort value);
|
||||
|
||||
void setWOs(byte slave, ushort address, ushort[] values);
|
||||
|
||||
ushort getWO(byte slave, ushort address);
|
||||
|
||||
ushort[] getWOs(byte slave, ushort address, int count);
|
||||
}
|
||||
}
|
||||
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusProtocol.cs
Normal file
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusProtocol.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusProtocol
|
||||
{
|
||||
IModbusWrapper Wrap(IModbusCommand wrapped);
|
||||
IModbusWrapper Parse(byte[] request, int offset);
|
||||
}
|
||||
}
|
||||
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusScanner.cs
Normal file
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusScanner.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusScanner
|
||||
{
|
||||
void Append(byte[] data, int offset, int count);
|
||||
IModbusWrapper Scan();
|
||||
}
|
||||
}
|
||||
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusStream.cs
Normal file
10
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusStream.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusStream : IDisposable
|
||||
{
|
||||
void Write(byte[] data);
|
||||
int Read(byte[] data);
|
||||
}
|
||||
}
|
||||
11
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusWrapper.cs
Normal file
11
常用工具集/Utility/Network/Modbus/SharpModbus/IModbusWrapper.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public interface IModbusWrapper : IModbusCommand
|
||||
{
|
||||
IModbusCommand Wrapped { get; }
|
||||
byte[] GetException(byte code);
|
||||
void CheckException(byte[] respose, int count);
|
||||
}
|
||||
}
|
||||
17
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusException.cs
Normal file
17
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusException.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusException : Exception
|
||||
{
|
||||
private readonly byte code;
|
||||
|
||||
public byte Code { get { return code; } }
|
||||
|
||||
public ModbusException(byte code) :
|
||||
base(string.Format("Modbus exception {0}", code))
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using SharpSerial;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusIsolatedStream : IModbusStream
|
||||
{
|
||||
private readonly Action<char, byte[], int> monitor;
|
||||
private readonly SerialProcess serialProcess;
|
||||
private readonly int timeout;
|
||||
|
||||
public ModbusIsolatedStream(object settings, int timeout, Action<char, byte[], int> monitor = null)
|
||||
{
|
||||
this.serialProcess = new SerialProcess(settings);
|
||||
this.timeout = timeout;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Dispose(serialProcess);
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
if (monitor != null) monitor('>', data, data.Length);
|
||||
serialProcess.Write(data);
|
||||
}
|
||||
|
||||
public int Read(byte[] data)
|
||||
{
|
||||
var response = serialProcess.Read(data.Length, -1, timeout);
|
||||
var count = response.Length;
|
||||
for (var i = 0; i < count; i++) data[i] = response[i];
|
||||
if (monitor != null) monitor('<', data, count);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
132
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusMaster.cs
Normal file
132
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusMaster.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using MES.Utility.Core;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusMaster : IDisposable
|
||||
{
|
||||
public delegate void OnSendedData(byte[] bytes, string hexString);
|
||||
public event OnSendedData OnSended;
|
||||
|
||||
public delegate void OnRecivedData(byte[] bytes, string hexString);
|
||||
public event OnRecivedData OnRecived;
|
||||
|
||||
public static ModbusMaster RTU(SerialSettings settings, int timeout = 400)
|
||||
{
|
||||
var stream = new ModbusSerialStream(settings, timeout);
|
||||
var protocol = new ModbusRTUProtocol();
|
||||
return new ModbusMaster(stream, protocol);
|
||||
}
|
||||
|
||||
public static ModbusMaster IsolatedRTU(SerialSettings settings, int timeout = 400)
|
||||
{
|
||||
var stream = new ModbusIsolatedStream(settings, timeout);
|
||||
var protocol = new ModbusRTUProtocol();
|
||||
return new ModbusMaster(stream, protocol);
|
||||
}
|
||||
|
||||
public static ModbusMaster TCP(string ip, int port, int timeout = 400)
|
||||
{
|
||||
var socket = Tools.ConnectWithTimeout(ip, port, timeout);
|
||||
var stream = new ModbusSocketStream(socket, timeout);
|
||||
var protocol = new ModbusTCPProtocol();
|
||||
return new ModbusMaster(stream, protocol);
|
||||
}
|
||||
|
||||
private readonly IModbusProtocol protocol;
|
||||
private readonly IModbusStream stream;
|
||||
|
||||
public ModbusMaster(IModbusStream stream, IModbusProtocol protocol)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Dispose(stream);
|
||||
}
|
||||
|
||||
public bool ReadCoil(byte slave, ushort address)
|
||||
{
|
||||
return ReadCoils(slave, address, 1)[0]; //there is no code for single read
|
||||
}
|
||||
|
||||
public bool ReadInput(byte slave, ushort address)
|
||||
{
|
||||
return ReadInputs(slave, address, 1)[0]; //there is no code for single read
|
||||
}
|
||||
|
||||
public ushort ReadInputRegister(byte slave, ushort address)
|
||||
{
|
||||
return ReadInputRegisters(slave, address, 1)[0]; //there is no code for single read
|
||||
}
|
||||
|
||||
public ushort ReadHoldingRegister(byte slave, ushort address)
|
||||
{
|
||||
return ReadHoldingRegisters(slave, address, 1)[0]; //there is no code for single read
|
||||
}
|
||||
|
||||
public bool[] ReadCoils(byte slave, ushort address, ushort count)
|
||||
{
|
||||
return Execute(new ModbusF01ReadCoils(slave, address, count)) as bool[];
|
||||
}
|
||||
|
||||
public bool[] ReadInputs(byte slave, ushort address, ushort count)
|
||||
{
|
||||
return Execute(new ModbusF02ReadInputs(slave, address, count)) as bool[];
|
||||
}
|
||||
|
||||
public ushort[] ReadInputRegisters(byte slave, ushort address, ushort count)
|
||||
{
|
||||
return Execute(new ModbusF04ReadInputRegisters(slave, address, count)) as ushort[];
|
||||
}
|
||||
|
||||
public ushort[] ReadHoldingRegisters(byte slave, ushort address, ushort count)
|
||||
{
|
||||
return Execute(new ModbusF03ReadHoldingRegisters(slave, address, count)) as ushort[];
|
||||
}
|
||||
|
||||
public void WriteCoil(byte slave, ushort address, bool value)
|
||||
{
|
||||
Execute(new ModbusF05WriteCoil(slave, address, value));
|
||||
}
|
||||
|
||||
public void WriteRegister(byte slave, ushort address, ushort value)
|
||||
{
|
||||
Execute(new ModbusF06WriteRegister(slave, address, value));
|
||||
}
|
||||
|
||||
public void WriteCoils(byte slave, ushort address, params bool[] values)
|
||||
{
|
||||
Execute(new ModbusF15WriteCoils(slave, address, values));
|
||||
}
|
||||
|
||||
public void WriteRegisters(byte slave, ushort address, params ushort[] values)
|
||||
{
|
||||
Execute(new ModbusF16WriteRegisters(slave, address, values));
|
||||
}
|
||||
|
||||
private object Execute(IModbusCommand cmd)
|
||||
{
|
||||
var wrapper = protocol.Wrap(cmd);
|
||||
var request = new byte[wrapper.RequestLength];
|
||||
var response = new byte[wrapper.ResponseLength];
|
||||
wrapper.FillRequest(request, 0);
|
||||
OnSended?.Invoke(request, DataHexString(request));
|
||||
stream.Write(request);
|
||||
var count = stream.Read(response);
|
||||
OnRecived?.Invoke(response, DataHexString(response));
|
||||
if (count < response.Length) wrapper.CheckException(response, count);
|
||||
return wrapper.ParseResponse(response, 0);
|
||||
}
|
||||
|
||||
private string DataHexString(byte[] bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
return string.Empty;
|
||||
return bytes.Select(it => Convert.ToString(it, 16).PadLeft(2, '0').ToUpper()).ToList().GetStrArray(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
152
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusModel.cs
Normal file
152
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusModel.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public enum ModbusIoType
|
||||
{
|
||||
DI,
|
||||
DO,
|
||||
WO,
|
||||
WI
|
||||
}
|
||||
|
||||
public class ModbusModel : IModbusModel
|
||||
{
|
||||
private readonly IDictionary<string, bool> digitals = new Dictionary<string, bool>();
|
||||
private readonly IDictionary<string, ushort> words = new Dictionary<string, ushort>();
|
||||
|
||||
public void setDI(byte slave, ushort address, bool value)
|
||||
{
|
||||
var key = Key(ModbusIoType.DI, slave, address);
|
||||
digitals[key] = value;
|
||||
}
|
||||
|
||||
public void setDIs(byte slave, ushort address, bool[] values)
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.DI, slave, address + i);
|
||||
digitals[key] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool getDI(byte slave, ushort address)
|
||||
{
|
||||
var key = Key(ModbusIoType.DI, slave, address);
|
||||
return digitals[key];
|
||||
}
|
||||
|
||||
public bool[] getDIs(byte slave, ushort address, int count)
|
||||
{
|
||||
var values = new bool[count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.DI, slave, address + i);
|
||||
values[i] = digitals[key];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setDO(byte slave, ushort address, bool value)
|
||||
{
|
||||
var key = Key(ModbusIoType.DO, slave, address);
|
||||
digitals[key] = value;
|
||||
}
|
||||
|
||||
public void setDOs(byte slave, ushort address, bool[] values)
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.DO, slave, address + i);
|
||||
digitals[key] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool getDO(byte slave, ushort address)
|
||||
{
|
||||
var key = Key(ModbusIoType.DO, slave, address);
|
||||
return digitals[key];
|
||||
}
|
||||
|
||||
public bool[] getDOs(byte slave, ushort address, int count)
|
||||
{
|
||||
var values = new bool[count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.DO, slave, address + i);
|
||||
values[i] = digitals[key];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setWI(byte slave, ushort address, ushort value)
|
||||
{
|
||||
var key = Key(ModbusIoType.WI, slave, address);
|
||||
words[key] = value;
|
||||
}
|
||||
|
||||
public void setWIs(byte slave, ushort address, ushort[] values)
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.WI, slave, address + i);
|
||||
words[key] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
public ushort getWI(byte slave, ushort address)
|
||||
{
|
||||
var key = Key(ModbusIoType.WI, slave, address);
|
||||
return words[key];
|
||||
}
|
||||
|
||||
public ushort[] getWIs(byte slave, ushort address, int count)
|
||||
{
|
||||
var values = new ushort[count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.WI, slave, address + i);
|
||||
values[i] = words[key];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setWO(byte slave, ushort address, ushort value)
|
||||
{
|
||||
var key = Key(ModbusIoType.WO, slave, address);
|
||||
words[key] = value;
|
||||
}
|
||||
|
||||
public void setWOs(byte slave, ushort address, ushort[] values)
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.WO, slave, address + i);
|
||||
words[key] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
public ushort getWO(byte slave, ushort address)
|
||||
{
|
||||
var key = Key(ModbusIoType.WO, slave, address);
|
||||
return words[key];
|
||||
}
|
||||
|
||||
public ushort[] getWOs(byte slave, ushort address, int count)
|
||||
{
|
||||
var values = new ushort[count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var key = Key(ModbusIoType.WO, slave, address + i);
|
||||
values[i] = words[key];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private string Key(ModbusIoType type, byte slave, int address)
|
||||
{
|
||||
return string.Format("{0},{1},{2}", slave, type, address);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusParser.cs
Normal file
90
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusParser.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public static class ModbusParser
|
||||
{
|
||||
public static IModbusCommand Parse(byte[] request, int offset)
|
||||
{
|
||||
var slave = request[offset + 0];
|
||||
var code = request[offset + 1];
|
||||
var address = ModbusUtils.GetUShort(request, offset + 2);
|
||||
switch (code)
|
||||
{
|
||||
case 1:
|
||||
return Parse01(slave, code, address, request, offset);
|
||||
case 2:
|
||||
return Parse02(slave, code, address, request, offset);
|
||||
case 3:
|
||||
return Parse03(slave, code, address, request, offset);
|
||||
case 4:
|
||||
return Parse04(slave, code, address, request, offset);
|
||||
case 5:
|
||||
return Parse05(slave, code, address, request, offset);
|
||||
case 6:
|
||||
return Parse06(slave, code, address, request, offset);
|
||||
case 15:
|
||||
return Parse15(slave, code, address, request, offset);
|
||||
case 16:
|
||||
return Parse16(slave, code, address, request, offset);
|
||||
}
|
||||
throw Tools.Make("Unsupported function code {0}", code);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse01(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
return new ModbusF01ReadCoils(slave, address, count);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse02(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
return new ModbusF02ReadInputs(slave, address, count);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse03(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
return new ModbusF03ReadHoldingRegisters(slave, address, count);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse04(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
return new ModbusF04ReadInputRegisters(slave, address, count);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse05(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var value = ModbusUtils.DecodeBool(request[offset + 4]);
|
||||
var zero = request[offset + 5];
|
||||
Tools.AssertEqual(zero, 0, "Zero mismatch got {0} expected {1}");
|
||||
return new ModbusF05WriteCoil(slave, address, value);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse06(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var value = ModbusUtils.GetUShort(request, offset + 4);
|
||||
return new ModbusF06WriteRegister(slave, address, value);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse15(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
var values = ModbusUtils.DecodeBools(request, offset + 7, count);
|
||||
var bytes = request[offset + 6];
|
||||
Tools.AssertEqual(ModbusUtils.BytesForBools(count), bytes, "Byte count mismatch got {0} expected {1}");
|
||||
return new ModbusF15WriteCoils(slave, address, values);
|
||||
}
|
||||
|
||||
private static IModbusCommand Parse16(byte slave, byte code, ushort address, byte[] request, int offset)
|
||||
{
|
||||
var count = ModbusUtils.GetUShort(request, offset + 4);
|
||||
var values = ModbusUtils.DecodeWords(request, offset + 7, count);
|
||||
var bytes = request[offset + 6];
|
||||
Tools.AssertEqual(ModbusUtils.BytesForWords(count), bytes, "Byte count mismatch got {0} expected {1}");
|
||||
return new ModbusF16WriteRegisters(slave, address, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusRTUProtocol : IModbusProtocol
|
||||
{
|
||||
public IModbusWrapper Wrap(IModbusCommand wrapped)
|
||||
{
|
||||
return new ModbusRTUWrapper(wrapped);
|
||||
}
|
||||
|
||||
public IModbusWrapper Parse(byte[] request, int offset)
|
||||
{
|
||||
var wrapped = ModbusParser.Parse(request, offset);
|
||||
var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength);
|
||||
Tools.AssertEqual(crc, ModbusUtils.GetUShortLittleEndian(request, offset + wrapped.RequestLength),
|
||||
"CRC mismatch {0:X4} expected {1:X4}");
|
||||
return new ModbusRTUWrapper(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUScanner.cs
Normal file
47
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUScanner.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusRTUScanner : IModbusScanner
|
||||
{
|
||||
private readonly ModbusRTUProtocol protocol = new ModbusRTUProtocol();
|
||||
private readonly List<byte> buffer = new List<byte>();
|
||||
|
||||
public void Append(byte[] data, int offset, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++) buffer.Add(data[offset + i]);
|
||||
}
|
||||
|
||||
public IModbusWrapper Scan()
|
||||
{
|
||||
//01,02,03,04,05,06 have 6 + 2(CRC)
|
||||
//15,16 have 6 + 1(len) + len + 2(CRC)
|
||||
if (buffer.Count >= 8)
|
||||
{
|
||||
var code = buffer[1];
|
||||
CheckCode(code);
|
||||
var length = 8;
|
||||
if (HasBytesAt6(code)) length += 1 + buffer[6];
|
||||
if (buffer.Count >= length)
|
||||
{
|
||||
var request = buffer.GetRange(0, length).ToArray();
|
||||
buffer.RemoveRange(0, length);
|
||||
return protocol.Parse(request, 0);
|
||||
}
|
||||
}
|
||||
return null; //not enough data to parse
|
||||
}
|
||||
|
||||
bool HasBytesAt6(byte code)
|
||||
{
|
||||
return "15,16".Contains(code.ToString("00"));
|
||||
}
|
||||
|
||||
void CheckCode(byte code)
|
||||
{
|
||||
var valid = "01,02,03,04,05,06,15,16".Contains(code.ToString("00"));
|
||||
if (!valid) Tools.Throw("Unsupported code {0}", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUWrapper.cs
Normal file
85
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUWrapper.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusRTUWrapper : IModbusWrapper
|
||||
{
|
||||
private readonly IModbusCommand wrapped;
|
||||
|
||||
public byte Code { get { return wrapped.Code; } }
|
||||
public byte Slave { get { return wrapped.Slave; } }
|
||||
public ushort Address { get { return wrapped.Address; } }
|
||||
public IModbusCommand Wrapped { get { return wrapped; } }
|
||||
public int RequestLength { get { return wrapped.RequestLength + 2; } }
|
||||
public int ResponseLength { get { return wrapped.ResponseLength + 2; } }
|
||||
|
||||
public ModbusRTUWrapper(IModbusCommand wrapped)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
wrapped.FillRequest(request, offset);
|
||||
var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength);
|
||||
request[offset + wrapped.RequestLength + 0] = ModbusUtils.Low(crc);
|
||||
request[offset + wrapped.RequestLength + 1] = ModbusUtils.High(crc);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
var crc1 = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength);
|
||||
//crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
|
||||
var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + wrapped.ResponseLength);
|
||||
Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}");
|
||||
return wrapped.ParseResponse(response, offset);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return wrapped.ApplyTo(model);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
wrapped.FillResponse(response, offset, value);
|
||||
var crc = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength);
|
||||
response[offset + wrapped.ResponseLength + 0] = ModbusUtils.Low(crc);
|
||||
response[offset + wrapped.ResponseLength + 1] = ModbusUtils.High(crc);
|
||||
}
|
||||
|
||||
public byte[] GetException(byte code)
|
||||
{
|
||||
var exception = new byte[5];
|
||||
exception[0] = wrapped.Slave;
|
||||
exception[1] = (byte)(wrapped.Code | 0x80);
|
||||
exception[2] = code;
|
||||
var crc = ModbusUtils.CRC16(exception, 0, 3);
|
||||
exception[3] = ModbusUtils.Low(crc);
|
||||
exception[4] = ModbusUtils.High(crc);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public void CheckException(byte[] response, int count)
|
||||
{
|
||||
if (count < 5) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 5);
|
||||
var offset = 0;
|
||||
var code = response[offset + 1];
|
||||
if ((code & 0x80) != 0)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}");
|
||||
var crc1 = ModbusUtils.CRC16(response, offset, 3);
|
||||
//crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
|
||||
var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + 3);
|
||||
Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}");
|
||||
throw new ModbusException(response[offset + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusRTUWrapper Wrapped={0}]", wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using SharpSerial;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusSerialStream : IModbusStream
|
||||
{
|
||||
private readonly Action<char, byte[], int> monitor;
|
||||
private readonly SerialDevice serialDevice;
|
||||
private readonly int timeout;
|
||||
|
||||
public ModbusSerialStream(SerialSettings settings, int timeout, Action<char, byte[], int> monitor = null)
|
||||
{
|
||||
this.serialDevice = new SerialDevice(settings);
|
||||
this.timeout = timeout;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Dispose(serialDevice);
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
if (monitor != null) monitor('>', data, data.Length);
|
||||
serialDevice.Write(data);
|
||||
}
|
||||
|
||||
public int Read(byte[] data)
|
||||
{
|
||||
var response = serialDevice.Read(data.Length, -1, timeout);
|
||||
var count = response.Length;
|
||||
for (var i = 0; i < count; i++) data[i] = response[i];
|
||||
if (monitor != null) monitor('<', data, count);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusSocketStream : IModbusStream
|
||||
{
|
||||
private readonly TcpClient socket;
|
||||
private readonly Action<char, byte[], int> monitor;
|
||||
|
||||
public ModbusSocketStream(TcpClient socket, int timeout, Action<char, byte[], int> monitor = null)
|
||||
{
|
||||
socket.ReceiveTimeout = timeout;
|
||||
socket.SendTimeout = timeout;
|
||||
this.monitor = monitor;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Dispose(socket);
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
//implicit discard to allow retry after timeout as per #5
|
||||
while (socket.Available > 0) socket.GetStream().ReadByte();
|
||||
if (monitor != null) monitor('>', data, data.Length);
|
||||
socket.GetStream().Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public int Read(byte[] data)
|
||||
{
|
||||
var count = 0;
|
||||
var dl = DateTime.Now.AddMilliseconds(socket.ReceiveTimeout);
|
||||
while (count < data.Length)
|
||||
{
|
||||
var available = socket.Available;
|
||||
if (available == 0)
|
||||
{
|
||||
if (DateTime.Now > dl) break;
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = (int)Math.Min(available, data.Length - count);
|
||||
count += socket.GetStream().Read(data, count, size);
|
||||
dl = DateTime.Now; //should come in single packet
|
||||
}
|
||||
}
|
||||
if (monitor != null) monitor('<', data, count);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusTCPProtocol : IModbusProtocol
|
||||
{
|
||||
private ushort transactionId = 0;
|
||||
|
||||
public ushort TransactionId
|
||||
{
|
||||
get { return transactionId; }
|
||||
set { transactionId = value; }
|
||||
}
|
||||
|
||||
public IModbusWrapper Wrap(IModbusCommand wrapped)
|
||||
{
|
||||
return new ModbusTCPWrapper(wrapped, transactionId++);
|
||||
}
|
||||
|
||||
public IModbusWrapper Parse(byte[] request, int offset)
|
||||
{
|
||||
var wrapped = ModbusParser.Parse(request, offset + 6);
|
||||
Tools.AssertEqual(wrapped.RequestLength, ModbusUtils.GetUShort(request, offset + 4),
|
||||
"RequestLength mismatch got {0} expected {1}");
|
||||
var transaction = ModbusUtils.GetUShort(request, offset);
|
||||
return new ModbusTCPWrapper(wrapped, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPScanner.cs
Normal file
31
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPScanner.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusTCPScanner : IModbusScanner
|
||||
{
|
||||
private readonly ModbusTCPProtocol protocol = new ModbusTCPProtocol();
|
||||
private readonly List<byte> buffer = new List<byte>();
|
||||
|
||||
public void Append(byte[] data, int offset, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++) buffer.Add(data[offset + i]);
|
||||
}
|
||||
|
||||
public IModbusWrapper Scan()
|
||||
{
|
||||
if (buffer.Count >= 6)
|
||||
{
|
||||
var length = ModbusUtils.GetUShort(buffer[4], buffer[5]);
|
||||
if (buffer.Count >= 6 + length)
|
||||
{
|
||||
var request = buffer.GetRange(0, 6 + length).ToArray();
|
||||
buffer.RemoveRange(0, 6 + length);
|
||||
return protocol.Parse(request, 0);
|
||||
}
|
||||
}
|
||||
return null; //not enough data to parse
|
||||
}
|
||||
}
|
||||
}
|
||||
92
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPWrapper.cs
Normal file
92
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPWrapper.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class ModbusTCPWrapper : IModbusWrapper
|
||||
{
|
||||
private readonly IModbusCommand wrapped;
|
||||
private readonly ushort transactionId;
|
||||
|
||||
public byte Code { get { return wrapped.Code; } }
|
||||
public byte Slave { get { return wrapped.Slave; } }
|
||||
public ushort Address { get { return wrapped.Address; } }
|
||||
public IModbusCommand Wrapped { get { return wrapped; } }
|
||||
public ushort TransactionId { get { return transactionId; } }
|
||||
public int RequestLength { get { return wrapped.RequestLength + 6; } }
|
||||
public int ResponseLength { get { return wrapped.ResponseLength + 6; } }
|
||||
|
||||
public ModbusTCPWrapper(IModbusCommand wrapped, ushort transactionId)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public void FillRequest(byte[] request, int offset)
|
||||
{
|
||||
request[offset + 0] = ModbusUtils.High(transactionId);
|
||||
request[offset + 1] = ModbusUtils.Low(transactionId);
|
||||
request[offset + 2] = 0;
|
||||
request[offset + 3] = 0;
|
||||
request[offset + 4] = ModbusUtils.High(wrapped.RequestLength);
|
||||
request[offset + 5] = ModbusUtils.Low(wrapped.RequestLength);
|
||||
wrapped.FillRequest(request, offset + 6);
|
||||
}
|
||||
|
||||
public object ParseResponse(byte[] response, int offset)
|
||||
{
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 0), transactionId, "TransactionId mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), 0, "Zero mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), wrapped.ResponseLength, "Length mismatch got {0} expected {1}");
|
||||
return wrapped.ParseResponse(response, offset + 6);
|
||||
}
|
||||
|
||||
public object ApplyTo(IModbusModel model)
|
||||
{
|
||||
return wrapped.ApplyTo(model);
|
||||
}
|
||||
|
||||
public void FillResponse(byte[] response, int offset, object value)
|
||||
{
|
||||
response[offset + 0] = ModbusUtils.High(transactionId);
|
||||
response[offset + 1] = ModbusUtils.Low(transactionId);
|
||||
response[offset + 2] = 0;
|
||||
response[offset + 3] = 0;
|
||||
response[offset + 4] = ModbusUtils.High(wrapped.ResponseLength);
|
||||
response[offset + 5] = ModbusUtils.Low(wrapped.ResponseLength);
|
||||
wrapped.FillResponse(response, offset + 6, value);
|
||||
}
|
||||
|
||||
public byte[] GetException(byte code)
|
||||
{
|
||||
var exception = new byte[9];
|
||||
exception[0] = ModbusUtils.High(transactionId);
|
||||
exception[1] = ModbusUtils.Low(transactionId);
|
||||
exception[2] = 0;
|
||||
exception[3] = 0;
|
||||
exception[4] = ModbusUtils.High(3);
|
||||
exception[5] = ModbusUtils.Low(3);
|
||||
exception[6 + 0] = wrapped.Slave;
|
||||
exception[6 + 1] = (byte)(wrapped.Code | 0x80);
|
||||
exception[6 + 2] = code;
|
||||
return exception;
|
||||
}
|
||||
|
||||
public void CheckException(byte[] response, int count)
|
||||
{
|
||||
if (count < 9) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 9);
|
||||
var offset = 6;
|
||||
var code = response[offset + 1];
|
||||
if ((code & 0x80) != 0)
|
||||
{
|
||||
Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}");
|
||||
Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}");
|
||||
throw new ModbusException(response[offset + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ModbusTCPWrapper Wrapped={0}, TransactionId={1}]", wrapped, transactionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTools.cs
Normal file
60
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTools.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public class SerialSettings : SharpSerial.SerialSettings { }
|
||||
|
||||
public static class Tools
|
||||
{
|
||||
public static void AssertEqual(int a, int b, string format)
|
||||
{
|
||||
if (a != b) Tools.Throw(format, a, b);
|
||||
}
|
||||
|
||||
public static TcpClient ConnectWithTimeout(string host, int port, int timeout)
|
||||
{
|
||||
var socket = new TcpClient();
|
||||
try
|
||||
{
|
||||
var result = socket.BeginConnect(host, port, null, null);
|
||||
var connected = result.AsyncWaitHandle.WaitOne(timeout, true);
|
||||
if (!connected) Tools.Throw("Timeout connecting to {0}:{1}", host, port);
|
||||
socket.EndConnect(result);
|
||||
return socket;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Tools.Dispose(socket);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Dispose(IDisposable disposable)
|
||||
{
|
||||
try { if (disposable != null) disposable.Dispose(); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
public static Exception Make(string format, params object[] args)
|
||||
{
|
||||
var message = format;
|
||||
if (args.Length > 0) message = string.Format(format, args);
|
||||
return new Exception(message);
|
||||
}
|
||||
|
||||
public static void Throw(string format, params object[] args)
|
||||
{
|
||||
var message = format;
|
||||
if (args.Length > 0) message = string.Format(format, args);
|
||||
throw new Exception(message);
|
||||
}
|
||||
|
||||
public static void Throw(Exception inner, string format, params object[] args)
|
||||
{
|
||||
var message = format;
|
||||
if (args.Length > 0) message = string.Format(format, args);
|
||||
throw new Exception(message, inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
169
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusUtils.cs
Normal file
169
常用工具集/Utility/Network/Modbus/SharpModbus/ModbusUtils.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
|
||||
namespace SharpModbus
|
||||
{
|
||||
public static class ModbusUtils
|
||||
{
|
||||
public static ushort CRC16(byte[] bytes, int offset, int count)
|
||||
{
|
||||
ushort crc = 0xFFFF;
|
||||
for (int pos = 0; pos < count; pos++)
|
||||
{
|
||||
crc ^= (ushort)bytes[pos + offset];
|
||||
for (int i = 8; i > 0; i--)
|
||||
{
|
||||
if ((crc & 0x0001) != 0)
|
||||
{
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
}
|
||||
else
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
public static byte EncodeBool(bool value)
|
||||
{
|
||||
return (byte)(value ? 0xFF : 0x00);
|
||||
}
|
||||
|
||||
public static bool DecodeBool(byte value)
|
||||
{
|
||||
return (value != 0x00);
|
||||
}
|
||||
|
||||
public static byte[] EncodeBools(bool[] bools)
|
||||
{
|
||||
var count = BytesForBools(bools.Length);
|
||||
var bytes = new byte[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
bytes[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < bools.Length; i++)
|
||||
{
|
||||
var v = bools[i];
|
||||
if (v)
|
||||
{
|
||||
var bi = i / 8;
|
||||
bytes[bi] |= (byte)(1 << (i % 8));
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static byte[] EncodeWords(ushort[] words)
|
||||
{
|
||||
var count = 2 * words.Length;
|
||||
var bytes = new byte[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
bytes[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < words.Length; i++)
|
||||
{
|
||||
bytes[2 * i + 0] = (byte)((words[i] >> 8) & 0xff);
|
||||
bytes[2 * i + 1] = (byte)((words[i] >> 0) & 0xff);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static bool[] DecodeBools(byte[] packet, int offset, ushort count)
|
||||
{
|
||||
var bools = new bool[count];
|
||||
var bytes = BytesForBools(count);
|
||||
for (var i = 0; i < bytes; i++)
|
||||
{
|
||||
var bits = count >= 8 ? 8 : count % 8;
|
||||
var b = packet[offset + i];
|
||||
ByteToBools(b, bools, bools.Length - count, bits);
|
||||
count -= (ushort)bits;
|
||||
}
|
||||
return bools;
|
||||
}
|
||||
|
||||
public static ushort[] DecodeWords(byte[] packet, int offset, ushort count)
|
||||
{
|
||||
var results = new ushort[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
results[i] = (ushort)(packet[offset + 2 * i] << 8 | packet[offset + 2 * i + 1]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void ByteToBools(byte b, bool[] bools, int offset, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
bools[offset + i] = ((b >> i) & 0x01) == 1;
|
||||
}
|
||||
|
||||
public static byte BytesForWords(int count)
|
||||
{
|
||||
return (byte)(2 * count);
|
||||
}
|
||||
|
||||
public static byte BytesForBools(int count)
|
||||
{
|
||||
return (byte)(count == 0 ? 0 : (count - 1) / 8 + 1);
|
||||
}
|
||||
|
||||
public static byte High(int value)
|
||||
{
|
||||
return (byte)((value >> 8) & 0xff);
|
||||
}
|
||||
|
||||
public static byte Low(int value)
|
||||
{
|
||||
return (byte)((value >> 0) & 0xff);
|
||||
}
|
||||
|
||||
public static ushort GetUShort(byte bh, byte bl)
|
||||
{
|
||||
return (ushort)(
|
||||
((bh << 8) & 0xFF00)
|
||||
| (bl & 0xff)
|
||||
);
|
||||
}
|
||||
|
||||
public static ushort GetUShort(byte[] bytes, int offset)
|
||||
{
|
||||
return (ushort)(
|
||||
((bytes[offset + 0] << 8) & 0xFF00)
|
||||
| (bytes[offset + 1] & 0xff)
|
||||
);
|
||||
}
|
||||
|
||||
public static ushort GetUShortLittleEndian(byte[] bytes, int offset)
|
||||
{
|
||||
return (ushort)(
|
||||
((bytes[offset + 1] << 8) & 0xFF00)
|
||||
| (bytes[offset + 0] & 0xff)
|
||||
);
|
||||
}
|
||||
|
||||
public static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
dst[dstOffset + i] = src[srcOffset + i];
|
||||
}
|
||||
|
||||
public static bool[] Clone(bool[] values)
|
||||
{
|
||||
var clone = new bool[values.Length];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
clone[i] = values[i];
|
||||
return clone;
|
||||
}
|
||||
|
||||
public static ushort[] Clone(ushort[] values)
|
||||
{
|
||||
var clone = new ushort[values.Length];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
clone[i] = values[i];
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs
Normal file
47
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
public static class Stdio
|
||||
{
|
||||
private static bool traceTimed = false;
|
||||
private static bool traceEnabled = false;
|
||||
private static readonly object locker = new object();
|
||||
|
||||
public static string ReadLine() => Console.ReadLine();
|
||||
|
||||
public static void WriteLine(string format, params object[] args)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
Console.Out.WriteLine(format, args);
|
||||
Console.Out.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Trace(string format, params object[] args)
|
||||
{
|
||||
if (traceEnabled)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (traceTimed)
|
||||
{
|
||||
Console.Error.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
|
||||
Console.Error.Write(" ");
|
||||
}
|
||||
Console.Error.WriteLine(format, args);
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnableTrace(bool enable, bool timed)
|
||||
{
|
||||
traceTimed = timed;
|
||||
traceEnabled = enable;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs
Normal file
99
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
public static class Tools
|
||||
{
|
||||
public static void SetupGlobalCatcher()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += ExceptionHandler;
|
||||
}
|
||||
|
||||
public static void ExceptionHandler(object sender, UnhandledExceptionEventArgs args)
|
||||
{
|
||||
ExceptionHandler(args.ExceptionObject as Exception);
|
||||
}
|
||||
|
||||
public static void ExceptionHandler(Exception ex)
|
||||
{
|
||||
Try(() => Stdio.Trace("!{0}", ex.ToString()));
|
||||
Try(() => Stdio.WriteLine("!{0}", ex.ToString()));
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
public static string StringHex(byte[] data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("<");
|
||||
foreach (var b in data) sb.Append(b.ToString("X2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static byte[] ParseHex(string text)
|
||||
{
|
||||
Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
|
||||
var bytes = new byte[text.Length / 2];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
var b2 = text.Substring(1 + i * 2, 2);
|
||||
bytes[i] = Convert.ToByte(b2, 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static void SetProperty(object target, string line)
|
||||
{
|
||||
var parts = line.Split(new char[] { '=' });
|
||||
if (parts.Length != 2) throw Make("Expected 2 parts in {0}", Readable(line));
|
||||
var propertyName = parts[0];
|
||||
var propertyValue = parts[1];
|
||||
var property = target.GetType().GetProperty(propertyName);
|
||||
if (property == null) throw Make("Property not found {0}", Readable(propertyName));
|
||||
var value = FromString(property.PropertyType, propertyValue);
|
||||
property.SetValue(target, value, null);
|
||||
}
|
||||
|
||||
public static object FromString(Type type, string text)
|
||||
{
|
||||
if (type.IsEnum) return Enum.Parse(type, text);
|
||||
return Convert.ChangeType(text, type);
|
||||
}
|
||||
|
||||
public static void Try(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//no clear use case for cleanup exception
|
||||
}
|
||||
}
|
||||
|
||||
public static Exception Make(string format, params object[] args)
|
||||
{
|
||||
var line = format;
|
||||
if (args.Length > 0) line = string.Format(format, args);
|
||||
return new Exception(line);
|
||||
}
|
||||
|
||||
public static string Readable(string text)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var c in text)
|
||||
{
|
||||
if (Char.IsControl(c)) sb.Append(((int)c).ToString("X2"));
|
||||
else if (Char.IsWhiteSpace(c)) sb.Append(((int)c).ToString("X2"));
|
||||
else sb.Append(c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static void Assert(bool condition, string format, params object[] args)
|
||||
{
|
||||
if (!condition) throw Make(format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs
Normal file
97
常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
// com0com BytesToRead is UNRELIABLE
|
||||
// Solution based on BaseStream and influenced by
|
||||
// https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport
|
||||
// https://www.vgies.com/a-reliable-serial-port-in-c/
|
||||
public class SerialDevice : ISerialStream, IDisposable
|
||||
{
|
||||
private readonly SerialPort serial;
|
||||
private readonly List<byte> list;
|
||||
private readonly Queue<byte> queue;
|
||||
private readonly byte[] buffer;
|
||||
|
||||
public SerialDevice(object settings)
|
||||
{
|
||||
this.buffer = new byte[256];
|
||||
this.list = new List<byte>(256);
|
||||
this.queue = new Queue<byte>(256);
|
||||
this.serial = new SerialPort();
|
||||
|
||||
//init serial port and launch async reader
|
||||
SerialSettings.CopyProperties(settings, serial);
|
||||
serial.Open();
|
||||
//DiscardInBuffer not needed by FTDI and ignored by com0com
|
||||
var stream = serial.BaseStream;
|
||||
//unavailable after closed so pass it
|
||||
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Try(serial.Close);
|
||||
Tools.Try(serial.Dispose);
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
var stream = serial.BaseStream;
|
||||
stream.Write(data, 0, data.Length);
|
||||
//always flush to allow sync by following read available
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
public byte[] Read(int size, int eop, int toms)
|
||||
{
|
||||
list.Clear();
|
||||
var dl = DateTime.Now.AddMilliseconds(toms);
|
||||
while (true)
|
||||
{
|
||||
int b = ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
//toms=0 should return immediately with available
|
||||
if (DateTime.Now >= dl) break;
|
||||
Thread.Sleep(1);
|
||||
continue;
|
||||
}
|
||||
list.Add((byte)b);
|
||||
if (eop >= 0 && b == eop) break;
|
||||
if (size >= 0 && list.Count >= size) break;
|
||||
dl = DateTime.Now.AddMilliseconds(toms);
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private int ReadByte()
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Count == 0) return -1;
|
||||
return queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCallback(IAsyncResult ar)
|
||||
{
|
||||
Tools.Try(() =>
|
||||
{
|
||||
//try needed to avoid triggering the domain unhandled
|
||||
//exception handler when used as standalone stream
|
||||
var stream = ar.AsyncState as Stream;
|
||||
int count = stream.EndRead(ar);
|
||||
if (count > 0) //0 for closed stream
|
||||
{
|
||||
lock (queue) for (var i = 0; i < count; i++) queue.Enqueue(buffer[i]);
|
||||
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs
Normal file
16
常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
public class SerialException : Exception
|
||||
{
|
||||
private readonly string trace;
|
||||
|
||||
public SerialException(string message, string trace) : base(message)
|
||||
{
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
public string Trace { get { return trace; } }
|
||||
}
|
||||
}
|
||||
126
常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs
Normal file
126
常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
//this class should not swallow exceptions outside dispose
|
||||
public class SerialProcess : ISerialStream, IDisposable
|
||||
{
|
||||
private readonly Process process;
|
||||
private readonly int pid;
|
||||
|
||||
public int Pid { get { return pid; } }
|
||||
|
||||
public SerialProcess(object settings)
|
||||
{
|
||||
var ss = new SerialSettings(settings);
|
||||
var args = new StringBuilder();
|
||||
foreach (var p in ss.GetType().GetProperties())
|
||||
{
|
||||
if (args.Length > 0) args.Append(" ");
|
||||
args.AppendFormat("{0}={1}", p.Name, p.GetValue(ss, null).ToString());
|
||||
}
|
||||
process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = typeof(SerialProcess).Assembly.Location,
|
||||
Arguments = args.ToString(),
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = false,
|
||||
};
|
||||
EnableStandardError(process.StartInfo);
|
||||
process.Start();
|
||||
pid = process.Id;
|
||||
ForwardStandardError(process.StandardError);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tools.Try(() =>
|
||||
{
|
||||
process.StandardInput.Close();
|
||||
process.WaitForExit(200);
|
||||
});
|
||||
Tools.Try(process.Kill);
|
||||
Tools.Try(process.Dispose);
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
WriteHex(data);
|
||||
var line = ReadLine();
|
||||
Tools.Assert(line == "<ok", "Unexpected write response {0}", line);
|
||||
}
|
||||
|
||||
public byte[] Read(int size, int eop, int toms)
|
||||
{
|
||||
WriteLine("$r,{0},{1},{2}", size, eop, toms);
|
||||
return ParseHex(ReadLine());
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
var line = process.StandardOutput.ReadLine();
|
||||
if (line == null) throw new EndOfStreamException("Serial process EOF");
|
||||
if (line.StartsWith("!"))
|
||||
{
|
||||
var trace = process.StandardOutput.ReadToEnd();
|
||||
throw new SerialException(line.Substring(1), trace);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private void WriteLine(string format, params object[] args)
|
||||
{
|
||||
process.StandardInput.WriteLine(format, args);
|
||||
process.StandardInput.Flush();
|
||||
}
|
||||
|
||||
private void WriteHex(byte[] data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(">");
|
||||
foreach (var b in data) sb.Append(b.ToString("X2"));
|
||||
WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
private byte[] ParseHex(string text)
|
||||
{
|
||||
Tools.Assert(text.StartsWith("<"), "First char < expected for {0}:{1}", text.Length, text);
|
||||
Tools.Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
|
||||
var bytes = new byte[text.Length / 2];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
var b2 = text.Substring(1 + i * 2, 2);
|
||||
bytes[i] = Convert.ToByte(b2, 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void EnableStandardError(ProcessStartInfo psi)
|
||||
{
|
||||
psi.RedirectStandardError = true;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void ForwardStandardError(StreamReader reader)
|
||||
{
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
while (line != null)
|
||||
{
|
||||
Stdio.Trace(line);
|
||||
line = reader.ReadLine();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
141
常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs
Normal file
141
常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Globalization;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
public class SerialSettings
|
||||
{
|
||||
public SerialSettings() => CopyProperties(new SerialPort(), this);
|
||||
public SerialSettings(string portName) => CopyProperties(new SerialPort(portName), this);
|
||||
public SerialSettings(object source) => CopyProperties(source, this);
|
||||
|
||||
public void CopyFrom(object source) => CopyProperties(source, this);
|
||||
public void CopyTo(object target) => CopyProperties(this, target);
|
||||
|
||||
[TypeConverter(typeof(PortNameConverter))]
|
||||
public string PortName { get; set; }
|
||||
[TypeConverter(typeof(BaudRateConverter))]
|
||||
public int BaudRate { get; set; }
|
||||
public int DataBits { get; set; }
|
||||
public Parity Parity { get; set; }
|
||||
public StopBits StopBits { get; set; }
|
||||
public Handshake Handshake { get; set; }
|
||||
|
||||
public static void CopyProperties(Object source, Object target)
|
||||
{
|
||||
CopyProperty(source, target, "PortName");
|
||||
CopyProperty(source, target, "BaudRate");
|
||||
CopyProperty(source, target, "DataBits");
|
||||
CopyProperty(source, target, "Parity");
|
||||
CopyProperty(source, target, "StopBits");
|
||||
CopyProperty(source, target, "Handshake");
|
||||
}
|
||||
|
||||
static void CopyProperty(Object source, Object target, string name)
|
||||
{
|
||||
var propertySource = source.GetType().GetProperty(name);
|
||||
var propertyTarget = target.GetType().GetProperty(name);
|
||||
propertyTarget.SetValue(target, propertySource.GetValue(source, null), null);
|
||||
}
|
||||
}
|
||||
|
||||
public class PortNameConverter : TypeConverter
|
||||
{
|
||||
//windows only?
|
||||
private readonly Regex re = new Regex(@"[^a-zA-Z0-9_]");
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||
{
|
||||
return new StandardValuesCollection(SerialPort.GetPortNames());
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
return (sourceType == typeof(string));
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context,
|
||||
CultureInfo culture, object value)
|
||||
{
|
||||
if (re.IsMatch(value.ToString())) throw Tools.Make("Invalid chars");
|
||||
return value;
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context,
|
||||
CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class BaudRateConverter : TypeConverter
|
||||
{
|
||||
public readonly static int[] BaudRates = new int[] {
|
||||
110,
|
||||
300,
|
||||
600,
|
||||
1200,
|
||||
2400,
|
||||
4800,
|
||||
9600,
|
||||
14400,
|
||||
19200,
|
||||
28800,
|
||||
38400,
|
||||
56000,
|
||||
57600,
|
||||
115200,
|
||||
128000,
|
||||
153600,
|
||||
230400,
|
||||
256000,
|
||||
460800,
|
||||
921600
|
||||
};
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||
{
|
||||
return new StandardValuesCollection(BaudRates);
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
return (sourceType == typeof(string));
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context,
|
||||
CultureInfo culture, object value)
|
||||
{
|
||||
return int.Parse(value.ToString());
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context,
|
||||
CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs
Normal file
10
常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace SharpSerial
|
||||
{
|
||||
public interface ISerialStream : IDisposable
|
||||
{
|
||||
void Write(byte[] data);
|
||||
byte[] Read(int size, int eop, int toms);
|
||||
}
|
||||
}
|
||||
1267
常用工具集/Utility/Network/OPCUA/ClientUtils.cs
Normal file
1267
常用工具集/Utility/Network/OPCUA/ClientUtils.cs
Normal file
File diff suppressed because it is too large
Load Diff
476
常用工具集/Utility/Network/OPCUA/FilterDeclaration.cs
Normal file
476
常用工具集/Utility/Network/OPCUA/FilterDeclaration.cs
Normal file
@@ -0,0 +1,476 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2019 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
|
||||
namespace OpcUaHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores a type declaration retrieved from a server.
|
||||
/// </summary>
|
||||
public class TypeDeclaration
|
||||
{
|
||||
/// <summary>
|
||||
/// The node if for the type.
|
||||
/// </summary>
|
||||
public NodeId NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The fully inhierited list of instance declarations for the type.
|
||||
/// </summary>
|
||||
public List<InstanceDeclaration> Declarations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores an instance declaration fetched from the server.
|
||||
/// </summary>
|
||||
public class InstanceDeclaration
|
||||
{
|
||||
/// <summary>
|
||||
/// The type that the declaration belongs to.
|
||||
/// </summary>
|
||||
public NodeId RootTypeId;
|
||||
|
||||
/// <summary>
|
||||
/// The browse path to the instance declaration.
|
||||
/// </summary>
|
||||
public QualifiedNameCollection BrowsePath;
|
||||
|
||||
/// <summary>
|
||||
/// The browse path to the instance declaration.
|
||||
/// </summary>
|
||||
public string BrowsePathDisplayText;
|
||||
|
||||
/// <summary>
|
||||
/// A localized path to the instance declaration.
|
||||
/// </summary>
|
||||
public string DisplayPath;
|
||||
|
||||
/// <summary>
|
||||
/// The node id for the instance declaration.
|
||||
/// </summary>
|
||||
public NodeId NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The node class of the instance declaration.
|
||||
/// </summary>
|
||||
public NodeClass NodeClass;
|
||||
|
||||
/// <summary>
|
||||
/// The browse name for the instance declaration.
|
||||
/// </summary>
|
||||
public QualifiedName BrowseName;
|
||||
|
||||
/// <summary>
|
||||
/// The display name for the instance declaration.
|
||||
/// </summary>
|
||||
public string DisplayName;
|
||||
|
||||
/// <summary>
|
||||
/// The description for the instance declaration.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// The modelling rule for the instance declaration (i.e. Mandatory or Optional).
|
||||
/// </summary>
|
||||
public NodeId ModellingRule;
|
||||
|
||||
/// <summary>
|
||||
/// The data type for the instance declaration.
|
||||
/// </summary>
|
||||
public NodeId DataType;
|
||||
|
||||
/// <summary>
|
||||
/// The value rank for the instance declaration.
|
||||
/// </summary>
|
||||
public int ValueRank;
|
||||
|
||||
/// <summary>
|
||||
/// The built-in type parent for the data type.
|
||||
/// </summary>
|
||||
public BuiltInType BuiltInType;
|
||||
|
||||
/// <summary>
|
||||
/// A localized name for the data type.
|
||||
/// </summary>
|
||||
public string DataTypeDisplayText;
|
||||
|
||||
/// <summary>
|
||||
/// An instance declaration that has been overridden by the current instance.
|
||||
/// </summary>
|
||||
public InstanceDeclaration OverriddenDeclaration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A field in a filter declaration.
|
||||
/// </summary>
|
||||
public class FilterDeclarationField
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclarationField.
|
||||
/// </summary>
|
||||
public FilterDeclarationField()
|
||||
{
|
||||
Selected = true;
|
||||
DisplayInList = false;
|
||||
FilterEnabled = false;
|
||||
FilterOperator = FilterOperator.Equals;
|
||||
FilterValue = Variant.Null;
|
||||
InstanceDeclaration = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclarationField.
|
||||
/// </summary>
|
||||
public FilterDeclarationField(InstanceDeclaration instanceDeclaration)
|
||||
{
|
||||
Selected = true;
|
||||
DisplayInList = false;
|
||||
FilterEnabled = false;
|
||||
FilterOperator = FilterOperator.Equals;
|
||||
FilterValue = Variant.Null;
|
||||
InstanceDeclaration = instanceDeclaration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclarationField.
|
||||
/// </summary>
|
||||
public FilterDeclarationField(FilterDeclarationField field)
|
||||
{
|
||||
Selected = field.Selected;
|
||||
DisplayInList = field.DisplayInList;
|
||||
FilterEnabled = field.FilterEnabled;
|
||||
FilterOperator = field.FilterOperator;
|
||||
FilterValue = field.FilterValue;
|
||||
InstanceDeclaration = field.InstanceDeclaration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the field is returned as part of the event notification.
|
||||
/// </summary>
|
||||
public bool Selected;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the field is displayed in the list view.
|
||||
/// </summary>
|
||||
public bool DisplayInList;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the filter is enabled.
|
||||
/// </summary>
|
||||
public bool FilterEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// The filter operator to use in the where clause.
|
||||
/// </summary>
|
||||
public FilterOperator FilterOperator;
|
||||
|
||||
/// <summary>
|
||||
/// The filter value to use in the where clause.
|
||||
/// </summary>
|
||||
public Variant FilterValue;
|
||||
|
||||
/// <summary>
|
||||
/// The instance declaration associated with the field.
|
||||
/// </summary>
|
||||
public InstanceDeclaration InstanceDeclaration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A declararion of an event filter.
|
||||
/// </summary>
|
||||
public class FilterDeclaration
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclaration.
|
||||
/// </summary>
|
||||
public FilterDeclaration()
|
||||
{
|
||||
EventTypeId = Opc.Ua.ObjectTypeIds.BaseEventType;
|
||||
Fields = new List<FilterDeclarationField>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclaration.
|
||||
/// </summary>
|
||||
public FilterDeclaration(TypeDeclaration eventType, FilterDeclaration template)
|
||||
{
|
||||
EventTypeId = eventType.NodeId;
|
||||
Fields = new List<FilterDeclarationField>();
|
||||
|
||||
foreach (InstanceDeclaration instanceDeclaration in eventType.Declarations)
|
||||
{
|
||||
if (instanceDeclaration.NodeClass == NodeClass.Method)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NodeId.IsNull(instanceDeclaration.ModellingRule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FilterDeclarationField element = new FilterDeclarationField(instanceDeclaration);
|
||||
Fields.Add(element);
|
||||
|
||||
// set reasonable defaults.
|
||||
if (template == null)
|
||||
{
|
||||
if (instanceDeclaration.RootTypeId == Opc.Ua.ObjectTypeIds.BaseEventType && instanceDeclaration.BrowseName != Opc.Ua.BrowseNames.EventId)
|
||||
{
|
||||
element.DisplayInList = true;
|
||||
}
|
||||
}
|
||||
|
||||
// preserve filter settings.
|
||||
else
|
||||
{
|
||||
foreach (FilterDeclarationField field in template.Fields)
|
||||
{
|
||||
if (field.InstanceDeclaration.BrowsePathDisplayText == element.InstanceDeclaration.BrowsePathDisplayText)
|
||||
{
|
||||
element.DisplayInList = field.DisplayInList;
|
||||
element.FilterEnabled = field.FilterEnabled;
|
||||
element.FilterOperator = field.FilterOperator;
|
||||
element.FilterValue = field.FilterValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a FilterDeclaration.
|
||||
/// </summary>
|
||||
public FilterDeclaration(FilterDeclaration declaration)
|
||||
{
|
||||
EventTypeId = declaration.EventTypeId;
|
||||
Fields = new List<FilterDeclarationField>(declaration.Fields.Count);
|
||||
|
||||
for (int ii = 0; ii < declaration.Fields.Count; ii++)
|
||||
{
|
||||
Fields.Add(new FilterDeclarationField(declaration.Fields[ii]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the event filter defined by the filter declaration.
|
||||
/// </summary>
|
||||
public EventFilter GetFilter()
|
||||
{
|
||||
EventFilter filter = new EventFilter();
|
||||
filter.SelectClauses = GetSelectClause();
|
||||
filter.WhereClause = GetWhereClause();
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a simple field to the declaration.
|
||||
/// </summary>
|
||||
public void AddSimpleField(QualifiedName browseName, BuiltInType dataType, bool displayInList)
|
||||
{
|
||||
AddSimpleField(new QualifiedName[] { browseName }, NodeClass.Variable, dataType, ValueRanks.Scalar, displayInList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a simple field to the declaration.
|
||||
/// </summary>
|
||||
public void AddSimpleField(QualifiedName browseName, BuiltInType dataType, int valueRank, bool displayInList)
|
||||
{
|
||||
AddSimpleField(new QualifiedName[] { browseName }, NodeClass.Variable, dataType, valueRank, displayInList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a simple field to the declaration.
|
||||
/// </summary>
|
||||
public void AddSimpleField(QualifiedName[] browseNames, BuiltInType dataType, int valueRank, bool displayInList)
|
||||
{
|
||||
AddSimpleField(browseNames, NodeClass.Variable, dataType, valueRank, displayInList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a simple field to the declaration.
|
||||
/// </summary>
|
||||
public void AddSimpleField(QualifiedName[] browseNames, NodeClass nodeClass, BuiltInType dataType, int valueRank, bool displayInList)
|
||||
{
|
||||
FilterDeclarationField field = new FilterDeclarationField();
|
||||
|
||||
field.DisplayInList = displayInList;
|
||||
field.InstanceDeclaration = new InstanceDeclaration();
|
||||
field.InstanceDeclaration.NodeClass = nodeClass;
|
||||
|
||||
if (browseNames != null)
|
||||
{
|
||||
field.InstanceDeclaration.BrowseName = browseNames[browseNames.Length - 1];
|
||||
field.InstanceDeclaration.BrowsePath = new QualifiedNameCollection();
|
||||
|
||||
StringBuilder path = new StringBuilder();
|
||||
|
||||
for (int ii = 0; ii < browseNames.Length; ii++)
|
||||
{
|
||||
if (path.Length > 0)
|
||||
{
|
||||
path.Append('/');
|
||||
}
|
||||
|
||||
path.Append(browseNames[ii]);
|
||||
field.InstanceDeclaration.BrowsePath.Add(browseNames[ii]);
|
||||
}
|
||||
|
||||
field.InstanceDeclaration.BrowsePathDisplayText = path.ToString();
|
||||
}
|
||||
|
||||
field.InstanceDeclaration.BuiltInType = dataType;
|
||||
field.InstanceDeclaration.DataType = (uint)dataType;
|
||||
field.InstanceDeclaration.ValueRank = valueRank;
|
||||
field.InstanceDeclaration.DataTypeDisplayText = dataType.ToString();
|
||||
|
||||
if (valueRank >= 0)
|
||||
{
|
||||
field.InstanceDeclaration.DataTypeDisplayText += "[]";
|
||||
}
|
||||
|
||||
field.InstanceDeclaration.DisplayName = field.InstanceDeclaration.BrowseName.Name;
|
||||
field.InstanceDeclaration.DisplayPath = field.InstanceDeclaration.BrowsePathDisplayText;
|
||||
field.InstanceDeclaration.RootTypeId = ObjectTypeIds.BaseEventType;
|
||||
Fields.Add(field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the select clause defined by the filter declaration.
|
||||
/// </summary>
|
||||
public SimpleAttributeOperandCollection GetSelectClause()
|
||||
{
|
||||
SimpleAttributeOperandCollection selectClause = new SimpleAttributeOperandCollection();
|
||||
|
||||
SimpleAttributeOperand operand = new SimpleAttributeOperand();
|
||||
operand.TypeDefinitionId = Opc.Ua.ObjectTypeIds.BaseEventType;
|
||||
operand.AttributeId = Attributes.NodeId;
|
||||
selectClause.Add(operand);
|
||||
|
||||
foreach (FilterDeclarationField field in Fields)
|
||||
{
|
||||
if (field.Selected)
|
||||
{
|
||||
operand = new SimpleAttributeOperand();
|
||||
operand.TypeDefinitionId = field.InstanceDeclaration.RootTypeId;
|
||||
operand.AttributeId = (field.InstanceDeclaration.NodeClass == NodeClass.Object) ? Attributes.NodeId : Attributes.Value;
|
||||
operand.BrowsePath = field.InstanceDeclaration.BrowsePath;
|
||||
selectClause.Add(operand);
|
||||
}
|
||||
}
|
||||
|
||||
return selectClause;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the where clause defined by the filter declaration.
|
||||
/// </summary>
|
||||
public ContentFilter GetWhereClause()
|
||||
{
|
||||
ContentFilter whereClause = new ContentFilter();
|
||||
ContentFilterElement element1 = whereClause.Push(FilterOperator.OfType, EventTypeId);
|
||||
|
||||
EventFilter filter = new EventFilter();
|
||||
|
||||
foreach (FilterDeclarationField field in Fields)
|
||||
{
|
||||
if (field.FilterEnabled)
|
||||
{
|
||||
SimpleAttributeOperand operand1 = new SimpleAttributeOperand();
|
||||
operand1.TypeDefinitionId = field.InstanceDeclaration.RootTypeId;
|
||||
operand1.AttributeId = (field.InstanceDeclaration.NodeClass == NodeClass.Object) ? Attributes.NodeId : Attributes.Value;
|
||||
operand1.BrowsePath = field.InstanceDeclaration.BrowsePath;
|
||||
|
||||
LiteralOperand operand2 = new LiteralOperand();
|
||||
operand2.Value = field.FilterValue;
|
||||
|
||||
ContentFilterElement element2 = whereClause.Push(field.FilterOperator, operand1, operand2);
|
||||
element1 = whereClause.Push(FilterOperator.And, element1, element2);
|
||||
}
|
||||
}
|
||||
|
||||
return whereClause;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value for the specified browse name.
|
||||
/// </summary>
|
||||
public T GetValue<T>(QualifiedName browseName, VariantCollection fields, T defaultValue)
|
||||
{
|
||||
if (fields == null || fields.Count == 0)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (browseName == null)
|
||||
{
|
||||
browseName = QualifiedName.Null;
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < this.Fields.Count; ii++)
|
||||
{
|
||||
if (this.Fields[ii].InstanceDeclaration.BrowseName == browseName)
|
||||
{
|
||||
if (ii >= fields.Count + 1)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
object value = fields[ii + 1].Value;
|
||||
|
||||
if (typeof(T).IsInstanceOfType(value))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of event.
|
||||
/// </summary>
|
||||
public NodeId EventTypeId;
|
||||
|
||||
/// <summary>
|
||||
/// The list of declarations for the fields.
|
||||
/// </summary>
|
||||
public List<FilterDeclarationField> Fields;
|
||||
}
|
||||
}
|
||||
1021
常用工具集/Utility/Network/OPCUA/FormUtils.cs
Normal file
1021
常用工具集/Utility/Network/OPCUA/FormUtils.cs
Normal file
File diff suppressed because it is too large
Load Diff
1450
常用工具集/Utility/Network/OPCUA/OpcUaClient.cs
Normal file
1450
常用工具集/Utility/Network/OPCUA/OpcUaClient.cs
Normal file
File diff suppressed because it is too large
Load Diff
65
常用工具集/Utility/Network/OPCUA/OpcUaStatusEventArgs.cs
Normal file
65
常用工具集/Utility/Network/OPCUA/OpcUaStatusEventArgs.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpcUaHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// OPC UA的状态更新消息
|
||||
/// </summary>
|
||||
public class OpcUaStatusEventArgs
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否异常
|
||||
/// </summary>
|
||||
public bool Error { get; set; }
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time { get; set; }
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转化为字符串
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Error ? "[异常]" : "[正常]" + Time.ToString(" yyyy-MM-dd HH:mm:ss ") + Text;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取属性过程中用于描述的
|
||||
/// </summary>
|
||||
public class OpcNodeAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性的名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 属性的类型描述
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
/// <summary>
|
||||
/// 操作结果状态描述
|
||||
/// </summary>
|
||||
public StatusCode StatusCode { get; set; }
|
||||
/// <summary>
|
||||
/// 属性的值,如果读取错误,返回文本描述
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
51
常用工具集/Utility/Network/Omrons/DataTools.cs
Normal file
51
常用工具集/Utility/Network/Omrons/DataTools.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OmronLib
|
||||
{
|
||||
public static class DataTools
|
||||
{
|
||||
/// <summary>
|
||||
/// 将byte类型数据数组转换为16进制字符串形式
|
||||
/// </summary>
|
||||
/// <param name="hex"></param>
|
||||
/// <param name="len"></param>
|
||||
/// <returns></returns>
|
||||
public static string ByteToHexString(byte[] hex, int len)
|
||||
{
|
||||
string returnstr = "";
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
returnstr += hex[i].ToString("X2");
|
||||
}
|
||||
return returnstr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个长字符串转化为每个元素包含两个字符的字符数组
|
||||
/// </summary>
|
||||
/// <param name="src"></param>
|
||||
/// <param name="def"></param>
|
||||
/// <returns></returns>
|
||||
public static string[] StrToStrArray(string src)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res = new string[src.Length / 2];
|
||||
|
||||
for (int i = 0; i < src.Length / 2; i++)
|
||||
{
|
||||
res[i] = src.Substring(i * 2, 2);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
960
常用工具集/Utility/Network/Omrons/Omron.cs
Normal file
960
常用工具集/Utility/Network/Omrons/Omron.cs
Normal file
@@ -0,0 +1,960 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace OmronLib
|
||||
{
|
||||
/*
|
||||
*Copyright: Copyright (c) 2019
|
||||
*Created on 2019-11-7
|
||||
*Author: coder_li@outlook.com
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// 欧姆龙PLC使用FINS通讯,在与PLC建立tcp连接后,必须先进行读写准备,获取到DA1和SA1
|
||||
/// 这里默认读取200个D区地址,从D5000开始到D5199,读取的返回值都是高位在前低位在后
|
||||
/// 写入时都是单个寄存器写入,写入的内容也是高位在前低位在后
|
||||
/// </summary>
|
||||
public class Omron
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// PLCIP
|
||||
/// </summary>
|
||||
public IPAddress IPAddr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PLC端口号
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
public int Timeout = 1000;
|
||||
/// <summary>
|
||||
/// 获取是否已经连接到PLC
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PlcClient == null)
|
||||
return false;
|
||||
else
|
||||
return PlcClient.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PLC内存区域类型
|
||||
/// </summary>
|
||||
public enum AreaType
|
||||
{
|
||||
CIO_Bit = 0x30,
|
||||
WR_Bit = 0x31,
|
||||
HR_Bit = 0x32,
|
||||
AR_Bit = 0x33,
|
||||
DM_Bit = 0x02,
|
||||
CIO_Word = 0xB0,
|
||||
WR_Word = 0xB1,
|
||||
HR_Word = 0xB2,
|
||||
AR_Word = 0xB3,
|
||||
DM_Word = 0x82
|
||||
}
|
||||
|
||||
|
||||
private readonly object locker = new object();
|
||||
private TcpClient PlcClient = null;
|
||||
private NetworkStream Stream = null;
|
||||
private IPEndPoint PlcIP = null;
|
||||
|
||||
private byte DA1 = 0x00, SA1 = 0x00;
|
||||
|
||||
public Omron()
|
||||
{ }
|
||||
|
||||
#region 命令模板
|
||||
/// <summary>
|
||||
/// 写命令
|
||||
/// </summary>
|
||||
private readonly byte[] WriteCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S
|
||||
0x00, 0x00, 0x00, 0x1c, // 命令长度
|
||||
0x00, 0x00, 0x00, 0x02, // 命令码
|
||||
0x00, 0x00, 0x00, 0x00, // 错误码
|
||||
0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA
|
||||
0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1
|
||||
0x00, 0xDA, // SA2 SID
|
||||
0x01, 0x02, // FINS主命令 FINS从命令 (01 02 写)
|
||||
0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(从高到低) 开始位地址
|
||||
0x00, 0x01 }; // 写入数量(从高到低) 后面都是写入的内容(可以加长)
|
||||
/// <summary>
|
||||
/// 读命令
|
||||
/// </summary>
|
||||
private readonly byte[] ReadCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S
|
||||
0x00, 0x00, 0x00, 0x1a, // 命令长度
|
||||
0x00, 0x00, 0x00, 0x02, // 命令码
|
||||
0x00, 0x00, 0x00, 0x00, // 错误码
|
||||
0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA ***************
|
||||
0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 FINS Header
|
||||
0x00, 0xDA, // SA2 SID ***************
|
||||
0x01, 0x01, // FINS主命令 FINS从命令 (01 01 读) ***************
|
||||
0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(高,低) 开始位地址 FINS Command
|
||||
0x00, 0x01 }; // 读取数量(高,低) ***************
|
||||
/// <summary>
|
||||
/// 获取节点地址的命令
|
||||
/// </summary>
|
||||
private readonly byte[] PrepareCmd = new byte[] { 0x46, 0x49, 0x4e, 0x53,
|
||||
0x00, 0x00, 0x00, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 向PLC发送指令
|
||||
/// </summary>
|
||||
/// <param name="commands">指令</param>
|
||||
/// <param name="info">报错信息</param>
|
||||
/// <returns>是否发送成功</returns>
|
||||
private bool DataSend(byte[] commands)
|
||||
{
|
||||
string info;
|
||||
try
|
||||
{
|
||||
if (this.PlcClient != null && PlcClient.Connected)
|
||||
{
|
||||
//lock (locker)
|
||||
{
|
||||
if (Stream == null) Stream = PlcClient.GetStream();
|
||||
Stream.Write(commands, 0, commands.Length);
|
||||
Stream.Flush();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
info = "连接已断开";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
info = "发送指令到PLC失败 " + exp.Message;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从PLC读取数据
|
||||
/// </summary>
|
||||
/// <param name="isExtCmd"></param>
|
||||
/// <param name="info">报错信息</param>
|
||||
/// <returns></returns>
|
||||
private string DataRead()
|
||||
{
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytestoread = 0;
|
||||
string info;
|
||||
|
||||
try
|
||||
{
|
||||
//if (Stream.DataAvailable)
|
||||
{
|
||||
int start = Environment.TickCount;
|
||||
do
|
||||
{
|
||||
bytestoread = Stream.Read(buffer, 0, buffer.Length);
|
||||
if (Math.Abs(Environment.TickCount - start) >= 20000)
|
||||
{
|
||||
info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!";
|
||||
break;
|
||||
}
|
||||
} while (bytestoread == 0);
|
||||
|
||||
if (buffer[11] == 3)
|
||||
{
|
||||
if (buffer[15] == 1)
|
||||
{
|
||||
info = "发送的命令不是有效的FINS命令!";
|
||||
return "";
|
||||
}
|
||||
else if (buffer[15] == 2)
|
||||
{
|
||||
info = "发送的命令长度超出范围!";
|
||||
return "";
|
||||
}
|
||||
else if (buffer[15] == 3)
|
||||
{
|
||||
info = "PLC不支持该‘FINS’命令!";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (buffer[25] == WriteCommand[25])
|
||||
return DataTools.ByteToHexString(buffer, bytestoread);
|
||||
else
|
||||
{
|
||||
info = "PLC返回错误,返回值 " + buffer[0].ToString();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否成功写入命令到PLC
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool Write2PlcSuccess()
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytestoread = 0;
|
||||
string info;
|
||||
int start = Environment.TickCount;
|
||||
|
||||
try
|
||||
{
|
||||
//lock (locker)
|
||||
{
|
||||
do
|
||||
{
|
||||
bytestoread = Stream.Read(buffer, 0, buffer.Length);
|
||||
if (Math.Abs(Environment.TickCount - start) >= 1000)
|
||||
{
|
||||
info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!";
|
||||
break;
|
||||
}
|
||||
} while (bytestoread == 0);
|
||||
|
||||
}
|
||||
if (buffer[25] == WriteCommand[25]) return true;
|
||||
else
|
||||
{
|
||||
info = "PLC返回错误,返回值 " + buffer[1].ToString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送连接字符串,获取PLC返回的DA1和SA1值
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool ConnectPrepare()
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream.Write(PrepareCmd, 0, PrepareCmd.Length);
|
||||
byte[] res = new byte[24];
|
||||
Stream.Read(res, 0, res.Length);
|
||||
DA1 = res[23];
|
||||
SA1 = res[19];
|
||||
}
|
||||
catch { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查命令帧中的EndCode
|
||||
/// </summary>
|
||||
/// <param name="Main">主码</param>
|
||||
/// <param name="Sub">副码</param>
|
||||
/// <param name="info">错误信息</param>
|
||||
/// <returns>指示程序是否可以继续进行</returns>
|
||||
private bool CheckEndCode(byte Main, byte Sub, out string info)
|
||||
{
|
||||
info = "";
|
||||
switch (Main)
|
||||
{
|
||||
case 0x00:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x00: return true;//the only situation of success
|
||||
case 0x01: info = "service canceled"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "local node not in network"; return false;
|
||||
case 0x02: info = "token timeout"; return false;
|
||||
case 0x03: info = "retries failed"; return false;
|
||||
case 0x04: info = "too many send frames"; return false;
|
||||
case 0x05: info = "node address range error"; return false;
|
||||
case 0x06: info = "node address duplication"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x02:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "destination node not in network"; return false;
|
||||
case 0x02: info = "unit missing"; return false;
|
||||
case 0x03: info = "third node missing"; return false;
|
||||
case 0x04: info = "destination node busy"; return false;
|
||||
case 0x05: info = "response timeout"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x03:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "communications controller error"; return false;
|
||||
case 0x02: info = "CPU unit error"; return false;
|
||||
case 0x03: info = "controller error"; return false;
|
||||
case 0x04: info = "unit number error"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x04:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "undefined command"; return false;
|
||||
case 0x02: info = "not supported by model/version"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x05:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "destination address setting error"; return false;
|
||||
case 0x02: info = "no routing tables"; return false;
|
||||
case 0x03: info = "routing table error"; return false;
|
||||
case 0x04: info = "too many relays"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x10:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "command too long"; return false;
|
||||
case 0x02: info = "command too short"; return false;
|
||||
case 0x03: info = "elements/data don't match"; return false;
|
||||
case 0x04: info = "command format error"; return false;
|
||||
case 0x05: info = "header error"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x11:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "area classification missing"; return false;
|
||||
case 0x02: info = "access size error"; return false;
|
||||
case 0x03: info = "address range error"; return false;
|
||||
case 0x04: info = "address range exceeded"; return false;
|
||||
case 0x06: info = "program missing"; return false;
|
||||
case 0x09: info = "relational error"; return false;
|
||||
case 0x0a: info = "duplicate data access"; return false;
|
||||
case 0x0b: info = "response too long"; return false;
|
||||
case 0x0c: info = "parameter error"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x20:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x02: info = "protected"; return false;
|
||||
case 0x03: info = "table missing"; return false;
|
||||
case 0x04: info = "data missing"; return false;
|
||||
case 0x05: info = "program missing"; return false;
|
||||
case 0x06: info = "file missing"; return false;
|
||||
case 0x07: info = "data mismatch"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x21:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "read-only"; return false;
|
||||
case 0x02: info = "protected , cannot write data link table"; return false;
|
||||
case 0x03: info = "cannot register"; return false;
|
||||
case 0x05: info = "program missing"; return false;
|
||||
case 0x06: info = "file missing"; return false;
|
||||
case 0x07: info = "file name already exists"; return false;
|
||||
case 0x08: info = "cannot change"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x22:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "not possible during execution"; return false;
|
||||
case 0x02: info = "not possible while running"; return false;
|
||||
case 0x03: info = "wrong PLC mode"; return false;
|
||||
case 0x04: info = "wrong PLC mode"; return false;
|
||||
case 0x05: info = "wrong PLC mode"; return false;
|
||||
case 0x06: info = "wrong PLC mode"; return false;
|
||||
case 0x07: info = "specified node not polling node"; return false;
|
||||
case 0x08: info = "step cannot be executed"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x23:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "file device missing"; return false;
|
||||
case 0x02: info = "memory missing"; return false;
|
||||
case 0x03: info = "clock missing"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x24:
|
||||
switch (Sub)
|
||||
{ case 0x01: info = "table missing"; return false; }
|
||||
break;
|
||||
case 0x25:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x02: info = "memory error"; return false;
|
||||
case 0x03: info = "I/O setting error"; return false;
|
||||
case 0x04: info = "too many I/O points"; return false;
|
||||
case 0x05: info = "CPU bus error"; return false;
|
||||
case 0x06: info = "I/O duplication"; return false;
|
||||
case 0x07: info = "CPU bus error"; return false;
|
||||
case 0x09: info = "SYSMAC BUS/2 error"; return false;
|
||||
case 0x0a: info = "CPU bus unit error"; return false;
|
||||
case 0x0d: info = "SYSMAC BUS No. duplication"; return false;
|
||||
case 0x0f: info = "memory error"; return false;
|
||||
case 0x10: info = "SYSMAC BUS terminator missing"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x26:
|
||||
switch (Sub)
|
||||
{
|
||||
case 0x01: info = "no protection"; return false;
|
||||
case 0x02: info = "incorrect password"; return false;
|
||||
case 0x04: info = "protected"; return false;
|
||||
case 0x05: info = "service already executing"; return false;
|
||||
case 0x06: info = "service stopped"; return false;
|
||||
case 0x07: info = "no execution right"; return false;
|
||||
case 0x08: info = "settings required before execution"; return false;
|
||||
case 0x09: info = "necessary items not set"; return false;
|
||||
case 0x0a: info = "number already defined"; return false;
|
||||
case 0x0b: info = "error will not clear"; return false;
|
||||
}
|
||||
break;
|
||||
case 0x30:
|
||||
switch (Sub)
|
||||
{ case 0x01: info = "no access right"; return false; }
|
||||
break;
|
||||
case 0x40:
|
||||
switch (Sub)
|
||||
{ case 0x01: info = "service aborted"; return false; }
|
||||
break;
|
||||
}
|
||||
info = "unknown exception";
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PLC初始化
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
private bool PlcInit()
|
||||
{
|
||||
bool flag = false;
|
||||
PlcClient = new TcpClient();
|
||||
IAsyncResult asyncResult = PlcClient.BeginConnect(IPAddr, Port, null, null);
|
||||
if (!asyncResult.AsyncWaitHandle.WaitOne(Timeout))
|
||||
{
|
||||
return flag;
|
||||
}
|
||||
PlcClient.EndConnect(asyncResult);
|
||||
|
||||
if (PlcClient != null)
|
||||
{
|
||||
Stream = PlcClient.GetStream();
|
||||
Stream.ReadTimeout = Timeout;
|
||||
Stream.WriteTimeout = Timeout;
|
||||
if (ConnectPrepare())
|
||||
{
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接PLC
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
public bool PlcConnect(out string info)
|
||||
{
|
||||
info = "";
|
||||
try
|
||||
{
|
||||
if (PlcClient == null)
|
||||
{
|
||||
PlcInit();
|
||||
}
|
||||
if (!PlcClient.Connected) // 没连上的话重试一遍
|
||||
{
|
||||
PlcClient.Close();
|
||||
PlcClient = null;
|
||||
PlcInit();
|
||||
}
|
||||
return PlcClient.Connected;
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
info = "连接PLC失败\n" + exp.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开PLC连接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool DisConnect()
|
||||
{
|
||||
if (this.PlcClient == null)
|
||||
return true;
|
||||
if (!this.PlcClient.Connected)
|
||||
return true;
|
||||
|
||||
this.PlcClient.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region 读
|
||||
|
||||
/// <summary>
|
||||
/// 读PLC字或位
|
||||
/// 读位时从左到右是低位到高位
|
||||
/// </summary>
|
||||
/// <param name="type">PLC内存区类型</param>
|
||||
/// <param name="startword">开始地址(字)</param>
|
||||
/// <param name="startbit">开始地址(位)</param>
|
||||
/// <param name="count">长度</param>
|
||||
/// <param name="result">返回的字符数组,每个元素表示一个字</param>
|
||||
/// <returns></returns>
|
||||
public bool Read(AreaType type, int startword, int startbit, int count, out string[] result)
|
||||
{
|
||||
result = new string[0];
|
||||
byte[] cmd = (byte[])ReadCommand.Clone();
|
||||
cmd[20] = DA1; //连接时获取到的DA1
|
||||
cmd[23] = SA1; //连接时获取到的SA1
|
||||
|
||||
// 内存类型
|
||||
cmd[28] = (byte)type;
|
||||
|
||||
// 读取起始地址
|
||||
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
|
||||
cmd[29] = bytesAddr[1];
|
||||
cmd[30] = bytesAddr[0];
|
||||
cmd[31] = (byte)startbit;
|
||||
|
||||
// 读取长度
|
||||
byte[] bytesLength = BitConverter.GetBytes((short)count);
|
||||
cmd[32] = bytesLength[1];
|
||||
cmd[33] = bytesLength[0];
|
||||
|
||||
//开始读取
|
||||
lock (locker)
|
||||
{
|
||||
if (DataSend(cmd))
|
||||
{
|
||||
string res = DataRead();
|
||||
if (res.Length == 0)
|
||||
return false;
|
||||
|
||||
result = DataTools.StrToStrArray(res.Substring(60));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取DM区的字
|
||||
/// </summary>
|
||||
/// <param name="startword"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadDWords(int startword, int count, out string[] result)
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = Read(AreaType.DM_Word, startword, 0, count, out res);
|
||||
result = res;
|
||||
return isSucess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取DM区的位
|
||||
/// </summary>
|
||||
/// <param name="startword"></param>
|
||||
/// <param name="startbit"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadDBits(int startword, int startbit, int count, out string[] result)
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = Read(AreaType.DM_Bit, startword, startbit, count, out res);
|
||||
result = res;
|
||||
return isSucess;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 写
|
||||
|
||||
/// <summary>
|
||||
/// 写PLC字
|
||||
/// </summary>
|
||||
/// <param name="type">地址类型</param>
|
||||
/// <param name="startword">开始字地址</param>
|
||||
/// <param name="count">字数</param>
|
||||
/// <param name="paras">值</param>
|
||||
/// <returns>写入成功与否</returns>
|
||||
public bool WriteWords(AreaType type, int startword, int count, int[] paras)
|
||||
{
|
||||
byte[] hexadd = BitConverter.GetBytes(startword);
|
||||
byte[] sendCmd = WriteCommand.Concat(new byte[count * 2]).ToArray(); // 这里扩容sendCmd数组,不然会溢出
|
||||
|
||||
sendCmd[7] = (byte)(26 + count * 2);
|
||||
sendCmd[20] = DA1;
|
||||
sendCmd[23] = SA1;
|
||||
|
||||
// 内存类型
|
||||
sendCmd[28] = (byte)type;
|
||||
|
||||
// 写入起始地址
|
||||
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
|
||||
sendCmd[29] = bytesAddr[1];
|
||||
sendCmd[30] = bytesAddr[0];
|
||||
sendCmd[31] = 0;
|
||||
|
||||
// 写入长度
|
||||
byte[] bytesLength = BitConverter.GetBytes((short)count);
|
||||
sendCmd[32] = bytesLength[1];
|
||||
sendCmd[33] = bytesLength[0];
|
||||
|
||||
byte[] hexPara;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
hexPara = BitConverter.GetBytes(paras[i]);
|
||||
sendCmd[34 + i * 2] = hexPara[1];
|
||||
sendCmd[34 + i * 2 + 1] = hexPara[0];
|
||||
}
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
if (DataSend(sendCmd))
|
||||
{
|
||||
if (Write2PlcSuccess())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写PLC位
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="type">地址类型</param>
|
||||
/// <param name="startword">开始字地址</param>
|
||||
/// <param name="startbit">开始位地址</param>
|
||||
/// <param name="count">写入位数</param>
|
||||
/// <param name="paras">值</param>
|
||||
/// <returns>写入成功与否</returns>
|
||||
public bool WriteBits(AreaType type, int startword, int startbit, int count, bool[] paras)
|
||||
{
|
||||
byte[] hexadd = BitConverter.GetBytes(startword);
|
||||
byte[] sendCmd = WriteCommand.Concat(new byte[count]).ToArray(); // 这里扩容sendCmd数组,不然会溢出
|
||||
|
||||
// 命令长度
|
||||
sendCmd[7] = (byte)(26 + count);
|
||||
|
||||
sendCmd[20] = DA1;
|
||||
sendCmd[23] = SA1;
|
||||
|
||||
// 内存类型
|
||||
sendCmd[28] = (byte)type;
|
||||
|
||||
// 写入起始地址
|
||||
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
|
||||
sendCmd[29] = bytesAddr[1];
|
||||
sendCmd[30] = bytesAddr[0];
|
||||
sendCmd[31] = (byte)startbit;
|
||||
|
||||
// 写入长度
|
||||
byte[] bytesLength = BitConverter.GetBytes((short)count);
|
||||
sendCmd[32] = bytesLength[1];
|
||||
sendCmd[33] = bytesLength[0];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sendCmd[34 + i] = Convert.ToByte(paras[i]);
|
||||
}
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
if (DataSend(sendCmd))
|
||||
{
|
||||
if (Write2PlcSuccess())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写D字
|
||||
/// </summary>
|
||||
/// <param name="startword"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="paras"></param>
|
||||
/// <returns></returns>
|
||||
public bool WriteDWords(int startword, int count, int[] paras)
|
||||
{
|
||||
return WriteWords(AreaType.DM_Word, startword, count, paras);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写D位
|
||||
/// </summary>
|
||||
/// <param name="startword"></param>
|
||||
/// <param name="startbit"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="paras"></param>
|
||||
/// <returns></returns>
|
||||
public bool WriteDBits(int startword, int startbit, int count, bool[] paras)
|
||||
{
|
||||
return WriteBits(AreaType.DM_Bit, startword, startbit, count, paras);
|
||||
}
|
||||
|
||||
public bool ReadString(int address, int count, out string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = ReadDWords(address, count, out res);
|
||||
if (!isSucess)
|
||||
{
|
||||
str = "";
|
||||
return false;
|
||||
}
|
||||
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
|
||||
List<int> list = new List<int>();
|
||||
for (int i = 0; i < bRes.Length; i += 2)
|
||||
{
|
||||
list.Add((int)(bRes[i] << 8 | bRes[i + 1]));
|
||||
}
|
||||
str = GetQRCodeByData(list.ToArray());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
str = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadFloat(int address, out float value)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = ReadDWords(address, 2, out res);
|
||||
if (!isSucess)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
|
||||
if (bRes.Length != 4)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
value = BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadFloats(int address, int count, out float[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = ReadDWords(address, count * 2, out res);
|
||||
if (!isSucess)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
|
||||
if (bRes.Length != (count * 4))
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
List<float> list = new List<float>();
|
||||
for (int i = 0; i < bRes.Length; i += 4)
|
||||
{
|
||||
list.Add(BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0));
|
||||
}
|
||||
values = list.ToArray();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool WriteFloat(int address, float[] values)
|
||||
{
|
||||
List<int> writeValues = new List<int>();
|
||||
foreach (float f in values)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(f);
|
||||
writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[0], bytes[1], 0, 0 }, 0));
|
||||
writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[2], bytes[3], 0, 0 }, 0));
|
||||
}
|
||||
return WriteWords(AreaType.DM_Word, address, values.Length * 2, writeValues.ToArray());
|
||||
}
|
||||
|
||||
|
||||
public bool ReadShort(int address, out short value)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = ReadDWords(address, 1, out res);
|
||||
if (!isSucess)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
|
||||
if (bRes.Length != 2)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = BitConverter.ToInt16(new byte[] { bRes[1], bRes[0] }, 0);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool ReadShorts(int address, int count, out short[] values)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] res;
|
||||
bool isSucess = ReadDWords(address, count, out res);
|
||||
if (!isSucess)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
|
||||
if (bRes.Length != (count * 2))
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
List<short> list = new List<short>();
|
||||
for (int i = 0; i < bRes.Length; i += 2)
|
||||
{
|
||||
list.Add(BitConverter.ToInt16(new byte[] { bRes[i + 1], bRes[i] }, 0));
|
||||
}
|
||||
values = list.ToArray();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteShort(int address, short[] values)
|
||||
{
|
||||
List<int> writeValues = new List<int>();
|
||||
foreach (short f in values)
|
||||
{
|
||||
|
||||
writeValues.Add(f);
|
||||
}
|
||||
return WriteWords(AreaType.DM_Word, address, values.Length, writeValues.ToArray());
|
||||
}
|
||||
public bool WriteString(int address, string str, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
int[] aaa = GetDataByQRCode(str, count);
|
||||
return WriteDWords(address, count, aaa);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private int[] GetDataByQRCode(string qrcode, int totalCount)
|
||||
{
|
||||
byte[] byteList = new byte[2 * totalCount];
|
||||
|
||||
byte[] bytes = Encoding.ASCII.GetBytes(qrcode);
|
||||
for (int i = 0; i < byteList.Length; i++)
|
||||
{
|
||||
if (i >= bytes.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
byteList[i] = bytes[i];
|
||||
}
|
||||
|
||||
List<int> shortList = new List<int>();
|
||||
for (int i = 0; i < (2 * totalCount); i += 2)
|
||||
{
|
||||
//将字节数组转为int存入list
|
||||
shortList.Add(BitConverter.ToInt32(new byte[] { byteList[i], byteList[i + 1], 0x00, 0x00 }, 0));
|
||||
}
|
||||
return shortList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将PLC中获取到的数据转成string
|
||||
/// </summary>
|
||||
/// <param name="qrcode"></param>
|
||||
/// <returns></returns>
|
||||
private string GetQRCodeByData(int[] qrcode)
|
||||
{
|
||||
List<byte> list = new List<byte>();
|
||||
for (int i = 0; i < qrcode.Length; i++)
|
||||
{
|
||||
//int中取两个字节存入list
|
||||
list.AddRange(BitConverter.GetBytes(qrcode[i]).Take(2));
|
||||
}
|
||||
//去掉多余的/0和空格 转为string
|
||||
string qrcodes = Encoding.ASCII.GetString(list.ToArray()).Trim('\0').Trim();
|
||||
return qrcodes;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
118
常用工具集/Utility/Network/S7netplus/COTP.cs
Normal file
118
常用工具集/Utility/Network/S7netplus/COTP.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// COTP Protocol functions and types
|
||||
/// </summary>
|
||||
internal class COTP
|
||||
{
|
||||
public enum PduType : byte
|
||||
{
|
||||
Data = 0xf0,
|
||||
ConnectionConfirmed = 0xd0
|
||||
}
|
||||
/// <summary>
|
||||
/// Describes a COTP TPDU (Transport protocol data unit)
|
||||
/// </summary>
|
||||
public class TPDU
|
||||
{
|
||||
public TPKT TPkt { get; }
|
||||
public byte HeaderLength;
|
||||
public PduType PDUType;
|
||||
public int TPDUNumber;
|
||||
public byte[] Data;
|
||||
public bool LastDataUnit;
|
||||
|
||||
public TPDU(TPKT tPKT)
|
||||
{
|
||||
TPkt = tPKT;
|
||||
|
||||
HeaderLength = tPKT.Data[0]; // Header length excluding this length byte
|
||||
if (HeaderLength >= 2)
|
||||
{
|
||||
PDUType = (PduType)tPKT.Data[1];
|
||||
if (PDUType == PduType.Data) //DT Data
|
||||
{
|
||||
var flags = tPKT.Data[2];
|
||||
TPDUNumber = flags & 0x7F;
|
||||
LastDataUnit = (flags & 0x80) > 0;
|
||||
Data = new byte[tPKT.Data.Length - HeaderLength - 1]; // substract header length byte + header length.
|
||||
Array.Copy(tPKT.Data, HeaderLength + 1, Data, 0, Data.Length);
|
||||
return;
|
||||
}
|
||||
//TODO: Handle other PDUTypes
|
||||
}
|
||||
Data = new byte[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads COTP TPDU (Transport protocol data unit) from the network stream
|
||||
/// See: https://tools.ietf.org/html/rfc905
|
||||
/// </summary>
|
||||
/// <param name="stream">The socket to read from</param>
|
||||
/// <returns>COTP DPDU instance</returns>
|
||||
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (tpkt.Length == 0)
|
||||
{
|
||||
throw new TPDUInvalidException("No protocol data received");
|
||||
}
|
||||
return new TPDU(tpkt);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Length: {0} PDUType: {1} TPDUNumber: {2} Last: {3} Segment Data: {4}",
|
||||
HeaderLength,
|
||||
PDUType,
|
||||
TPDUNumber,
|
||||
LastDataUnit,
|
||||
BitConverter.ToString(Data)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs
|
||||
/// </summary>
|
||||
public class TSDU
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the full COTP TSDU (Transport service data unit)
|
||||
/// See: https://tools.ietf.org/html/rfc905
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from</param>
|
||||
/// <returns>Data in TSDU</returns>
|
||||
public static async Task<byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (segment.LastDataUnit)
|
||||
{
|
||||
return segment.Data;
|
||||
}
|
||||
|
||||
// More segments are expected, prepare a buffer to store all data
|
||||
var buffer = new byte[segment.Data.Length];
|
||||
Array.Copy(segment.Data, buffer, segment.Data.Length);
|
||||
|
||||
while (!segment.LastDataUnit)
|
||||
{
|
||||
segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
var previousLength = buffer.Length;
|
||||
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
|
||||
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
public static class TcpClientMixins
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
226
常用工具集/Utility/Network/S7netplus/Conversion.cs
Normal file
226
常用工具集/Utility/Network/S7netplus/Conversion.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Conversion methods to convert from Siemens numeric format to C# and back
|
||||
/// </summary>
|
||||
public static class Conversion
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a binary string to Int32 value
|
||||
/// </summary>
|
||||
/// <param name="txt"></param>
|
||||
/// <returns></returns>
|
||||
public static int BinStringToInt32(this string txt)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
for (int i = 0; i < txt.Length; i++)
|
||||
{
|
||||
ret = (ret << 1) | ((txt[i] == '1') ? 1 : 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a binary string to a byte. Can return null.
|
||||
/// </summary>
|
||||
/// <param name="txt"></param>
|
||||
/// <returns></returns>
|
||||
public static byte? BinStringToByte(this string txt)
|
||||
{
|
||||
if (txt.Length == 8) return (byte)BinStringToInt32(txt);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the value to a binary string
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string ValToBinString(this object value)
|
||||
{
|
||||
int cnt = 0;
|
||||
int cnt2 = 0;
|
||||
int x = 0;
|
||||
string txt = "";
|
||||
long longValue = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (value.GetType().Name.IndexOf("[]") < 0)
|
||||
{
|
||||
// ist nur ein Wert
|
||||
switch (value.GetType().Name)
|
||||
{
|
||||
case "Byte":
|
||||
x = 7;
|
||||
longValue = (long)((byte)value);
|
||||
break;
|
||||
case "Int16":
|
||||
x = 15;
|
||||
longValue = (long)((Int16)value);
|
||||
break;
|
||||
case "Int32":
|
||||
x = 31;
|
||||
longValue = (long)((Int32)value);
|
||||
break;
|
||||
case "Int64":
|
||||
x = 63;
|
||||
longValue = (long)((Int64)value);
|
||||
break;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
for (cnt = x; cnt >= 0; cnt += -1)
|
||||
{
|
||||
if (((Int64)longValue & (Int64)Math.Pow(2, cnt)) > 0)
|
||||
txt += "1";
|
||||
else
|
||||
txt += "0";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ist ein Array
|
||||
switch (value.GetType().Name)
|
||||
{
|
||||
case "Byte[]":
|
||||
x = 7;
|
||||
byte[] ByteArr = (byte[])value;
|
||||
for (cnt2 = 0; cnt2 <= ByteArr.Length - 1; cnt2++)
|
||||
{
|
||||
for (cnt = x; cnt >= 0; cnt += -1)
|
||||
if ((ByteArr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0";
|
||||
}
|
||||
break;
|
||||
case "Int16[]":
|
||||
x = 15;
|
||||
Int16[] Int16Arr = (Int16[])value;
|
||||
for (cnt2 = 0; cnt2 <= Int16Arr.Length - 1; cnt2++)
|
||||
{
|
||||
for (cnt = x; cnt >= 0; cnt += -1)
|
||||
if ((Int16Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0";
|
||||
}
|
||||
break;
|
||||
case "Int32[]":
|
||||
x = 31;
|
||||
Int32[] Int32Arr = (Int32[])value;
|
||||
for (cnt2 = 0; cnt2 <= Int32Arr.Length - 1; cnt2++)
|
||||
{
|
||||
for (cnt = x; cnt >= 0; cnt += -1)
|
||||
if ((Int32Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0";
|
||||
}
|
||||
break;
|
||||
case "Int64[]":
|
||||
x = 63;
|
||||
byte[] Int64Arr = (byte[])value;
|
||||
for (cnt2 = 0; cnt2 <= Int64Arr.Length - 1; cnt2++)
|
||||
{
|
||||
for (cnt = x; cnt >= 0; cnt += -1)
|
||||
if ((Int64Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get a bit value given a byte and the bit index.
|
||||
/// Example: DB1.DBX0.5 -> var bytes = ReadBytes(DB1.DBW0); bool bit = bytes[0].SelectBit(5);
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="bitPosition"></param>
|
||||
/// <returns></returns>
|
||||
public static bool SelectBit(this byte data, int bitPosition)
|
||||
{
|
||||
int mask = 1 << bitPosition;
|
||||
int result = data & mask;
|
||||
|
||||
return (result != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from ushort value to short value; it's used to retrieve negative values from words
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static short ConvertToShort(this ushort input)
|
||||
{
|
||||
short output;
|
||||
output = short.Parse(input.ToString("X"), NumberStyles.HexNumber);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from short value to ushort value; it's used to pass negative values to DWs
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static ushort ConvertToUshort(this short input)
|
||||
{
|
||||
ushort output;
|
||||
output = ushort.Parse(input.ToString("X"), NumberStyles.HexNumber);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from UInt32 value to Int32 value; it's used to retrieve negative values from DBDs
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static Int32 ConvertToInt(this uint input)
|
||||
{
|
||||
int output;
|
||||
output = int.Parse(input.ToString("X"), NumberStyles.HexNumber);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from Int32 value to UInt32 value; it's used to pass negative values to DBDs
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static UInt32 ConvertToUInt(this int input)
|
||||
{
|
||||
uint output;
|
||||
output = uint.Parse(input.ToString("X"), NumberStyles.HexNumber);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from float to DWord (DBD)
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static UInt32 ConvertToUInt(this float input)
|
||||
{
|
||||
uint output;
|
||||
output = S7.Net.Types.DWord.FromByteArray(S7.Net.Types.Real.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from DWord (DBD) to float
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static float ConvertToFloat(this uint input)
|
||||
{
|
||||
float output;
|
||||
output = S7.Net.Types.Real.FromByteArray(S7.Net.Types.DWord.ToByteArray(input));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
211
常用工具集/Utility/Network/S7netplus/Enums.cs
Normal file
211
常用工具集/Utility/Network/S7netplus/Enums.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
namespace S7.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of S7 cpu supported by the library
|
||||
/// </summary>
|
||||
public enum CpuType
|
||||
{
|
||||
/// <summary>
|
||||
/// S7 200 cpu type
|
||||
/// </summary>
|
||||
S7200 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Siemens Logo 0BA8
|
||||
/// </summary>
|
||||
Logo0BA8 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// S7 200 Smart
|
||||
/// </summary>
|
||||
S7200Smart = 2,
|
||||
|
||||
/// <summary>
|
||||
/// S7 300 cpu type
|
||||
/// </summary>
|
||||
S7300 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// S7 400 cpu type
|
||||
/// </summary>
|
||||
S7400 = 20,
|
||||
|
||||
/// <summary>
|
||||
/// S7 1200 cpu type
|
||||
/// </summary>
|
||||
S71200 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// S7 1500 cpu type
|
||||
/// </summary>
|
||||
S71500 = 40,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types of error code that can be set after a function is called
|
||||
/// </summary>
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// The function has been executed correctly
|
||||
/// </summary>
|
||||
NoError = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Wrong type of CPU error
|
||||
/// </summary>
|
||||
WrongCPU_Type = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Connection error
|
||||
/// </summary>
|
||||
ConnectionError = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Ip address not available
|
||||
/// </summary>
|
||||
IPAddressNotAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// Wrong format of the variable
|
||||
/// </summary>
|
||||
WrongVarFormat = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Wrong number of received bytes
|
||||
/// </summary>
|
||||
WrongNumberReceivedBytes = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Error on send data
|
||||
/// </summary>
|
||||
SendData = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Error on read data
|
||||
/// </summary>
|
||||
ReadData = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Error on write data
|
||||
/// </summary>
|
||||
WriteData = 50
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types of memory area that can be read
|
||||
/// </summary>
|
||||
public enum DataType
|
||||
{
|
||||
/// <summary>
|
||||
/// Input area memory
|
||||
/// </summary>
|
||||
Input = 129,
|
||||
|
||||
/// <summary>
|
||||
/// Output area memory
|
||||
/// </summary>
|
||||
Output = 130,
|
||||
|
||||
/// <summary>
|
||||
/// Merkers area memory (M0, M0.0, ...)
|
||||
/// </summary>
|
||||
Memory = 131,
|
||||
|
||||
/// <summary>
|
||||
/// DB area memory (DB1, DB2, ...)
|
||||
/// </summary>
|
||||
DataBlock = 132,
|
||||
|
||||
/// <summary>
|
||||
/// Timer area memory(T1, T2, ...)
|
||||
/// </summary>
|
||||
Timer = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Counter area memory (C1, C2, ...)
|
||||
/// </summary>
|
||||
Counter = 28
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types
|
||||
/// </summary>
|
||||
public enum VarType
|
||||
{
|
||||
/// <summary>
|
||||
/// S7 Bit variable type (bool)
|
||||
/// </summary>
|
||||
Bit,
|
||||
|
||||
/// <summary>
|
||||
/// S7 Byte variable type (8 bits)
|
||||
/// </summary>
|
||||
Byte,
|
||||
|
||||
/// <summary>
|
||||
/// S7 Word variable type (16 bits, 2 bytes)
|
||||
/// </summary>
|
||||
Word,
|
||||
|
||||
/// <summary>
|
||||
/// S7 DWord variable type (32 bits, 4 bytes)
|
||||
/// </summary>
|
||||
DWord,
|
||||
|
||||
/// <summary>
|
||||
/// S7 Int variable type (16 bits, 2 bytes)
|
||||
/// </summary>
|
||||
Int,
|
||||
|
||||
/// <summary>
|
||||
/// DInt variable type (32 bits, 4 bytes)
|
||||
/// </summary>
|
||||
DInt,
|
||||
|
||||
/// <summary>
|
||||
/// Real variable type (32 bits, 4 bytes)
|
||||
/// </summary>
|
||||
Real,
|
||||
|
||||
/// <summary>
|
||||
/// LReal variable type (64 bits, 8 bytes)
|
||||
/// </summary>
|
||||
LReal,
|
||||
|
||||
/// <summary>
|
||||
/// Char Array / C-String variable type (variable)
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// S7 String variable type (variable)
|
||||
/// </summary>
|
||||
S7String,
|
||||
|
||||
/// <summary>
|
||||
/// S7 WString variable type (variable)
|
||||
/// </summary>
|
||||
S7WString,
|
||||
|
||||
/// <summary>
|
||||
/// Timer variable type
|
||||
/// </summary>
|
||||
Timer,
|
||||
|
||||
/// <summary>
|
||||
/// Counter variable type
|
||||
/// </summary>
|
||||
Counter,
|
||||
|
||||
/// <summary>
|
||||
/// DateTIme variable type
|
||||
/// </summary>
|
||||
DateTime,
|
||||
|
||||
/// <summary>
|
||||
/// DateTimeLong variable type
|
||||
/// </summary>
|
||||
DateTimeLong
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Helper
|
||||
{
|
||||
internal static class MemoryStreamExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper function to write to whole content of the given byte array to a memory stream.
|
||||
///
|
||||
/// Writes all bytes in value from 0 to value.Length to the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void Write(this MemoryStream stream, byte[] value)
|
||||
{
|
||||
stream.Write(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to write the whole content of the given byte span to a memory stream.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="value"></param>
|
||||
//public static void Write(this MemoryStream stream, ReadOnlySpan<byte> value)
|
||||
//{
|
||||
// byte[] buffer = ArrayPool<byte>.Shared.Rent(value.Length);
|
||||
|
||||
// value.CopyTo(buffer);
|
||||
// stream.Write(buffer, 0, value.Length);
|
||||
|
||||
// ArrayPool<byte>.Shared.Return(buffer);
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
28
常用工具集/Utility/Network/S7netplus/Internal/TaskQueue.cs
Normal file
28
常用工具集/Utility/Network/S7netplus/Internal/TaskQueue.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net.Internal
|
||||
{
|
||||
internal class TaskQueue
|
||||
{
|
||||
private static readonly object Sentinel = new object();
|
||||
|
||||
private Task prev = Task.FromResult(Sentinel);
|
||||
|
||||
public async Task<T> Enqueue<T>(Func<Task<T>> action)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
return await action.Invoke().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tcs.SetResult(Sentinel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
常用工具集/Utility/Network/S7netplus/InvalidDataException.cs
Normal file
39
常用工具集/Utility/Network/S7netplus/InvalidDataException.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
[Serializable]
|
||||
public class InvalidDataException : Exception
|
||||
{
|
||||
public byte[] ReceivedData { get; }
|
||||
public int ErrorIndex { get; }
|
||||
public byte ExpectedValue { get; }
|
||||
|
||||
public InvalidDataException(string message, byte[] receivedData, int errorIndex, byte expectedValue)
|
||||
: base(FormatMessage(message, receivedData, errorIndex, expectedValue))
|
||||
{
|
||||
ReceivedData = receivedData;
|
||||
ErrorIndex = errorIndex;
|
||||
ExpectedValue = expectedValue;
|
||||
}
|
||||
|
||||
protected InvalidDataException(System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
ReceivedData = (byte[])info.GetValue(nameof(ReceivedData), typeof(byte[]));
|
||||
ErrorIndex = info.GetInt32(nameof(ErrorIndex));
|
||||
ExpectedValue = info.GetByte(nameof(ExpectedValue));
|
||||
}
|
||||
|
||||
private static string FormatMessage(string message, byte[] receivedData, int errorIndex, byte expectedValue)
|
||||
{
|
||||
if (errorIndex >= receivedData.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(errorIndex),
|
||||
$"{nameof(errorIndex)} {errorIndex} is outside the bounds of {nameof(receivedData)} with length {receivedData.Length}.");
|
||||
|
||||
return $"{message} Invalid data received. Expected '{expectedValue}' at index {errorIndex}, " +
|
||||
$"but received {receivedData[errorIndex]}. See the {nameof(ReceivedData)} property " +
|
||||
"for the full message received.";
|
||||
}
|
||||
}
|
||||
}
|
||||
47
常用工具集/Utility/Network/S7netplus/ORM/S7Client.cs
Normal file
47
常用工具集/Utility/Network/S7netplus/ORM/S7Client.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using S7.Net;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus.ORM
|
||||
{
|
||||
public class S7Client : IDisposable
|
||||
{
|
||||
public S7Helper Plc;
|
||||
public bool IsConnected;
|
||||
public S7Client(CpuType cpu, string ip)
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc = new S7Helper(cpu, ip);
|
||||
IsConnected = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public S7Read<T> Readable<T>() where T : class, new()
|
||||
{
|
||||
return new S7Read<T>(this);
|
||||
}
|
||||
|
||||
public S7Write<T> Writeable<T>(T entity) where T : class, new()
|
||||
{
|
||||
return new S7Write<T>(this, entity);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
Plc.Dispose();
|
||||
Plc = null;
|
||||
}
|
||||
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
19
常用工具集/Utility/Network/S7netplus/ORM/S7NodeType.cs
Normal file
19
常用工具集/Utility/Network/S7netplus/ORM/S7NodeType.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus.ORM
|
||||
{
|
||||
public enum S7NodeType
|
||||
{
|
||||
[Description("可读可写")]
|
||||
ReadAndWrite,
|
||||
[Description("只读")]
|
||||
ReadOnly,
|
||||
[Description("只写")]
|
||||
WriteOnly
|
||||
}
|
||||
}
|
||||
29
常用工具集/Utility/Network/S7netplus/ORM/S7PLCAttribute.cs
Normal file
29
常用工具集/Utility/Network/S7netplus/ORM/S7PLCAttribute.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MES.Utility.Network.S7netplus.ORM;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus.ORM
|
||||
{
|
||||
public class S7PLCAttribute : Attribute
|
||||
{
|
||||
public string Address { get; set; }
|
||||
public int StrLength { get; set; }
|
||||
public S7NodeType Type { get; set; }
|
||||
public S7PLCAttribute() { }
|
||||
|
||||
public S7PLCAttribute(string address, S7NodeType type)
|
||||
{
|
||||
this.Address = address;
|
||||
this.Type = type;
|
||||
}
|
||||
public S7PLCAttribute(string address, int strLength, S7NodeType type)
|
||||
{
|
||||
this.Address = address;
|
||||
this.StrLength = strLength;
|
||||
this.Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
187
常用工具集/Utility/Network/S7netplus/ORM/S7Read.cs
Normal file
187
常用工具集/Utility/Network/S7netplus/ORM/S7Read.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus.ORM
|
||||
{
|
||||
public class S7Read<T> where T : class, new()
|
||||
{
|
||||
private S7Client opcClient;
|
||||
private Dictionary<string, PropertyInfo> fieldDict;
|
||||
private Dictionary<string, int> stringLengthDict;
|
||||
/// <summary>
|
||||
/// 构造方法,传入参数
|
||||
/// </summary>
|
||||
/// <param name="opcClient"></param>
|
||||
public S7Read(S7Client opcClient)
|
||||
{
|
||||
this.opcClient = opcClient;
|
||||
//默认取出所有的只读或者可读可写的属性
|
||||
GetNodeType(out fieldDict, out stringLengthDict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得所有具有可读属性的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void GetNodeType(out Dictionary<string, PropertyInfo> fieldDict, out Dictionary<string, int> stringLengthDict)
|
||||
{
|
||||
fieldDict = new Dictionary<string, PropertyInfo>();
|
||||
stringLengthDict = new Dictionary<string, int>();
|
||||
|
||||
//获得对象T的属性
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
//得到所有添加注解的属性
|
||||
foreach (PropertyInfo info in properties)
|
||||
{
|
||||
//判断是否具有HttpController属性
|
||||
Object[] attr = info.GetCustomAttributes(false);
|
||||
if (attr.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//从注解数组中取第一个注解(一个属性可以包含多个注解)
|
||||
S7PLCAttribute myattr = attr[0] as S7PLCAttribute;
|
||||
if (myattr == null)
|
||||
continue;
|
||||
if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.ReadOnly)
|
||||
{
|
||||
fieldDict.Add(myattr.Address, info);
|
||||
stringLengthDict.Add(myattr.Address, myattr.StrLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行获得数据,映射成对象
|
||||
/// </summary>
|
||||
/// <param name="message">返回错误信息</param>
|
||||
/// <returns></returns>
|
||||
public bool Execute(out T t)
|
||||
{
|
||||
|
||||
if (fieldDict.Count == 0)
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!opcClient.IsConnected)
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
bool flag = false;
|
||||
T entity = (T)Activator.CreateInstance(typeof(T));
|
||||
//循环读取每一个
|
||||
foreach (KeyValuePair<string, PropertyInfo> keyValue in fieldDict)
|
||||
{
|
||||
//判断当前keyValue是什么类型
|
||||
if (keyValue.Value.PropertyType == typeof(ushort))
|
||||
{
|
||||
ushort value=0;
|
||||
flag = opcClient.Plc.ReadInt16(keyValue.Key, out value);
|
||||
if (!flag)
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
keyValue.Value.SetValue(entity, value);
|
||||
}
|
||||
else if (keyValue.Value.PropertyType == typeof(int))
|
||||
{
|
||||
ushort value =0;
|
||||
flag = opcClient.Plc.ReadInt16(keyValue.Key, out value);
|
||||
if (!flag)
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
keyValue.Value.SetValue(entity, value);
|
||||
}
|
||||
//else if (keyValue.Value.PropertyType == typeof(float))
|
||||
//{
|
||||
// float value;
|
||||
// flag = opcClient.Plc.ReadReal(keyValue.Key, out value);
|
||||
// if (!flag)
|
||||
// {
|
||||
// t = default(T);
|
||||
// return false;
|
||||
// }
|
||||
// keyValue.Value.SetValue(entity, value);
|
||||
//}
|
||||
else if (keyValue.Value.PropertyType == typeof(string))
|
||||
{
|
||||
string value;
|
||||
flag = opcClient.Plc.ReadString(keyValue.Key, stringLengthDict[keyValue.Key], out value);
|
||||
if (!flag)
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
keyValue.Value.SetValue(entity, value);
|
||||
}
|
||||
}
|
||||
t = entity;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
t = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 传入Lambda表达式,指定读取的数据
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <returns></returns>
|
||||
public S7Read<T> ReadField(Expression<Func<T, object>> columns)
|
||||
{
|
||||
Dictionary<string, PropertyInfo> fieldDict = new Dictionary<string, PropertyInfo>();
|
||||
Dictionary<string, int> stringLengthDict = new Dictionary<string, int>();
|
||||
|
||||
//取出要读的属性
|
||||
string lambda = columns.Body.ToString();
|
||||
int index1 = lambda.IndexOf('(');
|
||||
int index2 = lambda.IndexOf(')');
|
||||
string str = lambda.Substring(index1 + 1, index2 - index1 - 1);
|
||||
List<string> keyList = str.Split(',').Select(it => it.Split('=')[0].Trim()).ToList();
|
||||
fieldDict.Clear();
|
||||
//获得对象T的属性
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
//得到所有添加注解的属性
|
||||
foreach (PropertyInfo info in properties)
|
||||
{
|
||||
//判断是否具有HttpController属性
|
||||
Object[] attr = info.GetCustomAttributes(false);
|
||||
if (attr.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//从注解数组中取第一个注解(一个属性可以包含多个注解)
|
||||
S7PLCAttribute myattr = attr[0] as S7PLCAttribute;
|
||||
if (myattr == null)
|
||||
continue;
|
||||
if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.ReadOnly)
|
||||
{
|
||||
if (keyList.Contains(info.Name))
|
||||
{
|
||||
fieldDict.Add(myattr.Address, info);
|
||||
stringLengthDict.Add(myattr.Address, myattr.StrLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.fieldDict = fieldDict;
|
||||
this.stringLengthDict = stringLengthDict;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
常用工具集/Utility/Network/S7netplus/ORM/S7Write.cs
Normal file
176
常用工具集/Utility/Network/S7netplus/ORM/S7Write.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus.ORM
|
||||
{
|
||||
public class S7Write<T> where T : class, new()
|
||||
{
|
||||
private S7Client opcClient;
|
||||
private T entity;
|
||||
private Dictionary<string, PropertyInfo> dict;
|
||||
private Dictionary<string, int> strLengthDict;
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法,传入参数
|
||||
/// </summary>
|
||||
/// <param name="opcClient"></param>
|
||||
/// <param name="entity"></param>
|
||||
public S7Write(S7Client opcClient, T entity)
|
||||
{
|
||||
this.opcClient = opcClient;
|
||||
this.entity = entity;
|
||||
//默认取出所有的只写或者可读可写的属性
|
||||
GetAllKeyList(out dict, out strLengthDict);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获得所有具有可写属性的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void GetAllKeyList(out Dictionary<string, PropertyInfo> dict, out Dictionary<string, int> strLengthDict)
|
||||
{
|
||||
dict = new Dictionary<string, PropertyInfo>();
|
||||
strLengthDict = new Dictionary<string, int>();
|
||||
|
||||
|
||||
//获取entity对象的所有属性
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
//得到所有添加注解的属性
|
||||
foreach (PropertyInfo info in properties)
|
||||
{
|
||||
//判断是否具有HttpController属性
|
||||
object[] attr = info.GetCustomAttributes(false);
|
||||
if (attr.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//从注解数组中取第一个注解(一个属性可以包含多个注解)
|
||||
S7PLCAttribute myattr = attr[0] as S7PLCAttribute;
|
||||
if (myattr == null)
|
||||
continue;
|
||||
if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.WriteOnly)
|
||||
{
|
||||
dict.Add(myattr.Address, info);
|
||||
strLengthDict.Add(myattr.Address, myattr.StrLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行写入数据,将对象中的数据写入到OPC服务中
|
||||
/// </summary>
|
||||
/// <param name="message">返回写入过程中出现的错误信息</param>
|
||||
/// <returns></returns>
|
||||
public bool Execute()
|
||||
{
|
||||
if (dict.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!opcClient.IsConnected)
|
||||
return false;
|
||||
//循环写入
|
||||
try
|
||||
{
|
||||
bool flag;
|
||||
foreach (KeyValuePair<string, PropertyInfo> keyValue in dict)
|
||||
{
|
||||
//判断当前keyValue是什么类型
|
||||
if (keyValue.Value.PropertyType == typeof(ushort))
|
||||
{
|
||||
flag = opcClient.Plc.WriteInt16(keyValue.Key, (ushort)keyValue.Value.GetValue(entity));
|
||||
if (!flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (keyValue.Value.PropertyType == typeof(int))
|
||||
{
|
||||
flag = opcClient.Plc.WriteInt32(keyValue.Key, (int)keyValue.Value.GetValue(entity));
|
||||
if (!flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (keyValue.Value.PropertyType == typeof(float))
|
||||
{
|
||||
flag = opcClient.Plc.WriteSingle(keyValue.Key, (float)keyValue.Value.GetValue(entity));
|
||||
if (!flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (keyValue.Value.PropertyType == typeof(string))
|
||||
{
|
||||
flag = opcClient.Plc.WriteString(keyValue.Key, (string)keyValue.Value.GetValue(entity), strLengthDict[keyValue.Key]);
|
||||
if (!flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 传入Lambda表达式,指定写入的数据
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <returns></returns>
|
||||
public S7Write<T> WriteField(Expression<Func<T, object>> columns)
|
||||
{
|
||||
Dictionary<string, PropertyInfo> dict = new Dictionary<string, PropertyInfo>();
|
||||
Dictionary<string, int> strLengthDict = new Dictionary<string, int>();
|
||||
|
||||
|
||||
string lambda = columns.Body.ToString();
|
||||
int index1 = lambda.IndexOf('(');
|
||||
int index2 = lambda.IndexOf(')');
|
||||
string str = lambda.Substring(index1 + 1, index2 - index1 - 1);
|
||||
List<string> keyList = str.Split(',').Select(it => it.Split('=')[0].Trim()).ToList();
|
||||
|
||||
//获取entity对象的所有属性
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
//得到所有添加注解的属性
|
||||
foreach (PropertyInfo info in properties)
|
||||
{
|
||||
//判断是否具有HttpController属性
|
||||
Object[] attr = info.GetCustomAttributes(false);
|
||||
if (attr.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//从注解数组中取第一个注解(一个属性可以包含多个注解)
|
||||
S7PLCAttribute myattr = attr[0] as S7PLCAttribute;
|
||||
if (myattr == null)
|
||||
continue;
|
||||
if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.WriteOnly)
|
||||
{
|
||||
string name = info.Name;
|
||||
if (keyList.Contains(name))
|
||||
{
|
||||
dict.Add(myattr.Address, info);
|
||||
strLengthDict.Add(myattr.Address, myattr.StrLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dict = dict;
|
||||
this.strLengthDict = strLengthDict;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
329
常用工具集/Utility/Network/S7netplus/PLC.cs
Normal file
329
常用工具集/Utility/Network/S7netplus/PLC.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using S7.Net.Internal;
|
||||
using S7.Net.Protocol;
|
||||
using S7.Net.Types;
|
||||
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of S7.Net driver
|
||||
/// </summary>
|
||||
public partial class Plc : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The default port for the S7 protocol.
|
||||
/// </summary>
|
||||
public const int DefaultPort = 102;
|
||||
|
||||
/// <summary>
|
||||
/// The default timeout (in milliseconds) used for <see cref="P:ReadTimeout"/> and <see cref="P:WriteTimeout"/>.
|
||||
/// </summary>
|
||||
public const int DefaultTimeout = 10_000;
|
||||
|
||||
private readonly TaskQueue queue = new TaskQueue();
|
||||
|
||||
//TCP connection to device
|
||||
private TcpClient tcpClient;
|
||||
private NetworkStream _stream;
|
||||
|
||||
private int readTimeout = DefaultTimeout; // default no timeout
|
||||
private int writeTimeout = DefaultTimeout; // default no timeout
|
||||
|
||||
/// <summary>
|
||||
/// IP address of the PLC
|
||||
/// </summary>
|
||||
public string IP { get; }
|
||||
|
||||
/// <summary>
|
||||
/// PORT Number of the PLC, default is 102
|
||||
/// </summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The TSAP addresses used during the connection request.
|
||||
/// </summary>
|
||||
public TsapPair TsapPair { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CPU type of the PLC
|
||||
/// </summary>
|
||||
public CpuType CPU { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Rack of the PLC
|
||||
/// </summary>
|
||||
public Int16 Rack { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Slot of the CPU of the PLC
|
||||
/// </summary>
|
||||
public Int16 Slot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Max PDU size this cpu supports
|
||||
/// </summary>
|
||||
public int MaxPDUSize { get; private set; }
|
||||
|
||||
/// <summary>Gets or sets the amount of time that a read operation blocks waiting for data from PLC.</summary>
|
||||
/// <returns>A <see cref="T:System.Int32" /> that specifies the amount of time, in milliseconds, that will elapse before a read operation fails. The default value, <see cref="F:System.Threading.Timeout.Infinite" />, specifies that the read operation does not time out.</returns>
|
||||
public int ReadTimeout
|
||||
{
|
||||
get => readTimeout;
|
||||
set
|
||||
{
|
||||
readTimeout = value;
|
||||
if (tcpClient != null) tcpClient.ReceiveTimeout = readTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the amount of time that a write operation blocks waiting for data to PLC. </summary>
|
||||
/// <returns>A <see cref="T:System.Int32" /> that specifies the amount of time, in milliseconds, that will elapse before a write operation fails. The default value, <see cref="F:System.Threading.Timeout.Infinite" />, specifies that the write operation does not time out.</returns>
|
||||
public int WriteTimeout
|
||||
{
|
||||
get => writeTimeout;
|
||||
set
|
||||
{
|
||||
writeTimeout = value;
|
||||
if (tcpClient != null) tcpClient.SendTimeout = writeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a connection to the PLC has been established.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="IsConnected"/> property gets the connection state of the Client socket as
|
||||
/// of the last I/O operation. When it returns <c>false</c>, the Client socket was either
|
||||
/// never connected, or is no longer connected.
|
||||
///
|
||||
/// <para>
|
||||
/// Because the <see cref="IsConnected"/> property only reflects the state of the connection
|
||||
/// as of the most recent operation, you should attempt to send or receive a message to
|
||||
/// determine the current state. After the message send fails, this property no longer
|
||||
/// returns <c>true</c>. Note that this behavior is by design. You cannot reliably test the
|
||||
/// state of the connection because, in the time between the test and a send/receive, the
|
||||
/// connection could have been lost. Your code should assume the socket is connected, and
|
||||
/// gracefully handle failed transmissions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool IsConnected => tcpClient?.Connected ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PLC object with all the parameters needed for connections.
|
||||
/// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0.
|
||||
/// You need slot > 0 if you are connecting to external ethernet card (CP).
|
||||
/// For S7-300 and S7-400 the default is rack = 0 and slot = 2.
|
||||
/// </summary>
|
||||
/// <param name="cpu">CpuType of the PLC (select from the enum)</param>
|
||||
/// <param name="ip">Ip address of the PLC</param>
|
||||
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
|
||||
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
|
||||
/// If you use an external ethernet card, this must be set accordingly.</param>
|
||||
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
|
||||
: this(cpu, ip, DefaultPort, rack, slot)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PLC object with all the parameters needed for connections.
|
||||
/// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0.
|
||||
/// You need slot > 0 if you are connecting to external ethernet card (CP).
|
||||
/// For S7-300 and S7-400 the default is rack = 0 and slot = 2.
|
||||
/// </summary>
|
||||
/// <param name="cpu">CpuType of the PLC (select from the enum)</param>
|
||||
/// <param name="ip">Ip address of the PLC</param>
|
||||
/// <param name="port">Port number used for the connection, default 102.</param>
|
||||
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
|
||||
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
|
||||
/// If you use an external ethernet card, this must be set accordingly.</param>
|
||||
public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
|
||||
: this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot))
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(CpuType), cpu))
|
||||
throw new ArgumentException(
|
||||
$"The value of argument '{nameof(cpu)}' ({cpu}) is invalid for Enum type '{typeof(CpuType).Name}'.",
|
||||
nameof(cpu));
|
||||
|
||||
CPU = cpu;
|
||||
Rack = rack;
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PLC object with all the parameters needed for connections.
|
||||
/// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0.
|
||||
/// You need slot > 0 if you are connecting to external ethernet card (CP).
|
||||
/// For S7-300 and S7-400 the default is rack = 0 and slot = 2.
|
||||
/// </summary>
|
||||
/// <param name="ip">Ip address of the PLC</param>
|
||||
/// <param name="tsapPair">The TSAP addresses used for the connection request.</param>
|
||||
public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PLC object with all the parameters needed for connections. Use this constructor
|
||||
/// if you want to manually override the TSAP addresses used during the connection request.
|
||||
/// </summary>
|
||||
/// <param name="ip">Ip address of the PLC</param>
|
||||
/// <param name="port">Port number used for the connection, default 102.</param>
|
||||
/// <param name="tsapPair">The TSAP addresses used for the connection request.</param>
|
||||
public Plc(string ip, int port, TsapPair tsapPair)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ip))
|
||||
throw new ArgumentException("IP address must valid.", nameof(ip));
|
||||
|
||||
IP = ip;
|
||||
Port = port;
|
||||
MaxPDUSize = 240;
|
||||
TsapPair = tsapPair;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close connection to PLC
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (tcpClient != null)
|
||||
{
|
||||
if (tcpClient.Connected) tcpClient.Close();
|
||||
tcpClient = null; // Can not reuse TcpClient once connection gets closed.
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertPduSizeForRead(ICollection<DataItem> dataItems)
|
||||
{
|
||||
// send request limit: 19 bytes of header data, 12 bytes of parameter data for each dataItem
|
||||
var requiredRequestSize = 19 + dataItems.Count * 12;
|
||||
if (requiredRequestSize > MaxPDUSize) throw new Exception($"Too many vars requested for read. Request size ({requiredRequestSize}) is larger than protocol limit ({MaxPDUSize}).");
|
||||
|
||||
// response limit: 14 bytes of header data, 4 bytes of result data for each dataItem and the actual data
|
||||
var requiredResponseSize = GetDataLength(dataItems) + dataItems.Count * 4 + 14;
|
||||
if (requiredResponseSize > MaxPDUSize) throw new Exception($"Too much data requested for read. Response size ({requiredResponseSize}) is larger than protocol limit ({MaxPDUSize}).");
|
||||
}
|
||||
|
||||
private void AssertPduSizeForWrite(ICollection<DataItem> dataItems)
|
||||
{
|
||||
// 12 bytes of header data, 18 bytes of parameter data for each dataItem
|
||||
if (dataItems.Count * 18 + 12 > MaxPDUSize) throw new Exception("Too many vars supplied for write");
|
||||
|
||||
// 12 bytes of header data, 16 bytes of data for each dataItem and the actual data
|
||||
if (GetDataLength(dataItems) + dataItems.Count * 16 + 12 > MaxPDUSize)
|
||||
throw new Exception("Too much data supplied for write");
|
||||
}
|
||||
|
||||
private void ConfigureConnection()
|
||||
{
|
||||
if (tcpClient == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tcpClient.ReceiveTimeout = ReadTimeout;
|
||||
tcpClient.SendTimeout = WriteTimeout;
|
||||
}
|
||||
|
||||
private int GetDataLength(IEnumerable<DataItem> dataItems)
|
||||
{
|
||||
// Odd length variables are 0-padded
|
||||
return dataItems.Select(di => VarTypeToByteLength(di.VarType, di.Count))
|
||||
.Sum(len => (len & 1) == 1 ? len + 1 : len);
|
||||
}
|
||||
|
||||
private static void AssertReadResponse(byte[] s7Data, int dataLength)
|
||||
{
|
||||
var expectedLength = dataLength + 18;
|
||||
|
||||
PlcException NotEnoughBytes() =>
|
||||
new PlcException(ErrorCode.WrongNumberReceivedBytes,
|
||||
$"Received {s7Data.Length} bytes: '{BitConverter.ToString(s7Data)}', expected {expectedLength} bytes.")
|
||||
;
|
||||
|
||||
if (s7Data == null)
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes, "No s7Data received.");
|
||||
|
||||
if (s7Data.Length < 15) throw NotEnoughBytes();
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7Data[14]);
|
||||
|
||||
if (s7Data.Length < expectedLength) throw NotEnoughBytes();
|
||||
}
|
||||
|
||||
internal static void ValidateResponseCode(ReadWriteErrorCode statusCode)
|
||||
{
|
||||
switch (statusCode)
|
||||
{
|
||||
case ReadWriteErrorCode.ObjectDoesNotExist:
|
||||
throw new Exception("Received error from PLC: Object does not exist.");
|
||||
case ReadWriteErrorCode.DataTypeInconsistent:
|
||||
throw new Exception("Received error from PLC: Data type inconsistent.");
|
||||
case ReadWriteErrorCode.DataTypeNotSupported:
|
||||
throw new Exception("Received error from PLC: Data type not supported.");
|
||||
case ReadWriteErrorCode.AccessingObjectNotAllowed:
|
||||
throw new Exception("Received error from PLC: Accessing object not allowed.");
|
||||
case ReadWriteErrorCode.AddressOutOfRange:
|
||||
throw new Exception("Received error from PLC: Address out of range.");
|
||||
case ReadWriteErrorCode.HardwareFault:
|
||||
throw new Exception("Received error from PLC: Hardware fault.");
|
||||
case ReadWriteErrorCode.Success:
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Invalid response from PLC: statusCode={(byte)statusCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
private Stream GetStreamIfAvailable()
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
|
||||
}
|
||||
|
||||
return _stream;
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Dispose Plc Object
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
|
||||
// TODO: set large fields to null.
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
|
||||
// ~Plc() {
|
||||
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
// Dispose(false);
|
||||
// }
|
||||
|
||||
// This code added to correctly implement the disposable pattern.
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
Dispose(true);
|
||||
// TODO: uncomment the following line if the finalizer is overridden above.
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
207
常用工具集/Utility/Network/S7netplus/PLCAddress.cs
Normal file
207
常用工具集/Utility/Network/S7netplus/PLCAddress.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
namespace S7.Net
|
||||
{
|
||||
internal class PLCAddress
|
||||
{
|
||||
private DataType dataType;
|
||||
private int dbNumber;
|
||||
private int startByte;
|
||||
private int bitNumber;
|
||||
private VarType varType;
|
||||
|
||||
public DataType DataType
|
||||
{
|
||||
get => dataType;
|
||||
set => dataType = value;
|
||||
}
|
||||
|
||||
public int DbNumber
|
||||
{
|
||||
get => dbNumber;
|
||||
set => dbNumber = value;
|
||||
}
|
||||
|
||||
public int StartByte
|
||||
{
|
||||
get => startByte;
|
||||
set => startByte = value;
|
||||
}
|
||||
|
||||
public int BitNumber
|
||||
{
|
||||
get => bitNumber;
|
||||
set => bitNumber = value;
|
||||
}
|
||||
|
||||
public VarType VarType
|
||||
{
|
||||
get => varType;
|
||||
set => varType = value;
|
||||
}
|
||||
|
||||
public PLCAddress(string address)
|
||||
{
|
||||
Parse(address, out dataType, out dbNumber, out varType, out startByte, out bitNumber);
|
||||
}
|
||||
|
||||
public static void Parse(string input, out DataType dataType, out int dbNumber, out VarType varType, out int address, out int bitNumber)
|
||||
{
|
||||
bitNumber = -1;
|
||||
dbNumber = 0;
|
||||
|
||||
switch (input.Substring(0, 2))
|
||||
{
|
||||
case "DB":
|
||||
string[] strings = input.Split(new char[] { '.' });
|
||||
if (strings.Length < 2)
|
||||
throw new InvalidAddressException("To few periods for DB address");
|
||||
|
||||
dataType = DataType.DataBlock;
|
||||
dbNumber = int.Parse(strings[0].Substring(2));
|
||||
address = int.Parse(strings[1].Substring(3));
|
||||
|
||||
string dbType = strings[1].Substring(0, 3);
|
||||
switch (dbType)
|
||||
{
|
||||
case "DBB":
|
||||
varType = VarType.Byte;
|
||||
return;
|
||||
case "DBW":
|
||||
varType = VarType.Word;
|
||||
return;
|
||||
case "DBD":
|
||||
varType = VarType.DWord;
|
||||
return;
|
||||
case "DBX":
|
||||
bitNumber = int.Parse(strings[2]);
|
||||
if (bitNumber > 7)
|
||||
throw new InvalidAddressException("Bit can only be 0-7");
|
||||
varType = VarType.Bit;
|
||||
return;
|
||||
default:
|
||||
throw new InvalidAddressException();
|
||||
}
|
||||
case "IB":
|
||||
case "EB":
|
||||
// Input byte
|
||||
dataType = DataType.Input;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Byte;
|
||||
return;
|
||||
case "IW":
|
||||
case "EW":
|
||||
// Input word
|
||||
dataType = DataType.Input;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Word;
|
||||
return;
|
||||
case "ID":
|
||||
case "ED":
|
||||
// Input double-word
|
||||
dataType = DataType.Input;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.DWord;
|
||||
return;
|
||||
case "QB":
|
||||
case "AB":
|
||||
case "OB":
|
||||
// Output byte
|
||||
dataType = DataType.Output;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Byte;
|
||||
return;
|
||||
case "QW":
|
||||
case "AW":
|
||||
case "OW":
|
||||
// Output word
|
||||
dataType = DataType.Output;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Word;
|
||||
return;
|
||||
case "QD":
|
||||
case "AD":
|
||||
case "OD":
|
||||
// Output double-word
|
||||
dataType = DataType.Output;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.DWord;
|
||||
return;
|
||||
case "MB":
|
||||
// Memory byte
|
||||
dataType = DataType.Memory;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Byte;
|
||||
return;
|
||||
case "MW":
|
||||
// Memory word
|
||||
dataType = DataType.Memory;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.Word;
|
||||
return;
|
||||
case "MD":
|
||||
// Memory double-word
|
||||
dataType = DataType.Memory;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(2));
|
||||
varType = VarType.DWord;
|
||||
return;
|
||||
default:
|
||||
switch (input.Substring(0, 1))
|
||||
{
|
||||
case "E":
|
||||
case "I":
|
||||
// Input
|
||||
dataType = DataType.Input;
|
||||
varType = VarType.Bit;
|
||||
break;
|
||||
case "Q":
|
||||
case "A":
|
||||
case "O":
|
||||
// Output
|
||||
dataType = DataType.Output;
|
||||
varType = VarType.Bit;
|
||||
break;
|
||||
case "M":
|
||||
// Memory
|
||||
dataType = DataType.Memory;
|
||||
varType = VarType.Bit;
|
||||
break;
|
||||
case "T":
|
||||
// Timer
|
||||
dataType = DataType.Timer;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(1));
|
||||
varType = VarType.Timer;
|
||||
return;
|
||||
case "Z":
|
||||
case "C":
|
||||
// Counter
|
||||
dataType = DataType.Counter;
|
||||
dbNumber = 0;
|
||||
address = int.Parse(input.Substring(1));
|
||||
varType = VarType.Counter;
|
||||
return;
|
||||
default:
|
||||
throw new InvalidAddressException(string.Format("{0} is not a valid address", input.Substring(0, 1)));
|
||||
}
|
||||
|
||||
string txt2 = input.Substring(1);
|
||||
if (txt2.IndexOf(".") == -1)
|
||||
throw new InvalidAddressException("To few periods for DB address");
|
||||
|
||||
address = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
|
||||
bitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
|
||||
if (bitNumber > 7)
|
||||
throw new InvalidAddressException("Bit can only be 0-7");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
常用工具集/Utility/Network/S7netplus/PLCExceptions.cs
Normal file
101
常用工具集/Utility/Network/S7netplus/PLCExceptions.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
public class WrongNumberOfBytesException : Exception
|
||||
{
|
||||
public WrongNumberOfBytesException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public WrongNumberOfBytesException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public WrongNumberOfBytesException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected WrongNumberOfBytesException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidAddressException : Exception
|
||||
{
|
||||
public InvalidAddressException() : base ()
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidAddressException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidAddressException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected InvalidAddressException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidVariableTypeException : Exception
|
||||
{
|
||||
public InvalidVariableTypeException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidVariableTypeException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidVariableTypeException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected InvalidVariableTypeException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TPKTInvalidException : Exception
|
||||
{
|
||||
public TPKTInvalidException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public TPKTInvalidException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TPKTInvalidException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected TPKTInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TPDUInvalidException : Exception
|
||||
{
|
||||
public TPDUInvalidException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public TPDUInvalidException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TPDUInvalidException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected TPDUInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
265
常用工具集/Utility/Network/S7netplus/PLCHelpers.cs
Normal file
265
常用工具集/Utility/Network/S7netplus/PLCHelpers.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
using S7.Net.Helper;
|
||||
using S7.Net.Protocol.S7;
|
||||
using S7.Net.Types;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DateTime = S7.Net.Types.DateTime;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
public partial class Plc
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the header to read bytes from the PLC
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
|
||||
{
|
||||
//header size = 19 bytes
|
||||
stream.Write(new byte[] { 0x03, 0x00 });
|
||||
//complete package size
|
||||
stream.Write(Int.ToByteArray((short)(19 + (12 * amount))));
|
||||
stream.Write(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
|
||||
//data part size
|
||||
stream.Write(Word.ToByteArray((ushort)(2 + (amount * 12))));
|
||||
stream.Write(new byte[] { 0x00, 0x00, 0x04 });
|
||||
//amount of requests
|
||||
stream.WriteByte((byte)amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
|
||||
/// the address of the memory, the address of the byte and the bytes count.
|
||||
/// </summary>
|
||||
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
|
||||
/// <param name="db">Address of the memory to be read</param>
|
||||
/// <param name="startByteAdr">Start address of the byte</param>
|
||||
/// <param name="count">Number of bytes to be read</param>
|
||||
/// <returns></returns>
|
||||
private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1)
|
||||
{
|
||||
//single data req = 12
|
||||
stream.Write(new byte[] { 0x12, 0x0a, 0x10 });
|
||||
switch (dataType)
|
||||
{
|
||||
case DataType.Timer:
|
||||
case DataType.Counter:
|
||||
stream.WriteByte((byte)dataType);
|
||||
break;
|
||||
default:
|
||||
stream.WriteByte(0x02);
|
||||
break;
|
||||
}
|
||||
|
||||
stream.Write(Word.ToByteArray((ushort)(count)));
|
||||
stream.Write(Word.ToByteArray((ushort)(db)));
|
||||
stream.WriteByte((byte)dataType);
|
||||
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
|
||||
stream.WriteByte((byte)overflow);
|
||||
switch (dataType)
|
||||
{
|
||||
case DataType.Timer:
|
||||
case DataType.Counter:
|
||||
stream.Write(Word.ToByteArray((ushort)(startByteAdr)));
|
||||
break;
|
||||
default:
|
||||
stream.Write(Word.ToByteArray((ushort)((startByteAdr) * 8)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a S7 variable type (Bool, Word, DWord, etc.), it converts the bytes in the appropriate C# format.
|
||||
/// </summary>
|
||||
/// <param name="varType"></param>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="varCount"></param>
|
||||
/// <param name="bitAdr"></param>
|
||||
/// <returns></returns>
|
||||
private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
return null;
|
||||
|
||||
switch (varType)
|
||||
{
|
||||
case VarType.Byte:
|
||||
if (varCount == 1)
|
||||
return bytes[0];
|
||||
else
|
||||
return bytes;
|
||||
case VarType.Word:
|
||||
if (varCount == 1)
|
||||
return Word.FromByteArray(bytes);
|
||||
else
|
||||
return Word.ToArray(bytes);
|
||||
case VarType.Int:
|
||||
if (varCount == 1)
|
||||
return Int.FromByteArray(bytes);
|
||||
else
|
||||
return Int.ToArray(bytes);
|
||||
case VarType.DWord:
|
||||
if (varCount == 1)
|
||||
return DWord.FromByteArray(bytes);
|
||||
else
|
||||
return DWord.ToArray(bytes);
|
||||
case VarType.DInt:
|
||||
if (varCount == 1)
|
||||
return DInt.FromByteArray(bytes);
|
||||
else
|
||||
return DInt.ToArray(bytes);
|
||||
case VarType.Real:
|
||||
if (varCount == 1)
|
||||
return Types.Real.FromByteArray(bytes);
|
||||
else
|
||||
return Types.Real.ToArray(bytes);
|
||||
case VarType.LReal:
|
||||
if (varCount == 1)
|
||||
return Types.LReal.FromByteArray(bytes);
|
||||
else
|
||||
return Types.LReal.ToArray(bytes);
|
||||
|
||||
case VarType.String:
|
||||
return Types.String.FromByteArray(bytes);
|
||||
case VarType.S7String:
|
||||
return S7String.FromByteArray(bytes);
|
||||
case VarType.S7WString:
|
||||
return S7WString.FromByteArray(bytes);
|
||||
|
||||
case VarType.Timer:
|
||||
if (varCount == 1)
|
||||
return Timer.FromByteArray(bytes);
|
||||
else
|
||||
return Timer.ToArray(bytes);
|
||||
case VarType.Counter:
|
||||
if (varCount == 1)
|
||||
return Counter.FromByteArray(bytes);
|
||||
else
|
||||
return Counter.ToArray(bytes);
|
||||
case VarType.Bit:
|
||||
if (varCount == 1)
|
||||
{
|
||||
if (bitAdr > 7)
|
||||
return null;
|
||||
else
|
||||
return Bit.FromByte(bytes[0], bitAdr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Bit.ToBitArray(bytes, varCount);
|
||||
}
|
||||
case VarType.DateTime:
|
||||
if (varCount == 1)
|
||||
{
|
||||
return DateTime.FromByteArray(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.ToArray(bytes);
|
||||
}
|
||||
case VarType.DateTimeLong:
|
||||
if (varCount == 1)
|
||||
{
|
||||
return DateTimeLong.FromByteArray(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTimeLong.ToArray(bytes);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a S7 <see cref="VarType"/> (Bool, Word, DWord, etc.), it returns how many bytes to read.
|
||||
/// </summary>
|
||||
/// <param name="varType"></param>
|
||||
/// <param name="varCount"></param>
|
||||
/// <returns>Byte lenght of variable</returns>
|
||||
internal static int VarTypeToByteLength(VarType varType, int varCount = 1)
|
||||
{
|
||||
switch (varType)
|
||||
{
|
||||
case VarType.Bit:
|
||||
return (varCount + 7) / 8;
|
||||
case VarType.Byte:
|
||||
return (varCount < 1) ? 1 : varCount;
|
||||
case VarType.String:
|
||||
return varCount;
|
||||
case VarType.S7String:
|
||||
return ((varCount + 2) & 1) == 1 ? (varCount + 3) : (varCount + 2);
|
||||
case VarType.S7WString:
|
||||
return (varCount * 2) + 4;
|
||||
case VarType.Word:
|
||||
case VarType.Timer:
|
||||
case VarType.Int:
|
||||
case VarType.Counter:
|
||||
return varCount * 2;
|
||||
case VarType.DWord:
|
||||
case VarType.DInt:
|
||||
case VarType.Real:
|
||||
return varCount * 4;
|
||||
case VarType.LReal:
|
||||
case VarType.DateTime:
|
||||
return varCount * 8;
|
||||
case VarType.DateTimeLong:
|
||||
return varCount * 12;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetS7ConnectionSetup()
|
||||
{
|
||||
return new byte[] { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3,
|
||||
3, 192 // Use 960 PDU size
|
||||
};
|
||||
}
|
||||
|
||||
private void ParseDataIntoDataItems(byte[] s7data, List<DataItem> dataItems)
|
||||
{
|
||||
int offset = 14;
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
// check for Return Code = Success
|
||||
if (s7data[offset] != 0xff)
|
||||
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
|
||||
|
||||
// to Data bytes
|
||||
offset += 4;
|
||||
|
||||
int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count);
|
||||
dataItem.Value = ParseBytes(
|
||||
dataItem.VarType,
|
||||
s7data.Skip(offset).Take(byteCnt).ToArray(),
|
||||
dataItem.Count,
|
||||
dataItem.BitAdr
|
||||
);
|
||||
|
||||
// next Item
|
||||
offset += byteCnt;
|
||||
|
||||
// Always align to even offset
|
||||
if (offset % 2 != 0)
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BuildReadRequestPackage(IList<DataItemAddress> dataItems)
|
||||
{
|
||||
int packageSize = 19 + (dataItems.Count * 12);
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
|
||||
BuildHeaderPackage(package, dataItems.Count);
|
||||
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAddress, dataItem.ByteLength);
|
||||
}
|
||||
|
||||
return package.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
582
常用工具集/Utility/Network/S7netplus/PlcAsynchronous.cs
Normal file
582
常用工具集/Utility/Network/S7netplus/PlcAsynchronous.cs
Normal file
@@ -0,0 +1,582 @@
|
||||
using S7.Net.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using S7.Net.Protocol;
|
||||
using System.Threading;
|
||||
using S7.Net.Protocol.S7;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of S7.Net driver
|
||||
/// </summary>
|
||||
public partial class Plc
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that the cancellation will not affect opening the socket in any way and only affects data transfers for configuring the connection after the socket connection is successfully established.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous open operation.</returns>
|
||||
public async Task OpenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stream = await ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await queue.Enqueue(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
_stream = stream;
|
||||
|
||||
return default(object);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
stream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<NetworkStream> ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
ConfigureConnection();
|
||||
|
||||
#if NETFRAMEWORK
|
||||
IAsyncResult asyncResult = tcpClient.BeginConnect(IP, Port, null, null);
|
||||
if (!asyncResult.AsyncWaitHandle.WaitOne(1000))
|
||||
{
|
||||
throw new Exception("<22><><EFBFBD>ӳ<EFBFBD>ʱ");
|
||||
}
|
||||
tcpClient.EndConnect(asyncResult);
|
||||
#else
|
||||
await tcpClient.ConnectAsync(IP, Port, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
#endif
|
||||
return tcpClient.GetStream();
|
||||
}
|
||||
|
||||
private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task RequestConnection(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestData = ConnectionRequest.GetCOTPConnectionRequest(TsapPair);
|
||||
var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.PDUType != COTP.PduType.ConnectionConfirmed)
|
||||
{
|
||||
throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetupConnection(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var setupData = GetS7ConnectionSetup();
|
||||
|
||||
var s7data = await NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (s7data.Length < 2)
|
||||
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
|
||||
|
||||
//Check for S7 Ack Data
|
||||
if (s7data[1] != 0x03)
|
||||
throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03);
|
||||
|
||||
if (s7data.Length < 20)
|
||||
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
|
||||
|
||||
// TODO: check if this should not rather be UInt16.
|
||||
MaxPDUSize = s7data[18] * 256 + s7data[19];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns the bytes in an array</returns>
|
||||
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var resultBytes = new byte[count];
|
||||
int index = 0;
|
||||
while (count > 0)
|
||||
{
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = Math.Min(count, MaxPDUSize - 18);
|
||||
await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken).ConfigureAwait(false);
|
||||
count -= maxToRead;
|
||||
index += maxToRead;
|
||||
}
|
||||
return resultBytes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read and decode a certain number of bytes of the "VarType" provided.
|
||||
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="varType">Type of the variable/s that you are reading</param>
|
||||
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
|
||||
/// <param name="varCount"></param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
public async Task<object> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int cntBytes = VarTypeToByteLength(varType, varCount);
|
||||
byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes, cancellationToken).ConfigureAwait(false);
|
||||
return ParseBytes(varType, bytes, varCount, bitAdr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
|
||||
public async Task<object> ReadAsync(string variable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and return an object that can be casted to the struct.
|
||||
/// </summary>
|
||||
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns a struct that must be cast.</returns>
|
||||
public async Task<object> ReadStructAsync(Type structType, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int numBytes = Types.Struct.GetStructSize(structType);
|
||||
// now read the package
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// and decode it
|
||||
return Types.Struct.FromBytes(structType, resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and returns the struct or null if nothing was read.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The struct type</typeparam>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns a nulable struct. If nothing was read null will be returned.</returns>
|
||||
public async Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : struct
|
||||
{
|
||||
return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken).ConfigureAwait(false) as T?;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">Instance of the class that will store the values</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>The number of read bytes</returns>
|
||||
public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int numBytes = (int)Class.GetClassSize(sourceClass);
|
||||
if (numBytes <= 0)
|
||||
{
|
||||
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
|
||||
}
|
||||
|
||||
// now read the package
|
||||
var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false);
|
||||
// and decode it
|
||||
Class.FromBytes(sourceClass, resultBytes);
|
||||
|
||||
return new Tuple<int, object>(resultBytes.Length, sourceClass);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic
|
||||
/// type, the class needs a default constructor.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class that will be instantiated. Requires a default constructor</typeparam>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public async Task<T> ReadClassAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class that will be instantiated</typeparam>
|
||||
/// <param name="classFactory">Function to instantiate the class</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public async Task<T> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
var instance = classFactory();
|
||||
var res = await ReadClassAsync(instance, db, startByteAdr, cancellationToken).ConfigureAwait(false);
|
||||
int readBytes = res.Item1;
|
||||
if (readBytes <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (T)res.Item2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads multiple vars in a single request.
|
||||
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
|
||||
/// Values are stored in the property "Value" of the dataItem and are already converted.
|
||||
/// If you don't want the conversion, just create a dataItem of bytes.
|
||||
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
|
||||
/// </summary>
|
||||
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
public async Task<List<DataItem>> ReadMultipleVarsAsync(List<DataItem> dataItems, CancellationToken cancellationToken = default)
|
||||
{
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
AssertPduSizeForRead(dataItems);
|
||||
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList());
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
|
||||
ParseDataIntoDataItems(s7data, dataItems);
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, socketException);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, exc);
|
||||
}
|
||||
return dataItems;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int localIndex = 0;
|
||||
int count = value.Length;
|
||||
while (count > 0)
|
||||
{
|
||||
var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35);
|
||||
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken).ConfigureAwait(false);
|
||||
count -= maxToWrite;
|
||||
localIndex += maxToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit from a DB with the specified index.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (bitAdr < 0 || bitAdr > 7)
|
||||
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
|
||||
|
||||
await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit from a DB with the specified index.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (value < 0 || value > 1)
|
||||
throw new ArgumentException("Value must be 0 or 1", nameof(value));
|
||||
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes in input an object and tries to parse it to an array of values. This can be used to write many data, all of the same type.
|
||||
/// You must specify the memory area type, memory are address, byte start address and bytes count.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (bitAdr != -1)
|
||||
{
|
||||
//Must be writing a bit value as bitAdr is specified
|
||||
if (value is bool boolean)
|
||||
{
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, boolean, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (value is int intValue)
|
||||
{
|
||||
if (intValue < 0 || intValue > 7)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
string.Format(
|
||||
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
|
||||
bitAdr), nameof(bitAdr));
|
||||
|
||||
await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
|
||||
}
|
||||
else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
|
||||
/// </summary>
|
||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
||||
/// <param name="value">Value to be written to the PLC</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a C# struct to a DB in the PLC
|
||||
/// </summary>
|
||||
/// <param name="structValue">The struct to be written</param>
|
||||
/// <param name="db">Db address</param>
|
||||
/// <param name="startByteAdr">Start bytes on the PLC</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bytes = Struct.ToBytes(structValue).ToList();
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a C# class to a DB in the PLC
|
||||
/// </summary>
|
||||
/// <param name="classValue">The class to be written</param>
|
||||
/// <param name="db">Db address</param>
|
||||
/// <param name="startByteAdr">Start bytes on the PLC</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
byte[] bytes = new byte[(int)Class.GetClassSize(classValue)];
|
||||
Types.Class.ToBytes(classValue, bytes);
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataToSend = BuildReadRequestPackage(new[] { new DataItemAddress(dataType, db, startByteAdr, count) });
|
||||
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
|
||||
AssertReadResponse(s7data, count);
|
||||
|
||||
Array.Copy(s7data, 18, buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
|
||||
/// or when the PLC reports errors for item(s) written.
|
||||
/// </summary>
|
||||
/// <param name="dataItems">The DataItem(s) to write to the PLC.</param>
|
||||
/// <returns>Task that completes when response from PLC is parsed.</returns>
|
||||
public async Task WriteAsync(params DataItem[] dataItems)
|
||||
{
|
||||
AssertPduSizeForWrite(dataItems);
|
||||
|
||||
var message = new ByteArray();
|
||||
var length = S7WriteMultiple.CreateRequest(message, dataItems);
|
||||
|
||||
var response = await RequestTsduAsync(message.Array, 0, length).ConfigureAwait(false);
|
||||
|
||||
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes up to 200 bytes to the PLC. You must specify the memory area type, memory are address, byte start address and bytes count.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<byte[]> RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) =>
|
||||
RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken);
|
||||
|
||||
private Task<byte[]> RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stream = GetStreamIfAvailable();
|
||||
|
||||
return queue.Enqueue(() =>
|
||||
NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken));
|
||||
}
|
||||
|
||||
private async Task<COTP.TPDU> NoLockRequestTpduAsync(Stream stream, byte[] requestData,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
using (var closeOnCancellation = cancellationToken.Register(Close))
|
||||
{
|
||||
await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false);
|
||||
return await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
if (exc is TPDUInvalidException || exc is TPKTInvalidException)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> NoLockRequestTsduAsync(Stream stream, byte[] requestData, int offset, int length,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
using (var closeOnCancellation = cancellationToken.Register(Close))
|
||||
{
|
||||
await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false);
|
||||
return await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
if (exc is TPDUInvalidException || exc is TPKTInvalidException)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
常用工具集/Utility/Network/S7netplus/PlcException.cs
Normal file
34
常用工具集/Utility/Network/S7netplus/PlcException.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
[Serializable]
|
||||
public class PlcException : Exception
|
||||
{
|
||||
public ErrorCode ErrorCode { get; }
|
||||
|
||||
public PlcException(ErrorCode errorCode) : this(errorCode, $"PLC communication failed with error '{errorCode}'.")
|
||||
{
|
||||
}
|
||||
|
||||
public PlcException(ErrorCode errorCode, Exception innerException) : this(errorCode, innerException.Message,
|
||||
innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public PlcException(ErrorCode errorCode, string message) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
public PlcException(ErrorCode errorCode, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
protected PlcException(System.Runtime.Serialization.SerializationInfo info,System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
ErrorCode = (ErrorCode) info.GetInt32(nameof(ErrorCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
585
常用工具集/Utility/Network/S7netplus/PlcSynchronous.cs
Normal file
585
常用工具集/Utility/Network/S7netplus/PlcSynchronous.cs
Normal file
@@ -0,0 +1,585 @@
|
||||
using S7.Net.Types;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using S7.Net.Protocol;
|
||||
using S7.Net.Helper;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
//Implement synchronous methods here
|
||||
namespace S7.Net
|
||||
{
|
||||
public partial class Plc
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
|
||||
/// </summary>
|
||||
public void Open(int timeout = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (timeout == 0)
|
||||
{
|
||||
OpenAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(timeout);
|
||||
OpenAsync(cts.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ConnectionError,
|
||||
$"Couldn't establish the connection to {IP}.\nMessage: {exc.Message}", exc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
|
||||
/// <returns>Returns the bytes in an array</returns>
|
||||
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
|
||||
{
|
||||
//var result = new byte[count];
|
||||
|
||||
//ReadBytes(result, dataType, db, startByteAdr);
|
||||
|
||||
//return result;
|
||||
|
||||
|
||||
var result = new byte[count];
|
||||
int index = 0;
|
||||
while (count > 0)
|
||||
{
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = Math.Min(count, MaxPDUSize - 18);
|
||||
ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, result, index, maxToRead);
|
||||
count -= maxToRead;
|
||||
index += maxToRead;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
int packageSize = 19 + 12; // 19 header + 12 for 1 request
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
BuildHeaderPackage(package);
|
||||
// package.Add(0x02); // datenart
|
||||
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
|
||||
|
||||
var dataToSend = package.ToArray();
|
||||
var s7data = RequestTsdu(dataToSend);
|
||||
AssertReadResponse(s7data, count);
|
||||
|
||||
Array.Copy(s7data, 18, buffer, offset, count);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read and decode a certain number of bytes of the "VarType" provided.
|
||||
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="varType">Type of the variable/s that you are reading</param>
|
||||
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
|
||||
/// <param name="varCount"></param>
|
||||
public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
|
||||
{
|
||||
int cntBytes = VarTypeToByteLength(varType, varCount);
|
||||
byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes);
|
||||
|
||||
return ParseBytes(varType, bytes, varCount, bitAdr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
||||
/// <returns>Returns an object that contains the value. This object must be cast accordingly. If no data has been read, null will be returned</returns>
|
||||
public object Read(string variable)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and return an object that can be casted to the struct.
|
||||
/// </summary>
|
||||
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>Returns a struct that must be cast. If no data has been read, null will be returned</returns>
|
||||
public object ReadStruct(Type structType, int db, int startByteAdr = 0)
|
||||
{
|
||||
int numBytes = Struct.GetStructSize(structType);
|
||||
// now read the package
|
||||
var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
|
||||
|
||||
// and decode it
|
||||
return Struct.FromBytes(structType, resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and returns the struct or null if nothing was read.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The struct type</typeparam>
|
||||
/// <param name="db">Address of the DB.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>Returns a nullable struct. If nothing was read null will be returned.</returns>
|
||||
public T? ReadStruct<T>(int db, int startByteAdr = 0) where T : struct
|
||||
{
|
||||
return ReadStruct(typeof(T), db, startByteAdr) as T?;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">Instance of the class that will store the values</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>The number of read bytes</returns>
|
||||
public int ReadClass(object sourceClass, int db, int startByteAdr = 0)
|
||||
{
|
||||
int numBytes = (int)Class.GetClassSize(sourceClass);
|
||||
if (numBytes <= 0)
|
||||
{
|
||||
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
|
||||
}
|
||||
|
||||
// now read the package
|
||||
var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
|
||||
// and decode it
|
||||
Class.FromBytes(sourceClass, resultBytes);
|
||||
return resultBytes.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic
|
||||
/// type, the class needs a default constructor.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class that will be instantiated. Requires a default constructor</typeparam>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public T ReadClass<T>(int db, int startByteAdr = 0) where T : class
|
||||
{
|
||||
return ReadClass(() => Activator.CreateInstance<T>(), db, startByteAdr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
|
||||
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class that will be instantiated</typeparam>
|
||||
/// <param name="classFactory">Function to instantiate the class</param>
|
||||
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
|
||||
public T ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
|
||||
{
|
||||
var instance = classFactory();
|
||||
int readBytes = ReadClass(instance, db, startByteAdr);
|
||||
if (readBytes <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
|
||||
{
|
||||
//WriteBytes(dataType, db, startByteAdr, value.AsSpan());
|
||||
int localIndex = 0;
|
||||
int count = value.Length;
|
||||
while (count > 0)
|
||||
{
|
||||
//TODO: Figure out how to use MaxPDUSize here
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
|
||||
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
|
||||
count -= maxToWrite;
|
||||
localIndex += maxToWrite;
|
||||
}
|
||||
}
|
||||
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
|
||||
var s7data = RequestTsdu(dataToSend);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
//public void WriteBytes(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
//{
|
||||
// int localIndex = 0;
|
||||
// while (value.Length > 0)
|
||||
// {
|
||||
// //TODO: Figure out how to use MaxPDUSize here
|
||||
// //Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
// //replies with bigger PDU size in connection setup.
|
||||
// var maxToWrite = Math.Min(value.Length, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
|
||||
// WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Slice(0, maxToWrite));
|
||||
// value = value.Slice(maxToWrite);
|
||||
// localIndex += maxToWrite;
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit from a DB with the specified index.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
|
||||
{
|
||||
if (bitAdr < 0 || bitAdr > 7)
|
||||
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
|
||||
|
||||
WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit to a DB with the specified index.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to write DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
/// <param name="value">Value to write (0 or 1).</param>
|
||||
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
|
||||
{
|
||||
if (value < 0 || value > 1)
|
||||
throw new ArgumentException("Value must be 0 or 1", nameof(value));
|
||||
|
||||
WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes in input an object and tries to parse it to an array of values. This can be used to write many data, all of the same type.
|
||||
/// You must specify the memory area type, memory are address, byte start address and bytes count.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
||||
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
||||
public void Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
|
||||
{
|
||||
if (bitAdr != -1)
|
||||
{
|
||||
//Must be writing a bit value as bitAdr is specified
|
||||
if (value is bool boolean)
|
||||
{
|
||||
WriteBit(dataType, db, startByteAdr, bitAdr, boolean);
|
||||
}
|
||||
else if (value is int intValue)
|
||||
{
|
||||
if (intValue < 0 || intValue > 7)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
string.Format(
|
||||
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
|
||||
bitAdr), nameof(bitAdr));
|
||||
|
||||
WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
|
||||
}
|
||||
else WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
|
||||
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
|
||||
/// </summary>
|
||||
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
|
||||
/// <param name="value">Value to be written to the PLC</param>
|
||||
public void Write(string variable, object value)
|
||||
{
|
||||
var adr = new PLCAddress(variable);
|
||||
Write(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a C# struct to a DB in the PLC
|
||||
/// </summary>
|
||||
/// <param name="structValue">The struct to be written</param>
|
||||
/// <param name="db">Db address</param>
|
||||
/// <param name="startByteAdr">Start bytes on the PLC</param>
|
||||
public void WriteStruct(object structValue, int db, int startByteAdr = 0)
|
||||
{
|
||||
WriteStructAsync(structValue, db, startByteAdr).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a C# class to a DB in the PLC
|
||||
/// </summary>
|
||||
/// <param name="classValue">The class to be written</param>
|
||||
/// <param name="db">Db address</param>
|
||||
/// <param name="startByteAdr">Start bytes on the PLC</param>
|
||||
public void WriteClass(object classValue, int db, int startByteAdr = 0)
|
||||
{
|
||||
WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
//private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, Span<byte> buffer)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// // first create the header
|
||||
// const int packageSize = 19 + 12; // 19 header + 12 for 1 request
|
||||
// var dataToSend = new byte[packageSize];
|
||||
// var package = new MemoryStream(dataToSend);
|
||||
// BuildHeaderPackage(package);
|
||||
// // package.Add(0x02); // datenart
|
||||
// BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length);
|
||||
|
||||
// var s7data = RequestTsdu(dataToSend);
|
||||
// AssertReadResponse(s7data, buffer.Length);
|
||||
|
||||
// s7data.AsSpan(18, buffer.Length).CopyTo(buffer);
|
||||
// }
|
||||
// catch (Exception exc)
|
||||
// {
|
||||
// throw new PlcException(ErrorCode.ReadData, exc);
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
|
||||
/// or when the PLC reports errors for item(s) written.
|
||||
/// </summary>
|
||||
/// <param name="dataItems">The DataItem(s) to write to the PLC.</param>
|
||||
public void Write(params DataItem[] dataItems)
|
||||
{
|
||||
AssertPduSizeForWrite(dataItems);
|
||||
|
||||
var message = new ByteArray();
|
||||
var length = S7WriteMultiple.CreateRequest(message, dataItems);
|
||||
var response = RequestTsdu(message.Array, 0, length);
|
||||
|
||||
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
|
||||
}
|
||||
|
||||
//private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value);
|
||||
// var s7data = RequestTsdu(dataToSend);
|
||||
|
||||
// ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
// }
|
||||
// catch (Exception exc)
|
||||
// {
|
||||
// throw new PlcException(ErrorCode.WriteData, exc);
|
||||
// }
|
||||
//}
|
||||
|
||||
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
|
||||
{
|
||||
int varCount = count;
|
||||
// first create the header
|
||||
int packageSize = 35 + varCount;
|
||||
var package = new MemoryStream(new byte[packageSize]);
|
||||
|
||||
package.WriteByte(3);
|
||||
package.WriteByte(0);
|
||||
//complete package size
|
||||
package.Write(Int.ToByteArray((short)packageSize));
|
||||
package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
|
||||
package.Write(Word.ToByteArray((ushort)(varCount - 1)));
|
||||
package.Write(new byte[] { 0, 0x0e });
|
||||
package.Write(Word.ToByteArray((ushort)(varCount + 4)));
|
||||
package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
|
||||
package.Write(Word.ToByteArray((ushort)varCount));
|
||||
package.Write(Word.ToByteArray((ushort)(db)));
|
||||
package.WriteByte((byte)dataType);
|
||||
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
|
||||
package.WriteByte((byte)overflow);
|
||||
package.Write(Word.ToByteArray((ushort)(startByteAdr * 8)));
|
||||
package.Write(new byte[] { 0, 4 });
|
||||
package.Write(Word.ToByteArray((ushort)(varCount * 8)));
|
||||
|
||||
// now join the header and the data
|
||||
package.Write(value, dataOffset, count);
|
||||
|
||||
return package.ToArray();
|
||||
}
|
||||
|
||||
//private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
//{
|
||||
// int varCount = value.Length;
|
||||
// // first create the header
|
||||
// int packageSize = 35 + varCount;
|
||||
// var packageData = new byte[packageSize];
|
||||
// var package = new MemoryStream(packageData);
|
||||
|
||||
// package.WriteByte(3);
|
||||
// package.WriteByte(0);
|
||||
// //complete package size
|
||||
// package.Write(Int.ToByteArray((short)packageSize));
|
||||
// // This overload doesn't allocate the byte array, it refers to assembly's static data segment
|
||||
// package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
|
||||
// package.Write(Word.ToByteArray((ushort)(varCount - 1)));
|
||||
// package.Write(new byte[] { 0, 0x0e });
|
||||
// package.Write(Word.ToByteArray((ushort)(varCount + 4)));
|
||||
// package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
|
||||
// package.Write(Word.ToByteArray((ushort)varCount));
|
||||
// package.Write(Word.ToByteArray((ushort)(db)));
|
||||
// package.WriteByte((byte)dataType);
|
||||
// var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
|
||||
// package.WriteByte((byte)overflow);
|
||||
// package.Write(Word.ToByteArray((ushort)(startByteAdr * 8)));
|
||||
// package.Write(new byte[] { 0, 4 });
|
||||
// package.Write(Word.ToByteArray((ushort)(varCount * 8)));
|
||||
|
||||
// // now join the header and the data
|
||||
// package.Write(value);
|
||||
|
||||
// return packageData;
|
||||
//}
|
||||
|
||||
private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr)
|
||||
{
|
||||
var value = new[] { bitValue ? (byte)1 : (byte)0 };
|
||||
int varCount = 1;
|
||||
// first create the header
|
||||
int packageSize = 35 + varCount;
|
||||
var packageData = new byte[packageSize];
|
||||
var package = new MemoryStream(packageData);
|
||||
|
||||
package.WriteByte(3);
|
||||
package.WriteByte(0);
|
||||
//complete package size
|
||||
package.Write(Int.ToByteArray((short)packageSize));
|
||||
package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
|
||||
package.Write(Word.ToByteArray((ushort)(varCount - 1)));
|
||||
package.Write(new byte[] { 0, 0x0e });
|
||||
package.Write(Word.ToByteArray((ushort)(varCount + 4)));
|
||||
package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit
|
||||
package.Write(Word.ToByteArray((ushort)varCount));
|
||||
package.Write(Word.ToByteArray((ushort)(db)));
|
||||
package.WriteByte((byte)dataType);
|
||||
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
|
||||
package.WriteByte((byte)overflow);
|
||||
package.Write(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
|
||||
package.Write(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
|
||||
package.Write(Word.ToByteArray((ushort)(varCount)));
|
||||
|
||||
// now join the header and the data
|
||||
package.Write(value);
|
||||
|
||||
return packageData;
|
||||
}
|
||||
|
||||
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
|
||||
var s7data = RequestTsdu(dataToSend);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads multiple vars in a single request.
|
||||
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
|
||||
/// Values are stored in the property "Value" of the dataItem and are already converted.
|
||||
/// If you don't want the conversion, just create a dataItem of bytes.
|
||||
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
|
||||
/// </summary>
|
||||
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
|
||||
public void ReadMultipleVars(List<DataItem> dataItems)
|
||||
{
|
||||
AssertPduSizeForRead(dataItems);
|
||||
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
int packageSize = 19 + (dataItems.Count * 12);
|
||||
var dataToSend = new byte[packageSize];
|
||||
var package = new MemoryStream(dataToSend);
|
||||
BuildHeaderPackage(package, dataItems.Count);
|
||||
// package.Add(0x02); // datenart
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count));
|
||||
}
|
||||
|
||||
byte[] s7data = RequestTsdu(dataToSend);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
|
||||
ParseDataIntoDataItems(s7data, dataItems);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length);
|
||||
|
||||
private byte[] RequestTsdu(byte[] requestData, int offset, int length)
|
||||
{
|
||||
return RequestTsduAsync(requestData, offset, length).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
internal static class ConnectionRequest
|
||||
{
|
||||
public static byte[] GetCOTPConnectionRequest(TsapPair tsapPair)
|
||||
{
|
||||
byte[] bSend1 = {
|
||||
3, 0, 0, 22, //TPKT
|
||||
17, //COTP Header Length
|
||||
224, //Connect Request
|
||||
0, 0, //Destination Reference
|
||||
0, 46, //Source Reference
|
||||
0, //Flags
|
||||
193, //Parameter Code (src-tasp)
|
||||
2, //Parameter Length
|
||||
tsapPair.Local.FirstByte, tsapPair.Local.SecondByte, //Source TASP
|
||||
194, //Parameter Code (dst-tasp)
|
||||
2, //Parameter Length
|
||||
tsapPair.Remote.FirstByte, tsapPair.Remote.SecondByte, //Destination TASP
|
||||
192, //Parameter Code (tpdu-size)
|
||||
1, //Parameter Length
|
||||
10 //TPDU Size (2^10 = 1024)
|
||||
};
|
||||
|
||||
return bSend1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
internal enum ReadWriteErrorCode : byte
|
||||
{
|
||||
Reserved = 0x00,
|
||||
HardwareFault = 0x01,
|
||||
AccessingObjectNotAllowed = 0x03,
|
||||
AddressOutOfRange = 0x05,
|
||||
DataTypeNotSupported = 0x06,
|
||||
DataTypeInconsistent = 0x07,
|
||||
ObjectDoesNotExist = 0x0a,
|
||||
Success = 0xff
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace S7.Net.Protocol.S7
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an area of memory in the PLC
|
||||
/// </summary>
|
||||
internal class DataItemAddress
|
||||
{
|
||||
public DataItemAddress(DataType dataType, int db, int startByteAddress, int byteLength)
|
||||
{
|
||||
DataType = dataType;
|
||||
DB = db;
|
||||
StartByteAddress = startByteAddress;
|
||||
ByteLength = byteLength;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Memory area to read
|
||||
/// </summary>
|
||||
public DataType DataType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of memory area to read (example: for DB1 this value is 1, for T45 this value is 45)
|
||||
/// </summary>
|
||||
public int DB { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of the first byte to read
|
||||
/// </summary>
|
||||
public int StartByteAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of data to read
|
||||
/// </summary>
|
||||
public int ByteLength { get; }
|
||||
}
|
||||
}
|
||||
163
常用工具集/Utility/Network/S7netplus/Protocol/S7WriteMultiple.cs
Normal file
163
常用工具集/Utility/Network/S7netplus/Protocol/S7WriteMultiple.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using S7.Net.Types;
|
||||
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
internal static class S7WriteMultiple
|
||||
{
|
||||
public static int CreateRequest(ByteArray message, DataItem[] dataItems)
|
||||
{
|
||||
message.Add(Header.Template);
|
||||
|
||||
message[Header.Offsets.ParameterCount] = (byte) dataItems.Length;
|
||||
var paramSize = dataItems.Length * Parameter.Template.Length;
|
||||
|
||||
Serialization.SetWordAt(message, Header.Offsets.ParameterSize,
|
||||
(ushort) (2 + paramSize));
|
||||
|
||||
var paramOffset = Header.Template.Length;
|
||||
var data = new ByteArray();
|
||||
|
||||
var itemCount = 0;
|
||||
|
||||
foreach (var item in dataItems)
|
||||
{
|
||||
itemCount++;
|
||||
message.Add(Parameter.Template);
|
||||
var value = Serialization.SerializeDataItem(item);
|
||||
var wordLen = item.Value is bool ? 1 : 2;
|
||||
|
||||
message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen;
|
||||
Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.Amount, (ushort) value.Length);
|
||||
Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.DbNumber, (ushort) item.DB);
|
||||
message[paramOffset + Parameter.Offsets.Area] = (byte) item.DataType;
|
||||
|
||||
data.Add(0x00);
|
||||
if (item.Value is bool b)
|
||||
{
|
||||
if (item.BitAdr > 7)
|
||||
throw new ArgumentException(
|
||||
$"Cannot read bit with invalid {nameof(item.BitAdr)} '{item.BitAdr}'.", nameof(dataItems));
|
||||
|
||||
Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr,
|
||||
item.BitAdr);
|
||||
|
||||
data.Add(0x03);
|
||||
data.AddWord(1);
|
||||
|
||||
data.Add(b ? (byte)1 : (byte)0);
|
||||
if (itemCount != dataItems.Length) {
|
||||
data.Add(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, 0);
|
||||
|
||||
var len = value.Length;
|
||||
data.Add(0x04);
|
||||
data.AddWord((ushort) (len << 3));
|
||||
data.Add(value);
|
||||
|
||||
if ((len & 0b1) == 1 && itemCount != dataItems.Length)
|
||||
{
|
||||
data.Add(0);
|
||||
}
|
||||
}
|
||||
|
||||
paramOffset += Parameter.Template.Length;
|
||||
}
|
||||
|
||||
message.Add(data.Array);
|
||||
|
||||
Serialization.SetWordAt(message, Header.Offsets.MessageLength, (ushort) message.Length);
|
||||
Serialization.SetWordAt(message, Header.Offsets.DataLength, (ushort) (message.Length - paramOffset));
|
||||
|
||||
return message.Length;
|
||||
}
|
||||
|
||||
public static void ParseResponse(byte[] message, int length, DataItem[] dataItems)
|
||||
{
|
||||
if (length < 12) throw new Exception("Not enough data received to parse write response.");
|
||||
|
||||
var messageError = Serialization.GetWordAt(message, 10);
|
||||
if (messageError != 0)
|
||||
throw new Exception($"Write failed with error {messageError}.");
|
||||
|
||||
if (length < 14 + dataItems.Length)
|
||||
throw new Exception("Not enough data received to parse individual item responses.");
|
||||
|
||||
IList<byte> itemResults = new ArraySegment<byte>(message, 14, dataItems.Length);
|
||||
|
||||
List<Exception> errors = null;
|
||||
|
||||
for (int i = 0; i < dataItems.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if (errors == null) errors = new List<Exception>();
|
||||
errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (errors != null)
|
||||
throw new AggregateException(
|
||||
$"Write failed for {errors.Count} items. See the innerExceptions for details.", errors);
|
||||
}
|
||||
|
||||
private static class Header
|
||||
{
|
||||
public static byte[] Template { get; } =
|
||||
{
|
||||
0x03, 0x00, 0x00, 0x00, // TPKT
|
||||
0x02, 0xf0, 0x80, // ISO DT
|
||||
0x32, // S7 protocol ID
|
||||
0x01, // JobRequest
|
||||
0x00, 0x00, // Reserved
|
||||
0x05, 0x00, // PDU reference
|
||||
0x00, 0x0e, // Parameters length
|
||||
0x00, 0x00, // Data length
|
||||
0x05, // Function: Write var
|
||||
0x00, // Number of items to write
|
||||
};
|
||||
|
||||
public static class Offsets
|
||||
{
|
||||
public const int MessageLength = 2;
|
||||
public const int ParameterSize = 13;
|
||||
public const int DataLength = 15;
|
||||
public const int ParameterCount = 18;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Parameter
|
||||
{
|
||||
public static byte[] Template { get; } =
|
||||
{
|
||||
0x12, // Spec
|
||||
0x0a, // Length of remaining bytes
|
||||
0x10, // Addressing mode
|
||||
0x02, // Transport size
|
||||
0x00, 0x00, // Number of elements
|
||||
0x00, 0x00, // DB number
|
||||
0x84, // Area type
|
||||
0x00, 0x00, 0x00 // Area offset
|
||||
};
|
||||
|
||||
public static class Offsets
|
||||
{
|
||||
public const int WordLength = 3;
|
||||
public const int Amount = 4;
|
||||
public const int DbNumber = 6;
|
||||
public const int Area = 8;
|
||||
public const int Address = 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
常用工具集/Utility/Network/S7netplus/Protocol/Serialization.cs
Normal file
102
常用工具集/Utility/Network/S7netplus/Protocol/Serialization.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using S7.Net.Types;
|
||||
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
internal static class Serialization
|
||||
{
|
||||
public static ushort GetWordAt(IList<byte> buf, int index)
|
||||
{
|
||||
return (ushort)((buf[index] << 8) + buf[index]);
|
||||
}
|
||||
|
||||
public static byte[] SerializeDataItem(DataItem dataItem)
|
||||
{
|
||||
if (dataItem.Value == null)
|
||||
{
|
||||
throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}");
|
||||
}
|
||||
|
||||
if (dataItem.Value is string s)
|
||||
{
|
||||
switch (dataItem.VarType)
|
||||
{
|
||||
case VarType.S7String:
|
||||
return S7String.ToByteArray(s, dataItem.Count);
|
||||
case VarType.S7WString:
|
||||
return S7WString.ToByteArray(s, dataItem.Count);
|
||||
default:
|
||||
return Types.String.ToByteArray(s, dataItem.Count);
|
||||
}
|
||||
}
|
||||
return SerializeValue(dataItem.Value);
|
||||
}
|
||||
|
||||
public static byte[] SerializeValue(object value)
|
||||
{
|
||||
switch (value.GetType().Name)
|
||||
{
|
||||
case "Boolean":
|
||||
return new[] { (byte)((bool)value ? 1 : 0) };
|
||||
case "Byte":
|
||||
return Types.Byte.ToByteArray((byte)value);
|
||||
case "Int16":
|
||||
return Types.Int.ToByteArray((Int16)value);
|
||||
case "UInt16":
|
||||
return Types.Word.ToByteArray((UInt16)value);
|
||||
case "Int32":
|
||||
return Types.DInt.ToByteArray((Int32)value);
|
||||
case "UInt32":
|
||||
return Types.DWord.ToByteArray((UInt32)value);
|
||||
case "Single":
|
||||
return Types.Real.ToByteArray((float)value);
|
||||
case "Double":
|
||||
return Types.LReal.ToByteArray((double)value);
|
||||
case "DateTime":
|
||||
return Types.DateTime.ToByteArray((System.DateTime)value);
|
||||
case "Byte[]":
|
||||
return (byte[])value;
|
||||
case "Int16[]":
|
||||
return Types.Int.ToByteArray((Int16[])value);
|
||||
case "UInt16[]":
|
||||
return Types.Word.ToByteArray((UInt16[])value);
|
||||
case "Int32[]":
|
||||
return Types.DInt.ToByteArray((Int32[])value);
|
||||
case "UInt32[]":
|
||||
return Types.DWord.ToByteArray((UInt32[])value);
|
||||
case "Single[]":
|
||||
return Types.Real.ToByteArray((float[])value);
|
||||
case "Double[]":
|
||||
return Types.LReal.ToByteArray((double[])value);
|
||||
case "String":
|
||||
// Hack: This is backwards compatible with the old code, but functionally it's broken
|
||||
// if the consumer does not pay attention to string length.
|
||||
var stringVal = (string)value;
|
||||
return Types.String.ToByteArray(stringVal, stringVal.Length);
|
||||
case "DateTime[]":
|
||||
return Types.DateTime.ToByteArray((System.DateTime[])value);
|
||||
case "DateTimeLong[]":
|
||||
return Types.DateTimeLong.ToByteArray((System.DateTime[])value);
|
||||
default:
|
||||
throw new InvalidVariableTypeException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAddressAt(ByteArray buffer, int index, int startByte, byte bitNumber)
|
||||
{
|
||||
var start = startByte * 8 + bitNumber;
|
||||
buffer[index + 2] = (byte)start;
|
||||
start >>= 8;
|
||||
buffer[index + 1] = (byte)start;
|
||||
start >>= 8;
|
||||
buffer[index] = (byte)start;
|
||||
}
|
||||
|
||||
public static void SetWordAt(ByteArray buffer, int index, ushort value)
|
||||
{
|
||||
buffer[index] = (byte)(value >> 8);
|
||||
buffer[index + 1] = (byte)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
常用工具集/Utility/Network/S7netplus/Protocol/Tsap.cs
Normal file
31
常用工具集/Utility/Network/S7netplus/Protocol/Tsap.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a representation of the Transport Service Access Point, or TSAP in short. TSAP's are used
|
||||
/// to specify a client and server address. For most PLC types a default TSAP is available that allows
|
||||
/// connection from any IP and can be calculated using the rack and slot numbers.
|
||||
/// </summary>
|
||||
public struct Tsap
|
||||
{
|
||||
/// <summary>
|
||||
/// First byte of the TSAP.
|
||||
/// </summary>
|
||||
public byte FirstByte { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Second byte of the TSAP.
|
||||
/// </summary>
|
||||
public byte SecondByte { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tsap" /> class using the specified values.
|
||||
/// </summary>
|
||||
/// <param name="firstByte">The first byte of the TSAP.</param>
|
||||
/// <param name="secondByte">The second byte of the TSAP.</param>
|
||||
public Tsap(byte firstByte, byte secondByte)
|
||||
{
|
||||
FirstByte = firstByte;
|
||||
SecondByte = secondByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
常用工具集/Utility/Network/S7netplus/Protocol/TsapPair.cs
Normal file
96
常用工具集/Utility/Network/S7netplus/Protocol/TsapPair.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a pair of TSAP addresses used to connect to a PLC.
|
||||
/// </summary>
|
||||
public class TsapPair
|
||||
{
|
||||
/// <summary>
|
||||
/// The local <see cref="Tsap" />.
|
||||
/// </summary>
|
||||
public Tsap Local { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The remote <see cref="Tsap" />
|
||||
/// </summary>
|
||||
public Tsap Remote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TsapPair" /> class using the specified local and
|
||||
/// remote TSAP.
|
||||
/// </summary>
|
||||
/// <param name="local">The local TSAP.</param>
|
||||
/// <param name="remote">The remote TSAP.</param>
|
||||
public TsapPair(Tsap local, Tsap remote)
|
||||
{
|
||||
Local = local;
|
||||
Remote = remote;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a <see cref="TsapPair" /> that can be used to connect to a PLC using the default connection
|
||||
/// addresses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The remote TSAP is constructed using <code>new Tsap(0x03, (byte) ((rack << 5) | slot))</code>.
|
||||
/// </remarks>
|
||||
/// <param name="cpuType">The CPU type of the PLC.</param>
|
||||
/// <param name="rack">The rack of the PLC's network card.</param>
|
||||
/// <param name="slot">The slot of the PLC's network card.</param>
|
||||
/// <returns>A TSAP pair that matches the given parameters.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="cpuType"/> is invalid.
|
||||
///
|
||||
/// -or-
|
||||
///
|
||||
/// The <paramref name="rack"/> parameter is less than 0.
|
||||
///
|
||||
/// -or-
|
||||
///
|
||||
/// The <paramref name="rack"/> parameter is greater than 15.
|
||||
///
|
||||
/// -or-
|
||||
///
|
||||
/// The <paramref name="slot"/> parameter is less than 0.
|
||||
///
|
||||
/// -or-
|
||||
///
|
||||
/// The <paramref name="slot"/> parameter is greater than 15.</exception>
|
||||
public static TsapPair GetDefaultTsapPair(CpuType cpuType, int rack, int slot)
|
||||
{
|
||||
if (rack < 0) throw InvalidRackOrSlot(rack, nameof(rack), "minimum", 0);
|
||||
if (rack > 0x0F) throw InvalidRackOrSlot(rack, nameof(rack), "maximum", 0x0F);
|
||||
|
||||
if (slot < 0) throw InvalidRackOrSlot(slot, nameof(slot), "minimum", 0);
|
||||
if (slot > 0x0F) throw InvalidRackOrSlot(slot, nameof(slot), "maximum", 0x0F);
|
||||
|
||||
switch (cpuType)
|
||||
{
|
||||
case CpuType.S7200:
|
||||
return new TsapPair(new Tsap(0x10, 0x00), new Tsap(0x10, 0x01));
|
||||
case CpuType.Logo0BA8:
|
||||
// The actual values are probably on a per-project basis
|
||||
return new TsapPair(new Tsap(0x01, 0x00), new Tsap(0x01, 0x02));
|
||||
case CpuType.S7200Smart:
|
||||
case CpuType.S71200:
|
||||
case CpuType.S71500:
|
||||
case CpuType.S7300:
|
||||
case CpuType.S7400:
|
||||
// Testing with S7 1500 shows only the remote TSAP needs to match. This might differ for other
|
||||
// PLC types.
|
||||
return new TsapPair(new Tsap(0x01, 0x00), new Tsap(0x03, (byte) ((rack << 5) | slot)));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(cpuType), "Invalid CPU Type specified");
|
||||
}
|
||||
}
|
||||
|
||||
private static ArgumentOutOfRangeException InvalidRackOrSlot(int value, string name, string extrema,
|
||||
int extremaValue)
|
||||
{
|
||||
return new ArgumentOutOfRangeException(name,
|
||||
$"Invalid {name} value specified (decimal: {value}, hexadecimal: {value:X}), {extrema} value " +
|
||||
$"is {extremaValue} (decimal) or {extremaValue:X} (hexadecimal).");
|
||||
}
|
||||
}
|
||||
}
|
||||
301
常用工具集/Utility/Network/S7netplus/S7Helper.cs
Normal file
301
常用工具集/Utility/Network/S7netplus/S7Helper.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using S7.Net;
|
||||
using S7.Net.Types;
|
||||
|
||||
namespace MES.Utility.Network.S7netplus
|
||||
{
|
||||
public class S7Helper : IDisposable
|
||||
{
|
||||
private object _lock = new object();
|
||||
private Plc plc;
|
||||
public S7Helper(CpuType cpuType, string ipAddress) : this(cpuType, ipAddress, 0, 1)
|
||||
{
|
||||
}
|
||||
|
||||
public S7Helper(CpuType cpuType, string ipAddress, short rack, short slot)
|
||||
{
|
||||
plc = new Plc(cpuType, ipAddress, 102, rack, slot);
|
||||
plc.Open(1000);
|
||||
}
|
||||
|
||||
#region BOOL类型读写
|
||||
/// <summary>
|
||||
/// 写入BOOL
|
||||
/// </summary>
|
||||
/// <param name="address">例如:DB45.DBX52.0</param>
|
||||
/// <param name="value">true/false</param>
|
||||
/// <returns></returns>
|
||||
public bool WriteBool(string address, bool value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
|
||||
plc.Write(address, value);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取bool类型
|
||||
/// </summary>
|
||||
/// <param name="address">例如:DB45.DBX52.0</param>
|
||||
/// <param name="value">输出bool值</param>
|
||||
/// <returns>返回false 读取失败,PLC通信异常</returns>
|
||||
public bool ReadBool(string address, out bool value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
value = (bool)plc.Read(address);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region INT16读写
|
||||
public bool ReadInt16(string address, out ushort value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
value = (ushort)plc.Read(address);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool WriteInt16(string address, ushort value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
plc.Write(address, value);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region REAL读写
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="address">例如 DB1.DBD4.0</param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadSingle(string address, out float value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
value = ((uint)plc.Read(address)).ConvertToFloat();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadBytes(int db, int address, int count, out byte[] value)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = plc.ReadBytes(DataType.DataBlock, db, address, count);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="address">例如 DB1.DBD4.0</param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public bool WriteSingle(string address, float value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
plc.Write(address, value);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region String读写
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="address">例如DB45.DBX52.0</param>
|
||||
/// <param name="strLength">字符串长度</param>
|
||||
/// <param name="value">返回字符串数据</param>
|
||||
/// <returns></returns>
|
||||
public bool ReadString(string address, int strLength, out string value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
string[] array = address.Split('.');
|
||||
int db = Convert.ToInt32(array[0].Replace("DB", ""));
|
||||
int startAddress = Convert.ToInt32(array[1].Replace("DBX", ""));
|
||||
value = (string)plc.Read(DataType.DataBlock, db, startAddress, VarType.S7String, strLength);//获取对应长度的字符串
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = "";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入字符串
|
||||
/// </summary>
|
||||
/// <param name="address">例如DB45.DBX52.0</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <param name="value">写入长度</param>
|
||||
/// <returns></returns>
|
||||
public bool WriteString(string address, string value, int strLength)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
//转换address,取到DB 和 开始地址
|
||||
string[] array = address.Split('.');
|
||||
int db = Convert.ToInt32(array[0].Replace("DB", ""));
|
||||
int startAddress = Convert.ToInt32(array[1].Replace("DBX", ""));
|
||||
var bytes = S7.Net.Types.S7String.ToByteArray(value, strLength);
|
||||
plc.WriteBytes(DataType.DataBlock, db, startAddress, bytes);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public bool ReadBytesString(string address, int strLength, out string value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
string[] array = address.Split('.');
|
||||
int db = Convert.ToInt32(array[0].Replace("DB", ""));
|
||||
int startAddress = Convert.ToInt32(array[1].Replace("DBX", ""));
|
||||
byte[] values = plc.ReadBytes(DataType.DataBlock, db, startAddress, strLength);
|
||||
value = Encoding.ASCII.GetString(values);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = "";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入字符串
|
||||
/// </summary>
|
||||
/// <param name="address">例如DB45.DBX52.0</param>
|
||||
/// <param name="value">写入数据</param>
|
||||
/// <param name="value">写入长度</param>
|
||||
/// <returns></returns>
|
||||
public bool WriteBytesString(string address, string value, int strLength)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
//转换address,取到DB 和 开始地址
|
||||
string[] array = address.Split('.');
|
||||
int db = Convert.ToInt32(array[0].Replace("DB", ""));
|
||||
int startAddress = Convert.ToInt32(array[1].Replace("DBX", ""));
|
||||
var bytes = Encoding.ASCII.GetBytes(value);
|
||||
if (bytes.Length > strLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({strLength}).");
|
||||
var buffer = new byte[strLength];
|
||||
Array.Copy(bytes, 0, buffer, 0, bytes.Length);
|
||||
plc.WriteBytes(DataType.DataBlock, db, startAddress, buffer);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region INT32读写
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="address">例如:DB1.DBD264.0</param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadInt32(string address, out int value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
value = (int)plc.Read(address);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool WriteInt32(string address, int value)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
plc.Write(address, value);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
plc?.Close();
|
||||
plc = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
57
常用工具集/Utility/Network/S7netplus/StreamExtensions.cs
Normal file
57
常用工具集/Utility/Network/S7netplus/StreamExtensions.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for Streams
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read
|
||||
/// </summary>
|
||||
/// <param name="stream">the Stream to read from</param>
|
||||
/// <param name="buffer">the buffer to read into</param>
|
||||
/// <param name="offset">the offset in the buffer to read into</param>
|
||||
/// <param name="count">the amount of bytes to read into the buffer</param>
|
||||
/// <returns>returns the amount of read bytes</returns>
|
||||
public static int ReadExact(this Stream stream, byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = 0;
|
||||
int received;
|
||||
do
|
||||
{
|
||||
received = stream.Read(buffer, offset + read, count - read);
|
||||
read += received;
|
||||
}
|
||||
while (read < count && received > 0);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read
|
||||
/// </summary>
|
||||
/// <param name="stream">the Stream to read from</param>
|
||||
/// <param name="buffer">the buffer to read into</param>
|
||||
/// <param name="offset">the offset in the buffer to read into</param>
|
||||
/// <param name="count">the amount of bytes to read into the buffer</param>
|
||||
/// <returns>returns the amount of read bytes</returns>
|
||||
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
int read = 0;
|
||||
int received;
|
||||
do
|
||||
{
|
||||
received = await stream.ReadAsync(buffer, offset + read, count - read, cancellationToken).ConfigureAwait(false);
|
||||
read += received;
|
||||
}
|
||||
while (read < count && received > 0);
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
常用工具集/Utility/Network/S7netplus/TPKT.cs
Normal file
66
常用工具集/Utility/Network/S7netplus/TPKT.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Describes a TPKT Packet
|
||||
/// </summary>
|
||||
internal class TPKT
|
||||
{
|
||||
|
||||
|
||||
public byte Version;
|
||||
public byte Reserved1;
|
||||
public int Length;
|
||||
public byte[] Data;
|
||||
private TPKT(byte version, byte reserved1, int length, byte[] data)
|
||||
{
|
||||
Version = version;
|
||||
Reserved1 = reserved1;
|
||||
Length = length;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a TPKT from the socket Async
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from</param>
|
||||
/// <returns>Task TPKT Instace</returns>
|
||||
public static async Task<TPKT> ReadAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var buf = new byte[4];
|
||||
int len = await stream.ReadExactAsync(buf, 0, 4, cancellationToken).ConfigureAwait(false);
|
||||
if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid");
|
||||
|
||||
var version = buf[0];
|
||||
var reserved1 = buf[1];
|
||||
var length = buf[2] * 256 + buf[3]; //BigEndian
|
||||
|
||||
var data = new byte[length - 4];
|
||||
len = await stream.ReadExactAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (len < data.Length)
|
||||
throw new TPKTInvalidException("TPKT payload incomplete / invalid");
|
||||
|
||||
return new TPKT
|
||||
(
|
||||
version: version,
|
||||
reserved1: reserved1,
|
||||
length: length,
|
||||
data: data
|
||||
);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Version: {0} Length: {1} Data: {2}",
|
||||
Version,
|
||||
Length,
|
||||
BitConverter.ToString(Data)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
常用工具集/Utility/Network/S7netplus/Types/Bit.cs
Normal file
43
常用工具集/Utility/Network/S7netplus/Types/Bit.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Bit from S7 plc to C#.
|
||||
/// </summary>
|
||||
public static class Bit
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a Bit to bool
|
||||
/// </summary>
|
||||
public static bool FromByte(byte v, byte bitAdr)
|
||||
{
|
||||
return (((int)v & (1 << bitAdr)) != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to a BitArray.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes to convert.</param>
|
||||
/// <returns>A BitArray with the same number of bits and equal values as <paramref name="bytes"/>.</returns>
|
||||
public static BitArray ToBitArray(byte[] bytes) => ToBitArray(bytes, bytes.Length * 8);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to a BitArray.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes to convert.</param>
|
||||
/// <param name="length">The number of bits to return.</param>
|
||||
/// <returns>A BitArray with <paramref name="length"/> bits.</returns>
|
||||
public static BitArray ToBitArray(byte[] bytes, int length)
|
||||
{
|
||||
if (length > bytes.Length * 8) throw new ArgumentException($"Not enough data in bytes to return {length} bits.", nameof(bytes));
|
||||
|
||||
var bitArr = new BitArray(bytes);
|
||||
var bools = new bool[length];
|
||||
for (var i = 0; i < length; i++) bools[i] = bitArr[i];
|
||||
|
||||
return new BitArray(bools);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
常用工具集/Utility/Network/S7netplus/Types/Boolean.cs
Normal file
64
常用工具集/Utility/Network/S7netplus/Types/Boolean.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to read, set and reset bits inside bytes
|
||||
/// </summary>
|
||||
public static class Boolean
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the value of a bit in a bit, given the address of the bit
|
||||
/// </summary>
|
||||
public static bool GetValue(byte value, int bit)
|
||||
{
|
||||
return (((int)value & (1 << bit)) != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a bit to 1 (true), given the address of the bit. Returns
|
||||
/// a copy of the value with the bit set.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to modify.</param>
|
||||
/// <param name="bit">The index (zero based) of the bit to set.</param>
|
||||
/// <returns>The modified value with the bit at index set.</returns>
|
||||
public static byte SetBit(byte value, int bit)
|
||||
{
|
||||
SetBit(ref value, bit);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a bit to 1 (true), given the address of the bit.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to modify.</param>
|
||||
/// <param name="bit">The index (zero based) of the bit to set.</param>
|
||||
public static void SetBit(ref byte value, int bit)
|
||||
{
|
||||
value = (byte) ((value | (1 << bit)) & 0xFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value of a bit to 0 (false), given the address of the bit. Returns
|
||||
/// a copy of the value with the bit cleared.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to modify.</param>
|
||||
/// <param name="bit">The index (zero based) of the bit to clear.</param>
|
||||
/// <returns>The modified value with the bit at index cleared.</returns>
|
||||
public static byte ClearBit(byte value, int bit)
|
||||
{
|
||||
ClearBit(ref value, bit);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value of a bit to 0 (false), given the address of the bit
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to modify.</param>
|
||||
/// <param name="bit">The index (zero based) of the bit to clear.</param>
|
||||
public static void ClearBit(ref byte value, int bit)
|
||||
{
|
||||
value = (byte) (value & ~(1 << bit) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
常用工具集/Utility/Network/S7netplus/Types/Byte.cs
Normal file
33
常用工具集/Utility/Network/S7netplus/Types/Byte.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert from bytes to byte arrays
|
||||
/// </summary>
|
||||
public static class Byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a byte to byte array
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(byte value)
|
||||
{
|
||||
return new byte[] { value }; ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a byte array to byte
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static byte FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 1)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 1 bytes.");
|
||||
}
|
||||
return bytes[0];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
63
常用工具集/Utility/Network/S7netplus/Types/ByteArray.cs
Normal file
63
常用工具集/Utility/Network/S7netplus/Types/ByteArray.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
class ByteArray
|
||||
{
|
||||
List<byte> list = new List<byte>();
|
||||
|
||||
public byte this[int index]
|
||||
{
|
||||
get => list[index];
|
||||
set => list[index] = value;
|
||||
}
|
||||
|
||||
public byte[] Array
|
||||
{
|
||||
get { return list.ToArray(); }
|
||||
}
|
||||
|
||||
public int Length => list.Count;
|
||||
|
||||
public ByteArray()
|
||||
{
|
||||
list = new List<byte>();
|
||||
}
|
||||
|
||||
public ByteArray(int size)
|
||||
{
|
||||
list = new List<byte>(size);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
list = new List<byte>();
|
||||
}
|
||||
|
||||
public void Add(byte item)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
public void AddWord(ushort value)
|
||||
{
|
||||
list.Add((byte) (value >> 8));
|
||||
list.Add((byte) value);
|
||||
}
|
||||
|
||||
public void Add(byte[] items)
|
||||
{
|
||||
list.AddRange(items);
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<byte> items)
|
||||
{
|
||||
list.AddRange(items);
|
||||
}
|
||||
|
||||
public void Add(ByteArray byteArray)
|
||||
{
|
||||
list.AddRange(byteArray.Array);
|
||||
}
|
||||
}
|
||||
}
|
||||
354
常用工具集/Utility/Network/S7netplus/Types/Class.cs
Normal file
354
常用工具集/Utility/Network/S7netplus/Types/Class.cs
Normal file
@@ -0,0 +1,354 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert a C# class to S7 data types
|
||||
/// </summary>
|
||||
public static class Class
|
||||
{
|
||||
private static IEnumerable<PropertyInfo> GetAccessableProperties(Type classType)
|
||||
{
|
||||
return classType
|
||||
.GetProperties(
|
||||
BindingFlags.SetProperty |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance)
|
||||
.Where(p => p.GetSetMethod() != null);
|
||||
}
|
||||
|
||||
private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo propertyInfo)
|
||||
{
|
||||
switch (type.Name)
|
||||
{
|
||||
case "Boolean":
|
||||
numBytes += 0.125;
|
||||
break;
|
||||
case "Byte":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
numBytes++;
|
||||
break;
|
||||
case "Int16":
|
||||
case "UInt16":
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "Int32":
|
||||
case "UInt32":
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 8;
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||
if (attribute == default(S7StringAttribute))
|
||||
throw new ArgumentException("Please add S7StringAttribute to the string field");
|
||||
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += attribute.ReservedLengthInBytes;
|
||||
break;
|
||||
default:
|
||||
var propertyClass = Activator.CreateInstance(type);
|
||||
numBytes = GetClassSize(propertyClass, numBytes, true);
|
||||
break;
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the class in bytes.
|
||||
/// </summary>
|
||||
/// <param name="instance">An instance of the class</param>
|
||||
/// <returns>the number of bytes</returns>
|
||||
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
|
||||
{
|
||||
var properties = GetAccessableProperties(instance.GetType());
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.PropertyType.IsArray)
|
||||
{
|
||||
Type elementType = property.PropertyType.GetElementType();
|
||||
Array array = (Array)property.GetValue(instance, null);
|
||||
if (array.Length <= 0)
|
||||
{
|
||||
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
|
||||
}
|
||||
|
||||
IncrementToEven(ref numBytes);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property);
|
||||
}
|
||||
}
|
||||
if (false == isInnerProperty)
|
||||
{
|
||||
// enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static object GetPropertyValue(Type propertyType, PropertyInfo propertyInfo, byte[] bytes, ref double numBytes)
|
||||
{
|
||||
object value = null;
|
||||
|
||||
switch (propertyType.Name)
|
||||
{
|
||||
case "Boolean":
|
||||
// get the value
|
||||
int bytePos = (int)Math.Floor(numBytes);
|
||||
int bitPos = (int)((numBytes - (double)bytePos) / 0.125);
|
||||
if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
|
||||
value = true;
|
||||
else
|
||||
value = false;
|
||||
numBytes += 0.125;
|
||||
break;
|
||||
case "Byte":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
value = (byte)(bytes[(int)numBytes]);
|
||||
numBytes++;
|
||||
break;
|
||||
case "Int16":
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
|
||||
value = source.ConvertToShort();
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "UInt16":
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "Int32":
|
||||
IncrementToEven(ref numBytes);
|
||||
var wordBuffer = new byte[4];
|
||||
Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length);
|
||||
uint sourceUInt = DWord.FromByteArray(wordBuffer);
|
||||
value = sourceUInt.ConvertToInt();
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "UInt32":
|
||||
IncrementToEven(ref numBytes);
|
||||
var wordBuffer2 = new byte[4];
|
||||
Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length);
|
||||
value = DWord.FromByteArray(wordBuffer2);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
value = Real.FromByteArray(
|
||||
new byte[] {
|
||||
bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 3] });
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
IncrementToEven(ref numBytes);
|
||||
var buffer = new byte[8];
|
||||
Array.Copy(bytes, (int)numBytes, buffer, 0, 8);
|
||||
// hier auswerten
|
||||
value = LReal.FromByteArray(buffer);
|
||||
numBytes += 8;
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||
if (attribute == default(S7StringAttribute))
|
||||
throw new ArgumentException("Please add S7StringAttribute to the string field");
|
||||
|
||||
IncrementToEven(ref numBytes);
|
||||
|
||||
// get the value
|
||||
var sData = new byte[attribute.ReservedLengthInBytes];
|
||||
Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
|
||||
switch (attribute.Type)
|
||||
{
|
||||
case S7StringType.S7String:
|
||||
value = S7String.FromByteArray(sData); break;
|
||||
case S7StringType.S7WString:
|
||||
value = S7WString.FromByteArray(sData); break;
|
||||
default:
|
||||
throw new ArgumentException("Please use a valid string type for the S7StringAttribute");
|
||||
}
|
||||
numBytes += sData.Length;
|
||||
break;
|
||||
default:
|
||||
var propClass = Activator.CreateInstance(propertyType);
|
||||
numBytes = FromBytes(propClass, bytes, numBytes);
|
||||
value = propClass;
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the object's values with the given array of bytes
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
|
||||
/// <param name="bytes">The array of bytes</param>
|
||||
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
|
||||
{
|
||||
if (bytes == null)
|
||||
return numBytes;
|
||||
|
||||
var properties = GetAccessableProperties(sourceClass.GetType());
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.PropertyType.IsArray)
|
||||
{
|
||||
Array array = (Array)property.GetValue(sourceClass, null);
|
||||
IncrementToEven(ref numBytes);
|
||||
Type elementType = property.PropertyType.GetElementType();
|
||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||
{
|
||||
array.SetValue(
|
||||
GetPropertyValue(elementType, property, bytes, ref numBytes),
|
||||
i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
property.SetValue(
|
||||
sourceClass,
|
||||
GetPropertyValue(property.PropertyType, property, bytes, ref numBytes),
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static double SetBytesFromProperty(object propertyValue, PropertyInfo propertyInfo, byte[] bytes, double numBytes)
|
||||
{
|
||||
int bytePos = 0;
|
||||
int bitPos = 0;
|
||||
byte[] bytes2 = null;
|
||||
|
||||
switch (propertyValue.GetType().Name)
|
||||
{
|
||||
case "Boolean":
|
||||
// get the value
|
||||
bytePos = (int)Math.Floor(numBytes);
|
||||
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
|
||||
if ((bool)propertyValue)
|
||||
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
|
||||
else
|
||||
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
|
||||
numBytes += 0.125;
|
||||
break;
|
||||
case "Byte":
|
||||
numBytes = (int)Math.Ceiling(numBytes);
|
||||
bytePos = (int)numBytes;
|
||||
bytes[bytePos] = (byte)propertyValue;
|
||||
numBytes++;
|
||||
break;
|
||||
case "Int16":
|
||||
bytes2 = Int.ToByteArray((Int16)propertyValue);
|
||||
break;
|
||||
case "UInt16":
|
||||
bytes2 = Word.ToByteArray((UInt16)propertyValue);
|
||||
break;
|
||||
case "Int32":
|
||||
bytes2 = DInt.ToByteArray((Int32)propertyValue);
|
||||
break;
|
||||
case "UInt32":
|
||||
bytes2 = DWord.ToByteArray((UInt32)propertyValue);
|
||||
break;
|
||||
case "Single":
|
||||
bytes2 = Real.ToByteArray((float)propertyValue);
|
||||
break;
|
||||
case "Double":
|
||||
bytes2 = LReal.ToByteArray((double)propertyValue);
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||
if (attribute == default(S7StringAttribute))
|
||||
throw new ArgumentException("Please add S7StringAttribute to the string field");
|
||||
switch (attribute.Type)
|
||||
{
|
||||
case S7StringType.S7String:
|
||||
bytes2 = S7String.ToByteArray((string)propertyValue, attribute.ReservedLength); break;
|
||||
case S7StringType.S7WString:
|
||||
bytes2 = S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength); break;
|
||||
default:
|
||||
throw new ArgumentException("Please use a valid string type for the S7StringAttribute");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
numBytes = ToBytes(propertyValue, bytes, numBytes);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes2 != null)
|
||||
{
|
||||
IncrementToEven(ref numBytes);
|
||||
|
||||
bytePos = (int)numBytes;
|
||||
for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
|
||||
bytes[bytePos + bCnt] = bytes2[bCnt];
|
||||
numBytes += bytes2.Length;
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a byte array depending on the struct type.
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">The struct object</param>
|
||||
/// <returns>A byte array or null if fails.</returns>
|
||||
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
|
||||
{
|
||||
var properties = GetAccessableProperties(sourceClass.GetType());
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.PropertyType.IsArray)
|
||||
{
|
||||
Array array = (Array)property.GetValue(sourceClass, null);
|
||||
IncrementToEven(ref numBytes);
|
||||
Type elementType = property.PropertyType.GetElementType();
|
||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||
{
|
||||
numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes);
|
||||
}
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static void IncrementToEven(ref double numBytes)
|
||||
{
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if (numBytes % 2 > 0) numBytes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
常用工具集/Utility/Network/S7netplus/Types/Counter.cs
Normal file
63
常用工具集/Utility/Network/S7netplus/Types/Counter.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Counter from S7 plc to C# ushort (UInt16).
|
||||
/// </summary>
|
||||
public static class Counter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a Counter (2 bytes) to ushort (UInt16)
|
||||
/// </summary>
|
||||
public static UInt16 FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes.");
|
||||
}
|
||||
// bytes[0] -> HighByte
|
||||
// bytes[1] -> LowByte
|
||||
return (UInt16)((bytes[0] << 8) | bytes[1]);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a ushort (UInt16) to word (2 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(UInt16 value)
|
||||
{
|
||||
byte[] bytes = new byte[2];
|
||||
|
||||
bytes[0] = (byte)((value << 8) & 0xFF);
|
||||
bytes[1] = (byte)((value) & 0xFF);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of ushort (UInt16) to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(UInt16[] value)
|
||||
{
|
||||
ByteArray arr = new ByteArray();
|
||||
foreach (UInt16 val in value)
|
||||
arr.Add(ToByteArray(val));
|
||||
return arr.Array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to an array of ushort
|
||||
/// </summary>
|
||||
public static UInt16[] ToArray(byte[] bytes)
|
||||
{
|
||||
UInt16[] values = new UInt16[bytes.Length / 2];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 2; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
常用工具集/Utility/Network/S7netplus/Types/DInt.cs
Normal file
65
常用工具集/Utility/Network/S7netplus/Types/DInt.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert DInt from S7 plc to C# int (Int32).
|
||||
/// </summary>
|
||||
public static class DInt
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 DInt (4 bytes) to int (Int32)
|
||||
/// </summary>
|
||||
public static Int32 FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
|
||||
}
|
||||
return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a int (Int32) to S7 DInt (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(Int32 value)
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
bytes[0] = (byte)((value >> 24) & 0xFF);
|
||||
bytes[1] = (byte)((value >> 16) & 0xFF);
|
||||
bytes[2] = (byte)((value >> 8) & 0xFF);
|
||||
bytes[3] = (byte)((value) & 0xFF);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of int (Int32) to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(Int32[] value)
|
||||
{
|
||||
ByteArray arr = new ByteArray();
|
||||
foreach (Int32 val in value)
|
||||
arr.Add(ToByteArray(val));
|
||||
return arr.Array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 DInt to an array of int (Int32)
|
||||
/// </summary>
|
||||
public static Int32[] ToArray(byte[] bytes)
|
||||
{
|
||||
Int32[] values = new Int32[bytes.Length / 4];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 4; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
73
常用工具集/Utility/Network/S7netplus/Types/DWord.cs
Normal file
73
常用工具集/Utility/Network/S7netplus/Types/DWord.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert DWord from S7 plc to C#.
|
||||
/// </summary>
|
||||
public static class DWord
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 DWord (4 bytes) to uint (UInt32)
|
||||
/// </summary>
|
||||
public static UInt32 FromByteArray(byte[] bytes)
|
||||
{
|
||||
return (UInt32)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts 4 bytes to DWord (UInt32)
|
||||
/// </summary>
|
||||
public static UInt32 FromBytes(byte b1, byte b2, byte b3, byte b4)
|
||||
{
|
||||
return (UInt32)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a uint (UInt32) to S7 DWord (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(UInt32 value)
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
bytes[0] = (byte)((value >> 24) & 0xFF);
|
||||
bytes[1] = (byte)((value >> 16) & 0xFF);
|
||||
bytes[2] = (byte)((value >> 8) & 0xFF);
|
||||
bytes[3] = (byte)((value) & 0xFF);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of uint (UInt32) to an array of S7 DWord (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(UInt32[] value)
|
||||
{
|
||||
ByteArray arr = new ByteArray();
|
||||
foreach (UInt32 val in value)
|
||||
arr.Add(ToByteArray(val));
|
||||
return arr.Array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 DWord to an array of uint (UInt32)
|
||||
/// </summary>
|
||||
public static UInt32[] ToArray(byte[] bytes)
|
||||
{
|
||||
UInt32[] values = new UInt32[bytes.Length / 4];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 4; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
常用工具集/Utility/Network/S7netplus/Types/DataItem.cs
Normal file
104
常用工具集/Utility/Network/S7netplus/Types/DataItem.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using S7.Net.Protocol.S7;
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an instance of a memory block that can be read by using ReadMultipleVars
|
||||
/// </summary>
|
||||
public class DataItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory area to read
|
||||
/// </summary>
|
||||
public DataType DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of data to be read (default is bytes)
|
||||
/// </summary>
|
||||
public VarType VarType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of memory area to read (example: for DB1 this value is 1, for T45 this value is 45)
|
||||
/// </summary>
|
||||
public int DB { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of the first byte to read
|
||||
/// </summary>
|
||||
public int StartByteAdr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Addess of bit to read from StartByteAdr
|
||||
/// </summary>
|
||||
public byte BitAdr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of variables to read
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the value of the memory area after the read has been executed
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of DataItem
|
||||
/// </summary>
|
||||
public DataItem()
|
||||
{
|
||||
VarType = VarType.Byte;
|
||||
Count = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of <see cref="DataItem"/> from the supplied address.
|
||||
/// </summary>
|
||||
/// <param name="address">The address to create the DataItem for.</param>
|
||||
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/>.</returns>
|
||||
/// <remarks>The <see cref="Count" /> property is not parsed from the address.</remarks>
|
||||
public static DataItem FromAddress(string address)
|
||||
{
|
||||
PLCAddress.Parse(address, out var dataType, out var dbNumber, out var varType, out var startByte,
|
||||
out var bitNumber);
|
||||
|
||||
return new DataItem
|
||||
{
|
||||
DataType = dataType,
|
||||
DB = dbNumber,
|
||||
VarType = varType,
|
||||
StartByteAdr = startByte,
|
||||
BitAdr = (byte)(bitNumber == -1 ? 0 : bitNumber)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of <see cref="DataItem"/> from the supplied address and value.
|
||||
/// </summary>
|
||||
/// <param name="address">The address to create the DataItem for.</param>
|
||||
/// <param name="value">The value to be applied to the DataItem.</param>
|
||||
/// <returns>A new <see cref="DataItem"/> instance with properties parsed from <paramref name="address"/> and the supplied value set.</returns>
|
||||
public static DataItem FromAddressAndValue<T>(string address, T value)
|
||||
{
|
||||
var dataItem = FromAddress(address);
|
||||
dataItem.Value = value;
|
||||
|
||||
if (typeof(T).IsArray)
|
||||
{
|
||||
var array = ((Array)dataItem.Value);
|
||||
if (array != null)
|
||||
{
|
||||
dataItem.Count = array.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return dataItem;
|
||||
}
|
||||
|
||||
internal static DataItemAddress GetDataItemAddress(DataItem dataItem)
|
||||
{
|
||||
return new DataItemAddress(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, Plc.VarTypeToByteLength(dataItem.VarType, dataItem.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
156
常用工具集/Utility/Network/S7netplus/Types/DateTime.cs
Normal file
156
常用工具集/Utility/Network/S7netplus/Types/DateTime.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert between <see cref="T:System.DateTime"/> and S7 representation of datetime values.
|
||||
/// </summary>
|
||||
public static class DateTime
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="T:System.DateTime"/> value from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>A <see cref="T:System.DateTime"/> object representing the value read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not 8 or any value in <paramref name="bytes"/>
|
||||
/// is outside the valid range of values.</exception>
|
||||
public static System.DateTime FromByteArray(byte[] bytes)
|
||||
{
|
||||
return FromByteArrayImpl(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an array of <see cref="T:System.DateTime"/> values from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>An array of <see cref="T:System.DateTime"/> objects representing the values read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not a multiple of 8 or any value in
|
||||
/// <paramref name="bytes"/> is outside the valid range of values.</exception>
|
||||
public static System.DateTime[] ToArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length % 8 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long.");
|
||||
|
||||
var cnt = bytes.Length / 8;
|
||||
var result = new System.DateTime[bytes.Length / 8];
|
||||
|
||||
for (var i = 0; i < cnt; i++)
|
||||
result[i] = FromByteArrayImpl(new ArraySegment<byte>(bytes, i * 8, 8));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static System.DateTime FromByteArrayImpl(IList<byte> bytes)
|
||||
{
|
||||
if (bytes.Count != 8)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count,
|
||||
$"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long.");
|
||||
|
||||
int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111);
|
||||
|
||||
int ByteToYear(byte bcdYear)
|
||||
{
|
||||
var input = DecodeBcd(bcdYear);
|
||||
if (input < 90) return input + 2000;
|
||||
if (input < 100) return input + 1900;
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear,
|
||||
$"Value '{input}' is higher than the maximum '99' of S7 date and time representation.");
|
||||
}
|
||||
|
||||
int AssertRangeInclusive(int input, byte min, byte max, string field)
|
||||
{
|
||||
if (input < min)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
|
||||
if (input > max)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
var year = ByteToYear(bytes[0]);
|
||||
var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month");
|
||||
var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month");
|
||||
var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour");
|
||||
var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute");
|
||||
var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second");
|
||||
var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits");
|
||||
var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit");
|
||||
var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week");
|
||||
|
||||
return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="T:System.DateTime"/> value to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The DateTime value to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representation of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
|
||||
/// <paramref name="dateTime"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime dateTime)
|
||||
{
|
||||
byte EncodeBcd(int value)
|
||||
{
|
||||
return (byte) ((value / 10 << 4) | value % 10);
|
||||
}
|
||||
|
||||
if (dateTime < SpecMinimumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
if (dateTime > SpecMaximumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000);
|
||||
|
||||
int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1;
|
||||
|
||||
return new[]
|
||||
{
|
||||
EncodeBcd(MapYear(dateTime.Year)),
|
||||
EncodeBcd(dateTime.Month),
|
||||
EncodeBcd(dateTime.Day),
|
||||
EncodeBcd(dateTime.Hour),
|
||||
EncodeBcd(dateTime.Minute),
|
||||
EncodeBcd(dateTime.Second),
|
||||
EncodeBcd(dateTime.Millisecond / 10),
|
||||
(byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTimes">The DateTime values to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
||||
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime[] dateTimes)
|
||||
{
|
||||
var bytes = new List<byte>(dateTimes.Length * 8);
|
||||
foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
185
常用工具集/Utility/Network/S7netplus/Types/DateTimeLong.cs
Normal file
185
常用工具集/Utility/Network/S7netplus/Types/DateTimeLong.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert between <see cref="T:System.DateTime" /> and S7 representation of DateTimeLong (DTL) values.
|
||||
/// </summary>
|
||||
public static class DateTimeLong
|
||||
{
|
||||
public const int TypeLengthInBytes = 12;
|
||||
/// <summary>
|
||||
/// The minimum <see cref="T:System.DateTime" /> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="T:System.DateTime" /> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="T:System.DateTime" /> value from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>A <see cref="T:System.DateTime" /> object representing the value read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the length of
|
||||
/// <paramref name="bytes" /> is not 12 or any value in <paramref name="bytes" />
|
||||
/// is outside the valid range of values.
|
||||
/// </exception>
|
||||
public static System.DateTime FromByteArray(byte[] bytes)
|
||||
{
|
||||
return FromByteArrayImpl(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an array of <see cref="T:System.DateTime" /> values from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>An array of <see cref="T:System.DateTime" /> objects representing the values read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the length of
|
||||
/// <paramref name="bytes" /> is not a multiple of 12 or any value in
|
||||
/// <paramref name="bytes" /> is outside the valid range of values.
|
||||
/// </exception>
|
||||
public static System.DateTime[] ToArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length % TypeLengthInBytes != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long.");
|
||||
}
|
||||
|
||||
var cnt = bytes.Length / TypeLengthInBytes;
|
||||
var result = new System.DateTime[cnt];
|
||||
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
var slice = new byte[TypeLengthInBytes];
|
||||
Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes);
|
||||
result[i] = FromByteArrayImpl(slice);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static System.DateTime FromByteArrayImpl(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != TypeLengthInBytes)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long.");
|
||||
}
|
||||
|
||||
|
||||
var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year");
|
||||
var month = AssertRangeInclusive(bytes[2], 1, 12, "month");
|
||||
var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month");
|
||||
var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week");
|
||||
var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour");
|
||||
var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute");
|
||||
var second = AssertRangeInclusive(bytes[7], 0, 59, "second");
|
||||
;
|
||||
|
||||
var nanoseconds = AssertRangeInclusive<uint>(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0,
|
||||
999999999, "nanoseconds");
|
||||
|
||||
var time = new System.DateTime(year, month, day, hour, minute, second);
|
||||
return time.AddTicks(nanoseconds / 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="T:System.DateTime" /> value to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The DateTime value to convert.</param>
|
||||
/// <returns>A byte array containing the S7 DateTimeLong representation of <paramref name="dateTime" />.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the value of
|
||||
/// <paramref name="dateTime" /> is before <see cref="P:SpecMinimumDateTime" />
|
||||
/// or after <see cref="P:SpecMaximumDateTime" />.
|
||||
/// </exception>
|
||||
public static byte[] ToByteArray(System.DateTime dateTime)
|
||||
{
|
||||
if (dateTime < SpecMinimumDateTime)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation.");
|
||||
}
|
||||
|
||||
if (dateTime > SpecMaximumDateTime)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation.");
|
||||
}
|
||||
|
||||
var stream = new MemoryStream(TypeLengthInBytes);
|
||||
// Convert Year
|
||||
stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2);
|
||||
|
||||
// Convert Month
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Month));
|
||||
|
||||
// Convert Day
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Day));
|
||||
|
||||
// Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1.
|
||||
stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1));
|
||||
|
||||
// Convert Hour
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Hour));
|
||||
|
||||
// Convert Minutes
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Minute));
|
||||
|
||||
// Convert Seconds
|
||||
stream.WriteByte(Convert.ToByte(dateTime.Second));
|
||||
|
||||
// Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns.
|
||||
// Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds.
|
||||
stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of <see cref="T:System.DateTime" /> values to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTimes">The DateTime values to convert.</param>
|
||||
/// <returns>A byte array containing the S7 DateTimeLong representations of <paramref name="dateTimes" />.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when any value of
|
||||
/// <paramref name="dateTimes" /> is before <see cref="P:SpecMinimumDateTime" />
|
||||
/// or after <see cref="P:SpecMaximumDateTime" />.
|
||||
/// </exception>
|
||||
public static byte[] ToByteArray(System.DateTime[] dateTimes)
|
||||
{
|
||||
var bytes = new List<byte>(dateTimes.Length * TypeLengthInBytes);
|
||||
foreach (var dateTime in dateTimes)
|
||||
{
|
||||
bytes.AddRange(ToByteArray(dateTime));
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
private static T AssertRangeInclusive<T>(T input, T min, T max, string field) where T : IComparable<T>
|
||||
{
|
||||
if (input.CompareTo(min) < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
|
||||
}
|
||||
|
||||
if (input.CompareTo(max) > 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
常用工具集/Utility/Network/S7netplus/Types/Double.cs
Normal file
68
常用工具集/Utility/Network/S7netplus/Types/Double.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
[Obsolete("Class Double is obsolete. Use Real instead for 32bit floating point, or LReal for 64bit floating point.")]
|
||||
public static class Double
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Real (4 bytes) to double
|
||||
/// </summary>
|
||||
public static double FromByteArray(byte[] bytes) => Real.FromByteArray(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a S7 DInt to double
|
||||
/// </summary>
|
||||
public static double FromDWord(Int32 value)
|
||||
{
|
||||
byte[] b = DInt.ToByteArray(value);
|
||||
double d = FromByteArray(b);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a S7 DWord to double
|
||||
/// </summary>
|
||||
public static double FromDWord(UInt32 value)
|
||||
{
|
||||
byte[] b = DWord.ToByteArray(value);
|
||||
double d = FromByteArray(b);
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a double to S7 Real (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double value) => Real.ToByteArray((float)value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of double to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double[] value)
|
||||
{
|
||||
ByteArray arr = new ByteArray();
|
||||
foreach (double val in value)
|
||||
arr.Add(ToByteArray(val));
|
||||
return arr.Array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 Real to an array of double
|
||||
/// </summary>
|
||||
public static double[] ToArray(byte[] bytes)
|
||||
{
|
||||
double[] values = new double[bytes.Length / 4];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 4; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
87
常用工具集/Utility/Network/S7netplus/Types/Int.cs
Normal file
87
常用工具集/Utility/Network/S7netplus/Types/Int.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Int from S7 plc to C#.
|
||||
/// </summary>
|
||||
public static class Int
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Int (2 bytes) to short (Int16)
|
||||
/// </summary>
|
||||
public static short FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes.");
|
||||
}
|
||||
// bytes[0] -> HighByte
|
||||
// bytes[1] -> LowByte
|
||||
return (short)((int)(bytes[1]) | ((int)(bytes[0]) << 8));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a short (Int16) to a S7 Int byte array (2 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(Int16 value)
|
||||
{
|
||||
byte[] bytes = new byte[2];
|
||||
|
||||
bytes[0] = (byte) (value >> 8 & 0xFF);
|
||||
bytes[1] = (byte)(value & 0xFF);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of short (Int16) to a S7 Int byte array (2 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(Int16[] value)
|
||||
{
|
||||
byte[] bytes = new byte[value.Length * 2];
|
||||
int bytesPos = 0;
|
||||
|
||||
for(int i=0; i< value.Length; i++)
|
||||
{
|
||||
bytes[bytesPos++] = (byte)((value[i] >> 8) & 0xFF);
|
||||
bytes[bytesPos++] = (byte) (value[i] & 0xFF);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 Int to an array of short (Int16)
|
||||
/// </summary>
|
||||
public static Int16[] ToArray(byte[] bytes)
|
||||
{
|
||||
int shortsCount = bytes.Length / 2;
|
||||
|
||||
Int16[] values = new Int16[shortsCount];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < shortsCount; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a C# int value to a C# short value, to be used as word.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static Int16 CWord(int value)
|
||||
{
|
||||
if (value > 32767)
|
||||
{
|
||||
value -= 32768;
|
||||
value = 32768 - value;
|
||||
value *= -1;
|
||||
}
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
57
常用工具集/Utility/Network/S7netplus/Types/LReal.cs
Normal file
57
常用工具集/Utility/Network/S7netplus/Types/LReal.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
public static class LReal
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 LReal (8 bytes) to double
|
||||
/// </summary>
|
||||
public static double FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 8)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 8 bytes.");
|
||||
}
|
||||
var buffer = bytes;
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(buffer);
|
||||
}
|
||||
|
||||
return BitConverter.ToDouble(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a double to S7 LReal (8 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of double to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(double[] value) => TypeHelper.ToByteArray(value, ToByteArray);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 LReal to an array of double
|
||||
/// </summary>
|
||||
public static double[] ToArray(byte[] bytes) => TypeHelper.ToArray(bytes, FromByteArray);
|
||||
|
||||
}
|
||||
}
|
||||
75
常用工具集/Utility/Network/S7netplus/Types/Real.cs
Normal file
75
常用工具集/Utility/Network/S7netplus/Types/Real.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the conversion methods to convert Real from S7 plc to C# double.
|
||||
/// </summary>
|
||||
public static class Real
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a S7 Real (4 bytes) to float
|
||||
/// </summary>
|
||||
public static float FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
|
||||
}
|
||||
|
||||
// sps uses bigending so we have to reverse if platform needs
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
// create deep copy of the array and reverse
|
||||
bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a float to S7 Real (4 bytes)
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(float value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
|
||||
// sps uses bigending so we have to check if platform is same
|
||||
if (!BitConverter.IsLittleEndian) return bytes;
|
||||
|
||||
// create deep copy of the array and reverse
|
||||
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of float to an array of bytes
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(float[] value)
|
||||
{
|
||||
var buffer = new byte[4 * value.Length];
|
||||
var stream = new MemoryStream(buffer);
|
||||
foreach (var val in value)
|
||||
{
|
||||
stream.Write(ToByteArray(val), 0, 4);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of S7 Real to an array of float
|
||||
/// </summary>
|
||||
public static float[] ToArray(byte[] bytes)
|
||||
{
|
||||
var values = new float[bytes.Length / 4];
|
||||
|
||||
int counter = 0;
|
||||
for (int cnt = 0; cnt < bytes.Length / 4; cnt++)
|
||||
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] });
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
80
常用工具集/Utility/Network/S7netplus/Types/S7String.cs
Normal file
80
常用工具集/Utility/Network/S7netplus/Types/S7String.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert from S7 strings to C# strings
|
||||
/// An S7 String has a preceeding 2 byte header containing its capacity and length
|
||||
/// </summary>
|
||||
public static class S7String
|
||||
{
|
||||
private static Encoding stringEncoding = Encoding.ASCII;
|
||||
|
||||
/// <summary>
|
||||
/// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default)
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">StringEncoding must not be null</exception>
|
||||
public static Encoding StringEncoding
|
||||
{
|
||||
get => stringEncoding;
|
||||
set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts S7 bytes to a string
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string FromByteArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length < 2)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short");
|
||||
}
|
||||
|
||||
int size = bytes[0];
|
||||
int length = bytes[1];
|
||||
if (length > size)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / length larger than capacity");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return StringEncoding.GetString(bytes, 2, length);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData,
|
||||
$"Failed to parse {VarType.S7String} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to convert to byte array.</param>
|
||||
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
|
||||
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
|
||||
public static byte[] ToByteArray(string value, int reservedLength)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (reservedLength > 254) throw new ArgumentException($"The maximum string length supported is 254.");
|
||||
|
||||
var bytes = StringEncoding.GetBytes(value);
|
||||
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");
|
||||
|
||||
var buffer = new byte[2 + reservedLength];
|
||||
Array.Copy(bytes, 0, buffer, 2, bytes.Length);
|
||||
buffer[0] = (byte)reservedLength;
|
||||
buffer[1] = (byte)bytes.Length;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
常用工具集/Utility/Network/S7netplus/Types/S7StringAttribute.cs
Normal file
67
常用工具集/Utility/Network/S7netplus/Types/S7StringAttribute.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class S7StringAttribute : Attribute
|
||||
{
|
||||
private readonly S7StringType type;
|
||||
private readonly int reservedLength;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="S7StringAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The string type.</param>
|
||||
/// <param name="reservedLength">Reserved length of the string in characters.</param>
|
||||
/// <exception cref="ArgumentException">Please use a valid value for the string type</exception>
|
||||
public S7StringAttribute(S7StringType type, int reservedLength)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(S7StringType), type))
|
||||
throw new ArgumentException("Please use a valid value for the string type");
|
||||
|
||||
this.type = type;
|
||||
this.reservedLength = reservedLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the string.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The string type.
|
||||
/// </value>
|
||||
public S7StringType Type => type;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reserved length of the string in characters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The reserved length of the string in characters.
|
||||
/// </value>
|
||||
public int ReservedLength => reservedLength;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reserved length in bytes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The reserved length in bytes.
|
||||
/// </value>
|
||||
public int ReservedLengthInBytes => type == S7StringType.S7String ? reservedLength + 2 : (reservedLength * 2) + 4;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// String type.
|
||||
/// </summary>
|
||||
public enum S7StringType
|
||||
{
|
||||
/// <summary>
|
||||
/// ASCII string.
|
||||
/// </summary>
|
||||
S7String = VarType.S7String,
|
||||
|
||||
/// <summary>
|
||||
/// Unicode string.
|
||||
/// </summary>
|
||||
S7WString = VarType.S7WString
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user