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);

读到的应该是 12345678

示例里监听 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,从站侧就能看到变化。

这个比只看主站返回“写入成功”更踏实。

到这里

单个从站模拟,主要就是这几个类:

  • ModbusTcpSlaveServer
  • ModbusRtuSlaveServer
  • ModbusAsciiSlaveServer
  • ModbusSlaveDataStore

它们能帮你在没有真实设备时开发主站,也能帮你复现异常、超时、写入变化这些现场问题。

如果后面要模拟网关或一条总线下面的多台设备,就要从单个 server 换成多 UnitId / 多 SlaveId 的 network。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐