📦 地址自动组包(Packer)
Snet.Core.packer 为工业协议提供地址自动组包能力。读取大量分散地址时,它将地址分组、排序、合并为最少的批量读取结构,大幅减少与设备的往返次数。每个受支持的协议族都有专属的 Packer 实现,理解其地址格式与物理上限。
包: Snet.Core(自动包含)| 命名空间: Snet.Core.packer(入口:Snet.Temporary.Core.handler)
▶️ 快速开始
using Snet.Temporary.Core.handler; // AutoPackHandler 入口
using Snet.Core.packer; // ProtocolFamily, IPacker
// 创建处理器(单例池工厂)
var handler = await AutoPackHandler.InstanceAsync();
// 为设备协议优化地址批次
// maxByteLength = 0 → 自动使用协议的保守安全上限
Address optimized = handler.AddressAutoPackOrPassthrough(
address,
basics.ProtocolType.ToString(), // 如 "SiemensS7Net_S1200"、"ModbusTcpNet"
maxByteLength: 0,
format: DataFormat.ABCD
);
AddressAutoPackOrPassthrough 是推荐的智能入口:
- 协议族支持组包 → 返回优化后的批量读取结构
- 协议族不支持组包 → 原样返回地址(透传)
- 输入为
null→ 返回null - 永不抛异常
⚙️ 安装
无需单独安装包——Snet.Core.packer 随 Snet.Core 发布,而 Snet.Core 被每个协议驱动自动引入。
🧠 核心概念
1. 组包管线
PackerBase.Pack() 对输入地址执行固定管线:
分离可组包 / 不可组包地址
→ GroupAndSort (按区域分组、去重、按 ByteIndex + BitIndex 排序)
→ SplitIntoBatches (按 maxByteLength 拆批次)
→ BuildResult (组包批次标记 "组包批次SN[n]",透传项标记 "未组包SN[n]")
- 组包批次以
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 映射转换任意字节(参见 Snet.Core.Test/handler/BytesTransformTest.cs)。
2. 支持的协议族(16 个)
| 协议族 | 中文名 | Packer | MaxReadLength | PhysicalMaxBytes | 地址解析要点 |
|---|---|---|---|---|---|
Siemens |
西门子 S7 | SiemensPacker |
240 | 960 | DB1.DBX0.3 → (0, 3) |
Modbus |
Modbus | ModbusPacker |
250 | 250 | 40001 → (0, 0);覆写 SplitIntoBatches |
Mitsubishi |
三菱 Melsec | MelsecPacker |
960 | 960 | |
Omron |
欧姆龙 FINS | FinsPacker |
1000 | 1000 | |
Fuji |
富士 SPH | FujiPacker |
480 | 480 | |
Keyence |
基恩士 KV | KeyencePacker |
960 | 960 | |
Yokogawa |
横河 Link | YokogawaPacker |
500 | 500 | |
Panasonic |
松下 Mewtocol | PanasonicPacker |
500 | 500 | |
Yaskawa |
安川 Memobus | YaskawaPacker |
250 | 250 | |
GE |
GE SRTP | GePacker |
500 | 500 | |
Fatek |
永宏 Fatek | FatekPacker |
200 | 200 | |
Fanuc |
发那科 | FanucPacker |
500 | 500 | GetRegionKey 恒返回 "REG" |
LSis |
LSis Cnet | LSisPacker |
500 | 500 | D 字设备 ×2 字节;M/P/K/L/T/C/F 位设备 /16 |
Cimon |
Cimon HMI | CimonPacker |
500 | 500 | |
XinJE |
信捷 | XinJEPacker |
500 | 500 | |
Vigor |
Vigor | VigorPacker |
200 | 200 |
全部 16 个已在 PackerFactory 静态构造中预注册。
3. 不支持组包的协议
部分协议使用符号/标签寻址,无法安全组包——智能入口原样返回:
SiemensS7Plus(符号访问)MelsecCipNet(标签访问)OmronCipNet/OmronConnectedCipNet(标签访问)
VendorProtocolMap 将 100+ 个供应商 ProtocolType 枚举名(如 SiemensS7Net_S1200、ModbusTcpNet、OmronFinsNet、LSCnet)映射到 16 个 ProtocolFamily 值。
📚 API 参考
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
实现公共管线的抽象基类。子类只需实现 Family、ParseAddress、GetRegionKey、MaxReadLength。可覆写点:GroupAndSort、SplitIntoBatches、BuildResult;PhysicalMaxBytes 与 SupportsAutoPack(默认 true)。
受保护嵌套结构体:AddrInfo(FullName, RegionKey, ByteIndex, BitIndex, DataType, Length) 与 BatchItem(FullName, StartByte, Length, BoolIndex = 0)。
PackerFactory(静态、线程安全注册表)
| 成员 | 签名 | 描述 |
|---|---|---|
Register |
void Register(IPacker packer) |
注册 Packer(ConcurrentDictionary<ProtocolFamily, IPacker>) |
GetPacker |
IPacker? GetPacker(ProtocolFamily family) |
按协议族查找 |
GetPacker |
IPacker? 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, Cimon, XinJE, Vigor
}
每个值带 [Description] 中文名(如 西门子 S7、三菱 Melsec、欧姆龙 FINS)。
AutoPackHandler
命名空间说明: 入口类位于
Snet.Temporary.Core.handler(文件位于Snet.Core/handler/AutoPackHandler.cs)——与当前源码布局一致。
public class AutoPackHandler : CoreUnify<AutoPackHandler, string>
{
public AutoPackHandler();
public AutoPackHandler(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 Address? AddressAutoPackOrPassthrough(Address? address, string vendorProtocolTypeName, int maxByteLength = 0, DataFormat format = DataFormat.ABCD);
}
💻 代码示例
打包分散地址批次(西门子)
using Snet.Siemens;
using Snet.Temporary.Core.handler;
using Snet.Core.handler; // 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("Reading", "DB1.DBD0", DataType.Float),
new("Reading 2", "DB1.DBD4", DataType.Float),
new("Reading 3", "DB1.DBW8", DataType.Int),
new("Reading 4", "DB1.DBX10.3", DataType.Bool)
});
// 打包为最小批量读取(S7 上限:240 字节 / 960 物理)
var handler = await AutoPackHandler.InstanceAsync();
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}");
}
不支持协议的透传
// 标签型协议原样返回输入
var unchanged = handler.AddressAutoPackOrPassthrough(address, "OmronCipNet");
// → 地址不变,不应用组包
查看已注册协议族
using Snet.Core.packer;
string[] types = AutoPackHandler.GetSupportAutoPackDeviceTypes();
foreach (var family in PackerFactory.GetRegisteredFamilies())
Console.WriteLine(family);
❓ 常见问题
1. 何时应使用自动组包?
当读取大量分散地址(分散的 DB 偏移、不连续寄存器)时,组包将地址合并为少量批量读取,往返次数从 N 降到约 1。对于密集、已连续的读取,组包无额外收益——直接使用 ReadAsync 即可。
2. maxByteLength 的含义?
0(推荐)→ 使用协议的保守安全默认值(MaxReadLength)。正数将批次限制为该大小,但受 PhysicalMaxBytes 约束(如 S7 物理上限 960)。
3. 组包过程中地址会丢失吗?
不会。不可组包地址以 未组包SN[n] 标记原样透传;组包批次携带 组包批次SN[n] 标记。结果始终包含每个输入地址。
4. 为什么命名空间是 Snet.Temporary.Core.handler?
入口类当前位于源码树中的该命名空间(文件 Snet.Core/handler/AutoPackHandler.cs)。组包引擎本身在 Snet.Core.packer 下。请以发布的 NuGet 包中的实际命名空间为准。
5. 读取组包批次后如何解析数据?
读取结果是原始批次字节,而非各地址的值。使用 BytesHandler.TransformAsync(AddressValue) 拆包:它从返回的 AddressValue 读取 OriginalValue(批次字节)与 AddressExtendParam(List<BytesModel>),返回以地址名为键的 ConcurrentDictionary<string, AddressValue>。参见上方快速开始示例。
📅 版本历史
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-08-02 | — | 新建文档:地址自动组包(Snet.Core.packer,源码提交 477fa1b 新增) |
| 2026-08-02 | — | 代码示例补充 BytesHandler.TransformAsync 拆包(读取结果为原始批次字节) |
