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>
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".
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
implementsIDisposable
so you should wrap calls to it inusing
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.
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;
}
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.
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;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With