📦 地址自动组包(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.packer 随 Snet.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;100、format=DCBA;100、100H、100.1、file=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 内存寻址字节偏移(M100、M100.3);i= 绝对内存、ig= 自定义索引组;s= 符号地址降级(偏移不可预知) |
全部 22 个已在 PackerFactory 静态构造中预注册。
所有 Packer 生成 |B / |W 区域键,同地址的 Bool 与 Word 读取互不冲突。区域无法承载的类型(如字区的 Bool)由 IsPackable 降级为原样保留,任何地址都不会丢失。
3. 不支持组包的协议
部分协议使用符号/标签寻址,无法安全组包——智能入口原样返回:
SiemensS7Plus(符号访问)MelsecCipNet(标签访问)OmronCipNet/OmronConnectedCipNet(标签访问)
VendorProtocolMap 将 80 个供应商 ProtocolType 枚举名(如 SiemensS7Net_S1200、ModbusTcpNet、OmronFinsNet、LSCnet)映射到 22 个 ProtocolFamily 值。近期新增映射:
- Modbus 变体(地址由驱动
TranslateToModbusAddress转换):Delta(DeltaTcpNet、DeltaSerial、DeltaSerialOverTcp、DeltaSerialAscii、DeltaSerialAsciiOverTcp)、Inovance(InovanceTcpNet、InovanceSerial、InovanceSerialOverTcp、InovanceEasyNet、InovanceComputerLink)、MegMeet(MegMeetTcpNet、MegMeetSerial、MegMeetSerialOverTcp)。标准数字 Modbus 地址正常组包;原生格式(X0.0、M100等)安全降级原样保留。 - Toyo
ToyoPuc、AllenBradley(AllenBradleySLCNet、AllenBradleyDF1Serial、AllenBradleyPcccNet)、FX 链接(MelsecFxLinks、MelsecFxLinksOverTcp——原映射 Mitsubishi,现为独立协议族,X/Y 八进制编址)。 - 三菱 A4C
MelsecA4CNet/MelsecA4CNetOverTcp复用 A3C 兼容地址模型,映射到Mitsubishi协议族。 - 倍福 ADS
BeckhoffAdsNet/BeckhoffAdsNetWithAdsRouter(新协议,基于官方Beckhoff.TwinCAT.Ads库;M/I/Q 地址模型相同,共用BeckhoffPacker)— M/I/Q 内存寻址字节偏移(M100、M100.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/LSCnetOverTcp→LSis_Cnet,LSCpu→LSis_Cpu、LSFastEnet→LSis_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.IPacker(IDaq 成员接口,见 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 的公共管线抽象基类。子类只需实现 Family、ParseAddress、GetRegionKey、MaxReadLength。可覆写点:GroupAndSort、SplitIntoBatches、BuildResult、IsPackable;PhysicalMaxBytes 与 SupportsAutoPack(默认 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 / 8、BoolIndex = 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:归一到包含字节地址 M2→M1;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");
40001–49999 范围的地址不再换算——地址零基原样传递(与 ReadAsync 一致)。Modbus 变体(Delta/Inovance/MegMeet)组包方式相同;原生格式(X0.0、M100 等)降级原样返回。2026-08 重构起修饰符值严格校验(s=/x=/w= 必须可装入 byte、file= 装入 ushort、format= 必须是 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.Length(ushort)。计算字节长度将超过 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(批次字节)与 AddressExtendParam(List<BytesModel>),返回以地址名为键的 ConcurrentDictionary<string, AddressValue>。参见上方快速开始示例。当同一地址名携带两种数据类型(如 Modbus 100 的 Bool + Int)时,两个值都保留——第二个键加 DataType 后缀。
🔗 相关资源
- IPacker 接口 — 高层
IDaq级组包 API - 地址系统
- DaqAbstract 基类
- CoreUnify 基类
