using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Reflection;
using System.Diagnostics;
/*
类使用说明:
1. 在调用协议函数前,需先调用 Connect()函数,以便初始化数据端口。程序退出前也需调用Disconnect()函数。以释放端口。
2. 函数Send()和ReceiveByte()在协议函数中使用,无需在通讯时再次使用。
3. 串口ModbusMasterSerial类的构造函数中完成了协议的相关模式定义,并存入类成员变量中。函数名ModbusMasterSerial(); 需代入参数有串口模式(ASCII OR RTU),端口号,波特率,数据位宽,校验位,停止位,握手位。
4.
*/
namespace ModbusField
{
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Custom Events Args
// Event args for remote endpoint connection
public sealed class ModbusTCPUDPClientConnectedEventArgs : EventArgs
{
#region Global Variables
/// <summary>
/// Remote endpoint
/// </summary>
IPEndPoint remote_ep;
#endregion
#region Parameters
/// <summary>
/// Remote EndPoint
/// </summary>
public IPEndPoint RemoteEndPoint
{
get { return remote_ep; }
}
#endregion
#region Constructor
// Constructor
/// <param name="remote_ep">Remote EndPoint</param>
public ModbusTCPUDPClientConnectedEventArgs(IPEndPoint remote_ep)
{
this.remote_ep = remote_ep;
}
#endregion
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Enumerations
// Connection types
public enum ConnectionType
{
SERIAL_RTU = 0,
SERIAL_ASCII = 1,
TCP_IP = 2,
UDP_IP = 3
}
// Type of modbus serial
public enum ModbusSerialType
{
RTU = 0,
ASCII = 1
}
//Type of device
public enum DeviceType
{
MASTER = 0,
SLAVE = 1
}
// Tabelle del database modbus
public enum ModbusDBTables
{
DISCRETE_INPUTS_REGISTERS = 0,
COIL_REGISTERS = 1,
INPUT_REGISTERS = 2,
HOLDING_REGISTERS = 3
}
// Modbus calling codes
enum ModbusCodes
{
READ_COILS = 0x01,
READ_DISCRETE_INPUTS = 0x02,
READ_HOLDING_REGISTERS = 0x03,
READ_INPUT_REGISTERS = 0x04,
WRITE_SINGLE_COIL = 0x05,
WRITE_SINGLE_REGISTER = 0x06,
READ_EXCEPTION_STATUS = 0x07,
DIAGNOSTIC = 0x08,
GET_COM_EVENT_COUNTER = 0x0B,
GET_COM_EVENT_LOG = 0x0C,
WRITE_MULTIPLE_COILS = 0x0F,
WRITE_MULTIPLE_REGISTERS = 0x10,
REPORT_SLAVE_ID = 0x11,
READ_FILE_RECORD = 0x14,
WRITE_FILE_RECORD = 0x15,
MASK_WRITE_REGISTER = 0x16,
READ_WRITE_MULTIPLE_REGISTERS = 0x17,
READ_FIFO_QUEUE = 0x18,
READ_DEVICE_IDENTIFICATION = 0x2B
}
// Error codes
public enum Errors
{
NO_ERROR = 0,
RX_TIMEOUT = -1,
WRONG_TRANSACTION_ID = -2,
WRONG_PROTOCOL_ID = -3,
WRONG_RESPONSE_UNIT_ID = -4,
WRONG_RESPONSE_FUNCTION_CODE = -5,
WRONG_MESSAGE_LEN = -6,
WRONG_RESPONSE_ADDRESS = -7,
WRONG_RESPONSE_REGISTERS = -8,
WRONG_RESPONSE_VALUE = -9,
WRONG_CRC = -10,
WRONG_LRC = -11,
START_CHAR_NOT_FOUND = -12,
END_CHARS_NOT_FOUND = -13,
WRONG_RESPONSE_AND_MASK = -14,
WRONG_RESPONSE_OR_MASK = -15,
THREAD_BLOCK_REQUEST = -16,
WRONG_WRITE_SINGLE_COIL_VALUE = -17,
TOO_MANY_REGISTERS_REQUESTED = -18,
ZERO_REGISTERS_REQUESTED = -19,
EXCEPTION_ILLEGAL_FUNCTION = -20,
EXCEPTION_ILLEGAL_DATA_ADDRESS = -21,
EXCEPTION_ILLEGAL_DATA_VALUE = -22,
EXCEPTION_SLAVE_DEVICE_FAILURE = -23,
EXCEPTION_ACKNOLEDGE = -24,
EXCEPTION_SLAVE_DEVICE_BUSY = -25,
EXCEPTION_MEMORY_PARITY_ERROR = -26,
EXCEPTION_GATEWAY_PATH_UNAVAILABLE = -27,
EXCEPTION_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = -28,
WRONG_REGISTER_ADDRESS = -29
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Base abstract class
// Base abstract class
public abstract class ModbusBase
{
//--------------------------------------------------------------------------------------------------------------
//常量
protected const ushort PROTOCOL_ID = 0x0000;// Modbus protocol identifier (only TCP and UDP)
const int DEFAULT_RX_TIMEOUT = 6000;// Default rx timeout in milliseconds
protected const int MBAP_HEADER_LEN = 7;// Length in bytes of MBAP header
protected const char ASCII_START_FRAME = ':';// Start frame character (only Modbus serial ASCII)
protected const char ASCII_STOP_FRAME_1ST = '\x0D';// End frame first character (only Modbus serial ASCII)
protected const char ASCII_STOP_FRAME_2ND = '\x0A';// End frame second character (only Modbus serial ASCII)
public const ushort MAX_COILS_IN_READ_MSG = 2000;// Max number of coil registers that can be read
public const ushort MAX_DISCRETE_INPUTS_IN_READ_MSG = 2000;// Max number of discrete inputs registers that can be read
public const ushort MAX_HOLDING_REGISTERS_IN_READ_MSG = 125;// Max number of holding registers that can be read
public const ushort MAX_INPUT_REGISTERS_IN_READ_MSG = 125;// Max number of input registers that can be read
public const ushort MAX_COILS_IN_WRITE_MSG = 1968;// Max number of coil registers that can be written
public const ushort MAX_HOLDING_REGISTERS_IN_WRITE_MSG = 123;// Max number of holding registers that can be written
public const ushort MAX_HOLDING_REGISTERS_TO_READ_IN_READWRITE_MSG = 125;// Max number of holding registers that can be read in a read/write message
public const ushort MAX_HOLDING_REGISTERS_TO_WRITE_IN_READWRITE_MSG = 121;// Max number of holding registers that can be written in a read/write message
//--------------------------------------------------------------------------------------------------------------
//全局变量
protected ConnectionType connection_type; // Connection type(SERIAL_RTU = 0;SERIAL_ASCII = 1;TCP_IP = 2;UDP_IP = 3)
protected DeviceType device_type; // Device type(MASTER = 0;SLAVE = 1)
protected int rx_timeout = DEFAULT_RX_TIMEOUT; // Reception timeout (milliseconds)
protected Errors error;// Modbus errors
protected int interframe_delay;// Delay between two Modbus serial RTU frame (milliseconds)
protected int interchar_delay;// Delay between two Modbus serial RTU characters (milliseconds)
#region Utility functions
//--------------------------------------------------------------------------------------------------------------
// Get or set reception timeout (milliseconds)
public int RxTimeout
{
get { return rx_timeout; }
set { rx_timeout = value; }
}
//--------------------------------------------------------------------------------------------------------------
// Get last error code
public Errors Error
{
get { return error; }
}
//--------------------------------------------------------------------------------------------------------------
//将一个16Bit无符号整型数(大端数据),转换成数组并返回。数据的高8位存储在数组的第一个元素中
// Return an array of bytes from an unsigned 16 bit integer using BIG ENDIAN codification
// <param name="value">Value to convert</param>
// <returns>Bytes array</returns>
protected byte[] GetBytes(ushort value)
{
byte[] array = new byte[2];
array[0] = (byte)(value >> 8);
array[1] = (byte)(value & 0x00FF);
return array;
}
//--------------------------------------------------------------------------------------------------------------
//将输入数组中的每个元素,按高低4位分割,每个4个位的值与十六进制字符‘0’-‘F’对应。将一个数据字节转换成两个字节的字符数据(ASCII码),并返回转换后的数组数据
// Return an array of bytes coded in ASCII according to Modbus specification
// <param name="buffer">Buffer to codify</param>
// <returns>Buffer codified</returns>
// <remarks>
// Example of codification : Byte = 0x5B
// Codified in two chars : 0x35 = '5' and 0x42 = 'B' in ASCII
// The returned vector is exactly the double of the introduced one.
// </remarks>
protected byte[] GetASCIIBytesFromBinaryBuffer(byte[] buffer)
{
List<char> chars = new List<char>();
for (int ii = 0, jj = 0; ii < buffer.Length * 2; ii++)
{
char ch;
byte val = (byte)((ii % 2) == 0 ? buffer[jj] >> 4 : buffer[jj] & 0x0F);
switch (val)
{
default:
case 0x00: ch = '0'; break;
case 0x01: ch = '1'; break;
case 0x02: ch = '2'; break;
case 0x03: ch = '3'; break;
case 0x04: ch = '4'; break;
case 0x05: ch = '5'; break;
case 0x06: ch = '6'; break;
case 0x07: ch = '7'; break;
case 0x08: ch = '8'; break;
case 0x09: ch = '9'; break;
case 0x0A: ch = 'A'; break;
case 0x0B: ch = 'B'; break;
case 0x0C: ch = 'C'; break;
case 0x0D: ch = 'D'; break;
case 0x0E: ch = 'E'; break;
case 0x0F: ch = 'F'; break;
}
chars.Add(ch);
if ((ii % 2) != 0)
jj++;
}
return Encoding.ASCII.GetBytes(chars.ToArray());
}
//--------------------------------------------------------------------------------------------------------------
//将输入数组中的数据(十六进制字符的ASCII码),两个数组元素一组,按序合成一个字节,低4位对应第一个元素(字符)的数值(0x00-0x0F),高4位对应第二个元素。返回转换后的字节型数组
// Return a binary buffer from a byte array codified in ASCII according to Modbus specification
// <param name="buffer">ASCII codified buffer</param>
// <returns>Binary buffer</returns>
// <remarks>
// Example of codification : Char1 = 0x35 ('5') and Char2 = 0x42 ('B')
// Byte decodified : Byte = 0x5B
// The returned vector is exactly the half of the introduced one
// </remarks>
protected byte[] GetBinaryBufferFromASCIIBytes(byte[] buffer)
{
List<byte> ret = new List<byte>();
char[] chars = Encoding.ASCII.GetChars(buffer);
byte bt = 0;
for (int ii = 0; ii < buffer.Length; ii++)
{
byte tmp;
switch (chars[ii])
{
default:
case '0': tmp = 0x00; break;
case '1': tmp = 0x01; break;
case '2': tmp = 0x02; break;
case '3': tmp = 0x03; break;
case '4': tmp = 0x04; break;
case '5': tmp = 0x05; break;
case '6': tmp = 0x06; break;
case '7': tmp = 0x07; break;
case '8': tmp = 0x08; break;
case '9': tmp = 0x09; break;
case 'A': tmp = 0x0A; break;
case 'B': tmp = 0x0B; break;
case 'C': tmp = 0x0C; break;
case 'D': tmp = 0x0D; break;
case 'E': tmp = 0x0E; break;
case 'F': tmp = 0x0F; break;
}
if (ii % 2 != 0)
{
bt |= tmp;
ret.Add(bt);
bt = 0;
}
else
bt = (byte)(tmp << 4);
}
return ret.ToArray();
}
//--------------------------------------------------------------------------------------------------------------
//将两个连续Byte数组的元超级流感,按大端格式合组一个16Bit整型无符号数
// Return a 16 bit unsigned integer from two bytes according to BIG ENDIAN codification
// <param name="value">Source byte array</param>
// <param name="offset">Buffer offset</param>
// <returns>Integer returned</returns>
protected ushort ToUInt16(byte[] value, int offset)
{
return (ushort)((value[offset] << 8) | (value[offset + 1] & 0x00FF));
}
//--------------------------------------------------------------------------------------------------------------
//Bool数组转换为字节数据(输入数组长度减去偏置量后,必须大于等于8)
// Return a byte from an 8-bit boolean array
// <param name="array">Array booleano di 8 bit</param>
// <param name="offset">Array offset</param>
// <returns>Byte returned</returns>
protected byte EightBitToByte(bool[] array, int offset)
{
if (array.Length < 8)
throw new Exception(MethodInfo.GetCurrentMethod().Name + ": The array must be at least 8-bit length!");
byte ret = 0x00;
for (int ii = 0; ii < 8; ii++)
{
switch (array[offset + ii])
{
case true:
ret |= (byte)(1 << ii);
break;
case false:
ret &= (byte)(~(1 << ii));
break;
}
}
return ret;
}
//--------------------------------------------------------------------------------------------------------------
//获取数据帧间延时
// Get delay time between two modbus RTU frame in milliseconds
// <param name="sp">Serial Port</param>
// <returns>Calculated delay (milliseconds)</returns>
protected int GetInterframeDelay(SerialPort sp)
{
int ret_val;
if (sp.BaudRate > 19200)
ret_val = 2; // Fixed value = 1.75ms up rounded
else
{
int nbits = 1 + sp.DataBits;
nbits += sp.Parity == Parity.None ? 0 : 1;
switch (sp.StopBits)
{
case StopBits.One:
nbits += 1;
break;
case StopBits.OnePointFive: // Ceiling
case StopBits.Two:
nbits += 2;
break;
}
ret_val = Convert.ToInt32(Math.Ceiling(1 / (((double)sp.BaudRate / ((double)nbits * 3.5d)) / 1000)));
}
return ret_val;
}
//--------------------------------------------------------------------------------------------------------------
//依据波特率,计算传输字符间延时
// Get max delay time in milliseconds between received chars in modbus RTU trasmission
// <param name="sp">Serial Port</param>
// <returns>Calculated delay (milliseconds)</returns>
protected int GetIntercharDelay(SerialPort sp)
{
int ret_val;
if (sp.BaudRate > 19200)
ret_val = 1; // Fixed value = 0.75 ms up rounded
else
{
int nbits = 1 + sp.DataBits;
nbits += sp.Parity == Parity.None ? 0 : 1;
switch (sp.StopBits)
{
case StopBits.One:
nbits += 1;
break;
case StopBits.OnePointFive: // Ceiling
case StopBits.Two:
nbits += 2;
break;
}
ret_val = Convert.ToInt32(Math.Ceiling(1 / (((double)sp.BaudRate / ((double)nbits * 1.5d)) / 1000)));
}
return ret_val;
}
#endregion
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Base abstract class for Modbus master instances
/// <summary>
/// Base abstract class for Modbus master instances
/// </summary>
public abstract class ModbusMaster : ModbusBase
{
protected bool connected = false;// Remote host connection status
protected List<byte> send_buffer = new List<byte>();// Trasmission buffer
protected List<byte> receive_buffer = new List<byte>();// Reception buffer
protected ushort transaction_id = 0;// Modbus transaction ID (only Modbus TCP/UDP)
protected Stopwatch sw_ch;// Interchar timeout timer
//--------------------------------------------------------------------------------------------------------------------------------------------
//项目中使用到的函数
// Get remote host connection status
public bool Connected
{
get { return connected; }
}
#region Constructor
// Constructor
public ModbusMaster()
{
device_type = DeviceType.MASTER; // Device type
sw_ch = new Stopwatch(); // Initialize interchar timeout timer
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
public abstract void Connect();// Open connection
public abstract void Disconnect();// Close connection
protected abstract void Send();// Function to send trasminission buffer
protected abstract int ReceiveByte();// Function to read a byte from a device,returns a Byte readed or -1 if no data are present
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
//Modbus协议实现功能函数
#region Protocol functions
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Init a new Modbus TCP/UDP message
protected void InitTCPUDPMasterMessage()
{
transaction_id++;// Increase transaction_id
send_buffer.Clear();// Tx buffer emptying
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Build MBAP header for Modbus TCP/UDP
// <param name="dest_address">Destination address</param>
// <param name="message_len">Message length</param>
protected void BuildMBAPHeader(byte dest_address, ushort message_len)
{
send_buffer.InsertRange(0, GetBytes(transaction_id));// Transaction ID (incremented by 1 on each trasmission)
send_buffer.InsertRange(2, GetBytes(PROTOCOL_ID));// Protocol ID (fixed value)
send_buffer.InsertRange(4, GetBytes(message_len));// Message length
send_buffer.Insert(6, dest_address);// Remote unit ID
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
//接收数据,并执行校验,地址检测等操作
// Exec a query to a destination master
// <param name="unit_id">Salve device address</param>
// <param name="msg_len">Message lenght</param>
protected void Query(byte unit_id, ushort msg_len)
{
int rcv;
long tmo;
error = Errors.NO_ERROR; // Set errors to null
switch (connection_type) // Start to build message
{
case ConnectionType.SERIAL_ASCII:
send_buffer.Insert(0, unit_id);// Add destination device address
byte[] lrc = GetASCIIBytesFromBinaryBuffer(new byte[] { LRC.CalcLRC(send_buffer.ToArray(), 0, send_buffer.Count) });// Calc message LCR
send_buffer = GetASCIIBytesFromBinaryBuffer(send_buffer.ToArray()).ToList();// Convert 'send_buffer' from binary to ASCII
send_buffer.AddRange(lrc);// Add LRC at the end of the message
send_buffer.Insert(0, Encoding.ASCII.GetBytes(new char[] { ASCII_START_FRAME }).First());// Insert the start frame char
char[] end_frame = new char[] { ASCII_STOP_FRAME_1ST, ASCII_STOP_FRAME_2ND };// Insert stop frame chars
send_buffer.AddRange(Encoding.ASCII.GetBytes(end_frame));
break;
case ConnectionType.SERIAL_RTU:
send_buffer.Insert(0, unit_id);// Insert 'unit_id' in front of the message
send_buffer.AddRange(BitConverter.GetBytes(CRC16.CalcCRC16(send_buffer.ToArray(), 0, send_buffer.Count)));// Append CRC16
Thread.Sleep(interframe_delay);// Wait for interframe delay
break;
case ConnectionType.UDP_IP:
case ConnectionType.TCP_IP:
BuildMBAPHeader(unit_id, msg_len);
break;
}
Send();// Send trasmission buffer
receive_buffer.Clear();// Start receiving...
Thread.Sleep(50);// Wait
bool done = false;
bool in_ric = false;
Stopwatch sw = new Stopwatch();
sw.Start();
do
{
rcv = ReceiveByte();
if (rcv > -1)
{
if (!in_ric)
in_ric = true;
//检测是否接收到了引导字符
if (connection_type == ConnectionType.SERIAL_ASCII)
{
if ((byte)rcv == Encoding.ASCII.GetBytes(new char[] { ASCII_START_FRAME }).First())
receive_buffer.Clear();
}
receive_buffer.Add((byte)rcv);
}
else if ((rcv == -1) && in_ric)
done = true;
tmo = sw.ElapsedMilliseconds;
} while ((!done) && (rx_timeout > tmo));
sw_ch.Stop();
sw.Stop();
if (tmo >= rx_timeout)
{
error = Errors.RX_TIMEOUT;
return;
}
else
{
int min_frame_length;
switch (connection_type)
{
default:
case ConnectionType.SERIAL_RTU:
min_frame_length = 5;
break;
case ConnectionType.SERIAL_ASCII:
min_frame_length = 11;
break;
case ConnectionType.UDP_IP:
case ConnectionType.TCP_IP:
min_frame_length = 9;
break;
}
if (receive_buffer.Count < min_frame_length)
{
error = Errors.WRONG_MESSAGE_LEN;
return;
}
switch (connection_type)
{
case ConnectionType.SERIAL_ASCII:
// Check and remove start char
if (receive_buffer[0] != send_buffer[0])
{
error = Errors.START_CHAR_NOT_FOUND;
return;
}
receive_buffer.RemoveRange(0, 1);
// Check and remove stop chars
char[] orig_end_frame = new char[] { ASCII_STOP_FRAME_1ST, ASCII_STOP_FRAME_2ND };
char[] rec_end_frame = Encoding.ASCII.GetChars(receive_buffer.GetRange(receive_buffer.Count - 2, 2).ToArray());
if (!orig_end_frame.SequenceEqual(rec_end_frame))
{
error = Errors.END_CHARS_NOT_FOUND;
break;
}
receive_buffer.RemoveRange(receive_buffer.Count - 2, 2);
// Convert receive buffer from ASCII to binary
receive_buffer = GetBinaryBufferFromASCIIBytes(receive_buffer.ToArray()).ToList();
// Check and remove message LRC
byte lrc_calculated = LRC.CalcLRC(receive_buffer.ToArray(), 0, receive_buffer.Count - 1);
byte lrc_received = receive_buffer[receive_buffer.Count - 1];
if (lrc_calculated != lrc_received)
{
error = Errors.WRONG_LRC;
break;
}
receive_buffer.RemoveRange(receive_buffer.Count - 1, 1);
// Remove address byte
receive_buffer.RemoveRange(0, 1);
break;
case ConnectionType.SERIAL_RTU:
// Check message 16-bit CRC
ushort calc_crc = CRC16.CalcCRC16(receive_buffer.ToArray(), 0, receive_buffer.Count - 2);
ushort rec_crc = BitConverter.ToUInt16(receive_buffer.ToArray(), receive_buffer.Count - 2);
if (rec_crc != calc_crc)
{
error = Errors.WRONG_CRC;
return;
}
// Check message consistency
byte addr = receive_buffer[0];
if (addr != send_buffer[0])
{
error = Errors.WRONG_RESPONSE_ADDRESS;
return;
}
// Remove address
receive_buffer.RemoveRange(0, 1);
// Remove CRC
receive_buffer.RemoveRange(receive_buffer.Count - 2, 2);
break;
case ConnectionType.UDP_IP:
case ConnectionType.TCP_IP:
// Check MBAP header
ushort tid = ToUInt16(receive_buffer.ToArray(), 0);
if (tid != transaction_id)
{
error = Errors.WRONG_TRANSACTION_ID;
return;
}
ushort pid = ToUInt16(receive_buffer.ToArray(), 2);
if (pid != PROTOCOL_ID)
{
error = Errors.WRONG_TRANSACTION_ID;
return;
}
ushort len = ToUInt16(receive_buffer.ToArray(), 4);
if ((receive_buffer.Count - MBAP_HEADER_LEN + 1) < len)
{
error = Errors.WRONG_MESSAGE_LEN;
return;
}
byte uid = receive_buffer[6];
if (uid != send_buffer[6])
{
error = Errors.WRONG_RESPONSE_UNIT_ID;
return;
}
// Let only useful bytes in receive buffer
receive_buffer.RemoveRange(0, MBAP_HEADER_LEN);
break;
}
// Controllo eventuali messaggi di errore
if (receive_buffer[0] > 0x80)
{
// E' stato segnalato un errore, controllo l'exception code
switch (receive_buffer[1])
{
case 1:
error = Errors.EXCEPTION_ILLEGAL_FUNCTION;
break;
case 2:
error = Errors.EXCEPTION_ILLEGAL_DATA_ADDRESS;
break;
case 3:
error = Errors.EXCEPTION_ILLEGAL_DATA_VALUE;
break;
case 4:
error = Errors.EXCEPTION_SLAVE_DEVICE_FAILURE;
break;
}
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
//读线圈寄存器
// Read coil registers
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be read</param>
// <param name="len">Number of registers to be read</param>
// <returns>Array of readed registers</returns>
public bool[] ReadCoils(byte unit_id, ushort start_address, ushort len)
{
if (len < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if (len > MAX_COILS_IN_READ_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return null;
}
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.READ_COILS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes(len));
Query(unit_id, msg_len);
if (error != Errors.NO_ERROR)
return null;
BitArray ba = new BitArray(receive_buffer.GetRange(2, receive_buffer.Count - 2).ToArray());
bool[] ret = new bool[ba.Count];
ba.CopyTo(ret, 0);
return ret;
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Read a set of discrete inputs
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be read</param>
// <param name="len">Number of registers to be read</param>
// <returns>Array of readed registers</returns>
public bool[] ReadDiscreteInputs(byte unit_id, ushort start_address, ushort len)
{
if (len < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if (len > MAX_DISCRETE_INPUTS_IN_READ_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return null;
}
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.READ_DISCRETE_INPUTS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes(len));
Query(unit_id, msg_len);
if (error != Errors.NO_ERROR)
return null;
BitArray ba = new BitArray(receive_buffer.GetRange(2, receive_buffer.Count - 2).ToArray());
bool[] ret = new bool[ba.Count];
ba.CopyTo(ret, 0);
return ret;
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Read a set of holding registers
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be read</param>
// <param name="len">Number of registers to be read</param>
// <returns>Array of readed registers</returns>
public ushort[] ReadHoldingRegisters(byte unit_id, ushort start_address, ushort len)
{
if (len < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if (len > MAX_HOLDING_REGISTERS_IN_READ_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return null;
}
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.READ_HOLDING_REGISTERS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes(len));
Query(unit_id, msg_len);
if (error != Errors.NO_ERROR)
return null;
List<ushort> ret = new List<ushort>();
for (int ii = 0; ii < receive_buffer[1]; ii += 2)
ret.Add(ToUInt16(receive_buffer.ToArray(), ii + 2));
return ret.ToArray();
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Read a set of input registers
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be read</param>
// <param name="len">Number of registers to be read</param>
// <returns>Array of readed registers</returns>
public ushort[] ReadInputRegisters(byte unit_id, ushort start_address, ushort len)
{
if (len < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if (len > MAX_INPUT_REGISTERS_IN_READ_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return null;
}
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.READ_INPUT_REGISTERS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes(len));
Query(unit_id, msg_len);
if (error != Errors.NO_ERROR)
return null;
List<ushort> ret = new List<ushort>();
for (int ii = 0; ii < receive_buffer[1]; ii += 2)
ret.Add(ToUInt16(receive_buffer.ToArray(), ii + 2));
return ret.ToArray();
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Write a coil register
// <param name="unit_id">Slave device address</param>
// <param name="address">Register address</param>
// <param name="value">Value to write</param>
public void WriteSingleCoil(byte unit_id, ushort address, bool value)
{
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.WRITE_SINGLE_COIL);
send_buffer.AddRange(GetBytes(address));
send_buffer.AddRange(GetBytes((ushort)(value == true ? 0xFF00 : 0x0000)));
Query(unit_id, msg_len);
if (error == Errors.NO_ERROR)
{
ushort addr = ToUInt16(receive_buffer.ToArray(), 1);
ushort regval = ToUInt16(receive_buffer.ToArray(), 3);
if (addr != address)
{
error = Errors.WRONG_RESPONSE_ADDRESS;
return;
}
if ((regval == 0xFF00) && !value)
{
error = Errors.WRONG_RESPONSE_VALUE;
return;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Write an holding register
// <param name="unit_id">Slave device address</param>
// <param name="address">Register address</param>
// <param name="value">Value to write</param>
public void WriteSingleRegister(byte unit_id, ushort address, ushort value)
{
ushort msg_len = 6;
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.WRITE_SINGLE_REGISTER);
send_buffer.AddRange(GetBytes(address));
send_buffer.AddRange(GetBytes(value));
Query(unit_id, msg_len);
if (error == Errors.NO_ERROR)
{
ushort addr = ToUInt16(receive_buffer.ToArray(), 1);
if (addr != address)
{
error = Errors.WRONG_RESPONSE_ADDRESS;
return;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Write a set of coil registers
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be write</param>
// <param name="values">Array of values to write</param>
public void WriteMultipleCoils(byte unit_id, ushort start_address, bool[] values)
{
if (values == null)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return;
}
if (values.Length < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return;
}
if (values.Length > MAX_COILS_IN_WRITE_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return;
}
byte byte_cnt = (byte)((values.Length / 8) + ((values.Length % 8) == 0 ? 0 : 1));
ushort msg_len = (ushort)(1 + 6 + byte_cnt);
byte[] data = new byte[byte_cnt];
BitArray ba = new BitArray(values);
ba.CopyTo(data, 0);
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.WRITE_MULTIPLE_COILS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes((ushort)values.Length));
send_buffer.Add(byte_cnt);
send_buffer.AddRange(data);
Query(unit_id, msg_len);
if (error == Errors.NO_ERROR)
{
ushort sa = ToUInt16(receive_buffer.ToArray(), 1);
ushort nr = ToUInt16(receive_buffer.ToArray(), 3);
if (sa != start_address)
{
error = Errors.WRONG_RESPONSE_ADDRESS;
return;
}
if (nr != values.Length)
{
error = Errors.WRONG_RESPONSE_REGISTERS;
return;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Write a set of holding registers
// <param name="unit_id">Slave device address</param>
// <param name="start_address">Address of first register to be write</param>
// <param name="values">Array of values to write</param>
public void WriteMultipleRegisters(byte unit_id, ushort start_address, ushort[] values)
{
if (values == null)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return;
}
if (values.Length < 1)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return;
}
if (values.Length > MAX_HOLDING_REGISTERS_IN_WRITE_MSG)
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return;
}
ushort msg_len = (ushort)(7 + (values.Length * 2));
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.WRITE_MULTIPLE_REGISTERS);
send_buffer.AddRange(GetBytes(start_address));
send_buffer.AddRange(GetBytes((ushort)values.Length));
send_buffer.Add((byte)(values.Length * 2));
for (int ii = 0; ii < values.Length; ii++)
send_buffer.AddRange(GetBytes(values[ii]));
Query(unit_id, msg_len);
if (error == Errors.NO_ERROR)
{
ushort sa = ToUInt16(receive_buffer.ToArray(), 1);
ushort nr = ToUInt16(receive_buffer.ToArray(), 3);
if (sa != start_address)
{
error = Errors.WRONG_RESPONSE_ADDRESS;
return;
}
if (nr != values.Length)
{
error = Errors.WRONG_RESPONSE_REGISTERS;
return;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------
// Read and write a set of holding registers in a single shot
// <param name="unit_id">Slave device address</param>
// <param name="read_start_address">Address of first registers to be read</param>
// <param name="read_len">Number of registers to be read</param>
// <param name="write_start_address">Address of first registers to be write</param>
// <param name="values">Array of values to be write</param>
// <returns>Array of readed registers</returns>
// <remarks>
// Write is the first operation, than the read operation
// </remarks>
public ushort[] ReadWriteMultipleRegisters(byte unit_id, ushort read_start_address, ushort read_len, ushort write_start_address, ushort[] values)
{
if (values == null)
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if ((read_len < 1) || (values.Length < 1))
{
error = Errors.ZERO_REGISTERS_REQUESTED;
return null;
}
if ((read_len > MAX_HOLDING_REGISTERS_TO_READ_IN_READWRITE_MSG) || (values.Length > MAX_HOLDING_REGISTERS_TO_WRITE_IN_READWRITE_MSG))
{
error = Errors.TOO_MANY_REGISTERS_REQUESTED;
return null;
}
ushort msg_len = (ushort)(11 + (values.Length * 2));
InitTCPUDPMasterMessage();
send_buffer.Add((byte)ModbusCodes.READ_WRITE_MULTIPLE_REGISTERS);
send_buffer.AddRange(GetBytes(read_start_address));
send_buffer.AddRange(GetBytes(read_len));
send_buffer.AddRange(GetBytes(write_start_address));
send_buffer.AddRange(GetBytes((ushort)values.Length));
send_buffer.Add((byte)(values.Length * 2));
for (int ii = 0; ii < values.Length; ii++)
send_buffer.AddRange(GetBytes(values[ii]));
Query(unit_id, msg_len);
if (error != Errors.NO_ERROR)
return null;
List<ushort> ret = new List<ushort>();
for (int ii = 0; ii < receive_buffer[1]; ii += 2)
ret.Add(ToUInt16(receive_buffer.ToArray(), ii + 2));
return ret.ToArray();
}
#endregion
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Modbus Master TCP Class
/// Modbus Master TCP Class
public class ModbusMasterTCP : ModbusMaster
{
string _remote_host;// Remote hostname or IP address
int _port;// Remote host Modbus TCP listening port
TcpClient tcpc;// TCP Client
NetworkStream ns;// Network stream
#region Constructor
// Constructor
// <param name="remote_host">Remote hostname or IP address</param>
// <param name="port">Remote host Modbus TCP listening port</param>
public ModbusMasterTCP(string remote_host, int port)
{
connection_type = ConnectionType.TCP_IP;// Set device states
_remote_host = remote_host;// Set socket client
_port = port;
}
#endregion
//Connect function
public override void Connect()
{
if (tcpc == null)
tcpc = new TcpClient();
tcpc.Connect(_remote_host, _port);
if (tcpc.Connected)
{
ns = tcpc.GetStream();
connected = true;
}
}
// Disconnect function
public override void Disconnect()
{
ns.Close();
tcpc.Close();
tcpc = null;
connected = false;
}
// Send trasmission buffer
protected override void Send()
{
ns.Write(send_buffer.ToArray(), 0, send_buffer.Count);
}
// Read a byte from network stream
// <returns>Readed byte or <c>-1</c> if there are any bytes</returns>
protected override int ReceiveByte()
{
if (ns.DataAvailable)
return ns.ReadByte();
else
return -1;
}
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region Modbus Serial Master Class
/// <summary>
/// Modbus serial master class
/// </summary>
public sealed class ModbusMasterSerial : ModbusMaster
{
SerialPort sp;// Serial port instance
long char_tmo = 3000;// Char timeout
#region Constructor
// Constructor
// <param name="type">Type of serial protocol</param>
// <param name="port">Serial port name</param>
// <param name="baudrate">Baudrate</param>
// <param name="databits">Data bits</param>
// <param name="parity">Parity</param>
// <param name="stopbits">Stop bits</param>
// <param name="handshake">Handshake</param>
public ModbusMasterSerial(ModbusSerialType type, string port, int baudrate, int databits, Parity parity, StopBits stopbits, Handshake handshake)
{
// Set device states
switch (type)
{
case ModbusSerialType.RTU:
connection_type = ConnectionType.SERIAL_RTU;
break;
case ModbusSerialType.ASCII:
connection_type = ConnectionType.SERIAL_ASCII;
break;
}
// Set serial port instance
sp = new SerialPort(port, baudrate, parity, databits, stopbits);
sp.Handshake = handshake;
// Get interframe delay
interframe_delay = GetInterframeDelay(sp);
// Get interchar delay
interchar_delay = GetIntercharDelay(sp);
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Connect function
public override void Connect()
{
sp.Open();
if (sp.IsOpen)
{
sp.DiscardInBuffer();
sp.DiscardOutBuffer();
connected = true;
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Disconnect function
public override void Disconnect()
{
sp.Close();
connected = false;
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Send trasmission buffer
protected override void Send()
{
sp.Write(send_buffer.ToArray(), 0, send_buffer.Count);
char_tmo = 0;// Reset timeout counter
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Read a byte from stream
// <returns>Readed byte or <c>-1</c> if there are any bytes</returns>
protected override int ReceiveByte()
{
bool done = false;
int ret_val;
// Await 1 char...
if (!sw_ch.IsRunning)
sw_ch.Start();
do
{
if (sp.BytesToRead > 0)
{
ret_val = sp.ReadByte();
done = true;
}
else
ret_val = -1;
} while ((!done) && ((sw_ch.ElapsedMilliseconds - char_tmo) < interchar_delay));
if (done)
char_tmo = sw_ch.ElapsedMilliseconds; // Char received with no errors...reset timeout counter for next char!
return ret_val;
}
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region CRC16 Class
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Static class for CRC16 compute
internal static class CRC16
{
const ushort POLY = 0xA001;// base poly
static ushort[] crc_tab16;// CRC table
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Initialize CRC table
static void InitCRC16Tab()
{
ushort crc, c;
if (crc_tab16 == null)
{
crc_tab16 = new ushort[256];
for (int ii = 0; ii < 256; ii++)
{
crc = 0;
c = (ushort)ii;
for (int jj = 0; jj < 8; jj++)
{
if (((crc ^ c) & 0x0001) == 0x0001)
crc = (ushort)((crc >> 1) ^ POLY);
else
crc = (ushort)(crc >> 1);
c = (ushort)(c >> 1);
}
crc_tab16[ii] = crc;
}
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Update CRC value
// <param name="crc">Actual CRC value</param>
// <param name="bt">Data byte</param>
// <returns>Computed CRC</returns>
static ushort UpdateCRC16(ushort crc, byte bt)
{
ushort tmp, ushort_bt;
ushort_bt = (ushort)(0x00FF & (ushort)bt);
InitCRC16Tab();
tmp = (ushort)(crc ^ ushort_bt);
crc = (ushort)((crc >> 8) ^ crc_tab16[tmp & 0xff]);
return crc;
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
// Calc buffer CRC16
// <param name="buffer">Data Buffer</param>
// <param name="offset">Buffer offset</param>
// <param name="length">Data length</param>
// <returns>Computed CRC</returns>
public static ushort CalcCRC16(byte[] buffer, int offset, int length)
{
ushort crc = 0xFFFF;
for (int ii = 0; ii < length; ii++)
crc = UpdateCRC16(crc, buffer[offset + ii]);
return crc;
}
}
#endregion
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
#region LRC Class
// LRC Class
internal static class LRC
{
// Compute Buffer LRC
// <param name="buffer">Data buffer</param>
// <param name="offset">Buffer offset</param>
// <param name="lenght">Data length</param>
// <returns>Computed LRC</returns>
public static byte CalcLRC(byte[] buffer, int offset, int lenght)
{
byte lrc = 0;
for (int ii = 0; ii < lenght; ii++)
lrc += buffer[ii + offset];
return (byte)(-(sbyte)lrc);
}
}
#endregion
}
结合这段代码分析一下
最新发布