.net, Raspberry Pi 3

Community feedback – adding diagnostics to the Magellanic.I2C library

I’ve recently been chatting to another engineer who’s using the Raspberry Pi with Windows IoT, and who ran into a few problems with a device I’ve posted about recently. During our conversation to try to identify the issue, it occurred to me that diagnostic information would have been really useful, but there wasn’t an easy way to find that out from my error messages. This felt like an opportunity to fulfil a community need, so I’m going to write about taking that opportunity to make my code better in this post.

All of the code below is in the Magellanic.I2C GitHub repository.

Better device version diagnostics

When I’ve been trying to debug issues with my code on a Raspberry Pi, one of the key pieces of information to know is the version number of the software running on the Pi, and also the details of the hardware platform. This is quite well buried in the Windows 10 IoT Core API, but it’s definitely there. I’ve created a new diagnostics class in my Magellanic.I2C NuGet package, which easily returns information about the operating system, and the hardware and software versions.

The Windows IoT sample for the Default App contains lots of great code to tell you about the device you’re using.

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}";
        }
    }
}

The code below shows how this can be used.

// 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());

Bettter exception handling

Another issue was that my code didn’t have custom exceptions – I had written it to throw a standard System.Exception with a descriptive message when an unexpected scenario occurred (for the reasons detailed here). However, after a few real life exceptions, I know users are expecting to be able to trap different error conditions, so I created some custom exceptions for different exceptional scenarios.

It’s worth noting that I augment the error message with some of the static diagnostic methods from above – this will make it a lot easier to pinpoint issues, especially while the Windows 10 IoT Core framework is still changing rapidly as part of the Windows Insider preview programme.

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()}")
    {
    }
}

More intelligent I2C scanning and better information

In Raspberry Pi communities supporting other operating systems, it’s commonly possible to test if any I2C devices are available on the Pi’s bus, and also what slave addresses allow instructions to be transferred. This is definitely possible with C# on the Raspberry Pi using a slight modification to the standard I2C initialization code, so I’ve added a static method called DetectI2cDevicesAsync() to the I2cDiagnostics class.

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[] { 0x000x00 };
 
        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;
}

Better example code

Finally – for now – I’ve added more complete example code to my project ReadMe.md files on GitHub. Previously I just showed a simple method with how to use the code, but not how to call that method. I’ve adjusted my sensor samples to be more complete descriptions of how to use the code with those sensors.

For example, the code below shows how to call the DetectI2cDevicesAsync() method (without compiler warnings), and write information to the standard output (whether that’s the slave address that responds, or exception information).

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);
        }
    }
}

I hope this extra diagnostic information helps the community use the Magellanic.I2C NuGet package.