Files
ConeCalorimeter/ConeCalorimeter/Services/ModbusRealtimeDataService.cs
GukSang.Jin 95a4d4d671 更新
2026-05-21 19:41:41 +08:00

127 lines
5.5 KiB
C#

using System.Diagnostics;
using ConeCalorimeter.Models;
namespace ConeCalorimeter.Services;
public sealed class ModbusRealtimeDataService : IRealtimeDataService
{
private const ushort CurrentMassRegister = 1;
private const ushort OxygenRegister = 10;
private const ushort OrificeFlowRegister = 14;
private const ushort OrificePressureRegister = 16;
private const ushort CarbonMonoxideRegister = 18;
private const ushort CarbonDioxideRegister = 20;
private const ushort ConeTemperatureRegister = 26;
private const ushort OrificeTemperatureRegister = 30;
private const ushort SampleTemperatureRegister = 36;
private const ushort CFactorRegister = 312;
private const ushort HeatReleaseRateRegister = 354;
private const ushort PeakHeatReleaseRateRegister = 376;
private const ushort Qa180Register = 366;
private const ushort Qa300Register = 370;
private const ushort SmokeProductionRegister = 392;
private const ushort IrradianceRegister = 410;
private const ushort IgnitionSecondsRegister = 1014;
private const ushort TestSecondsRegister = 1015;
private const ushort M3FlameMonitorBit = 3;
private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
private readonly HashSet<ushort> _loggedFloatDiagnostics = [];
private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
public ModbusRealtimeDataService(ITcpDeviceConnectionService tcpDeviceConnectionService)
{
_tcpDeviceConnectionService = tcpDeviceConnectionService;
}
public RealtimeSnapshot GetCurrentSnapshot(TimeSpan elapsed)
{
return new RealtimeSnapshot(
OrificeFlow: ReadRangedFloatOrEmpty("OrificeFlow", OrificeFlowRegister, 0, 10),
OrificePressure: ReadRangedFloatOrEmpty("OrificePressure", OrificePressureRegister, -5000, 5000),
OrificeTemperature: ReadRangedFloatOrEmpty("OrificeTemperature", OrificeTemperatureRegister, 200, 700),
ConeTemperature: ReadRangedFloatOrEmpty("ConeTemperature", ConeTemperatureRegister, 0, 1200),
SampleTemperature: ReadRangedFloatOrEmpty("SampleTemperature", SampleTemperatureRegister, -50, 1200),
Irradiance: ReadRangedFloatOrEmpty("Irradiance", IrradianceRegister, 0, 100),
FlameDetected: ReadCoilOrFalse(M3FlameMonitorBit),
Oxygen: ReadRangedFloatOrEmpty("O2", OxygenRegister, 0, 30),
CarbonDioxide: ReadRangedFloatOrEmpty("CO2", CarbonDioxideRegister, 0, 20),
CarbonMonoxide: ReadRangedFloatOrEmpty("CO", CarbonMonoxideRegister, 0, 10),
HeatReleaseRate: ReadRangedFloatOrEmpty("HeatReleaseRate", HeatReleaseRateRegister, 0, 5000),
PeakHeatReleaseRate: ReadRangedFloatOrEmpty("PeakHeatReleaseRate", PeakHeatReleaseRateRegister, 0, 5000),
CFactor: ReadRangedFloatOrEmpty("CFactor", CFactorRegister, 0, 1000),
Qa180: ReadRangedFloatOrEmpty("Qa180", Qa180Register, 0, 1000),
Qa300: ReadRangedFloatOrEmpty("Qa300", Qa300Register, 0, 1000),
TotalHeatRelease: double.NaN,
SmokeProduction: ReadRangedFloatOrEmpty("SmokeProduction", SmokeProductionRegister, 0, 100),
CurrentMass: ReadRangedFloatOrEmpty("CurrentMass", CurrentMassRegister, 0, 100000),
InitialMass: double.NaN,
MassLoss: double.NaN,
MassLossRate: double.NaN,
IgnitionSeconds: ReadInt16OrEmpty(IgnitionSecondsRegister),
TestSeconds: ReadInt16OrEmpty(TestSecondsRegister),
TotalSmoke: double.NaN);
}
private double ReadRangedFloatOrEmpty(string label, ushort registerAddress, double minimum, double maximum)
{
if (!_tcpDeviceConnectionService.TryReadFloatValues(registerAddress, out var result))
{
return double.NaN;
}
if (!ModbusFloatSelector.TrySelectRangedFloat(result, minimum, maximum, out var byteOrder, out var value, out var matchCount))
{
LogFloatDiagnostic(label, registerAddress, result, null, matchCount);
return double.NaN;
}
LogFloatDiagnostic(label, registerAddress, result, byteOrder, matchCount);
return NormalizeRealtimeValue(value);
}
private void LogFloatDiagnostic(
string label,
ushort registerAddress,
ModbusFloatReadResult result,
ModbusFloatByteOrder? selectedByteOrder,
int matchCount)
{
if (selectedByteOrder is null)
{
if (!_loggedInvalidFloatDiagnostics.Add(registerAddress))
{
return;
}
}
else if (!_loggedFloatDiagnostics.Add(registerAddress))
{
return;
}
var selectedText = selectedByteOrder?.ToString() ?? "none";
Debug.WriteLine(
$"Realtime float {label} register {registerAddress} raw [{result.RawHex}], "
+ $"ABCD={result.Abcd:G9}, CDAB={result.Cdab:G9}, "
+ $"BADC={result.Badc:G9}, DCBA={result.Dcba:G9}, "
+ $"selected={selectedText}, matches={matchCount}.");
}
private static double NormalizeRealtimeValue(double value)
{
return Math.Abs(value) < 0.005 ? 0 : value;
}
private int ReadInt16OrEmpty(ushort registerAddress)
{
return _tcpDeviceConnectionService.TryReadInt16(registerAddress, out var value)
? value
: -1;
}
private bool ReadCoilOrFalse(ushort coilAddress)
{
return _tcpDeviceConnectionService.TryReadCoil(coilAddress, out var value) && value;
}
}