Modbus - Snet Docs

🔧 Modbus 协议驱动

包: Snet.Modbus | 类: ModbusOperate | 类型: 6

提供通过 TCP、UDP、RTU(串行)和 ASCII(串行)传输方式连接 Modbus 兼容设备和 PLC 的能力。

安装

dotnet add package Snet.Modbus

快速开始

using Snet.Modbus;

var op = await ModbusOperate.InstanceAsync(new ModbusData.Basics
{
    IpAddress = "192.168.1.100",
    Port = 502,
    Station = 1,
    DataFormat = Snet.Driver.Core.DataFormat.CDAB,
    AddressStartWithZero = false
});

var address = new Address(new List<AddressDetails>
{
    new("读数", "1", DataType.Int16),
    new("读数2", "3", DataType.Int16)
});
var result = await op.ReadAsync(address);
if (result.Status)
{
    var data = result.GetSource<ConcurrentDictionary<string, AddressValue>>();
    foreach (var kv in data)
        Console.WriteLine($"{kv.Key} = {kv.Value.ResultValue}");
}
else
{
    Console.WriteLine($"读取失败: {result.Message}");
}

// 订阅之前 绑定数据事件
op.OnDataEventAsync += async (sender, e) =>
{
    if (e.Status)
    {
        var data = e.GetSource<ConcurrentDictionary<string, AddressValue>>();
        foreach (var kv in data)
            Console.WriteLine($"{kv.Key} = {kv.Value.ResultValue}");
    }
    else
    {
        Console.WriteLine($"{e.Message}");
    }
};

var subAddr = new Address(new List<AddressDetails>
{
    new("读数", "1", DataType.Int16)
});
await op.SubscribeAsync(subAddr);

// 后续:取消订阅
// await op.UnSubscribeAsync(subAddr);

await op.DisposeAsync();

配置

参数 类型 默认值 描述
IpAddress string 127.0.0.1 设备 IP 地址
Port int 6688 TCP 端口(RTU/ASCII 忽略)
ConnectTimeOut int 1000 连接超时(毫秒)
ReceiveTimeOut int 1000 接收超时(毫秒)
Station int 1 从站 ID
DataFormat enum CDAB 多寄存器字节序(Snet.Driver.Core.DataFormat: ABCD, BADC, CDAB, DCBA)
SleepTime int 0 操作间隔休眠时间(毫秒)
SocketKeepAliveTime int -1 套接字保活时间(-1 为默认)
IsPersistentConnection bool true 保持持久连接
AddressStartWithZero bool true 地址编号从零开始
IsCheckMessageId bool true 检查消息 ID 匹配响应
IsStringReverse bool false 字符串数据反转字节序
SerialPortInfo string COM3-9600-8-N-1 串口配置信息
RtsEnable bool false 启用串口 RTS
DtrEnable bool false 启用串口 DTR
Crc16CheckEnable bool true 启用 CRC16 校验验证
IsClearCacheBeforeRead bool false 读取前清除缓存
StationCheckMatch bool true 检查响应中站号匹配
ProtocolType enum ModbusTcpNet 协议类型
HandleInterval int 1000 处理间隔(毫秒)
ChangeOut bool true 启用变更输出
AllOut bool false 启用全部输出
TaskNumber int 5 并发任务数量

支持的协议类型

类型 描述
ModbusTcpNet 以太网 TCP/IP,默认端口 502
ModbusUdpNet 以太网 UDP,无连接
ModbusRtu 串行 RS-232/485,二进制帧格式
ModbusRtuOverTcp 通过 TCP 桥接的 RTU 帧格式
ModbusAscii 串行 RS-232/485,ASCII 帧格式
ModbusAsciiOverTcp 通过 TCP 桥接的 ASCII 帧格式

地址格式

Modbus 标准按功能码定义地址区间 — 此处仅作概念参考:

概念 位/字 标准编号 功能码
线圈 位, 可读写 00001–09999 FC1
离散输入 位, 只读 10001–19999 FC2
输入寄存器 16 位字, 只读 30001–39999 FC4
保持寄存器 16 位字, 可读写 40001–49999 FC3

这里不做传统4xxxx编号减基址换算。 输入是原始数值地址;AddressStartWithZero=false时"1"对应线缆0,"40001"本身仍是可表达的原始地址,而不是自动指向第一个保持寄存器。

修饰符 含义 示例
x= 功能码(显式 FC2/FC4) x=2;100, x=4;100
w= 写功能码 w=16;100
s= 站号 s=2;100
H 后缀 十六进制地址 100H (= 256)
.{位} 寄存器内位 100.1
format= 仅32/64位数值支持的地址级格式;其他类型透传 format=DCBA;100
file= 文件寄存器 (FC 0x14/0x15) file=0;100
注意

无 x= 时,Bool → FC1(带 .位 则 FC3),Word → FC3。区域键 = S{站号}|FC{功能码};file= 用 S{站号}|FILE{文件号}。完整修饰符语法见 地址自动组包。

寄存器 Bool 位序: 仅点号Bool(含显式x=3/x=4)参与批读,至少覆盖两字节。全局ABCD/CDAB按字内交换后取位,BADC/DCBA按原响应取位。Packer(..., format: ...) 必须与连接的DataFormat一致;BoolIndex是模型切片内位偏移,不是寄存器地址。

合批边界: FC1/2 不参与字节合批,即使配置成字类型也会降级;FC3/4 寄存器及兼容文件读可合批,寄存器 Bool 的真实响应至少两字节。点号位号、UInt16 地址范围及所有修饰段须完整合法,未知段或溢出原样降级,不会被忽略后混入正常批。

批首归一化(26.235.x 起): 驱动 ModbusAddress.Parse 只接受 s=/x=/w= 修饰符 + uint 地址——点号批首(90.1)或 format= 修饰符会导致驱动抛异常、整批读失败,因此组包器从批首剥离点号并丢弃 format=(保留 s=/x=/w=/file=)。批内 BytesModel 偏移不受影响。

支持的数据类型

所有读取按 AddressDetails.DataType 分发 — 别名(Short = Int16、Float = Single、Int = Int32 等)共用同一分支。

数据类型 支持 说明
Bool ✅ 位读取 — 线圈 / 字内位
Int16 / Short, UInt16 / Ushort ✅ 16 位整数
Int32 / Int, UInt32 / Uint ✅ 32 位整数
Int64 / Long, UInt64 / Ulong ✅ 64 位整数
Float / Single, Double ✅ 32 / 64 位浮点
String / Char ✅ 带 length + 编码读取
ByteArray + 全部 10 种 *Array ✅ 带 length 的数组读取
Byte ❌ ReadAsync 中有 case 但无底层读取 — "不支持该类型读取"
注意

None、Date、DateTime、Time 在 ReadAsync 中没有分支 — 不支持。

地址自动组包与解包

每个数采驱动都实现 IPacker,因此 ModbusOperate 可直接将分散地址合并为最小批量读取,并直接解包结果——无需装配处理器(见 IPacker 接口):

// 组包:将分散地址合并为最少的批量读取
var packed = await op.PackerAsync(address, "ModbusTcpNet");
if (!packed.Status) return;                                  // 组包失败
var packedAddr = packed.GetSource<Address>();

// 一次往返读取组包后的批次
var result = await op.ReadAsync(packedAddr);
if (!result.Status) return;                                  // 读取失败
var data = result.GetSource<ConcurrentDictionary<string, AddressValue>>();

// 解包:将每个批次的原始字节解析回各地址的值
var unpacked = await op.UnPackerAsync(data);
if (!unpacked.Status) return;                                // 解包失败
var values = unpacked.GetSource<List<ConcurrentDictionary<string, AddressValue>>>();
foreach (var dict in values)
    foreach (var v in dict)
        Console.WriteLine($"{v.Key} = {v.Value.ResultValue}");

Modbus 全部协议类型都支持组包。引擎细节:地址自动组包。

订阅

先绑定数据事件,再通过 SubscribeAsync 添加地址;数据通过 OnDataEventAsync 到达:

op.OnDataEventAsync += async (sender, e) =>
{
    if (e.Status)
    {
        var data = e.GetSource<ConcurrentDictionary<string, AddressValue>>();
        foreach (var kv in data)
            Console.WriteLine($"{kv.Key} = {kv.Value.ResultValue}");
    }
    else
    {
        Console.WriteLine($"订阅错误: {e.Message}");
    }
};

var subAddr = new Address(new List<AddressDetails>
{
    new("读数", "1", DataType.Int16)
});
await op.SubscribeAsync(subAddr);

// 后续:停止监视
// await op.UnSubscribeAsync(subAddr);

支持的操作

操作 方法
连接 OnAsync()
断开 OffAsync()
读取 ReadAsync(address)
写入 WriteAsync(values)
订阅 SubscribeAsync(address)
组包 / 解包 PackerAsync(address, protocolTypeKey) / UnPackerAsync(data)

参见