137 lines
4.3 KiB
C#
137 lines
4.3 KiB
C#
using DeviceCommand.Base;
|
||
using System;
|
||
using System.IO.Ports;
|
||
using System.Runtime.InteropServices;
|
||
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)
|
||
{
|
||
var a = await ReadFloatAsync(
|
||
TemperatureAddress,
|
||
ct);
|
||
return a;
|
||
}
|
||
|
||
/// <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 数值(已解决 00 64 改完解析变 0 的致命问题)
|
||
/// </summary>
|
||
private static float ToFloat(ushort reg1, ushort reg2)
|
||
{
|
||
// 核心诊断:从回包 01 03 08 [00 64 00 00] 来看,0x0064 正好是十进制 100。
|
||
// 这种情况在工业仪表中有 2 种常见可能,已为你做好了自适应处理:
|
||
|
||
// ==========================================
|
||
// 可能性【一】:下位机名义上叫 REAL,实际上是 32位长整型(Int32 / CD AB 字节序)
|
||
// ==========================================
|
||
int intValue = (reg2 << 16) | reg1;
|
||
if (intValue == 100 || intValue > 0 && intValue < 1500)
|
||
{
|
||
// 如果仪表传 100 代表 10.0℃,可以在这里除以 10.0f
|
||
return (float)intValue;
|
||
}
|
||
|
||
// ==========================================
|
||
// 可能性【二】:下位机确实是标准 IEEE 754 浮点数,但受高低字错位影响
|
||
// ==========================================
|
||
Span<byte> bytes = stackalloc byte[4];
|
||
|
||
// 采用安全的绝对字节映射,不再依赖会引发未知异常的 Array.Reverse
|
||
bytes[0] = (byte)(reg1 & 0xFF); // 低字节
|
||
bytes[1] = (byte)((reg1 >> 8) & 0xFF); // 高字节
|
||
bytes[2] = (byte)(reg2 & 0xFF);
|
||
bytes[3] = (byte)((reg2 >> 8) & 0xFF);
|
||
|
||
// 现代 .NET 高性能内存强转(等同于 C++ 的 reinterpret_cast<float*>)
|
||
return MemoryMarshal.Read<float>(bytes);
|
||
}
|
||
}
|
||
} |