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 } }