地址自动组包 - Snet Docs

📦 地址自动组包(Packer)

Snet.Core.packer 为工业协议提供地址自动组包能力。读取大量分散地址时,它将地址分组、排序、合并为最少的批量读取结构,大幅减少与设备的往返次数。每个受支持的协议族都有专属的 Packer 实现,理解其地址格式与物理上限。

组包能力以两个层级开放:

层级 API 位置
直接使用(推荐) IDaq.Packer / IDaq.UnPacker —— 高层 IPacker 接口 IPacker 接口
引擎(进阶) PackerHandler + IPackerCore 组包器 本页

包: Snet.Core(自动包含)| 命名空间: Snet.Core.packer(入口:Snet.Core.handler


▶️ 快速开始 — 直接通过 IDaq

每个数采驱动(IDaq)都继承 IPacker,组包与解包一步即可完成,无需手动装配处理器:

using Snet.Siemens;
using Snet.Model.data;
using Snet.Model.@enum;
using Snet.Model.@interface;   // IPacker

var op = await SiemensOperate.InstanceAsync(new SiemensData.Basics
{
    IpAddress = "192.168.1.100",
    Port = 102
});

// 1) 组包:将分散地址合并为最少的批量读取
var address = new Address(new List<AddressDetails>
{
    new AddressDetails("DB1.DBD0", DataType.Float),
    new AddressDetails("DB1.DBD4", DataType.Float),
    new AddressDetails("DB1.DBW8", DataType.Int),
    new AddressDetails("DB1.DBX10.3", DataType.Bool)
});
var packed = await op.PackerAsync(address, "SiemensS7Net_S1200");
if (!packed.Status) return;                                   // 组包失败 / 协议不支持
var packedAddr = packed.GetSource<Address>();                // ResultData = 优化后的 Address

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

// 3) 解包:将每个批次的原始字节解析回各地址的值
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}");

协议族不支持组包时(如符号/标签寻址),Packer 原样返回地址——永不抛异常,对有效输入永不返回 null。完整 API 见 IPacker 接口


▶️ 快速开始 — 处理器(引擎层)

using Snet.Core.handler;   // PackerHandler 入口
using Snet.Core.packer;              // ProtocolFamily, IPackerCore

// 创建处理器(单例池工厂;需传入名称,如 "myHandler")
var handler = await PackerHandler.InstanceAsync("myHandler");

// 为设备协议优化地址批次
// maxByteLength = 0 → 自动使用协议的保守安全上限
Address optimized = handler.AddressAutoPackOrPassthrough(
    address,
    basics.ProtocolType.ToString(),  // 如 "SiemensS7Net_S1200"、"ModbusTcpNet"
    maxByteLength: 0,
    format: Snet.Driver.Core.DataFormat.ABCD
);

AddressAutoPackOrPassthrough推荐的智能入口

  • 协议族支持组包 → 返回优化后的批量读取结构
  • 协议族不支持组包 → 原样返回地址(透传)
  • 输入为 null → 返回 null
  • 永不抛异常

⚙️ 安装

无需单独安装包——Snet.Core.packerSnet.Core 发布,而 Snet.Core 被每个协议驱动自动引入。


🧠 核心概念

1. 组包管线

PackerBase.Pack() 对输入地址执行固定管线:

分离可组包 / 不可组包地址
    → GroupAndSort  (按区域分组、去重、按 ByteIndex + BitIndex 排序)
    → SplitIntoBatches (按 maxByteLength 拆批次)
    → BuildResult (组包批次在 AddressDescribe 字段标记 "packer - {n}";透传项不带 "packer" 标记)
  • 组包批次以 AddressDetails 条目输出,DataType.ByteArray + 总字节长
  • 每个批次的 AddressExtendParam 持有 List<BytesModel>——批内各个打包地址及偏移
  • 地址永不丢失——不可组包地址原样透传

读取后,每个组包批次必须用 BytesHandler.TransformAsync 拆包——返回的 AddressValue 携带 OriginalValue(批次原始字节)与 AddressExtendParam(各地址的 BytesModel 列表)。TransformAsync(AddressValue) 将批次原始字节解析回以地址名为键的 ConcurrentDictionary<string, AddressValue>

using Snet.Core.handler;

using var bytesHandler = BytesHandler.Instance(Guid.NewGuid().ToString());
var unpacked = await bytesHandler.TransformAsync(kv.Value);   // kv.Value = 一个组包批次
var values = unpacked.GetSource<ConcurrentDictionary<string, AddressValue>>();

底层重载:TransformAsync(byte[] bytes, DateTime time, IEnumerable<BytesModel> transformParams)BytesModel 映射转换任意字节。

2. 支持的协议族(22 个)

协议族 中文名 Packer MaxReadLength PhysicalMaxBytes 地址解析要点
Siemens 西门子 S7 SiemensPacker 240 960 DB1.DBX0.3 → (0, 3)
Modbus Modbus ModbusPacker 250 131070 零基地址 + 富修饰符(x=4;100format=DCBA;100100H100.1file=0;100);覆写 SplitIntoBatches
Mitsubishi 三菱 Melsec MelsecPacker 960 131070 仅支持驱动白名单中的 MC 设备代码;未知或格式错误的代码原样透传
Omron 欧姆龙 FINS FinsPacker 1000 131070 TIM/CNT 位访问;EM 库号十六进制
Fuji 富士 SPH FujiPacker 210 210 自动判别 SPH / SPB / CST 三种地址格式
Keyence 基恩士 KV KeyencePacker 960 131070 仅 MC Binary 语义(B/M/L/SM/X/Y/TS/TC/CS/CC 位区);X/Y/B 十六进制
Yokogawa 横河 Link YokogawaPacker 500 131070 白名单区域校验
Panasonic 松下 Mewtocol PanasonicPacker 500 131070 位读仅 X/Y/R/L;复合十六进制位号(X1F → 31)
Yaskawa 安川 Memobus YaskawaPacker 250 250 富地址 {数字}[.位号];x=;mfc=(SFC1 线圈 / SFC3 寄存器)
GE GE SRTP GePacker 128 128 地址从 1 起始(SRTP);S/SA/SB/SC/G 位区
Fatek 永宏 Fatek FatekPacker 128 131070 位设备位流布局(46W 字读);字区仅 D/R
Fanuc 发那科 FanucPacker 128 128 字母前缀区 + 别名偏移(SDO/RDI/UI/SI...)
LSis_Cnet LSis Cnet 串口 LSisPacker 500 131070 位宽 B/W/X/D/L 缩放;U/I/Q 复合区(U{a}.{b}.{c} / I{a}.{b}.{c}
LSis_Cpu LSis LSCpu(GLOFA 串口) LSisPacker 120 120 hex 编址(10 位/字);仅 Bool 位流批组包
LSis_FastEnet LSis FastEnet(XGB 以太网) LSisPacker 500 500 仅显式位宽 B/W/D/L Word 组包;X=位/无位宽/U/I/Q/点号降级
Cimon Cimon HMI CimonPacker 500 500 D{字}.{hex位} 字内位访问
XinJE 信捷 XinJEPacker 240 131070 X/Y 八进制编址
Vigor Vigor VigorPacker 128 131070 D≥9000 → SD 区,M≥9000 → SM 区
Toyota 丰田 ToyoPuc ToyoPacker 128 128 prg=N; 前缀;末位十六进制字符为字内位(K1005 = K100 位 5);E/G 系列区域
AllenBradley 罗克韦尔 SLC AllenBradleyPacker 500 500 {类型}{文件号}:{元素号}(B3:0、N7:10);带 /位 寻址不可组包
MitsubishiFx 三菱 FX 链接 FxLinksPacker 128 131070 X/Y 八进制;s=N; 站号前缀;位流布局(WR 字读);位区 Word 降级
Beckhoff 倍福 ADS BeckhoffPacker 1024 1024 M/I/Q 内存寻址字节偏移(M100M100.3);i= 绝对内存、ig= 自定义索引组;s= 符号地址降级(偏移不可预知)

全部 22 个已在 PackerFactory 静态构造中预注册。

PhysicalMaxBytes = 131070 表示驱动会自动分帧读取(如 Modbus 每帧 120 寄存器、FxLinks 每帧 64 字),组包批次单次调用可跨任意数量。未显式覆写 PhysicalMaxBytes 的协议族默认 = MaxReadLength。无论 PhysicalMaxBytes 多大,单个批次上限为 65535 字节(ushort.MaxValue)——因为 AddressDetails.Length/BytesModel.Length 均为 ushort;批次或单点字节长度超过 65535 时 Pack 抛出 ArgumentOutOfRangeException(不再静默截断)。 位/字分区

所有 Packer 生成 |B / |W 区域键,同地址的 Bool 与 Word 读取互不冲突。区域无法承载的类型(如字区的 Bool)由 IsPackable 降级为原样保留,任何地址都不会丢失。

3. 不支持组包的协议

部分协议使用符号/标签寻址,无法安全组包——智能入口原样返回:

  • SiemensS7Plus(符号访问)
  • MelsecCipNet(标签访问)
  • OmronCipNet / OmronConnectedCipNet(标签访问)

VendorProtocolMap 将 80 个供应商 ProtocolType 枚举名(如 SiemensS7Net_S1200ModbusTcpNetOmronFinsNetLSCnet)映射到 22 个 ProtocolFamily 值。近期新增映射:

  • Modbus 变体(地址由驱动 TranslateToModbusAddress 转换):Delta(DeltaTcpNetDeltaSerialDeltaSerialOverTcpDeltaSerialAsciiDeltaSerialAsciiOverTcp)、Inovance(InovanceTcpNetInovanceSerialInovanceSerialOverTcpInovanceEasyNetInovanceComputerLink)、MegMeet(MegMeetTcpNetMegMeetSerialMegMeetSerialOverTcp)。标准数字 Modbus 地址正常组包;原生格式(X0.0M100 等)安全降级原样保留。
  • Toyo ToyoPucAllenBradleyAllenBradleySLCNetAllenBradleyDF1SerialAllenBradleyPcccNet)、FX 链接MelsecFxLinksMelsecFxLinksOverTcp——原映射 Mitsubishi,现为独立协议族,X/Y 八进制编址)。
  • 三菱 A4C MelsecA4CNet / MelsecA4CNetOverTcp 复用 A3C 兼容地址模型,映射到 Mitsubishi 协议族。
  • 倍福 ADS BeckhoffAdsNet / BeckhoffAdsNetWithAdsRouter(新协议,基于官方 Beckhoff.TwinCAT.Ads 库;M/I/Q 地址模型相同,共用 BeckhoffPacker)— M/I/Q 内存寻址字节偏移(M100M100.3)、i= 绝对内存、ig= 自定义索引组;s= 符号地址降级(驱动经 TransValueHandle 运行时查符号表,偏移不可预知)。Bool 与 Word 使用不同的 ADS 索引组(M 16417/16416、I 61473/61472、Q 61489/61488),故位/字区域分离(|B/|W);点号地址(M100.3)仅限位读——Word 访问降级。26.235.x 起改为字节空间建模M100.3 = 字节 100 位 3(仅点号 Bool 组包),无点号 Bool 降级(字节区批读返回位 800 起的 8 位,与位 100 的单点位读语义不一致)。
  • LSis 三模式拆分(2026-08):LSCnet/LSCnetOverTcpLSis_CnetLSCpuLSis_CpuLSFastEnetLSis_FastEnet——原 LSCpu/LSFastEnet 原样返回,现可自动组包(见上方 LSis 三行说明)。

从映射中移除的协议(原样返回——地址模型与协议族 Packer 语义不兼容):KeyenceNanoSerial / KeyenceNanoSerialOverTcp / KeyenceKvOld(KV 老协议 ASCII / Nano 地址模型)、PanasonicMcNet(MC 语义与 Mewtocol 位号编排不同)、OmronHostLinkCMode / OmronHostLinkCModeOverTcp(2026-08 重构从映射移除——此前按 Omron 组包,现原样返回)。


📚 API 参考

IPackerCore

提示

Snet.Core.packer.IPackerCore 是底层引擎契约——不要与高层 Snet.Model.interface.IPackerIDaq 成员接口,见 IPacker 接口)混淆。

成员 签名 描述
Family ProtocolFamily Family { get; } 该 Packer 处理的协议族
Pack Address? Pack(Address address, int maxByteLength, DataFormat format) 将地址集优化为批量读取结构;失败返回 null
ParseAddress (int ByteIndex, int BitIndex) ParseAddress(string address) 解析协议地址 →(字节偏移, 位索引);失败返回 (-1, 0)
TryParseAddress bool TryParseAddress(string address, out int byteIndex, out int bitIndex) 不抛异常的解析
GetRegionKey string GetRegionKey(string address) 地址区域前缀(分组依据)
MaxReadLength int MaxReadLength { get; } 保守安全默认值(maxByteLength <= 0 时使用)
PhysicalMaxBytes int PhysicalMaxBytes { get; } 协议物理硬上限(默认 = MaxReadLength;S7 覆写为 960)
SupportsAutoPack bool SupportsAutoPack { get; } 是否支持自动组包优化

PackerBase

实现 IPackerCore 的公共管线抽象基类。子类只需实现 FamilyParseAddressGetRegionKeyMaxReadLength。可覆写点:GroupAndSortSplitIntoBatchesBuildResultIsPackablePhysicalMaxBytesSupportsAutoPack(默认 true)。

受保护嵌套结构体:AddrInfo(FullName, RegionKey, ByteIndex, BitIndex, DataType, Length, EncodingType = EncodingType.UTF8, DataFormat = -1)BatchItem(FullName, StartByte, Length, BoolIndex = 0, EncodingType = EncodingType.UTF8, DataFormat = -1, DataType = DataType.ByteArray) —— EncodingType 仅对 String/Char 项生效;DataFormat 为地址级格式覆盖(-1 = 使用全局格式);BatchItem.DataType 保留原始类型,供 BytesHandler 正确解包。

结果长度契约: 组包批次 Length = 字节总数;未组包(透传)数组 Length = 元素数(2026-08 重构移除了字节数折算——消费层按原始 DataType 读取,Length 语义为元素数而非字节数)。重构后内部 ushort 转换(ToUShortLength不再静默截断:批次或透传项的计算字节长度超过 65535(ushort.MaxValue)时,Pack 抛出 ArgumentOutOfRangeException——任何地址都不会被静默裁剪。

IsPackable(string address, DataType dataType)(虚方法,默认 true)允许 Packer 将区域无法承载类型的地址降级(如 FxLinks 字区 D100 的 Bool)——原样保留而非组包。

位流批布局(26.235.x 起,系统性修复): 消费层对 ByteArray 批走驱动字读路径,字读位区的响应是从批首地址起的连续位流(8 位/字节、LSB-first)——因此批内偏移必须是位号差而非绝对字节差。Packer 覆写 UseBitStreamLayout = true 即启用(Melsec/Keyence/Fins/Fuji/FxLinks/GE/Yaskawa/Yokogawa/Fatek/LSis_Cpu)。两个配套钩子:GetBitNumber(AddrInfo)(绝对 PLC 位号,默认 ByteIndex × 8 + BitIndex;1 位 1 字节协议覆写为 ByteIndex)与 AlignBitStreamHead(int)(批首对齐:Fins/Fuji 16 位/字对齐、GE 8 位/字节对齐)。位流批内 StartByte = rel / 8BoolIndex = rel % 8(rel = 与对齐批首的位号差)。2026-08 重构还修复了批次重启:位流批溢出、新批开始时,批首偏移为 rel = GetBitNumber(v) - headBit(旧行为新批从 rel = 0 重新开始)。

批首归一化(26.235.x 起): NormalizeBatchHead(string fullName)(虚方法,默认原样)重写批次首地址的 AddressName,使驱动地址解析器可消费——点号位地址(90.1)、format= 修饰符或驱动拒绝的区域形式按协议剥离/归一(Modbus:剥点 + 剥 format=,保留 s=/x=/w=/file=;Beckhoff/Cimon/Toyo:剥点;GE:归一到包含字节地址 M2M1;LSis_Cnet:U/I/Q 点号地址不剥——驱动原生解析)。

去重键现为区域 + 地址名 + 数据类型:同地址不同类型(如西门子 M10 Bool + M10 Word)可在同一批中共存,BytesHandler 遇到键冲突时两个值都保留(第二个值加 DataType 后缀)。

PackerFactory(静态、线程安全注册表)

成员 签名 描述
Register void Register(IPackerCore packer) 注册 Packer(ConcurrentDictionary<ProtocolFamily, IPackerCore>
GetPacker IPackerCore? GetPacker(ProtocolFamily family) 按协议族查找
GetPacker IPackerCore? GetPacker(string deviceType) 按设备类型查找(不区分大小写的枚举解析)
GetRegisteredFamilies ProtocolFamily[] GetRegisteredFamilies() 已注册协议族快照
Count int Count { get; } 已注册 Packer 数量

ProtocolFamily

public enum ProtocolFamily
{
    Siemens, Modbus, Mitsubishi, Omron, Fuji, Keyence, Yokogawa,
    Panasonic, Yaskawa, GE, Fatek, Fanuc, LSis_Cnet, LSis_Cpu, LSis_FastEnet,
    Cimon, XinJE, Vigor,
    Toyota, AllenBradley, MitsubishiFx, Beckhoff
}

每个值带 [Description] 中文名(如 西门子 S7、三菱 Melsec、欧姆龙 FINS)。

PackerHandler

说明

入口类位于 Snet.Core.handler(文件位于 Snet.Core/handler/PackerHandler.cs),与 BytesHandler 同命名空间。它是 IDaq.Packer/IDaq.UnPacker 内部委托的单例池处理器。

编码说明(26.236.x 起): PackerHandler 静态构造函数注册 CodePagesEncodingProvider.Instance——类型首次使用即注册,GB2312/GBK(936)与 GB18030(54936)立即可解析,组包/解包 GB2312 编码字符串无需手动调用 Encoding.RegisterProvider

public class PackerHandler : CoreUnify<PackerHandler, string>
{
    public PackerHandler();
    public PackerHandler(string basics);

    public static string[] GetSupportAutoPackDeviceTypes();   // 已注册协议族名称数组
    public static ProtocolFamily[] GetFullAutoPackFamilies(); // SupportsAutoPack == true 的协议族
    public static bool CanAutoPack(string vendorProtocolTypeName);

    public Address? AddressAutoPack(Address address, ProtocolFamily family, int maxByteLength = 0, DataFormat format = DataFormat.ABCD);
    public Address? AddressAutoPack(Address address, string deviceType = "Siemens", int maxByteLength = 0, DataFormat format = DataFormat.ABCD);
    public Task<Address?> AddressAutoPackAsync(Address address, ProtocolFamily family, int maxByteLength = 0, DataFormat format = DataFormat.ABCD, CancellationToken token = default);
    public Task<Address?> AddressAutoPackAsync(Address address, string deviceType = "Siemens", int maxByteLength = 0, DataFormat format = DataFormat.ABCD, CancellationToken token = default);
    public Address? AddressAutoPackOrPassthrough(Address? address, string vendorProtocolTypeName, int maxByteLength = 0, DataFormat format = DataFormat.ABCD);
    public Task<Address?> AddressAutoPackOrPassthroughAsync(Address? address, string vendorProtocolTypeName, int maxByteLength = 0, DataFormat format = DataFormat.ABCD, CancellationToken token = default);
}

💻 代码示例

打包分散地址批次(西门子)

using Snet.Siemens;
using Snet.Core.handler;              // PackerHandler, BytesHandler
using Snet.Model.data;
using Snet.Model.@enum;

var op = await SiemensOperate.InstanceAsync(new SiemensData.Basics
{
    IpAddress = "192.168.1.100",
    Port = 102
});

// 分散地址:DB1 中偏移不连续的字
var address = new Address(new List<AddressDetails>
{
    new AddressDetails("DB1.DBD0", DataType.Float),
    new AddressDetails("DB1.DBD4", DataType.Float),
    new AddressDetails("DB1.DBW8", DataType.Int),
    new AddressDetails("DB1.DBX10.3", DataType.Bool)
});

// 打包为最小批量读取(S7 上限:240 字节 / 960 物理)
var handler = await PackerHandler.InstanceAsync("myHandler");
var packed = handler.AddressAutoPackOrPassthrough(address, "SiemensS7Net_S1200", maxByteLength: 0);

// 一次往返读取打包后的批次
var result = await op.ReadAsync(packed!);
var data = result.GetSource<ConcurrentDictionary<string, AddressValue>>();

// 拆包:每个批次的原始字节必须经 TransformAsync 解析回各地址的值
using var bytesHandler = BytesHandler.Instance(Guid.NewGuid().ToString());
foreach (var kv in data)
{
    // kv.Value 携带 OriginalValue(批次字节)+ AddressExtendParam(List<BytesModel>)
    var unpacked = await bytesHandler.TransformAsync(kv.Value);
    var values = unpacked.GetSource<ConcurrentDictionary<string, AddressValue>>();
    foreach (var v in values)
        Console.WriteLine($"{v.Key} = {v.Value.ResultValue}");
}

Modbus 富地址

ModbusPacker 接受零基地址 + 富修饰符(任意顺序;最后一个非修饰符段为地址本体):

using Snet.Modbus;
using Snet.Model.data;
using Snet.Model.@enum;

var op = await ModbusOperate.InstanceAsync(new ModbusData.Basics
{
    IpAddress = "192.168.1.100",
    Port = 502
});

var address = new Address(new List<AddressDetails>
{
    new("R1", "100", DataType.Int),                 // 保持寄存器 100(FC3)
    new("R2", "x=4;100", DataType.Int),             // 输入寄存器 100(FC4)
    new("C1", "100", DataType.Bool),                // 线圈 100(FC1)
    new("B1", "100.1", DataType.Bool),              // 寄存器 100 的位 1(FC3)
    new("R3", "100H", DataType.Int),                // 十六进制地址 → 256
    new("R4", "format=DCBA;200", DataType.Int),     // 地址级格式覆盖
    new("R5", "file=0;100", DataType.Int),          // 文件寄存器(FC20/21)
    new("R6", "s=2;100", DataType.Int)              // 站号 2
});
var packed = await op.PackerAsync(address, "ModbusTcpNet");

4000149999 范围的地址不再换算——地址零基原样传递(与 ReadAsync 一致)。Modbus 变体(Delta/Inovance/MegMeet)组包方式相同;原生格式(X0.0M100 等)降级原样返回。2026-08 重构起修饰符值严格校验s=/x=/w= 必须可装入 byte、file= 装入 ushortformat= 必须是 ABCD/BADC/DCBA/CDAB 之一)——越界或未知的修饰符值使整个地址不可组包(原样保留,而非旧行为的静默跳过)。

不支持协议的透传

// 标签型协议原样返回输入
var unchanged = handler.AddressAutoPackOrPassthrough(address, "OmronCipNet");
// → 地址不变,不应用组包

查看已注册协议族

using Snet.Core.packer;

string[] types = PackerHandler.GetSupportAutoPackDeviceTypes();
foreach (var family in PackerFactory.GetRegisteredFamilies())
    Console.WriteLine(family);

❓ 常见问题

1. 何时应使用自动组包?

当读取大量分散地址(分散的 DB 偏移、不连续寄存器)时,组包将地址合并为少量批量读取,往返次数从 N 降到约 1。对于密集、已连续的读取,组包无额外收益——直接使用 ReadAsync 即可。

2. maxByteLength 的含义?

0(推荐)→ 使用协议的保守安全默认值(MaxReadLength)。正数将批次限制为该大小,但受 PhysicalMaxBytes 约束(如 S7 物理上限 960),并最终封顶 65535 字节(ushort.MaxValue——即使协议的 PhysicalMaxBytes 更大(如 Modbus 131070),单个批次也不超过 65535 字节,因为批次长度必须能放入 AddressDetails.Length/BytesModel.Lengthushort)。计算字节长度将超过 65535 的批次/单点使 Pack 抛出 ArgumentOutOfRangeException,而非静默截断。

3. 组包过程中地址会丢失吗?

不会。组包批次在 AddressDescribe 字段标记 packer - {n};不可组包地址不带该标记、原样透传。结果始终包含每个输入地址。

4. PackerHandler 位于哪个命名空间?

PackerHandler(及 BytesHandler)位于 Snet.Core.handler;组包引擎(PackerFactory 及各族 IPackerCore 实现)位于 Snet.Core.packer。两个命名空间都随 Snet.Core 包提供。

5. 读取组包批次后如何解析数据?

读取结果是原始批次字节,而非各地址的值。使用 BytesHandler.TransformAsync(AddressValue) 拆包:它从返回的 AddressValue 读取 OriginalValue(批次字节)与 AddressExtendParamList<BytesModel>),返回以地址名为键的 ConcurrentDictionary<string, AddressValue>。参见上方快速开始示例。当同一地址名携带两种数据类型(如 Modbus 100 的 Bool + Int)时,两个值都保留——第二个键加 DataType 后缀。


🔗 相关资源