工控 modbusTCP 报文
public class ModBusTCP
{
public int ushorts2int(ushort[] res)
{
int high = res[0];
int low = res[1];
int value = (high << 16) + low;
return value;
}
public ushort[] int2ushorts(int res)
{
ushort ust1 = (ushort)(res >> 16);
ushort ust2 = (ushort)res;
return new ushort[] { ust1, ust2 };
}
//声明一个Socket对象
private Socket tcpClient;
//单元标识符
public byte SlaveId { get; set; } = 0x01;
/// <summary>
/// 建立连接
/// </summary>
/// <param name="ip">IP地址 </param>
/// <param name="port">端口号</param>
/// <returns></returns>
public bool Connect(string ip, int port)
{
//实例化Socket对象
tcpClient =new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
try
{
//建立连接 三次握手
tcpClient.Connect(IPAddress.Parse(ip), port);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void DisConnect()
{
// 4次握手
tcpClient?.Close();
}
/// <summary>
/// 读取多个数据
/// </summary>
/// <param name="startAdr"></param>
/// <param name="length"></param>
/// <returns></returns>
public byte[] ReadOutputRegisters(ushort startAdr, ushort length)
{
#region 1 拼接报文
List<byte> SendCommand = new List<byte>();
//ModBusTCP 报文 格式 MBAP + 功能码+ 数据部分
// MBAP 事务标识符(2字节) 协议标识符(2字节) 长度(2字节) 单元标识符(1字节)
// 功能码 (1字节)
// 数据部分 起始寄存器地址(2字节) 寄存器长度(2字节)
//=======================报文拼接===============================
//事务标识符 设置固定 0x00 0x00
// 等价 SendCommand.Add(0x00); SendCommand.Add(0x00);
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 协议标识符 设置固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 长度
SendCommand.AddRange(new byte[] {0x00,0x06});
// 单元标识符 1字节
SendCommand.Add(SlaveId);
//功能码 固定 0x03
SendCommand.Add(0x03);
//数据部分 起始寄存器地址(2字节)
#region ushort 转换字节数组
// % /
//byte Hbyte = (byte)(startAdr / 255);
//byte Lbyte = (byte)(startAdr % 255);
//SendCommand.AddRange(new byte[] { Hbyte,Lbyte});
#endregion
//BitConverter 大小端是颠倒
byte[] temp = BitConverter.GetBytes(startAdr);
SendCommand.AddRange(new byte[] { temp[1], temp[0] });
//数据部分 寄存器长度(2字节)
temp = BitConverter.GetBytes(length);
SendCommand.AddRange(new byte[] { temp[1], temp[0] });
#endregion
#region 2 发送报文
tcpClient.Send(SendCommand.ToArray());
#endregion
#region 3 接收报文
Thread.Sleep(50);
int count = tcpClient.Available;
byte[] buffer= new byte[count];
tcpClient.Receive(buffer,buffer.Length,SocketFlags.None);
#endregion
#region 4 验证报文
//Tx 发送报文: 00 C9 00 00 00 06 01 03 00 00 00 02
//Rx 接收报文: 00 C9 00 00 00 07 01 03 04 01 4D 00 01
// 校验 00 07 01 03 04 这几位
// 00 C9 00 00 00 07 01 03 04 这9位是长度固定的, + 获取寄存器长度length*2 每个寄存器占2字节
if (buffer.Length == 9+length*2)
{
//两位字节转换整数 result_h 高位字节 result_1 地位字节
//result = result_h * 256 + result_1
// 00 07
// 3+length*2 3来源于01 03 04 固定的
if (buffer[4] * 256 + buffer[5]==3+length*2)
{
// 单元标识符 和 功能码
if (buffer[6] == SlaveId && buffer[7] == 0x03)
{
#region 5 解析报文
// 返回的数据字节数,就是 要读取寄存器*2
byte[] result = new byte[length*2];
Array.Copy(buffer,9,result,0,length*2);
return result;
#endregion
}
}
}
return null;
#endregion
}
/// <summary>
/// 写入多数据
/// </summary>
/// <param name="startAdr">开始寄存器地址</param>
/// <param name="length">寄存器长度</param>
/// <param name="dates">具体数据</param>
/// <returns></returns>
public void WriteResisters(ushort startAdr, ushort length,ushort datecount, ushort[] dates)
{
#region 1 拼接报文
List<byte> SendCommand = new List<byte>();
// 事务标识符 2字节 固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 协议标识符 2字节 固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 长度 2字节 7: 单元标识符1字节+功能码1字节+ 开始寄存器地址2字节 寄存器长度2字节 + 字节长度1字节
ushort[] lens=int2ushorts(2*length+7);
//byte[] start1 = BitConverter.GetBytes(lens[0]);
byte[] len = BitConverter.GetBytes(lens[1]);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(len);
}
SendCommand.AddRange(len);
// 单元标识符 1字节 固定 SlaveId
SendCommand.Add(SlaveId);
// 功能码 写入 多个保持寄存器
SendCommand.Add(0x10);
// 数据部分 开始寄存器地址 寄存器长度 字节长度 具体数据
// 开始寄存器地址
byte[] start = BitConverter.GetBytes(startAdr);
//数据值
List<byte> valueBytes = new List<byte>();
byte[] value;
foreach (ushort b in dates)
{
value= BitConverter.GetBytes(b);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(value);
}
valueBytes.AddRange(value);
}
//根据计算机大小端存储方式进行高低字节转换
if (BitConverter.IsLittleEndian)
{
Array.Reverse(start);
}
SendCommand.AddRange(start);
//寄存器长度 2字节
byte[] registLen = BitConverter.GetBytes(length);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(registLen);
}
SendCommand.AddRange(registLen);
//字节长度 1字节
SendCommand.Add((byte)(datecount%255));
//SendCommand.AddRange(new byte[] { 0,1});
SendCommand.AddRange(valueBytes);
#endregion
#region 2 发送报文
Thread.Sleep(50);
//tcpClient.Send(SendCommand.ToArray(),0,SendCommand.Count(),SocketFlags.Partial);
//tcpClient.Send(SendCommand.ToArray());
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 08, 0xFF, 0x05, 00, 05 , 00 ,01, 01, 01 });
//Rx: 000002 - 00 01 00 00 00 09 01 0A 00 05 00 01 01 00 0F
//事务标识符2字节 1F 97
//协议标识符2字节 00 00
//长度 2字节 00 11
//单元标识符 01
//功能码 10
//起始地址 2字节 00 04
//寄存器长度 2字节 00 02 读2个寄存器
//字节数 寄存器长度*2 04
//字节数组N*2字节 00 06 00 09
//1F 97 00 00 00 11 01 10 00 04 00 02 04 00 06 00 09
// 00 01 00 00 00 11 01 10 00 03 00 02 04 00 02 01 09
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 11, 01, 0x10, 00, 00 ,00 ,02 ,04,01,02, 30 ,39 });
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 11, 01, 0x10, 00, 00 ,00 ,02 ,04,01,02, 30 ,39 });
tcpClient.Send(SendCommand.ToArray());
#endregion
}
}