jefffhaynes/XBee

EndOfStreamException when using in UWP app

Closed this issue · 17 comments

When I try to use this lib in a UWP app, it throws an EndOfStreamException
at System.IO.BinaryReader.ReadByte() at BinarySerialization.Graph.ValueGraph.ValueValueNode.Deserialize(EndianAwareBinaryReader reader, SerializedType serializedType, Nullable1 length)`

The exception is thrown at the very beginning of the process, during FindAndOpen()... actually it happens the first time ReadByte() is called.

The same thing happens with an FTDI-based XBee board, and with a Leonardo-based serial passthrough device. The XBees are accessible from XCTU through both adapters. I tried with a simple S2, and an S2C Pro TH module, both set to 9600 baud, AO=1, and the result was the same: EndOfStreamException.

Do you have any idea what might be wrong?

It might be a side-effect of UWP but let me take a look tomorrow. I've been porting to .net core and should have something by the end of the week, which might help.
Thanks

On Mon, Mar 21, 2016 at 10:40 AM -0700, "guyeeba" notifications@github.com wrote:

When I try to use this lib in a UWP app, it throws an EndOfStreamException

at System.IO.BinaryReader.ReadByte()

at BinarySerialization.Graph.ValueGraph.ValueValueNode.Deserialize(EndianAwareBinaryReader reader, SerializedType serializedType, Nullable1 length)`

The exception is thrown at the very beginning of the process, during FindAndOpen()... actually it happens the first time ReadByte() is called.

The same thing happens with an FTDI-based XBee board, and with a Leonardo-based serial passthrough device. The XBees are accessible from XCTU through both adapters. I tried with a simple S2, and an S2C Pro TH module, both set to 9600 baud, AO=1, and the result was the same: EndOfStreamException.

Do you have any idea what might be wrong?


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub

Sorry, I'm still working on porting the underlying serializer to .net core. Hope to have something soon...

It's okay, thanks for your effors, it's really appreciated.

I managed to get closer to solution (once again, UWP app issue).

  1. the default value of SerialDevice.DataBits is 7. Explicitly setting it to 8 makes the serial port work perfectly:
        public static async Task<XBeeController> FindAndOpen()
        {
            var controller = new XBeeController();

            string aqs = SerialDevice.GetDeviceSelector();
            var devices = await DeviceInformation.FindAllAsync(aqs);

            foreach (var device in devices)
            {
                try
                {
                    var serialDevice = await SerialDevice.FromIdAsync(device.Id);
                    if (serialDevice == null)
                        return null;
                    else
                    {
                        serialDevice.DataBits = 8;

                        await controller.OpenAsync(serialDevice);
                        return controller;
                    }
                }
                catch (InvalidOperationException)
                {
                }
                catch (UnauthorizedAccessException)
                {
                }
                catch (ArgumentOutOfRangeException)
                {
                }
                catch (ArgumentException)
                {
                }
                catch (TimeoutException)
                {
                }
                catch (IOException)
                {
                }
            }

            return null;
        }
  1. The way SerialDevice works in UWP does not allow you to close the port, therefore the open-getHV-close-reopen logic you use in XBeeController.OpenAsync does not work (BinarySerializer is blocked in Stream.Read as the underlying port cannot be closed). Restructuring BinarySerializer to support CancellationTokens would be way too much work for me, so my workaround was to insert an additional Stream layer into the SerialDevice-FrameSerializer path (warning, ugly code, proof-of-concept only!):

SerialConnection.Open:

[...]
                        try
                        {
#if NETCORE
                            _serialReadStream = new ClosableInputStream(_serialPort.InputStream, cancellationToken);

                            Frame frame = _frameSerializer.Deserialize(_serialReadStream);
#else
                            Frame frame = _frameSerializer.Deserialize(_serialPort.BaseStream);
#endif
[...]

where ClosableInputStream is

    public class ClosableInputStream : Stream
    {
        public ClosableInputStream(IInputStream stream, CancellationToken cancellationToken)
        {
            Source = stream;
            _cancellationToken = cancellationToken;
            dataReaderObject = new DataReader(stream);
        }

        public IInputStream Source { get; }
        public CancellationToken _cancellationToken { get; }
        public DataReader dataReaderObject { get; }

        public override bool CanRead => true;
        public override bool CanSeek => false;
        public override bool CanWrite => false;
        public override long Length => 0;
        public override long Position
        {
            get
            { return 0; }
            set
            { }
        }

        public override void Flush()
        {
            throw new NotImplementedException();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            _cancellationToken.ThrowIfCancellationRequested();

            dataReaderObject.InputStreamOptions = InputStreamOptions.Partial;

            try
            {
                Task<UInt32> loadAsyncTask = dataReaderObject.LoadAsync((uint)count).AsTask(_cancellationToken);

                loadAsyncTask.Wait(_cancellationToken);
                UInt32 bytesRead = loadAsyncTask.Result;

                if (bytesRead > 0)
                {
                    IBuffer buff = dataReaderObject.ReadBuffer((uint)count);
                    buff.CopyTo(0, buffer, offset, count);
                }

                return (int)bytesRead;
            }
            catch
            {
                throw;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }
    }

So it seems to implement (production-ready) support for UWP needs some restructuring of underlying libraries to support cancellation. Or do you have a better idea how to workaround this issue?

That's funny, I was just cursing myself a few days ago for going with the
open-and-stay-open approach. Let me digest all this and get back to you.
Thanks.

On Thu, Apr 21, 2016 at 3:02 AM, guyeeba notifications@github.com wrote:

I managed to get closer to solution (once again, UWP app issue).

  1. the default value of SerialDevice.DataBits is 7. Explicitly setting it
    to 8 makes the serial port work perfectly:
    public static async Task<XBeeController> FindAndOpen()
    {
        var controller = new XBeeController();

        string aqs = SerialDevice.GetDeviceSelector();
        var devices = await DeviceInformation.FindAllAsync(aqs);

        foreach (var device in devices)
        {
            try
            {
                var serialDevice = await SerialDevice.FromIdAsync(device.Id);
                if (serialDevice == null)
                    return null;
                else
                {
                    serialDevice.DataBits = 8;

                    await controller.OpenAsync(serialDevice);
                    return controller;
                }
            }
            catch (InvalidOperationException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
            catch (ArgumentOutOfRangeException)
            {
            }
            catch (ArgumentException)
            {
            }
            catch (TimeoutException)
            {
            }
            catch (IOException)
            {
            }
        }

        return null;
    }
  1. The way SerialDevice works in UWP does not allow you to close the port,
    therefore the open-getHV-close-reopen logic you use in
    XBeeController.OpenAsync does not work (BinarySerializer is blocked in
    Stream.Read as the underlying port cannot be closed). Restructuring
    BinarySerializer to support CancellationTokens would be way too much work
    for me, so my workaround was to insert an additional Stream layer into the
    SerialDevice-FrameSerializer path (warning, ugly code, proof-of-concept
    only!):

SerialConnection.Open:

[...]
try
{
#if NETCORE
_serialReadStream = new ClosableInputStream(_serialPort.InputStream, cancellationToken);

                        Frame frame = _frameSerializer.Deserialize(_serialReadStream);

#else
Frame frame = _frameSerializer.Deserialize(_serialPort.BaseStream);
#endif
[...]

where ClosableInputStream is

public class ClosableInputStream : Stream
{
    public ClosableInputStream(IInputStream stream, CancellationToken cancellationToken)
    {
        Source = stream;
        _cancellationToken = cancellationToken;
        dataReaderObject = new DataReader(stream);
    }

    public IInputStream Source { get; }
    public CancellationToken _cancellationToken { get; }
    public DataReader dataReaderObject { get; }

    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;
    public override long Length => 0;
    public override long Position
    {
        get
        { return 0; }
        set
        { }
    }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        _cancellationToken.ThrowIfCancellationRequested();

        dataReaderObject.InputStreamOptions = InputStreamOptions.Partial;

        try
        {
            Task<UInt32> loadAsyncTask = dataReaderObject.LoadAsync((uint)count).AsTask(_cancellationToken);

            loadAsyncTask.Wait(_cancellationToken);
            UInt32 bytesRead = loadAsyncTask.Result;

            if (bytesRead > 0)
            {
                IBuffer buff = dataReaderObject.ReadBuffer((uint)count);
                buff.CopyTo(0, buffer, offset, count);
            }

            return (int)bytesRead;
        }
        catch
        {
            throw;
        }
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }
}

So it seems to implement (production-ready) support for UWP needs some
restructuring of underlying libraries to support cancellation. Or do you
have a better idea how to workaround this issue?


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#4 (comment)

If you want to build a ship, don't drum up people together to collect wood
and don't assign them tasks and work, but rather teach them to long for the
endless immensity of the sea.

Antoine de Saint-Exupery

If you get a chance take a look at my latest check-in and see if it solves anything. I need to load up your code I just haven't had time yet.

The latest check-in is not really UWP-aware, I'm going to mess with it in the evening.

I noticed that you added XBeeProS2C to HardwareVersion with value of 0x21. Well, actually I use XBee Pro S2C modules (through-hole mounting scheme), but my devices have hardware version 0x2D (or 0x2D41 to be exact). You might add them, too... :)

Yeah, I'm actually surprised you got it working on UWP at all. I didn't think BinarySerializer would work yet. I really need to devote more time to it. 
Thanks for the hardware info. I'll add it. 

On Thu, Apr 21, 2016 at 11:33 PM -0700, "guyeeba" notifications@github.com wrote:

The latest check-in is not really UWP-aware, I'm going to mess with it in the evening.

I noticed that you added XBeeProS2C to HardwareVersion with value of 0x21. Well, actually I use XBee Pro S2C modules (through-hole mounting scheme), but my devices have hardware version 0x2D (or 0x2D41 to be exact). You might add them, too... :)


You are receiving this because you commented.
Reply to this email directly or view it on GitHub

Any news regarding UWP support?

I'm making progress on the underlying serializer using VS 2017 RC. I should be able to have something before too long but there still seem to be some issues with .NET Standard in the RC.

Any updates on this?

I'm struggling a little with the correct way to go about UWP support as they just released .net core 2.0 preview with serial support. The idea of doing everything as pure .net core is tempting but I don't know if (or when) that will translate to IoT core support, for example. Need to do some testing.

Talked to an MS guy and he said don't use SerialPort on IoT so I'm targeting SerialDevice. I have it somewhat working but the SerialDevice behavior seems even more unreliable than SerialPort so far...

If you want, give this a try. It will require some refactoring but look at the "core" branch for an example.

https://www.nuget.org/packages/XBee.Universal/

The 5.0+ library includes UWP support. Please let me know if you have any issues with it.