免費地方門戶網(wǎng)站系統(tǒng)寧波網(wǎng)絡推廣平臺
接上篇
C#使用TCP-S7協(xié)議讀寫西門子PLC(二)-CSDN博客
這里我們進行封裝讀寫西門子PLC的S7協(xié)議命令以及連接西門子PLC并兩次握手
新建部分類文件SiemensS7ProtocolUtil.ReadWrite.cs
主要方法:
連接西門子PLC并發(fā)送兩次握手。兩次握手成功后,才真正連接到PLC
public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000)
生成一個寫入字節(jié)數(shù)據(jù)的指令
public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data)
生成一個讀取字數(shù)據(jù)指令頭的通用方法【一個字Word占用兩個字節(jié)Byte】
public static OperateResult<byte[]> BuildReadCommand(OperateResult<byte, int, ushort>[] address, ushort[] length)
SiemensS7ProtocolUtil.ReadWrite.cs源程序如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace PlcSiemesS7Demo
{/// <summary>/// 西門子S7協(xié)議,封裝讀寫命令/// 關鍵方法:連接PLC并發(fā)送兩次握手、生成寫PLC命令,生成讀PLC命令/// (1).public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000)/// (2).public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data)/// (3).BuildReadCommand(OperateResult<byte, int, ushort>[] address, ushort[] length)/// </summary>public partial class SiemensS7ProtocolUtil{#region 西門子S7協(xié)議握手【兩次握手】命令,固定.不同型號的PLC握手命令有少許不同private byte[] plcHead1 = new byte[22]{0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,0x00,0x01,0x00,0xC0,0x01,0x0A,0xC1,0x02,0x01,0x02,0xC2,0x02,0x01,0x00};private byte[] plcHead2 = new byte[25]{0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,0x01,0x00,0x00,0x04,0x00,0x00,0x08,0x00,0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x01,0xE0};private byte[] plcHead1_200smart = new byte[22]{0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,0x00,0x01,0x00,0xC1,0x02,0x10,0x00,0xC2,0x02,0x03,0x00,0xC0,0x01,0x0A};private byte[] plcHead2_200smart = new byte[25]{0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,0x01,0x00,0x00,0xCC,0xC1,0x00,0x08,0x00,0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x03,0xC0};#endregion/// <summary>/// 連接西門子PLC并發(fā)送兩次握手。兩次握手成功后,才真正連接到PLC/// 使用網(wǎng)絡終結點和PLC型號枚舉進行連接/// </summary>/// <param name="siemensPlcCategory"></param>/// <param name="endPoint"></param>/// <param name="timeout"></param>/// <returns></returns>public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000){isConnected = false;this.SiemensPlcCategory = siemensPlcCategory;switch (siemensPlcCategory){case SiemensPlcCategory.S1200:case SiemensPlcCategory.S1500:plcHead1[21] = 0;break;case SiemensPlcCategory.S300:plcHead1[21] = 2;break;case SiemensPlcCategory.S400:plcHead1[21] = 3;plcHead1[17] = 0x00;break;case SiemensPlcCategory.S200Smart:plcHead1 = plcHead1_200smart;plcHead2 = plcHead2_200smart;break;default:plcHead1[18] = 0;break;}// 重新連接之前,先將舊的數(shù)據(jù)進行清空CoreSocket?.Close();OperateResult<Socket> result = ConnectPlc(endPoint, timeout);if (result.IsSuccess){// 第一次握手 -> First handshakeOperateResult<byte[]> read_first = SendDataAndWaitResult(result.Content, plcHead1);if (!read_first.IsSuccess){RecordLogEvent?.Invoke($"第一次握手出錯:{read_first.Message}");return read_first;}// 第二次握手 -> Second handshakeOperateResult<byte[]> read_second = SendDataAndWaitResult(result.Content, plcHead2);if (!read_second.IsSuccess){RecordLogEvent?.Invoke($"第二次握手出錯:{read_second.Message}");return read_second;}// 返回成功的信號 CoreSocket = result.Content;result.IsSuccess = true;isConnected = true;RecordLogEvent?.Invoke($"連接PLC【{endPoint}】成功并且兩次握手成功");}else{result.Content?.Close();CoreSocket = null;result.IsSuccess = false;}return result;}/// <summary>/// 連接西門子PLC并發(fā)送兩次握手。兩次握手成功后,才真正連接到PLC/// 使用IP地址和端口【默認102】和PLC型號枚舉進行連接/// </summary>/// <param name="siemensPlcCategory"></param>/// <param name="ipAddress"></param>/// <param name="port"></param>/// <param name="timeout"></param>/// <returns></returns>public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, string ipAddress, int port = 102, int timeout = 3000){return ConnectPlcAndHandshake(siemensPlcCategory, new IPEndPoint(IPAddress.Parse(ipAddress), port), timeout);}private OperateResult<byte[]> Read(OperateResult<byte, int, ushort>[] address, ushort[] length){// 構建指令 -> Build read commandOperateResult<byte[]> command = BuildReadCommand(address, length);if (!command.IsSuccess) return command;// 核心交互 -> Core InteractionsOperateResult<byte[]> read = SendDataAndWaitResult(command.Content);if (!read.IsSuccess) return read;// 分析結果 -> Analysis resultsint receiveCount = 0;for (int i = 0; i < length.Length; i++){receiveCount += length[i];}if (read.Content.Length >= 21 && read.Content[20] == length.Length){byte[] buffer = new byte[receiveCount];int kk = 0;int ll = 0;for (int ii = 21; ii < read.Content.Length; ii++){if ((ii + 1) < read.Content.Length){if (read.Content[ii] == 0xFF && read.Content[ii + 1] == 0x04){Array.Copy(read.Content, ii + 4, buffer, ll, length[kk]);ii += length[kk] + 3;ll += length[kk];kk++;}}}return OperateResult.CreateSuccessResult(buffer);}else{return new OperateResult<byte[]>() { ErrorCode = read.ErrorCode, Message = "數(shù)據(jù)塊長度校驗失敗,請檢查是否開啟put/get以及關閉db塊優(yōu)化" };}}private OperateResult<byte[]> ReadBitFromPLC(OperateResult<byte, int, ushort> analysis){// 指令生成 -> Build bit read commandOperateResult<byte[]> command = BuildBitReadCommand(analysis);if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);// 核心交互 -> Core interactiveOperateResult<byte[]> read = SendDataAndWaitResult(command.Content);if (!read.IsSuccess) return read;// 分析結果 -> Analysis read resultint receiveCount = 1;if (read.Content.Length >= 21 && read.Content[20] == 1){byte[] buffer = new byte[receiveCount];if (22 < read.Content.Length){if (read.Content[21] == 0xFF && read.Content[22] == 0x03){buffer[0] = read.Content[25];}}return OperateResult.CreateSuccessResult(buffer);}else{return new OperateResult<byte[]>(read.ErrorCode, "數(shù)據(jù)塊長度校驗失敗,請檢查是否開啟put/get以及關閉db塊優(yōu)化");}}/// <summary>/// 基礎的寫入數(shù)據(jù)的操作支持 -> Operational support for the underlying write data/// </summary>/// <param name="entireValue">完整的字節(jié)數(shù)據(jù) -> Full byte data</param>/// <returns>是否寫入成功的結果對象 -> Whether to write a successful result object</returns>private OperateResult WriteBase(byte[] entireValue){OperateResult<byte[]> write = SendDataAndWaitResult(entireValue);if (!write.IsSuccess) return write;if (write.Content[write.Content.Length - 1] != 0xFF){// 寫入異常 -> WriteErrorreturn new OperateResult(write.Content[write.Content.Length - 1], "寫入數(shù)據(jù)異常,代號為:" + write.Content[write.Content.Length - 1]);}else{return OperateResult.CreateSuccessResult();}}/// <summary>/// 生成一個寫入字節(jié)數(shù)據(jù)的指令 -> Generate an instruction to write byte data/// </summary>/// <param name="analysis">內(nèi)存區(qū)域標識,起始地址*8,DB塊編號 </param>/// <param name="data">原始的字節(jié)數(shù)據(jù) -> Raw byte data</param>/// <returns>包含結果對象的報文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data){byte[] _PLCCommand = new byte[35 + data.Length];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 長度 -> Length_PLCCommand[2] = (byte)((35 + data.Length) / 256);_PLCCommand[3] = (byte)((35 + data.Length) % 256);// 固定 -> Fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令 發(fā) -> command to send_PLCCommand[8] = 0x01;// 標識序列號 -> Identification serial Number_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 固定 -> Fixed_PLCCommand[13] = 0x00;_PLCCommand[14] = 0x0E;// 寫入長度+4 -> Write Length +4_PLCCommand[15] = (byte)((4 + data.Length) / 256);_PLCCommand[16] = (byte)((4 + data.Length) % 256);// 讀寫指令 -> Read and write instructions_PLCCommand[17] = 0x05;// 寫入數(shù)據(jù)塊個數(shù) -> Number of data blocks written_PLCCommand[18] = 0x01;// 固定,返回數(shù)據(jù)長度 -> Fixed, return data length_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 寫入方式,1是按位,2是按字 -> Write mode, 1 is bitwise, 2 is by word_PLCCommand[22] = 0x02;// 寫入數(shù)據(jù)的個數(shù) -> Number of Write Data_PLCCommand[23] = (byte)(data.Length / 256);_PLCCommand[24] = (byte)(data.Length % 256);// DB塊編號,如果訪問的是DB塊的話 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 寫入數(shù)據(jù)的類型 -> Types of writing data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position 因65535*8 即0x07 FF F8 偏移地址占用三個字節(jié):下面注釋的三行等價于BitConverter.GetBytes(Int32) //_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256 % 256);//_PLCCommand[29] = (byte)(analysis.Content2 / 256 % 256);//_PLCCommand[30] = (byte)(analysis.Content2 % 256);byte[] offsetAddress = BitConverter.GetBytes(analysis.Content2);_PLCCommand[28] = offsetAddress[2];_PLCCommand[29] = offsetAddress[1];_PLCCommand[30] = offsetAddress[0];// 按字寫入 -> Write by Word_PLCCommand[31] = 0x00;_PLCCommand[32] = 0x04;// 按位計算的長度 -> The length of the bitwise calculation_PLCCommand[33] = (byte)(data.Length * 8 / 256);_PLCCommand[34] = (byte)(data.Length * 8 % 256);data.CopyTo(_PLCCommand, 35);return OperateResult.CreateSuccessResult(_PLCCommand);}public static OperateResult<byte[]> BuildWriteBitCommand(OperateResult<byte, int, ushort> analysis, bool data){//OperateResult<byte, int, ushort> analysis = AnalysisAddress(address);if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(analysis);byte[] buffer = new byte[1];buffer[0] = data ? (byte)0x01 : (byte)0x00;byte[] _PLCCommand = new byte[35 + buffer.Length];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 長度 -> length_PLCCommand[2] = (byte)((35 + buffer.Length) / 256);_PLCCommand[3] = (byte)((35 + buffer.Length) % 256);// 固定 -> fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令 發(fā) -> command to send_PLCCommand[8] = 0x01;// 標識序列號 -> Identification serial Number_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 固定 -> fixed_PLCCommand[13] = 0x00;_PLCCommand[14] = 0x0E;// 寫入長度+4 -> Write Length +4_PLCCommand[15] = (byte)((4 + buffer.Length) / 256);_PLCCommand[16] = (byte)((4 + buffer.Length) % 256);// 命令起始符 -> Command start character_PLCCommand[17] = 0x05;// 寫入數(shù)據(jù)塊個數(shù) -> Number of data blocks written_PLCCommand[18] = 0x01;_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 寫入方式,1是按位,2是按字 -> Write mode, 1 is bitwise, 2 is by word_PLCCommand[22] = 0x01;// 寫入數(shù)據(jù)的個數(shù) -> Number of Write Data_PLCCommand[23] = (byte)(buffer.Length / 256);_PLCCommand[24] = (byte)(buffer.Length % 256);// DB塊編號,如果訪問的是DB塊的話 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 寫入數(shù)據(jù)的類型 -> Types of writing data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256);_PLCCommand[29] = (byte)(analysis.Content2 / 256);_PLCCommand[30] = (byte)(analysis.Content2 % 256);// 按位寫入 -> Bitwise Write_PLCCommand[31] = 0x00;_PLCCommand[32] = 0x03;// 按位計算的長度 -> The length of the bitwise calculation_PLCCommand[33] = (byte)(buffer.Length / 256);_PLCCommand[34] = (byte)(buffer.Length % 256);buffer.CopyTo(_PLCCommand, 35);return OperateResult.CreateSuccessResult(_PLCCommand);}/// <summary>/// 解析地址,返回元組【地址類型,起始地址,DB塊編號】/// </summary>/// <param name="plcRegisterCategory">寄存器類型</param>/// <param name="offsetAddress">偏移量,偏移地址</param>/// <param name="dbNumber">DB塊號</param>/// <param name="bitIndex">位索引0~7</param>/// <returns></returns>private static OperateResult<byte, int, ushort> AnalysisAddress(PlcRegisterCategory plcRegisterCategory, ushort offsetAddress, ushort dbNumber = 0, int bitIndex = 0){//地址類型,起始地址,DB塊編號OperateResult<byte, int, ushort> result = new OperateResult<byte, int, ushort>();result.Content3 = 0;switch (plcRegisterCategory){case PlcRegisterCategory.DB:result.Content1 = 0x84;result.Content2 = offsetAddress * 8 + bitIndex;result.Content3 = dbNumber;break;case PlcRegisterCategory.M:result.Content1 = 0x83;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.Input:result.Content1 = 0x81;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.Quit:result.Content1 = 0x82;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.T:result.Content1 = 0x1D;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.C:result.Content1 = 0x1C;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.V:result.Content1 = 0x84;result.Content2 = offsetAddress * 8 + bitIndex;result.Content3 = 1;break;default:result.Message = "輸入的類型不支持,請重新輸入";result.Content1 = 0;result.Content2 = 0;result.Content3 = 0;return result;}result.IsSuccess = true;return result;}/// <summary>/// 生成一個讀取字數(shù)據(jù)指令頭的通用方法【一個字Word占用兩個字節(jié)Byte】 ->/// A general method for generating a command header to read a Word data/// </summary>/// <param name="address">起始地址,例如M100,I0,Q0,DB2.100 ->/// Start address, such as M100,I0,Q0,DB2.100</param>/// <param name="length">讀取數(shù)據(jù)長度 -> Read Data length</param>/// <returns>包含結果對象的報文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildReadCommand(OperateResult<byte, int, ushort>[] address, ushort[] length){if (address == null) throw new NullReferenceException("address");if (length == null) throw new NullReferenceException("count");if (address.Length != length.Length) throw new Exception("兩個參數(shù)的個數(shù)不一致");if (length.Length > 19) throw new Exception("讀取的數(shù)組數(shù)量不允許大于19");int readCount = length.Length;byte[] _PLCCommand = new byte[19 + readCount * 12];// ======================================================================================_PLCCommand[0] = 0x03; // 報文頭 -> Head_PLCCommand[1] = 0x00;_PLCCommand[2] = (byte)(_PLCCommand.Length / 256); // 長度 -> Length_PLCCommand[3] = (byte)(_PLCCommand.Length % 256);_PLCCommand[4] = 0x02; // 固定 -> Fixed_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32; // 協(xié)議標識 -> Protocol identification_PLCCommand[8] = 0x01; // 命令:發(fā) -> Command: Send_PLCCommand[9] = 0x00; // 冗余標識(保留)-> redundancy identification (reserved): 0x0000;_PLCCommand[10] = 0x00; // protocol data unit reference; it’s increased by request event;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01; // 參數(shù)命令數(shù)據(jù)總長度 -> Parameter command Data total length_PLCCommand[13] = (byte)((_PLCCommand.Length - 17) / 256);_PLCCommand[14] = (byte)((_PLCCommand.Length - 17) % 256);_PLCCommand[15] = 0x00; // 讀取內(nèi)部數(shù)據(jù)時為00,讀取CPU型號為Data數(shù)據(jù)長度 -> Read internal data is 00, read CPU model is data length_PLCCommand[16] = 0x00;// =====================================================================================_PLCCommand[17] = 0x04; // 讀寫指令,04讀,05寫 -> Read-write instruction, 04 read, 05 Write_PLCCommand[18] = (byte)readCount; // 讀取數(shù)據(jù)塊個數(shù) -> Number of data blocks readfor (int ii = 0; ii < readCount; ii++){//===========================================================================================// 指定有效值類型 -> Specify a valid value type_PLCCommand[19 + ii * 12] = 0x12;// 接下來本次地址訪問長度 -> The next time the address access length_PLCCommand[20 + ii * 12] = 0x0A;// 語法標記,ANY -> Syntax tag, any_PLCCommand[21 + ii * 12] = 0x10;// 按字為單位 -> by word_PLCCommand[22 + ii * 12] = 0x02;// 訪問數(shù)據(jù)的個數(shù) -> Number of Access data_PLCCommand[23 + ii * 12] = (byte)(length[ii] / 256);_PLCCommand[24 + ii * 12] = (byte)(length[ii] % 256);// DB塊編號,如果訪問的是DB塊的話 -> DB block number, if you are accessing a DB block_PLCCommand[25 + ii * 12] = (byte)(address[ii].Content3 / 256);_PLCCommand[26 + ii * 12] = (byte)(address[ii].Content3 % 256);// 訪問數(shù)據(jù)類型 -> Accessing data types_PLCCommand[27 + ii * 12] = address[ii].Content1;// 偏移位置 -> Offset position_PLCCommand[28 + ii * 12] = (byte)(address[ii].Content2 / 256 / 256 % 256);_PLCCommand[29 + ii * 12] = (byte)(address[ii].Content2 / 256 % 256);_PLCCommand[30 + ii * 12] = (byte)(address[ii].Content2 % 256);}return OperateResult.CreateSuccessResult(_PLCCommand);}/// <summary>/// 生成一個位讀取數(shù)據(jù)指令頭的通用方法 ->/// A general method for generating a bit-read-Data instruction header/// </summary>/// <param name="address">起始地址,例如M100.0,I0.1,Q0.1,DB2.100.2 ->/// Start address, such as M100.0,I0.1,Q0.1,DB2.100.2/// </param>/// <returns>包含結果對象的報文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildBitReadCommand(OperateResult<byte, int, ushort> analysis){//OperateResult<byte, int, ushort> analysis = AnalysisAddress(address);if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(analysis);byte[] _PLCCommand = new byte[31];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 長度 -> Length_PLCCommand[2] = (byte)(_PLCCommand.Length / 256);_PLCCommand[3] = (byte)(_PLCCommand.Length % 256);// 固定 -> Fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令:發(fā) -> command to send_PLCCommand[8] = 0x01;// 標識序列號_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 命令數(shù)據(jù)總長度 -> Identification serial Number_PLCCommand[13] = (byte)((_PLCCommand.Length - 17) / 256);_PLCCommand[14] = (byte)((_PLCCommand.Length - 17) % 256);_PLCCommand[15] = 0x00;_PLCCommand[16] = 0x00;// 命令起始符 -> Command start character_PLCCommand[17] = 0x04;// 讀取數(shù)據(jù)塊個數(shù) -> Number of data blocks read_PLCCommand[18] = 0x01;//===========================================================================================// 讀取地址的前綴 -> Read the prefix of the address_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 讀取的數(shù)據(jù)時位 -> Data read-time bit_PLCCommand[22] = 0x01;// 訪問數(shù)據(jù)的個數(shù) -> Number of Access data_PLCCommand[23] = 0x00;_PLCCommand[24] = 0x01;// DB塊編號,如果訪問的是DB塊的話 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 訪問數(shù)據(jù)類型 -> Types of reading data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256 % 256);_PLCCommand[29] = (byte)(analysis.Content2 / 256 % 256);_PLCCommand[30] = (byte)(analysis.Content2 % 256);return OperateResult.CreateSuccessResult(_PLCCommand);}}
}