引入NModbus
dotnet add package NModbus --version 3.0.81
Program.cs
using IOT.Common;
using NModbus;
using System.Net.Sockets;
using System.Net;
var dataStore = ModbusSlaveDataStore.Instance;
_ = Task.Run(() => {
StartListen(dataStore);
});
await Task.Delay(1000);
// 循环写入点位值
_ = Task.Run(async () =>
{
do
{
ushort startAddr = 0;
var f1 = Random.Shared.Next(1, 500) * 2.25;
WriteFloatPoints(dataStore.InputRegisters, startAddr, (float)f1);
var f2 = (ushort)Random.Shared.Next(1, 999);
dataStore.InputRegisters.WritePoints((ushort)(startAddr + 2), [f2]);
await Task.Delay(500);
} while (true);
});
// 一秒读取一次点位值
List<string> nodes = [
"1,0,2","1,2,1"
];
do
{
var values = await ModbusTcpHelper.Instance.ReadModbusTcpServer("localhost", nodes);
foreach (var (k, v) in values)
{
if (v.Contains(","))
{
var arr = v.Split(",");
var low = ushort.Parse(arr[0]);
var high = ushort.Parse(arr[1]);
var floatValue = ReadFloatPoint(low, high);
Console.WriteLine($"{k}: {floatValue}");
}
else
{
Console.WriteLine($"{k}: {v}");
}
}
await Task.Delay(1000);
} while (true);
void WriteFloatPoints(IPointSource<ushort> left, ushort startAddress, float value)
{
var bytes = BitConverter.GetBytes(value);
ushort lowOrderValue = BitConverter.ToUInt16(bytes, 0);
ushort highOrderValue = BitConverter.ToUInt16(bytes, 2);
ushort[] registers = [lowOrderValue, highOrderValue];
left.WritePoints(startAddress, registers);
}
float ReadFloatPoint(ushort low,ushort high)
{
var bytes = new byte[4];
BitConverter.GetBytes(low).CopyTo(bytes, 0);
BitConverter.GetBytes(high).CopyTo(bytes, 2);
float value = BitConverter.ToSingle(bytes, 0);
return value;
}
void StartListen(ModbusSlaveDataStore slaveDataStore)
{
// 创建TCP监听器
int port = 502;
TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
// 创建Modbus工厂
IModbusFactory modbusFactory = new ModbusFactory();
// 创建服务器网络流
var modbusSlaveNetwork = modbusFactory.CreateSlaveNetwork(tcpListener);
// 添加从站ID 1
var slave = modbusFactory.CreateSlave(1, slaveDataStore);
modbusSlaveNetwork.AddSlave(slave);
// 启动Modbus服务器
modbusSlaveNetwork.ListenAsync();
// 打印启动信息
Console.WriteLine($"Modbus TCP Server is listening on port {port}");
Console.ReadKey();
// 停止服务器
tcpListener.Stop();
}
public class ModbusSlaveDataStore : ISlaveDataStore
{
private static readonly object _lock = new object();
private static ModbusSlaveDataStore? _instance;
public static ModbusSlaveDataStore Instance
{
get
{
lock (_lock)
{
_instance = _instance ??= new ModbusSlaveDataStore();
return _instance;
}
}
}
private readonly ushort[] _holdingRegisters = new ushort[100];
private readonly ushort[] _inputRegisters = new ushort[100];
private readonly bool[] _coilDiscretes = new bool[100];
private readonly bool[] _inputDiscretes = new bool[100];
public IPointSource<bool> CoilDiscretes => new DiscretePointSource(_coilDiscretes);
public IPointSource<bool> CoilInputs => new DiscretePointSource(_inputDiscretes);
public IPointSource<ushort> HoldingRegisters => new RegisterPointSource(_holdingRegisters);
public IPointSource<ushort> InputRegisters => new RegisterPointSource(_inputRegisters);
private class RegisterPointSource : IPointSource<ushort>
{
private readonly ushort[] _registers;
public RegisterPointSource(ushort[] registers) => _registers = registers;
public ushort[] ReadPoints(ushort startAddress, ushort numberOfPoints)
{
ushort[] points = new ushort[numberOfPoints];
Array.Copy(_registers, startAddress, points, 0, numberOfPoints);
return points;
}
public void WritePoints(ushort startAddress, ushort[] values)
{
Array.Copy(values, 0, _registers, startAddress, values.Length);
}
}
private class DiscretePointSource : IPointSource<bool>
{
private readonly bool[] _discretes;
public DiscretePointSource(bool[] discretes) => _discretes = discretes;
public bool[] ReadPoints(ushort startAddress, ushort numberOfPoints)
{
bool[] points = new bool[numberOfPoints];
Array.Copy(_discretes, startAddress, points, 0, numberOfPoints);
return points;
}
public void WritePoints(ushort startAddress, bool[] values)
{
Array.Copy(values, 0, _discretes, startAddress, values.Length);
}
}
}
ModbusTcpHelper.cs
using NModbus;
using NModbus.Device;
using NModbus.IO;
using Serilog;
using System.Net.Sockets;
namespace IOT.Common;
/// <summary>
/// 单例
/// </summary>
public class ModbusTcpHelper
{
static readonly object _lock = new object();
private static ModbusTcpHelper? _instance;
public static ModbusTcpHelper Instance
{
get
{
lock (_lock)
{
_instance = _instance ?? new ModbusTcpHelper();
return _instance;
}
}
}
public async Task<Dictionary<string, string>> ReadModbusTcpServer(string ipAddress, List<string> attrIds, int port = 502)
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
try
{
// 创建Modbus TCP客户端
TcpClient tcpClient = new TcpClient(ipAddress, port);
IStreamResource streamResource = new TcpClientAdapter(tcpClient);
ModbusIpTransport transport = new ModbusIpTransport(streamResource, new ModbusFactory(), new CustomModbusLogger());
// 创建 Modbus TCP 主站实例
ModbusIpMaster master = new ModbusIpMaster(transport);
try
{
Log.Information($"attrIds Value: " + string.Join(",", attrIds));
foreach (var attrId in attrIds)
{
var attrIdArr = attrId.Split(",");
if (attrIdArr.Length > 2) {
///从站地址
byte slaveId = byte.Parse(attrIdArr[0]);
// 寄存器起始地址
ushort startAddress = Convert.ToUInt16(attrIdArr[1], 16); // 注意这里是16进制
// 寄存器地址长度
ushort numOfPoints = ushort.Parse(attrIdArr[2]);
// 读取 DI3 对应的寄存器值
ushort[] inputs = master.ReadInputRegisters(slaveId, startAddress, numOfPoints);
Log.Information($"{nameof(ModbusTcpHelper)}: {startAddress} read is {string.Join(",", inputs)}");
keyValuePairs.Add(attrId.ToString(),string.Join(',',inputs));
}
}
}
catch (Exception ex)
{
Log.Error($"{nameof(ModbusTcpHelper)}: [{ipAddress}] {ex}");
}
finally
{
// 关闭连接
tcpClient.Close();
master.Dispose();
}
}
catch (Exception ex)
{
Log.Error($"{nameof(ModbusTcpHelper)}: [{ipAddress}] {ex}");
}
return keyValuePairs;
}
}
public class CustomModbusLogger : IModbusLogger
{
public bool ShouldLog(LoggingLevel level)
{
return true;
}
void IModbusLogger.Log(LoggingLevel level, string message)
{
if(level != LoggingLevel.Error)return;
Log.Error($"{nameof(ModbusTcpHelper)}: {message}");
//Console.WriteLine($"Function Code: {level}, Data: {message}");
}
}