Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SerialDevice.FromIdAsync() yields a null serial port

I have something strange happening with my code when trying to connect to a serial port. When using Serial Sample, everything works just fine and it connects on the first try. However, with my code the serial port returns a null.

I have found a workaround by nested the call to write the serial port in a do loop until the serial port is no longer null and it works. It takes, on average, 500 loops to connect and it has always connected. I know it's dangerous because there is the infinite loop if it's always null, so I tried switching the do loop to a for loop. With a for loop, it always returns back as null, even when I iterate it 50,000 times. Has anyone else encountered this and found a better solution? I'm connecting to an Arduino Uno.

The reason I'm not just using the Serial Sample code is because I want to use a drop down menu and use the port name instead of the device id name. My code is below. Thanks.

    //lists com ports
    private async void listPorts()
    {
        try
        {
            string aqs = SerialDevice.GetDeviceSelector();
            var dlist = await DeviceInformation.FindAllAsync(aqs);
            status.Text = "Select a device and connect";

            for (int i = 0; i < dlist.Count; i++)
            {
                var port = await SerialDevice.FromIdAsync(dlist[0].Id);
                ConnectDevices.Items.Add(port.PortName);
            }

            comConnect.IsEnabled = true;
            ConnectDevices.SelectedIndex = 0;
        }
        catch (Exception ex)
        {
            status.Text = ex.Message;
        }
    }

    //connects to the selected com port
    private async void comConnect_Click(object sender, RoutedEventArgs e)
    {
        _key = 0;
        status2.Text = "";
        string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        if (dlist.Count <= 0)
        {
            status.Text = "No devices found.";
            return;
        }
        try
        {
            do
            {
                serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
                status.Text = "Connecting to serial port...";
            } while (serialPort == null);

            // Configure serial settings
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 38400;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;
            status.Text = "Serial port configured successfully.";

I have added the following to package.appmanifest

<Capabilities>
 <DeviceCapability Name="serialcommunication">
  <Device Id="any">
   <Function Type="name:serialPort" />
  </Device>
 </DeviceCapability>
</Capabilities>
like image 608
John Muse Avatar asked May 29 '16 00:05

John Muse


2 Answers

In my project, the serial port work correctly after I add the following code to package.appmanifest.

    <Capabilities>
     <DeviceCapability Name="serialcommunication">
      <Device Id="any">
       <Function Type="name:serialPort" />
      </Device>
     </DeviceCapability>
    </Capabilities>

The serial port variable should be "serialPort".

like image 165
林剑平 Avatar answered Oct 18 '22 20:10

林剑平


The problem here (that you have correctly identified) is that your listPorts is holding open the connection to the SerialPort.

SerialDevice class manages the serial ports for you and ensures that only a single process can consume any given port at one time. Instead of throwing exceptions in this case, multiple calls to create a SerialDevice, through will return null until you have released the device.

SerialDevice implements IDisposable so you should wrap calls to it in using to correctly release the connection when your code goes out of scope.

//lists com ports
private async void listPorts()
{
    try
    {
        string aqs = SerialDevice.GetDeviceSelector();
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        status.Text = "Select a device and connect";

        for (int i = 0; i < dlist.Count; i++)
        {
            using(var port = await SerialDevice.FromIdAsync(dlist[0].Id))
            {
                ConnectDevices.Items.Add(port.PortName);
            }
        }

        comConnect.IsEnabled = true;
        ConnectDevices.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
}

With the above code change in place, you can remove the infinite loop in your comConnect_Click logic:

    // NOTE: possible infinite loop!
    //do
    //{
    status.Text = "Connecting to serial port...";
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    //
    //} while (serialPort == null);

The reason that it worked before, is that after a period of time after listPorts execution has gone out of scope, the GarbageCollector will destroy the port variable resources and release the physical Serial Port connection.

If your code no longer hogs the connection, then waiting for an external process that might be hogging the connection really could be infinite, or certainly in-determinate.

  • It would be a better pattern in this case to abort the connection process:
        status.Text = "Connecting to serial port...";
        serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
        if(serialPort == null)
        {
            status.Text = "Port busy, please try again later";
            return;
        }
    

NOTE: Device Selection

You should avoid hardcoding device array selections like dlist[0].Id unless you have already used a very specific device selection query such that there will only be one (or zero) items in the list.

OP's specific code does ensure unique selection, if you are following along at home, do not simply use the first item in the list, especially if you have enumerated the SerialDevices with this sort of code.

string aqs = Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector();
var dlist = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(aqs, null);

This query will routinely return the local PC at index zero, then each serial attached device after that.

To avoid this pitfall, save a reference to the specific item in the list you want to operate on, rather than hardcoding the index.

var selectedDevice = dlist[0];

This is a more obvious point to diagnose null value or array index issues in your code.

Regarding infinite loops

Only in an asynchronous environment or application where many processes (or many instances of the same process) may need access to the SerialPort, or you want to allow external processes on the same OS to access the SerialPort when they need it, should you worry about the loop structure around creating the SerialPort. However you should add some checks to allow aborting the process

If you did want to allow for retry, you should limit either the number of retries, or the period of time to allow the retry process to wait. You can even do both, wrapping all that together you might have something like this, along with some diagnostic info to inspect:

var timer = Stopwatch.StartNew();
int attempts = 0;
do
{
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    attempts++;
    if(serialPort != null)
        break;
    await Task.Delay(1000); // pause between retries
} while (serialPort == null && timer.ElapsedMilliseconds < 10000 && attempts < 5); 
// max wait 10 seconds, or 5 total retries for serial device coms to come online.
timer.Stop();
Debug.WriteLine($"Connection to SerialPort {(serialPort == null ? "FAILED" : "Successs")} in: {timer.Elapsed} ({attempts} attempts)");

if(serialPort == null)
{
    status.Text = "Port busy, please try again later";
    return;
}

The structure of your application is such that you want to maintain an exclusive connection to the SerialPort and block access to other processes that might also want to use the same device, in this instance you do not need to dispose the SerialPort when you connect with the Connect button, however you should make sure you cleanup the serialPort instance variable in your form's dispose method.

If instead you were to structure all of your calls to always create a new SerialPort connection when you need it, then you will need to make sure that you dispose the object when you are done with it. In this pattern, if you are implementing a while loop to connect to the device, instead of a using statement you can use a try-finally pattern:

//connects to the selected com port
private async void comConnect_Click(object sender, RoutedEventArgs e)
{
    _key = 0;
    status2.Text = "";
    string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
    var dlist = await DeviceInformation.FindAllAsync(aqs);
    if (dlist.Count <= 0)
    {
        status.Text = "No devices found.";
        return;
    }
    try
    {
        // NOTE: possible infinite loop!
        do
        {
            serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
            status.Text = "Connecting to serial port...";
        } while (serialPort == null);

        // Configure serial settings
        serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
        serialPort.BaudRate = 38400;
        serialPort.Parity = SerialParity.None;
        serialPort.StopBits = SerialStopBitCount.One;
        serialPort.DataBits = 8;
        serialPort.Handshake = SerialHandshake.None;
        status.Text = "Serial port configured successfully.";

        // Perform other serial logic... 
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
    finally
    {
        if(serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }
}
like image 1
Chris Schaller Avatar answered Oct 18 '22 22:10

Chris Schaller