设备驱动修改
This commit is contained in:
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,80 @@ using Model.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// CTS3-6-300-8IS0 设备:通过 EnovaDataController 接收下位机 POST 上报的通道数据
|
||||
/// </summary>
|
||||
public class CTS3 : EnovaDataReporter
|
||||
{
|
||||
/// <summary>
|
||||
/// 最近一次收到的通道数据快照(key = ChannelCode)
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, EnovaChannelData> LatestChannelData => _latestChannelData;
|
||||
private readonly Dictionary<string, EnovaChannelData> _latestChannelData = new Dictionary<string, EnovaChannelData>();
|
||||
private readonly object _dataLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 业务侧可订阅此事件以获得更友好的回调(仅本设备数据)
|
||||
/// </summary>
|
||||
public event EventHandler<EnovaChannelDataReceivedEventArgs>? OnDataUpdated;
|
||||
|
||||
public CTS3(HttpClient httpClient) : base(httpClient)
|
||||
{
|
||||
// 订阅基类事件:当 Controller 调用 HandleIncomingChannelData 时会触发
|
||||
ChannelDataReceived += OnChannelDataReceivedInternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定 DeviceCode 的便捷构造,便于多台 CTS3 共存时按 deviceCode 精确匹配
|
||||
/// </summary>
|
||||
public CTS3(HttpClient httpClient, string deviceCode) : this(httpClient)
|
||||
{
|
||||
DeviceCode = deviceCode ?? string.Empty;
|
||||
}
|
||||
|
||||
private void OnChannelDataReceivedInternal(object? sender, EnovaChannelDataReceivedEventArgs e)
|
||||
{
|
||||
if (e?.DataList == null || e.DataList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 缓存最近一次的通道快照
|
||||
lock (_dataLock)
|
||||
{
|
||||
foreach (var data in e.DataList)
|
||||
{
|
||||
if (data == null) continue;
|
||||
string key = data.ChannelCode ?? string.Empty;
|
||||
_latestChannelData[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 业务异常拦截示例:通道错误状态
|
||||
foreach (var data in e.DataList)
|
||||
{
|
||||
if (data == null) continue;
|
||||
if (data.ChannelState == "错误" || data.ChannelState == "0x04")
|
||||
{
|
||||
// TODO: 触发本地报警逻辑
|
||||
// Logger.LoggerHelper.WarnWithNotify($"通道 {data.ChannelCode} 故障");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 转发给业务订阅者
|
||||
OnDataUpdated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public override void Unregister()
|
||||
{
|
||||
ChannelDataReceived -= OnChannelDataReceivedInternal;
|
||||
base.Unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
DeviceCommand/Devices/Others.cs
Normal file
108
DeviceCommand/Devices/Others.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using DeviceCommand.Base;
|
||||
|
||||
public class Others : ModbusTcp
|
||||
{
|
||||
private readonly byte _slaveId;
|
||||
|
||||
/// <summary>
|
||||
/// 通道1PV
|
||||
/// </summary>
|
||||
private const ushort CH1_PV = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 通道2PV
|
||||
/// </summary>
|
||||
private const ushort CH2_PV = 102;
|
||||
|
||||
/// <summary>
|
||||
/// 通道3PV
|
||||
/// </summary>
|
||||
private const ushort CH3_PV = 104;
|
||||
|
||||
/// <summary>
|
||||
/// 通道4PV
|
||||
/// </summary>
|
||||
private const ushort CH4_PV = 106;
|
||||
|
||||
public Others(
|
||||
byte slaveId,
|
||||
string ip,
|
||||
int port = 508,
|
||||
int sendTimeoutMs = 3000,
|
||||
int receiveTimeoutMs = 3000)
|
||||
: base()
|
||||
{
|
||||
_slaveId = slaveId;
|
||||
ConfigureDevice(ip, port, sendTimeoutMs, receiveTimeoutMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取通道1PV
|
||||
/// </summary>
|
||||
public async Task<float> ReadCH1Async(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadRealAsync(CH1_PV, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取通道2PV
|
||||
/// </summary>
|
||||
public async Task<float> ReadCH2Async(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadRealAsync(CH2_PV, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取通道3PV
|
||||
/// </summary>
|
||||
public async Task<float> ReadCH3Async(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadRealAsync(CH3_PV, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取通道4PV
|
||||
/// </summary>
|
||||
public async Task<float> ReadCH4Async(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadRealAsync(CH4_PV, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次读取4个通道PV
|
||||
/// </summary>
|
||||
public async Task<(float ch1, float ch2, float ch3, float ch4)> ReadAllAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] raw = await ReadHoldingRegistersAsync(_slaveId, 100, 8, ct);
|
||||
|
||||
return
|
||||
(
|
||||
ToFloat(raw[0], raw[1]),
|
||||
ToFloat(raw[2], raw[3]),
|
||||
ToFloat(raw[4], raw[5]),
|
||||
ToFloat(raw[6], raw[7])
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<float> ReadRealAsync(ushort address, CancellationToken ct)
|
||||
{
|
||||
ushort[] raw = await ReadHoldingRegistersAsync(_slaveId, address, 2, ct);
|
||||
return ToFloat(raw[0], raw[1]);
|
||||
}
|
||||
|
||||
private static float ToFloat(ushort high, ushort low)
|
||||
{
|
||||
byte[] bytes =
|
||||
{
|
||||
(byte)(high >> 8),
|
||||
(byte)high,
|
||||
(byte)(low >> 8),
|
||||
(byte)low
|
||||
};
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
public class SDE720SH_A : ModbusTcp
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
public class SDL710FH:ModbusTcp
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,47 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
public class THC1100:S7Device
|
||||
public class THC1100 : S7Device
|
||||
{
|
||||
// 定义 PLC 地址常量,便于后期维护
|
||||
private const string RealTimeTemperatureSetPointAddress = "DB53.DBD280";
|
||||
private const string RealTimeHumidityMeasuredValueAddress = "DB53.DBD1092";
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取温度实时测量值 (DB53.DBD1052, 浮点型)
|
||||
/// </summary>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
/// <returns>温度设定值 (float)</returns>
|
||||
public async Task<float> GetRealTimeTemperatureSetPointAsync(CancellationToken ct = default)
|
||||
{
|
||||
// S7.Net 读取浮点型时,可以直接将其强转为 float
|
||||
// 内部已通过基类的 _commLock 保证线程安全
|
||||
return await ReadAsync<float>(RealTimeTemperatureSetPointAddress, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取湿度实时测量值 (DB53.DBD1092, 浮点型)
|
||||
/// </summary>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
/// <returns>湿度测量值 (float)</returns>
|
||||
public async Task<float> GetRealTimeHumidityMeasuredValueAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<float>(RealTimeHumidityMeasuredValueAddress, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次性获取温湿度相关的运行数据包(选填,若需要批量展示可以使用此方法)
|
||||
/// </summary>
|
||||
public async Task<(float TemperatureSetPoint, float HumidityMeasuredValue)> GetThcDataPackAsync(CancellationToken ct = default)
|
||||
{
|
||||
var tempSet = await GetRealTimeTemperatureSetPointAsync(ct);
|
||||
var humidMeasure = await GetRealTimeHumidityMeasuredValueAsync(ct);
|
||||
return (tempSet, humidMeasure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
DeviceCommand/Devices/UMC1000Rtu.cs
Normal file
123
DeviceCommand/Devices/UMC1000Rtu.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// UMC1000 温湿度控制器(RS485)
|
||||
/// </summary>
|
||||
public class UMC1000Rtu : ModbusRtu
|
||||
{
|
||||
private readonly byte _slaveId;
|
||||
|
||||
/// <summary>
|
||||
/// 温度测量值 REAL
|
||||
/// </summary>
|
||||
private const ushort TemperatureAddress = 150;
|
||||
|
||||
/// <summary>
|
||||
/// 湿度测量值 REAL
|
||||
/// </summary>
|
||||
private const ushort HumidityAddress = 152;
|
||||
|
||||
public UMC1000Rtu(
|
||||
byte slaveId,
|
||||
string portName,
|
||||
int baudRate = 9600,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000)
|
||||
{
|
||||
_slaveId = slaveId;
|
||||
|
||||
ConfigureDevice(
|
||||
portName,
|
||||
baudRate,
|
||||
dataBits,
|
||||
stopBits,
|
||||
parity,
|
||||
readTimeout,
|
||||
writeTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取温度
|
||||
/// </summary>
|
||||
public async Task<float> ReadTemperatureAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return await ReadFloatAsync(
|
||||
TemperatureAddress,
|
||||
ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取湿度
|
||||
/// </summary>
|
||||
public async Task<float> ReadHumidityAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return await ReadFloatAsync(
|
||||
HumidityAddress,
|
||||
ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次读取温湿度
|
||||
/// </summary>
|
||||
public async Task<(float Temperature, float Humidity)> ReadAllAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ushort[] regs = await ReadHoldingRegistersAsync(
|
||||
_slaveId,
|
||||
TemperatureAddress,
|
||||
4,
|
||||
ct);
|
||||
|
||||
return
|
||||
(
|
||||
ToFloat(regs[0], regs[1]),
|
||||
ToFloat(regs[2], regs[3])
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<float> ReadFloatAsync(
|
||||
ushort address,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ushort[] regs = await ReadHoldingRegistersAsync(
|
||||
_slaveId,
|
||||
address,
|
||||
2,
|
||||
ct);
|
||||
|
||||
return ToFloat(regs[0], regs[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两个寄存器转Float
|
||||
/// </summary>
|
||||
private static float ToFloat(
|
||||
ushort highWord,
|
||||
ushort lowWord)
|
||||
{
|
||||
byte[] bytes =
|
||||
{
|
||||
(byte)(highWord >> 8),
|
||||
(byte)highWord,
|
||||
(byte)(lowWord >> 8),
|
||||
(byte)lowWord
|
||||
};
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
public class SDE710FH_A : ModbusTcp
|
||||
public class UMC1300 : ModbusTcp
|
||||
{
|
||||
// 从站地址(设备 ID)
|
||||
private readonly byte _slaveId;
|
||||
@@ -27,7 +27,7 @@ namespace DeviceCommand.Devices
|
||||
/// <param name="port">端口,默认 502</param>
|
||||
/// <param name="sendTimeoutMs">发送超时(毫秒)</param>
|
||||
/// <param name="receiveTimeoutMs">接收超时(毫秒)</param>
|
||||
public SDE710FH_A(byte slaveId, string ip, int port = 502,
|
||||
public UMC1300(byte slaveId, string ip, int port = 502,
|
||||
int sendTimeoutMs = 3000, int receiveTimeoutMs = 3000)
|
||||
: base()
|
||||
{
|
||||
@@ -82,21 +82,5 @@ namespace DeviceCommand.Devices
|
||||
raw[3] * SCALE // 湿度 MV
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 写入 ====================
|
||||
|
||||
/// <summary>设定温度 MV(工程值)</summary>
|
||||
public async Task WriteTemperatureMVAsync(float value, CancellationToken ct = default)
|
||||
{
|
||||
ushort raw = (ushort)(value / SCALE);
|
||||
await WriteSingleRegisterAsync(_slaveId, ADDR_TEMP_MV, raw, ct);
|
||||
}
|
||||
|
||||
/// <summary>设定湿度 MV(工程值)</summary>
|
||||
public async Task WriteHumidityMVAsync(float value, CancellationToken ct = default)
|
||||
{
|
||||
ushort raw = (ushort)(value / SCALE);
|
||||
await WriteSingleRegisterAsync(_slaveId, ADDR_HUMID_MV, raw, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user