Comment capturer un port série qui disparaît parce que le câble USB se débranche

StackOverflow https://stackoverflow.com/questions/286184

  •  08-07-2019
  •  | 
  •  

Question

J'ai un programme c # winforms et il ouvre un port série. Le problème survient lorsque l’utilisateur final débranche le câble USB, puis le périphérique disparaît. Après cela, le programme se bloquera et voudra signaler l’erreur à Microsoft.

Existe-t-il un moyen de capturer cet événement et de le fermer en douceur?

Était-ce utile?

La solution

Vous pouvez utiliser WMI (Windows Management Instrumentation) pour recevoir des notifications sur les événements USB. Je l’ai fait exactement il ya deux ans, en surveillant le branchement et le débranchement d’un périphérique USB spécifique.
Malheureusement, le code reste chez mon ancien employeur, mais j'ai trouvé un exemple à l'adresse bytes.com :

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Management;
class UsbWatcher 
{
    public static void Main() 
    {
        WMIEvent wEvent = new WMIEvent();
        ManagementEventWatcher watcher = null;
        WqlEventQuery query;
        ManagementOperationObserver observer = new ManagementOperationObserver();

        ManagementScope scope = new ManagementScope("root\\CIMV2");
        scope.Options.EnablePrivileges = true; 
        try 
        {
            query = new WqlEventQuery();
            query.EventClassName = "__InstanceCreationEvent";
            query.WithinInterval = new TimeSpan(0,0,10);

            query.Condition = @"TargetInstance ISA 'Win32_USBControllerDevice' ";
            watcher = new ManagementEventWatcher(scope, query);

            watcher.EventArrived 
                += new EventArrivedEventHandler(wEvent.UsbEventArrived);
            watcher.Start();
        }
        catch (Exception e)
        {
            //handle exception
        }
}

Je ne me souviens pas si j'ai modifié la requête pour recevoir des événements uniquement pour un périphérique spécifique, ou si j'ai filtré les événements d'autres périphériques dans mon gestionnaire d'événements. Pour plus d'informations, consultez le répertoire de code MSDN WMI .NET <. / a>.

MODIFIER J'ai trouvé quelques informations supplémentaires sur le gestionnaire d'événements. Il se présente comme suit:

protected virtual void OnUsbConnected(object Sender, EventArrivedEventArgs Arguments)
{
    PropertyData TargetInstanceData = Arguments.NewEvent.Properties["TargetInstance"];

    if (TargetInstanceData != null)
    {
        ManagementBaseObject TargetInstanceObject = (ManagementBaseObject)TargetInstanceData.Value;
        if (TargetInstanceObject != null)
        {
            string dependent = TargetInstanceObject.Properties["Dependent"].Value.ToString();
            string deviceId = dependent.Substring(dependent.IndexOf("DeviceID=") + 10);

            // device id string taken from windows device manager
            if (deviceId = "USB\\\\VID_0403&PID_6001\\\\12345678\"")
            {
                // Device is connected
            }
        }
    }
}

Vous pouvez cependant ajouter un peu de gestion des exceptions.

Autres conseils

Oui, il existe un moyen de capturer l'événement. Malheureusement, il peut s'écouler un long délai entre le moment où le périphérique est retiré et le moment où le programme reçoit une notification.

L’approche consiste à intercepter les événements de port de communication tels que ErrorReceived et à récupérer le message WM_DEVICECHANGE.

Vous ne savez pas pourquoi votre programme plante; vous devriez jeter un coup d'œil à la pile pour voir où cela se produit.

Dans le registre à:
HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM
est la liste actuelle des ports. Si votre port a disparu, cela signifie qu'il était débranché.

Exemple concret: (essayez de supprimer votre clé USB et appuyez sur F5 dans l'éditeur de registre)

Windows Registry Editor Version 5.00
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM]
"Winachsf0"="COM10"
"\\Device\\mxuport0"="COM1"
"\\Device\\Serial2"="COM13"

COM10 - Mon modem fax
COM1 - USB - Convertisseur série moxa usb
COM13 - USB - Convertisseur série Profilic

Cordialement

Bien que les réponses déjà fournies fournissent un bon point de départ, j'aimerais ajouter quelques exemples de travail pour .net 4.5 ainsi qu'un exemple de capture d'un type de périphérique USB.

Dans sa réponse, Treb utilisait le 'Win32_USBControllerDevice' . Cela peut être ou ne pas être la meilleure condition pour votre requête, en fonction de ce que vous voulez accomplir. L'identifiant de périphérique de Win32_USBControllerDevice est unique pour chaque périphérique. Donc, si vous recherchez un identifiant unique identifiant un seul périphérique, c'est exactement ce que vous voulez. Mais si vous recherchez un certain type de périphérique, vous pouvez utiliser 'Win32_PnPEntity' et accéder à la propriété Description . Voici un exemple d'obtention d'un certain type de périphérique par sa description:

using System;
using System.ComponentModel.Composition;
using System.Management;

public class UsbDeviceMonitor
{
    private ManagementEventWatcher plugInWatcher;
    private ManagementEventWatcher unPlugWatcher;
    private const string MyDeviceDescription = @"My Device Description";

    ~UsbDeviceMonitor()
    {
        Dispose();
    }

    public void Dispose()
    {
        if (plugInWatcher != null)
            try
            {
                plugInWatcher.Dispose();
                plugInWatcher = null;
            }
            catch (Exception) { }

        if (unPlugWatcher == null) return;
        try
        {
            unPlugWatcher.Dispose();
            unPlugWatcher = null;
        }
        catch (Exception) { }
    }

    public void Start()
    {
        const string plugInSql = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'";
        const string unpluggedSql = "SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'";

        var scope = new ManagementScope("root\\CIMV2") {Options = {EnablePrivileges = true}};

        var pluggedInQuery = new WqlEventQuery(plugInSql);
        plugInWatcher = new ManagementEventWatcher(scope, pluggedInQuery);
        plugInWatcher.EventArrived += HandlePluggedInEvent;
        plugInWatcher.Start();

        var unPluggedQuery = new WqlEventQuery(unpluggedSql);
        unPlugWatcher = new ManagementEventWatcher(scope, unPluggedQuery);
        unPlugWatcher.EventArrived += HandleUnPluggedEvent;
        unPlugWatcher.Start();
    }

    private void HandleUnPluggedEvent(object sender, EventArrivedEventArgs e)
    {
        var description = GetDeviceDescription(e.NewEvent);
        if (description.Equals(MyDeviceDescription))
            // Take actions here when the device is unplugged
    }

    private void HandlePluggedInEvent(object sender, EventArrivedEventArgs e)
    {
        var description = GetDeviceDescription(e.NewEvent);
        if (description.Equals(MyDeviceDescription))
            // Take actions here when the device is plugged in
    }

    private static string GetDeviceDescription(ManagementBaseObject newEvent)
    {
        var targetInstanceData = newEvent.Properties["TargetInstance"];
        var targetInstanceObject = (ManagementBaseObject) targetInstanceData.Value;
        if (targetInstanceObject == null) return "";

        var description = targetInstanceObject.Properties["Description"].Value.ToString();
        return description;
    }
}

Certains liens pouvant être utiles pour rechercher les classes à utiliser dans vos instructions SQL:

Classes Win32 - In Dans l'exemple ci-dessus, la classe 'Win32_PnPEntity' a été utilisée.

Classes système WMI - Dans l'exemple ci-dessus, les classes __ InstanceCreationEvent et __ InstanceDeletionEvent ont été utilisées.

Vous pouvez essayer de gérer ErrorReceived .

private void buttonStart_Click(object sender, EventArgs e)
{
    port.ErrorReceived += new System.IO.Ports.SerialErrorReceivedEventHandler(port_ErrorReceived);
}

void port_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
{
    // TODO: handle the problem here
}

De plus, vous pouvez vérifier si le port existe avant de continuer. Vous voudrez peut-être vérifier de temps en temps, peut-être juste avant de lire / écrire.

string[] ports = System.IO.Ports.SerialPort.GetPortNames();
if (ports.Contains("COM7:"))
{
    // TODO: Can continue
}
else
{
    // TODO: Cannot, terminate properly
}

Vous devez également placer des blocs try-catch pour toutes vos opérations de port série. Cela devrait aider à prévenir les arrêts imprévus.

Vous pouvez essayer d’exécuter l’application en mode débogage sous votre IDE et simuler l’erreur. Si une exception est lancée, vous pourrez identifier où le problème devient le plus évident. À partir de là, vous pourrez probablement essayer de trouver des solutions plus spécifiques.

Si votre instruction try n'intercepte pas l'exception, espérons que Microsoft inspectera les vidages.

Certaines API SetupDi (je pense… cela fait un moment) vous permettent d'être averti des arrivées et des suppressions de périphérique, mais cela ne vous aidera pas si vous êtes déjà tombé en panne, car le périphérique supprimé se trouvait au milieu de votre opération de lecture ou d’écriture.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top