📡 DaqAbstract 基类
DaqAbstract<O, D> 是所有数据采集协议实现的基类。它继承自 CoreUnify<O, D> 并实现 IDaq。
public abstract class DaqAbstract<O, D> : CoreUnify<O, D>, IDaq
where O : class
where D : class
8个抽象异步方法
每个协议都必须实现这些方法。它们构成了 DAQ 设备的完整生命周期:
1. OnAsync -- 打开连接
public abstract Task<OperateResult> OnAsync(CancellationToken token = default);
建立与设备的物理/逻辑连接。对于 Modbus TCP,此操作打开一个 Socket。对于串行协议,此操作打开 COM 端口。
2. OffAsync -- 关闭连接
public abstract Task<OperateResult> OffAsync(bool hardClose = false, CancellationToken token = default);
关闭连接。hardClose = true 执行强制关闭;false 尝试优雅关闭。
3. ReadAsync -- 读取数据
public abstract Task<OperateResult> ReadAsync(Address address, CancellationToken token = default);
核心数据检索方法。接收一个包含一个或多个 AddressDetails 的 Address 对象。返回带有已解析数据的 OperateResult,数据存放在 ResultData 中。
4. WriteAsync -- 写入数据
// 抽象方法(子类必须实现)
public abstract Task<OperateResult> WriteAsync(
ConcurrentDictionary<string, (object value, EncodingType? encodingType)> values,
CancellationToken token = default);
// 便捷重载 1:自动推导编码(非抽象,委托给抽象方法)
public Task<OperateResult> WriteAsync(
ConcurrentDictionary<string, object> values,
CancellationToken token = default);
// 便捷重载 2:WriteModel 类型转换(非抽象,委托给抽象方法)
public Task<OperateResult> WriteAsync(
ConcurrentDictionary<string, WriteModel> values,
CancellationToken token = default);
将值写入设备地址。抽象方法以 (object value, EncodingType? encodingType) 元组为核心参数形式,子类只实现这一个抽象方法即可。另外两个重载是便捷方法:ConcurrentDictionary<string, object> 版本自动赋予 ASCII 编码后委托给抽象方法;ConcurrentDictionary<string, WriteModel> 版本根据 WriteModel.AddressDataType 逐项类型转换后委托。
5. SubscribeAsync -- 开始订阅
public abstract Task<OperateResult> SubscribeAsync(Address address, CancellationToken token = default);
注册地址以进行连续轮询。已订阅的地址在每个轮询周期触发 OnDataEvent/OnDataEventAsync。
6. UnSubscribeAsync -- 停止订阅
public abstract Task<OperateResult> UnSubscribeAsync(Address address, CancellationToken token = default);
从订阅列表中移除地址。
7. GetStatusAsync -- 设备状态
public abstract Task<OperateResult> GetStatusAsync(CancellationToken token = default);
返回设备的当前连接和运行状态。
8. GetBaseObjectAsync -- 底层对象
public abstract Task<OperateResult> GetBaseObjectAsync(CancellationToken token = default);
返回底层的通信或驱动对象。适用于高级场景。
3个必需属性(来自 CoreUnify)
重写这些属性以便为 GetParamAsync() 提供元数据:
protected virtual string CN { get; } // 中文显示名称
protected virtual string CD { get; } // 中文描述
protected virtual List<propertie> AP { get; } // 附加自定义属性
6个内置 WebAPI 端点
每个 DaqAbstract 子类通过 HttpServiceOperate 自动暴露 REST API:
| 端点 | 路由 | 请求体 |
|---|---|---|
| 打开 | /api/on |
无 |
| 关闭 | /api/off |
无 |
| 读取 | /api/read |
Address JSON |
| 写入 | /api/write |
ConcurrentDictionary<string, WriteModel> JSON |
| 状态 | /api/getstatus |
无 |
| 切换语言 | /api/switchlanguage |
无 |
启用 WebAPI
var wam = new WAModel { Port = 8080 };
await modbus.WAOnAsync(wam); // 在端口 8080 上启动 HTTP 服务器
WebAPI 请求示例
// POST /api/read 的 JSON 请求体:
{
"SN": "8c71f4a7-04eb-4f9c-88d9-849c2f0c3a00",
"AddressArray": [
{
"SN": "TestAddress",
"AddressName": "M100",
"AddressDataType": "Float"
}
],
"CreationTime": "2024-06-05T13:01:24.6245462+08:00"
}
响应:序列化为 JSON 的 OperateResult。
ReadAsync 管道
协议类中 ReadAsync 的实现通常遵循以下流程:
1. 解析 AddressDetails -> 协议特定的地址字节
2. AddressHandler.ExecuteDispose() -- 虚拟地址拦截
3. 构建协议帧(功能码、寄存器、数量)
4. 通过通信层发送帧
5. 接收响应字节
6. 验证响应(CRC、长度、站号)
7. 根据 DataFormat(CDAB/ABCD/等)转换字节
8. 根据 AddressDataType 解析为类型化的 AddressValue
9. 将值填充到 AddressArray -> EventDataResult
10. 触发 OnDataEvent + OnDataEventAsync
11. 返回带有 ResultData 的 OperateResult
AddressHandler.ExecuteDispose() 步骤是在实际 I/O 发生之前拦截虚拟地址的地方。
编码
DaqAbstract 在其静态构造函数中注册 CodePagesEncodingProvider,以支持 GB2312 编码,这是中国制造的 PLC 通常需要的编码。
static DaqAbstract()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
