127 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|