diff --git a/src/NmeaParser/Gnss/GnssMonitor.cs b/src/NmeaParser/Gnss/GnssMonitor.cs
index c78db20..669f2a9 100644
--- a/src/NmeaParser/Gnss/GnssMonitor.cs
+++ b/src/NmeaParser/Gnss/GnssMonitor.cs
@@ -14,7 +14,9 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
+using System.Threading;
using NmeaParser.Messages;
namespace NmeaParser.Gnss
@@ -22,10 +24,12 @@ namespace NmeaParser.Gnss
///
/// Helper class for monitoring GNSS messages and combine them into a single useful location info
///
- public class GnssMonitor
+ public class GnssMonitor : INotifyPropertyChanged
{
private bool m_supportGNMessages; // If device detect GN* messages, ignore all other Talker ID
private bool m_supportGGaMessages; //If device support GGA, ignore RMC for location
+ private Dictionary m_allMessages { get; } = new Dictionary();
+ private object m_lock = new object();
///
/// Initializes a new instance of the class.
@@ -37,8 +41,18 @@ namespace NmeaParser.Gnss
throw new ArgumentNullException(nameof(device));
Device = device;
Device.MessageReceived += NmeaMessageReceived;
+ SynchronizationContext = SynchronizationContext.Current;
}
+ ///
+ /// Gets or sets the syncronization context that should be fired on
+ ///
+ ///
+ /// The default is the context this thread was created monitor was created on, but for use in UI applications,
+ /// it can be beneficial to ensure this is the UI Thread. You can also set this to null for best performance
+ ///
+ public SynchronizationContext? SynchronizationContext { get; set; }
+
///
/// Gets the NMEA device that is being monitored
///
@@ -53,14 +67,16 @@ namespace NmeaParser.Gnss
/// Called when a message is received.
///
/// The NMEA message that was received
- protected virtual void OnMessageReceived(NmeaMessage message)
- {
+ protected virtual void OnMessageReceived(NmeaMessage message)
+ {
bool isNewFix = false;
bool lostFix = false;
double lat = 0;
double lon = 0;
- AllMessages[message.MessageType] = message;
-
+ List properties = new List();
+ lock (m_lock)
+ m_allMessages[message.MessageType] = message;
+ properties.Add(nameof(AllMessages));
if (message.TalkerId == NmeaParser.Talker.GlobalNavigationSatelliteSystem)
m_supportGNMessages = true; // Support for GN* messages detected
else if (m_supportGNMessages && message.TalkerId != NmeaParser.Talker.GlobalNavigationSatelliteSystem)
@@ -68,25 +84,52 @@ namespace NmeaParser.Gnss
if (message is NmeaParser.Messages.Garmin.Pgrme rme)
{
- HorizontalError = rme.HorizontalError;
- VerticalError = rme.VerticalError;
+ if (rme.HorizontalError != HorizontalError)
+ {
+ properties.Add(nameof(HorizontalError));
+ HorizontalError = rme.HorizontalError;
+ }
+ if (rme.VerticalError != VerticalError)
+ {
+ VerticalError = rme.VerticalError;
+ properties.Add(nameof(VerticalError));
+ }
}
else if (message is Gst gst)
{
Gst = gst;
- VerticalError = gst.SigmaHeightError;
- HorizontalError = Math.Round(Math.Sqrt(Gst.SigmaLatitudeError * Gst.SigmaLatitudeError + Gst.SigmaLongitudeError * Gst.SigmaLongitudeError), 3);
+ properties.Add(nameof(Gst));
+ var error = Math.Round(Math.Sqrt(Gst.SigmaLatitudeError * Gst.SigmaLatitudeError + Gst.SigmaLongitudeError * Gst.SigmaLongitudeError), 3);
+ if (error != HorizontalError)
+ {
+ HorizontalError = error;
+ properties.Add(nameof(HorizontalError));
+ }
+ if (VerticalError != gst.SigmaHeightError)
+ {
+ VerticalError = gst.SigmaHeightError;
+ properties.Add(nameof(VerticalError));
+ }
}
else if (message is Rmc rmc)
{
+ if (Speed != rmc.Speed)
+ properties.Add(nameof(Speed));
+ if (Course != rmc.Course)
+ properties.Add(nameof(Course));
Rmc = rmc;
+ properties.Add(nameof(Rmc));
if (!m_supportGGaMessages)
{
if (Rmc.Active)
{
lat = Rmc.Latitude;
lon = Rmc.Longitude;
- FixTime = Rmc.FixTime.TimeOfDay;
+ if (FixTime != Rmc.FixTime.TimeOfDay)
+ {
+ FixTime = Rmc.FixTime.TimeOfDay;
+ properties.Add(nameof(FixTime));
+ }
isNewFix = true;
}
else
@@ -101,52 +144,102 @@ namespace NmeaParser.Gnss
{
// Datum change
Dtm = dtm;
+ properties.Add(nameof(Dtm));
Latitude = double.NaN;
Longitude = double.NaN;
IsFixValid = false;
+ properties.Add(nameof(Dtm));
+ properties.Add(nameof(Datum));
+ properties.Add(nameof(Latitude));
+ properties.Add(nameof(Longitude));
+ properties.Add(nameof(IsFixValid));
}
}
else if (message is Gga gga)
{
+ if (gga.Hdop != Hdop)
+ properties.Add(nameof(Hdop));
+ if (gga.Quality != FixQuality)
+ properties.Add(nameof(FixQuality));
Gga = gga;
+ properties.Add(nameof(Gga));
m_supportGGaMessages = true;
if (gga.Quality != Gga.FixQuality.Invalid)
{
lat = gga.Latitude;
lon = gga.Longitude;
GeoidHeight = gga.GeoidalSeparation;
+ properties.Add(nameof(GeoidHeight));
Altitude = gga.Altitude + gga.GeoidalSeparation; //Convert to ellipsoidal height
+ properties.Add(nameof(Altitude));
}
if (gga.Quality == Gga.FixQuality.Invalid || gga.Quality == Gga.FixQuality.Estimated)
{
lostFix = true;
}
- FixTime = Gga.FixTime;
+ if (FixTime != Gga.FixTime)
+ {
+ FixTime = Gga.FixTime;
+ properties.Add(nameof(FixTime));
+ }
isNewFix = true;
}
else if (message is Gsa gsa)
{
+ if (gsa.Hdop != Hdop)
+ properties.Add(nameof(Hdop));
+ if (gsa.Pdop != Pdop)
+ properties.Add(nameof(Pdop));
+ if (gsa.Vdop != Vdop)
+ properties.Add(nameof(Vdop));
Gsa = gsa;
+ properties.Add(nameof(Gsa));
}
else if (message is Vtg vtg)
{
+ if (Speed != vtg.SpeedKnots)
+ properties.Add(nameof(Speed));
Vtg = vtg;
+ properties.Add(nameof(Vtg));
+ }
+ else if (message is Gsv)
+ {
+ properties.Add(nameof(Satellites));
+ properties.Add(nameof(SatellitesInView));
}
if (lostFix)
{
if (!IsFixValid)
{
IsFixValid = false;
+ properties.Add(nameof(IsFixValid));
+ properties.Add(nameof(FixQuality));
LocationLost?.Invoke(this, EventArgs.Empty);
}
}
if (isNewFix)
{
- Latitude = lat;
- Longitude = lon;
- IsFixValid = true;
+ if (Latitude != lat)
+ {
+ properties.Add(nameof(Latitude));
+ Latitude = lat;
+ }
+ if (Longitude != lon)
+ {
+ properties.Add(nameof(Longitude));
+ Longitude = lon;
+ }
+ if (!IsFixValid)
+ {
+ properties.Add(nameof(IsFixValid));
+ if (Gga == null)
+ properties.Add(nameof(FixQuality));
+ IsFixValid = true;
+ }
LocationChanged?.Invoke(this, EventArgs.Empty);
}
+ if (properties.Count > 0)
+ OnPropertyChanged(properties);
}
///
@@ -157,7 +250,7 @@ namespace NmeaParser.Gnss
///
///
public bool IsFixValid { get; private set; }
-
+
///
/// Gets the latitude for the current or last known location.
///
@@ -171,7 +264,7 @@ namespace NmeaParser.Gnss
///
///
public double Longitude { get; private set; } = double.NaN;
-
+
///
/// Gets the geight above the ellipsoid
///
@@ -254,22 +347,51 @@ namespace NmeaParser.Gnss
///
/// Gets a list of satellite vehicles in the sky
///
- public IEnumerable Satellites => AllMessages.Values.OfType().SelectMany(s => s.SVs);
+ public IEnumerable Satellites
+ {
+ get
+ {
+ lock (m_lock)
+ {
+ return m_allMessages.Values.OfType().SelectMany(s => s.SVs).ToArray();
+ }
+ }
+ }
///
/// Gets the number of satellites in the sky
///
- public int SatellitesInView => AllMessages.Values.OfType().Sum(s => s.SatellitesInView);
+ public int SatellitesInView
+ {
+ get
+ {
+ lock (m_lock)
+ {
+ return m_allMessages.Values.OfType().Sum(s => s.SatellitesInView);
+ }
+ }
+ }
+
///
/// Gets the quality of the current fix
///
public Gga.FixQuality FixQuality => !IsFixValid ? Gga.FixQuality.Invalid : (Gga?.Quality ?? Gga.FixQuality.GpsFix);
+
///
/// Gets a list of all NMEA messages currently part of this location
///
- public Dictionary AllMessages { get; } = new Dictionary();
+ public IEnumerable> AllMessages
+ {
+ get
+ {
+ lock (m_lock)
+ {
+ return m_allMessages.ToArray();
+ }
+ }
+ }
///
/// Gets a value indicating the current Datum being used.
@@ -301,5 +423,27 @@ namespace NmeaParser.Gnss
///
///
public event EventHandler? LocationLost;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ private void OnPropertyChanged(IEnumerable properties)
+ {
+ if (PropertyChanged == null)
+ return;
+ if (SynchronizationContext != null)
+ {
+ SynchronizationContext.Post((d) =>
+ {
+ foreach (string propertyName in (IEnumerable)d)
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }, properties);
+ }
+ else
+ {
+ foreach (string propertyName in properties)
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
}
}
diff --git a/src/SampleApp.WinDesktop/GnssMonitorView.xaml b/src/SampleApp.WinDesktop/GnssMonitorView.xaml
index 565f480..959b74c 100644
--- a/src/SampleApp.WinDesktop/GnssMonitorView.xaml
+++ b/src/SampleApp.WinDesktop/GnssMonitorView.xaml
@@ -10,19 +10,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs b/src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs
index 6bf6837..216e0d3 100644
--- a/src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs
+++ b/src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs
@@ -1,7 +1,9 @@
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Data;
using System.Windows.Media;
using NmeaParser.Gnss;
using NmeaParser.Messages;
@@ -16,9 +18,33 @@ namespace SampleApp.WinDesktop
public GnssMonitorView()
{
InitializeComponent();
- }
+ SetupBindings();
+ }
- public GnssMonitor Monitor
+ private void SetupBindings()
+ {
+ var props = typeof(GnssMonitor).GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
+ List> values = new List>();
+ int count = 0;
+ ArrayConverter conv = new ArrayConverter();
+ foreach (var prop in props.OrderBy(t => t.Name))
+ {
+ if (prop.Name == nameof(GnssMonitor.AllMessages) || prop.Name == nameof(GnssMonitor.SynchronizationContext)) continue;
+ data.RowDefinitions.Add(new RowDefinition());
+ var title = new TextBlock() { Text = prop.Name, FontWeight = FontWeights.Bold, Margin = new Thickness(0, 0, 5, 0), VerticalAlignment = VerticalAlignment.Top };
+ Grid.SetRow(title, count);
+ data.Children.Add(title);
+ var valuebox = new TextBlock() { VerticalAlignment = VerticalAlignment.Top, TextWrapping = TextWrapping.Wrap };
+ Grid.SetRow(valuebox, count);
+ Grid.SetColumn(valuebox, 1);
+ var binding = new Binding(prop.Name) { Converter = conv };
+ valuebox.SetBinding(TextBlock.TextProperty, binding);
+ data.Children.Add(valuebox);
+ count++;
+ }
+ }
+
+ public GnssMonitor Monitor
{
get { return (GnssMonitor)GetValue(MonitorProperty); }
set { SetValue(MonitorProperty, value); }
@@ -29,48 +55,29 @@ namespace SampleApp.WinDesktop
private void OnMonitorPropertyChanged(DependencyPropertyChangedEventArgs e)
{
- if (e.OldValue is GnssMonitor oldMonitor)
- {
- oldMonitor.LocationChanged -= LocationChanged;
- oldMonitor.LocationLost -= LocationChanged;
- }
- if (e.NewValue is GnssMonitor newMonitor)
- {
- newMonitor.LocationChanged += LocationChanged;
- newMonitor.LocationLost += LocationChanged;
- }
- UpdateValues();
+ data.DataContext = Monitor;
}
- private void LocationChanged(object sender, System.EventArgs e)
+ private class ArrayConverter : IValueConverter
{
- Dispatcher.Invoke(UpdateValues);
- }
- private void UpdateValues()
- {
- if (Monitor == null)
- Values.ItemsSource = null;
- else
+ public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
{
- var props = Monitor.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
- List> values = new List>();
- foreach (var prop in props.OrderBy(t => t.Name))
+ if (!(value is string) && value is System.Collections.IEnumerable arr)
{
- if (prop.Name == nameof(GnssMonitor.AllMessages)) continue;
- if (prop.PropertyType.IsSubclassOf(typeof(NmeaMessage)))
- continue;
- var value = prop.GetValue(Monitor);
- if (!(value is string) && value is System.Collections.IEnumerable arr)
- {
- var str = "[" + string.Join(",", arr.OfType