0

I am having a play with a Raspberry Pi 3 running Windows IoT. I have wired up a DS18B20 sensor and I am able to communicate to it fine via a UWP app.

I now wanted to make this app into a BackgroundTask app. I am using this code for the OneWire coms

class WireSearchResult
{
    public byte[] id = new byte[8];
    public int lastForkPoint = 0;
}
public class OneWire
{
    private SerialDevice serialPort = null;
    DataWriter dataWriteObject = null;
    DataReader dataReaderObject = null;

    public async Task<string> GetFirstSerialPort()
    {
        try
        {
            string aqs = SerialDevice.GetDeviceSelector("UART0");
            var dis = await DeviceInformation.FindAllAsync(aqs);
            if (dis.Count > 0)
            {
                var deviceInfo = dis.First();
                return deviceInfo.Id;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Unable to get serial device: " + ex.Message);
        }

        return null;
    }

    public void shutdown()
    {
        if (serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }

    async Task<bool> onewireReset(string deviceId)
    {
        try
        {
            if (serialPort != null)
                serialPort.Dispose();

            serialPort = await SerialDevice.FromIdAsync(deviceId);

            // Configure serial settings
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 9600;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;

            dataWriteObject = new DataWriter(serialPort.OutputStream);
            dataWriteObject.WriteByte(0xF0);
            await dataWriteObject.StoreAsync();

            dataReaderObject = new DataReader(serialPort.InputStream);
            await dataReaderObject.LoadAsync(1);
            byte resp = dataReaderObject.ReadByte();
            if (resp == 0xFF)
            {
                System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
                return false;
            }
            else if (resp == 0xF0)
            {
                System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
                return false;
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("Response: " + resp);
                serialPort.Dispose();
                serialPort = await SerialDevice.FromIdAsync(deviceId);

                // Configure serial settings
                serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
                serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
                serialPort.BaudRate = 115200;
                serialPort.Parity = SerialParity.None;
                serialPort.StopBits = SerialStopBitCount.One;
                serialPort.DataBits = 8;
                serialPort.Handshake = SerialHandshake.None;
                dataWriteObject = new DataWriter(serialPort.OutputStream);
                dataReaderObject = new DataReader(serialPort.InputStream);
                return true;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return false;
        }
    }

    public async Task onewireWriteByte(byte b)
    {
        for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
        {
            // Run through the bits in the byte, extracting the
            // LSB (bit 0) and sending it to the bus
            await onewireBit((byte)(b & 0x01));
        }
    }

    async Task<byte> onewireBit(byte b)
    {
        var bit = b > 0 ? 0xFF : 0x00;
        dataWriteObject.WriteByte((byte)bit);
        await dataWriteObject.StoreAsync();
        await dataReaderObject.LoadAsync(1);
        var data = dataReaderObject.ReadByte();
        return (byte)(data & 0xFF);
    }

    async Task<byte> onewireReadByte()
    {
        byte b = 0;
        for (byte i = 0; i < 8; i++)
        {
            // Build up byte bit by bit, LSB first
            b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
        }
       // System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
        return b;
    }

    public async Task<double> getTemperature(string deviceId)
    {
        double tempCelsius = -200;

        if (await onewireReset(deviceId))
        {
            await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
            await onewireWriteByte(0x44); //DS18B20 convert T command 
                                          // (initiate single temperature conversion)
                                          // thermal data is stored in 2-byte temperature 
                                          // register in scratchpad memory

            // Wait for at least 750ms for data to be collated
            await Task.Delay(750);

            // Get the data
            await onewireReset(deviceId);
            await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
            await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
                                          // DS18B20 will transmit 9 bytes to master (us)
                                          // starting with the LSB

            byte tempLSB = await onewireReadByte(); //read lsb
            byte tempMSB = await onewireReadByte(); //read msb

            // Reset bus to stop sensor sending unwanted data
            await onewireReset(deviceId);

            // Log the Celsius temperature
            tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
            var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it

            System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
        }
        return tempCelsius;
    }
}

And finally the StartupTask

public sealed class StartupTask : IBackgroundTask
{
    private BackgroundTaskDeferral deferral;

    private OneWire onewire;
    private string deviceId = string.Empty;
    private bool inprog = false;
    private Timer timer;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        deferral = taskInstance.GetDeferral(); 

        onewire = new OneWire();
        deviceId = await onewire.GetFirstSerialPort();

        if(deviceId != null)
          await onewire.getTemperature(deviceId));

        BackgroundTaskDeferral.Complete();
    }

}

The problem I have is that when I run this code it hangs on one of the lines that disposes of the SerialDevice in OneWire class.

I have read in a few places that its related to the BackgroundTask and using Async/Await

Gaz83
  • 2,293
  • 4
  • 32
  • 57
  • Did you actually go read the documentation at the link shown in your code example? I.e. http://aka.ms/backgroundtaskdeferral. It seems to me to provide a very clear, easy-to-follow explanation for exactly how to deal with the problem you are asking about. – Peter Duniho Jul 03 '17 at 22:08
  • Make sure set `serialPort = null;` after each `serialPort.Dispose();` and test again. – Rita Han Jul 04 '17 at 03:13
  • @PeterDuniho Please Elaborate. I have read the documentation and I don't see the clear explanation to my issue. I obtained a Defferral, I executed code and the only piece missing is setting the Defferral.Complete() when the code has finished. My problem is that the code does not finish, it hangs so I would never get to set Defferral.Complete(). – Gaz83 Jul 04 '17 at 16:10
  • @RitaHan-MSFT I tried setting the serialport to null but it did not work. – Gaz83 Jul 04 '17 at 16:11
  • _"the only piece missing is setting the Defferral.Complete()"_ -- yes, that part is missing. Why not start with code that's as correct as you can make it, instead of code that's known to be _incorrect_. You claim that the code hangs on a call to `Dispose()`, but a) your non-[mcve] shows no call to `Dispose()`, and b) it's entirely possible that the dispose doesn't complete because you never call the `Complete()` method. – Peter Duniho Jul 04 '17 at 18:51
  • @PeterDuniho the dispose code I was referring to is in the link I provided, right at the top! That plus what I wrote is the complete code. For arguments sake I have modified the code and added Defferral.Complete(). Guess What? still doesn't work – Gaz83 Jul 04 '17 at 19:01
  • _"is in the link I provided"_ -- links are not useful ways to present your question. Your question needs to include a good [mcve]. See also [ask], including the articles linked at the bottom of that page. _"still doesn't work"_ -- so you say. But there's nothing in the code you posted in the question to suggest you fixed your code so that it looks like the guidance in the Microsoft documentation. – Peter Duniho Jul 04 '17 at 19:11

4 Answers4

0

This issue is not related to the BackgroundTask. Because your code produces the same issue in non BackgroundTask(app).

Its reason looks like SerialPort is somewhat prone to deadlock.

I found there are too many callings of onewireReset method that close and reopen the SerialPort. I don´t know why should this be done but this cause the problem.

So there is a workaround: Rewrite the related part logic and make sure open the SerialPort at the start of your program and dispose it when you don't need it any more.

Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • The code DOES work in a non-BackgroundTask app. I mention at the top of the post that this works in a UWP app. I could obtain the temperature every second for 10 minutes and saw no issues. I have tried changing the logic so there is only a create the serial at the start and dispose at the end, but the sensor no longer responds correctly. When it performs this tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0; I get 4096.something (can't remember off the top of my head the exact number). – Gaz83 Jul 05 '17 at 10:26
0

I'm using the same onewire code in a background task to talk to the DS18B20 sensor and was experiencing exactly the same behaviour as you.

What I found is that if I put a delay of 100 milliseconds before calling the serial port dispose method it works

await Task.Delay(100)

I've tried less than 100 milliseconds but it just kept hanging.

This stackoverflow questions first answer explains the issue with serial ports in the .Net Framework Why Thread.Sleep() before SerialPort.Open and Close?

DazFahy
  • 181
  • 9
  • Interesting, I will try this later. I ran a few experiments last night and re-wrote some of the code. I managed to remove all of the creating and disposing of the serial port. The best I could achieve was that I could create a serial connection, read the temperature, and then close the connection 4 times before it would hang on the 5th loop. I did try the idea of just creating the connection and then do the reading in a endless loop with a 5 second gap, but I got an exception at around loop 15. A bit of googling and switching to native debug and it now looks like visual studio is the issue. – Gaz83 Jul 06 '17 at 09:35
  • I had it running for over 1 hour in visual studio 2017 returning temperatures before I turned it off. – DazFahy Jul 06 '17 at 09:57
  • This is sounding like good news to me :-) Will report back when I have tried the 100 milliseconds delay. – Gaz83 Jul 06 '17 at 10:38
  • Yep this looks like it has solved the problem, thank you @DazFahy – Gaz83 Jul 06 '17 at 16:22
0

I have just been through the exact same problem myself yesterday (program suddenly hangs when disposing the SerialDevice object in the onewireReset(...) method) and I managed to resolve it.

Solution principle: Don't constantly dispose/reaquire the serial port. Instead, aquire the port once and reconfigure it on the fly as needed (=change the baud rate). This way the hanging SerialDevice.Dispose() call is avoided completely.

Note: In order to be able to change the baud rate, you must first detatch both the DataReader and DataWriter objects from the port stream, or you will get an exception. After the change, reattach new DataReader and DataWriter objects (remember to properly dispose of the old ones).

Modified OneWire class:

    public class OneWire
{
    private SerialDevice serialPort = null;
    DataWriter dataWriteObject = null;
    DataReader dataReaderObject = null;

    public void shutdown()
    {
        if (serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }

    private async Task ReconfigurePort(uint baudRate, string deviceId)
    {
        if (serialPort == null)
        {
            serialPort = await SerialDevice.FromIdAsync(deviceId);
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = baudRate;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;
            dataWriteObject = new DataWriter(serialPort.OutputStream);
        }
        else
        {
            dataWriteObject.DetachStream();
            dataWriteObject.DetachBuffer();
            dataWriteObject.Dispose();

            dataReaderObject.DetachStream();
            dataReaderObject.Dispose();

            serialPort.BaudRate = baudRate;

            dataWriteObject = new DataWriter(serialPort.OutputStream);
        }
    }

    async Task<bool> onewireReset(string deviceId)
    {
        try
        {
            await ReconfigurePort(9600, deviceId);

            dataWriteObject.WriteByte(0xF0);
            await dataWriteObject.StoreAsync();

            dataReaderObject = new DataReader(serialPort.InputStream);
            await dataReaderObject.LoadAsync(1);

            byte resp = dataReaderObject.ReadByte();
            if (resp == 0xFF)
            {
                //System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
                return false;
            }
            else if (resp == 0xF0)
            {
                //System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
                return false;
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("Response: " + resp);
                await ReconfigurePort(115200, deviceId);
                dataReaderObject = new DataReader(serialPort.InputStream);
                return true;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return false;
        }
    }

    public async Task onewireWriteByte(byte b)
    {
        for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
        {
            // Run through the bits in the byte, extracting the
            // LSB (bit 0) and sending it to the bus
            await onewireBit((byte)(b & 0x01));
        }
    }

    async Task<byte> onewireBit(byte b)
    {
        var bit = b > 0 ? 0xFF : 0x00;
        dataWriteObject.WriteByte((byte)bit);
        await dataWriteObject.StoreAsync();
        await dataReaderObject.LoadAsync(1);
        var data = dataReaderObject.ReadByte();
        return (byte)(data & 0xFF);
    }

    async Task<byte> onewireReadByte()
    {
        byte b = 0;
        for (byte i = 0; i < 8; i++)
        {
            // Build up byte bit by bit, LSB first
            b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
        }
        //System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
        return b;
    }

    public async Task<double> getTemperature(string deviceId)
    {
        double tempCelsius = -200;

        if (await onewireReset(deviceId))
        {
            await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
            await onewireWriteByte(0x44); //DS18B20 convert T command 
                                          // (initiate single temperature conversion)
                                          // thermal data is stored in 2-byte temperature 
                                          // register in scratchpad memory

            // Wait for at least 750ms for data to be collated
            //await Task.Delay(250);

            // Get the data
            await onewireReset(deviceId);
            await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
            await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
                                          // DS18B20 will transmit 9 bytes to master (us)
                                          // starting with the LSB

            byte tempLSB = await onewireReadByte(); //read lsb
            byte tempMSB = await onewireReadByte(); //read msb

            // Reset bus to stop sensor sending unwanted data
            await onewireReset(deviceId);

            // Log the Celsius temperature
            tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
            var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it

            //System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
        }
        return tempCelsius;
    }
}
Martin Christiansen
  • 1,037
  • 3
  • 9
  • 28
0

I ran into the same issue with the SerialDevice.Dispose() hanging. The await Task.Delay(100) did seem to resolve the problem. However, I am not a fan of blindly adding delays for reasons I dont understand. After some testing, it seems that the same can be accomplished by running dispose on its own task.

//Dispose of the serial port object
System.Diagnostics.Debug.WriteLine("Disposing");
mDataWriter.DetachStream();
mDataWriter.Dispose();
mDataWriter = null;
mDataReader.DetachStream();
mDataReader.Dispose();
mDataReader = null;
try
{
    CancellationTokenSource DisposePortCancellationTokenSource = new CancellationTokenSource(200);
    await Task.Run(() => mSerialDevice.Dispose(), DisposePortCancellationTokenSource.Token);
}
catch(Exception ex)
{
    System.Diagnostics.Debug.WriteLine("Error disposing of serial device. Device may need to be manually unplugged and re-plugged in");
}
mSerialDevice = null;
System.Diagnostics.Debug.WriteLine("Disposed");