From 5d4686c059354c25759b5c41515d35106a175f56 Mon Sep 17 00:00:00 2001 From: Morten Nielsen Date: Fri, 27 Nov 2020 21:25:17 -0800 Subject: [PATCH] Added iOS `EAAccessoryDevice` --- src/NmeaParser/EAAccessoryDevice.iOS.cs | 190 ++++++++++++++++++++++++ src/NmeaParser/NmeaDevice.cs | 6 +- 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 src/NmeaParser/EAAccessoryDevice.iOS.cs diff --git a/src/NmeaParser/EAAccessoryDevice.iOS.cs b/src/NmeaParser/EAAccessoryDevice.iOS.cs new file mode 100644 index 0000000..4ce8e9e --- /dev/null +++ b/src/NmeaParser/EAAccessoryDevice.iOS.cs @@ -0,0 +1,190 @@ +// ******************************************************************************* +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * 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 __IOS__ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ExternalAccessory; + +namespace NmeaParser +{ + /// + /// Creates an object to read from an iOS accessory like a Bluetooth device. + /// + /// + /// It's worth nothing that iOS is very limited to what devices it can connect to, and generally + /// needs to be MFI certified devices. + /// + /// To connect to a device in an iOS app, the device must be supported by iOS and enabled in Info.plist. + /// As an example you can declare a Bad Elf GPS receiver like this in the plist: + /// + /// + /// <key>UISupportedExternalAccessoryProtocols</key> + /// <array> + /// <string>com.bad-elf.gps</string> + /// </array> + /// + /// + /// If using a bad-elf GPS, make sure you send the configuration to enable sending NMEA data. + /// Example: + /// + /// var device = new EAAccessoryDevice(accessory, "com.bad-elf.gps"); + /// await device.OpenAsync(); + /// // BadElf start packet. + /// // See https://github.com/BadElf/gps-sdk/blob/master/README.md#extended-nmea-sentences-with-gsa-and-gsv-satellite-data-and-dop-values + /// byte[] startPacket = new byte[] { 0x24, 0xbe, 0x00, 0x11, 0x16, 0x01, 0x02, 0xf4, 0x31, 0x0a, 0x32, 0x04, 0x33, 0x02, 0x5a, 0x0d, 0x0a }; + /// await device.WriteAsync(startPacket, 0, startPacket.Length); + /// + /// + /// + public class EAAccessoryDevice : NmeaDevice + { + private readonly EAAccessory m_accessory; + private readonly string m_protocol; + + /// + /// Gets a list of supported known EAAccessories that support NMEA + /// + /// + /// Returns a list of devices. Only devices enabled in the Info.plist will be enabled. See below for an example: + /// + /// <key>UISupportedExternalAccessoryProtocols</key> + /// <array> + /// <string>com.bad-elf.gps</string> + /// </array> + /// + /// Supported protocols: + /// + /// Bad Elf: com.bad-elf.gps. + /// Generic bluetooth serial: 00001101-0000-1000-8000-00805F9B34FB + /// + /// If you know of other MFI certificed NMEA devices, please make a request to have it added here: https://github.com/dotMorten/NmeaParser/issues/new + /// or you can use to iterate and find the devices yourself. + /// + /// A list of supported devices + public static IEnumerable GetDevices() + { + foreach (var accessory in EAAccessoryManager.SharedAccessoryManager.ConnectedAccessories) + { + if (accessory.ProtocolStrings.Contains("com.bad-elf.gps")) + { + yield return new EAAccessoryDevice(accessory, "com.bad-elf.gps"); + } + else if (accessory.ProtocolStrings.Contains("com.dualav.xgps150")) + { + yield return new EAAccessoryDevice(accessory, "com.dualav.xgps150"); + } + else if (accessory.ProtocolStrings.Contains("00001101-0000-1000-8000-00805F9B34FB")) + { + yield return new EAAccessoryDevice(accessory, "00001101-0000-1000-8000-00805F9B34FB"); + } + // TODO: Add more known devices here. + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public EAAccessoryDevice(EAAccessory accessory, string protocol) + { + m_accessory = accessory; + m_protocol = protocol; + } + + /// + protected override Task CloseStreamAsync(Stream stream) + { + stream.Dispose(); + return Task.CompletedTask; + } + + /// + protected override Task OpenStreamAsync() + => Task.FromResult(new EAAccessoryStream(m_accessory, m_protocol)); + + private class EAAccessoryStream : Stream + { + private readonly EASession m_session; + + public EAAccessoryStream(EAAccessory accessory, string protocol) + { + m_session = new EASession(accessory, protocol); + m_session.InputStream?.Open(); + m_session.OutputStream?.Open(); + } + + ~EAAccessoryStream() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + m_session.InputStream?.Close(); + m_session.InputStream?.Dispose(); + m_session.OutputStream?.Close(); + m_session.OutputStream?.Dispose(); + m_session.Dispose(); + } + base.Dispose(disposing); + } + + public override bool CanRead => m_session.InputStream != null; + + public override int Read(byte[] buffer, int offset, int count) + { + if (m_session.InputStream == null) + throw new NotSupportedException(); + if (!m_session.InputStream.HasBytesAvailable()) + return 0; + return (int)m_session.InputStream.Read(buffer, offset, (nuint)count); + } + + public override bool CanWrite => m_session.OutputStream != null; + + public override void Write(byte[] buffer, int offset, int count) + { + if (m_session.OutputStream == null) + throw new NotSupportedException(); + m_session.OutputStream.Write(buffer, offset, (nuint)count); + } + + public override bool CanSeek => false; + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override long Length => -1; + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + } + } +} +#endif \ No newline at end of file diff --git a/src/NmeaParser/NmeaDevice.cs b/src/NmeaParser/NmeaDevice.cs index f45a348..611bb6b 100644 --- a/src/NmeaParser/NmeaDevice.cs +++ b/src/NmeaParser/NmeaDevice.cs @@ -267,7 +267,11 @@ namespace NmeaParser /// public virtual Task WriteAsync(byte[] buffer, int offset, int length) { - throw new NotSupportedException(); + if (m_stream is null) + throw new InvalidOperationException("Device not opened"); + if (!m_stream.CanWrite) + throw new NotSupportedException(); + return m_stream.WriteAsync(buffer, offset, length); } }