diff --git a/src/SampleApp.WinDesktop/BluetoothDevice.cs b/src/SampleApp.WinDesktop/BluetoothDevice.cs
new file mode 100644
index 0000000..00bf599
--- /dev/null
+++ b/src/SampleApp.WinDesktop/BluetoothDevice.cs
@@ -0,0 +1,152 @@
+// Bluetooth device using the Win10 contracts
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Threading.Tasks;
+using Windows.Networking.Sockets;
+using Windows.Devices.Bluetooth.Rfcomm;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading;
+using Windows.Devices.Enumeration;
+using Windows.Networking.Proximity;
+using NmeaParser;
+
+namespace SampleApp.WinDesktop
+{
+ public class BluetoothDevice : NmeaDevice
+ {
+ private Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService? m_deviceService;
+ private Windows.Networking.Proximity.PeerInformation? m_devicePeer;
+ private StreamSocket? m_socket;
+ private bool m_disposeService;
+ private SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Gets a list of bluetooth devices that supports serial communication
+ ///
+ /// A set of bluetooth devices available that supports serial connections
+ public static async Task> GetBluetoothSerialDevicesAsync()
+ {
+ List services = new List();
+ if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService"))
+ {
+
+ string serialDeviceType = RfcommDeviceService.GetDeviceSelector(RfcommServiceId.SerialPort);
+ var devices = await DeviceInformation.FindAllAsync(serialDeviceType);
+ foreach (var d in devices)
+ services.Add(await RfcommDeviceService.FromIdAsync(d.Id));
+ }
+ return services;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The RF Comm Device service.
+ /// Whether this devicee should also dispose the RfcommDeviceService provided when this device disposes.
+ public BluetoothDevice(RfcommDeviceService service, bool disposeService = false)
+ {
+ m_deviceService = service ?? throw new ArgumentNullException(nameof(service));
+ m_disposeService = disposeService;
+ }
+
+ public RfcommDeviceService Service => m_deviceService;
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (m_disposeService && m_deviceService != null)
+ m_deviceService.Dispose();
+ m_deviceService = null;
+ m_devicePeer = null;
+ base.Dispose(disposing);
+ }
+
+ ///
+ protected override async Task OpenStreamAsync()
+ {
+ var socket = new Windows.Networking.Sockets.StreamSocket();
+ socket.Control.KeepAlive = true;
+ if (m_devicePeer != null)
+ {
+ await socket.ConnectAsync(m_devicePeer.HostName, "1");
+ }
+ else if (m_deviceService != null)
+ {
+ await socket.ConnectAsync(m_deviceService.ConnectionHostName, m_deviceService.ConnectionServiceName);
+ }
+ else
+ throw new InvalidOperationException();
+ m_socket = socket;
+
+ return new DummyStream(); //We're going to use WinRT buffers instead and will handle read/write, so no reason to return a real stream. This is mainly done to avoid locking issues reading and writing at the same time
+ }
+
+ private class DummyStream : Stream
+ {
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => false;
+ public override long Length => throw new NotSupportedException();
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+ public override void Flush() => throw new NotSupportedException();
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+ public override void SetLength(long value) => throw new NotSupportedException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+ }
+
+ ///
+ protected override Task CloseStreamAsync(System.IO.Stream stream)
+ {
+ if (m_socket == null)
+ throw new InvalidOperationException("No connection to close");
+ m_socket.Dispose();
+ m_socket = null;
+ return Task.FromResult(true);
+ }
+
+
+ ///
+ protected override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ // Reading and writing to the Bluetooth serial connection at the same time seems very unstable in UWP,
+ // so we use a semaphore to ensure we don't read and write at the same time
+ await m_semaphoreSlim.WaitAsync().ConfigureAwait(false);
+ if (m_socket == null)
+ throw new InvalidOperationException("Socket not initialized");
+ try
+ {
+ var r = await m_socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)count, Windows.Storage.Streams.InputStreamOptions.None);
+ return (int)r.Length;
+ }
+ finally
+ {
+ m_semaphoreSlim.Release();
+ }
+ }
+
+ ///
+ public override bool CanWrite => true;
+
+ ///
+ public override async Task WriteAsync(byte[] buffer, int offset, int length)
+ {
+ if (m_socket == null)
+ throw new InvalidOperationException("Device not open");
+ // Reading and writing to the Bluetooth serial connection at the same time seems very unstable in UWP,
+ // so we use a semaphore to ensure we don't read and write at the same time
+ await m_semaphoreSlim.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ await m_socket.OutputStream.WriteAsync(buffer.AsBuffer(offset, length)).AsTask().ConfigureAwait(false);
+ }
+ finally
+ {
+ m_semaphoreSlim.Release();
+ }
+ }
+ }
+}
diff --git a/src/SampleApp.WinDesktop/MainWindow.xaml b/src/SampleApp.WinDesktop/MainWindow.xaml
index ad24d2e..9de8803 100644
--- a/src/SampleApp.WinDesktop/MainWindow.xaml
+++ b/src/SampleApp.WinDesktop/MainWindow.xaml
@@ -88,6 +88,12 @@
+
+
+
+
+
+
diff --git a/src/SampleApp.WinDesktop/MainWindow.xaml.cs b/src/SampleApp.WinDesktop/MainWindow.xaml.cs
index b4da0e6..d1c92f6 100644
--- a/src/SampleApp.WinDesktop/MainWindow.xaml.cs
+++ b/src/SampleApp.WinDesktop/MainWindow.xaml.cs
@@ -30,7 +30,7 @@ namespace SampleApp.WinDesktop
InitializeComponent();
//Get list of serial ports for device tab
- var availableSerialPorts = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s=>s);
+ var availableSerialPorts = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s => s);
serialPorts.ItemsSource = availableSerialPorts;
serialPorts.SelectedIndex = 0;
// Use serial portName:
@@ -41,6 +41,37 @@ namespace SampleApp.WinDesktop
//Use a log file for playing back logged data
var device = new NmeaParser.NmeaFileDevice("NmeaSampleData.txt") { EmulatedBaudRate = 9600, BurstRate = TimeSpan.FromSeconds(1d) };
_ = StartDevice(device);
+
+ LoadBluetoothDevices();
+
+ }
+ public class DeviceInfo
+ {
+ public Func> CreateMethod { get; set; }
+ public string DisplayName { get; set; }
+ public override string ToString() => DisplayName;
+ }
+ private async void LoadBluetoothDevices()
+ {
+ var deviceList = new List();
+ if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService"))
+ {
+ var btdevices = await BluetoothDevice.GetBluetoothSerialDevicesAsync();
+ foreach (var item in btdevices)
+ {
+ deviceList.Add(new DeviceInfo()
+ {
+ DisplayName = $"{item.Device.Name} (Bluetooth)",
+ CreateMethod = () =>
+ {
+ return Task.FromResult(new BluetoothDevice(item));
+ }
+ });
+ }
+ }
+ bluetoothDevices.ItemsSource = deviceList;
+ if (deviceList.Count > 0)
+ bluetoothDevices.SelectedIndex = 0;
}
///
@@ -74,7 +105,7 @@ namespace SampleApp.WinDesktop
satSnr.ClearGsv();
//Start new device
currentDevice = device;
- foreach(var child in MessagePanel.Children.OfType().ToArray())
+ foreach (var child in MessagePanel.Children.OfType().ToArray())
{
MessagePanel.Children.Remove(child);
}
@@ -88,6 +119,10 @@ namespace SampleApp.WinDesktop
((NmeaParser.SerialPortDevice)device).Port.PortName,
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
}
+ else if (device is BluetoothDevice bd)
+ {
+ currentDeviceInfo.Text = $"Bluetooth {bd.Service.Device.Name}";
+ }
await device.OpenAsync();
gnssMonitorView.Monitor = monitor = new GnssMonitor(device);
gnssMonitorView.Monitor.LocationChanged += Monitor_LocationChanged;
@@ -103,42 +138,42 @@ namespace SampleApp.WinDesktop
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
{
- Dispatcher.BeginInvoke((Action) delegate()
- {
- messages.Enqueue(args.Message.ToString());
- if (messages.Count > 100) messages.Dequeue(); //Keep message queue at 100
+ Dispatcher.BeginInvoke((Action)delegate ()
+ {
+ messages.Enqueue(args.Message.ToString());
+ if (messages.Count > 100) messages.Dequeue(); //Keep message queue at 100
output.Text = string.Join("\n", messages.ToArray());
- output.Select(output.Text.Length - 1, 0); //scroll to bottom
+ output.Select(output.Text.Length - 1, 0); //scroll to bottom
if (args.Message is NmeaParser.Messages.Gsv gpgsv)
- {
- satView.SetGsv(gpgsv);
- satSnr.SetGsv(gpgsv);
- }
- else if (args.Message is NmeaParser.Messages.Rmc)
- gprmcView.Message = args.Message as NmeaParser.Messages.Rmc;
- else if (args.Message is NmeaParser.Messages.Gga)
- gpggaView.Message = args.Message as NmeaParser.Messages.Gga;
- else if (args.Message is NmeaParser.Messages.Gsa)
- gpgsaView.Message = args.Message as NmeaParser.Messages.Gsa;
- else if (args.Message is NmeaParser.Messages.Gll)
- gpgllView.Message = args.Message as NmeaParser.Messages.Gll;
- else if (args.Message is NmeaParser.Messages.Garmin.Pgrme)
- pgrmeView.Message = args.Message as NmeaParser.Messages.Garmin.Pgrme;
- else
- {
- var ctrl = MessagePanel.Children.OfType().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault();
- if (ctrl == null)
- {
- ctrl = new UnknownMessageControl()
- {
- Style = this.Resources["card"] as Style
- };
- MessagePanel.Children.Add(ctrl);
- }
- ctrl.Message = args.Message;
- }
- });
+ {
+ satView.SetGsv(gpgsv);
+ satSnr.SetGsv(gpgsv);
+ }
+ else if (args.Message is NmeaParser.Messages.Rmc)
+ gprmcView.Message = args.Message as NmeaParser.Messages.Rmc;
+ else if (args.Message is NmeaParser.Messages.Gga)
+ gpggaView.Message = args.Message as NmeaParser.Messages.Gga;
+ else if (args.Message is NmeaParser.Messages.Gsa)
+ gpgsaView.Message = args.Message as NmeaParser.Messages.Gsa;
+ else if (args.Message is NmeaParser.Messages.Gll)
+ gpgllView.Message = args.Message as NmeaParser.Messages.Gll;
+ else if (args.Message is NmeaParser.Messages.Garmin.Pgrme)
+ pgrmeView.Message = args.Message as NmeaParser.Messages.Garmin.Pgrme;
+ else
+ {
+ var ctrl = MessagePanel.Children.OfType().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault();
+ if (ctrl == null)
+ {
+ ctrl = new UnknownMessageControl()
+ {
+ Style = this.Resources["card"] as Style
+ };
+ MessagePanel.Children.Add(ctrl);
+ }
+ ctrl.Message = args.Message;
+ }
+ });
}
//Browse to nmea file and create device from selected file
@@ -153,7 +188,7 @@ namespace SampleApp.WinDesktop
{
await StartDevice(device);
}
- catch(System.Exception ex)
+ catch (System.Exception ex)
{
MessageBox.Show("Failed to start device: " + ex.Message);
}
@@ -177,13 +212,39 @@ namespace SampleApp.WinDesktop
MessageBox.Show("Failed to start device: " + ex.Message);
}
}
- catch(System.Exception ex)
+ catch (System.Exception ex)
+ {
+ MessageBox.Show("Error connecting: " + ex.Message);
+ }
+ }
+
+
+ private async void ConnectToBluetoothButton_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var info = bluetoothDevices.SelectedItem as DeviceInfo;
+ if (info != null)
+ {
+ var device = await info.CreateMethod();
+ try
+ {
+ await StartDevice(device);
+ }
+ catch (System.Exception ex)
+ {
+ MessageBox.Show("Failed to start device: " + ex.Message);
+ }
+ }
+ }
+ catch (System.Exception ex)
{
MessageBox.Show("Error connecting: " + ex.Message);
}
}
}
-
+
+
public class ReverseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
diff --git a/src/SampleApp.WinDesktop/SampleApp.NetCore.csproj b/src/SampleApp.WinDesktop/SampleApp.NetCore.csproj
index 1fd8daa..33cc83c 100644
--- a/src/SampleApp.WinDesktop/SampleApp.NetCore.csproj
+++ b/src/SampleApp.WinDesktop/SampleApp.NetCore.csproj
@@ -20,6 +20,7 @@
PreserveNewest
+
PreserveNewest