diff --git a/src/NmeaParser.Shared/BufferedStreamDevice.cs b/src/NmeaParser.Shared/BufferedStreamDevice.cs new file mode 100644 index 0000000..aa71bdf --- /dev/null +++ b/src/NmeaParser.Shared/BufferedStreamDevice.cs @@ -0,0 +1,144 @@ +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NmeaParser +{ + public abstract class BufferedStreamDevice : NmeaDevice + { + BufferedStream m_stream; + int m_readSpeed; + /// + /// + /// + /// + /// The time to wait between each line being read in milliseconds + protected BufferedStreamDevice( int readSpeed = 200) + { + m_readSpeed = readSpeed; + } + protected abstract Task GetStreamAsync(); + + protected sealed async override Task OpenStreamAsync() + { + var stream = await GetStreamAsync(); + StreamReader sr = new StreamReader(stream); + m_stream = new BufferedStream(sr, m_readSpeed); + return m_stream; + } + + protected override Task CloseStreamAsync(System.IO.Stream stream) + { + m_stream.Dispose(); + return Task.FromResult(true); + } + + // stream that slowly populates a buffer from a StreamReader to simulate nmea messages coming in line by line + // at a steady stream + private class BufferedStream : Stream + { + StreamReader m_sr; + byte[] buffer = new byte[0]; + System.Threading.Timer timer; + object lockObj = new object(); + public BufferedStream(StreamReader stream, int readSpeed) + { + m_sr = stream; + timer = new System.Threading.Timer(OnRead, null, 0, readSpeed); //add a new line to buffer every 100 ms + } + private void OnRead(object state) + { + if (m_sr.EndOfStream) + m_sr.BaseStream.Seek(0, SeekOrigin.Begin); //start over + + //populate the buffer with a line + string line = m_sr.ReadLine() + '\n'; + var bytes = Encoding.UTF8.GetBytes(line); + lock (lockObj) + { + byte[] newBuffer = new byte[buffer.Length + bytes.Length]; + buffer.CopyTo(newBuffer, 0); + bytes.CopyTo(newBuffer, buffer.Length); + buffer = newBuffer; + } + } + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override void Flush() { } + + public override long Length { get { return m_sr.BaseStream.Length; } } + + public override long Position + { + get { return m_sr.BaseStream.Position; } + set + { + throw new NotSupportedException(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + lock (lockObj) + { + if (this.buffer.Length <= count) + { + int length = this.buffer.Length; + this.buffer.CopyTo(buffer, 0); + this.buffer = new byte[0]; + return length; + } + else + { + Array.Copy(this.buffer, buffer, count); + byte[] newBuffer = new byte[this.buffer.Length - count]; + Array.Copy(this.buffer, count, newBuffer, 0, newBuffer.Length); + this.buffer = newBuffer; + return count; + } + } + } + + 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 void Dispose(bool disposing) + { + base.Dispose(disposing); + m_sr.Dispose(); + timer.Dispose(); + } + } + } +} diff --git a/src/NmeaParser.Shared/NmeaFileDevice.cs b/src/NmeaParser.Shared/NmeaFileDevice.cs index 3448462..e57809a 100644 --- a/src/NmeaParser.Shared/NmeaFileDevice.cs +++ b/src/NmeaParser.Shared/NmeaFileDevice.cs @@ -23,14 +23,13 @@ using System.Threading.Tasks; namespace NmeaParser { - public class NmeaFileDevice : NmeaDevice + public class NmeaFileDevice : BufferedStreamDevice { #if NETFX_CORE Windows.Storage.IStorageFile m_filename; #else string m_filename; #endif - BufferedStream m_stream; int m_readSpeed; /// /// @@ -38,124 +37,22 @@ namespace NmeaParser /// /// The time to wait between each line being read in milliseconds #if NETFX_CORE - public NmeaFileDevice(Windows.Storage.IStorageFile filename, int readSpeed = 200) + public NmeaFileDevice(Windows.Storage.IStorageFile filename, int readSpeed = 200) : base(readSpeed) #else - public NmeaFileDevice(string filename, int readSpeed = 200) + public NmeaFileDevice(string filename, int readSpeed = 200) : base(readSpeed) #endif { m_filename = filename; m_readSpeed = readSpeed; } - + protected override Task GetStreamAsync() + { #if NETFX_CORE - protected async override Task OpenStreamAsync() - { - var stream = await m_filename.OpenStreamForReadAsync(); - StreamReader sr = new StreamReader(stream); - m_stream = new BufferedStream(sr, m_readSpeed); - return m_stream; + return m_filename.OpenStreamForReadAsync(); #else - protected override Task OpenStreamAsync() - { - StreamReader sr = System.IO.File.OpenText(m_filename); - m_stream = new BufferedStream(sr, m_readSpeed); - return Task.FromResult(m_stream); + var sr = System.IO.File.OpenRead(m_filename); + return Task.FromResult(sr); #endif } - - protected override Task CloseStreamAsync(System.IO.Stream stream) - { - m_stream.Dispose(); - return Task.FromResult(true); - } - - // stream that slowly populates a buffer from a StreamReader to simulate nmea messages coming in line by line - // at a steady stream - private class BufferedStream : Stream - { - StreamReader m_sr; - byte[] buffer = new byte[0]; - System.Threading.Timer timer; - object lockObj = new object(); - public BufferedStream(StreamReader stream, int readSpeed) - { - m_sr = stream; - timer = new System.Threading.Timer(OnRead, null, 0, readSpeed); //add a new line to buffer every 100 ms - } - private void OnRead(object state) - { - if (m_sr.EndOfStream) - m_sr.BaseStream.Seek(0, SeekOrigin.Begin); //start over - - //populate the buffer with a line - string line = m_sr.ReadLine() + '\n'; - var bytes = Encoding.UTF8.GetBytes(line); - lock (lockObj) - { - byte[] newBuffer = new byte[buffer.Length + bytes.Length]; - buffer.CopyTo(newBuffer, 0); - bytes.CopyTo(newBuffer, buffer.Length); - buffer = newBuffer; - } - } - public override bool CanRead { get { return true; } } - public override bool CanSeek { get { return false; } } - public override bool CanWrite { get { return false; } } - public override void Flush() { } - - public override long Length { get { return m_sr.BaseStream.Length; } } - - public override long Position - { - get { return m_sr.BaseStream.Position; } - set - { - throw new NotSupportedException(); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - lock (lockObj) - { - if(this.buffer.Length <= count) - { - int length = this.buffer.Length; - this.buffer.CopyTo(buffer, 0); - this.buffer = new byte[0]; - return length; - } - else - { - Array.Copy(this.buffer, buffer, count); - byte[] newBuffer = new byte[this.buffer.Length - count]; - Array.Copy(this.buffer, count, newBuffer, 0, newBuffer.Length); - this.buffer = newBuffer; - return count; - } - } - } - - 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 void Dispose(bool disposing) - { - base.Dispose(disposing); - m_sr.Dispose(); - timer.Dispose(); - } - } } } diff --git a/src/NmeaParser.Shared/NmeaParser.Shared.projitems b/src/NmeaParser.Shared/NmeaParser.Shared.projitems index a38561c..7ebfe8c 100644 --- a/src/NmeaParser.Shared/NmeaParser.Shared.projitems +++ b/src/NmeaParser.Shared/NmeaParser.Shared.projitems @@ -9,6 +9,7 @@ NmeaParser + diff --git a/src/NmeaParser.Tests/BufferedStringDevice.cs b/src/NmeaParser.Tests/BufferedStringDevice.cs new file mode 100644 index 0000000..e8e61f5 --- /dev/null +++ b/src/NmeaParser.Tests/BufferedStringDevice.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NmeaParser.Tests +{ + internal class BufferedStringDevice : NmeaParser.BufferedStreamDevice + { + string m_data; + public BufferedStringDevice(string data) + { + m_data = data; + } + protected override Task GetStreamAsync() + { + return Task.FromResult(new System.IO.MemoryStream(UTF8Encoding.UTF8.GetBytes(m_data))); + } + } +} diff --git a/src/NmeaParser.Tests/DeviceTests.cs b/src/NmeaParser.Tests/DeviceTests.cs new file mode 100644 index 0000000..c0bcb93 --- /dev/null +++ b/src/NmeaParser.Tests/DeviceTests.cs @@ -0,0 +1,80 @@ +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NmeaParser.Tests +{ + [TestClass] + public class DeviceTests + { + [TestMethod] + [TestCategory("Device")] + public async Task TestGpgsvGroupMessage() + { + var message = "$GPGSV,3,1,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4C\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + NmeaDevice dev = new BufferedStringDevice(message); + TaskCompletionSource tcs = new TaskCompletionSource(); + int count = 0; + dev.MessageReceived += (s, e) => + { + count++; + try + { + Assert.IsTrue(e.IsMultiPart, "IsMultiPart"); + Assert.IsInstanceOfType(e.Message, typeof(NmeaParser.Nmea.Gps.Gpgsv)); + var msg = e.Message as NmeaParser.Nmea.Gps.Gpgsv; + if (msg.TotalMessages == msg.MessageNumber) + { + Assert.IsNotNull(e.MessageParts); + Assert.AreEqual(e.MessageParts.Length, 3, "MessageParts.Length"); + tcs.SetResult(true); + } + else + Assert.IsNull(e.MessageParts); + if (count > 3) + Assert.Fail(); + } + catch(System.Exception ex) + { + tcs.SetException(ex); + } + }; + await dev.OpenAsync(); + await tcs.Task; + var _ = dev.CloseAsync(); + } + + [TestMethod] + [TestCategory("Device")] + public async Task TestInvalidGpgsvGroupMessage() + { + var message = "$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4D\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + NmeaDevice dev = new BufferedStringDevice(message); + TaskCompletionSource tcs = new TaskCompletionSource(); + int count = 0; + dev.MessageReceived += (s, e) => + { + count++; + try + { + Assert.IsTrue(e.IsMultiPart, "IsMultiPart"); + Assert.IsInstanceOfType(e.Message, typeof(NmeaParser.Nmea.Gps.Gpgsv)); + var msg = e.Message as NmeaParser.Nmea.Gps.Gpgsv; + Assert.IsNull(e.MessageParts); + if (count > 6) + tcs.SetResult(true); + } + catch (System.Exception ex) + { + tcs.SetException(ex); + } + }; + await dev.OpenAsync(); + await tcs.Task; + var _ = dev.CloseAsync(); + } + } +} diff --git a/src/NmeaParser.Tests/NmeaParser.Tests.csproj b/src/NmeaParser.Tests/NmeaParser.Tests.csproj index c58d807..6265791 100644 --- a/src/NmeaParser.Tests/NmeaParser.Tests.csproj +++ b/src/NmeaParser.Tests/NmeaParser.Tests.csproj @@ -46,6 +46,8 @@ + +