更新20260601

This commit is contained in:
GukSang.Jin
2026-06-01 15:48:38 +08:00
parent 9d9d091e74
commit b891a5b8a0
3 changed files with 136 additions and 14 deletions

View File

@@ -24,5 +24,6 @@ public sealed record RealtimeSnapshot(
double MassLoss,
double MassLossRate,
int IgnitionSeconds,
int DeviceTestSeconds,
int TestSeconds,
double TotalSmoke);

View File

@@ -23,8 +23,12 @@ public sealed class ExperimentDataService : IExperimentDataService
private double _accumulatedTotalSmoke;
private int? _lastAccumulationSeconds;
private int? _lastRecordedSeconds;
private int? _deviceTestTimerBaselineSeconds;
private bool _hasObservedDeviceTestTimerReset;
private bool _isTestRunning;
private bool _isRecordingActive;
private bool _isPollingSnapshot;
private RealtimeSnapshot? _completedSnapshot;
public ExperimentDataService(IRealtimeDataService realtimeDataService)
{
@@ -50,26 +54,26 @@ public sealed class ExperimentDataService : IExperimentDataService
{
Records.Clear();
ResetComputedState();
_completedSnapshot = null;
_isTestRunning = true;
_testClock.Restart();
if (double.IsFinite(CurrentSnapshot.CurrentMass))
{
_initialMass = CurrentSnapshot.CurrentMass;
}
_isRecordingActive = false;
_testClock.Reset();
PrepareDeviceTestTimerGate();
PublishSnapshot(BuildWaitingSnapshot(CurrentSnapshot));
QueueImmediateDevicePoll();
Log.Information(
"Experiment test started. InitialMass={InitialMass}, SystemSeconds={TestSeconds}.",
_initialMass,
CurrentSnapshot.TestSeconds);
"Experiment test started. Waiting for D1015 timing marker. DeviceTimerBaseline={DeviceTimerBaseline}.",
_deviceTestTimerBaselineSeconds);
}
public void StopTest()
{
_isTestRunning = false;
_isRecordingActive = false;
_testClock.Stop();
_completedSnapshot = BuildCompletedSnapshot(CurrentSnapshot);
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
Log.Information(
@@ -85,6 +89,8 @@ public sealed class ExperimentDataService : IExperimentDataService
if (!_isTestRunning)
{
_testClock.Reset();
_isRecordingActive = false;
_completedSnapshot = null;
ResetComputedState();
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
}
@@ -110,7 +116,7 @@ public sealed class ExperimentDataService : IExperimentDataService
try
{
var elapsed = _isTestRunning ? _testClock.Elapsed : TimeSpan.Zero;
var elapsed = _isTestRunning && _isRecordingActive ? _testClock.Elapsed : TimeSpan.Zero;
var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed));
if (_isTestRunning)
{
@@ -157,6 +163,28 @@ public sealed class ExperimentDataService : IExperimentDataService
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);
if (_lastRecordedSeconds.HasValue && currentSecond <= _lastRecordedSeconds.Value)
{
@@ -183,6 +211,11 @@ public sealed class ExperimentDataService : IExperimentDataService
return BuildIdleSnapshot(snapshot);
}
if (!_isRecordingActive)
{
return BuildWaitingSnapshot(snapshot);
}
var timedSnapshot = snapshot with
{
TestSeconds = GetElapsedTestSeconds()
@@ -209,17 +242,103 @@ public sealed class ExperimentDataService : IExperimentDataService
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
{
TestSeconds = -1,
InitialMass = _initialMass ?? double.NaN,
MassLoss = double.NaN,
InitialMass = displayInitialMass,
MassLoss = double.IsFinite(displayInitialMass) ? 0 : double.NaN,
MassLossRate = double.NaN,
TotalHeatRelease = double.NaN,
TotalSmoke = double.NaN
TotalHeatRelease = 0,
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()
{
return Math.Max(0, (int)Math.Floor(_testClock.Elapsed.TotalSeconds));

View File

@@ -26,6 +26,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
private const ushort SmokeProductionRegister = 392;
private const ushort IrradianceRegister = 410;
private const ushort IgnitionSecondsRegister = 1014;
private const ushort DeviceTestSecondsRegister = 1015;
private const ushort M3FlameMonitorBit = 3;
private static readonly ModbusFloatByteOrder[] RealtimeFloatByteOrders =
[
@@ -84,6 +85,7 @@ public sealed class ModbusRealtimeDataService : IRealtimeDataService
MassLoss: double.NaN,
MassLossRate: double.NaN,
IgnitionSeconds: ReadInt16OrEmpty(IgnitionSecondsRegister),
DeviceTestSeconds: ReadInt16OrEmpty(DeviceTestSecondsRegister),
TestSeconds: ToElapsedSeconds(elapsed),
TotalSmoke: double.NaN);
}