How to get satellite info .NET WPF 100.5?

1265
5
Jump to solution
09-17-2019 08:39 PM
PrashantKirpan
Occasional Contributor

Hi All,

I want to show available satellites info in application. In latest .Net 100 API i am not able to find any relevant classes. I believe LocationDisplay only provides location information of device.

If functionality is not provided by runtime API then what is the best approach to get satellite details without external GPS?

Any help would be appreciated.

Regards,

Prashant

0 Kudos
1 Solution

Accepted Solutions
dotMorten_esri
Esri Notable Contributor

That information isn't being made available with the OS's built-in location datasources that these devices have, so it's not exposed since there's nothing to expose. You'd have to use an External GPS to get that data.

View solution in original post

5 Replies
dotMorten_esri
Esri Notable Contributor

That information isn't being made available with the OS's built-in location datasources that these devices have, so it's not exposed since there's nothing to expose. You'd have to use an External GPS to get that data.

PrashantKirpan
Occasional Contributor

I was looking dotMorten/NmeaParser. Is location provider works for bluetooth GPS for Windows WPF desktop app?

If not, then could you please point me in the right direction to make it working.

Thanks,

Prashant 

0 Kudos
JoeHershman
MVP Regular Contributor

Morten Nielsen may have upgraded some stuff since the last time I went through setting up things in Windows.  The library does not have bluetooth support in WPF.  WPF does not have native bluetooth support.  There are external bluetooth libraries (e.g., GitHub - inthehand/32feet: Personal Area Networking for .NET ) which will work for WPF, but native API support does not exist, you need UWP for that.

Many external GPS units will mimic a serial connection and in this case it is possible to use Morten's SerialPortDevice.  However, you still do need to create a LocationDataSource in Runtime to use as the DataSource on the LocationProvider.  Using the 32feet library it is possible to create a receiver for the signal over bluetooth in WPF.

Thanks,
-Joe
JoeHershman
MVP Regular Contributor

This is a BluetoothLocationDataSource that was written for a WPF application a while back.  It uses the 32feet library previously mentioned for connectivity and the dotMorten/NmeaParser (thanks Morten Nielsen)  There is code specific to the framework used in the application, but I think you could extract the primary logic.

Note:  Not 100% sure on my computation of Accuracy, I have done research on this topic and found differing computations of overall sigma.  As a side note, accuracy is a bit of a nebulous term, what this really means is there is a certain % chance (usually 95%) that a point falls within a radius of the location given.

public class BluetoothLocationDataSource : LocationDataSource
{
	private ILog4NetFacade Log { get; }

	#region Private Fields

	private bool _isTryingToOpen;
	private MapPoint _mapPoint;
	private double _velocity, _course, _accuracy;

	#endregion

	public BluetoothLocationDataSource(IEventAggregator eventAggregator, ILog4NetFacade log)
	{
		Log = log;
		EventAggregator = eventAggregator;

		EventAggregator.GetEvent<SendNTripCorrectionEvent>().Subscribe(OnSendNTripCorrection);
	}

	protected IEventAggregator EventAggregator { get; }
	protected BluetoothClient BluetoothClient { get; set; }
	public BluetoothDeviceInfo BluetoothDevice { get; set; }

	protected override async Task OnStartAsync()
	{
		try
		{
			//Don't want to come back in if we are still trying to open port
			if ( _isTryingToOpen ) return;
			if ( IsStarted ) return;

			BluetoothClient = new BluetoothClient();

			var devices = await BluetoothClient.DiscoverDevicesAsync(5, true, true, false);
			Log.Debug($"Bluetooth found devices: {devices.Length}");
			for (var i = 0; i < devices.Length; i++)
			{
				var device = devices[i];
				Log.Debug($"Device {i}: Device Name: {device.DeviceName}");
				Log.Debug($"Device {i}: Device Class: {device.ClassOfDevice.Device} {device.ClassOfDevice.Service}");
				Log.Debug($"Device {i}: Service Class: {device.ClassOfDevice.Service}");
			}

			//BluetoothDevice = devices.FirstOrDefault(d => d.ClassOfDevice.Equals(new ClassOfDevice(DeviceClass.Miscellaneous, ServiceClass.Positioning)) && d.DeviceName.IndexOf("SXBlue", StringComparison.CurrentCultureIgnoreCase) > 0);
			BluetoothDevice = devices.FirstOrDefault(d => d.ClassOfDevice.Equals(new ClassOfDevice(DeviceClass.Miscellaneous, ServiceClass.Positioning)));


			if (BluetoothDevice == null)
			{
				Log.Debug("Bluetooth device not found");
				throw new Exception("Unable to locate bluetooth device");
			}

			Log.Debug("Bluetooth device found");

			if ( !BluetoothDevice.InstalledServices.Contains(BluetoothService.SerialPort) )
			{
				Log.Debug("Bluetooth device does not contain Serial Port Service");
				throw new Exception("Bluetooth device is not valid for this LocationDataSource");
			}
			Log.Debug("Bluetooth device contains SerialPort service");

			_isTryingToOpen = true;
			if ( !await OpenPort() )
			{
				Log.Debug("Unable to Open Bluetooth port");
				EventAggregator.GetEvent<GpsConnectCompleteEvent>().Publish(new GpsConnectCompleteEventArgs { ConnectSuccess = false });
				return;
			}

			var _ = ReceiveData();

			EventAggregator.GetEvent<GpsConnectCompleteEvent>().Publish(new GpsConnectCompleteEventArgs { ConnectSuccess = true });
		}
		catch (Exception e)
		{
			Log.Error(e.Message, e);
			// ReSharper disable once PossibleIntendedRethrow
			throw e;
		}
	}


	private async Task<bool> OpenPort()
	{
		for (int i = 0; i < 5; i++)
		{
			try
			{
				await BluetoothClient.ConnectAsync(BluetoothDevice.DeviceAddress, BluetoothService.SerialPort);
				if ( !BluetoothClient.Connected ) throw new Exception();

				Log.Debug($"Bluetooth port opened after {i} attempts");
				_isTryingToOpen = false;
				return true;
			}
			catch (Exception)
			{
				//continue;
			}
		}

		_isTryingToOpen = false;
		return false;
	}

	public void SendMessage()
	{
		var b = Encoding.ASCII.GetBytes("$JT\r\n");
		BluetoothClient.Client.Send(b);
	}

	private async Task ReceiveData()
	{
		byte[] bytesReceived = new byte[256];

		string result = string.Empty;

		do
		{
			var bytes = 0;
			try
			{
				bytes = await BluetoothClient.Client.ReceiveAsync(bytesReceived, 0, 256, SocketFlags.None);
			}
			//exception handlers both do same thing but wanted to know if it is socket exception
			catch (SocketException e)
			{
				Log.Error("Stopped receiving bluetooth data due to scoket exception");
				Log.Error(e.Message);

				//stopped recieving data so going to try and see if we can reconnect
				_isTryingToOpen = false;

				EventAggregator.GetEvent<GpsConnectCompleteEvent>().Publish(new GpsConnectCompleteEventArgs { ConnectSuccess = false });

				break;
			}
			catch (Exception e)
			{
				Log.Error("Stopped receiving bluetooth data");
				Log.Error(e.Message, e);

				//Not receiving data and Socket resets
				EventAggregator.GetEvent<GpsConnectCompleteEvent>().Publish(new GpsConnectCompleteEventArgs { ConnectSuccess = false });

				break;
			}
			result = result + Encoding.ASCII.GetString(bytesReceived, 0, bytes);
			if ( bytes < 10 ) continue;

			string[] lines = result.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
			for (var index = 0; index < lines.Length - 1; index++)
			{
				//split data into lines at this \r\n
				var line = lines[index];

				if ( line.StartsWith("$>") )
				{
					//TODO:  This is a response
				}

				UpdateLocationFromNemaSentence(line);
			}

			result = lines.Last();

		} while (true);
	}

	private void UpdateLocationFromNemaSentence(string line)
	{
		try
		{
			line = line.TrimEnd('\n', '\r');

			var message = NmeaMessage.Parse(line);

			ReadLocationAttributesFromMessage(message);
		}
		catch (Exception)
		{
			//
		}

		if ( _mapPoint == null || double.IsNaN(_mapPoint.X) || double.IsNaN(_mapPoint.Y) )
		{
			UpdateLastKnownLocation();
			return;
		}

		UpdateLocation(new Esri.ArcGISRuntime.Location.Location(_mapPoint, _accuracy, _velocity, _course, false));
	}

	private void ReadLocationAttributesFromMessage(NmeaMessage message)
	{
		switch (message)
		{
			case Gpgga gpgga:
				_mapPoint = new MapPoint(gpgga.Longitude, gpgga.Latitude, SpatialReference.Create(4326));
				EventAggregator.GetEvent<UpdateLastGgaEvent>().Publish(gpgga.ToString());
				break;
			case Gpgsa gpgsa:
			   
				break;
			case Gprmc gprmc:
				_velocity = double.IsNaN(gprmc.Speed) ? 0 : gprmc.Speed * 0.5144; //knots to m\s
				_course = double.IsNaN(gprmc.Course) ? 0 : gprmc.Course;
				_mapPoint = new MapPoint(gprmc.Longitude, gprmc.Latitude, SpatialReference.Create(4326));

				break;
			case Gpgsv gpgsv:
				break;
			case Gpgst gpgst:
				double latError = gpgst.SigmaLatitudeError;
				double lonError = gpgst.SigmaLongitudeError;

				_accuracy = Math.Sqrt(0.5 * (Math.Pow(latError, 2) + Math.Pow(lonError, 2)));

				break;
		}
	}

	private void UpdateLastKnownLocation()
	{
		//If latest point not valid send previous point as LastKnown
		if ( _mapPoint == null )
		{
			_mapPoint = new MapPoint(double.NaN, double.NaN, SpatialReferences.Wgs84);
		}

		UpdateLocation(new Esri.ArcGISRuntime.Location.Location(_mapPoint, _accuracy, _velocity, _course, true));
	}

	protected override Task OnStopAsync()
	{
		BluetoothClient?.Client?.Close(250);
		BluetoothClient?.Close();
		BluetoothClient = null;

		return Task.FromResult(true);
	}

	private async void OnSendNTripCorrection(SendNTripCorrectionEventArgs obj)
	{
		try
		{
			await BluetoothClient.Client.SendAsync(obj.Bytes);
		}
		catch (Exception e)
		{
			Log.Error(e.Message, e);
		}
	}
}
Thanks,
-Joe
PrashantKirpan
Occasional Contributor

Really appreciate your help Joe,

As you said earlier, I have verified my Bluetooth GPS(Garmin) device and it uses serial port for communication. So I can use  dotMorten/NmeaParser library as is for now.

 I will definitely try to integrate Bluetooth locationdatasource code if required.

Thanks again,

Prashant

0 Kudos