How to auto-detect Arduino COM port? - c#

I'm using an Arduino with the Firmata library for communication to a C# application, and I want to eliminate a COM port configuration component since it can change from machine to machine...
Is it possible to:
Enumerate list of COM ports in the system? (In my googling I've seen some fairly ugly Win32 API code, hoping there's maybe a cleaner version now)
Auto-detect which COM port(s) are connected to an Arduino?

This little bit of code has performed very well for this (returns the COM port string, i.e. "COM12" if Arduino is detected):
private string AutodetectArduinoPort()
{
ManagementScope connectionScope = new ManagementScope();
SelectQuery serialQuery = new SelectQuery("SELECT * FROM Win32_SerialPort");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(connectionScope, serialQuery);
try
{
foreach (ManagementObject item in searcher.Get())
{
string desc = item["Description"].ToString();
string deviceId = item["DeviceID"].ToString();
if (desc.Contains("Arduino"))
{
return deviceId;
}
}
}
catch (ManagementException e)
{
/* Do Nothing */
}
return null;
}

You can use SerialPort.GetPortNames() to return an array of string COM port names.
I dont think you can auto detect the ports, youd have to ping the device in order to see if the device is connected.

Taking the WMI Management route a bit further, I've come up with a wrapper class which hooks on to the Win32_SerialPorts events and dynamically populated a list of SerialPorts for Arduino and Digi International (X-Bee) devices, complete with PortNames and BaudRates.
For now, I've used the devices Description field in the Win32_SerialPorts entry as the Key for the Dictionary, but this can easily be changed.
It has been tested to a limited capacity with an Arduino UNO and it seems to be stable.
// -------------------------------------------------------------------------
// <copyright file="ArduinoDeviceManager.cs" company="ApacheTech Consultancy">
// Copyright (c) ApacheTech Consultancy. All rights reserved.
// </copyright>
// <license type="GNU General Public License" version="3">
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
// <license>
// -------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Management;
using System.Runtime.CompilerServices;
// Automatically imported by Jetbeans Resharper
using ArduinoLibrary.Annotations;
namespace ArduinoLibrary
{
/// <summary>
/// Provides automated detection and initiation of Arduino devices. This class cannot be inherited.
/// </summary>
public sealed class ArduinoDeviceManager : IDisposable, INotifyPropertyChanged
{
/// <summary>
/// A System Watcher to hook events from the WMI tree.
/// </summary>
private readonly ManagementEventWatcher _deviceWatcher = new ManagementEventWatcher(new WqlEventQuery(
"SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2 OR EventType = 3"));
/// <summary>
/// A list of all dynamically found SerialPorts.
/// </summary>
private Dictionary<string, SerialPort> _serialPorts = new Dictionary<string, SerialPort>();
/// <summary>
/// Initialises a new instance of the <see cref="ArduinoDeviceManager"/> class.
/// </summary>
public ArduinoDeviceManager()
{
// Attach an event listener to the device watcher.
_deviceWatcher.EventArrived += _deviceWatcher_EventArrived;
// Start monitoring the WMI tree for changes in SerialPort devices.
_deviceWatcher.Start();
// Initially populate the devices list.
DiscoverArduinoDevices();
}
/// <summary>
/// Gets a list of all dynamically found SerialPorts.
/// </summary>
/// <value>A list of all dynamically found SerialPorts.</value>
public Dictionary<string, SerialPort> SerialPorts
{
get { return _serialPorts; }
private set
{
_serialPorts = value;
OnPropertyChanged();
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
// Stop the WMI monitors when this instance is disposed.
_deviceWatcher.Stop();
}
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Handles the EventArrived event of the _deviceWatcher control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArrivedEventArgs"/> instance containing the event data.</param>
private void _deviceWatcher_EventArrived(object sender, EventArrivedEventArgs e)
{
DiscoverArduinoDevices();
}
/// <summary>
/// Dynamically populates the SerialPorts property with relevant devices discovered from the WMI Win32_SerialPorts class.
/// </summary>
private void DiscoverArduinoDevices()
{
// Create a temporary dictionary to superimpose onto the SerialPorts property.
var dict = new Dictionary<string, SerialPort>();
try
{
// Scan through each SerialPort registered in the WMI.
foreach (ManagementObject device in
new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_SerialPort").Get())
{
// Ignore all devices that do not have a relevant VendorID.
if (!device["PNPDeviceID"].ToString().Contains("VID_2341") && // Arduino
!device["PNPDeviceID"].ToString().Contains("VID_04d0")) continue; // Digi International (X-Bee)
// Create a SerialPort to add to the collection.
var port = new SerialPort();
// Gather related configuration details for the Arduino Device.
var config = device.GetRelated("Win32_SerialPortConfiguration")
.Cast<ManagementObject>().ToList().FirstOrDefault();
// Set the SerialPort's PortName property.
port.PortName = device["DeviceID"].ToString();
// Set the SerialPort's BaudRate property. Use the devices maximum BaudRate as a fallback.
port.BaudRate = (config != null)
? int.Parse(config["BaudRate"].ToString())
: int.Parse(device["MaxBaudRate"].ToString());
// Add the SerialPort to the dictionary. Key = Arduino device description.
dict.Add(device["Description"].ToString(), port);
}
// Return the dictionary.
SerialPorts = dict;
}
catch (ManagementException mex)
{
// Send a message to debug.
Debug.WriteLine(#"An error occurred while querying for WMI data: " + mex.Message);
}
}
/// <summary>
/// Called when a property is set.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Try this, I'm working on a very similar project, also anyone please feel free to edit this!
In the setup portion of the Arduino code, I have it call a setupComms() method, this method simply prints an "A" until it receives an "a". Once "a" is received it jumps to the main loop() function. So the C# portion checks each available port for "A" and if "A" is found we know that we have opened the port to the Arduino!
Again, this may not be very clean but it does work, I am open to any comments and suggestions!
foreach (string s in SerialPort.GetPortNames())
{
com.Close(); // To handle the exception, in case the port isn't found and then they try again...
bool portfound = false;
com.PortName = s;
com.BaudRate = 38400;
try
{
com.Open();
status.Clear();
status.Text += "Trying port: " + s+"\r";
}
catch (IOException c)
{
status.Clear();
status.Text += "Invalid Port"+"\r";
return;
}
catch (InvalidOperationException c1)
{
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
catch (ArgumentNullException c2)
{
// System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c2);
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
catch (TimeoutException c3)
{
// System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c3);
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
catch (UnauthorizedAccessException c4)
{
//System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c);
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
catch (ArgumentOutOfRangeException c5)
{
//System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c5);
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
catch (ArgumentException c2)
{
//System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c2);
status.Clear();
status.Text += "Invalid Port" + "\r";
return;
}
if (!portfound)
{
if (com.IsOpen) // Port has been opened properly...
{
com.ReadTimeout = 500; // 500 millisecond timeout...
sent.Text += "Attemption to open port " + com.PortName + "\r";
try
{
sent.Text += "Waiting for a response from controller: " + com.PortName + "\r";
string comms = com.ReadLine();
sent.Text += "Reading From Port " + com.PortName+"\r";
if (comms.Substring(0,1) == "A") // We have found the arduino!
{
status.Clear();
status.Text += s + com.PortName+" Opened Successfully!" + "\r";
//com.Write("a"); // Sends 0x74 to the arduino letting it know that we are connected!
com.ReadTimeout = 200;
com.Write("a");
sent.Text += "Port " + com.PortName + " Opened Successfully!"+"\r";
brbox.Text += com.BaudRate;
comboBox1.Text = com.PortName;
}
else
{
sent.Text += "Port Not Found! Please cycle controller power and try again" + "\r";
com.Close();
}
}
catch (Exception e1)
{
status.Clear();
status.Text += "Incorrect Port! Trying again...";
com.Close();
}
}
}
}
All of the Try Catch statements are in there from when I was originally testing, this has worked for me so far. Good luck!

I just had a similar challenge with a Teensyduino communicating with a PC based processing language program. It may be useful for someone working with something Java or processing language rather than C#.
The basic idea of this solution was to send a handshake request ("!sh\n") to each Serial port and then listen for a response ("$h\n") from each device until the correct handshake response was received. Thereby showing which of the ports was the device I was seeking.
Also, I'm quite new to StackOverflow so please forgive and educate me if I'm breaking any StackOverflow etiquette in this answer.
Processing Language Code:
import processing.serial.*;
int ellipticalWalkerTeensyIndex; /* Represents Elliptical Walker Serial Port index in the Serial.list() String array. */
boolean latch;
String[] serialPortList = Serial.list();
int serialPortCount = serialPortList.length;
Serial[] ports = new Serial[serialPortCount];
int[] serialConnected = new int[serialPortCount];
void setup(){
for (int z = 0; z < serialPortCount; ++z) { /* Initialise serialConnected array to 0; Anything not marked to 1 later will be ignored. */
serialConnected[z] = 0;
}
ellipticalWalkerTeensyIndex = -1; /* Initialise ellipticalWalkerTeensyIndex to -1, as the correct serial port is not yet known. */
latch = false;
for (int z = 0; z < serialPortCount; ++z) {
try {
ports[z] = new Serial(this, serialPortList[z], 9600);
serialConnected[z] = 1; /* Mark this index as connected. */
ports[z].write("!sh"); /* Send handshake request; Expected response is "$h\n" */
}catch (Exception e){
println("Could not connect to "+Integer.toString(z)+" exception details: "+e);
}
}
}
void draw(){
if (ellipticalWalkerTeensyIndex < 0) {
for (int z = 0; z < serialPortCount; ++z) {
if(serialConnected[z]>0){ /* Only attempt communication if we have marked this serial port as connected during the setup routine. */
if (ports[z].available()>0) { /* Read from serial port 'z' if data is available. */
String lineOfData = ports[z].readStringUntil('\n');
if(lineOfData.charAt(0)=='$' && lineOfData.charAt(1)=='h'){ /* Check if received response matches expected handshake response */
ellipticalWalkerTeensyIndex = z; /* Note the correct serial port for the teensy. */
}
}
}
}
}else{
if (!latch) {
println("The teensyduino is on serial port: "+serialPortList[ellipticalWalkerTeensyIndex]);
latch = true;
exit();
}
}
}
Runtime results:
PS C:\repos\elliptical_walker> processing-java --sketch=c:\repos\elliptical_walker\EW0 --run
The teensyduino is on serial port: COM3
Finished.

I've noticed that my Chinese clone of Arduino nano shows up the COM port correctly in Devices Manager, but it doesn't show on the C# app dorp down list when you try and get all ports using this command:
using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"));
However, it is shown correctly when executing:
foreach (string z in SerialPort.GetPortNames())
So I have 2 lists: one with output of 1st code, and one with output from 2nd code. When comparing both, it will find the correct COM port. Obviously, when using Original Andurino Uno both commands displays port correctly so this solution will only work for Chinese clones which, for some odd reason, are invisible to using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"));

This method does not help you find out which port your arduino is connected to your computer
SerialPort.GetPortNames Method ()
// Get a list of serial port names.
string[] ports = SerialPort.GetPortNames();
Console.WriteLine("The following serial ports were found:");
Console.WriteLine("Aşşağıda Seri Bağlantı Noktaları Bulundu:");//For Turkish
// Display each port name to the console.
foreach(string port in ports)
{
Console.WriteLine(port);
}
Console.ReadLine();

With this php script you can transmit and receive data from arduino
<?php
/**
* Remember to go to Device Manager> Ports (COM & LPT)>Arduino XXX (COMXX)>right
* click>Properties>
* Port Settings>Advanced>uncheck "use FIFO buffers ........."
* In other hand, remeber that the Tx speed has to be the same in PhpConnect.php, in
* Arduino sketch and in the COM
* properties in Device manager, I selected 115200 b/s.
*
*/
// RX form PC**************
$t = $_POST['text1'];
include 'PruebaBatchCOM.php';
$puerto = escapeshellarg($usbCOM);
$dato = escapeshellarg($t);
exec("node C:\\xampp\\htdocs\\DisenoWEBTerminados\\BatteryTester\\Scripts\\writeandread.js {$puerto} {$dato} 2>&1", $output1);
$str = implode($output1);
$str1 = explode(",",$str);
$myJSON = json_encode($str1);// this is the response to AJAX
echo $myJSON;
?>
PruebaBatchCOM.php is
<?php
$puerto = array();
$file111 = "PruebaCOMRetrieve.bat";
exec($file111, $puerto);
$usbCOM = implode(",",$puerto);
?>
PruebaCOMRetrieve.bat
#echo off
setlocal
for /f "tokens=1* delims==" %%I in ('wmic path win32_pnpentity get caption
/format:list ^| find "Arduino Uno"') do (
call :setCOM "%%~J"
)
:: end main batch
goto :EOF
:setCOM <WMIC_output_line>
:: sets _COM#=line
setlocal
set "str=%~1"
set "num=%str:*(COM=%"
set "num=%num:)=%"
set port=COM%num%
echo %port%

Related

C# communication with serial ports [duplicate]

I just started learning how to send and receive data from my hardware through the C# GUI.
Can anyone please write a detail how to read data from the serial port?
SerialPort (RS-232 Serial COM Port) in C# .NET
This article explains how to use the SerialPort class in .NET to read and write data, determine what serial ports are available on your machine, and how to send files. It even covers the pin assignments on the port itself.
Example Code:
using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace SerialPortExample
{
class SerialPortProgram
{
// Create the serial port with basic settings
private SerialPort port = new SerialPort("COM1",
9600, Parity.None, 8, StopBits.One);
[STAThread]
static void Main(string[] args)
{
// Instatiate this class
new SerialPortProgram();
}
private SerialPortProgram()
{
Console.WriteLine("Incoming Data:");
// Attach a method to be called when there
// is data waiting in the port's buffer
port.DataReceived += new
SerialDataReceivedEventHandler(port_DataReceived);
// Begin communications
port.Open();
// Enter an application loop to keep this thread alive
Application.Run();
}
private void port_DataReceived(object sender,
SerialDataReceivedEventArgs e)
{
// Show all the incoming data in the port's buffer
Console.WriteLine(port.ReadExisting());
}
}
}
I spent a lot of time to use SerialPort class and has concluded to use SerialPort.BaseStream class instead. You can see source code: SerialPort-source and
SerialPort.BaseStream-source for deep understanding.
I created and use code that shown below.
The core function
public int Recv(byte[] buffer, int maxLen)
has name and works like "well known" socket's recv().
It means that
in one hand it has timeout for no any data and throws TimeoutException.
In other hand, when any data has received,
it receives data either until maxLen bytes
or short timeout (theoretical 6 ms) in UART data flow
.
public class Uart : SerialPort
{
private int _receiveTimeout;
public int ReceiveTimeout { get => _receiveTimeout; set => _receiveTimeout = value; }
static private string ComPortName = "";
/// <summary>
/// It builds PortName using ComPortNum parameter and opens SerialPort.
/// </summary>
/// <param name="ComPortNum"></param>
public Uart(int ComPortNum) : base()
{
base.BaudRate = 115200; // default value
_receiveTimeout = 2000;
ComPortName = "COM" + ComPortNum;
try
{
base.PortName = ComPortName;
base.Open();
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Error: Port {0} is in use", ComPortName);
}
catch (Exception ex)
{
Console.WriteLine("Uart exception: " + ex);
}
} //Uart()
/// <summary>
/// Private property returning positive only Environment.TickCount
/// </summary>
private int _tickCount { get => Environment.TickCount & Int32.MaxValue; }
/// <summary>
/// It uses SerialPort.BaseStream rather SerialPort functionality .
/// It Receives up to maxLen number bytes of data,
/// Or throws TimeoutException if no any data arrived during ReceiveTimeout.
/// It works likes socket-recv routine (explanation in body).
/// Returns:
/// totalReceived - bytes,
/// TimeoutException,
/// -1 in non-ComPortNum Exception
/// </summary>
/// <param name="buffer"></param>
/// <param name="maxLen"></param>
/// <returns></returns>
public int Recv(byte[] buffer, int maxLen)
{
/// The routine works in "pseudo-blocking" mode. It cycles up to first
/// data received using BaseStream.ReadTimeout = TimeOutSpan (2 ms).
/// If no any message received during ReceiveTimeout property,
/// the routine throws TimeoutException
/// In other hand, if any data has received, first no-data cycle
/// causes to exit from routine.
int TimeOutSpan = 2;
// counts delay in TimeOutSpan-s after end of data to break receive
int EndOfDataCnt;
// pseudo-blocking timeout counter
int TimeOutCnt = _tickCount + _receiveTimeout;
//number of currently received data bytes
int justReceived = 0;
//number of total received data bytes
int totalReceived = 0;
BaseStream.ReadTimeout = TimeOutSpan;
//causes (2+1)*TimeOutSpan delay after end of data in UART stream
EndOfDataCnt = 2;
while (_tickCount < TimeOutCnt && EndOfDataCnt > 0)
{
try
{
justReceived = 0;
justReceived = base.BaseStream.Read(buffer, totalReceived, maxLen - totalReceived);
totalReceived += justReceived;
if (totalReceived >= maxLen)
break;
}
catch (TimeoutException)
{
if (totalReceived > 0)
EndOfDataCnt--;
}
catch (Exception ex)
{
totalReceived = -1;
base.Close();
Console.WriteLine("Recv exception: " + ex);
break;
}
} //while
if (totalReceived == 0)
{
throw new TimeoutException();
}
else
{
return totalReceived;
}
} // Recv()
} // Uart
Note that usage of a SerialPort.DataReceived event is optional. You can set proper timeout using SerialPort.ReadTimeout and continuously call SerialPort.Read() after you wrote something to a port until you get a full response.
Moreover, you can use SerialPort.BaseStream property to extract an underlying Stream instance. The benefit of using a Stream is that you can easily utilize various decorators with it:
var port = new SerialPort();
// LoggingStream inherits Stream, implements IDisposable, needed abstract methods and
// overrides needed virtual methods.
Stream portStream = new LoggingStream(port.BaseStream);
portStream.Write(...); // Logs write buffer.
portStream.Read(...); // Logs read buffer.
For more information:
Top 5 SerialPort Tips article by Kim Hamilton, BCL Team Blog
C# await event and timeout in serial port communication discussion on StackOverflow

Serial port sometimes takes (exactly) 30 seconds to Open or Close, only connects once without manual RTS control

I'm having a very peculiar issue with the serial port class in C# (using .NET 4.5 as a target). Our application needs the ability to switch between (close then open another) serial COM ports which are all USB 1.1 virtual COM port devices. The problem I'm having has two symptoms that may be related. First, we can only connect to our device after having already read from it using another terminal emulator or similar application and even then only once. After one successful connection all other attempts after closing and re-opening the port will never receive data (DataReceivedEvent never fires). The only workaround I have found to this is to manually drive the RTS signal as shown below (even though every other serial library or emulation program requires no handshaking to talk to this device, including TerraTerm, Java, and PySerial).
Second, even then, while the port may be freely opened and closed successful, occasionally (1/30 ish) the Open() or Close() functions take precisely 30 seconds to finish (30008 ms measured via a diagnostic Stopwatch) where it would normally take a mere 5-8 ms. There seems to be no explanation for this. Any thoughts?
Measures already used to try eliminating or mitigating the problem:
Attempts to Open or Close the port are executed as separate tasks in the thread pool so that they can be terminated or aborted cleanly after a specified period of time if unsuccessful (I use 60 seconds for this value in testing)
I've added various time delays both short (100 ms) and extreme (5000 ms) in every conceivable place with no effect (between port close and reopen, between open and close, and between open/close and RTS true/false
Double triple and quadruple checked the designed port parameters for our USB serial device
I've looked at this article by bvoigt and this article by Zach Saw which both seem to suggest that there are deeply rooted issues with .NET SerialPort class. This has left me with the impression that a third party library might be the best option (there are some good candidates on NuGet). Ultimately this will be used as the serial port within a WPF application.
Before anyone asks, no, simply opening the port and leaving it open is not an option in our application and, frankly, this is something that works seamlessly 100% of the time with the same device from the same hosts with the same driver in other languages' standard serial implementations (see above).
Please see the simple exemplary application below which I have been using to successfully reproduce this problem. Any pointers in the right direction are greatly appreciated!!!
SerialTestApplication.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Diagnostics;
using System.Threading;
namespace SerialTestApplication
{
class Program
{
private SerialPort S = new SerialPort();
private bool S_active = false;
private int succeed = 0;
private int fail = 0;
static void Main(string[] args)
{
Program P = new Program();
System.Threading.Thread.Sleep(1000);
var test_timer = new Stopwatch();
test_timer.Start();
for (int a = 0; a < 100; a++)
{
P.ConnectSerial(P);
P.CloseSerial(P);
}
Console.WriteLine("EXIT");
System.Threading.Thread.Sleep(1000);
Console.ReadLine();
}
private bool ConnectSerial(Program P)
{
if (S.IsOpen)
{
fail++;
Console.WriteLine("PORT NOT CLOSED YET");
return false;
}
// Setup The Serial COMM Port
S.PortName = "COM1";
S.BaudRate = 115200;
S.Handshake = System.IO.Ports.Handshake.None;
S.Parity = Parity.None;
S.DataBits = 8;
S.StopBits = StopBits.One;
// Write Properties
S.WriteBufferSize = 2048;
S.WriteTimeout = 500;
// Read Properties
S.ReceivedBytesThreshold = 1;
S.ReadBufferSize = 2048;
S.ReadTimeout = 500;
try
{
S_active = false;
if (!P.OpenSerial(P))
{
fail++;
return false;
}
var connection_timer = new Stopwatch();
connection_timer.Start();
while (S.IsOpen && !S_active)
{
if (connection_timer.ElapsedMilliseconds > 1000)
{
fail++;
CloseSerial(this);
Console.WriteLine("Succeed/Fail: " + succeed + " " + fail);
return false;
}
}
succeed++;
Console.WriteLine("Succeed/Fail: " + succeed + " " + fail);
Console.WriteLine(S.BytesToWrite + " " + S.BytesToRead);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
private void Receive(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (S.IsOpen)
{
S_active = true;
S.ReadExisting();
}
}
private bool OpenSerial(Program P)
{
Task OpenAttempt = Task.Factory.StartNew(() =>
{
try
{
if (S.IsOpen)
{
Console.WriteLine("PORT STILL OPEN!");
return;
}
S.Open();
S.DiscardInBuffer();
S.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(Receive);
S.RtsEnable = true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
});
var open_timer = new Stopwatch();
open_timer.Start();
OpenAttempt.Wait(60000);
Console.WriteLine("Opened In: " + open_timer.ElapsedMilliseconds);
return S.IsOpen;
}
private void CloseSerial(Program P)
{
Task CloseAttempt = Task.Factory.StartNew(() =>
{
try
{
S.RtsEnable = false;
S.Close();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
});
var close_timer = new Stopwatch();
close_timer.Start();
CloseAttempt.Wait(60000);
Console.WriteLine("Closed In: " + close_timer.ElapsedMilliseconds);
}
}
}
EDIT 1
I've found one issue with this but unfortunately it doesn't solve the core "takes 30 seconds to finish the Open() or Close() method" problem. The way I am recycling (closing and reopening) the SerialPort object in this test application adds a new DataReceived event handler every time the port is reopened. Basically after closing/opening the port 10 times there will be ten DataReceived handlers associated with the object. To correct that I'm now letting Close() Dispose() the SerialPort object then setting the reference to null and creating a completely new instance of SerialPort on the next open. That seems to ensure that the serial port is properly released so long as the USB cable is not physically pulled out of the computer.

Debugging Program Keeps Sending Emails After Exit

I have the below code which I use as part of a program which reads some event logs.
After stepping through the code this morning to test something out, I keep getting error messages emailed to me:
EventLogHelper error occurred in QueryEvents
Exception: The RPC server is unavailable
User:
Client: D7-089
The process is not running on my machine, and I only stepped through the method in question once or twice. However The messages just keep coming. The space of time between each message seems to vary, and I have received at least 15 now.
How is this possible? I feel that if the program is sending me emails, it must be executing somewhere, but I cannot see it in the Processes tab in Task Manager, and I tried ending all Visual Studio related processes without any joy.
I restarted the PC on which VS was running, but I am still receiving the emails.
Main()
public static void Main()
{
var eh = new EventLogHelper();
var eventFired = eh.CheckEvents();
}
EventLogHelper.cs
public readonly string PcName;
private readonly int Timespan;
private readonly string Filter;
private static EventLogSession Session;
/// <summary>
/// ctor
/// </summary>
public EventLogHelper()
{
Timespan = 30000; // 30 seconds
PcName = "D7-089"; // This is usually "Environment.MachineName", but specified it here for testing
Filter = $"*[System[(EventID='5061' or EventID='5058') and TimeCreated[timediff(#SystemTime) <= {Timespan}]]]";
}
CheckEvents
/// <summary>
/// Checks the event logs for remote pc and returns true if any of the events we are interested in fired
/// </summary>
/// <returns></returns>
public bool CheckEvents()
{
var query = BuildQuery(PcName, Filter);
for (var i = 0; i < 60; i++)
{
var logs = QueryEvents(query);
var events = ReadLogs(logs);
if (events > 0)
{
return true;
}
System.Threading.Thread.Sleep(1000);
}
return false;
}
BuildQuery
/// <summary>
/// Builds an EventLogQuery for the given pcname and filter. This should be set up with a user who has admin rights
/// </summary>bh
private static EventLogQuery BuildQuery(string pcName, string filter)
{
try
{
using (var pw = GetPassword())
{
Session = new EventLogSession(
pcName,
"DOMAIN",
"USER",
pw,
SessionAuthentication.Default);
}
return new EventLogQuery("Security", PathType.LogName, filter)
{ Session = Session };
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in BuildQuery \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {pcName}");
Environment.Exit(Environment.ExitCode);
return null;
}
}
QueryEvents
This is where the error is occurring. I stepped through this method 2 times at most and as I type this question I am still getting error emails through.
/// <summary>
/// Execute the given EventLogQuery
/// </summary>
private EventLogReader QueryEvents(EventLogQuery query)
{
try
{
return new EventLogReader(query);
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in QueryEvents \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {PcName}");
Environment.Exit(Environment.ExitCode);
return null;
}
}
ReadLogs
/// <summary>
/// Read the given EventLogReader and return the amount of events that match the IDs we are looking for
/// </summary>
/// <param name="logReader"></param>
/// <returns></returns>
private int ReadLogs(EventLogReader logReader)
{
var count5058 = 0;
var count5061 = 0;
EventRecord entry;
try
{
while ((entry = logReader.ReadEvent()) != null)
{
if (entry.Id == 5058)
{
count5058++;
}
else
{
count5061++;
}
}
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in ReadLogs \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {PcName}");
Environment.Exit(Environment.ExitCode);
}
return count5058 + count5061;
}
It turns out that this was being caused by a stupid mistake on my part.
This program has a Post-Build Event which is called with:
if $(ConfigurationName) == Release ("$(ProjectDir)PostBuildRelease.bat" "$(TargetDir)" #(VersionNumber) "$(TargetFileName)" "$(TargetName)")
So it only runs when VS build configuration is set to Release.
PostBuildRelease.bat simply copies the resuling assembly to the live location, ready for users to have copied to their desktops at logon.
Whilst testing my app, I foolishly edited the source code to query a specific PC, and then stepped through the code.
However, the build configuration was set to Release, So once the assembly was built ready to be debugged, it was automatically copied into the live executable location and therefore also copied to user's desktops at logon.
If the code is run with a hard-coded PcName where that PC is not the current machine, the event query appears to fail with the above error message.
So all of the emails I receiving were being sent out because the program was actually being executed on user PCs. However because PcName was hard-coded in, it always looked like it was coming from my instance of the program!
The lesson here is to always be aware of which build configuration is currently selected, especially if a Post-Build event is specified.

Reasons for long-running WCF service dying?

I have a long-running WCF service, hosted in IIS, that handles printing without any user interaction. After about 2 hours of looping through 1000 print jobs, the WCF service just seems to die. The log file I track indicates that the last print job was sent to the printer, but it never returns success nor failure (again, according to the log file).
From the log file, it would normally say:
2015-12-17 19:00:23,673 [27] INFO Barn.WCF.SysPrsPrintServer - Sending Print Job... PrintDocumentId=168;PrintSectionId=742;CustomerId=112702;DeliveryAddressId=474984;DocumentName=/SystemProcesses/Reports/CertificateOfRegistry;PrinterLocation=HB-MFP1;PrinterPaperSource=Cassette 3
2015-12-17 19:00:32,626 [27] INFO Barn.WCF.SysPrsPrintServer - PdfPrintHandler.Print: Printer HB-MFP1 indicates the print job is complete.
But the last log file entry is:
2015-12-17 19:00:32,688 [27] INFO Barn.WCF.SysPrsPrintServer - Sending Print Job... PrintDocumentId=169;PrintSectionId=742;CustomerId=112702;DeliveryAddressId=474984;DocumentName=/SystemProcesses/Reports/CertificateOfRegistry;PrinterLocation=HB-MFP1;PrinterPaperSource=Cassette 3
Can you see how this might be possible that I get no message back at all? Is it possible the WCF service just died or IIS recycled the app pool or something like that? So without further ado, here is my class:
using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.IO;
using System.Threading;
using log4net;
using Aspose.Pdf.Facades;
namespace Barn.API.Print.PrintHandlers
{
public class PdfPrintHandler
{
private const int LargePdfByteCount = 3000000;
private readonly ILog _log;
private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
public PdfPrintHandler(ILog log)
{
_log = log;
var license = new Aspose.Pdf.License();
var pathLicense = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "bin\\Aspose.Pdf.lic";
license.SetLicense(pathLicense);
}
/// <summary>
/// Prints the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="printerSettings">The printer settings.</param>
/// <param name="pageSettings">The page settings.</param>
/// <param name="timeout">The timeout.</param>
/// <param name="errors"></param>
/// <returns>The status of the print job.</returns>
public PrintJobStatusEnum Print(Stream stream, PrinterSettings printerSettings, PageSettings pageSettings, PrintDocumentModel printJob, int timeout, out List<string> errors)
{
errors = new List<string>();
// Reset the wait handler to make sure the event is not signaled
_resetEvent.Reset();
// Set attributes for printing
var viewer = new PdfViewer
{
AutoResize = true,
AutoRotate = true,
PrintPageDialog = false,
PrintAsImage = false
};
// Add an event listener when print job sent to printer
viewer.EndPrint += ViewerOnEndPrint;
//Print document using printer and page settings
try
{
_log.InfoFormat("Sending Print Job... PrintDocumentId={0};PrintSectionId={1};CustomerId={2};DeliveryAddressId={3};DocumentName={4};PrinterLocation={5};PrinterPaperSource={6}",
printJob.PrintDocumentId,
printJob.PrintSectionId != null ? printJob.PrintSectionId.ToString() : "null",
printJob.CustomerId != null ? printJob.CustomerId.ToString() : "null",
printJob.DeliveryAddressId != null ? printJob.DeliveryAddressId.ToString() : "null",
printJob.DocumentName != null ? printJob.DocumentName : "null",
printJob.PrinterLocation != null ? printJob.PrinterLocation : "null",
pageSettings.PaperSource.SourceName != null ? pageSettings.PaperSource.SourceName : "null");
// Print
if (stream.Length <= LargePdfByteCount)
{
// Bind the stream then print
viewer.BindPdf(stream);
viewer.PrintDocumentWithSettings(pageSettings, printerSettings);
}
else
{
// Use a more efficient printing method with larger documents
viewer.PrintLargePdf(stream, pageSettings, printerSettings);
}
// Block until the event finishes or timeout reached
_resetEvent.WaitOne(TimeSpan.FromMinutes(timeout));
// Check the print status
if (viewer.PrintStatus != null)
{
// An exception was thrown
var ex = viewer.PrintStatus as Exception;
if (ex != null)
{
// Get exception message
_log.Error("PdfPrintHandler.Print: Print Error: " + ex.Message + ex.StackTrace, ex);
errors.Add(ex.Message);
}
return PrintJobStatusEnum.Error;
}
else
{
// No errors were found. Printing job has completed successfully
_log.InfoFormat("PdfPrintHandler.Print: Printer {0} indicates the print job is complete.", printerSettings.PrinterName);
return PrintJobStatusEnum.Printed;
}
}
catch (Exception e)
{
_log.Error("PdfPrintHandler.Print Exception: " + e.Message + e.StackTrace, e);
errors.Add(e.Message);
}
finally
{
viewer.Close();
}
return PrintJobStatusEnum.Error;
}
private void ViewerOnEndPrint(object sender, PrintEventArgs printEventArgs)
{
// Signal the event is finished
_resetEvent.Set();
}
}
}
WCF Service Interface:
using System.ServiceModel;
namespace Barn.WCF
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "ISysPrsPrintServer" in both code and config file together.
[ServiceContract]
public interface ISysPrsPrintServer
{
[OperationContract]
string DoWork(string data);
}
}
I would suggest checking the operation contracts of your print methods. I have seen WCF services fail out when the isOneWay attribute is not set or is set to false on a void method. try something like this
[OperationContract(IsOneWay=true)]
public void PrintDocumentWithSettings(PageSettings settings, PrinterSettings settings);
Here it is in the Windows event log:
A worker process with process id of '3236' serving application pool
'testws.mydomain.ca' was shutdown due to inactivity. Application Pool
timeout configuration was set to 20 minutes. A new worker process
will be started when needed.
So IIS seems to have thought that there was 20 minutes of inactivity because the WCF service is called asynchronously. The process is working away, but IIS decided to recycle the application pool in the middle of the process running.
I changed the settings in IIS on the application pool according to this:
https://serverfault.com/questions/333907/what-should-i-do-to-make-sure-that-iis-does-not-recycle-my-application

Enumerating composite device nodes

I have the following class that enumerates all COM ports on the local PC, finding and storing only those with a friendly name that starts with the given prefix.
Now, assuming I know such COM ports are part of a composite device and that the composite device also has a network adapter, how can I find the network adapter associated with the given device? I'm sure there must be a relatively simple way of doing this (both devices will have the same parent node I think), but I'm not sure what the non-interop method would be... Can anyone assist?
Here is the class:
/// <summary>
/// A class to enumerate all COM ports through USB.
/// </summary>
public class SerialPortUSB
{
/// <summary>
/// Structure to store a port name and details.
/// </summary>
public struct PortName
{
public string Port;
public string Fullname;
public bool HadPrefix;
};
/// <summary>
/// Function to return all ports with the given prefix.
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
static public List<PortName> PortsWithPrefix(string prefix)
{
List<PortName> ports = new List<PortName>();
try
{
// Select all COM ports.
ManagementObjectSearcher searcher = new ManagementObjectSearcher( "root\\CIMV2",
"SELECT * FROM Win32_SerialPort");
// Now iterate results looking for those that start with the prefix.
foreach (ManagementObject item in searcher.Get())
{
string friendlyName = (string)item["Caption"];
if (!friendlyName.StartsWith(prefix))
{
continue;
}
// Construct an item for this.
PortName name = new PortName();
int start = friendlyName.LastIndexOf('(') + 1, end = friendlyName.LastIndexOf(')');
name.HadPrefix = true;
name.Port = friendlyName.Substring(start, end - start);
name.Fullname = friendlyName;
ports.Add(name);
}
}
catch (ManagementException e)
{
// Failed to find any...
}
// Return the list of ports.
return ports;
}
}
Thought I'd reply to my own, rather than deleting the question just in case someone else finds the answer useful.
It turns out the PNPDeviceID, a property present in both Win32_SerialPorts and Win32_NetworkAdapters contains an ID unique to the device, so it's possible to identify them both. The solution is first to enumerate the serial ports, then extract the unique part of the PNPDeviceID and then to find all network adapters that are LIKE '%value%', where value is part of the PNPDeviceID.
Turns out the number is the same for both, i.e.:
Serial Port PNPDeviceID = USB\VID_17DC&PID_0500&MI_02\8&18F2972&0&0002"
and
Network Adapter PNPDeviceID = USB\VID_17DC&PID_0500&MI_00\8&18F2972&0&0000"
, where the common ID is 18F2972.

Categories