欧姆龙NJ/NX系列PLC 基于以太网的CIP通讯(EtherNet/IP)

前言

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资源在以太网上传递消息。

二、未连接消息模式下欧姆龙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模拟器通讯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值