Community-Feedback – Hinzufügen von Diagnosen zur Magellanic.I2C-Bibliothek

Community-Feedback – Hinzufügen von Diagnosen zur Magellanic.I2C-Bibliothek

Ich habe mich kürzlich mit einem anderen Ingenieur unterhalten, der den Raspberry Pi mit Windows IoT verwendet und der auf ein paar Probleme mit einem Gerät gestoßen ist, über das ich kürzlich gepostet habe. Während unseres Gesprächs, um das Problem zu identifizieren, kam mir der Gedanke, dass Diagnoseinformationen wirklich nützlich gewesen wären, aber es gab keine einfache Möglichkeit, dies anhand meiner Fehlermeldungen herauszufinden. Dies fühlte sich wie eine Gelegenheit an, ein Bedürfnis der Community zu erfüllen, also werde ich in diesem Beitrag darüber schreiben, wie ich diese Gelegenheit nutzen konnte, um meinen Code zu verbessern.

Der gesamte Code unten befindet sich im Magellanic.I2C GitHub-Repository.

Bessere Geräteversionsdiagnose

Wenn ich versuche, Probleme mit meinem Code auf einem Raspberry Pi zu debuggen, ist eine der wichtigsten Informationen die Versionsnummer der Software, die auf dem Pi läuft, sowie die Details der Hardwareplattform. Dies ist ziemlich gut in der Windows 10 IoT Core-API vergraben, aber es ist definitiv vorhanden. Ich habe in meinem NuGet-Paket Magellanic.I2C eine neue Diagnoseklasse erstellt, die auf einfache Weise Informationen über das Betriebssystem und die Hardware- und Softwareversionen zurückgibt.

public static class I2cDiagnostics
{
    public static string GetDeviceOperatingSystem()
    {
        return new EasClientDeviceInformation().OperatingSystem;
    }
 
    public static string GetDeviceHardwareInformation()
    {
        var device = new EasClientDeviceInformation();
 
        return $"{device.SystemManufacturer}, {device.SystemProductName} ({device.SystemSku})";
    }
 
    public static string GetDeviceOperatingSystemVersion()
    {
        ulong version = 0;
        if (!ulong.TryParse(Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamilyVersion, out version))
        {
            return null;
        }
        else
        {
            var versionComponent1 = (version & 0xFFFF000000000000) >> 48;
            var versionComponent2 = (version & 0x0000FFFF00000000) >> 32;
            var versionComponent3 = (version & 0x00000000FFFF0000) >> 16;
            var versionComponent4 = version & 0x000000000000FFFF;
 
            return $"{versionComponent1}.{versionComponent2}.{versionComponent3}.{versionComponent4}";
        }
    }
}

Der folgende Code zeigt, wie dies verwendet werden kann.

// This gives the hardware type and version of the device, as well as the SKU (stock-keeping unit) information
// e.g. Raspberry Pi, Raspberry Pi 3 (RPi3-1GB)
Debug.WriteLine(I2cDiagnostics.GetDeviceHardwareInformation());
 
// Normally expect this to be Windows!
Debug.WriteLine(I2cDiagnostics.GetDeviceOperatingSystem());
 
// This will be a version number in the format of "10.0.14342.1000"
Debug.WriteLine(I2cDiagnostics.GetDeviceOperatingSystemVersion());

Bessere Ausnahmebehandlung

Ein weiteres Problem war, dass mein Code keine benutzerdefinierten Ausnahmen hatte – ich hatte ihn geschrieben, um eine standardmäßige System.Exception mit einer beschreibenden Meldung auszulösen, wenn ein unerwartetes Szenario auftrat (aus den hier beschriebenen Gründen). Nach einigen Ausnahmen im wirklichen Leben weiß ich jedoch, dass Benutzer erwarten, dass sie verschiedene Fehlerbedingungen abfangen können, also habe ich einige benutzerdefinierte Ausnahmen für verschiedene Ausnahmeszenarien erstellt.

Es ist erwähnenswert, dass ich die Fehlermeldung mit einigen der statischen Diagnosemethoden von oben erweitere – dies wird es viel einfacher machen, Probleme zu lokalisieren, insbesondere während sich das Windows 10 IoT Core-Framework als Teil des Windows Insider-Vorschauprogramms immer noch schnell ändert .

public class I2cDeviceConnectionException : Exception
{
    public I2cDeviceConnectionException(string message) : base($"{message} Device: {GetDeviceHardwareInformation()}, {GetDeviceOperatingSystem()} {GetDeviceOperatingSystemVersion()}")
    {
    }
}
public class I2cDeviceNotFoundException : Exception
{
    public I2cDeviceNotFoundException(string message) : base($"{message} Device: {GetDeviceHardwareInformation()}, {GetDeviceOperatingSystem()} {GetDeviceOperatingSystemVersion()}")
    {
    }
}
public class I2cSlaveAddressInUseException : Exception
{
    public I2cSlaveAddressInUseException(string message) : base($"{message} Device: {GetDeviceHardwareInformation()}, {GetDeviceOperatingSystem()} {GetDeviceOperatingSystemVersion()}")
    {
    }
}

Intelligenteres I2C-Scanning und bessere Informationen

In Raspberry-Pi-Communities, die andere Betriebssysteme unterstützen, ist es allgemein möglich zu testen, ob I2C-Geräte auf dem Bus des Pi verfügbar sind und welche Slave-Adressen die Übertragung von Anweisungen zulassen. Dies ist mit C# auf dem Raspberry Pi definitiv möglich, indem der Standard-I2C-Initialisierungscode geringfügig geändert wird. Daher habe ich der I2cDiagnostics-Klasse eine statische Methode namens DetectI2cDevicesAsync() hinzugefügt.

public async static Task<List<byte>> DetectI2cDevicesAsync()
{
    string advancedQueryString = I2cDevice.GetDeviceSelector();
 
    var deviceInformations = await DeviceInformation.FindAllAsync(advancedQueryString);
 
    if (!deviceInformations.Any())
    {
        throw new I2cDeviceNotFoundException("No I2C controllers are connected.");
    }
 
    var matchingAddresses = new List<byte>();
 
    for (byte i = 0; i < 128; i++)
    {
        var i2cSettings = new I2cConnectionSettings(i);
                
        i2cSettings.BusSpeed = I2cBusSpeed.FastMode;
                
        var i2cDevice = await I2cDevice.FromIdAsync(deviceInformations[0].Id, i2cSettings);
 
        var addressToReadFrom = new byte[] { 0x00, 0x00 };
 
        var result = i2cDevice.ReadPartial(addressToReadFrom);
 
        if (result.Status == I2cTransferStatus.FullTransfer)
        {
            matchingAddresses.Add(i);
        }
    }
 
    if (!matchingAddresses.Any())
    {
        throw new I2cDeviceNotFoundException("No I2C Devices found on the controller.");
    }
 
    return matchingAddresses;
}

Besseres Beispielcode

Schließlich habe ich – vorerst – einen vollständigeren Beispielcode zu meinen Projekt-ReadMe.md-Dateien auf GitHub hinzugefügt. Zuvor habe ich nur eine einfache Methode mit der Verwendung des Codes gezeigt, aber nicht, wie diese Methode aufgerufen wird. Ich habe meine Sensorbeispiele so angepasst, dass sie vollständigere Beschreibungen der Verwendung des Codes mit diesen Sensoren enthalten.

Der folgende Code zeigt beispielsweise, wie die Methode DetectI2cDevicesAsync() (ohne Compiler-Warnungen) aufgerufen und Informationen in die Standardausgabe geschrieben werden (ob das die Slave-Adresse ist, die antwortet, oder Ausnahmeinformationen).

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
 
        Loaded += MainPage_Loaded;
    }
 
    private async void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        try
        {
            var i2cDevices = await I2cDiagnostics.DetectI2cDevicesAsync();
 
            // Writes the first I2C device found to the standard output.
            Debug.WriteLine(i2cDevices[0]);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

Ich hoffe, diese zusätzlichen Diagnoseinformationen helfen der Community bei der Verwendung des NuGet-Pakets Magellanic.I2C.