using HslCommunication.BasicFramework;
using HslCommunication.Serial;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HslCommunication.Core;
using HslCommunication.Core.Address;
namespace HslCommunication.ModBus
{
///
/// Modbus-Rtu通讯协议的类库,多项式码0xA001
///
///
/// 本客户端支持的标准的modbus-rtu协议,自动实现了CRC16的验证,地址格式采用富文本表示形式
///
/// 地址共可以携带3个信息,最完整的表示方式"s=2;x=3;100",对应的modbus报文是 02 03 00 64 00 01 的前四个字节,站号,功能码,起始地址,下面举例
///
/// -
/// 读取线圈
/// ReadCoil("100")表示读取线圈100的值,ReadCoil("s=2;100")表示读取站号为2,线圈地址为100的值
///
/// -
/// 读取离散输入
/// ReadDiscrete("100")表示读取离散输入100的值,ReadDiscrete("s=2;100")表示读取站号为2,离散地址为100的值
///
/// -
/// 读取寄存器
/// ReadInt16("100")表示读取寄存器100的值,ReadInt16("s=2;100")表示读取站号为2,寄存器100的值
///
/// -
/// 读取输入寄存器
/// ReadInt16("x=4;100")表示读取输入寄存器100的值,ReadInt16("s=2;x=4;100")表示读取站号为2,输入寄存器100的值
///
///
/// 对于写入来说也是一致的
///
/// -
/// 写入线圈
/// WriteCoil("100",true)表示读取线圈100的值,WriteCoil("s=2;100",true)表示读取站号为2,线圈地址为100的值
///
/// -
/// 写入寄存器
/// Write("100",(short)123)表示写寄存器100的值123,Write("s=2;100",(short)123)表示写入站号为2,寄存器100的值123
///
///
///
///
///
/// 基本的用法请参照下面的代码示例,初始化部分的代码省略
///
///
public class ModbusRtu : SerialDeviceBase
{
#region Constructor
///
/// 实例化一个Modbus-Rtu协议的客户端对象
///
public ModbusRtu( )
{
ByteTransform = new ReverseWordTransform( );
}
///
/// 指定服务器地址,端口号,客户端自己的站号来初始化
///
/// 客户端自身的站号
public ModbusRtu( byte station = 0x01 )
{
ByteTransform = new ReverseWordTransform( );
this.station = station;
}
#endregion
#region Private Member
private byte station = ModbusInfo.ReadCoil; // 本客户端的站号
private bool isAddressStartWithZero = true; // 线圈值的地址值是否从零开始
#endregion
#region Public Member
///
/// 获取或设置起始的地址是否从0开始,默认为True
///
///
/// 因为有些设备的起始地址是从1开始的,就要设置本属性为True
///
public bool AddressStartWithZero
{
get { return isAddressStartWithZero; }
set { isAddressStartWithZero = value; }
}
///
/// 获取或者重新修改服务器的默认站号信息
///
///
/// 当你调用 ReadCoil("100") 时,对应的站号就是本属性的值,当你调用 ReadCoil("s=2;100") 时,就忽略本属性的值,读写寄存器的时候同理
///
public byte Station
{
get { return station; }
set { station = value; }
}
///
/// 获取或设置数据解析的格式,默认ABCD,可选BADC,CDAB,DCBA格式
///
///
/// 对于Int32,UInt32,float,double,Int64,UInt64类型来说,存在多地址的电脑情况,需要和服务器进行匹配
///
public DataFormat DataFormat
{
get { return ByteTransform.DataFormat; }
set { ByteTransform.DataFormat = value; }
}
///
/// 字符串数据是否按照字来反转
///
///
/// 字符串按照2个字节的排列进行颠倒,根据实际情况进行设置
///
public bool IsStringReverse
{
get { return ByteTransform.IsStringReverse; }
set { ByteTransform.IsStringReverse = value; }
}
#endregion
#region Build Command
///
/// 生成一个读取线圈的指令头
///
/// 地址
/// 长度
/// 携带有命令字节
public OperateResult BuildReadCoilCommand( string address, ushort count )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.ReadCoil );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终tcp指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateReadCoils( station, count ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成一个读取离散信息的指令头
///
/// 地址
/// 长度
/// 携带有命令字节
public OperateResult BuildReadDiscreteCommand( string address, ushort length )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.ReadDiscrete );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终tcp指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateReadDiscrete( station, length ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成一个读取寄存器的指令头
///
/// 地址
/// 长度
/// 携带有命令字节
public OperateResult BuildReadRegisterCommand( string address, ushort length )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.ReadRegister );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateReadRegister( station, length ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成一个读取寄存器的指令头
///
/// 地址
/// 长度
/// 携带有命令字节
private OperateResult BuildReadRegisterCommand( ModbusAddress address, ushort length )
{
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( address.CreateReadRegister( station, length ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成一个写入单线圈的指令头
///
/// 地址
/// 长度
/// 包含结果对象的报文
public OperateResult BuildWriteOneCoilCommand( string address, bool value )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.WriteOneCoil );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateWriteOneCoil( station, value ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成一个写入单个寄存器的报文
///
/// 地址
/// 长度
/// 包含结果对象的报文
public OperateResult BuildWriteOneRegisterCommand( string address, byte[] data )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.WriteOneRegister );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateWriteOneRegister( station, data ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成批量写入单个线圈的报文信息
///
/// 地址
/// 实际数据值
/// 包含结果对象的报文
public OperateResult BuildWriteCoilCommand( string address, bool[] values )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.WriteCoil );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateWriteCoil( station, values ) );
return OperateResult.CreateSuccessResult( buffer );
}
///
/// 生成批量写入寄存器的报文信息
///
/// 地址
/// 实际值
/// 包含结果对象的报文
public OperateResult BuildWriteRegisterCommand( string address, byte[] values )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.WriteRegister );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
// 生成最终rtu指令
byte[] buffer = ModbusInfo.PackCommandToRtu( analysis.Content.CreateWriteRegister( station, values ) );
return OperateResult.CreateSuccessResult( buffer );
}
#endregion
#region Core Interative
///
/// 检查当前的Modbus-Rtu响应是否是正确的
///
/// 发送的数据信息
/// 带是否成功的结果数据
protected virtual OperateResult CheckModbusTcpResponse( byte[] send )
{
// 核心交互
OperateResult result = ReadBase( send );
if (!result.IsSuccess) return result;
// 长度校验
if (result.Content.Length < 5) return new OperateResult( StringResources.Language.ReceiveDataLengthTooShort + "5" );
// 检查crc
if (!SoftCRC16.CheckCRC16( result.Content )) return new OperateResult( StringResources.Language.ModbusCRCCheckFailed +
SoftBasic.ByteToHexString( result.Content, ' ' ) );
// 发生了错误
if ((send[1] + 0x80) == result.Content[1]) return new OperateResult( result.Content[2], ModbusInfo.GetDescriptionByErrorCode( result.Content[2] ) );
if (send[1] != result.Content[1]) return new OperateResult( result.Content[1], $"Receive Command Check Failed: " );
// 移除CRC校验
byte[] buffer = new byte[result.Content.Length - 2];
Array.Copy( result.Content, 0, buffer, 0, buffer.Length );
return OperateResult.CreateSuccessResult( buffer );
}
#endregion
#region Protect Override
///
/// 检查当前接收的字节数据是否正确的
///
/// 从设备反馈回来的数据
/// 是否校验成功
protected override bool CheckReceiveBytes( byte[] rBytes )
{
return SoftCRC16.CheckCRC16( rBytes );
}
#endregion
#region Read Support
///
/// 读取服务器的数据,需要指定不同的功能码
///
/// 指令
/// 地址
/// 长度
/// 带结果信息的字节返回数据
protected OperateResult ReadModBusBase( byte code, string address, ushort length )
{
OperateResult command = null;
switch (code)
{
case ModbusInfo.ReadCoil:
{
command = BuildReadCoilCommand( address, length );
break;
}
case ModbusInfo.ReadDiscrete:
{
command = BuildReadDiscreteCommand( address, length );
break;
}
case ModbusInfo.ReadRegister:
{
command = BuildReadRegisterCommand( address, length );
break;
}
default: command = new OperateResult( ) { Message = StringResources.Language.ModbusTcpFunctionCodeNotSupport }; break;
}
if (!command.IsSuccess) return OperateResult.CreateFailedResult( command );
OperateResult resultBytes = CheckModbusTcpResponse( command.Content );
if (resultBytes.IsSuccess)
{
// 二次数据处理
if (resultBytes.Content?.Length >= 3)
{
byte[] buffer = new byte[resultBytes.Content.Length - 3];
Array.Copy( resultBytes.Content, 3, buffer, 0, buffer.Length );
resultBytes.Content = buffer;
}
}
return resultBytes;
}
///
/// 读取服务器的数据,需要指定不同的功能码
///
/// 地址
/// 长度
/// 带结果信息的字节返回数据
protected OperateResult ReadModBusBase( ModbusAddress address, ushort length )
{
OperateResult command = BuildReadRegisterCommand( address, length );
if (!command.IsSuccess) return OperateResult.CreateFailedResult( command );
OperateResult resultBytes = CheckModbusTcpResponse( command.Content );
if (resultBytes.IsSuccess)
{
// 二次数据处理
if (resultBytes.Content?.Length >= 3)
{
byte[] buffer = new byte[resultBytes.Content.Length - 3];
Array.Copy( resultBytes.Content, 3, buffer, 0, buffer.Length );
resultBytes.Content = buffer;
}
}
return resultBytes;
}
///
/// 读取线圈,需要指定起始地址
///
/// 起始地址,格式为"1234"
/// 带有成功标志的bool对象
public OperateResult ReadCoil( string address )
{
var read = ReadCoil( address, 1 );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
return OperateResult.CreateSuccessResult( read.Content[0] );
}
///
/// 批量的读取线圈,需要指定起始地址,读取长度
///
/// 起始地址,格式为"1234"
/// 读取长度
/// 带有成功标志的bool数组对象
public OperateResult ReadCoil( string address, ushort length )
{
var read = ReadModBusBase( ModbusInfo.ReadCoil, address, length );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
return OperateResult.CreateSuccessResult( SoftBasic.ByteToBoolArray( read.Content, length ) );
}
///
/// 读取输入线圈,需要指定起始地址
///
/// 起始地址,格式为"1234"
/// 带有成功标志的bool对象
public OperateResult ReadDiscrete( string address )
{
var read = ReadDiscrete( address, 1 );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
return OperateResult.CreateSuccessResult( read.Content[0] );
}
///
/// 批量的读取输入点,需要指定起始地址,读取长度
///
/// 起始地址,格式为"1234"
/// 读取长度
/// 带有成功标志的bool数组对象
public OperateResult ReadDiscrete( string address, ushort length )
{
var read = ReadModBusBase( ModbusInfo.ReadDiscrete, address, length );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
return OperateResult.CreateSuccessResult( SoftBasic.ByteToBoolArray( read.Content, length ) );
}
///
/// 从Modbus服务器批量读取寄存器的信息,需要指定起始地址,读取长度
///
/// 起始地址,格式为"1234",或者是带功能码格式x=3;1234
/// 读取的数量
/// 带有成功标志的字节信息
///
/// 此处演示批量读取的示例
///
///
public override OperateResult Read( string address, ushort length )
{
OperateResult analysis = ModbusInfo.AnalysisAddress( address, isAddressStartWithZero, ModbusInfo.ReadRegister );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis );
List lists = new List( );
ushort alreadyFinished = 0;
while (alreadyFinished < length)
{
ushort lengthTmp = (ushort)Math.Min( (length - alreadyFinished), 120 );
OperateResult read = ReadModBusBase( analysis.Content.AddressAdd( alreadyFinished ), lengthTmp );
if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
lists.AddRange( read.Content );
alreadyFinished += lengthTmp;
}
return OperateResult.CreateSuccessResult( lists.ToArray( ) );
}
#endregion
#region Write One Register
///
/// 写一个寄存器数据
///
/// 起始地址
/// 高位
/// 地位
/// 返回写入结果
public OperateResult WriteOneRegister( string address, byte high, byte low )
{
OperateResult command = BuildWriteOneRegisterCommand( address, new byte[] { high, low } );
if (!command.IsSuccess) return command;
return CheckModbusTcpResponse( command.Content );
}
///
/// 写一个寄存器数据
///
/// 起始地址
/// 写入值
/// 返回写入结果
public OperateResult WriteOneRegister( string address, short value )
{
byte[] buffer = BitConverter.GetBytes( value );
return WriteOneRegister( address, buffer[1], buffer[0] );
}
///
/// 写一个寄存器数据
///
/// 起始地址
/// 写入值
/// 返回写入结果
public OperateResult WriteOneRegister( string address, ushort value )
{
byte[] buffer = BitConverter.GetBytes( value );
return WriteOneRegister( address, buffer[1], buffer[0] );
}
#endregion
#region Write Base
///
/// 将数据写入到Modbus的寄存器上去,需要指定起始地址和数据内容
///
/// 起始地址,格式为"1234"
/// 写入的数据,长度根据data的长度来指示
/// 返回写入结果
///
/// 富地址格式,支持携带站号信息,功能码信息,具体参照类的示例代码
///
///
/// 此处演示批量写入的示例
///
///
public override OperateResult Write( string address, byte[] value )
{
// 解析指令
OperateResult command = BuildWriteRegisterCommand( address, value );
if (!command.IsSuccess) return command;
// 核心交互
return CheckModbusTcpResponse( command.Content );
}
#endregion
#region Write Coil
///
/// 写一个线圈信息,指定是否通断
///
/// 起始地址
/// 写入值
/// 返回写入结果
public OperateResult WriteCoil( string address, bool value )
{
// 解析指令
OperateResult command = BuildWriteOneCoilCommand( address, value );
if (!command.IsSuccess) return command;
// 核心交互
return CheckModbusTcpResponse( command.Content );
}
///
/// 批量写入线圈信息,指定是否通断
///
/// 起始地址
/// 写入值
/// 返回写入结果
public OperateResult WriteCoil( string address, bool[] values )
{
// 解析指令
OperateResult command = BuildWriteCoilCommand( address, values );
if (!command.IsSuccess) return command;
// 核心交互
return CheckModbusTcpResponse( command.Content );
}
#endregion
#region Write bool[]
///
/// 向寄存器中写入bool数组,返回值说明,比如你写入M100,那么data[0]对应M100.0
///
/// 要写入的数据地址
/// 要写入的实际数据,长度为8的倍数
/// 返回写入结果
public OperateResult Write( string address, bool[] values )
{
return Write( address, BasicFramework.SoftBasic.BoolArrayToByte( values ) );
}
#endregion
#region Object Override
///
/// 返回表示当前对象的字符串
///
/// 字符串信息
public override string ToString( )
{
return $"ModbusRtu[{PortName}:{BaudRate}]";
}
#endregion
}
}