0

I'm currently working on a project where I use a serial port with a UWP app. Sometimes I have the problem that I expect a certain amount of data but when I don't get that amount of data my serial port keeps reading until I cancel it manually.

When I initialize the serialDevice I set the serialDevice.ReadTimeout parameter but that doesn't do anything. I also tried to bind a cancellationToken to the ReadTimeout parameter but that didn't work either. If I remember correctly it cancelled my method before it even finished...

Is there some way to implement a working timeout?

I use this as my base:

public class ReadWriteAdapter
{
    public SemaphoreSlim Semaphore { get; }

    private static readonly object LockObject = new object();
    private static ReadWriteAdapter _instance;

    public static ReadWriteAdapter Current
    {
        get
        {
            if (_instance == null)
            {
                lock (LockObject)
                {
                    if (_instance == null)
                    {
                        _instance = new ReadWriteAdapter();
                    }
                }
            }
            return _instance;
        }
    }

    private ReadWriteAdapter()
    {
        Semaphore = new SemaphoreSlim(1, 1);
    }

    private SerialDevice _serialPort;

    public CancellationTokenSource ReadCancellationTokenSource;
    public CancellationTokenSource WriteCancellationTokenSource;

    private object _readCancelLock = new object();
    private object _writeCancelLock = new object();

    private DataWriter _dataWriter;
    private DataReader _dataReader;

    public bool IsDeviceInitialized()
    {
        return _serialPort != null;
    }


    public async Task<string> Init()
    {
        try
        {
            if (_serialPort != null) return "port already configured!";

            var aqs = SerialDevice.GetDeviceSelector("COM3");

            var devices = await DeviceInformation.FindAllAsync(aqs, null);

            if (!devices.Any()) return "no devices found!";

            await OpenPort(devices[0].Id);                

            WriteCancellationTokenSource = new CancellationTokenSource();
            ReadCancellationTokenSource = new CancellationTokenSource();

            return "found port!";

        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }

    private async Task OpenPort(string deviceId)
    {
        try
        {
            _serialPort = await SerialDevice.FromIdAsync(deviceId);

            if (_serialPort != null)
            {
                _serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
                _serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
                _serialPort.BaudRate = 19200;
                _serialPort.Parity = SerialParity.None;
                _serialPort.StopBits = SerialStopBitCount.One;
                _serialPort.DataBits = 8;
                _serialPort.Handshake = SerialHandshake.None;

                _dataWriter = new DataWriter(_serialPort.OutputStream);
                _dataReader = new DataReader(_serialPort.InputStream);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }

    private async Task<byte[]> ReadAsync(CancellationToken cancellationToken, uint readBufferLength)
    {
        Task<uint> loadAsyncTask;
        var returnArray = new byte[readBufferLength];

        lock (_readCancelLock)
        {
            cancellationToken.ThrowIfCancellationRequested();

            _dataReader.InputStreamOptions = InputStreamOptions.Partial;

            loadAsyncTask = _dataReader.LoadAsync(readBufferLength).AsTask(cancellationToken);
        }

        var bytesRead = await loadAsyncTask;

        if (bytesRead > 0)
        {
            _dataReader.ReadBytes(returnArray);
        }
        return returnArray;
    }

    public async Task<uint> WriteCommand(string PairedCommand)
    {
        var commandArray = StringToByteArray(PairedCommand);

        uint taskResult = 0;

        try
        {
            if (_serialPort != null)
            {
                if (_dataWriter == null)
                {
                    _dataWriter = new DataWriter(_serialPort.OutputStream);

                    taskResult = await WriteAsync(WriteCancellationTokenSource.Token, commandArray);
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            _dataWriter.DetachStream();
            _dataWriter.Dispose();
            _dataWriter = null;
        }
        return taskResult;
    }

    public async Task<uint> WriteAsync(CancellationToken cancellationToken, byte[] data)
    {
        Task<uint> storeAsyncTask;

        try
        {
            lock (_writeCancelLock)
            {
                cancellationToken.ThrowIfCancellationRequested();

                _dataWriter.WriteBytes(data);

                storeAsyncTask = _dataWriter.StoreAsync().AsTask(cancellationToken);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return await storeAsyncTask;
    }        

    public async Task<byte[]> Listen(uint bufferLength)
    {
        var listen = new byte[bufferLength];

        try
        {
            if (_serialPort != null)
            {
                if (_dataReader == null)
                {
                    _dataReader = new DataReader(_serialPort.InputStream);
                }

                listen = await ReadAsync(ReadCancellationTokenSource.Token, bufferLength);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (_dataReader != null)
            {
                _dataReader.DetachStream();
                _dataReader.Dispose();
                _dataReader = null;
            }
        }
        return listen;
    }

    public byte[] StringToByteArray(string str)
    {
        var enc = new ASCIIEncoding();
        return enc.GetBytes(str);
    }

    public void CancelReadTask()
    {
        lock (_readCancelLock)
        {
            if (ReadCancellationTokenSource == null) return;
            if (ReadCancellationTokenSource.IsCancellationRequested) return;

            ReadCancellationTokenSource.Cancel();

            ReadCancellationTokenSource = new CancellationTokenSource();
        }
    }

    private void CancelWriteTask()
    {
        lock (_writeCancelLock)
        {
            if (WriteCancellationTokenSource == null) return;
            if (WriteCancellationTokenSource.IsCancellationRequested) return;

            WriteCancellationTokenSource.Cancel();

            WriteCancellationTokenSource = new CancellationTokenSource();
        }
    }
}

And I call it with a code similar to this (short version):

public class ReadData
{
    private ReadBlock readBlock = new ReadBlock();

    public async Task<byte[]> ReadParaBlock()
    {
        await ReadWriteAdapter.Current.Semaphore.WaitAsync();

        await ReadWriteAdapter.Current.WriteCommand("getData");

        byte[] recievedArray = await ReadWriteAdapter.Current.Listen(100);

        ReadWriteAdapter.Current.Semaphore.Release();

        return recievedArray;            
    }                  
}
Daniel
  • 242
  • 2
  • 12
  • Post your code. Which method did you use to read data? Did you specify a non-default buffer size? ReadTimeout for example doesn't affect the asynchronous methods of [BaseStream](https://msdn.microsoft.com/en-us/library/system.io.ports.serialport.basestream(v=vs.110).aspx). – Panagiotis Kanavos Feb 15 '18 at 13:32
  • Where do you read from the port in all this? It seems like the relevant class is DataReader. – Panagiotis Kanavos Feb 15 '18 at 14:04
  • I start the listen method with a size of 100 bytes. And in the "Listen"-method I await the "ReadAsync"-task. This task is used to read from the serial port. I can probably go without the "Listen"-Task in between but it's there for the time being. The DataReader is used to read the InputStream so yes it is the relevant class. – Daniel Feb 15 '18 at 14:15

1 Answers1

0

The simplest way to solve this issue is using the ReadTimeOut itself, but performing the reading independently of what was received.

Read ReadTimeOut Information in Microsoft WebSite

Definition: If ReadTimeout is set to TimeSpan.FromMilliseconds(ulong.MaxValue) (see TimeSpan), then a read request completes immediately with the bytes that have already been received, even if no bytes have been received.

In other words, this way it will immediately read whatever is in the serial buffer.

So you can implement it this way:

SerialDevice SerialDeviceComm;
Task.Run(async () => { SerialDeviceComm = await SerialDevice.FromIdAsync(_serialConnectionInfo[_indexPort].PortID); }).Wait();
SerialDeviceComm.BaudRate = _baudRate;
SerialDeviceComm.Parity = _parity;
SerialDeviceComm.StopBits = _stopBitCount;
SerialDeviceComm.DataBits = _dataBit;
SerialDeviceComm.Handshake = _serialHandshake;
// This will make that regardless of receiving bytes or not it will read and continue.
SerialDeviceComm.ReadTimeout = TimeSpan.FromMilliseconds(uint.MaxValue); // Secret is here 
dataReader = new DataReader(SerialDeviceComm.InputStream)
{
    InputStreamOptions = InputStreamOptions.Partial
};
string _tempData = "";
Stopwatch sw = new Stopwatch();
sw.Reset();
sw.Start();
while (sw.ElapsedMilliseconds < timeOut)
{
    uint receivedStringSize = 0;
    Task.Delay(50).Wait(); // Time to take some bytes
    dataReader.InputStreamOptions = InputStreamOptions.Partial;
    Task.Run(async () => { receivedStringSize = await dataReader.LoadAsync(200); }).Wait();
    _tempData += dataReader.ReadString(receivedStringSize);
}

You can see a example code in my git: GitHub with example