地址系统 (Address System)
程序集: Snet.Model / Snet.Core
命名空间: Snet.Model.data, Snet.Core.handler, Snet.Core.virtualAddress, Snet.Core.extend
概述
地址系统是 Snet 框架中所有读/写/订阅操作的核心数据模型。它定义了数据点如何被标识、类型化、传输和处理——从原始 PLC 寄存器值,经过类型转换、自定义解析和 MQ 发布,最终成为轻量级的 Protobuf 传输对象。
架构
Address ────── (容器: SN + List<AddressDetails> + CreationTime)
|
+── AddressDetails[] ─── (每个数据点一个实例)
| |-- AddressName (PLC 地址, 如 "DB100.DBD0")
| |-- AddressDataType (DataType 枚举, 默认 String)
| |-- AddressType (Reality / VirtualStatic / VirtualDynamic_*)
| |-- Length (ushort, 1 = 单值)
| |-- EncodingType (默认 ANSI, 基于 CodePage)
| |-- AddressMqParam (MQ 发布配置, 可选)
| +-- AddressParseParam (基于反射的自定义解析, 可选)
|
+── AddressValue : AddressDetails ─── (运行时读取结果)
| |-- Quality (QualityType 枚举)
| |-- ResultValue (解析后的最终值)
| |-- OriginalValue (解析前的原始值)
| |-- Message (详细信息/错误消息)
| |-- Time (DateTime 时间戳)
| +-- .GetSimplify() (---> AddressValueSimplify)
|
+── AddressValueSimplify ([ProtoContract], 9 个 ProtoMember 字段)
+── AddressValueSimplifyProtobuf ([ProtoContract], 批量传输包装器)
数据流
PLC/设备 --原始字节--> [驱动] --字符串值--> AddressHandler.ExecuteDispose
|
+--> DataTypeConvert (字符串 -> .NET 类型值)
+--> Parse (基于反射的自定义转换)
+--> Produce (如配置则发布到 MQ)
|
AddressValue
/ \
GetSimplify() 直接使用
|
AddressValueSimplify
|
[通过 GetSimplifyProtobuf/SelectSimplifyProtobuf 批量处理]
|
AddressValueSimplifyProtobuf
|
[Protobuf 二进制传输]
核心类
Address
顶层容器。用共享标识符将相关的数据点分组。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
SN |
string |
Guid.NewGuid().ToUpperNString() |
组标识符(工位/产线/车间/工厂) |
AddressArray |
List<AddressDetails> |
-- | 组内所有地址详情 |
CreationTime |
DateTime |
DateTime.Now |
组创建时间戳 |
关键方法:
GetAddressInfo(addressName?, addressAnotherName?)-- 按名称/别名查找 AddressDetailsCheckAddress()-- 验证所有 AddressName 是否非空RemoveVirtualAddress()-- 过滤仅保留 Reality 类型(用于订阅场景)GetRealityAddressCount()-- 计算非虚拟地址数量
AddressDetails
每个数据点一个实例。定义标识、类型、编码以及可选的 MQ/解析行为。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
SN |
string |
Guid.NewGuid().ToUpperNString() |
唯一点标识符 |
AddressName |
string |
-- | PLC 地址(如 "DB100.DBD0", "M100", "40001") |
AddressAnotherName |
string? |
-- | 别名/显示名称 |
AddressPropertyName |
string? |
-- | 实体属性名称,用于 ORM 映射 |
AddressDescribe |
string? |
-- | 人类可读的描述 |
AddressDataType |
DataType |
DataType.String |
期望的 .NET 类型 |
AddressType |
AddressType |
AddressType.Reality |
Reality 或 Virtual* 变体 |
Length |
ushort |
1 |
数组长度(Array 数据类型时 >1) |
EncodingType |
EncodingType |
EncodingType.ANSI |
字符串编码(ANSI=0, UTF8=65001 等) |
IsEnable |
bool |
true |
启用/禁用此地址 |
AddressExtendParam |
object? |
-- | 扩展通信参数 |
AddressMqParam |
AddressMq? |
-- | MQ 发布配置 |
AddressParseParam |
AddressParse? |
-- | 基于反射的自定义解析配置 |
AddressValue
继承 AddressDetails,扩展运行时读/写结果数据。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Quality |
QualityType |
QualityType.None |
数据质量指示器 |
ResultValue |
object? |
-- | 解析后的最终值 |
OriginalValue |
object? |
-- | 解析前的原始值 |
Message |
string |
-- | 详细信息或错误消息 |
Time |
DateTime |
DateTime.Now |
读取/更新时间戳 |
关键方法:
SET(AddressDetails)-- 从 AddressDetails 复制所有基础属性SET(value, source, time, bytesModel, msg?, quality?)-- 从驱动级 BytesModel 设置GetSimplify()-- 转换为轻量级AddressValueSimplify用于传输GetDetails(out resultValue, out originalValue, out message, out time)-- 解构
QualityType 枚举
| 值 | 名称 | 含义 |
|---|---|---|
-1 |
None |
尚未处理 |
0 |
Exception |
异常 / 空值 |
1 |
Normal |
数据有效且类型正确 |
2 |
DataTypeError |
类型转换失败 |
3 |
ParseUnknown |
解析成功,正确性未知 |
4 |
ParseError |
自定义解析抛出异常 |
AddressType 枚举
| 值 | 描述 |
|---|---|
Reality |
实际硬件地址 |
VirtualStatic |
固定随机值(一次性生成) |
VirtualDynamic_Random |
随机值,每 {interval}ms 变化 |
VirtualDynamic_RandomScope |
在 {min,max} 范围内的随机值 |
VirtualDynamic_Order |
按 {step} 步长顺序递增 |
VirtualDynamic_OrderScope |
在 {min,max} 内顺序递增,超出后循环 |
AddressValueSimplify
轻量级 [ProtoContract] 传输模型,共 9 个 ProtoMember 字段:
| ProtoMember | 字段 | 类型 | 描述 |
|---|---|---|---|
| 1 | SN |
string? |
唯一标识符 |
| 2 | Add |
string? |
点位地址名称 |
| 3 | L |
ushort |
数据长度 |
| 4 | ENC |
EncodingType |
编码类型 |
| 5 | Q |
QualityType |
质量指示器 |
| 6 | Msg |
string? |
详细信息 |
| 7 | VAL |
string? |
值(JSON 序列化) |
| 8 | Ts |
long |
时间戳(DateTime.Ticks) |
| 9 | DT |
DataType |
数据类型 |
AddressValueSimplifyProtobuf
批量包装器 [ProtoContract],用于 Protobuf 传输。
| ProtoMember | 字段 | 类型 | 描述 |
|---|---|---|---|
| 1 | Ts |
long |
时间戳(DateTime.UtcNow.Ticks) |
| 2 | ITM |
IEnumerable<AddressValueSimplify> |
精简值集合 |
辅助类
AddressMq
配置数据采集后的 MQ 发布。
| 属性 | 类型 | 描述 |
|---|---|---|
Topic |
string |
MQ 主题 |
ContentFormat |
string? |
格式字符串,{0} 为数据占位符;null = 原始数据 |
ISns |
List<string>? |
目标 MQ 实例 SN;null = 所有活跃实例 |
AddressParse
配置基于反射的自定义解析。
| 属性 | 类型 | 描述 |
|---|---|---|
ReflectionParam |
object[]? |
[0] = ReflectionData.Basics(程序集),[1] = 方法 SN |
方法签名: Func<string, string, string> -- 接收 (addressName, value),返回解析后的字符串。
处理管道: AddressHandler.ExecuteDispose
主要的数据处理入口点。管道阶段:
1. 空值检查
value 为 null 或空白 --> QualityType.Exception, 返回
2. DataTypeConvert (字符串 -> .NET 类型值)
处理 30+ 种 DataType 变体:
Bool, String, Char, Byte, Double, Float/Single, Short/Int16, Int/Int32
Long/Int64, Ushort/UInt16, Uint/UInt32, Ulong/UInt64
DateTime/Date/Time
ByteArray, BoolArray, DoubleArray, FloatArray/SingleArray
ShortArray/Int16Array, UshortArray/UInt16Array
IntArray/Int32Array, UintArray/UInt32Array
LongArray/Int64Array, UlongArray/UInt64Array
数组类型通过 ToJsonEntity<T[]> 从 JSON 反序列化。
3. Parse (基于反射的自定义转换, 可选)
如果 AddressParseParam 配置了有效的 ReflectionParam[0] 和 [1],
则通过反射执行外部方法。
结果质量 -> ParseUnknown (成功) 或 ParseError (失败)。
4. Produce (MQ 发布, 可选)
如果配置了 AddressMqParam:
- 应用 ContentFormat (使用 {0} 占位符的 string.Format)
- 通过线程池任务异步发布到 MQ 主题 (非阻塞)
5. 返回 AddressValue
传输助手 (CoreExtend 扩展方法)
静态类 Snet.Core.extend.CoreExtend:
| 方法 | 签名 | 描述 |
|---|---|---|
GetSimplifyArray |
ConcurrentDictionary<string,AddressValue> -> IEnumerable<AddressValueSimplify>? |
将所有值转换为精简形式 |
GetSimplifyProtobuf |
ConcurrentDictionary<string,AddressValue> -> AddressValueSimplifyProtobuf? |
批量包装用于 Protobuf |
SelectSimplifyArray |
(dict, List<string> addressNames) -> IEnumerable<AddressValueSimplify>? |
筛选的精简值 |
SelectSimplifyProtobuf |
(dict, List<string> addressNames) -> AddressValueSimplifyProtobuf? |
筛选的 Protobuf 批量数据 |
GetArray |
ConcurrentDictionary<string,AddressValue> -> IEnumerable<AddressValue>? |
所有原始 AddressValue |
SelectArray |
(dict, List<string> addressNames) -> IEnumerable<AddressValue>? |
筛选的原始值 |
CheckAddress |
Address -> bool |
验证 AddressName 是否非空 |
GetEncoding |
EncodingType? -> Encoding |
将编码枚举转换为 System.Text.Encoding |
ReadGetLength |
DataType -> ushort |
类型字节大小 (8/4/2/0) |
WriteGetBytes |
object -> byte[] |
数值转字节 (BitConverter) |
WriteGetBytes |
string + Encoding -> byte[] |
字符串转编码字节 |
GetDefaultEncodingWrite |
ConcurrentDictionary<string,object> + EncodingType? -> ConcurrentDictionary<string,(object,EncodingType?)> |
为写入值附加编码 |
MessageStandard |
string -> string |
标准化 [TAG] 消息格式 |
虚拟地址系统
虚拟地址无需真实硬件即可生成模拟数据——用于测试和开发。
VirtualAddressManage
管理器类 (IDisposable, IAsyncDisposable),使用 ConcurrentDictionary<string, (VirtualAddress, AddressType, DataType)>:
InitVirtualAddress(AddressDetails, out bool IsVA)-- 如果是虚拟类型则初始化IsVirtualAddress(AddressDetails) -> bool-- 检查地址类型IsVirtualAddress(string addressName) -> bool-- 按名称检查Read(AddressDetails) -> object?/Read(string) -> object?-- 读取当前值Write(AddressDetails, object) -> bool/Write(string, object) -> bool-- 写入值
VirtualAddress
实例类 (CoreUnify<VirtualAddress, VirtualAddressData>):
- 通过 AddressName 配置: 参数嵌入在花括号中
- 线程安全: 使用
lock(_syncLock)保护读写,CancellationTokenSource管理任务生命周期 - ThreadLocal Random: 每个线程独立的 Random 实例,避免锁竞争
配置格式
| 类型 | 语法 | 示例 |
|---|---|---|
VirtualStatic |
{点名称} |
"TestPoint" -- 生成一个随机值 |
VirtualDynamic_Random |
{点名称}{间隔} |
"MyPoint{500}" -- 每 500ms 变化 |
VirtualDynamic_RandomScope |
{点名称}{间隔,最小值^最大值} |
"MyPoint{200,0^100}" -- 每 200ms 在 [0,100] 内随机 |
VirtualDynamic_Order |
{点名称}{间隔,步长} |
"MyPoint{1000,0.5}" -- 每 1000ms 递增 0.5 |
VirtualDynamic_OrderScope |
{点名称}{间隔,步长,最小值^最大值} |
"MyPoint{500,1,0^100}" -- 0->1->2... 到 100 后循环 |
所有间隔值经过校验,最低为 10ms,防止 CPU 空转。
代码示例
创建包含多个数据点的 Address
using Snet.Model.data;
using Snet.Model.@enum;
var details = new List<AddressDetails>
{
new AddressDetails("DB100.DBD0", DataType.Double, length: 1, encodingType: EncodingType.ANSI)
{
AddressDescribe = "温度传感器读数",
AddressType = AddressType.Reality,
AddressMqParam = new AddressMq("sensors/temperature", "{0}", null)
},
new AddressDetails("M100", DataType.Bool)
{
AddressDescribe = "急停状态"
},
new AddressDetails("VirtualRandom{500,0^1000}", DataType.Int)
{
AddressDescribe = "模拟计数器",
AddressType = AddressType.VirtualDynamic_RandomScope
}
};
var address = new Address("Line1_Station3", details, DateTime.Now);
读写数据流
using Snet.Core.handler;
using Snet.Core.virtualAddress;
var vaManager = new VirtualAddressManage();
// 初始化虚拟地址
foreach (var detail in address.AddressArray)
{
vaManager.InitVirtualAddress(detail, out bool isVA);
if (isVA) Console.WriteLine($"虚拟地址: {detail.AddressName}");
}
// 处理来自硬件的值
string hardwareValue = "23.5";
AddressValue? result = address.AddressArray[0].ExecuteDispose(hardwareValue, "读取成功");
if (result != null)
{
Console.WriteLine($"质量: {result.Quality}"); // Normal
Console.WriteLine($"值: {result.ResultValue}"); // 23.5 (double)
Console.WriteLine($"原始值: {result.OriginalValue}"); // 23.5 (double)
}
// 转换为传输格式
var simplify = result.GetSimplify();
// simplify.VAL == "\"23.5\"", simplify.Q == Normal
// 批量传输
var dict = new ConcurrentDictionary<string, AddressValue>();
dict.TryAdd(result.AddressName, result);
var proto = dict.GetSimplifyProtobuf();
byte[] bytes = proto.ToProtobuf(); // Protobuf 二进制传输
虚拟地址读写
// 向虚拟地址写入值
vaManager.Write("VirtualRandom{500,0^1000}", 42);
// 读取当前值
object? currentValue = vaManager.Read("VirtualRandom{500,0^1000}");
// 动态虚拟地址在后台任务中自动更新
// 间隔为 500ms 的 VirtualDynamic_Random 点位将通过
// Task.Run + Task.Delay 循环持续生成新的随机值
