架构概览 - Snet Docs

Snet 框架核心架构

源码路径: Snet.Core/ 命名空间根: Snet.Core


概述

Snet 框架是一个统一的工业数据平台,构建于单一基类 CoreUnify<O, D> 之上。所有操作组件(PLC 驱动、通信客户端、消息队列、缓存、插件、虚拟地址处理器)都继承自此根基类。该框架提供了一致的单例生命周期、结构化事件系统、内置计时/日志记录、多语言支持以及跨所有子系统的标准化 OperateResult 契约。


架构图

                         +-------------------------------+
                         |       CoreUnify<O, D>        |
                         |  (extend/CoreUnify.cs)        |
                         |                               |
                         |  单例池 (最大 255 实例)         |
                         |  3 对事件 (同步+异步)          |
                         |  计时 (Beg/EndOperate)        |
                         |  Serilog 日志                 |
                         |  多语言 (zh/en)                |
                         |  反射参数                       |
                         +-------------+-----------------+
                                       |
         +-----------------+----------+--------+----------+----------------+
         |                 |                   |          |                |
   DaqAbstract    CommunicationAbstract  MqAbstract  CommunicationService  Direct
   (IDaq)         (ICommunication)       (IMq)       Abstract              Subclasses
         |                 |                   |    (ICommunicationService)   |
   +-----+-----+    +------+------+     +-----+-----+   +----+----+    +---+---+
   | 36 种驱动  |    | SerialOperate|    | MqttClient |   |TcpService|   |Subscribe|
   | Snet.DB    |    | TcpClient    |    | Kafka      |   |WsService |   |Channel  |
   | Snet.Sim   |    | WsClient     |    | RabbitMQ   |   |UdpService|   |MqOperate|
   | Snet.TEP   |    | UdpClient    |    | NetMQ      |                 |HttpClient|
   | Snet.Opc   |    | UdpMulticast |    | NettyClient|                 |HttpService|
                     | UdpBroadcast |                                   |Reflection |
                     +--------------+                                   |VirtualAddr|
                                                                        |BytesHandler|
                                                                        |ShareCache |
                                                                        |ProcessCache|
                                                                        |Plugin     |
                                                                        +-----------+

1. CoreUnify <O, D> -- 万法之基

文件: Snet.Core/extend/CoreUnify.cs 命名空间: Snet.Core.extend 实现接口: IArgs, IInstance, IEvent, ILog, ILanguage, IClone, IDisposable, IAsyncDisposable 类型约束: where O : class, where D : class

CoreUnify 是框架中所有操作组件的根基类。O 是操作者类型(正在定义的具体类),D 是配置/基础数据类型,用作单例字典的键。

1.1 单例池

protected static readonly ConcurrentDictionary<D, O> objList = new ConcurrentDictionary<D, O>();
protected static readonly int maxInstanceCount = byte.MaxValue; // 255

每个派生类共享一个静态字典,将配置对象(D)映射到其单例操作者实例(O)。池的上限为 255 个实例。实例创建由 SemaphoreSlim(1, 1) 保护,防止竞争条件。

1.2 实例工厂

// 异步 -- 推荐使用
public static async Task<O> InstanceAsync(D? param = null, CancellationToken token = default)

// 同步便捷包装
public static O Instance(D? param = null)

工厂算法:

  1. 如果 param 为 null,通过 Activator.CreateInstance 创建默认 D
  2. 获取信号量。
  3. objList 中精确匹配键。
  4. 如果未找到,通过对所有现有键执行 Comparer(param).result 进行深度比较查找。
  5. 如果仍未找到匹配,调用私有方法 CreateInstance(param),使用反射调用构造函数 typeof(O).GetConstructor([typeof(D)])
  6. 通过 TryAdd 将新实例添加到 objList

使用示例:

// 创建或获取 Modbus RTU 驱动实例
var basics = new ModbusData.Basics
{
    IpAddress = "192.168.1.100",
    Port = 502,
    Station = 1
};
var modbus = await ModbusOperate.InstanceAsync(basics);

// 后续使用等效数据的调用返回的是同一个实例
var sameModbus = await ModbusOperate.InstanceAsync(basics);
// modbus == sameModbus  (同一个对象引用)

// 打开连接、读取数据、关闭连接
await modbus.OnAsync();
var result = await modbus.ReadAsync(new Address
{
    SN = Guid.NewGuid().ToString(),
    AddressArray = new List<AddressDetails>
    {
        new AddressDetails { AddressName = "D100", AddressDataType = DataType.Int }
    }
});
await modbus.OffAsync();

1.3 计时 -- BegOperate / EndOperate

每个方法都可以用计时调用来封装其执行过程,生成标准化的 OperateResult

// 方法开始 -- 记录开始时间戳
string methodName = await BegOperateAsync(token);

// 方法结束 -- 计算耗时(毫秒),格式化本地化日志消息
return await EndOperateAsync(
    status: true,
    message: "操作完成",
    resultData: someResult,
    token: token
);

EndOperate 会自动:

  • 通过 TimeHandler.StopRecord() 计算耗时(毫秒)。
  • 格式化包含组件标签、方法名、成功/失败状态、源文件和行号的日志消息。
  • 如果 status == falselogOutput == true,通过 LogHelper.ErrorAsync(Serilog)写入错误条目。
  • 返回 OperateResult(status, message, runTime, resultData)

1.4 事件 -- 三对同步/异步事件

同步事件 异步事件包装器 事件参数类型
OnDataEvent OnDataEventAsync(通过 EventingWrapperAsync EventDataResult
OnInfoEvent OnInfoEventAsync(通过 EventingWrapperAsync EventInfoResult
OnLanguageEvent OnLanguageEventAsync(通过 EventingWrapperAsync EventLanguageResult

每个异步事件使用私有的 EventingWrapperAsync<T> 实例,提供 AddHandler/RemoveHandler 方法。受保护的 OnDataEventHandler / OnInfoEventHandler / OnLanguageEventHandler 方法同时触发同步和异步订阅者。语言事件在构造函数中自动注册,在 Dispose/DisposeAsync 中注销,使用缓存的委托实例以防止内存泄漏。

订阅事件:

var modbus = await ModbusOperate.InstanceAsync(basics);

// 同步处理程序
modbus.OnDataEvent += (sender, e) =>
{
    Console.WriteLine($"收到数据: {e.GetSource<SomeDataType>()}");
};

// 异步处理程序(安全即发即忘)
modbus.OnDataEventAsync += async (sender, e) =>
{
    var data = e.GetSource<SomeDataType>();
    await SaveToDatabaseAsync(data);
};

1.5 日志 -- Serilog 集成

LogOperateSet / LogOperateGet 控制底层 LogHelper 使用的 LogModel 配置:

// 配置日志及通知回调
await modbus.LogOperateSetAsync(
    logOut: true,
    consoleOut: true,
    notice: (source, level, message, ex) =>
    {
        // 自定义通知逻辑
    }
);

同时支持同步 Action<...> 和异步 Func<..., Task> 通知回调。

1.6 多语言 -- ILanguage

// 获取翻译值
string translated = modbus.GetLanguageValue("openSuccess");

// 运行时切换语言
modbus.SetLanguage(LanguageType.en);  // 英文
modbus.SetLanguage(LanguageType.zh);  // 中文

// 异步变体
string? translated = await modbus.GetLanguageValueAsync("openSuccess", null, token);

语言值通过 LanguageHandler 静态委托系统解析。切换语言会在所有已注册实例上触发 OnLanguageEvent/OnLanguageEventAsync

1.7 参数自省 -- IArgs

GetArgs() 使用反射生成适用于动态 UI 表单生成的参数树:

var result = await modbus.GetArgsAsync();
// result.ResultData 包含一个 ParamModel,其中有所有可配置属性

// 原始基础数据
var rawResult = await modbus.GetArgsAsync(getBasicsParam: true);

// 自动分配参数(遵循 AutoAllocatingTagAttribute 特性)
var autoParam = await modbus.GetAutoAllocatingArgsAsync();

1.8 构造函数链

// 无参:调用 RegisterLanguageEvent()
protected CoreUnify()

// 有参:存储 basics,然后 RegisterLanguageEvent()
protected CoreUnify(D param)

1.9 资源清理

// Dispose 从单例字典中移除并注销语言事件
public virtual void Dispose()
{
    UnregisterLanguageEvent();
    objList?.Remove(basics, out _);
    GC.SuppressFinalize(this);
}

2. DaqAbstract <O, D> -- 数据采集基类

文件: Snet.Core/abstract/DaqAbstract.cs 继承: CoreUnify<O, D> 实现: IDaq

所有数据采集驱动程序(PLC、OPC、数据库、仿真)的基类。它扩展了 CoreUnify,增加了 WebApi 远程控制能力和类型安全的写入重载。静态构造函数注册 CodePagesEncodingProvider 以支持 GB2312 编码。

2.1 抽象方法(具体驱动程序必须实现)

方法 签名 用途
OnAsync Task<OperateResult> OnAsync(CancellationToken) 激活/打开设备连接
OffAsync Task<OperateResult> OffAsync(bool hardClose, CancellationToken) 停用/关闭,支持可选的硬关闭
ReadAsync Task<OperateResult> ReadAsync(Address, CancellationToken) 从指定地址读取数值
WriteAsync Task<OperateResult> WriteAsync(ConcurrentDictionary<string, (object, EncodingType?)>, CancellationToken) 将类型化数值写入地址
SubscribeAsync Task<OperateResult> SubscribeAsync(Address, CancellationToken) 启动周期性轮询订阅
UnSubscribeAsync Task<OperateResult> UnSubscribeAsync(Address, CancellationToken) 停止订阅
GetBaseObjectAsync Task<OperateResult> GetBaseObjectAsync(CancellationToken) 返回底层通信对象
GetStatusAsync Task<OperateResult> GetStatusAsync(CancellationToken) 返回当前设备状态

2.2 WebApi 集成

DaqAbstract 通过 WAOn/WAOff/WAStatus 提供内置的 HTTP API 端点用于远程设备控制:

端点 HTTP 用途 参数
/api/on POST 打开设备连接
/api/off POST 关闭设备连接
/api/read POST 从地址读取 请求体中传入 Address 对象
/api/write POST 写入地址 请求体中传入 ConcurrentDictionary<string, WriteModel>
/api/getstatus POST 获取设备状态
/api/switchlanguage POST 切换中英文
// 为驱动程序激活 WebApi
var wam = new WAModel { IpAddress = "0.0.0.0", Port = 8080 };
await modbus.WAOnAsync(wam);

// 现在可通过 HTTP 访问:POST http://localhost:8080/api/read
// 请求体:{ "SN": "...", "AddressArray": [{"AddressName":"D100","AddressDataType":"Int"}] }

2.3 写入重载

三种写入重载用于不同的数据表示:

// 原始 (value, encoding) 元组
await driver.WriteAsync(new ConcurrentDictionary<string, (object, EncodingType?)>
{
    ["D100"] = (123, null),
    ["D101"] = (45.6f, null)
});

// 普通对象值(默认为 ASCII 编码)
await driver.WriteAsync(new ConcurrentDictionary<string, object>
{
    ["D100"] = 123,
    ["D101"] = 45.6f
});

// 带有显式 DataType 的 WriteModel(自动转换)
await driver.WriteAsync(new ConcurrentDictionary<string, WriteModel>
{
    ["D100"] = new WriteModel { Value = 99.1, AddressDataType = DataType.Float }
});

2.4 具体驱动程序

以下全部继承自 DaqAbstract<O, D>

PLC 驱动(32+ 种): Snet.Siemens(西门子)、Snet.Mitsubishi(三菱)、Snet.Omron(欧姆龙)、Snet.AllenBradley(AB)、Snet.Beckhoff(倍福)、Snet.CimonSnet.Delta(台达)、Snet.Fanuc(发那科)、Snet.Fatek(永宏)、Snet.FreedomSnet.Fuji(富士)、Snet.GE(通用电气)、Snet.Inovance(汇川)、Snet.Invt(英威腾)、Snet.Keyence(基恩士)、Snet.Kossi(顾美)、Snet.LSisSnet.MegMeet(麦格米特)、Snet.ModbusSnet.OrientalMotor(东方马达)、Snet.Panasonic(松下)、Snet.RKCSnet.Toyota(丰田)、Snet.Turck(图尔克)、Snet.Vigor(丰炜)、Snet.WeCon(维控)、Snet.XinJE(信捷)、Snet.Yamatake(山武)、Snet.Yaskawa(安川)、Snet.Yokogawa(横河)、Snet.YuDian(宇电)

其他 DAQ: Snet.DB(数据库驱动)、Snet.Sim(仿真设备)、Snet.TEP(TepMaster)、Snet.Opc(OpcDaClient、OpcDaHttp、OpcUaClient)


3. MqAbstract <O, D> -- 消息队列基类

文件: Snet.Core/abstract/MqAbstract.cs 继承: CoreUnify<O, D> 实现: IMq

消息队列/消息代理集成的基类。在异构 MQ 实现之上提供一致的生产/消费抽象。

3.1 抽象方法

方法 签名 用途
OnAsync Task<OperateResult> OnAsync(CancellationToken) 打开与代理的连接
OffAsync Task<OperateResult> OffAsync(bool, CancellationToken) 关闭连接
ProduceAsync Task<OperateResult> ProduceAsync(string topic, byte[], CancellationToken) 将二进制负载发布到主题
ConsumeAsync Task<OperateResult> ConsumeAsync(string topic, CancellationToken) 开始从主题消费
UnConsumeAsync Task<OperateResult> UnConsumeAsync(string topic, CancellationToken) 停止从主题消费
GetStatusAsync Task<OperateResult> GetStatusAsync(CancellationToken) 返回代理连接状态

3.2 字符串转字节的便捷方法

// 具体实现:使用默认 UTF-8 或指定编码将字符串序列化为 byte[]
public virtual Task<OperateResult> ProduceAsync(
    string topic, string content, Encoding? encoding = null, CancellationToken token = default)
{
    return ProduceAsync(topic,
        encoding == null ? Encoding.UTF8.GetBytes(content) : encoding.GetBytes(content));
}

3.3 具体 MQ 实现

协议
Snet.Mqtt MqttClientOperate MQTT 3.1.1 / 5.0
Snet.Kafka Kafka client Apache Kafka
Snet.RabbitMQ RabbitMQ client AMQP 0-9-1
Snet.NetMQ NetMQ client ZeroMQ(C# 原生)
Snet.Netty Netty client 基于 TCP 的自定义 MQ

3.4 使用示例

var mqttData = new MqttClientData.Basics
{
    IpAddress = "broker.emqx.io",
    Port = 1883,
    ClientID = "snet-client-001"
};
var mqtt = await MqttClientOperate.InstanceAsync(mqttData);

await mqtt.OnAsync();
await mqtt.ProduceAsync("snet/data", "{\"temp\": 23.5}");
await mqtt.ConsumeAsync("snet/commands");
await mqtt.OffAsync();

4. CommunicationAbstract <O, D> -- 客户端通信基类

文件: Snet.Core/abstract/CommunicationAbstract.cs 继承: CoreUnify<O, D> 实现: ICommunication

客户端通信传输(出站连接)的基类。

4.1 抽象方法

方法 用途
OnAsync 连接到远程端点
OffAsync(bool hardClose) 断开连接
SendAsync(byte[]) 发送原始字节
SendWaitAsync(byte[], CancellationToken) 发送并等待响应
GetBaseObjectAsync 返回底层 socket/客户端对象
GetStatusAsync 返回连接状态

4.2 具体实现

文件 传输方式
SerialOperate communication/serial/SerialOperate.cs RS-232/RS-485
TcpClientOperate communication/net/tcp/client/TcpClientOperate.cs TCP/IP
WsClientOperate communication/net/ws/client/WsClientOperate.cs WebSocket
UdpClientOperate communication/net/udp/unicast/client/UdpClientOperate.cs UDP 单播
UdpMulticastOperate communication/net/udp/multicast/UdpMulticastOperate.cs UDP 组播
UdpBroadcastOperate communication/net/udp/broadcast/UdpBroadcastOperate.cs UDP 广播

4.3 资源清理

public override void Dispose()
{
    Off(true);      // 先硬关闭连接
    base.Dispose(); // 从单例池中移除,注销事件
}

5. CommunicationServiceAbstract <O, D> -- 服务端通信基类

文件: Snet.Core/abstract/CommunicationServiceAbstract.cs 继承: CoreUnify<O, D> 实现: ICommunicationService

服务端通信传输(入站监听)的基类。

5.1 抽象方法

方法 用途
OnAsync 启动服务端监听
OffAsync(bool hardClose) 停止服务端
SendAsync(byte[], string[]?) 向多个目标客户端发送字节
SendAsync(byte[], string) 向指定客户端发送字节
RemoveAsync(string[]) 移除/断开指定客户端连接
GetBaseObjectAsync 返回底层服务端对象
GetStatusAsync 返回服务端状态及已连接客户端

5.2 具体实现

文件
TcpServiceOperate communication/net/tcp/service/TcpServiceOperate.cs
WsServiceOperate communication/net/ws/service/WsServiceOperate.cs
UdpServiceOperate communication/net/udp/unicast/service/UdpServiceOperate.cs

6. 完整继承层次结构

CoreUnify<O, D>                              (Snet.Core/extend/CoreUnify.cs)
|
+-- DaqAbstract<O, D>                 (Snet.Core/abstract/DaqAbstract.cs) [IDaq]
|   +-- 所有 PLC 驱动(32+ 个包:Siemens、Mitsubishi、Omron、AB 等)
|   +-- Snet.DB(数据库)
|   +-- Snet.Sim(仿真)
|   +-- Snet.TEP(TepMaster)
|   +-- Snet.Opc(OpcDaClient、OpcDaHttp、OpcUaClient)
|
+-- CommunicationAbstract<O, D>       (Snet.Core/abstract/CommunicationAbstract.cs) [ICommunication]
|   +-- SerialOperate(串口操作)
|   +-- TcpClientOperate(TCP 客户端操作)
|   +-- WsClientOperate(WebSocket 客户端操作)
|   +-- UdpClientOperate(UDP 单播客户端操作)
|   +-- UdpMulticastOperate(UDP 组播操作)
|   +-- UdpBroadcastOperate(UDP 广播操作)
|
+-- CommunicationServiceAbstract<O,D> (Snet.Core/abstract/CommunicationServiceAbstract.cs) [ICommunicationService]
|   +-- TcpServiceOperate(TCP 服务端操作)
|   +-- WsServiceOperate(WebSocket 服务端操作)
|   +-- UdpServiceOperate(UDP 单播服务端操作)
|
+-- MqAbstract<O, D>                  (Snet.Core/abstract/MqAbstract.cs) [IMq]
|   +-- Snet.Mqtt  (MqttClientOperate)
|   +-- Snet.Kafka
|   +-- Snet.RabbitMQ
|   +-- Snet.NetMQ
|   +-- Snet.Netty (NettyClient)
|
+-- SubscribeOperate                  (Snet.Core/subscribe/SubscribeOperate.cs)
+-- SubscribeSource<T>                (Snet.Core/subscribe/core/SubscribeSource.cs)
+-- SubscribeService<T>               (Snet.Core/subscribe/core/SubscribeService.cs)
+-- HttpServiceOperate                (Snet.Core/communication/net/http/service/HttpServiceOperate.cs)
+-- HttpClientOperate                 (Snet.Core/communication/net/http/client/HttpClientOperate.cs)
+-- ChannelOperate<T>                 (Snet.Core/channel/ChannelOperate.cs)
+-- MqOperate                         (Snet.Core/mq/MqOperate.cs)
+-- ReflectionOperate                 (Snet.Core/reflection/ReflectionOperate.cs)
+-- VirtualAddress                    (Snet.Core/virtualAddress/VirtualAddress.cs)
+-- BytesHandler                      (Snet.Core/handler/BytesHandler.cs)
+-- ShareCacheOperate                 (Snet.Core/cache/share/ShareCacheOperate.cs)
+-- ProcessCacheOperate               (Snet.Core/cache/process/ProcessCacheOperate.cs)
+-- PluginOperate                     (Snet.Core/plugin/PluginOperate.cs)

7. 核心设计模式

7.1 配置即键的单例池

每个 CoreUnify 子类拥有一个 static ConcurrentDictionary<D, O>。配置对象 D 同时充当构造参数和字典键。深度比较确保等效配置返回相同实例。255 实例上限防止无限制的资源消耗。

7.2 OperateResult -- 统一返回信封

每个方法都返回 OperateResult,它包含:

  • Status(bool)-- 成功/失败指示器
  • Message(string?)-- 人类可读的本地化描述
  • RunTime(int)-- 执行时间(毫秒)
  • ResultData(object?)-- 实际负载
// 成功
return OperateResult.CreateSuccessResult("读取完成", rawData);
// 失败
return OperateResult.CreateFailureResult("连接超时");

7.3 抽象方法 + 具体便捷实现

抽象基类定义了子类必须实现的异步契约。同步重载作为便捷包装,使用 GetAwaiter().GetResult()。此模式防止调用者在异步上下文中意外阻塞,同时仍为控制台应用和脚本提供简单的同步 API。

7.4 WebApi 远程控制

DaqAbstract 内嵌了一个 HTTP 服务器(HttpServiceOperate),将设备操作暴露为 REST 端点。这使得任何 DAQ 驱动程序都可以在无需额外基础设施的情况下进行远程控制。


8. 包依赖

包 / 命名空间 角色
Snet.Core 框架核心:所有抽象类、扩展类、通信、缓存、通道、MQ、插件、反射、订阅、虚拟地址
Snet.Model 数据模型、接口(IDaqIMqICommunication 等)、枚举、事件参数类型
Snet.Log 封装 Serilog 的日志抽象层
Snet.Utility 工具类(JSON、字节、反射、时间、比较)
Snet.{Vendor} 各厂商 PLC 驱动包(每个都继承 DaqAbstract
Snet.Mqtt MQTT 客户端(继承 MqAbstract
Snet.Kafka Kafka 客户端(继承 MqAbstract
Snet.RabbitMQ RabbitMQ 客户端(继承 MqAbstract
Snet.NetMQ NetMQ/ZeroMQ 客户端(继承 MqAbstract
Snet.Netty Netty 客户端(继承 MqAbstract
Snet.Opc OPC DA / OPC UA 客户端(继承 DaqAbstract
Snet.DB 数据库 DAQ 驱动(继承 DaqAbstract
Snet.Sim 仿真驱动(继承 DaqAbstract
Snet.TEP TepMaster 集成(继承 DaqAbstract

9. CoreUnify 实例生命周期

1. 实例创建
   InstanceAsync(param)
   ├── param 为 null?→ Activator.CreateInstance<D>()
   ├── semaphore.WaitAsync()
   ├── objList.TryGetValue(param)   → 精确匹配?返回现有实例
   ├── kvp.Key.Comparer(param)      → 深度匹配?返回现有实例
   ├── CreateInstance(param)        → ConstructorInfo.Invoke()
   ├── objList.TryAdd(param, inst)  → 添加到池中
   └── semaphore.Release()

2. 操作
   BegOperateAsync() → TimeHandler.StartRecord()
   ... 业务逻辑 ...
   EndOperateAsync(status, msg, data, ex)
   ├── TimeHandler.StopRecord()     → 计算 runTime
   ├── 格式化本地化日志消息
   ├── 如果 !status:通过 Serilog 调用 LogHelper.ErrorAsync()
   └── 返回 OperateResult(status, msg, runTime, data)

3. 释放
   DisposeAsync()
   ├── [子类清理:WAOff()、Off(true)]
   ├── UnregisterLanguageEvent()    → -= 缓存的委托
   ├── objList.Remove(basics)       → 从池中移除
   └── GC.SuppressFinalize()

10. 接口摘要

接口 定义位置 实现类 关键成员
IArgs Snet.Model CoreUnify GetArgs, GetAutoAllocatingArgs, UpdateArgs, UpdateArgs<T>, GetBasicsArgs, ExistsAutoAllocatingArgs
IInstance Snet.Model CoreUnify CreateInstance<T>CreateInstance(string json)
IEvent Snet.Model CoreUnify OnDataEventOnInfoEventOnLanguageEvent(+ async)
ILog Snet.Model CoreUnify LogOperateSetLogOperateGet
ILanguage Snet.Model CoreUnify GetLanguageValueSetLanguageGetLanguage
IClone Snet.Model CoreUnify CloneThis, CloneThisAsync 深度对象克隆
IDaq Snet.Model DaqAbstract 继承 IOnIOffIReadIWriteISubscribeIStatusIWAIObject
IMq Snet.Model MqAbstract 继承 IOnIOffIProducerIConsumerIStatus
ICommunication Snet.Model CommunicationAbstract OnOffSendSendWaitGetBaseObjectGetStatus
ICommunicationService Snet.Model CommunicationServiceAbstract OnOffSend(byte[],string[])Send(byte[],string)RemoveGetBaseObjectGetStatus

相关文档

  • API 参考 — 接口、数据模型、枚举、事件、通信类
  • 日志系统 — 基于 Serilog 的日志基础设施
  • 工具库 — 共享工具方法和扩展类
  • 协议概览 — 每个 Snet.{Vendor} 包的驱动文档