前言
CIP (Common Industrial Protocol, 通用工业协议) 是由 ODVA组织提出并维护的具有增强服务的自动化通讯协议。是一种使用生产者-消费者通信模型的与媒体无关的协议,并且是上层的严格面向对象的协议。每个CIP对象都有属性(数据)、服务(命令)、连接和行为(属性值和服务之间的关系)。CIP包括一个广泛的对象库,用于支持通用网络通信、文件传输等网络服务以及模拟和数字输入/输出设备、HMI、运动控制和位置反馈等典型自动化功能。
EtherNet/IP是基于以太网的通讯协议,为用户提供了为工业自动化应用部署标准以太网技术(IEEE 802.3与TCP/IP套件相结合)的网络工具,同时实现了互联网和企业连接,从而随时随地产生数据。EtherNet/IP提供各种拓扑选项,包括具有标准以太网基础设施设备的传统星形,或启用EtherNet/IP设备的设备级环(DLR)。QuickConnectTM功能允许在网络运行时通过使用简短的启动程序快速重新连接设备。
一、 EtherNet/IP协议的两种消息传递模式
1.消息种类
1)显式消息
显式消息连接是点对点的关系,旨在方便两个节点之间的请求-响应事务。这些连接是通用性质的,通常用于两个节点之间的频繁请求。它们可用于访问设备内的任何网络可访问项。显式消息连接利用TCP/IP服务在以太网上传递消息。
2)隐式消息
隐式(I/O数据)连接是为了在规律的时间间隔内移动特定应用程序的I/O数据而建立的。这些连接可以设置为一对一的关系或一对多的关系,以充分利用生产者-消费者组播模型。隐式消息使用UDP/IP资源使以太网上的组播数据传输成为现实。
2. 已连接消息传递(CMM)
用于传递EtherNet/IP上每个节点内预先专用于特定目的的资源,例如频繁的显式消息交易或实时I/O数据。连接资源是使用通过UCMM可用的通信服务保留和配置的。
3. 未连接消息传递(UCMM)
在连接建立过程中用于传递不频繁、低优先级的显式消息。设备中的未连接资源称为未连接消息管理器或UCMM。EtherNet/IP上的未连接消息利用TCP/IP资源在以太网上传递消息。文章来源:https://www.toymoban.com/news/detail-815332.html
二、未连接消息模式下欧姆龙NJ/NX系列PLC通讯数据报文格式解析C#代码实现
1. 报文格式分析
1)头部数据帧解析
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OmronCIP
{
internal class EncapsulationHeader
{
/// <summary>
/// Encapsulation Command
/// </summary>
public ushort Command { get; set; }
/// <summary>
/// Length of the data portion in bytes
/// </summary>
public ushort Length { get; set; }
/// <summary>
/// Session handle
/// </summary>
public uint SessionHandle { get; set; }
/// <summary>
/// Status Code
/// </summary>
public uint Status { get; set; }
private byte[] _senderContext = new byte[8];
/// <summary>
/// Sender Context
/// </summary>
/// <remarks>Information only pertinent to the sender of the encaps command. Must be 8 bytes.</remarks>
public byte[] SenderContext
{
get { return _senderContext; }
set
{
if (value == null)
_senderContext = new byte[8];
if (value.Length >= 8)
{
_senderContext = new byte[8];
Array.Copy(value, _senderContext, 8);
}
if (value.Length < 8)
{
_senderContext = new byte[8];
Array.Copy(value, _senderContext, value.Length);
}
}
}
/// <summary>
/// Options
/// </summary>
public uint Options { get; set; }
/// <summary>
/// Protocol Version
/// </summary>
public ushort ProtocolVersion { get; set; }
/// <summary>
/// Options Flags
/// </summary>
public ushort OptionsFlag { get; set; }
public void Expand(byte[] DataArray, int Offset)
{
if (DataArray.Length < Offset + 24)
throw new IndexOutOfRangeException("Not enough data in the DataArray for the encapsulated packet");
Command = BitConverter.ToUInt16(DataArray, Offset);
Length = BitConverter.ToUInt16(DataArray, Offset + 2);
SessionHandle = BitConverter.ToUInt32(DataArray, Offset + 4);
Status = BitConverter.ToUInt32(DataArray, Offset + 8);
_senderContext = new byte[8];
Buffer.BlockCopy(DataArray, Offset + 12, _senderContext, 0, 8);
Options = BitConverter.ToUInt32(DataArray, Offset + 20);
if (Command == (ushort)EncapsCommand.RegisterSession)
{
ProtocolVersion = BitConverter.ToUInt16(DataArray, Offset + 24);
OptionsFlag = BitConverter.ToUInt16(DataArray, Offset + 26);
}
}
public byte[] ReplyPack()
{
byte[] data = null;
if (Command == (ushort)EncapsCommand.RegisterSession)
{
data = new byte[28];
Buffer.BlockCopy(BitConverter.GetBytes(Command), 0, data, 0, 2);
Buffer.BlockCopy(BitConverter.GetBytes(Length), 0, data, 2, 2);
Buffer.BlockCopy(BitConverter.GetBytes(SessionHandle), 0, data, 4, 4);
Buffer.BlockCopy(BitConverter.GetBytes(Status), 0, data, 8, 4);
Buffer.BlockCopy(_senderContext, 0, data, 12, 8);
Buffer.BlockCopy(BitConverter.GetBytes(Options), 0, data, 20, 4);
Buffer.BlockCopy(BitConverter.GetBytes(ProtocolVersion), 0, data, 24, 2);
Buffer.BlockCopy(BitConverter.GetBytes(OptionsFlag), 0, data, 26, 2);
}
if (Command == (ushort)EncapsCommand.UnRegisterSession || Command == (ushort)EncapsCommand.SendRRData)
{
data = new byte[24];
Buffer.BlockCopy(BitConverter.GetBytes(Command), 0, data, 0, 2);
Buffer.BlockCopy(BitConverter.GetBytes(Length), 0, data, 2, 2);
Buffer.BlockCopy(BitConverter.GetBytes(SessionHandle), 0, data, 4, 4);
Buffer.BlockCopy(BitConverter.GetBytes(Status), 0, data, 8, 4);
Buffer.BlockCopy(_senderContext, 0, data, 12, 8);
Buffer.BlockCopy(BitConverter.GetBytes(Options), 0, data, 20, 4);
}
return data;
}
}
}
2)服务命令与数据内容解析(CommandSpecificData)
// 这里的解析部分代码省略(代码太长)
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmronCIP
{
public class CommandSpecificData
{
/// <summary>
/// Interface handle
/// </summary>
public uint InterfaceHandle { get; set; }
/// <summary>
/// Connect timeout
/// </summary>
public ushort ConnectTimeout { get; set; }
/// <summary>
/// Service timeout
/// </summary>
public ushort ServiceTimeout { get; set; }
/// <summary>
/// Message request size
/// </summary>
public ushort MessageRequestSize { get; set; }
/// <summary>
/// Multiple service packet
/// </summary>
public byte MultipleServicePacket { get; set; }
/// <summary>
/// Multiple tag request path size
/// </summary>
public byte MultipleTagRequestPathSize { get; set; }
/// <summary>
/// Message router request path
/// </summary>
public byte[] MessageRouterRequestPath { get; set; }
/// <summary>
/// Number of Services
/// </summary>
public ushort ServiceNumber { get; set; }
/// <summary>
/// Offset list
/// </summary>
public ushort[] OffsetList { get; set; }
/// <summary>
/// Item count
/// </summary>
public ushort ItemCount { get; set; }
/// <summary>
/// Service type
/// </summary>
public byte ServiceType { get; set; }
/// <summary>
/// Request service code
/// </summary>
public byte RequestService { get; set; }
/// <summary>
/// Address item
/// </summary>
public ushort AddressItem { get; set; }
/// <summary>
/// Address item length
/// </summary>
public ushort AddressItemLen { get; set; }
/// <summary>
/// Unconnected data item
/// </summary>
public ushort DataItem { get; set; }
/// <summary>
/// Unconnected data item length(byte)
/// </summary>
public ushort DataItemLen { get; set; }
/// <summary>
/// Request path length
/// </summary>
public byte RequestPathLen { get; set; }
/// <summary>
/// Request path
/// </summary>
public byte[] RequestPath { get; set; }
/// <summary>
/// CIP message length (word)
/// </summary>
public byte[] CipMsgLens { get; set; }
/// <summary>
/// Tag name byte array
/// </summary>
public string[] TagNames { get; set; }
/// <summary>
/// PLC slot serial number
/// </summary>
public uint Slot { get; set; }
/// <summary>
/// Read / Write data type
/// </summary>
public ushort DataType { get; set; }
/// <summary>
/// Return error code
/// </summary>
public ushort StateCode { get; set; }
/// <summary>
/// Write data type
/// </summary>
public DataTypeCode WriteDataType { get; set; }
/// <summary>
/// Write data quantity
/// </summary>
public ushort WriteDataQuantity { get; set; }
/// <summary>
/// Write value
/// </summary>
public object WriteDataValue { get; set; }
/// <summary>
/// Array length
/// </summary>
public int ArrayLength { get; set; } = 0;
}
}
2)服务命令与数据内容封装(EncapsulationPacket)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OmronCIP
{
public class EncapsulationPacket
{
private EncapsulationHeader encapsulationHeader = new EncapsulationHeader();
public CommandSpecificData CommandSpecificData = new CommandSpecificData();
/// <summary>
/// Encapsulation Command
/// </summary>
public ushort Command { get; set; }
public void Expand(byte[] data)
{
ushort flag = BitConverter.ToUInt16(data, 0);
Command = flag;
if (flag == (ushort)EncapsCommand.RegisterSession)
{
encapsulationHeader.Expand(data, 0);
CommandSpecificData.loadTags();//读写数组时需要加载全局变量列表
encapsulationHeader.SessionHandle = GenerateSessionHandle();
}
if (flag == (ushort)EncapsCommand.UnRegisterSession)
{
encapsulationHeader.Expand(data, 0);
encapsulationHeader.SessionHandle = 0;
}
if (flag == (ushort)EncapsCommand.SendRRData)
{
encapsulationHeader.Expand(data, 0);
CommandSpecificData.Expand(data, 24);
}
}
public byte[] ReplyPack()
{
byte[] packet = null;
byte[] headerPacket = null;
switch (Command)
{
case (ushort)EncapsCommand.RegisterSession://0x65
{
headerPacket = encapsulationHeader.ReplyPack();
packet = new byte[headerPacket.Length];
Buffer.BlockCopy(headerPacket, 0, packet, 0, headerPacket.Length);
break;
}
case (ushort)EncapsCommand.UnRegisterSession://0x66
{
headerPacket = encapsulationHeader.ReplyPack();
packet = new byte[headerPacket.Length];
Buffer.BlockCopy(headerPacket, 0, packet, 0, headerPacket.Length);
break;
}
case (ushort)EncapsCommand.SendRRData://0x6F
{
if (ServiceType.Read == (ServiceType)CommandSpecificData.ServiceType)
{
headerPacket = encapsulationHeader.ReplyPack();
byte[] tempPacket = CommandSpecificData.ReplyPack();
packet = new byte[headerPacket.Length + tempPacket.Length];
encapsulationHeader.Length = BitConverter.ToUInt16(BitConverter.GetBytes(tempPacket.Length), 0);
Buffer.BlockCopy(encapsulationHeader.ReplyPack(), 0, packet, 0, headerPacket.Length);
Buffer.BlockCopy(tempPacket, 0, packet, headerPacket.Length, tempPacket.Length);
}
else if (ServiceType.Write == (ServiceType)CommandSpecificData.ServiceType)
{
headerPacket = encapsulationHeader.ReplyPack();
byte[] tempPacket = CommandSpecificData.ReplyPack();
packet = new byte[headerPacket.Length + tempPacket.Length];
encapsulationHeader.Length = BitConverter.ToUInt16(BitConverter.GetBytes(tempPacket.Length), 0);
Buffer.BlockCopy(encapsulationHeader.ReplyPack(), 0, packet, 0, headerPacket.Length);
Buffer.BlockCopy(tempPacket, 0, packet, headerPacket.Length, tempPacket.Length);
}
break;
}
}
return packet;
}
private uint GenerateSessionHandle()
{
byte[] sessionHandle = new byte[4];
MD5 md5 = MD5.Create();
byte[] temp = md5.ComputeHash(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));
for (int i = 0; i < 4; i++)
sessionHandle[i] = temp[i];
return BitConverter.ToUInt32(sessionHandle, 0);
}
}
}
2. 欧姆龙EtherNet/IP协议的解析C#实现用途说明
以上代码都是小编在做项目的过程中所写,当然,有些代码是基于EEIP这个项目写的,小编在这里感谢EEIP项目的所有贡献者 ,本项目用于创建欧姆龙EtherNet/IP协议的虚拟服务端,HslCommunication这个通讯库可以直接访问,基于此可以访问欧姆龙编程软件Sysmac Studio的模拟器,并在没有实体PLC的情况下完全模拟欧姆龙PLC测试PLC程序的逻辑问题,也就是可以实现EIP协议的解析与协议报文封装。最后,已连接模式下的消息传递报文解析与封装将在下一片文章分享,届时将可以实现与威纶通HMI模拟器通讯。文章来源地址https://www.toymoban.com/news/detail-815332.html
到了这里,关于欧姆龙NJ/NX系列PLC 基于以太网的CIP通讯(EtherNet/IP)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!