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 标准按功能码定义地址区间 — 此处仅作概念参考:

概念 位/字 标准编号 功能码
线圈 位, 可读写 0000109999 FC1
离散输入 位, 只读 1000119999 FC2
输入寄存器 16 位字, 只读 3000139999 FC4
保持寄存器 16 位字, 可读写 4000149999 FC3

驱动不接受这些 5/6 位数字。 地址是零基的 — AddressStartWithZero = false 时,地址 "1" 映射到协议地址 0(第一个保持寄存器)。传 "40001" 会被解析为协议地址 40001(越界)。

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

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

寄存器 Bool 位序(组包,26.235 起): 点号寄存器 Bool(100.1)按 FC3 组包——位 0–7 位于寄存器的低字节(字节流的最后一个字节)。BoolIndex 是相对模型 buffer 的全局位偏移,位号 < 8 映射进低字节(100.1 → 低字节位 1)。解包时按 BoolIndex / 8 取字节、BoolIndex % 8 取位。

线圈 Bool 降级(26.235.x 起): 无点号 Bool(100)推断 FC1(x=2 则 FC2)——消费层对 ByteArray 批走 FC3 寄存器读,物理读错区域;线圈读响应是从批首线圈号起的位流,与绝对 /8 布局错位。因此线圈 Bool(含 x=1;100.1 线圈+点号组合)降级为单点 ReadBool(FC1)——无线圈功能码的点号 Bool 仍按 FC3 组包。

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

支持的数据类型

所有读取按 AddressDetails.DataType 分发 — 别名(Short = Int16Float = SingleInt = 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 但无底层读取 — "不支持该类型读取"
注意

NoneDateDateTimeTimeReadAsync 中没有分支 — 不支持。

地址自动组包与解包

每个数采驱动都实现 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)

参见