设备驱动修改

This commit is contained in:
“hsc”
2026-06-10 10:45:51 +08:00
parent b0a7742b8f
commit 5d14afcb66
19 changed files with 764 additions and 122 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
@@ -14,29 +15,72 @@ namespace DeviceCommand.Base
{
private readonly HttpClient _httpClient;
// ========= 静态注册表:用于 EnovaDataController 反向查找设备实例并分发数据 =========
private static readonly List<EnovaDataReporter> _instances = new List<EnovaDataReporter>();
private static readonly object _registryLock = new object();
/// <summary>
/// 当前已注册的所有 EnovaDataReporter 实例(线程安全快照)
/// </summary>
public static IReadOnlyList<EnovaDataReporter> Instances
{
get
{
lock (_registryLock)
{
return _instances.ToList();
}
}
}
/// <summary>
/// 设备编码:用于在多设备场景下按 deviceCode 过滤分发
/// 留空表示该实例接收所有上报数据
/// </summary>
public string DeviceCode { get; set; } = string.Empty;
// 显式实现/自动属性,方便外部随时更新配置
public string TargetUrl { get; set; } = "http://127.0.0.1:8080/api/channel/state";
public int TimeoutMilliseconds { get; set; } = 5000;
/// <summary>
/// 构造函数注入 HttpClient符合 Prism 依赖注入规范)
/// 收到下位机 POST 上报数据时触发
/// </summary>
public event EventHandler<EnovaChannelDataReceivedEventArgs>? ChannelDataReceived;
public EnovaDataReporter(HttpClient httpClient)
{
// 如果容器没有注入,则给个默认的单例/实例防空
_httpClient = httpClient ?? new HttpClient();
// 自动注册到静态实例表,便于 Controller 反向找到本实例
lock (_registryLock)
{
_instances.Add(this);
}
}
public async Task<EnovaReportResponse> ReportChannelStateAsync(List<EnovaChannelReportData> dataList, CancellationToken ct = default)
/// <summary>
/// 从静态注册表中注销当前实例(设备销毁/释放时调用)
/// </summary>
public virtual void Unregister()
{
lock (_registryLock)
{
_instances.Remove(this);
}
}
public async Task<ApiResponse> ReportChannelStateAsync(List<EnovaChannelData> dataList, CancellationToken ct = default)
{
if (dataList == null || dataList.Count == 0)
{
return new EnovaReportResponse { Success = false, ErrorInfo = "上报数据集合为空" };
return new ApiResponse { Success = false, ErrorInfo = "上报数据集合为空" };
}
if (string.IsNullOrWhiteSpace(TargetUrl))
{
return new EnovaReportResponse { Success = false, ErrorInfo = "目标上报 URL 未配置" };
return new ApiResponse { Success = false, ErrorInfo = "目标上报 URL 未配置" };
}
try
@@ -58,12 +102,12 @@ namespace DeviceCommand.Base
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<EnovaReportResponse>(responseContent);
return result ?? new EnovaReportResponse { Success = true }; // 防止对方返回空Body [cite: 261]
var result = JsonConvert.DeserializeObject<ApiResponse>(responseContent);
return result ?? new ApiResponse { Success = true }; // 防止对方返回空Body
}
else
{
return new EnovaReportResponse
return new ApiResponse
{
Success = false,
ErrorInfo = $"服务器响应错误代码: {(int)response.StatusCode} {response.ReasonPhrase}"
@@ -74,8 +118,98 @@ namespace DeviceCommand.Base
{
// 完美承接你上位机原有的异常日志记录器逻辑
// Logger.LoggerHelper.ErrorWithNotify($"Enova3 数据上传失败: {ex.Message}");
return new EnovaReportResponse { Success = false, ErrorInfo = $"网络异常: {ex.Message}" };
return new ApiResponse { Success = false, ErrorInfo = $"网络异常: {ex.Message}" };
}
}
/// <summary>
/// 处理 Controller 转发过来的下位机上报数据,并触发事件
/// </summary>
public virtual ApiResponse HandleIncomingChannelData(List<EnovaChannelData> dataList)
{
if (dataList == null || dataList.Count == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
}
try
{
// 触发事件,让派生类(如 CTS3或外部订阅者处理具体业务
ChannelDataReceived?.Invoke(this, new EnovaChannelDataReceivedEventArgs(dataList));
return new ApiResponse { Success = true, ErrorInfo = string.Empty };
}
catch (Exception ex)
{
return new ApiResponse { Success = false, ErrorInfo = $"处理上报数据时异常: {ex.Message}" };
}
}
/// <summary>
/// 由 Controller 调用:将下位机上报的数据广播到所有匹配的实例
/// 当数据中含 DeviceCode 时,按 DeviceCode 精确匹配;否则广播给所有实例
/// </summary>
public static ApiResponse Dispatch(List<EnovaChannelData> dataList)
{
if (dataList == null || dataList.Count == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
}
EnovaDataReporter[] snapshot;
lock (_registryLock)
{
snapshot = _instances.ToArray();
}
if (snapshot.Length == 0)
{
return new ApiResponse { Success = false, ErrorInfo = "无可用的设备实例接收数据" };
}
// 按 DeviceCode 分组分发:同一批数据可能来自多个 deviceCode
var groups = dataList
.GroupBy(d => d?.DeviceCode ?? string.Empty)
.ToList();
var errors = new List<string>();
int successCount = 0;
foreach (var group in groups)
{
string deviceCode = group.Key;
var items = group.ToList();
// 选取目标1) 设置了相同 DeviceCode 的实例2) 没设置 DeviceCode 的实例(通用接收者)
var targets = snapshot
.Where(r => string.Equals(r.DeviceCode, deviceCode, StringComparison.OrdinalIgnoreCase)
|| string.IsNullOrEmpty(r.DeviceCode))
.ToList();
if (targets.Count == 0)
{
errors.Add($"DeviceCode={deviceCode} 无匹配的设备实例");
continue;
}
foreach (var target in targets)
{
var resp = target.HandleIncomingChannelData(items);
if (resp != null && resp.Success)
{
successCount++;
}
else
{
errors.Add(resp?.ErrorInfo ?? "未知错误");
}
}
}
return new ApiResponse
{
Success = errors.Count == 0,
ErrorInfo = errors.Count == 0 ? string.Empty : string.Join("", errors)
};
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Model.Model;
@@ -6,12 +7,28 @@ using Model.Model;
namespace DeviceCommand.Base
{
/// <summary>
/// Enova3 上位机数据上报核心接口
/// Enova3 通道数据接收事件参数
/// </summary>
public class EnovaChannelDataReceivedEventArgs : EventArgs
{
public List<EnovaChannelData> DataList { get; }
public DateTime ReceivedTime { get; }
public EnovaChannelDataReceivedEventArgs(List<EnovaChannelData> dataList)
{
DataList = dataList ?? new List<EnovaChannelData>();
ReceivedTime = DateTime.Now;
}
}
/// <summary>
/// Enova3 上位机数据上报 / 接收核心接口
/// 既支持上位机主动推送数据到客户平台,也支持接收下位机通过 HTTP POST 上报的数据
/// </summary>
public interface IEnovaDataReporter
{
/// <summary>
/// 客户平台接收数据的目标 HTTP URL
/// 客户平台接收数据的目标 HTTP URL(用于主动推送)
/// </summary>
string TargetUrl { get; set; }
@@ -20,12 +37,25 @@ namespace DeviceCommand.Base
/// </summary>
int TimeoutMilliseconds { get; set; }
/// <summary>
/// 当 EnovaDataController 收到下位机 POST 上报的数据时触发
/// </summary>
event EventHandler<EnovaChannelDataReceivedEventArgs> ChannelDataReceived;
/// <summary>
/// 异步推送通道的实时状态数据到客户平台
/// </summary>
/// <param name="dataList">包含各通道状态的采集数据集合</param>
/// <param name="ct">取消令牌</param>
/// <returns>平台服务器的响应状态</returns>
Task<EnovaReportResponse> ReportChannelStateAsync(List<EnovaChannelReportData> dataList, CancellationToken ct = default);
Task<ApiResponse> ReportChannelStateAsync(List<EnovaChannelData> dataList, CancellationToken ct = default);
/// <summary>
/// 处理 EnovaDataController 转发过来的下位机上报数据
/// 由 Controller 在收到 HTTP POST 后调用,内部会触发 <see cref="ChannelDataReceived"/> 事件
/// </summary>
/// <param name="dataList">下位机上报的通道数据集合</param>
/// <returns>处理结果,将作为 HTTP 响应返回给下位机</returns>
ApiResponse HandleIncomingChannelData(List<EnovaChannelData> dataList);
}
}
}