|
|
using HslCommunication;
|
|
|
using HslCommunication.ModBus;
|
|
|
using HybirdFrameworkCore.Const;
|
|
|
using HybirdFrameworkCore.Utils;
|
|
|
using log4net;
|
|
|
|
|
|
namespace HybirdFrameworkDriver.ModbusTcpMaster;
|
|
|
|
|
|
public class ModbusTcpMaster
|
|
|
{
|
|
|
public delegate void MyReadAction(ModbusTcpMaster str);
|
|
|
|
|
|
private static readonly ILog Log = LogManager.GetLogger(typeof(ModbusTcpMaster));
|
|
|
|
|
|
private ModbusTcpNet? ModbusTcpNet;
|
|
|
|
|
|
public string Ip { get; set; } = "127.0.0.1";
|
|
|
|
|
|
public int Port { get; set; } = 502;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 字节序
|
|
|
/// </summary>
|
|
|
public EndingConst.ByteSeq ByteSeq { get; set; } = EndingConst.ByteSeq.AB;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 字序
|
|
|
/// </summary>
|
|
|
public EndingConst.WordSeq WordSeq { get; set; } = EndingConst.WordSeq.CD;
|
|
|
|
|
|
public int Duration { get; set; } = 1000;
|
|
|
public bool Connected { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 自动重连开关
|
|
|
/// </summary>
|
|
|
public bool AutoReConnect { get; set; }
|
|
|
|
|
|
public string connectId { get; set; }
|
|
|
|
|
|
public MyReadAction? ReadAction { get; set; }
|
|
|
|
|
|
private bool StopFlag { get; set; }
|
|
|
|
|
|
private ILog GetLog()
|
|
|
{
|
|
|
return Log;
|
|
|
}
|
|
|
|
|
|
public bool Connect()
|
|
|
{
|
|
|
Thread.Sleep(Duration);
|
|
|
GetLog().Info($"begin to connect {Ip}:{Port}");
|
|
|
try
|
|
|
{
|
|
|
if (ModbusTcpNet == null)
|
|
|
{
|
|
|
ModbusTcpNet = new ModbusTcpNet(Ip, Port);
|
|
|
var result = ModbusTcpNet.ConnectServer();
|
|
|
connectId = ModbusTcpNet.ConnectionId;
|
|
|
if (result.IsSuccess)
|
|
|
{
|
|
|
Connected = true;
|
|
|
GetLog().Info($"connect {Ip}:{Port} success");
|
|
|
StartListen();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
GetLog().Info($"connect {Ip}:{Port} failed {result.Message}");
|
|
|
if (AutoReConnect)
|
|
|
{
|
|
|
return Connect();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
GetLog().Error($"connect {Ip}:{Port} exception {e.Message}");
|
|
|
if (AutoReConnect)
|
|
|
{
|
|
|
return Connect();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return Connected;
|
|
|
}
|
|
|
|
|
|
public void StartListen()
|
|
|
{
|
|
|
StopListen();
|
|
|
StopFlag = false;
|
|
|
var readThread = new Thread(ReadFunc)
|
|
|
{
|
|
|
IsBackground = true,
|
|
|
Name = $"{Ip}:{Port}-reader-thread"
|
|
|
};
|
|
|
readThread.Start();
|
|
|
}
|
|
|
|
|
|
public void StopListen()
|
|
|
{
|
|
|
StopFlag = true;
|
|
|
Thread.Sleep(500);
|
|
|
}
|
|
|
|
|
|
private void ReadFunc()
|
|
|
{
|
|
|
if (ReadAction != null)
|
|
|
while (!StopFlag)
|
|
|
try
|
|
|
{
|
|
|
ReadAction(this);
|
|
|
Thread.Sleep(Duration);
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
Log.Error(e);
|
|
|
}
|
|
|
|
|
|
GetLog().Info("stop listen");
|
|
|
}
|
|
|
|
|
|
public byte[]? ReadRegister(int registerNo, int start, int length)
|
|
|
{
|
|
|
start = start % 16 == 0 ? start / 16 : start / 16 + 1;
|
|
|
length = length % 8 == 0 ? length / 8 : length / 8 + 1;
|
|
|
OperateResult<byte[]> result = ModbusTcpNet.Read("x=3;" + (registerNo + start), (ushort)length);
|
|
|
|
|
|
if (result.IsSuccess) return result.Content;
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
public byte[]? BatchReadHolderRegister(int registerNo, int length)
|
|
|
{
|
|
|
OperateResult<byte[]> result = ModbusTcpNet.Read("x=3;" + registerNo, (ushort)length);
|
|
|
if (result.IsSuccess) return result.Content;
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
public byte[]? BatchReadInputRegister(int registerNo, int length)
|
|
|
{
|
|
|
OperateResult<byte[]> result = ModbusTcpNet.Read("x=4;" + registerNo, (ushort)length);
|
|
|
if (result.IsSuccess) return result.Content;
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 读取线圈,需要指定起始地址,如果富文本地址不指定,默认使用的功能码是 0x01<br />
|
|
|
/// To read the coil, you need to specify the start address. If the rich text address is not specified, the default function code is 0x01.
|
|
|
/// </summary>
|
|
|
/// <param name="address">起始地址,格式为"1234"</param>
|
|
|
/// <returns>带有成功标志的bool对象</returns>
|
|
|
public OperateResult<bool> ReadCoil(string address) => ModbusTcpNet.ReadBool(address);
|
|
|
|
|
|
/// <summary>
|
|
|
/// 批量的读取线圈,需要指定起始地址,读取长度,如果富文本地址不指定,默认使用的功能码是 0x01<br />
|
|
|
/// For batch reading coils, you need to specify the start address and read length. If the rich text address is not specified, the default function code is 0x01.
|
|
|
/// </summary>
|
|
|
/// <param name="address">起始地址,格式为"1234"</param>
|
|
|
/// <param name="length">读取长度</param>
|
|
|
/// <returns>带有成功标志的bool数组对象</returns>
|
|
|
public OperateResult<bool[]> ReadCoil(string address, ushort length)
|
|
|
{
|
|
|
return ModbusTcpNet.ReadBool(address, length);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 读取输入线圈,需要指定起始地址,如果富文本地址不指定,默认使用的功能码是 0x02<br />
|
|
|
/// To read the input coil, you need to specify the start address. If the rich text address is not specified, the default function code is 0x02.
|
|
|
/// </summary>
|
|
|
/// <param name="address">起始地址,格式为"1234"</param>
|
|
|
/// <returns>带有成功标志的bool对象</returns>
|
|
|
public OperateResult<bool> ReadDiscrete(string address)
|
|
|
{
|
|
|
return ModbusTcpNet.ReadDiscrete(address);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 批量的读取输入点,需要指定起始地址,读取长度,如果富文本地址不指定,默认使用的功能码是 0x02<br />
|
|
|
/// To read input points in batches, you need to specify the start address and read length. If the rich text address is not specified, the default function code is 0x02
|
|
|
/// </summary>
|
|
|
/// <param name="address">起始地址,格式为"1234"</param>
|
|
|
/// <param name="length">读取长度</param>
|
|
|
/// <returns>带有成功标志的bool数组对象</returns>
|
|
|
public OperateResult<bool[]> ReadDiscrete(string address, ushort length)
|
|
|
{
|
|
|
return ModbusTcpNet.ReadDiscrete(address, length);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 将数据写入到Modbus的寄存器上去,需要指定起始地址和数据内容,如果富文本地址不指定,默认使用的功能码是 0x10<br />
|
|
|
/// To write data to Modbus registers, you need to specify the start address and data content. If the rich text address is not specified, the default function code is 0x10
|
|
|
/// </summary>
|
|
|
/// <param name="address">起始地址,比如"100","x=4;100","s=1;100","s=1;x=4;100"</param>
|
|
|
/// <param name="value">写入的数据,长度根据data的长度来指示</param>
|
|
|
/// <returns>返回写入结果</returns>
|
|
|
/// <remarks>富地址格式,支持携带站号信息,功能码信息,具体参照类的示例代码</remarks>
|
|
|
/// <example>
|
|
|
/// 此处演示批量写入的示例
|
|
|
/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Modbus\Modbus.cs" region="WriteExample1" title="Write示例" />
|
|
|
/// </example>
|
|
|
public OperateResult Write(string address, byte[] value)
|
|
|
{
|
|
|
return ModbusTcpNet.Write(address, value);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool WriteValue<T>(ModbusProperty<T> property)
|
|
|
{
|
|
|
var value = property.Value;
|
|
|
if (value == null)
|
|
|
{
|
|
|
GetLog().Warn($"write property{property.RegisterNo} null value");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var result = false;
|
|
|
var dataType = property.Type;
|
|
|
var start = property.Start;
|
|
|
var length = property.Length;
|
|
|
var registerNo = property.RegisterNo - 40000;
|
|
|
var setValue = BitUtls.Value2Bytes(value, property.Scale, property.Offset);
|
|
|
OperateResult operateResult;
|
|
|
|
|
|
switch (dataType)
|
|
|
{
|
|
|
case ModbusDataType.Register:
|
|
|
if (setValue.Length < length * 2)
|
|
|
{
|
|
|
//byte 需要读取寄存器中的内容
|
|
|
if (typeof(T) == typeof(byte))
|
|
|
{
|
|
|
OperateResult<byte[]> readResultRegister = ModbusTcpNet.Read("x=3;" + registerNo, 1);
|
|
|
if (readResultRegister.IsSuccess)
|
|
|
{
|
|
|
if (ByteSeq == EndingConst.ByteSeq.AB)
|
|
|
{
|
|
|
readResultRegister.Content[1] = setValue[0];
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
readResultRegister.Content[0] = setValue[0];
|
|
|
}
|
|
|
|
|
|
operateResult = ModbusTcpNet.Write("x=16;" + registerNo, readResultRegister.Content);
|
|
|
result = operateResult.IsSuccess;
|
|
|
}
|
|
|
}
|
|
|
//其他类型 String 占用几个寄存器 直接补0
|
|
|
else
|
|
|
{
|
|
|
var preWriteCont = BitUtls.ProcessEnding(setValue, ByteSeq, WordSeq);
|
|
|
|
|
|
operateResult = ModbusTcpNet.Write("x=16;" + registerNo, preWriteCont);
|
|
|
result = operateResult.IsSuccess;
|
|
|
}
|
|
|
}
|
|
|
else if (setValue.Length == length * 2)
|
|
|
{
|
|
|
var preWriteCont = BitUtls.ProcessEnding(setValue, ByteSeq, WordSeq);
|
|
|
operateResult = ModbusTcpNet.Write("x=16;" + registerNo, preWriteCont);
|
|
|
result = operateResult.IsSuccess;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
case ModbusDataType.Bit:
|
|
|
result = WriteRegisterOneBit(registerNo, start, Convert.ToBoolean(value));
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 写寄存器中的一个bit
|
|
|
/// </summary>
|
|
|
/// <param name="addr"></param>
|
|
|
/// <param name="location"></param>
|
|
|
/// <param name="boolResult"></param>
|
|
|
/// <returns></returns>
|
|
|
public bool WriteRegisterOneBit(int addr, int location, bool boolResult)
|
|
|
{
|
|
|
if (!Connected) return false;
|
|
|
var result = false;
|
|
|
ushort registerValue1 = 0x1234;
|
|
|
ushort mask = 0x1234;
|
|
|
OperateResult<byte[]> readResult = ModbusTcpNet.Read("x=3;" + addr, 1);
|
|
|
if (readResult.IsSuccess)
|
|
|
if (location >= 0 && location <= 15)
|
|
|
{
|
|
|
registerValue1 = ModbusTcpNet.ByteTransform.TransUInt16(readResult.Content, 0);
|
|
|
if (boolResult)
|
|
|
{
|
|
|
mask = (ushort)(1 << location);
|
|
|
registerValue1 |= mask;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
mask = (ushort)~(1 << location);
|
|
|
registerValue1 &= mask;
|
|
|
}
|
|
|
|
|
|
var writeResult = ModbusTcpNet.Write("x=6;" + addr, registerValue1);
|
|
|
if (writeResult.IsSuccess) result = true;
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
} |