更新
This commit is contained in:
@@ -18,8 +18,6 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
||||
private const ushort OrificeTemperatureRegister = 30;
|
||||
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 HeatReleaseRateRegister = 354;
|
||||
private const ushort PeakHeatReleaseRateRegister = 376;
|
||||
@@ -44,9 +42,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
||||
private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
|
||||
private readonly Dictionary<int, double> _stableRealtimeValues = [];
|
||||
private readonly Dictionary<int, int> _pendingZeroReadCounts = [];
|
||||
private double? _stableAbsorbance;
|
||||
private double? _pendingAbsorbance;
|
||||
private int _pendingAbsorbanceReadCount;
|
||||
private double? _lastAbsorbanceValue;
|
||||
private ModbusFloatByteOrder? _preferredFloatByteOrder;
|
||||
|
||||
public ModbusRealtimeDataService(
|
||||
@@ -113,8 +109,14 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
||||
private double ReadAbsorbanceOrEmpty()
|
||||
{
|
||||
var value = ReadRangedFloatOrEmpty("Absorbance", AbsorbanceRegister, 0, 100);
|
||||
return double.IsFinite(value) && TryUpdateStableAbsorbance(value, out var stableValue)
|
||||
? stableValue
|
||||
if (double.IsFinite(value))
|
||||
{
|
||||
_lastAbsorbanceValue = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
return _lastAbsorbanceValue.HasValue
|
||||
? _lastAbsorbanceValue.Value
|
||||
: double.NaN;
|
||||
}
|
||||
|
||||
@@ -126,41 +128,6 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
||||
: 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(
|
||||
ModbusFloatReadResult result,
|
||||
double minimum,
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class RealtimeDataRowViewModel
|
||||
CarbonMonoxideText = Format(record.CarbonMonoxide);
|
||||
OrificePressureText = Format(record.OrificePressure);
|
||||
OrificeTemperatureText = Format(record.OrificeTemperature);
|
||||
AbsorbanceText = Format(record.Absorbance);
|
||||
AbsorbanceText = Format(record.Absorbance, "0.00000");
|
||||
Hrr50Text = Format(record.HeatReleaseRate);
|
||||
ThrText = Format(record.TotalHeatRelease);
|
||||
Spr50Text = Format(record.SmokeProduction);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Windows.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ConeCalorimeter.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace ConeCalorimeter.ViewModels;
|
||||
|
||||
@@ -15,6 +16,15 @@ public sealed class SmokeDensitySettingsViewModel : PageViewModel
|
||||
private const double AbsorbanceMaximum = 100;
|
||||
private const double AbsorbanceStableTolerance = 0.25;
|
||||
private const int AbsorbanceStableReadCount = 2;
|
||||
private const double AbsorbanceZeroTolerance = 0.000005;
|
||||
private const string AbsorbanceFormat = "0.00000";
|
||||
private static readonly ModbusFloatByteOrder[] AbsorbanceByteOrders =
|
||||
[
|
||||
ModbusFloatByteOrder.Abcd,
|
||||
ModbusFloatByteOrder.Cdab,
|
||||
ModbusFloatByteOrder.Badc,
|
||||
ModbusFloatByteOrder.Dcba
|
||||
];
|
||||
|
||||
private readonly Action _closeAction;
|
||||
private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
|
||||
@@ -25,6 +35,7 @@ public sealed class SmokeDensitySettingsViewModel : PageViewModel
|
||||
private double? _stableAbsorbance;
|
||||
private double? _pendingAbsorbance;
|
||||
private int _pendingAbsorbanceReadCount;
|
||||
private ModbusFloatByteOrder? _absorbanceByteOrder;
|
||||
private string _absorbanceText = "";
|
||||
private string _lastCalibration = "待校准";
|
||||
|
||||
@@ -125,12 +136,7 @@ public sealed class SmokeDensitySettingsViewModel : PageViewModel
|
||||
|
||||
private void RefreshDeviceValues()
|
||||
{
|
||||
if (!TryReadRangedFloatValue(
|
||||
"Absorbance",
|
||||
AbsorbanceRegister,
|
||||
AbsorbanceMinimum,
|
||||
AbsorbanceMaximum,
|
||||
out var value))
|
||||
if (!TryReadAbsorbanceValue(out var value))
|
||||
{
|
||||
if (!_stableAbsorbance.HasValue)
|
||||
{
|
||||
@@ -142,45 +148,71 @@ public sealed class SmokeDensitySettingsViewModel : PageViewModel
|
||||
|
||||
if (TryUpdateStableAbsorbance(value, out var stableValue))
|
||||
{
|
||||
AbsorbanceText = stableValue.ToString("0.00", CultureInfo.InvariantCulture);
|
||||
AbsorbanceText = FormatAbsorbance(stableValue);
|
||||
}
|
||||
else if (_stableAbsorbance.HasValue)
|
||||
{
|
||||
AbsorbanceText = FormatAbsorbance(_stableAbsorbance.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryReadRangedFloatValue(
|
||||
string label,
|
||||
ushort registerAddress,
|
||||
double minimum,
|
||||
double maximum,
|
||||
out double value)
|
||||
private bool TryReadAbsorbanceValue(out double value)
|
||||
{
|
||||
value = double.NaN;
|
||||
|
||||
if (!_tcpDeviceConnectionService.TryReadFloatValues(registerAddress, out var result))
|
||||
if (!_tcpDeviceConnectionService.TryReadFloatValues(AbsorbanceRegister, out var result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ModbusFloatSelector.TrySelectRangedFloat(result, minimum, maximum, out var byteOrder, out var selectedValue, out var matchCount))
|
||||
var preferredByteOrder = _absorbanceByteOrder;
|
||||
if (!ModbusFloatSelector.TrySelectRangedFloat(
|
||||
result,
|
||||
AbsorbanceMinimum,
|
||||
AbsorbanceMaximum,
|
||||
out var byteOrder,
|
||||
out var selectedValue,
|
||||
out var matchCount,
|
||||
preferredByteOrder))
|
||||
{
|
||||
LogFloatDiagnostic(label, registerAddress, result, null, matchCount);
|
||||
return false;
|
||||
if (!TrySelectSingleNonZeroAbsorbanceCandidate(result, out byteOrder, out selectedValue))
|
||||
{
|
||||
LogFloatDiagnostic(result, null, matchCount, preferredByteOrder);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LogFloatDiagnostic(label, registerAddress, result, byteOrder, matchCount);
|
||||
if (!preferredByteOrder.HasValue
|
||||
&& IsZeroLikeAbsorbance(selectedValue)
|
||||
&& TrySelectSingleNonZeroAbsorbanceCandidate(result, out var nonZeroByteOrder, out var nonZeroValue))
|
||||
{
|
||||
byteOrder = nonZeroByteOrder;
|
||||
selectedValue = nonZeroValue;
|
||||
}
|
||||
|
||||
LogFloatDiagnostic(result, byteOrder, matchCount, preferredByteOrder);
|
||||
RememberAbsorbanceByteOrder(byteOrder, selectedValue);
|
||||
value = NormalizeDisplayValue(selectedValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryUpdateStableAbsorbance(double value, out double stableValue)
|
||||
{
|
||||
stableValue = _stableAbsorbance ?? value;
|
||||
value = NormalizeDisplayValue(value);
|
||||
|
||||
if (_stableAbsorbance.HasValue
|
||||
&& Math.Abs(value - _stableAbsorbance.Value) <= AbsorbanceStableTolerance)
|
||||
if (!_stableAbsorbance.HasValue)
|
||||
{
|
||||
_pendingAbsorbance = null;
|
||||
_pendingAbsorbanceReadCount = 0;
|
||||
stableValue = _stableAbsorbance.Value;
|
||||
AcceptStableAbsorbance(value);
|
||||
stableValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
stableValue = _stableAbsorbance.Value;
|
||||
|
||||
if (Math.Abs(value - _stableAbsorbance.Value) <= AbsorbanceStableTolerance)
|
||||
{
|
||||
AcceptStableAbsorbance(value);
|
||||
stableValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -199,44 +231,99 @@ public sealed class SmokeDensitySettingsViewModel : PageViewModel
|
||||
return false;
|
||||
}
|
||||
|
||||
_stableAbsorbance = value;
|
||||
_pendingAbsorbance = null;
|
||||
_pendingAbsorbanceReadCount = 0;
|
||||
AcceptStableAbsorbance(value);
|
||||
stableValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AcceptStableAbsorbance(double value)
|
||||
{
|
||||
_stableAbsorbance = value;
|
||||
_pendingAbsorbance = null;
|
||||
_pendingAbsorbanceReadCount = 0;
|
||||
}
|
||||
|
||||
private static bool TrySelectSingleNonZeroAbsorbanceCandidate(
|
||||
ModbusFloatReadResult result,
|
||||
out ModbusFloatByteOrder byteOrder,
|
||||
out double value)
|
||||
{
|
||||
var matches = AbsorbanceByteOrders
|
||||
.Select(candidate => new
|
||||
{
|
||||
ByteOrder = candidate,
|
||||
Value = result.GetValue(candidate)
|
||||
})
|
||||
.Where(candidate => ModbusFloatSelector.IsInRange(
|
||||
candidate.Value,
|
||||
AbsorbanceMinimum,
|
||||
AbsorbanceMaximum)
|
||||
&& !IsZeroLikeAbsorbance(candidate.Value))
|
||||
.ToArray();
|
||||
|
||||
if (matches.Length != 1)
|
||||
{
|
||||
byteOrder = default;
|
||||
value = double.NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
byteOrder = matches[0].ByteOrder;
|
||||
value = matches[0].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RememberAbsorbanceByteOrder(ModbusFloatByteOrder byteOrder, double value)
|
||||
{
|
||||
if (!IsZeroLikeAbsorbance(value))
|
||||
{
|
||||
_absorbanceByteOrder = byteOrder;
|
||||
}
|
||||
}
|
||||
|
||||
private void LogFloatDiagnostic(
|
||||
string label,
|
||||
ushort registerAddress,
|
||||
ModbusFloatReadResult result,
|
||||
ModbusFloatByteOrder? selectedByteOrder,
|
||||
int matchCount)
|
||||
int matchCount,
|
||||
ModbusFloatByteOrder? preferredByteOrder)
|
||||
{
|
||||
if (selectedByteOrder is null)
|
||||
{
|
||||
if (!_loggedInvalidFloatDiagnostics.Add(registerAddress))
|
||||
if (!_loggedInvalidFloatDiagnostics.Add(AbsorbanceRegister))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!_loggedFloatDiagnostics.Add(registerAddress))
|
||||
else if (!_loggedFloatDiagnostics.Add(AbsorbanceRegister))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedText = selectedByteOrder?.ToString() ?? "none";
|
||||
|
||||
Debug.WriteLine(
|
||||
$"Smoke density float {label} register {registerAddress} raw [{result.RawHex}], "
|
||||
var preferredText = preferredByteOrder?.ToString() ?? "none";
|
||||
var message =
|
||||
$"Smoke density absorbance D{AbsorbanceRegister} raw [{result.RawHex}], "
|
||||
+ $"ABCD={result.Abcd:G9}, CDAB={result.Cdab:G9}, "
|
||||
+ $"BADC={result.Badc:G9}, DCBA={result.Dcba:G9}, "
|
||||
+ $"selected={selectedText}, matches={matchCount}.");
|
||||
+ $"selected={selectedText}, preferred={preferredText}, matches={matchCount}.";
|
||||
|
||||
Debug.WriteLine(message);
|
||||
Log.Debug(message);
|
||||
}
|
||||
|
||||
private static double NormalizeDisplayValue(double value)
|
||||
{
|
||||
return Math.Abs(value) < 0.005 ? 0 : value;
|
||||
return IsZeroLikeAbsorbance(value) ? 0 : value;
|
||||
}
|
||||
|
||||
private static bool IsZeroLikeAbsorbance(double value)
|
||||
{
|
||||
return double.IsFinite(value) && Math.Abs(value) < AbsorbanceZeroTolerance;
|
||||
}
|
||||
|
||||
private static string FormatAbsorbance(double value)
|
||||
{
|
||||
return value.ToString(AbsorbanceFormat, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static bool TryGetCalibrationCoil(string label, out ushort coilAddress)
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
FontSize="22"
|
||||
Foreground="#111A18" />
|
||||
<TextBlock Margin="0,14,0,0"
|
||||
Text="2. 25%为放入75%遮光片校准;"
|
||||
Text="2. 25%为放入25%遮光片校准;"
|
||||
FontSize="22"
|
||||
Foreground="#111A18" />
|
||||
<TextBlock Margin="0,14,0,0"
|
||||
@@ -113,7 +113,7 @@
|
||||
FontSize="22"
|
||||
Foreground="#111A18" />
|
||||
<TextBlock Margin="0,14,0,0"
|
||||
Text="4. 75%为放入25%遮光片校准;"
|
||||
Text="4. 75%为放入75%遮光片校准;"
|
||||
FontSize="22"
|
||||
Foreground="#111A18" />
|
||||
<TextBlock Margin="0,14,0,0"
|
||||
|
||||
Reference in New Issue
Block a user