588 lines
21 KiB
C#
588 lines
21 KiB
C#
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
|
||
}
|
||
}
|