更新20260601
This commit is contained in:
@@ -24,5 +24,6 @@ public sealed record RealtimeSnapshot(
|
|||||||
double MassLoss,
|
double MassLoss,
|
||||||
double MassLossRate,
|
double MassLossRate,
|
||||||
int IgnitionSeconds,
|
int IgnitionSeconds,
|
||||||
|
int DeviceTestSeconds,
|
||||||
int TestSeconds,
|
int TestSeconds,
|
||||||
double TotalSmoke);
|
double TotalSmoke);
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
private double _accumulatedTotalSmoke;
|
private double _accumulatedTotalSmoke;
|
||||||
private int? _lastAccumulationSeconds;
|
private int? _lastAccumulationSeconds;
|
||||||
private int? _lastRecordedSeconds;
|
private int? _lastRecordedSeconds;
|
||||||
|
private int? _deviceTestTimerBaselineSeconds;
|
||||||
|
private bool _hasObservedDeviceTestTimerReset;
|
||||||
private bool _isTestRunning;
|
private bool _isTestRunning;
|
||||||
|
private bool _isRecordingActive;
|
||||||
private bool _isPollingSnapshot;
|
private bool _isPollingSnapshot;
|
||||||
|
private RealtimeSnapshot? _completedSnapshot;
|
||||||
|
|
||||||
public ExperimentDataService(IRealtimeDataService realtimeDataService)
|
public ExperimentDataService(IRealtimeDataService realtimeDataService)
|
||||||
{
|
{
|
||||||
@@ -50,26 +54,26 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
{
|
{
|
||||||
Records.Clear();
|
Records.Clear();
|
||||||
ResetComputedState();
|
ResetComputedState();
|
||||||
|
_completedSnapshot = null;
|
||||||
_isTestRunning = true;
|
_isTestRunning = true;
|
||||||
_testClock.Restart();
|
_isRecordingActive = false;
|
||||||
|
_testClock.Reset();
|
||||||
if (double.IsFinite(CurrentSnapshot.CurrentMass))
|
PrepareDeviceTestTimerGate();
|
||||||
{
|
PublishSnapshot(BuildWaitingSnapshot(CurrentSnapshot));
|
||||||
_initialMass = CurrentSnapshot.CurrentMass;
|
|
||||||
}
|
|
||||||
|
|
||||||
QueueImmediateDevicePoll();
|
QueueImmediateDevicePoll();
|
||||||
|
|
||||||
Log.Information(
|
Log.Information(
|
||||||
"Experiment test started. InitialMass={InitialMass}, SystemSeconds={TestSeconds}.",
|
"Experiment test started. Waiting for D1015 timing marker. DeviceTimerBaseline={DeviceTimerBaseline}.",
|
||||||
_initialMass,
|
_deviceTestTimerBaselineSeconds);
|
||||||
CurrentSnapshot.TestSeconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopTest()
|
public void StopTest()
|
||||||
{
|
{
|
||||||
_isTestRunning = false;
|
_isTestRunning = false;
|
||||||
|
_isRecordingActive = false;
|
||||||
_testClock.Stop();
|
_testClock.Stop();
|
||||||
|
_completedSnapshot = BuildCompletedSnapshot(CurrentSnapshot);
|
||||||
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
|
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
|
||||||
|
|
||||||
Log.Information(
|
Log.Information(
|
||||||
@@ -85,6 +89,8 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
if (!_isTestRunning)
|
if (!_isTestRunning)
|
||||||
{
|
{
|
||||||
_testClock.Reset();
|
_testClock.Reset();
|
||||||
|
_isRecordingActive = false;
|
||||||
|
_completedSnapshot = null;
|
||||||
ResetComputedState();
|
ResetComputedState();
|
||||||
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
|
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
|
||||||
}
|
}
|
||||||
@@ -110,7 +116,7 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var elapsed = _isTestRunning ? _testClock.Elapsed : TimeSpan.Zero;
|
var elapsed = _isTestRunning && _isRecordingActive ? _testClock.Elapsed : TimeSpan.Zero;
|
||||||
var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed));
|
var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed));
|
||||||
if (_isTestRunning)
|
if (_isTestRunning)
|
||||||
{
|
{
|
||||||
@@ -157,6 +163,28 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_isRecordingActive)
|
||||||
|
{
|
||||||
|
if (!ShouldStartTimedRecording(snapshot))
|
||||||
|
{
|
||||||
|
PublishSnapshot(BuildWaitingSnapshot(snapshot));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginTimedRecording(snapshot);
|
||||||
|
snapshot = snapshot with
|
||||||
|
{
|
||||||
|
TestSeconds = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snapshot = snapshot with
|
||||||
|
{
|
||||||
|
TestSeconds = GetElapsedTestSeconds()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var currentSecond = Math.Max(0, snapshot.TestSeconds);
|
var currentSecond = Math.Max(0, snapshot.TestSeconds);
|
||||||
if (_lastRecordedSeconds.HasValue && currentSecond <= _lastRecordedSeconds.Value)
|
if (_lastRecordedSeconds.HasValue && currentSecond <= _lastRecordedSeconds.Value)
|
||||||
{
|
{
|
||||||
@@ -183,6 +211,11 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
return BuildIdleSnapshot(snapshot);
|
return BuildIdleSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_isRecordingActive)
|
||||||
|
{
|
||||||
|
return BuildWaitingSnapshot(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
var timedSnapshot = snapshot with
|
var timedSnapshot = snapshot with
|
||||||
{
|
{
|
||||||
TestSeconds = GetElapsedTestSeconds()
|
TestSeconds = GetElapsedTestSeconds()
|
||||||
@@ -209,17 +242,103 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
|
|
||||||
private RealtimeSnapshot BuildIdleSnapshot(RealtimeSnapshot snapshot)
|
private RealtimeSnapshot BuildIdleSnapshot(RealtimeSnapshot snapshot)
|
||||||
{
|
{
|
||||||
|
var completedSnapshot = _completedSnapshot;
|
||||||
|
|
||||||
|
return snapshot with
|
||||||
|
{
|
||||||
|
TestSeconds = completedSnapshot?.TestSeconds ?? -1,
|
||||||
|
InitialMass = completedSnapshot?.InitialMass ?? double.NaN,
|
||||||
|
MassLoss = completedSnapshot?.MassLoss ?? double.NaN,
|
||||||
|
MassLossRate = completedSnapshot?.MassLossRate ?? double.NaN,
|
||||||
|
TotalHeatRelease = completedSnapshot?.TotalHeatRelease ?? double.NaN,
|
||||||
|
TotalSmoke = completedSnapshot?.TotalSmoke ?? double.NaN
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private RealtimeSnapshot BuildWaitingSnapshot(RealtimeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var displayInitialMass = double.IsFinite(snapshot.CurrentMass)
|
||||||
|
? snapshot.CurrentMass
|
||||||
|
: double.NaN;
|
||||||
|
|
||||||
return snapshot with
|
return snapshot with
|
||||||
{
|
{
|
||||||
TestSeconds = -1,
|
TestSeconds = -1,
|
||||||
InitialMass = _initialMass ?? double.NaN,
|
InitialMass = displayInitialMass,
|
||||||
MassLoss = double.NaN,
|
MassLoss = double.IsFinite(displayInitialMass) ? 0 : double.NaN,
|
||||||
MassLossRate = double.NaN,
|
MassLossRate = double.NaN,
|
||||||
TotalHeatRelease = double.NaN,
|
TotalHeatRelease = 0,
|
||||||
TotalSmoke = double.NaN
|
TotalSmoke = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RealtimeSnapshot? BuildCompletedSnapshot(RealtimeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
if (Records.Count == 0)
|
||||||
|
{
|
||||||
|
return snapshot.TestSeconds >= 0 ? snapshot : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastRecord = Records[Records.Count - 1];
|
||||||
|
return snapshot with
|
||||||
|
{
|
||||||
|
TotalHeatRelease = lastRecord.TotalHeatRelease,
|
||||||
|
InitialMass = lastRecord.InitialMass,
|
||||||
|
MassLoss = lastRecord.MassLoss,
|
||||||
|
MassLossRate = lastRecord.MassLossRate,
|
||||||
|
IgnitionSeconds = lastRecord.IgnitionSeconds,
|
||||||
|
TestSeconds = lastRecord.TestSeconds,
|
||||||
|
TotalSmoke = lastRecord.TotalSmoke
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareDeviceTestTimerGate()
|
||||||
|
{
|
||||||
|
var currentDeviceSeconds = CurrentSnapshot.DeviceTestSeconds;
|
||||||
|
_deviceTestTimerBaselineSeconds = currentDeviceSeconds >= 0 ? currentDeviceSeconds : null;
|
||||||
|
_hasObservedDeviceTestTimerReset = !_deviceTestTimerBaselineSeconds.HasValue
|
||||||
|
|| _deviceTestTimerBaselineSeconds.Value <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldStartTimedRecording(RealtimeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var deviceSeconds = snapshot.DeviceTestSeconds;
|
||||||
|
if (deviceSeconds < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_deviceTestTimerBaselineSeconds.HasValue || _hasObservedDeviceTestTimerReset)
|
||||||
|
{
|
||||||
|
return deviceSeconds > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var baselineSeconds = _deviceTestTimerBaselineSeconds.Value;
|
||||||
|
if (deviceSeconds <= 0)
|
||||||
|
{
|
||||||
|
_hasObservedDeviceTestTimerReset = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceSeconds != baselineSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginTimedRecording(RealtimeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
_isRecordingActive = true;
|
||||||
|
_testClock.Restart();
|
||||||
|
|
||||||
|
if (double.IsFinite(snapshot.CurrentMass))
|
||||||
|
{
|
||||||
|
_initialMass = snapshot.CurrentMass;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information(
|
||||||
|
"Experiment timed recording started by D1015. DeviceTestSeconds={DeviceTestSeconds}, InitialMass={InitialMass}.",
|
||||||
|
snapshot.DeviceTestSeconds,
|
||||||
|
_initialMass);
|
||||||
|
}
|
||||||
|
|
||||||
private int GetElapsedTestSeconds()
|
private int GetElapsedTestSeconds()
|
||||||
{
|
{
|
||||||
return Math.Max(0, (int)Math.Floor(_testClock.Elapsed.TotalSeconds));
|
return Math.Max(0, (int)Math.Floor(_testClock.Elapsed.TotalSeconds));
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
|||||||
private const ushort SmokeProductionRegister = 392;
|
private const ushort SmokeProductionRegister = 392;
|
||||||
private const ushort IrradianceRegister = 410;
|
private const ushort IrradianceRegister = 410;
|
||||||
private const ushort IgnitionSecondsRegister = 1014;
|
private const ushort IgnitionSecondsRegister = 1014;
|
||||||
|
private const ushort DeviceTestSecondsRegister = 1015;
|
||||||
private const ushort M3FlameMonitorBit = 3;
|
private const ushort M3FlameMonitorBit = 3;
|
||||||
private static readonly ModbusFloatByteOrder[] RealtimeFloatByteOrders =
|
private static readonly ModbusFloatByteOrder[] RealtimeFloatByteOrders =
|
||||||
[
|
[
|
||||||
@@ -84,6 +85,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
|
|||||||
MassLoss: double.NaN,
|
MassLoss: double.NaN,
|
||||||
MassLossRate: double.NaN,
|
MassLossRate: double.NaN,
|
||||||
IgnitionSeconds: ReadInt16OrEmpty(IgnitionSecondsRegister),
|
IgnitionSeconds: ReadInt16OrEmpty(IgnitionSecondsRegister),
|
||||||
|
DeviceTestSeconds: ReadInt16OrEmpty(DeviceTestSecondsRegister),
|
||||||
TestSeconds: ToElapsedSeconds(elapsed),
|
TestSeconds: ToElapsedSeconds(elapsed),
|
||||||
TotalSmoke: double.NaN);
|
TotalSmoke: double.NaN);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user