using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace HslCommunication.Profinet.AllenBradley { /// /// AB PLC的辅助类,用来辅助生成基本的指令信息 /// public class AllenBradleyHelper { #region Static Service Code /// /// CIP命令中的读取数据的服务 /// public const byte CIP_READ_DATA = 0x4C; /// /// CIP命令中的写数据的服务 /// public const int CIP_WRITE_DATA = 0x4D; /// /// CIP命令中的读并写的数据服务 /// public const int CIP_READ_WRITE_DATA = 0x4E; /// /// CIP命令中的读片段的数据服务 /// public const int CIP_READ_FRAGMENT = 0x52; /// /// CIP命令中的写片段的数据服务 /// public const int CIP_WRITE_FRAGMENT = 0x53; /// /// CIP命令中的对数据读取服务 /// public const int CIP_MULTIREAD_DATA = 0x1000; #endregion #region DataType Code /// /// bool型数据,一个字节长度 /// public const ushort CIP_Type_Bool = 0xC1; /// /// byte型数据,一个字节长度 /// public const ushort CIP_Type_Byte = 0xC2; /// /// 整型,两个字节长度 /// public const ushort CIP_Type_Word = 0xC3; /// /// 长整型,四个字节长度 /// public const ushort CIP_Type_DWord = 0xC4; /// /// 特长整型,8个字节 /// public const ushort CIP_Type_LInt = 0xC5; /// /// 实数数据,四个字节长度 /// public const ushort CIP_Type_Real = 0xCA; /// /// 实数数据,八个字节的长度 /// public const ushort CIP_Type_Double = 0xCB; /// /// 结构体数据,不定长度 /// public const ushort CIP_Type_Struct = 0xCC; /// /// 字符串数据内容 /// public const ushort CIP_Type_String = 0xD0; /// /// 二进制数据内容 /// public const ushort CIP_Type_BitArray = 0xD3; #endregion private static byte[] BuildRequestPathCommand( string address ) { using (MemoryStream ms = new MemoryStream( )) { string[] tagNames = address.Split( new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries ); for (int i = 0; i < tagNames.Length; i++) { string strIndex = string.Empty; int indexFirst = tagNames[i].IndexOf( '[' ); int indexSecond = tagNames[i].IndexOf( ']' ); if (indexFirst > 0 && indexSecond > 0 && indexSecond > indexFirst) { strIndex = tagNames[i].Substring( indexFirst + 1, indexSecond - indexFirst - 1 ); tagNames[i] = tagNames[i].Substring( 0, indexFirst ); } ms.WriteByte( 0x91 ); // 固定 ms.WriteByte( (byte)tagNames[i].Length ); // 节点的长度值 byte[] nameBytes = Encoding.ASCII.GetBytes( tagNames[i] ); ms.Write( nameBytes, 0, nameBytes.Length ); if (nameBytes.Length % 2 == 1) ms.WriteByte( 0x00 ); if (!string.IsNullOrEmpty( strIndex )) { string[] indexs = strIndex.Split( new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries ); for (int j = 0; j < indexs.Length; j++) { int index = Convert.ToInt32( indexs[j] ); if (index < 256) { ms.WriteByte( 0x28 ); ms.WriteByte( (byte)index ); } else { ms.WriteByte( 0x29 ); ms.WriteByte( 0x00 ); ms.WriteByte( BitConverter.GetBytes( index )[0] ); ms.WriteByte( BitConverter.GetBytes( index )[1] ); } } } } return ms.ToArray( ); } } /// /// 将CommandSpecificData的命令,打包成可发送的数据指令 /// /// 实际的命令暗号 /// 当前会话的id /// CommandSpecificData命令 /// 最终可发送的数据命令 public static byte[] PackRequestHeader( ushort command, uint session, byte[] commandSpecificData ) { byte[] buffer = new byte[commandSpecificData.Length + 24]; Array.Copy( commandSpecificData, 0, buffer, 24, commandSpecificData.Length ); BitConverter.GetBytes( command ).CopyTo( buffer, 0 ); BitConverter.GetBytes( session ).CopyTo( buffer, 4 ); BitConverter.GetBytes( (ushort)commandSpecificData.Length ).CopyTo( buffer, 2 ); return buffer; } /// /// 打包生成一个请求读取数据的节点信息,CIP指令信息 /// /// 地址 /// 指代数组的长度 /// CIP的指令信息 public static byte[] PackRequsetRead( string address, int length ) { byte[] buffer = new byte[1024]; int offect = 0; buffer[offect++] = CIP_READ_DATA; offect++; byte[] requestPath = BuildRequestPathCommand( address ); requestPath.CopyTo( buffer, offect ); offect += requestPath.Length; buffer[1] = (byte)((offect - 2) / 2); buffer[offect++] = BitConverter.GetBytes( length )[0]; buffer[offect++] = BitConverter.GetBytes( length )[1]; byte[] data = new byte[offect]; Array.Copy( buffer, 0, data, 0, offect ); return data; } /// /// 打包生成一个请求读取数据片段的节点信息,CIP指令信息 /// /// 节点的名称 /// 起始的索引位置 /// 读取的数据长度,对于short来说,最大是489长度 /// CIP的指令信息 public static byte[] PackRequestReadSegment(string address, int startIndex, int length ) { byte[] buffer = new byte[1024]; int offect = 0; buffer[offect++] = CIP_READ_FRAGMENT; offect++; byte[] requestPath = BuildRequestPathCommand( address + (address.EndsWith( "]" ) ? string.Empty : $"[{startIndex}]") ); requestPath.CopyTo( buffer, offect ); offect += requestPath.Length; buffer[1] = (byte)((offect - 2) / 2); buffer[offect++] = BitConverter.GetBytes( length )[0]; buffer[offect++] = BitConverter.GetBytes( length )[1]; buffer[offect++] = BitConverter.GetBytes( 0 )[0]; buffer[offect++] = BitConverter.GetBytes( 0 )[1]; buffer[offect++] = BitConverter.GetBytes( 0 )[2]; buffer[offect++] = BitConverter.GetBytes( 0 )[3]; byte[] data = new byte[offect]; Array.Copy( buffer, 0, data, 0, offect ); return data; } /// /// 根据指定的数据和类型,生成对应的数据 /// /// 地址信息 /// 数据类型 /// 字节值 /// 如果节点为数组,就是数组长度 /// CIP的指令信息 public static byte[] PackRequestWrite( string address, ushort typeCode, byte[] value, int length = 1 ) { byte[] buffer = new byte[1024]; int offect = 0; buffer[offect++] = CIP_WRITE_DATA; offect++; byte[] requestPath = BuildRequestPathCommand( address ); requestPath.CopyTo( buffer, offect ); offect += requestPath.Length; buffer[1] = (byte)((offect - 2) / 2); buffer[offect++] = BitConverter.GetBytes( typeCode )[0]; // 数据类型 buffer[offect++] = BitConverter.GetBytes( typeCode )[1]; buffer[offect++] = BitConverter.GetBytes( length )[0]; // 固定 buffer[offect++] = BitConverter.GetBytes( length )[1]; value.CopyTo( buffer, offect ); // 数值 offect += value.Length; byte[] data = new byte[offect]; Array.Copy( buffer, 0, data, 0, offect ); return data; } /// /// 生成读取直接节点数据信息的内容 /// /// PLC所在的槽号 /// cip指令内容 /// 最终的指令值 public static byte[] PackCommandSpecificData( byte slot, params byte[][] cips ) { System.IO.MemoryStream ms = new System.IO.MemoryStream( ); ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); ms.WriteByte( 0x01 ); // 超时 ms.WriteByte( 0x00 ); ms.WriteByte( 0x02 ); // 项数 ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); // 连接的地址项 ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); // 长度 ms.WriteByte( 0x00 ); ms.WriteByte( 0xB2 ); // 连接的项数 ms.WriteByte( 0x00 ); ms.WriteByte( 0x00 ); // 后面数据包的长度,等全部生成后在赋值 ms.WriteByte( 0x00 ); ms.WriteByte( 0x52 ); // 服务 ms.WriteByte( 0x02 ); // 请求路径大小 ms.WriteByte( 0x20 ); // 请求路径 ms.WriteByte( 0x06 ); ms.WriteByte( 0x24 ); ms.WriteByte( 0x01 ); ms.WriteByte( 0x0A ); // 超时时间 ms.WriteByte( 0xF0 ); ms.WriteByte( 0x00 ); // CIP指令长度 ms.WriteByte( 0x00 ); int count = 0; if (cips.Length == 1) { ms.Write( cips[0], 0, cips[0].Length ); count += cips[0].Length; } else { ms.WriteByte( 0x0A ); // 固定 ms.WriteByte( 0x02 ); ms.WriteByte( 0x20 ); ms.WriteByte( 0x02 ); ms.WriteByte( 0x24 ); ms.WriteByte( 0x01 ); count += 8; ms.Write( BitConverter.GetBytes( (ushort)cips.Length ), 0, 2 ); // 写入项数 ushort offect = (ushort)(0x02 + 2 * cips.Length); count += 2 * cips.Length; for (int i = 0; i < cips.Length; i++) { ms.Write( BitConverter.GetBytes( offect ), 0, 2 ); offect = (ushort)(offect + cips[i].Length); } for (int i = 0; i < cips.Length; i++) { ms.Write( cips[i], 0, cips[i].Length ); count += cips[i].Length; } } ms.WriteByte( 0x01 ); // Path Size ms.WriteByte( 0x00 ); ms.WriteByte( 0x01 ); // port ms.WriteByte( slot ); byte[] data = ms.ToArray( ); ms.Dispose( ); BitConverter.GetBytes( (short)count ).CopyTo( data, 24 ); data[14] = BitConverter.GetBytes( (short)(data.Length - 16) )[0]; data[15] = BitConverter.GetBytes( (short)(data.Length - 16) )[1]; return data; } /// /// 从PLC反馈的数据解析 /// /// PLC的反馈数据 /// 是否是返回的操作 /// 带有结果标识的最终数据 public static OperateResult ExtractActualData( byte[] response, bool isRead ) { List data = new List( ); int offect = 38; ushort count = BitConverter.ToUInt16( response, 38 ); // 剩余总字节长度,在剩余的字节里,有可能是一项数据,也有可能是多项 if (BitConverter.ToInt32( response, 40 ) == 0x8A) { // 多项数据 offect = 44; int dataCount = BitConverter.ToUInt16( response, offect ); for (int i = 0; i < dataCount; i++) { int offectStart = BitConverter.ToUInt16( response, offect + 2 + i * 2 ) + offect; int offectEnd = (i == dataCount - 1) ? response.Length : (BitConverter.ToUInt16( response, (offect + 4 + i * 2) ) + offect); ushort err = BitConverter.ToUInt16( response, offectStart + 2 ); switch (err) { case 0x04: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley04 }; case 0x05: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley05 }; case 0x06: { // 06的错误码通常是数据长度太多了 // CC是符号返回,D2是符号片段返回 if (response[offect + 2] == 0xD2 || response[offect + 2] == 0xCC) return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley06 }; break; } case 0x0A: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley0A }; case 0x13: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley13 }; case 0x1C: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley1C }; case 0x1E: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley1E }; case 0x26: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley26 }; case 0x00: break; default: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.UnknownError }; } if (isRead) { for (int j = offectStart + 6; j < offectEnd; j++) { data.Add( response[j] ); } } } } else { // 单项数据 byte err = response[offect + 4]; switch (err) { case 0x04: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley04 }; case 0x05: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley05 }; case 0x06: { if (response[offect + 2] == 0xD2 || response[offect + 2] == 0xCC) return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley06 }; break; } case 0x0A: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley0A }; case 0x13: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley13 }; case 0x1C: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley1C }; case 0x1E: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley1E }; case 0x26: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.AllenBradley26 }; case 0x00: break; default: return new OperateResult( ) { ErrorCode = err, Message = StringResources.Language.UnknownError }; } if (isRead) { for (int i = offect + 8; i < offect + 2 + count; i++) { data.Add( response[i] ); } } } return OperateResult.CreateSuccessResult( data.ToArray( ) ); } } }