Files
ConeCalorimeter/ConeCalorimeter/Services/ExperimentDataService.cs
GukSang.Jin 32f56a8710 更新
2026-05-07 16:36:24 +08:00

170 lines
4.7 KiB
C#

using System.Collections.ObjectModel;
using System.Windows.Threading;
using ConeCalorimeter.Models;
namespace ConeCalorimeter.Services;
public sealed class ExperimentDataService : IExperimentDataService
{
private const int MaximumRows = 1200;
private readonly IRealtimeDataService _realtimeDataService;
private readonly DispatcherTimer _timer;
private readonly DateTime _startedAt = DateTime.Now;
private double? _initialMass;
private double _accumulatedTotalHeatRelease;
private double _accumulatedTotalSmoke;
private int? _lastAccumulationSeconds;
private bool _isTestRunning;
public ExperimentDataService(IRealtimeDataService realtimeDataService)
{
_realtimeDataService = realtimeDataService;
Records = [];
CurrentSnapshot = _realtimeDataService.GetCurrentSnapshot(TimeSpan.Zero);
RecordSnapshot(CurrentSnapshot);
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_timer.Tick += (_, _) => RecordSnapshot(
_realtimeDataService.GetCurrentSnapshot(DateTime.Now - _startedAt));
_timer.Start();
}
public ObservableCollection<RealtimeDataRecord> Records { get; }
public RealtimeSnapshot CurrentSnapshot { get; private set; }
public event EventHandler<RealtimeSnapshot>? SnapshotUpdated;
public void StartTest()
{
Records.Clear();
ResetComputedState();
_isTestRunning = true;
if (double.IsFinite(CurrentSnapshot.CurrentMass))
{
_initialMass = CurrentSnapshot.CurrentMass;
}
}
public void StopTest()
{
_isTestRunning = false;
}
public void ClearRecords()
{
Records.Clear();
ResetComputedState();
}
private void RecordSnapshot(RealtimeSnapshot snapshot)
{
var massSnapshot = ApplyMassLoss(snapshot);
var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot);
CurrentSnapshot = accumulatedSnapshot;
Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot));
while (Records.Count > MaximumRows)
{
Records.RemoveAt(0);
}
SnapshotUpdated?.Invoke(this, accumulatedSnapshot);
}
private RealtimeSnapshot ApplyAccumulatedTotals(RealtimeSnapshot snapshot)
{
if (!_isTestRunning)
{
return snapshot with
{
TotalHeatRelease = double.NaN,
TotalSmoke = double.NaN
};
}
if (snapshot.TestSeconds < 0)
{
return snapshot with
{
TotalHeatRelease = _accumulatedTotalHeatRelease,
TotalSmoke = _accumulatedTotalSmoke
};
}
if (!_lastAccumulationSeconds.HasValue)
{
_lastAccumulationSeconds = snapshot.TestSeconds;
return snapshot with
{
TotalHeatRelease = _accumulatedTotalHeatRelease,
TotalSmoke = _accumulatedTotalSmoke
};
}
var deltaSeconds = snapshot.TestSeconds - _lastAccumulationSeconds.Value;
if (deltaSeconds > 0)
{
if (double.IsFinite(snapshot.HeatReleaseRate))
{
_accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds / 1000;
}
if (double.IsFinite(snapshot.SmokeProduction))
{
_accumulatedTotalSmoke += snapshot.SmokeProduction * deltaSeconds;
}
_lastAccumulationSeconds = snapshot.TestSeconds;
}
return snapshot with
{
TotalHeatRelease = _accumulatedTotalHeatRelease,
TotalSmoke = _accumulatedTotalSmoke
};
}
private RealtimeSnapshot ApplyMassLoss(RealtimeSnapshot snapshot)
{
if (!_isTestRunning)
{
return snapshot with
{
InitialMass = _initialMass ?? double.NaN,
MassLoss = double.NaN
};
}
if (!_initialMass.HasValue && double.IsFinite(snapshot.CurrentMass))
{
_initialMass = snapshot.CurrentMass;
}
var massLoss = _initialMass.HasValue && double.IsFinite(snapshot.CurrentMass)
? _initialMass.Value - snapshot.CurrentMass
: double.NaN;
return snapshot with
{
InitialMass = _initialMass ?? double.NaN,
MassLoss = massLoss
};
}
private void ResetComputedState()
{
_initialMass = null;
_accumulatedTotalHeatRelease = 0;
_accumulatedTotalSmoke = 0;
_lastAccumulationSeconds = null;
}
}