diff --git a/src/NmeaParser/BluetoothDevice.WinStore.cs b/src/NmeaParser/BluetoothDevice.WinStore.cs index efb359c..0bf62aa 100644 --- a/src/NmeaParser/BluetoothDevice.WinStore.cs +++ b/src/NmeaParser/BluetoothDevice.WinStore.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) 2014 Morten Nielsen -// -// Licensed under the Microsoft Public License (Ms-PL) (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://opensource.org/licenses/Ms-PL.html -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +// +// Copyright (c) 2014 Morten Nielsen +// +// Licensed under the Microsoft Public License (Ms-PL) (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://opensource.org/licenses/Ms-PL.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// #if NETFX_CORE using System; using System.Collections.Generic; @@ -23,7 +23,8 @@ using System.Threading.Tasks; using Windows.Networking.Sockets; using Windows.Devices.Bluetooth.Rfcomm; using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; +using System.Threading; +using Windows.Devices.Enumeration; #if WINDOWS_UWP using Windows.Networking.Proximity; #endif @@ -39,16 +40,33 @@ namespace NmeaParser #if WINDOWS_UWP private Windows.Networking.Proximity.PeerInformation m_devicePeer; #endif - private StreamSocket m_socket; - - /// - /// Initializes a new instance of the class. - /// - /// The RF Comm Device service. - public BluetoothDevice(Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService service) + private StreamSocket m_socket; + private bool m_disposeService; + + /// + /// Gets a list of bluetooth devices that supports serial communication + /// + /// + public static async Task> GetBluetoothSerialDevicesAsync() + { + string serialDeviceType = RfcommDeviceService.GetDeviceSelector(RfcommServiceId.SerialPort); + var devices = await DeviceInformation.FindAllAsync(serialDeviceType); + List services = new List(); + 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; - } + m_disposeService = disposeService; + } #if WINDOWS_UWP @@ -62,6 +80,16 @@ namespace NmeaParser } #endif + /// + protected override void Dispose(bool disposing) + { + if (m_disposeService && m_deviceService != null) + m_deviceService.Dispose(); + m_deviceService = null; + m_devicePeer = null; + base.Dispose(disposing); + } + /// /// Creates the stream the NmeaDevice is working on top off. /// @@ -69,6 +97,7 @@ namespace NmeaParser protected override async Task OpenStreamAsync() { var socket = new Windows.Networking.Sockets.StreamSocket(); + socket.Control.KeepAlive = true; #if WINDOWS_UWP if (m_devicePeer != null) { @@ -80,7 +109,7 @@ namespace NmeaParser await socket.ConnectAsync(m_deviceService.ConnectionHostName, m_deviceService.ConnectionServiceName); } m_socket = socket; - return socket.InputStream.AsStreamForRead(); + return null; //We're going to use WinRT buffers instead and will handle read/write, so no reason to return a stream } /// @@ -90,13 +119,52 @@ namespace NmeaParser /// protected override Task CloseStreamAsync(System.IO.Stream stream) { - if (stream == null) - throw new ArgumentNullException("stream"); - stream.Dispose(); + if(m_socket == null) + throw new InvalidOperationException("No connection to close"); m_socket.Dispose(); m_socket = null; return Task.FromResult(true); } + + private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + /// + 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 semaphoreSlim.WaitAsync().ConfigureAwait(false); + try + { + var r = await m_socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)count, Windows.Storage.Streams.InputStreamOptions.None); + return (int)r.Length; + } + finally + { + 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 semaphoreSlim.WaitAsync().ConfigureAwait(false); + try + { + await m_socket.OutputStream.WriteAsync(buffer.AsBuffer(offset, length)).AsTask().ConfigureAwait(false); + } + finally + { + semaphoreSlim.Release(); + } + } } } #endif \ No newline at end of file diff --git a/src/NmeaParser/NmeaDevice.cs b/src/NmeaParser/NmeaDevice.cs index 5f7c60a..bb58aaf 100644 --- a/src/NmeaParser/NmeaDevice.cs +++ b/src/NmeaParser/NmeaDevice.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; +using System.Threading; namespace NmeaParser { @@ -40,6 +41,8 @@ namespace NmeaParser protected NmeaDevice() { } + + bool isOpening; /// /// Opens the device connection. /// @@ -48,13 +51,18 @@ namespace NmeaParser { lock (m_lockObject) { - if (IsOpen) return; - IsOpen = true; + if (IsOpen || isOpening) return; + isOpening = true; } m_cts = new System.Threading.CancellationTokenSource(); m_stream = await OpenStreamAsync(); StartParser(); MultiPartMessageCache.Clear(); + lock (m_lockObject) + { + IsOpen = true; + isOpening = false; + } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "_")] @@ -64,14 +72,13 @@ namespace NmeaParser System.Diagnostics.Debug.WriteLine("Starting parser..."); var _ = Task.Run(async () => { - var stream = m_stream; byte[] buffer = new byte[1024]; while (!token.IsCancellationRequested) { int readCount = 0; try { - readCount = await stream.ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false); + readCount = await ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false); } catch { } if (token.IsCancellationRequested) @@ -87,11 +94,33 @@ namespace NmeaParser }); } + /// + /// Performs a read operation of the stream + /// + /// The buffer to write the data into. + /// The byte offset in buffer at which to begin writing data from the stream. + /// The maximum number of bytes to read. + /// The token to monitor for cancellation requests. The default value is System.Threading.CancellationToken.None. + /// + /// A task that represents the asynchronous read operation. The value of the TResult + /// parameter contains the total number of bytes read into the buffer. The result + /// value can be less than the number of bytes requested if the number of bytes currently + /// available is less than the requested number, or it can be 0 (zero) if the end + /// of the stream has been reached. + /// + protected virtual Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (m_stream == null) + return Task.FromResult(0); + return m_stream.ReadAsync(buffer, 0, 1024, cancellationToken); + } + /// /// Creates the stream the NmeaDevice is working on top off. /// /// protected abstract Task OpenStreamAsync(); + /// /// Closes the device. /// @@ -110,7 +139,10 @@ namespace NmeaParser MultiPartMessageCache.Clear(); m_stream = null; lock (m_lockObject) + { + isOpening = false; IsOpen = false; + } } /// /// Closes the stream the NmeaDevice is working on top off. @@ -235,6 +267,27 @@ namespace NmeaParser /// true if this instance is open; otherwise, false. /// public bool IsOpen { get; private set; } + + /// + /// Gets a value indicating whether this device supports writing + /// + /// + public virtual bool CanWrite { get => false; } + + /// + /// Writes to the device stream. Useful for transmitting RTCM corrections to the device + /// Check the property before calling this method. + /// + /// The byte array that contains the data to write to the port. + /// The zero-based byte offset in the buffer parameter at which to begin copying + /// bytes to the port. + /// The number of bytes to write. + /// Task + /// + public virtual Task WriteAsync(byte[] buffer, int offset, int length) + { + throw new NotSupportedException(); + } } /// diff --git a/src/NmeaParser/NmeaParser.csproj b/src/NmeaParser/NmeaParser.csproj index ac4f364..4fa8287 100644 --- a/src/NmeaParser/NmeaParser.csproj +++ b/src/NmeaParser/NmeaParser.csproj @@ -11,7 +11,7 @@ An NMEA stream parser for serial port, bluetooth and file-based nmea simulation. nmea winrt wpf uwp xamarin gps serialport bluetooth SharpGIS.NmeaParser - 1.10.1 + 1.11 http://opensource.org/licenses/ms-pl.html https://github.com/dotMorten/NmeaParser https://github.com/dotMorten/NmeaParser diff --git a/src/NmeaParser/SerialPortDevice.Desktop.cs b/src/NmeaParser/SerialPortDevice.Desktop.cs index ed080d1..1fc845f 100644 --- a/src/NmeaParser/SerialPortDevice.Desktop.cs +++ b/src/NmeaParser/SerialPortDevice.Desktop.cs @@ -81,10 +81,24 @@ namespace NmeaParser /// The zero-based byte offset in the buffer parameter at which to begin copying /// bytes to the port. /// The number of bytes to write. + [Obsolete("Use WriteAsync")] public void Write(byte[] buffer, int offset, int count) { - m_port.Write(buffer, offset, count); - } - } + m_port.Write(buffer, offset, count); + } + + /// + public override bool CanWrite => true; + + /// + public override Task WriteAsync(byte[] buffer, int offset, int length) + { + if (!m_port.IsOpen) + throw new InvalidOperationException("Device not open"); + + m_port.Write(buffer, offset, length); + return Task.FromResult(null); + } + } } #endif \ No newline at end of file diff --git a/src/NmeaParser/SerialPortDevice.UWP.cs b/src/NmeaParser/SerialPortDevice.UWP.cs index 656d9d5..d97c005 100644 --- a/src/NmeaParser/SerialPortDevice.UWP.cs +++ b/src/NmeaParser/SerialPortDevice.UWP.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) 2014 Morten Nielsen -// -// Licensed under the Microsoft Public License (Ms-PL) (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://opensource.org/licenses/Ms-PL.html -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +// +// Copyright (c) 2014 Morten Nielsen +// +// Licensed under the Microsoft Public License (Ms-PL) (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://opensource.org/licenses/Ms-PL.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// #if WINDOWS_UWP using System; using System.Collections.Generic; @@ -21,7 +21,8 @@ using System.Text; using System.IO; using System.Threading.Tasks; using Windows.Devices.SerialCommunication; - +using System.Runtime.InteropServices.WindowsRuntime; + namespace NmeaParser { /// @@ -80,10 +81,23 @@ namespace NmeaParser /// The zero-based byte offset in the buffer parameter at which to begin copying /// bytes to the port. /// The number of bytes to write. + [Obsolete("Use WriteAsync")] public void Write(byte[] buffer, int offset, int count) { m_port.OutputStream.AsStreamForWrite().Write(buffer, offset, count); - } - } + } + + /// + public override bool CanWrite => true; + + /// + public override Task WriteAsync(byte[] buffer, int offset, int length) + { + if (m_port == null) + throw new InvalidOperationException("Device not open"); + + return m_port.OutputStream.WriteAsync(buffer.AsBuffer(offset, length)).AsTask(); + } + } } #endif \ No newline at end of file diff --git a/src/NmeaParser/StreamDevice.cs b/src/NmeaParser/StreamDevice.cs index ce2d619..0823b83 100644 --- a/src/NmeaParser/StreamDevice.cs +++ b/src/NmeaParser/StreamDevice.cs @@ -66,6 +66,18 @@ namespace NmeaParser if (m_stream != null) m_stream.Dispose(); m_stream = null; - } + } + + + /// + public override bool CanWrite => m_stream?.CanWrite == true; + + /// + public override Task WriteAsync(byte[] buffer, int offset, int length) + { + if (m_stream == null) + throw new InvalidOperationException("Device not open"); + return m_stream.WriteAsync(buffer, offset, length); + } } }