Various sample tweaks + added BT sample to Android.

This commit is contained in:
Morten Nielsen 2018-07-10 16:04:35 -07:00
parent 102805a100
commit 2e4e6aa939
11 changed files with 264 additions and 107 deletions

View file

@ -11,7 +11,7 @@
<Description>An NMEA stream parser for serial port, bluetooth and file-based nmea simulation.</Description>
<PackageTags>nmea winrt wpf uwp xamarin gps serialport bluetooth</PackageTags>
<PackageId>SharpGIS.NmeaParser</PackageId>
<Version>1.10.0</Version>
<Version>1.10.1</Version>
<PackageLicenseUrl>http://opensource.org/licenses/ms-pl.html</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/dotMorten/NmeaParser</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotMorten/NmeaParser</RepositoryUrl>
@ -19,9 +19,10 @@
<Copyright>Copyright © Morten Nielsen 2015-2018</Copyright>
<OutputPath>$(MSBuildThisFileDirectory)..\Bin\$(Configuration)</OutputPath>
<PackageOutputPath>$(OutDir)</PackageOutputPath>
<AssemblyVersion>1.9.0.0</AssemblyVersion>
<FileVersion>1.9.0.0</FileVersion>
<PackageReleaseNotes>Added SystemNmeaDevice to the Android library, which uses the NMEA stream from the system's location provider.</PackageReleaseNotes>
<AssemblyVersion>1.10.1.0</AssemblyVersion>
<FileVersion>1.10.1.0</FileVersion>
<PackageReleaseNotes>Fixed missing shutdown of Android location device.
Exposed non-NMEA based Accuracy estimate on Android location device.</PackageReleaseNotes>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard1.4'">

View file

@ -23,13 +23,23 @@ namespace NmeaParser
private LocationManager manager;
/// <summary>
/// Initializes a new instance of the <see cref="SystemNmeaListener"/> class.
/// Initializes a new instance of the <see cref="SystemNmeaDevice"/> class.
/// </summary>
public SystemNmeaDevice()
{
manager = Application.Context.GetSystemService(Context.LocationService) as LocationManager;
}
/// <summary>
/// Gets the estimated accuracy of this location, in meters as reported by the system location provider (not NMEA based).
/// Returns NaN if no estimate is available or the active location provider isn't GPS (ie IP/WiFi/Network Provider)
/// </summary>
/// <remarks>
/// Normally the **GST messages will provide the estimated accuracy, but this is not always exposed as NMEA
/// on Android devices, so this serves as a useful fallback.
/// </remarks>
public float Accuracy => listener == null ? float.NaN : listener.Accuracy;
/// <inheritdoc />
[Android.Runtime.RequiresPermission("android.permission.ACCESS_FINE_LOCATION")]
protected override Task<Stream> OpenStreamAsync()
@ -43,8 +53,7 @@ namespace NmeaParser
listener = new Listener();
listener.NmeaMessage += (s, e) => stream?.Append(e);
bool success = manager.AddNmeaListener(listener);
manager.RequestLocationUpdates(LocationManager.GpsProvider, 100, .1f, listener );
manager.RequestLocationUpdates(LocationManager.GpsProvider, 0, 0f, listener );
return Task.FromResult<Stream>(stream);
}
@ -81,10 +90,17 @@ namespace NmeaParser
public event EventHandler<string> NmeaMessage;
public float Accuracy = float.NaN;
void ILocationListener.OnLocationChanged(Location location)
{
if (location.Provider != LocationManager.GpsProvider)
{
Accuracy = float.NaN;
return;
}
Accuracy = location.HasAccuracy ? location.Accuracy : float.NaN;
if (_isNmeaSupported) return;
if (location.Provider != LocationManager.GpsProvider) return;
// Not all Android devices support reporting NMEA, so we'll fallback to just generating
// simple RMC and GGA message so the provider continues to work across multiple devices
// $GPRMC:

View file

@ -13,47 +13,148 @@ using NmeaParser.Nmea;
namespace SampleApp.Droid
{
[Activity(Label = "SampleApp.Droid", MainLauncher = true)]
[Activity(Label = "NMEA Parser SampleApp", MainLauncher = true)]
public class MainActivity : Activity
{
private Button startButton;
private Button stopButton;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
startButton = FindViewById<Button>(Resource.Id.startButton);
startButton.Click += StartButton_Click;
Start();
stopButton = FindViewById<Button>(Resource.Id.stopButton);
stopButton.Click += StopButton_Click;
stopButton.Enabled = false;
devices.Add("System GPS", null);
var devicePicker = FindViewById<Spinner>(Resource.Id.device_picker);
Java.Util.UUID SERIAL_UUID = Java.Util.UUID.FromString("00001101-0000-1000-8000-00805F9B34FB");
var adapter = Android.Bluetooth.BluetoothAdapter.DefaultAdapter;
foreach (var d in adapter.BondedDevices.Where(d => d.GetUuids().Where(t => t.Uuid.ToString().Equals("00001101-0000-1000-8000-00805F9B34FB", StringComparison.InvariantCultureIgnoreCase)).Any()))
{
devices[d.Name + " " + d.Address] = d.Address;
}
devicePicker.Adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, devices.Keys.ToArray());
devicePicker.SetSelection(0);
}
private NmeaParser.SystemNmeaDevice listener;
private Dictionary<string, string> devices = new Dictionary<string, string>();
private void StopButton_Click(object sender, EventArgs e)
{
if (listener?.IsOpen == true)
{
Stop();
}
}
private void Stop()
{
listener.MessageReceived -= Listener_MessageReceived;
socket?.Close();
socket?.Dispose();
socket = null;
listener.CloseAsync();
listener = null;
startButton.Enabled = !(stopButton.Enabled = false);
}
private void StartButton_Click(object sender, EventArgs e)
{
if (listener?.IsOpen != true)
{
Start();
}
}
private NmeaParser.NmeaDevice listener;
private TextView status;
private bool launched;
private Android.Bluetooth.BluetoothSocket socket;
private async void Start()
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessFineLocation) != Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.AccessFineLocation }, 1000);
return;
}
if (launched)
return;
launched = true;
listener = new NmeaParser.SystemNmeaDevice();
listener.MessageReceived += Listener_MessageReceived;
startButton.Enabled = false;
status = FindViewById<TextView>(Resource.Id.output);
status.Text = "Opening device...";
await listener.OpenAsync();
var devicePicker = FindViewById<Spinner>(Resource.Id.device_picker);
var id = devicePicker.SelectedItem.ToString();
var btAddress = devices[id];
if (btAddress == null)
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessFineLocation) != Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.AccessFineLocation }, 1000);
return;
}
if (launched)
return;
launched = true;
listener = new NmeaParser.SystemNmeaDevice();
}
else //Bluetooth
{
try
{
status.Text = "Opening bluetooth...";
var adapter = Android.Bluetooth.BluetoothAdapter.DefaultAdapter;
var bt = Android.Bluetooth.BluetoothAdapter.DefaultAdapter.GetRemoteDevice(btAddress);
Java.Util.UUID SERIAL_UUID = Java.Util.UUID.FromString("00001101-0000-1000-8000-00805F9B34FB"); //UUID for Serial Device Service
socket = bt.CreateRfcommSocketToServiceRecord(SERIAL_UUID);
try
{
await socket.ConnectAsync();
}
catch(Java.IO.IOException)
{
// This sometimes fails. Use fallback approach to open socket
// Based on https://stackoverflow.com/a/41627149
socket.Dispose();
var m = bt.Class.GetMethod("createRfcommSocket", new Java.Lang.Class[] { Java.Lang.Integer.Type });
socket = m.Invoke(bt, new Java.Lang.Object[] { 1 }) as Android.Bluetooth.BluetoothSocket;
socket.Connect();
}
listener = new NmeaParser.StreamDevice(socket.InputStream);
}
catch(System.Exception ex)
{
socket?.Dispose();
socket = null;
status.Text += "\nError opening Bluetooth device:\n" + ex.Message;
}
}
if (listener != null)
{
listener.MessageReceived += Listener_MessageReceived;
status.Text += "\nOpening device...";
await listener.OpenAsync();
status.Text += "\nConnected!";
startButton.Enabled = !(stopButton.Enabled = true);
}
else
{
startButton.Enabled = !(stopButton.Enabled = false);
}
}
protected override void OnDestroy()
{
Stop();
base.OnDestroy();
}
protected override void OnResume()
{
base.OnResume();
// if it was resumed by the GPS permissions dialog
Start();
//Start();
}
Queue<NmeaParser.Nmea.NmeaMessage> messages = new Queue<NmeaParser.Nmea.NmeaMessage>(100);

View file

@ -3,5 +3,6 @@
<uses-sdk android:minSdkVersion="21" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application android:allowBackup="true" android:label="@string/app_name"></application>
</manifest>

View file

@ -243,23 +243,23 @@ namespace SampleApp.Droid
public partial class Id
{
// aapt resource value: 0x7f0a0015
public const int action0 = 2131361813;
// aapt resource value: 0x7f0a0012
public const int action0 = 2131361810;
public const int action_container = 2131361810;
// aapt resource value: 0x7f0a000f
public const int action_container = 2131361807;
// aapt resource value: 0x7f0a0019
public const int action_divider = 2131361817;
// aapt resource value: 0x7f0a0016
public const int action_divider = 2131361814;
// aapt resource value: 0x7f0a0013
public const int action_image = 2131361811;
// aapt resource value: 0x7f0a0010
public const int action_image = 2131361808;
// aapt resource value: 0x7f0a0014
public const int action_text = 2131361812;
// aapt resource value: 0x7f0a0011
public const int action_text = 2131361809;
// aapt resource value: 0x7f0a0020
public const int actions = 2131361824;
// aapt resource value: 0x7f0a0023
public const int actions = 2131361827;
// aapt resource value: 0x7f0a000d
public const int altitude = 2131361805;
@ -270,26 +270,29 @@ namespace SampleApp.Droid
// aapt resource value: 0x7f0a0007
public const int blocking = 2131361799;
// aapt resource value: 0x7f0a0013
public const int cancel_action = 2131361811;
// aapt resource value: 0x7f0a0016
public const int cancel_action = 2131361814;
// aapt resource value: 0x7f0a001b
public const int chronometer = 2131361819;
// aapt resource value: 0x7f0a001e
public const int chronometer = 2131361822;
// aapt resource value: 0x7f0a0022
public const int end_padder = 2131361826;
// aapt resource value: 0x7f0a000e
public const int device_picker = 2131361806;
// aapt resource value: 0x7f0a0025
public const int end_padder = 2131361829;
// aapt resource value: 0x7f0a0008
public const int forever = 2131361800;
// aapt resource value: 0x7f0a001d
public const int icon = 2131361821;
// aapt resource value: 0x7f0a0020
public const int icon = 2131361824;
// aapt resource value: 0x7f0a0021
public const int icon_group = 2131361825;
// aapt resource value: 0x7f0a0024
public const int icon_group = 2131361828;
// aapt resource value: 0x7f0a001c
public const int info = 2131361820;
// aapt resource value: 0x7f0a001f
public const int info = 2131361823;
// aapt resource value: 0x7f0a0009
public const int italic = 2131361801;
@ -306,32 +309,38 @@ namespace SampleApp.Droid
// aapt resource value: 0x7f0a000b
public const int longitude = 2131361803;
// aapt resource value: 0x7f0a0015
public const int media_actions = 2131361813;
// aapt resource value: 0x7f0a0018
public const int media_actions = 2131361816;
// aapt resource value: 0x7f0a000a
public const int normal = 2131361802;
// aapt resource value: 0x7f0a001f
public const int notification_background = 2131361823;
// aapt resource value: 0x7f0a0022
public const int notification_background = 2131361826;
// aapt resource value: 0x7f0a0018
public const int notification_main_column = 2131361816;
// aapt resource value: 0x7f0a001b
public const int notification_main_column = 2131361819;
// aapt resource value: 0x7f0a001a
public const int notification_main_column_container = 2131361818;
// aapt resource value: 0x7f0a0011
public const int output = 2131361809;
// aapt resource value: 0x7f0a0021
public const int right_icon = 2131361825;
// aapt resource value: 0x7f0a001c
public const int right_side = 2131361820;
// aapt resource value: 0x7f0a000f
public const int startButton = 2131361807;
// aapt resource value: 0x7f0a0017
public const int notification_main_column_container = 2131361815;
public const int status_bar_latest_event_content = 2131361815;
// aapt resource value: 0x7f0a000e
public const int output = 2131361806;
// aapt resource value: 0x7f0a001e
public const int right_icon = 2131361822;
// aapt resource value: 0x7f0a0019
public const int right_side = 2131361817;
// aapt resource value: 0x7f0a0014
public const int status_bar_latest_event_content = 2131361812;
// aapt resource value: 0x7f0a0010
public const int stopButton = 2131361808;
// aapt resource value: 0x7f0a0002
public const int tag_transition_group = 2131361794;
@ -342,8 +351,8 @@ namespace SampleApp.Droid
// aapt resource value: 0x7f0a0004
public const int text2 = 2131361796;
// aapt resource value: 0x7f0a001a
public const int time = 2131361818;
// aapt resource value: 0x7f0a001d
public const int time = 2131361821;
// aapt resource value: 0x7f0a0005
public const int title = 2131361797;

View file

@ -3,19 +3,45 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/longitude"
<TextView
android:id="@+id/longitude"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView android:id="@+id/latitude"
android:layout_height="wrap_content"
android:text="Latitude = " />
<TextView
android:id="@+id/latitude"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView android:id="@+id/altitude"
android:layout_height="wrap_content"
android:text="Longitude = " />
<TextView
android:id="@+id/altitude"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_height="wrap_content"
android:text="Altitude =" />
<Spinner
android:id="@+id/device_picker"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginTop="20dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="start" />
<Button
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="stop" />
</LinearLayout>
<TextView
android:id="@+id/output"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
/>
android:scrollbars="vertical" />
</LinearLayout>

View file

@ -37,7 +37,7 @@
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
@ -48,9 +48,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidUseSharedRuntime>true</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<EmbedAssembliesIntoApk>false</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -39,7 +39,7 @@
<local:SatelliteView MaxWidth="{Binding ActualHeight, ElementName=satView}"
Grid.Column="1" x:Name="satView" />
<local:SatelliteSnr Grid.Row="1"
GpgsvMessages="{Binding GpgsvMessages, ElementName=satView}" />
GsvMessages="{Binding GsvMessages, ElementName=satView}" />
</Grid>
</TabItem>
<TabItem Header="Messages">

View file

@ -12,7 +12,8 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NmeaParser;
namespace SampleApp.WinDesktop
{
/// <summary>
@ -66,7 +67,7 @@ namespace SampleApp.WinDesktop
gpgsaView.Message = null;
gpgllView.Message = null;
pgrmeView.Message = null;
satView.GpgsvMessages = null;
satView.GsvMessages = null;
//Start new device
currentDevice = device;
currentDevice.MessageReceived += device_MessageReceived;
@ -78,9 +79,10 @@ namespace SampleApp.WinDesktop
currentDeviceInfo.Text = string.Format("SerialPortDevice( port={0}, baud={1} )",
((NmeaParser.SerialPortDevice)device).Port.PortName,
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
}
}
}
}
Dictionary<string, List<NmeaParser.Nmea.Gsv>> gsvMessages = new Dictionary<string, List<NmeaParser.Nmea.Gsv>>();
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
{
Dispatcher.BeginInvoke((Action) delegate()
@ -90,11 +92,13 @@ namespace SampleApp.WinDesktop
output.Text = string.Join("\n", messages.ToArray());
output.Select(output.Text.Length - 1, 0); //scroll to bottom
if(args.Message is NmeaParser.Nmea.Gps.Gpgsv)
{
var gpgsv = (NmeaParser.Nmea.Gps.Gpgsv)args.Message;
if(args.IsMultipart && args.MessageParts != null)
satView.GpgsvMessages = args.MessageParts.OfType<NmeaParser.Nmea.Gps.Gpgsv>();
if(args.Message is NmeaParser.Nmea.Gsv gpgsv)
{
if (args.IsMultipart && args.MessageParts != null)
{
gsvMessages[args.Message.MessageType] = args.MessageParts.OfType<NmeaParser.Nmea.Gsv>().ToList();
satView.GsvMessages = gsvMessages.SelectMany(m=>m.Value);
}
}
if (args.Message is NmeaParser.Nmea.Gps.Gprmc)
gprmcView.Message = args.Message as NmeaParser.Nmea.Gps.Gprmc;

View file

@ -25,19 +25,19 @@ namespace SampleApp.WinDesktop
InitializeComponent();
}
public IEnumerable<NmeaParser.Nmea.Gps.Gpgsv> GpgsvMessages
public IEnumerable<NmeaParser.Nmea.Gsv> GsvMessages
{
get { return (IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>)GetValue(GpgsvMessagesProperty); }
set { SetValue(GpgsvMessagesProperty, value); }
get { return (IEnumerable<NmeaParser.Nmea.Gsv>)GetValue(GsvMessagesProperty); }
set { SetValue(GsvMessagesProperty, value); }
}
// Using a DependencyProperty as the backing store for GpgsvMessages. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GpgsvMessagesProperty =
DependencyProperty.Register("GpgsvMessages", typeof(IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>), typeof(SatelliteSnr), new PropertyMetadata(null, OnGpgsvMessagesChanged));
public static readonly DependencyProperty GsvMessagesProperty =
DependencyProperty.Register("GsvMessages", typeof(IEnumerable<NmeaParser.Nmea.Gsv>), typeof(SatelliteSnr), new PropertyMetadata(null, OnGpgsvMessagesChanged));
private static void OnGpgsvMessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sats = e.NewValue as IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>;
var sats = e.NewValue as IEnumerable<NmeaParser.Nmea.Gsv>;
if (sats == null)
(d as SatelliteSnr).satellites.ItemsSource = null;
else

View file

@ -27,19 +27,18 @@ namespace SampleApp.WinDesktop
public IEnumerable<NmeaParser.Nmea.Gps.Gpgsv> GpgsvMessages
public IEnumerable<NmeaParser.Nmea.Gsv> GsvMessages
{
get { return (IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>)GetValue(GpgsvMessagesProperty); }
set { SetValue(GpgsvMessagesProperty, value); }
get { return (IEnumerable<NmeaParser.Nmea.Gsv>)GetValue(GsvMessagesProperty); }
set { SetValue(GsvMessagesProperty, value); }
}
// Using a DependencyProperty as the backing store for GpgsvMessages. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GpgsvMessagesProperty =
DependencyProperty.Register("GpgsvMessages", typeof(IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>), typeof(SatelliteView), new PropertyMetadata(null, OnGpgsvMessagesChanged));
public static readonly DependencyProperty GsvMessagesProperty =
DependencyProperty.Register("GsvMessages", typeof(IEnumerable<NmeaParser.Nmea.Gsv>), typeof(SatelliteView), new PropertyMetadata(null, OnGsvMessagesChanged));
private static void OnGpgsvMessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnGsvMessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sats = e.NewValue as IEnumerable<NmeaParser.Nmea.Gps.Gpgsv>;
var sats = e.NewValue as IEnumerable<NmeaParser.Nmea.Gsv>;
if (sats == null)
(d as SatelliteView).satellites.ItemsSource = null;
else