I am writing a program that detects when a USB to serial device is removed from or attached to the PC. When a device is removed, it pauses a method that asynchronously reads from that serial port. When reconnected, the async method begins reading again.
The serial port functions all work, as does the detection code, but the detection event fires up to five or six times, causing a little confusion for the program temporarily.
This is the event code:
public Form1()
{
InitializeComponent();
// Check to see if USB Serial cable has been plugged or unplugged
SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
}
static SerialPortService()
{
_serialPorts = GetAvailableSerialPorts(); // Get valid serial ports
MonitorDeviceChanges();
}
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(global::IPPusher.EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(global::IPPusher.EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
if (eventType == global::IPPusher.EventType.Insertion)
{
Thread.Sleep(1000); // sleep while the PC sorts out its COM ports
var availableSerialPorts = GetAvailableSerialPorts();
var added = availableSerialPorts.Except(_serialPorts).ToArray();
var portInUse = availableSerialPorts.Max();
Form1.IsPaused = false;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added)); // Fire the insertion event
}
if (eventType == global::IPPusher.EventType.Removal)
{
serialMethods.CloseSerialPort(Form1.PortName); // close active serial port
Thread.Sleep(1000); // sleep to sort things out
var availableSerialPorts = GetAvailableSerialPorts();
Form1.IsPaused = true; // pause the async method
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed)); // fire the removal event
}
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public EventType EventType => _eventType;
}
}
This works, but I have no idea how to consume the event so that it only fires once for each state. As I said, it causes a little hiccup in the app and I lose some data from the serial port. Looking around at stack overflow, I see that I should unsubscribe and then re-subscribe to the event. I'm not sure where to put that code.
Any help would be greatly appreciated.