.net - sirve - Detectar inserción/extracción de puerto serie
puerto serial vb6 (5)
Me estoy conectando con un puerto USB a serie que se puede insertar o quitar en cualquier momento. Descubrí que puedo usar WMI (particularmente con el uso de WMI Code Creator ) para consultar los cambios de dispositivo en la PC.
En el fragmento de Win32_DeviceChangeEvent generado a continuación, se ha suscrito el Win32_DeviceChangeEvent . Sin embargo, este evento no revela qué dispositivo (por ejemplo, USB, puerto serie, etc.) causó el evento. ¿Hay alguna forma de recibir notificaciones solo cuando se insertan o eliminan puertos serie?
Para aclarar, el punto del código no es detectar la apertura / cierre de los puertos serie, es detectar si se ha agregado un nuevo puerto a la máquina o si se eliminó un puerto anterior.
using System;
using System.Management;
using System.Windows.Forms;
namespace WMISample
{
public class WMIReceiveEvent
{
public WMIReceiveEvent()
{
try
{
WqlEventQuery query = new WqlEventQuery(
"SELECT * FROM Win32_DeviceChangeEvent");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
watcher.EventArrived +=
new EventArrivedEventHandler(
HandleEvent);
// Start listening for events
watcher.Start();
// Do something while waiting for events
System.Threading.Thread.Sleep(10000);
// Stop listening for events
watcher.Stop();
return;
}
catch(ManagementException err)
{
MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
}
}
private void HandleEvent(object sender,
EventArrivedEventArgs e)
{
Console.WriteLine("Win32_DeviceChangeEvent event occurred.");
}
public static void Main()
{
WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
return;
}
}
}
Aquí hay una versión DeviceChangeEvents
de una clase de notificación de DeviceChangeEvents
que escribí hace algún tiempo, aunque nunca la completé completamente. Lo eliminé todo, excepto el evento PortArrived, ya que es bastante feo de lo contrario.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public sealed class PortArrivalEventArgs : EventArgs
{
public string Name { get; private set; }
public PortArrivalEventArgs(string name) { Name = name; }
}
public static class DeviceChangeEvents
{
#region Events
#region PortArrived
private static object PortArrivedEvent = new Object();
public static event EventHandler<PortArrivalEventArgs> PortArrived
{
add { AddEvent(PortArrivedEvent, value); }
remove { RemoveEvent(PortArrivedEvent, value); }
}
private static void FirePortArrived(IntPtr lParam)
{
EventHandler<PortArrivalEventArgs> handler
= (EventHandler<PortArrivalEventArgs>)events[PortArrivedEvent];
if (handler != null)
{
string portName = Marshal.PtrToStringAuto((IntPtr)((long)lParam + 12));
handler(null, new PortArrivalEventArgs(portName));
}
}
#endregion
#endregion
#region Internal
private static EventHandlerList events = new EventHandlerList();
private static MessageWindow messageWindow = null;
private static void AddEvent(object key, Delegate value)
{
events.AddHandler(key, value);
if (messageWindow == null)
messageWindow = new MessageWindow();
}
private static void RemoveEvent(object key, Delegate value)
{
events.RemoveHandler(key, value);
// In the more complete version of DeviceChangedEvents, System.ComponentModel.EventHandlerList
// is replaced by an identical event storage object which exposes a count of the number of
// handlers installed. It also removes empty handler stubs. Both of these are required
// to safely destroy the message window when the last handler is removed.
//if (messageWindow != null && events.Count == 0)
// messageWindow.DestroyHandle();
}
#endregion
private sealed class MessageWindow : NativeWindow
{
public MessageWindow()
{
CreateParams cp = new CreateParams();
cp.Caption = GetType().FullName;
// NOTE that you cannot use a "message window" for this broadcast message
//if (Environment.OSVersion.Platform == PlatformID.Win32NT)
// cp.Parent = (IntPtr)(-3); // HWND_MESSAGE
//Debug.WriteLine("Creating MessageWindow " + cp.Caption);
CreateHandle(cp);
}
const int WM_DESTROY = 0x02;
const int WM_DEVICECHANGE = 0x219;
enum DBT
{
DEVICEARRIVAL = 0x8000,
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DESTROY)
{
messageWindow = null;
}
else if (m.Msg == WM_DEVICECHANGE)
{
DBT changeType = (DBT)m.WParam;
int deviceType = m.LParam == IntPtr.Zero ? 0 : Marshal.ReadInt32(m.LParam, 4);
Debug.WriteLine(String.Format("WM_DEVICECHANGE changeType = {0}, deviceType = {1}", changeType, deviceType));
switch (changeType)
{
case DBT.DEVICEARRIVAL:
switch (deviceType)
{
case 3: // DBT_DEVTYP_PORT
FirePortArrived(m.LParam);
break;
}
break;
}
}
base.WndProc(ref m);
}
}
}
El evento de cambio de dispositivo se puede utilizar con la entidad WMI-PNP. Lo siguiente devolverá los detalles del dispositivo; en el código que aparece a continuación se muestra el nombre del dispositivo.
Dim moReturn As Management.ManagementObjectCollection
Dim moSearch As Management.ManagementObjectSearcher
Dim mo As Management.ManagementObject
moSearch = New Management.ManagementObjectSearcher("Select * from Win32_PnPEntity")
moReturn = moSearch.Get
For Each mo In moReturn
If CStr(mo.Properties.Item("Name").Value).Contains("Prolific") Then
returns something like: "Prolific USB-to-Serial Comm Port (COM17)"
txtStatus.Text &= CStr(mo.Properties.Item("Name").Value) & vbCrLf
End If
Next
También vea el código para acceder a otras propiedades de PNP que podrían usarse para filtrar o monitorear el cambio:
On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts://" & strComputer & "/root/cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_PnPEntity",,48)
For Each objItem in colItems
"Availability: " & objItem.Availability
"Caption: " & objItem.Caption
"ClassGuid: " & objItem.ClassGuid
"ConfigManagerErrorCode: " & objItem.ConfigManagerErrorCode
"ConfigManagerUserConfig: " & objItem.ConfigManagerUserConfig
"CreationClassName: " & objItem.CreationClassName
"Description: " & objItem.Description
"DeviceID: " & objItem.DeviceID
"ErrorCleared: " & objItem.ErrorCleared
"ErrorDescription: " & objItem.ErrorDescription
"InstallDate: " & objItem.InstallDate
"LastErrorCode: " & objItem.LastErrorCode
"Manufacturer: " & objItem.Manufacturer
"Name: " & objItem.Name
"PNPDeviceID: " & objItem.PNPDeviceID
"PowerManagementCapabilities: " & objItem.PowerManagementCapabilities
"PowerManagementSupported: " & objItem.PowerManagementSupported
"Service: " & objItem.Service
"Status: " & objItem.Status
"StatusInfo: " & objItem.StatusInfo
"SystemCreationClassName: " & objItem.SystemCreationClassName
"SystemName: " & objItem.SystemName
Next
NB: Intenté publicar esto como un comentario sobre la respuesta de @Pat, pero no tengo suficiente reputación para hacerlo.
Además del comentario de @ 2pietjuh2, RaisePortsChangedIfNecessary () se puede cambiar a lo siguiente:
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = GetAvailableSerialPorts();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
Los eventos generados incluyen el puerto serie insertado / eliminado, en lugar de la lista de puertos serie disponibles después de la inserción / eliminación.
No. Ve y descubre qué pasó con SerialPort.GetPortNames (). Escuchar el mensaje WM_DEVICECHANGE en una ventana puede darle una mejor información.
Terminé usando el consejo de WMI y @Hans para verificar qué puertos serie son nuevos / faltan.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.Contracts;
using System.IO.Ports;
using System.Management;
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
static SerialPortService()
{
_serialPorts = GetAvailableSerialPorts();
MonitorDeviceChanges();
}
/// <summary>
/// If this method isn''t called, an InvalidComObjectException will be thrown (like below):
/// System.Runtime.InteropServices.InvalidComObjectException was unhandled
///Message=COM object that has been separated from its underlying RCW cannot be used.
///Source=mscorlib
///StackTrace:
/// at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis, IntPtr pThread)
/// at System.Management.IWbemServices.CancelAsyncCall_(IWbemObjectSink pSink)
/// at System.Management.SinkForEventQuery.Cancel()
/// at System.Management.ManagementEventWatcher.Stop()
/// at System.Management.ManagementEventWatcher.Finalize()
///InnerException:
/// </summary>
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(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = GetAvailableSerialPorts();
if (!_serialPorts.SequenceEqual(availableSerialPorts))
{
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, _serialPorts));
}
}
}
public static string[] GetAvailableSerialPorts()
{
return SerialPort.GetPortNames();
}
}
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 string[] SerialPorts
{
get
{
return _serialPorts;
}
}
public EventType EventType
{
get
{
return _eventType;
}
}
}
El método MonitorDeviceChanges
realidad ve todos los cambios del dispositivo (como el Administrador de dispositivos), pero la verificación de los puertos serie nos permite solo generar un evento cuando estos han cambiado.
Para usar el código, simplemente suscríbase al evento PortsChanged
, por ejemplo, SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);
Ah, y el método .Raise
es solo un método de extensión que recogí en alguna parte:
/// <summary>
/// Tell subscribers, if any, that this event has been raised.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler">The generic event handler</param>
/// <param name="sender">this or null, usually</param>
/// <param name="args">Whatever you want sent</param>
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
// Copy to temp var to be thread-safe (taken from C# 3.0 Cookbook - don''t know if it''s true)
EventHandler<T> copy = handler;
if (copy != null)
{
copy(sender, args);
}
}