diff --git a/.gitignore b/.gitignore index f1b13ad6..712d3477 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ obj AnalysisReport.sarif debug.log upgrade-assistant.clef +.vs/ diff --git a/Daqifi.Desktop/Daqifi.Desktop.csproj.user b/Daqifi.Desktop/Daqifi.Desktop.csproj.user index 7937b018..8f2bbc2b 100644 --- a/Daqifi.Desktop/Daqifi.Desktop.csproj.user +++ b/Daqifi.Desktop/Daqifi.Desktop.csproj.user @@ -10,4 +10,14 @@ en-US false + + + Code + + + + + Designer + + \ No newline at end of file diff --git a/Daqifi.Desktop/Loggers/SummaryLogger.cs b/Daqifi.Desktop/Loggers/SummaryLogger.cs new file mode 100644 index 00000000..ecae57ec --- /dev/null +++ b/Daqifi.Desktop/Loggers/SummaryLogger.cs @@ -0,0 +1,411 @@ +using Daqifi.Desktop.Channel; +using Daqifi.Desktop.Commands; +using Daqifi.Desktop.Common.Loggers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Daqifi.Desktop.Logger +{ + /// + /// Provaides summary data for incoming samples + /// + public class SummaryLogger : ObservableObject, ILogger + { + + #region "Private Data" + + /// + /// Summary results object + /// + private class SummaryBuffer + { + public SummaryBuffer() + { + Devices = new HashSet(); + Channels = new HashSet(); + } + + /// + /// The number of samples seen + /// + public int SampleCount { get; set; } + + /// + /// The total elapsed time + /// + public long FirstSampleTicks { get; set; } + + /// + /// The total elapsed time + /// + public long LastSampleTicks { get; set; } + + /// + /// The average time between samples + /// + public double AverageDeltaTicks { get; set; } + + /// + /// The maximum time between samples + /// + public long MaxDeltaTicks { get; set; } + + /// + /// The minimum time between samples + /// + public long MinDeltaTicks { get; set; } + + /// + /// The devices seen + /// + public HashSet Devices { get; set; } + + /// + /// The channels seen + /// + public HashSet Channels { get; set; } + + /// + /// The maximum value of the incoming samples + /// + public double MaxValue { get; set; } + + /// + /// The minimum value of the incoming samples + /// + public double MinValue { get; set; } + + /// + /// The average value of the incoming samples + /// + public double AverageValue { get; set; } + + public void Reset() + { + SampleCount = 0; + FirstSampleTicks = 0; + LastSampleTicks = 0; + AverageDeltaTicks = 0; + MaxDeltaTicks = 0; + MinDeltaTicks = 0; + Devices.Clear(); + Channels.Clear(); + MaxValue = 0.0; + MinValue = 0.0; + AverageValue = 0.0; + } + } + + private int _sampleSize; + + private bool _enabled; + + /// + /// The in-progress sample set + /// + private SummaryBuffer _buffer; + + /// + /// The last completed sample set + /// + private SummaryBuffer _current; + + /// + /// Application logger + /// + public AppLogger AppLogger = AppLogger.Instance; + #endregion + + #region "Properties" + /// + /// Indicates whether the logger is accepting data + /// + public bool Enabled + { + get => _enabled; + set { _enabled = value; NotifyPropertyChanged("Enabled"); } + } + + /// + /// The number of samples to evaluate + /// + public int SampleSize + { + get => _sampleSize; + set { _sampleSize = value; NotifyPropertyChanged("SampleSize"); } + } + + /// + /// The total elapsed time + /// + public TimeSpan ElapsedTime + { + get + { + return TimeSpan.FromTicks(_current.LastSampleTicks - _current.FirstSampleTicks); + } + } + + /// + /// The time of the last sample + /// + public DateTime LastUpdate + { + get + { + return new DateTime(_current.LastSampleTicks); + } + } + + /// + /// The frequency sample rate + /// + public double SampleRate + { + get + { + return _current.AverageDeltaTicks >0 ? 1.0 / TimeSpan.FromTicks((long)_current.AverageDeltaTicks).TotalSeconds : 0.0; + } + } + + /// + /// The maximum time between samples + /// + public TimeSpan MaxDelta + { + get + { + return new TimeSpan(_current.MaxDeltaTicks); + } + } + + /// + /// The minimum time between samples + /// + public TimeSpan MinDelta + { + get + { + return new TimeSpan(_current.MinDeltaTicks); + } + } + + /// + /// The devices sthat reported data + /// + public string Devices + { + get + { + return _current.Devices.Any() ? _current.Devices.Aggregate((a, b) => String.Format("{0}, {1}", a, b)) : String.Empty; + } + } + + /// + /// The channels seen + /// + public string Channels + { + get + { + return _current.Channels.Any() ? _current.Channels.Aggregate((a, b) => String.Format("{0}, {1}", a, b)) : String.Empty; + } + } + + /// + /// The maximum value of the incoming samples + /// + public double MaxValue + { + get + { + return _current.MaxValue; + } + } + + /// + /// The minimum value of the incoming samples + /// + public double MinValue + { + get + { + return _current.MinValue; + } + } + + /// + /// The average value of the incoming samples + /// + public double AverageValue + { + get + { + return _current.AverageValue; + } + } + + #endregion + + #region "Command Properties" + + /// + /// Resets the summary logger + /// + public ICommand ResetCommand { get; } + + /// + /// Starts or stops the summary logger + /// + public ICommand ToggleEnabledCommand { get; } + + #endregion + + #region "Constructor" + + public SummaryLogger() + { + _buffer = new SummaryBuffer(); + _current = new SummaryBuffer(); + + _sampleSize = 1000; + + ResetCommand = new DelegateCommand(Reset); + ToggleEnabledCommand = new DelegateCommand(ToggleEnabled); + } + + #endregion + + public void Log(DataSample dataSample) + { + if (!_enabled) + { + return; + } + + lock(_buffer) + { + if (_buffer.SampleCount == 0) + { + _buffer.FirstSampleTicks = dataSample.TimestampTicks; + _buffer.MinValue = dataSample.Value; + _buffer.MaxValue = dataSample.Value; + } + else + { + _buffer.MinValue = Math.Min(dataSample.Value, _buffer.MinValue); + _buffer.MaxValue = Math.Max(dataSample.Value, _buffer.MaxValue); + } + _buffer.AverageValue += dataSample.Value / _sampleSize; + + if (_buffer.SampleCount > 0) + { + var elapsed = dataSample.TimestampTicks - _buffer.LastSampleTicks; + if (_buffer.SampleCount == 1) + { + _buffer.MinDeltaTicks = elapsed; + _buffer.MaxDeltaTicks = elapsed; + } + else + { + _buffer.MinDeltaTicks = Math.Min(_buffer.MinDeltaTicks, elapsed); + _buffer.MaxDeltaTicks = Math.Max(_buffer.MinDeltaTicks, elapsed); + } + _buffer.AverageDeltaTicks += elapsed / (double)(_sampleSize - 1); + } + _buffer.LastSampleTicks = dataSample.TimestampTicks; + + _buffer.Devices.Add(dataSample.DeviceName); + _buffer.Channels.Add(dataSample.ChannelName); + + ++_buffer.SampleCount; + if (_buffer.SampleCount == _sampleSize) + { + lock (_current) + { + SwapBuffer(); + } + } + } + } + + private void SwapBuffer() + { + lock(_buffer) + { + lock (_current) + { + (_current, _buffer) = (_buffer, _current); + _buffer.Reset(); + + NotifyResultsChanged(); + } + } + } + + private void NotifyResultsChanged() + { + NotifyPropertyChanged("ElapsedTime"); + NotifyPropertyChanged("LastUpdate"); + NotifyPropertyChanged("SampleRate"); + NotifyPropertyChanged("MaxDelta"); + NotifyPropertyChanged("MinDelta"); + NotifyPropertyChanged("Devices"); + NotifyPropertyChanged("Channels"); + NotifyPropertyChanged("MaxValue"); + NotifyPropertyChanged("MinValue"); + NotifyPropertyChanged("AverageValue"); + } + + private void ToggleEnabled(object o) + { + if (Enabled) + { + Stop(); + } + else + { + Start(); + } + } + + private void Start() + { + lock(_buffer) + { + _enabled = false; + _buffer.Reset(); + _enabled = true; + NotifyPropertyChanged("Enabled"); + } + } + + private void Stop() + { + lock (_buffer) + { + _enabled = false; + NotifyPropertyChanged("Enabled"); + } + } + + private void Reset(object o) + { + lock(_buffer) + { + lock (_current) + { + Enabled = false; + SampleSize = 1000; + _buffer.Reset(); + _current.Reset(); + NotifyResultsChanged(); + } + } + } + } +} diff --git a/Daqifi.Desktop/MainWindow.xaml b/Daqifi.Desktop/MainWindow.xaml index ebb244c4..98b3bcc9 100644 --- a/Daqifi.Desktop/MainWindow.xaml +++ b/Daqifi.Desktop/MainWindow.xaml @@ -8,6 +8,7 @@ xmlns:Service="clr-namespace:Daqifi.Desktop.DialogService" xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" Service:DialogService.IsRegisteredView="True" Title="DAQifi v1.0.9" Height="{Binding Height, Mode=TwoWay}" Width="{Binding Width, Mode=TwoWay}" WindowState="{Binding ViewWindowState, Mode=OneWayToSource}" BorderThickness="0" GlowBrush="Black"> @@ -82,7 +83,10 @@ - + + + + @@ -131,6 +135,12 @@ + +