地址自动组包 - Snet Docs

📦 地址自动组包(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.packerSnet.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_S1200ModbusTcpNetOmronFinsNetLSCnet)映射到 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

实现公共管线的抽象基类。子类只需实现 FamilyParseAddressGetRegionKeyMaxReadLength。可覆写点:GroupAndSortSplitIntoBatchesBuildResultPhysicalMaxBytesSupportsAutoPack(默认 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(批次字节)与 AddressExtendParamList<BytesModel>),返回以地址名为键的 ConcurrentDictionary<string, AddressValue>。参见上方快速开始示例。


📅 版本历史

日期 版本 变更
2026-08-02 新建文档:地址自动组包(Snet.Core.packer,源码提交 477fa1b 新增)
2026-08-02 代码示例补充 BytesHandler.TransformAsync 拆包(读取结果为原始批次字节)

🔗 相关资源