C#使用TCP-S7协议读写西门子PLC(三)
接上篇
C#使用TCP-S7协议读写西门子PLC(二)-CSDN博客
这里我们进行封装读写西门子PLC的S7协议命令以及连接西门子PLC并两次握手
新建部分类文件SiemensS7ProtocolUtil.ReadWrite.cs
主要方法:
连接西门子PLC并发送两次握手。两次握手成功后,才真正连接到PLC
public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000)
生成一个写入字节数据的指令
public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data)
生成一个读取字数据指令头的通用方法【一个字Word占用两个字节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协议,封装读写命令
/// 关键方法:连接PLC并发送两次握手、生成写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协议握手【两次握手】命令,固定.不同型号的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并发送两次握手。两次握手成功后,才真正连接到PLC
/// 使用网络终结点和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;
}
// 重新连接之前,先将旧的数据进行清空
CoreSocket?.Close();
OperateResult<Socket> result = ConnectPlc(endPoint, timeout);
if (result.IsSuccess)
{
// 第一次握手 -> First handshake
OperateResult<byte[]> read_first = SendDataAndWaitResult(result.Content, plcHead1);
if (!read_first.IsSuccess)
{
RecordLogEvent?.Invoke($"第一次握手出错:{read_first.Message}");
return read_first;
}
// 第二次握手 -> Second handshake
OperateResult<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并发送两次握手。两次握手成功后,才真正连接到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 command
OperateResult<byte[]> command = BuildReadCommand(address, length);
if (!command.IsSuccess) return command;
// 核心交互 -> Core Interactions
OperateResult<byte[]> read = SendDataAndWaitResult(command.Content);
if (!read.IsSuccess)
return read;
// 分析结果 -> Analysis results
int 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 = "数据块长度校验失败,请检查是否开启put/get以及关闭db块优化" };
}
}
private OperateResult<byte[]> ReadBitFromPLC(OperateResult<byte, int, ushort> analysis)
{
// 指令生成 -> Build bit read command
OperateResult<byte[]> command = BuildBitReadCommand(analysis);
if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);
// 核心交互 -> Core interactive
OperateResult<byte[]> read = SendDataAndWaitResult(command.Content);
if (!read.IsSuccess) return read;
// 分析结果 -> Analysis read result
int 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, "数据块长度校验失败,请检查是否开启put/get以及关闭db块优化");
}
}
/// <summary>
/// 基础的写入数据的操作支持 -> Operational support for the underlying write data
/// </summary>
/// <param name="entireValue">完整的字节数据 -> 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)
{
// 写入异常 -> WriteError
return new OperateResult(write.Content[write.Content.Length - 1], "写入数据异常,代号为:" + write.Content[write.Content.Length - 1]);
}
else
{
return OperateResult.CreateSuccessResult();
}
}
/// <summary>
/// 生成一个写入字节数据的指令 -> Generate an instruction to write byte data
/// </summary>
/// <param name="analysis">内存区域标识,起始地址*8,DB块编号 </param>
/// <param name="data">原始的字节数据 -> 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;
// 命令 发 -> 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;
// 写入数据块个数 -> Number of data blocks written
_PLCCommand[18] = 0x01;
// 固定,返回数据长度 -> 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;
// 写入数据的个数 -> 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);
// 写入数据的类型 -> Types of writing data
_PLCCommand[27] = analysis.Content1;
// 偏移位置 -> Offset position 因65535*8 即0x07 FF F8 偏移地址占用三个字节:下面注释的三行等价于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;
// 命令 发 -> 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;
// 写入数据块个数 -> 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;
// 写入数据的个数 -> 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);
// 写入数据的类型 -> 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>
/// 生成一个读取字数据指令头的通用方法【一个字Word占用两个字节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">读取数据长度 -> 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("两个参数的个数不一致");
if (length.Length > 19) throw new Exception("读取的数组数量不允许大于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; // 协议标识 -> Protocol identification
_PLCCommand[8] = 0x01; // 命令:发 -> 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; // 参数命令数据总长度 -> Parameter command Data total length
_PLCCommand[13] = (byte)((_PLCCommand.Length - 17) / 256);
_PLCCommand[14] = (byte)((_PLCCommand.Length - 17) % 256);
_PLCCommand[15] = 0x00; // 读取内部数据时为00,读取CPU型号为Data数据长度 -> 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; // 读取数据块个数 -> Number of data blocks read
for (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;
// 访问数据的个数 -> 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);
// 访问数据类型 -> 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>
/// 生成一个位读取数据指令头的通用方法 ->
/// 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;
// 命令:发 -> command to send
_PLCCommand[8] = 0x01;
// 标识序列号
_PLCCommand[9] = 0x00;
_PLCCommand[10] = 0x00;
_PLCCommand[11] = 0x00;
_PLCCommand[12] = 0x01;
// 命令数据总长度 -> 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;
// 读取数据块个数 -> Number of data blocks read
_PLCCommand[18] = 0x01;
//===========================================================================================
// 读取地址的前缀 -> Read the prefix of the address
_PLCCommand[19] = 0x12;
_PLCCommand[20] = 0x0A;
_PLCCommand[21] = 0x10;
// 读取的数据时位 -> Data read-time bit
_PLCCommand[22] = 0x01;
// 访问数据的个数 -> 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);
// 访问数据类型 -> 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);
}
}
}