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 @@
+
+