GrovePi, Raspberry Pi, and the Universal Windows Platform: better together

Blinky on Steroids

This article introduces a C# and XAML UWP app that has the ambition to make you feel comfortable with Windows IoT. We’ll make a LED blink, measure the temperature, detect motion, and do some other internet-of-things like that. This means that you need extra hardware to fully enjoy it: a Raspberry Pi computer running Windows 10 and connected to a monitor, a GrovePi board, and of some Grove sensors from a typical starter kit.

The Universal Windows Platform app that is presented here, allows you to execute tests against the GrovePi board that is hooked to the Raspberry Pi computer, but -more important- against a range of sensors and actuators that are plugged into that board. The app also contains a project that hooks sensors and C# code together into a burglar alert.

The list of supported sensors includes:

    • LED (a.k.a. Blinky)
    • Button 
    • Light Sensor
    • Passive Infrared Motion Sensor
    • Rotary Angle Sensor (knob) 
    • Temperature Sensor
    • Vibration Motor
    • LED Bar

Here’s a picture of the app and some of the hardware in action:

blinky

That picture brings me to the following quiz question: “How many Windows 10 devices does it take to make a picture of a blinking LED?

In case, the answer was 3:

  • The app was written on a Windows 10 development machine, and then
  • deployed to a Windows 10 Raspberry Pi, and finally
  • the picture was taken with a Windows 10 Lumia 950.

Getting started with Windows 10 IoT

In February 2015 Microsoft announced that Windows 10 was coming to the Raspberry Pi. Very soon, Windows 10 Internet-of-Things kits started to appear. Most of these looked (and still look) like this:

startkit

I can image that for a lot of software developers (the ones that are NOT writing device drivers) such a starter kit is out of their comfort zone, since it’s way too close to the bare metal. If you’re one of these developers: don’t worry. There are also IoT starter kits that look like this:

GrovePi for Raspberry Pi and Grove Sensors

What you see here is not only a Raspberry Pi computer, but also

  • a GrovePi board that you can connect to the Raspberry Pi and that exposes ports in which you can plug
  • a series of ready-for-use sensors and actuators,
  • all in pure Lego-style (well … almost: the picture does not show the connector cables).

Call it modular, call it service-oriented, call it component-based … it is clear that this approach is a lot closer to a developer’s eco-system. When you get your hands on such a kit, you may be more tempted to give it a try.

Configuring the hardware

It’s not the intention of this article to explain how to install Windows 10 on a Raspbery Pi and how to configure your development PC. Microsoft has excellent documentation on this right here.

In theory the GrovePi board itself does not need extra configuration, since it comes with its own built-in firmware. In practice, it is a relatively recent and fast evolving system, and some of the starter kits come with older versions of it. In order to support all sensors, you may need to upgrade the firmware from time to time. This is done from the Raspberry Pi. It can not be done when the Pi is running Windows 10, so you need to prepare an extra SD Card with Raspbian for Robots (which also contains an adapted version of the Scratch programming language, for your kids). There’s good documentation on all of this, and if you get stuck, the GrovePi forum moderators are always there to help you.

Connecting the sensors

The GrovePi board comes with a series of ports in which you can plug the sensors.

warningAlthough all ports look the same, it’s important to know that not every sensor will work on any port.

There are GrovePi-controlled analog as well as digital ports, and there are also serial ports and I2C ports that connect directly to the Raspberry Pi. All port characteristics are described right here.

warningIt’s also important to know that not every Grove sensor is already supported by the GrovePi board.

The company –Dexter Industries- produces more than 100 sensors, but they also produce several robots with exactly the same port layout. Not every sensor works on every robot. Here’s the official list of sensors that are currently supported by the GrovePi – that’s still more than 50.

The test bench app that I wrote supports a lot less sensors. It is limited by the sensors for which there is already GrovePi and C# support and that are available in this starter kit.

Before you deploy and run the app, you must connect the following sensors to the appropriate port:

  • LED in digital port 5
  • Temperature sensor in analog port 2
  • Light sensor in analog port 1
  • Rotary encoder in analog port 0
  • Push button in digital port 3
  • LEB Bar digital port 4
  • Motion sensor in digital port 2
  • Vibration motor in digital port 6
  • Infrared emitter in digital port 7
  • Infrared receiver in digital port 8

By the way, it’s best to connect the sensors before firing up the Raspberry Pi. Those stories saying that the Pi reboots when you plug a LED in it … are true.

Your board should look like this:

board

Talking C# to the GrovePi board

The pioneering library that allows you to communicate from C# with the GrovePi board lives here on GitHub. It contains the source code for the GrovePi for Windows IoT Nuget package:

GrovePiNuget

There are some forks available at Dexter Industries, Seeed Studio, and Exadon. These repositories are all extended with sample projects in C# and other languages, so they’re definitely worth a visit.

Recently, the NuGet package started to give some errors:

GrovePiNugetIssue

As a work around, I decided to uninstall the package, then add its core dll to the project and refer directly to it:

GrovePiNugetWorkaround

Alternatively, you could clone one of the source repositories and add the project to your Visual Studio solution.

Building the app

The sample app is a regular XAML-based UWP app and not a UI-less background task. As you see on the above screenshot, the project is MVVM-oriented. The Windows 10 IoT stack indeed supports the basic XAML and C# features that we’re used to on the PC, the tablet and the phone. If Notifications would work, I could even have reused the whole template that I currently use for this series of blog posts. As a matter of fact: most of the sample apps that I wrote this year, ran on the Raspberry Pi without any modification.

The GrovePi Test Bench

The main page of the sample app displays the list of connected sensors in a GridView. It allows you to start a test against each of them – simultaneously if you want. Each test takes about a minute. During the test, the sensor has a red border around it in the UI:

blinkyview

All sensors are represented by a ViewModel class that inherits from this one:

SensorBase

Here’s its code:

/// <summary>
/// Base class for testable GrovePi sensors.
/// </summary>
internal class SensorBase : ViewModelBase
{
    private string name;
    private string port;
    private string state;
    private bool isUnterTest;
    private string testDescription;

    public string Name
    {
        get { return name; }
        set { SetProperty(ref name, value); }
    }

    public string Port
    {
        get { return port; }
        set { SetProperty(ref port, value); }
    }

    public string State
    {
        get { return state; }
        set { SetProperty(ref state, value); }
    }

    public bool IsUnderTest
    {
        get { return isUnterTest; }
        set { SetProperty(ref isUnterTest, value); }
    }

    public string ImagePath { get; set; }

    public string TestDescription
    {
        get { return testDescription; }
        set { SetProperty(ref testDescription, value); }
    }

#pragma warning disable CS1998 
// Async method lacks 'await' operators and will run synchronously
    public virtual async Task Test()
    { }
#pragma warning restore CS1998

    public ICommand TestCommand
    {
        get { return new DelegateCommand(Test_Executed, Test_CanExecute); }
    }

    private bool Test_CanExecute()
    {
        return !IsUnderTest;
    }

    private async void Test_Executed()
    {
        await RunTheTest();
    }

    private async Task RunTheTest()
    {
        IsUnderTest = true;
        try
        {
            await Test();
        }
        catch (Exception ex)
        {
            State = ex.Message;
            Log.Error(this.Name + " - " + ex.Message);
        }

        IsUnderTest = false;
    }
}

When the app is started, we first check the board itself and retrieve its firmware version with some calls against the API in the GrovePi dll:

// Check the board.
var board = DeviceFactory.Build.GrovePi();
if (board == null)
{
    Message = "Sorry, your GrovePi board could not be detected.";
}
else
{
    try
    {
        Message = string.Format("Your GrovePi board is ready. 
	(Firmware version {0})", board.GetFirmwareVersion());
    }
    catch (Exception)
    {
        Message = "Your Grove board is ready.";
    }
}

Of course, every sensor has its own test. Here’s how to make the LED blink for a minute:

/// <summary>
/// Blinky.
/// </summary>
public override async Task Test()
{
    var blinky = DeviceFactory.Build.Led(Pin.DigitalPin5);
    if (blinky == null)
    {
        State = "Failed to intialize.";
        return;
    }

    for (int i = 0; i < 30; i++)
    {
        try
        {
            blinky.ChangeState(SensorStatus.On);
            State = "On";
        }
        catch (Exception ex)
        {
            Log.Error(this.Name + " - " + ex.Message);
        }

        await Task.Delay(TimeSpan.FromSeconds(1));

        try
        {
            blinky.ChangeState(SensorStatus.Off);
            State = "Off";
        }
        catch (Exception ex)
        {
            Log.Error(this.Name + " - " + ex.Message);
        }

        await Task.Delay(TimeSpan.FromSeconds(1));
    }

    State = String.Empty;
}

I’m pretty sure that you frowned a couple of times when looking at the amount of Try-Catch blocks and when looking at the content of these Catch blocks. Basically all exceptions are ignored in a true On-Error-Resume-Next style.

Allow me to defend this approach: the hardware that runs the app is pretty young and pretty inexpensive (it costs between 1% and 2% of a standard development laptop).

warningThe communications between the Raspberry Pi, the GrovePi, and the sensors fail very often, so you must wrap any API call in a Try block.

Writing a sensor test

The LED is a digital output sensor: you can programmatically turn it On of Off. All digital output sensors can be tested with exactly the same code. The test for the Vibration Motor (the buzzer in your cell phone) is identical to the previous code snippet.

Let’s take a look at the opposite: an analog input sensor. Here’s the code to read the value of  the light intensity sensor twice per second, during a minute:

public override async Task Test()
{
    var sensor = DeviceFactory.Build.LightSensor(Pin.AnalogPin1);
    if (sensor == null)
    {
        State = "Failed to intialize.";
        return;
    }

    for (int i = 0; i < 120; i++)
    {
        try
        {
            State = sensor.SensorValue().ToString();
        }
        catch (Exception ex)
        {
            Log.Error(this.Name + " - " + ex.Message);
        }

        await Task.Delay(TimeSpan.FromSeconds(.5));
    }

    State = String.Empty;
}

Again, most of the code is defensive exception handling, and again, this code is reusable for similar sensors like the temperature sensor and the rotary encoder.

While reading an analog value, you may want to apply some math to it. For the temperature sensor you probably want to translate the incoming value (which is between 0 and 1023) into your own unit measure. By default the temperature sensor API translates the raw input to the Metric system: you can directly read the value in °Celsius from the API. If you want to get the temperature in °Fahrenheit, just add an extension method.

A lot of currently ‘unsupported’ sensors can be easily read in the same way. It suffices to find out the algorithm to translate the value, by looking at the specs of the sensor, by reading sample code from another language, or by asking it in the forum. Here’s an example: a thread for the air quality sensor, leading from a forum question, to the specs and sample code.

Here’s an overview of the different sensor types and their relevant API calls to the GrovePi NuGet package:

Type Direction Examples GrovePi API calls
Analog Input Light sensor, temperature sensor, rotation angle sensor SensorValue()
Digital Input Button, motion sensor CurrentState
Analog Output LedBar, infrared emitter ?
Digital Output Led, vibration motor ChangeState(SensorStatus)

If the sensor API is not sufficient, then you can go one level deeper and call the GrovePi class directly. It comes with methods to specify the direction of a port (input or output) an then read or write to it, digital or analog:

IGrovePi

If you want to create your own implementation for a currently unsupported sensor, just create a viewmodel and hook it in the tree:

SensorViewModels

Also don’t forget to add the sensor in AddSensors() of MainPageViewModel or it won’t show up in the list.

[At the time of writing this article I’m still working on the tests against the Infrared receiver and the Infrared transmitter, since these are actually not yet officially GrovePi supported.]

Building a Burglar Alert

Now that all sensors are connected to the Raspberry Pi and testable, why not make a little IoT project with them?

The sample app contains a Burglar Alert page that reuses some of the viewmodels and data templates from the main page. It uses the motion sensor, the LED, and the buzzer. Here’s how it looks like:

burglarview

The process is always in one of the following states:

enum BurglarAlertState
{
    Sleeping,
    Active,
    MotionDetected,
    Alerting
}

When the alert is Active, we monitor the motion sensor. When motion was detected, we note the time, and upgrade the state to MotionDetected. The LED will start blinking. If there’s no motion anymore after 10 seconds, we consider it a false alert and go back to Active. If there is still motion however, we enter Alerting mode and the buzzer will vibrate until we set it to sleep.

Here’s the main process loop. It is executed twice per second as long as the timer ticks, i.e. as long as we’re not in Sleeping mode:

/// <summary>
/// Main process loop.
/// </summary>
private void Timer_Tick(object sender, object e)
{
    try
    {
        switch (state)
        {
            case BurglarAlertState.Sleeping:
                break;
            case BurglarAlertState.Active:
                if (irSensor.Sensor.CurrentState == 
	GrovePi.Sensors.SensorStatus.On)
                {
                    State = BurglarAlertState.MotionDetected;
                }
                break;
            case BurglarAlertState.MotionDetected:
                // Toggle LED
                ToggleLed();

                Log.Info("Motion detected " + 
	(DateTime.Now - motionDetection).Seconds.ToString());
                var delay = (DateTime.Now - motionDetection).Seconds;
                Info = string.Format("Motion detected ({0})", 10 - delay);

                if (delay > 10)
                {
                    if (irSensor.Sensor.CurrentState == 
	GrovePi.Sensors.SensorStatus.On)
                    {
                        State = BurglarAlertState.Alerting;
                    }
                    else
                    {
                        State = BurglarAlertState.Active;
                    }
                }
                break;
            case BurglarAlertState.Alerting:
                // Toggle LED
                ToggleLed();
                break;
            default:
                break;
        }
    }
    catch (Exception ex)
    {
        Log.Error(ex.Message);
    }
}

For the sake of completion, here’ s what happens when the state is changed:

public BurglarAlertState State
{
    get { return state; }
    set
    {
        blinky.Sensor.ChangeState(
	GrovePi.Sensors.SensorStatus.Off);
        buzzer.Sensor.ChangeState(
	GrovePi.Sensors.SensorStatus.Off);

        SetProperty(ref state, value);

        Info = State.ToString();

        switch (state)
        {
            case BurglarAlertState.Sleeping:
                timer.Stop();
                break;
            case BurglarAlertState.Active:
                timer.Start();
                break;
            case BurglarAlertState.MotionDetected:
                motionDetection = DateTime.Now;
                break;
            case BurglarAlertState.Alerting:
                try
                {
                    buzzer.Sensor.ChangeState(
	GrovePi.Sensors.SensorStatus.On);
                }
                catch (Exception ex)
                {
                    Log.Error(ex.Message);
                }
                break;
            default:
                break;
        }
    }
}

Here’s the Burglar Alert app and its sensors in action:

burglar

There are more projects in the making …

Source Code

The project lives here on GitHub.

Enjoy!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s