(九)YModbus从站模拟:搭建一个TCP、RTU、ASCII从站
GitHub 项目地址:https://github.com/lidecong133/YModbus
前面都是主站读设备。
但做上位机和调试工具时,另一个能力同样重要:自己模拟一个设备。
真实设备不一定随时在你手边。设备在客户现场、还没到货、不能随便写参数,或者你只是想测试主站程序的异常处理,这时候从站模拟就很有用。
YModbus.Slave 做的就是这件事。你可以在本机开一个 TCP 从站,也可以通过串口模拟 RTU / ASCII 从站。
从站模拟不是玩具
从站模拟能解决不少真实问题:
- 没有设备时,先开发主站界面和轮询逻辑
- 验证主站发出的功能码、地址、数量对不对
- 测试写线圈、写寄存器是否落到数据区
- 模拟异常码,看主站提示是否清楚
- 模拟慢响应和不响应,看主站会不会卡住
- 复现现场报文,方便远程分析
比如你要做一个读取温度和压力的主站程序,可以先在本机起一个 TCP 从站:
127.0.0.1:1502
UnitId = 1
HoldingRegister[0] = 235
HoldingRegister[1] = 1000
主站程序先对着这个从站读。界面、日志、解析、轮询都跑通以后,再接真实设备。
从站的核心是数据区
YModbus 用 ModbusSlaveDataStore 保存四类数据区。
using YModbus.Slave;
ModbusSlaveDataStore store = new(pointCount: 1000);
store.SetCoil(0, true);
store.SetDiscreteInput(0, false);
store.SetHoldingRegister(0, 1234);
store.SetInputRegister(0, 5678);
主站读到的,就是这里的值。
主站写线圈或保持寄存器时,数据区也会跟着变。这样你就能确认主站写入到底有没有发出来、地址有没有对上。
先搭一个TCP从站
本机测试最方便的是 TCP。
using System.Net;
using YModbus.Slave;
ModbusTcpSlaveOptions options = new()
{
ListenAddress = IPAddress.Loopback,
Port = 1502,
UnitId = 1,
PointCount = 1000
};
await using ModbusTcpSlaveServer server = new(options);
server.DataStore.SetHoldingRegister(0, 1234);
server.DataStore.SetHoldingRegister(1, 5678);
server.DataStore.SetCoil(0, true);
await server.StartAsync();
Console.WriteLine("TCP slave is running on 127.0.0.1:1502.");
Console.ReadLine();
await server.StopAsync();
主站这样读:
await using ModbusClient client =
await ModbusClientFactory.CreateTcpAsync(
host: "127.0.0.1",
port: 1502,
unitId: 1);
ushort[] values = await client.ReadHoldingRegistersAsync(0, 2);
读到的应该是 1234 和 5678。
示例里监听 IPAddress.Loopback,也就是只允许本机访问。调试阶段推荐这样做,简单、安全,也不容易被局域网里的其它程序误连。
标准 Modbus TCP 端口是 502,但本机测试常用 1502。端口一致就行,不必非要抢 502。
看主站到底发了什么
从站模拟器最有价值的地方,不只是返回数据,还能看主站请求。
server.Traffic += (_, traffic) =>
{
string frame = traffic.Frame.Length == 0
? string.Empty
: Convert.ToHexString(traffic.Frame);
Console.WriteLine($"{traffic.Direction} {traffic.Message} {frame}");
};
如果主站读不到,你先看从站有没有收到请求。
从站没收到,查 IP、端口、防火墙、串口线。
从站收到了,但 UnitId 或地址不对,就回去查主站配置。
这比只看一个“读取失败”有用得多。
RTU从站
RTU 从站需要串口。
using System.IO.Ports;
using YModbus.Serial;
using YModbus.Slave;
using SerialPort port = new("COM3")
{
BaudRate = 9600,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One
};
port.Open();
await using ModbusRtuSlaveServer server = new(
new SerialPortChannel(port, leaveOpen: true),
new ModbusRtuSlaveOptions
{
SlaveId = 1,
PointCount = 1000
});
server.DataStore.SetHoldingRegister(0, 1234);
await server.StartAsync();
Console.ReadLine();
await server.StopAsync();
RTU 调试时,串口参数必须和主站完全一致。波特率、数据位、校验位、停止位,只要一个不一样,就可能完全没响应。
ASCII从站
ASCII 从站和 RTU 类似,只是换成 ModbusAsciiSlaveServer。
using System.IO.Ports;
using YModbus.Serial;
using YModbus.Slave;
using SerialPort port = new("COM3")
{
BaudRate = 9600,
DataBits = 7,
Parity = Parity.Even,
StopBits = StopBits.One
};
port.Open();
await using ModbusAsciiSlaveServer server = new(
new SerialPortChannel(port, leaveOpen: true),
new ModbusAsciiSlaveOptions
{
SlaveId = 1,
PointCount = 1000
});
server.DataStore.SetHoldingRegister(0, 1234);
await server.StartAsync();
Console.ReadLine();
await server.StopAsync();
ASCII 现在少见一些,但遇到老设备时还是有价值。
模拟异常、延迟、不响应
真实设备不总是正常返回。
从站模拟器可以主动构造这些情况。
强制返回异常码:
using YModbus.Protocol;
ModbusTcpSlaveOptions options = new()
{
ListenAddress = IPAddress.Loopback,
Port = 1502,
UnitId = 1,
ForcedExceptionCode = ModbusExceptionCode.IllegalDataAddress
};
模拟慢响应:
ModbusTcpSlaveOptions options = new()
{
ListenAddress = IPAddress.Loopback,
Port = 1502,
UnitId = 1,
ResponseDelayMilliseconds = 1000
};
模拟不响应:
ModbusTcpSlaveOptions options = new()
{
ListenAddress = IPAddress.Loopback,
Port = 1502,
UnitId = 1,
SkipResponse = true
};
这些场景很适合测试主站程序。主站遇到异常码时怎么提示?遇到超时时会不会卡住?下一轮轮询能不能继续?都可以靠模拟从站提前验证。
数据变化也能监听
如果想知道主站有没有写成功,可以监听数据区变化:
server.DataStore.DataChanged += (_, args) =>
{
Console.WriteLine(
$"{args.Area} changed at {args.StartAddress}, quantity {args.Quantity}");
};
主站写保持寄存器 0 = 500,从站侧就能看到变化。
这个比只看主站返回“写入成功”更踏实。
到这里
单个从站模拟,主要就是这几个类:
ModbusTcpSlaveServerModbusRtuSlaveServerModbusAsciiSlaveServerModbusSlaveDataStore
它们能帮你在没有真实设备时开发主站,也能帮你复现异常、超时、写入变化这些现场问题。
如果后面要模拟网关或一条总线下面的多台设备,就要从单个 server 换成多 UnitId / 多 SlaveId 的 network。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)