This commit is contained in:
GukSang.Jin
2026-05-27 17:59:56 +08:00
parent cdb2190728
commit 0139c4475b
8 changed files with 80 additions and 43 deletions

View File

@@ -12,6 +12,7 @@ public sealed record RealtimeDataRecord(
double Oxygen, double Oxygen,
double CarbonDioxide, double CarbonDioxide,
double CarbonMonoxide, double CarbonMonoxide,
double Absorbance,
double HeatReleaseRate, double HeatReleaseRate,
double PeakHeatReleaseRate, double PeakHeatReleaseRate,
double CFactor, double CFactor,
@@ -43,6 +44,7 @@ public sealed record RealtimeDataRecord(
snapshot.Oxygen, snapshot.Oxygen,
snapshot.CarbonDioxide, snapshot.CarbonDioxide,
snapshot.CarbonMonoxide, snapshot.CarbonMonoxide,
snapshot.Absorbance,
snapshot.HeatReleaseRate, snapshot.HeatReleaseRate,
snapshot.PeakHeatReleaseRate, snapshot.PeakHeatReleaseRate,
snapshot.CFactor, snapshot.CFactor,

View File

@@ -11,6 +11,7 @@ public sealed record RealtimeSnapshot(
double Oxygen, double Oxygen,
double CarbonDioxide, double CarbonDioxide,
double CarbonMonoxide, double CarbonMonoxide,
double Absorbance,
double HeatReleaseRate, double HeatReleaseRate,
double PeakHeatReleaseRate, double PeakHeatReleaseRate,
double CFactor, double CFactor,

View File

@@ -16,6 +16,9 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
private const ushort ConeTemperatureRegister = 26; private const ushort ConeTemperatureRegister = 26;
private const ushort OrificeTemperatureRegister = 30; private const ushort OrificeTemperatureRegister = 30;
private const ushort SampleTemperatureRegister = 36; private const ushort SampleTemperatureRegister = 36;
private const ushort AbsorbanceRegister = 120;
private const double AbsorbanceStableTolerance = 0.25;
private const int AbsorbanceStableReadCount = 2;
private const ushort CFactorRegister = 312; private const ushort CFactorRegister = 312;
private const ushort HeatReleaseRateRegister = 354; private const ushort HeatReleaseRateRegister = 354;
private const ushort PeakHeatReleaseRateRegister = 376; private const ushort PeakHeatReleaseRateRegister = 376;
@@ -40,6 +43,9 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = []; private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
private readonly Dictionary<int, double> _stableRealtimeValues = []; private readonly Dictionary<int, double> _stableRealtimeValues = [];
private readonly Dictionary<int, int> _pendingZeroReadCounts = []; private readonly Dictionary<int, int> _pendingZeroReadCounts = [];
private double? _stableAbsorbance;
private double? _pendingAbsorbance;
private int _pendingAbsorbanceReadCount;
private ModbusFloatByteOrder? _preferredFloatByteOrder; private ModbusFloatByteOrder? _preferredFloatByteOrder;
public ModbusRealtimeDataService( public ModbusRealtimeDataService(
@@ -69,6 +75,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
Oxygen: ReadRangedFloatOrEmpty("O2", OxygenRegister, 0, 30), Oxygen: ReadRangedFloatOrEmpty("O2", OxygenRegister, 0, 30),
CarbonDioxide: ReadRangedFloatOrEmpty("CO2", CarbonDioxideRegister, 0, 20), CarbonDioxide: ReadRangedFloatOrEmpty("CO2", CarbonDioxideRegister, 0, 20),
CarbonMonoxide: ReadRangedFloatOrEmpty("CO", CarbonMonoxideRegister, 0, 10), CarbonMonoxide: ReadRangedFloatOrEmpty("CO", CarbonMonoxideRegister, 0, 10),
Absorbance: ReadAbsorbanceOrEmpty(),
HeatReleaseRate: ReadRangedFloatOrEmpty("HeatReleaseRate", HeatReleaseRateRegister, 0, 5000), HeatReleaseRate: ReadRangedFloatOrEmpty("HeatReleaseRate", HeatReleaseRateRegister, 0, 5000),
PeakHeatReleaseRate: ReadRangedFloatOrEmpty("PeakHeatReleaseRate", PeakHeatReleaseRateRegister, 0, 5000), PeakHeatReleaseRate: ReadRangedFloatOrEmpty("PeakHeatReleaseRate", PeakHeatReleaseRateRegister, 0, 5000),
CFactor: ReadRangedFloatOrEmpty("CFactor", CFactorRegister, 0, 1000), CFactor: ReadRangedFloatOrEmpty("CFactor", CFactorRegister, 0, 1000),
@@ -102,6 +109,49 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
return StabilizeRealtimeValue(registerAddress, value); return StabilizeRealtimeValue(registerAddress, value);
} }
private double ReadAbsorbanceOrEmpty()
{
var value = ReadRangedFloatOrEmpty("Absorbance", AbsorbanceRegister, 0, 100);
return double.IsFinite(value) && TryUpdateStableAbsorbance(value, out var stableValue)
? stableValue
: double.NaN;
}
private bool TryUpdateStableAbsorbance(double value, out double stableValue)
{
stableValue = _stableAbsorbance ?? value;
if (_stableAbsorbance.HasValue
&& Math.Abs(value - _stableAbsorbance.Value) <= AbsorbanceStableTolerance)
{
_pendingAbsorbance = null;
_pendingAbsorbanceReadCount = 0;
stableValue = _stableAbsorbance.Value;
return true;
}
if (!_pendingAbsorbance.HasValue
|| Math.Abs(value - _pendingAbsorbance.Value) > AbsorbanceStableTolerance)
{
_pendingAbsorbance = value;
_pendingAbsorbanceReadCount = 1;
return false;
}
_pendingAbsorbanceReadCount++;
if (_pendingAbsorbanceReadCount < AbsorbanceStableReadCount)
{
return false;
}
_stableAbsorbance = value;
_pendingAbsorbance = null;
_pendingAbsorbanceReadCount = 0;
stableValue = value;
return true;
}
private bool TrySelectRealtimeFloat( private bool TrySelectRealtimeFloat(
ModbusFloatReadResult result, ModbusFloatReadResult result,
double minimum, double minimum,

View File

@@ -13,19 +13,17 @@ public sealed class NpoiRealtimeDataExportService : IRealtimeDataExportService
"O2 (%)", "O2 (%)",
"CO2 (%)", "CO2 (%)",
"CO (%)", "CO (%)",
"吸光率%",
"孔板压差 (Pa)", "孔板压差 (Pa)",
"孔板温度 (K)", "孔板温度 (K)",
"HRR", "HRR",
"热释放速率180",
"热释放速率300",
"THR (MJ/m2)", "THR (MJ/m2)",
"SPR", "SPR",
"TSR (m2)", "TSR (m2)",
"MLR (g/s)", "MLR (g/s)",
"热释放KW/m2", "热释放KW/m2",
"EHC", "EHC",
"损失质量", "损失质量"
"试样温度(℃)"
]; ];
public void Export(string outputPath, IReadOnlyList<RealtimeDataRecord> records) public void Export(string outputPath, IReadOnlyList<RealtimeDataRecord> records)
@@ -60,19 +58,17 @@ public sealed class NpoiRealtimeDataExportService : IRealtimeDataExportService
SetNumeric(row, 1, record.Oxygen); SetNumeric(row, 1, record.Oxygen);
SetNumeric(row, 2, record.CarbonDioxide); SetNumeric(row, 2, record.CarbonDioxide);
SetNumeric(row, 3, record.CarbonMonoxide); SetNumeric(row, 3, record.CarbonMonoxide);
SetNumeric(row, 4, record.OrificePressure); SetNumeric(row, 4, record.Absorbance);
SetNumeric(row, 5, record.OrificeTemperature); SetNumeric(row, 5, record.OrificePressure);
SetNumeric(row, 6, record.HeatReleaseRate); SetNumeric(row, 6, record.OrificeTemperature);
SetNumeric(row, 7, record.Qa180); SetNumeric(row, 7, record.HeatReleaseRate);
SetNumeric(row, 8, record.Qa300); SetNumeric(row, 8, record.TotalHeatRelease);
SetNumeric(row, 9, record.TotalHeatRelease); SetNumeric(row, 9, record.SmokeProduction);
SetNumeric(row, 10, record.SmokeProduction); SetNumeric(row, 10, record.TotalSmoke);
SetNumeric(row, 11, record.TotalSmoke); SetNumeric(row, 11, record.MassLossRate);
SetNumeric(row, 12, record.MassLossRate); SetNumeric(row, 12, record.HeatReleaseRateKw);
SetNumeric(row, 13, record.HeatReleaseRateKw); SetNumeric(row, 13, record.EffectiveHeatOfCombustion);
SetNumeric(row, 14, record.EffectiveHeatOfCombustion); SetNumeric(row, 14, record.MassLoss);
SetNumeric(row, 15, record.MassLoss);
SetNumeric(row, 16, record.SampleTemperature);
} }
for (var i = 0; i < Headers.Length; i++) for (var i = 0; i < Headers.Length; i++)

View File

@@ -22,13 +22,13 @@ public sealed class NpoiReportExportService : IReportExportService
"O2 (%)", "O2 (%)",
"CO2 (%)", "CO2 (%)",
"CO (%)", "CO (%)",
"吸光率%",
"孔板压差 (Pa)", "孔板压差 (Pa)",
"孔板温度 (K)", "孔板温度 (K)",
"MLR (g/s)", "MLR (g/s)",
"热释放KW/m2", "热释放KW/m2",
"EHC", "EHC",
"损失质量", "损失质量",
"试样温度(℃)",
"Timestamp", "Timestamp",
"点火计时(s)", "点火计时(s)",
"火焰监测", "火焰监测",
@@ -37,8 +37,6 @@ public sealed class NpoiReportExportService : IReportExportService
"当前质量", "当前质量",
"初始质量", "初始质量",
"最大热释放", "最大热释放",
"热释放速率180",
"热释放速率300",
"C-系数" "C-系数"
]; ];
@@ -176,13 +174,13 @@ public sealed class NpoiReportExportService : IReportExportService
SetNumeric(row, 7, record.Oxygen); SetNumeric(row, 7, record.Oxygen);
SetNumeric(row, 8, record.CarbonDioxide); SetNumeric(row, 8, record.CarbonDioxide);
SetNumeric(row, 9, record.CarbonMonoxide); SetNumeric(row, 9, record.CarbonMonoxide);
SetNumeric(row, 10, record.OrificePressure); SetNumeric(row, 10, record.Absorbance);
SetNumeric(row, 11, record.OrificeTemperature); SetNumeric(row, 11, record.OrificePressure);
SetNumeric(row, 12, record.MassLossRate); SetNumeric(row, 12, record.OrificeTemperature);
SetNumeric(row, 13, record.HeatReleaseRateKw); SetNumeric(row, 13, record.MassLossRate);
SetNumeric(row, 14, record.EffectiveHeatOfCombustion); SetNumeric(row, 14, record.HeatReleaseRateKw);
SetNumeric(row, 15, record.MassLoss); SetNumeric(row, 15, record.EffectiveHeatOfCombustion);
SetNumeric(row, 16, record.SampleTemperature); SetNumeric(row, 16, record.MassLoss);
row.CreateCell(17).SetCellValue(record.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture)); row.CreateCell(17).SetCellValue(record.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture));
SetNumeric(row, 18, record.IgnitionSeconds >= 0 ? record.IgnitionSeconds : double.NaN); SetNumeric(row, 18, record.IgnitionSeconds >= 0 ? record.IgnitionSeconds : double.NaN);
SetNumeric(row, 19, record.FlameDetected ? 1 : 0); SetNumeric(row, 19, record.FlameDetected ? 1 : 0);
@@ -191,9 +189,7 @@ public sealed class NpoiReportExportService : IReportExportService
SetNumeric(row, 22, record.CurrentMass); SetNumeric(row, 22, record.CurrentMass);
SetNumeric(row, 23, record.InitialMass); SetNumeric(row, 23, record.InitialMass);
SetNumeric(row, 24, record.PeakHeatReleaseRate); SetNumeric(row, 24, record.PeakHeatReleaseRate);
SetNumeric(row, 25, record.Qa180); SetNumeric(row, 25, record.CFactor);
SetNumeric(row, 26, record.Qa300);
SetNumeric(row, 27, record.CFactor);
} }
for (var i = 0; i < DataHeaders.Length; i++) for (var i = 0; i < DataHeaders.Length; i++)

View File

@@ -20,7 +20,7 @@ public sealed record SerialScaleOptions(
public const string RegisterEnvironmentVariable = "CONE_SCALE_REGISTER"; public const string RegisterEnvironmentVariable = "CONE_SCALE_REGISTER";
public static SerialScaleOptions Default { get; } = new( public static SerialScaleOptions Default { get; } = new(
"COM1", "COM3",
9600, 9600,
Parity.None, Parity.None,
8, 8,

View File

@@ -17,9 +17,8 @@ public sealed class RealtimeDataRowViewModel
CarbonMonoxideText = Format(record.CarbonMonoxide); CarbonMonoxideText = Format(record.CarbonMonoxide);
OrificePressureText = Format(record.OrificePressure); OrificePressureText = Format(record.OrificePressure);
OrificeTemperatureText = Format(record.OrificeTemperature); OrificeTemperatureText = Format(record.OrificeTemperature);
AbsorbanceText = Format(record.Absorbance);
Hrr50Text = Format(record.HeatReleaseRate); Hrr50Text = Format(record.HeatReleaseRate);
Qa180Text = Format(record.Qa180);
Qa300Text = Format(record.Qa300);
ThrText = Format(record.TotalHeatRelease); ThrText = Format(record.TotalHeatRelease);
Spr50Text = Format(record.SmokeProduction); Spr50Text = Format(record.SmokeProduction);
TsrText = Format(record.TotalSmoke); TsrText = Format(record.TotalSmoke);
@@ -27,7 +26,6 @@ public sealed class RealtimeDataRowViewModel
HeatReleaseText = Format(record.HeatReleaseRateKw); HeatReleaseText = Format(record.HeatReleaseRateKw);
EhcText = Format(record.EffectiveHeatOfCombustion); EhcText = Format(record.EffectiveHeatOfCombustion);
MassLossText = Format(record.MassLoss); MassLossText = Format(record.MassLoss);
SampleTemperatureText = Format(record.SampleTemperature, "0.0");
} }
public string TimeText { get; init; } = string.Empty; public string TimeText { get; init; } = string.Empty;
@@ -42,12 +40,10 @@ public sealed class RealtimeDataRowViewModel
public string OrificeTemperatureText { get; init; } = string.Empty; public string OrificeTemperatureText { get; init; } = string.Empty;
public string AbsorbanceText { get; init; } = string.Empty;
public string Hrr50Text { get; init; } = string.Empty; public string Hrr50Text { get; init; } = string.Empty;
public string Qa180Text { get; init; } = string.Empty;
public string Qa300Text { get; init; } = string.Empty;
public string ThrText { get; init; } = string.Empty; public string ThrText { get; init; } = string.Empty;
public string Spr50Text { get; init; } = string.Empty; public string Spr50Text { get; init; } = string.Empty;
@@ -62,8 +58,6 @@ public sealed class RealtimeDataRowViewModel
public string MassLossText { get; init; } = string.Empty; public string MassLossText { get; init; } = string.Empty;
public string SampleTemperatureText { get; init; } = string.Empty;
private static string Format(double value, string format = "0.00") private static string Format(double value, string format = "0.00")
{ {
return double.IsFinite(value) ? value.ToString(format, CultureInfo.InvariantCulture) : string.Empty; return double.IsFinite(value) ? value.ToString(format, CultureInfo.InvariantCulture) : string.Empty;

View File

@@ -138,11 +138,10 @@
<DataGridTextColumn Header="O2 (%)" Binding="{Binding OxygenText}" Width="82" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="O2 (%)" Binding="{Binding OxygenText}" Width="82" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="CO2 (%)" Binding="{Binding CarbonDioxideText}" Width="88" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="CO2 (%)" Binding="{Binding CarbonDioxideText}" Width="88" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="CO (%)" Binding="{Binding CarbonMonoxideText}" Width="82" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="CO (%)" Binding="{Binding CarbonMonoxideText}" Width="82" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="吸光率%" Binding="{Binding AbsorbanceText}" Width="88" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="孔板压差 (Pa)" Binding="{Binding OrificePressureText}" Width="116" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="孔板压差 (Pa)" Binding="{Binding OrificePressureText}" Width="116" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="孔板温度 (K)" Binding="{Binding OrificeTemperatureText}" Width="116" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="孔板温度 (K)" Binding="{Binding OrificeTemperatureText}" Width="116" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="HRR" Binding="{Binding Hrr50Text}" Width="78" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="HRR" Binding="{Binding Hrr50Text}" Width="78" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="热释放速率180" Binding="{Binding Qa180Text}" Width="118" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="热释放速率300" Binding="{Binding Qa300Text}" Width="118" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="THR (MJ/m2)" Binding="{Binding ThrText}" Width="108" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="THR (MJ/m2)" Binding="{Binding ThrText}" Width="108" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="SPR" Binding="{Binding Spr50Text}" Width="78" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="SPR" Binding="{Binding Spr50Text}" Width="78" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="TSR (m2)" Binding="{Binding TsrText}" Width="88" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="TSR (m2)" Binding="{Binding TsrText}" Width="88" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
@@ -150,7 +149,6 @@
<DataGridTextColumn Header="热释放KW/m2" Binding="{Binding HeatReleaseText}" Width="122" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="热释放KW/m2" Binding="{Binding HeatReleaseText}" Width="122" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="EHC" Binding="{Binding EhcText}" Width="70" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="EHC" Binding="{Binding EhcText}" Width="70" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="损失质量" Binding="{Binding MassLossText}" Width="84" ElementStyle="{StaticResource RealtimeTextElementStyle}" /> <DataGridTextColumn Header="损失质量" Binding="{Binding MassLossText}" Width="84" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
<DataGridTextColumn Header="试样温度(℃)" Binding="{Binding SampleTemperatureText}" Width="124" ElementStyle="{StaticResource RealtimeTextElementStyle}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>