mirror of
https://github.com/dotMorten/NmeaParser.git
synced 2026-01-20 15:40:16 +01:00
Adds property change notification to properties
Also fixes a threading issue with AllMessages - Issue #76
This commit is contained in:
parent
da3b3f8788
commit
02579ab3e8
|
|
@ -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
|
|||
/// <summary>
|
||||
/// Helper class for monitoring GNSS messages and combine them into a single useful location info
|
||||
/// </summary>
|
||||
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<string, NmeaMessage> m_allMessages { get; } = new Dictionary<string, NmeaMessage>();
|
||||
private object m_lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GnssMonitor"/> class.
|
||||
|
|
@ -37,8 +41,18 @@ namespace NmeaParser.Gnss
|
|||
throw new ArgumentNullException(nameof(device));
|
||||
Device = device;
|
||||
Device.MessageReceived += NmeaMessageReceived;
|
||||
SynchronizationContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the syncronization context that <see cref="PropertyChanged"/> should be fired on
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <c>null</c> for best performance
|
||||
/// </remarks>
|
||||
public SynchronizationContext? SynchronizationContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NMEA device that is being monitored
|
||||
/// </summary>
|
||||
|
|
@ -53,14 +67,16 @@ namespace NmeaParser.Gnss
|
|||
/// Called when a message is received.
|
||||
/// </summary>
|
||||
/// <param name="message">The NMEA message that was received</param>
|
||||
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<string> properties = new List<string>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -157,7 +250,7 @@ namespace NmeaParser.Gnss
|
|||
/// </remarks>
|
||||
/// <seealso cref="LocationLost"/>
|
||||
public bool IsFixValid { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latitude for the current or last known location.
|
||||
/// </summary>
|
||||
|
|
@ -171,7 +264,7 @@ namespace NmeaParser.Gnss
|
|||
/// <seealso cref="IsFixValid"/>
|
||||
/// <seealso cref="Latitude"/>
|
||||
public double Longitude { get; private set; } = double.NaN;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the geight above the ellipsoid
|
||||
/// </summary>
|
||||
|
|
@ -254,22 +347,51 @@ namespace NmeaParser.Gnss
|
|||
/// <summary>
|
||||
/// Gets a list of satellite vehicles in the sky
|
||||
/// </summary>
|
||||
public IEnumerable<SatelliteVehicle> Satellites => AllMessages.Values.OfType<Gsv>().SelectMany(s => s.SVs);
|
||||
public IEnumerable<SatelliteVehicle> Satellites
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
return m_allMessages.Values.OfType<Gsv>().SelectMany(s => s.SVs).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of satellites in the sky
|
||||
/// </summary>
|
||||
public int SatellitesInView => AllMessages.Values.OfType<Gsv>().Sum(s => s.SatellitesInView);
|
||||
public int SatellitesInView
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
return m_allMessages.Values.OfType<Gsv>().Sum(s => s.SatellitesInView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality of the current fix
|
||||
/// </summary>
|
||||
public Gga.FixQuality FixQuality => !IsFixValid ? Gga.FixQuality.Invalid : (Gga?.Quality ?? Gga.FixQuality.GpsFix);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all NMEA messages currently part of this location
|
||||
/// </summary>
|
||||
public Dictionary<string, NmeaMessage> AllMessages { get; } = new Dictionary<string, NmeaMessage>();
|
||||
public IEnumerable<KeyValuePair<string, NmeaMessage>> AllMessages
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
return m_allMessages.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the current Datum being used.
|
||||
|
|
@ -301,5 +423,27 @@ namespace NmeaParser.Gnss
|
|||
/// </summary>
|
||||
/// <seealso cref="IsFixValid"/>
|
||||
public event EventHandler? LocationLost;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged(IEnumerable<string> properties)
|
||||
{
|
||||
if (PropertyChanged == null)
|
||||
return;
|
||||
if (SynchronizationContext != null)
|
||||
{
|
||||
SynchronizationContext.Post((d) =>
|
||||
{
|
||||
foreach (string propertyName in (IEnumerable<string>)d)
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string propertyName in properties)
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,11 @@
|
|||
<Border Background="CornflowerBlue" Padding="20,10" Margin="-10,-10,-10,5" x:Name="HeaderPanel">
|
||||
<TextBlock Text="GNSS Monitor" FontSize="20" FontWeight="Bold" Foreground="White" />
|
||||
</Border>
|
||||
<ItemsControl x:Name="Values" Margin="20,10">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="100" Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding Key}" Margin="0,0,5,0" VerticalAlignment="Top" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" Grid.Column="1" VerticalAlignment="Top" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Grid x:Name="data" Margin="20,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="100" Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -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<KeyValuePair<string, object>> values = new List<KeyValuePair<string, object>>();
|
||||
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<KeyValuePair<string, object>> values = new List<KeyValuePair<string, object>>();
|
||||
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<object>().ToArray()) + "]";
|
||||
if (str.Length == 2)
|
||||
str = "[ ]";
|
||||
value = str;
|
||||
}
|
||||
values.Add(new KeyValuePair<string, object>(prop.Name, value));
|
||||
var str = "[" + string.Join(",", arr.OfType<object>().ToArray()) + "]";
|
||||
if (str.Length == 2)
|
||||
str = "[ ]";
|
||||
value = str;
|
||||
}
|
||||
Values.ItemsSource = values;
|
||||
if (value is null)
|
||||
return "<null>";
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue