IoT Beer Rating

This is really more of a journal post, but if you are interested in any of the sections here, just let me know and I'd be happy to talk further about it.

A co-worker in my day job was working on a demo for a dev-ops process through Visual Studio Team Services and Azure deployments. He needed an application to demonstrate this process. It didn't matter what it was, but I was told that the event would be at a Brewery. With this knowledge, I decided on building a beer rating Internet of Things device.

I have been dabbling in the Internet of Things space for a few months now. I have a Raspberry Pi 2 Model B that drives my 3D printer. I have also been working on an instant replay feature for my day job's foosball table that uses another Raspberry Pi 2 and a USB camera. I am familiar with some of the concepts but I had never really needed to dive deep into any of it. I volunteered to spend my time creating this application and I think it turned out great. This post explains each part and some of the problems I ran into along the way.

Finished Product

Concept

The Columbus Brewing Company will have ten draft beers that the event attendees will be sampling. The goal is to create a device where the attendee can place a beer (or a representation of the beer) on a device and be able to rate that beer from 1 to 5. That rating is then sent to Microsoft Azure so that additional processing and reporting can be done on that. Ultimately, the attendees need to be able to see the results of those ratings on a web dashboard.

Ingredients

Button presses and LEDs

Button LEDs The functionality I was looking for was to have all five LED buttons light up when a beer is placed on the device. Then when the user presses a button, all other buttons will turn off and the rating you selected will stay lit until the beer is removed.

Knowing that I needed 11 inputs (1 for beer arrived/departed, 5 for button presses, 5 for LEDs) just for this functionality, I knew I would not have enough inputs on one board. I had an Arduino Uno lying around so I decided to use this Arduino for the sole purpose of managing the buttons and LEDs.

The process of getting code on the Arduino is extremely simple. I just started up Arduino Studio, looked at some examples, and modified from there. When I wanted to try it out I just needed to connect the Arduino over USB, select the device from a dropdown menu and upload the code to the Arduino.

My setup method defines all of my pins and their input/output mode.

pinMode(button1Pin, INPUT_PULLUP);
pinMode(button2Pin, INPUT_PULLUP);
pinMode(button3Pin, INPUT_PULLUP);
pinMode(button4Pin, INPUT_PULLUP);
pinMode(button5Pin, INPUT_PULLUP);

pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
pinMode(led3Pin, OUTPUT);
pinMode(led4Pin, OUTPUT);
pinMode(led5Pin, OUTPUT);

// Pin that the Pi tells the Arduino that a beer has arrived or departed
pinMode(A5, INPUT);

The buttons is INPUT_PULLUP instead of INPUT so that I don't need to wire up my own resistor to the buttons. This greatly simplifies my setup.

The pin that tells the Arduino about a beer is using an analog pin because I ran out of digital pins to use. Luckily, the Arduino supports having an analog pin function as a digital pin so my code can be simpler and not have to manage on my own if it was truly HIGH or LOW.

My main loop really just processes if any of my buttons have been pressed or I got a beer.

void loop()
{
  processActiveBeer();

  if (haveActiveBeer) {
    button1PreviousState = processButton(button1Pin, led1Pin, button1PreviousState);
    button2PreviousState = processButton(button2Pin, led2Pin, button2PreviousState);
    button3PreviousState = processButton(button3Pin, led3Pin, button3PreviousState);
    button4PreviousState = processButton(button4Pin, led4Pin, button4PreviousState);
    button5PreviousState = processButton(button5Pin, led5Pin, button5PreviousState);
  }
}

Where the processActiveBeer is as simple as checking if the pin changed and setting a helper bool.

void processActiveBeer() {
  int currentBeerReceived = digitalRead(A5);
  if (currentBeerReceived != previousBeerReceived) {
    // Turn on or off button LEDs if we have a beer active or not
    // But only if it just changed.
    setAllLeds(currentBeerReceived);
  }

  haveActiveBeer = currentBeerReceived == HIGH;
  previousBeerReceived = currentBeerReceived;
}

Setting the LEDs is a simple reusable method.

void setAllLeds(int ledState) {
  digitalWrite(led1Pin, ledState);
  digitalWrite(led2Pin, ledState);
  digitalWrite(led3Pin, ledState);
  digitalWrite(led4Pin, ledState);
  digitalWrite(led5Pin, ledState);
}

Checking for button presses is more complex.

int processButton(int buttonPin, int ledPin, int previousButtonState) {
  int currentButtonState = digitalRead(buttonPin);

  if (currentButtonState == HIGH && previousButtonState == LOW && millis() - timeLastPressed > debounce) {
    timeLastPressed = millis();

    // Turn off all LEDs then turn on only the button that was pressed.
    setAllLeds(LOW);
    digitalWrite(ledPin, HIGH);

    broadcastButtonPress(buttonPin);
  }

  resetBroadcastStates();
  return currentButtonState;
}

The processButton code has a lot going on. It reads the pin to see if it's HIGH or LOW and really only does anything with that value if it is different from the last loop iteration and only if it hasn't changed for a given amount of time (the debounce time). Debounce prevents you from getting strange results that can happen from the small amount of time that the button is in the pressed state. You just ignore any button state changes for the amount of time specified, which in my case is 500 ms

The broadcast methods referenced here are about communication back to the Raspberry Pi to tell it what rating was pressed. Then the Pi handles that input as described in the communication section below.

Detecting a Beer

To me, this was the most fun. Getting NFC to work can have a magical feel similar to Hanselman's LED moment. This step is what turned a real world object into something that can drive my application.

This was far more difficult to get working than I expected. Step 1 is installing Windows 10 IoT Core on the Raspberry Pi 2. This has gotten pretty simple now with the Windows 10 IoT Core Dashboard application. You simply connect the Pi's SD card to your development PC and run the dashboard application. Through the app, follow the "Setup a new device" process. This will put the current build of Windows 10 IoT Core on the SD card. Now you can put the SD card into the Pi. You can now power up the Pi.

The next step is getting the NFC reader working under Windows 10. The reason I spent about $50 on an NFC reader is because this reader is the only NFC reader listed on the Windows 10 IoT Core hardware compatibility list. Even though it is compatible, it still requires drivers. Living in a modern OS world where most things "just work" when you plug them in, we kind of forget that proper drivers are essential for things to "just work" and that when they are not in place you can feel pretty helpless. NXP has drivers and instructions for how to get up and running with Raspberry Pi 2 and Windows 10. The first step in that process is to get the files.

I wish it were just as simple as getting the files from NXP as instructed on their site, but If you are using build 10586 or somewhere close to that you probably need an updated driver. After I installed the supplied driver, the device manager was reporting errors. After much research, I found that this is a known problem and there is an updated driver that is only available on a support forum post. If you already tried installing the other driver, you will need to start over from a new Windows 10 IoT Core build. I was not able to fix this problem after putting the bad driver on the Pi. Anyway, just use the updated driver, which you can find here:

https://social.msdn.microsoft.com/Forums/en-US/d9b2e4df-cb64-4f50-a9e3-9fa2d83be14c/nxp-om5577-nfc-board-driver-broken-on-10586?forum=WindowsIoT

Now that you have the files, you need to copy the drivers onto the SD card. You can copy these files to the Pi by navigating to the pi's C drive. //minwinpc/C$. Navigate to the System directory as instructed and copy the two files indicated. Next, you need to understand how to connect to the Raspberry Pi through a remote PowerShell session. I was not familiar with this process and there were many steps missing from the NXP documentation.

Here are the instructions you will need to follow from NXP:
http://www.nxp.com/documents/application_note/AN11767.pdf

If you're following along, that was a big step, so congratulations on getting this far. It gets easier from here. The next step is to test the NFC reader using the test application that NXP provided. You can install this through the Windows 10 Device Portal on the Pi. You can get to this portal from the Windows 10 IoT Core Dashboard application in the "My Devices" section. If your Pi is on the same network as your development PC, you will see the Pi listed here. Use the device portal link and sign in with the Raspberry Pi credentials. You can find the default username and password in your Raspberry Pi documentation or on the official site.

Once you are connected, you can change settings or install applications. Install the test app using the fields on the Apps page. You will see that this adds the app to the running app on the Pi. If you are using an HDMI output for the pi, you can now test the NFC reader by placing one of your NFC tags on the reader and see that the text changes on the test page. You can also use an NFC compatible smart phone if you don't have a tag handy.

NFC Shield and Housing

Pi and Arduino Communication

The Arduino is expecting input from the Pi so it knows that a beer has arrived and when it has been removed so that it knows to light up all the buttons or not. This can be accomplished with one GPIO. The LOW will mean there is no beer to rate. The HIGH will mean there is a beer ready to rate.

Communicating with a GPIO in UWP app is pretty simple. You first need a reference to "Windows IoT Extensions for the UWP" and make this reference.

using Windows.Devices.Gpio;

Create a few instance variables

private readonly GpioController _gpio;
private readonly GpioPin _notifyOfActiveBeerPin;;

Then, you can write this code to initialize the pin.

_gpio = GpioController.GetDefault();

if (_gpio != null)
{
    _notifyOfActiveBeerPin = _gpio.OpenPin(16);
    _notifyOfActiveBeerPin.SetDriveMode(GpioPinDriveMode.Output);
}

I opened up pin 16 and indicated that this is an output pin, meaning I'm going to be writing to that pin and not reading it.

Then in my proximity device events I set up earlier, I can just write to that pin.

private void BeerDeparted(ProximityDevice sender)
{
    _notifyOfActiveBeerPin?.Write(GpioPinValue.Low);
}

private void BeerArrived(ProximityDevice sender)
{
    _notifyOfActiveBeerPin?.Write(GpioPinValue.High);
}

Next, we need to initialize the pins that the Arduino will write to when the user presses a rating from one to five. The pins for each rating are more of the same code with one catch. So now my instance variables look like this:

private readonly GpioController _gpio;
private readonly GpioPin _notifyOfActiveBeerPin;
private readonly GpioPin _rateOnePin;
private readonly GpioPin _rateTwoPin;
private readonly GpioPin _rateThreePin;
private readonly GpioPin _rateFourPin;
private readonly GpioPin _rateFivePin;

and my constructor looks like this:

_gpio = GpioController.GetDefault();

if (_gpio != null)
{
    _rateOnePin = _gpio.OpenPin(5);
    _rateOnePin.ValueChanged += RateOneReceived;

    _rateTwoPin = _gpio.OpenPin(6);
    _rateTwoPin.ValueChanged += RateTwoReceived;

    _rateThreePin = _gpio.OpenPin(13);
    _rateThreePin.ValueChanged += RateThreeReceived;

    _rateFourPin = _gpio.OpenPin(12);
    _rateFourPin.ValueChanged += RateFourReceived;

    _rateFivePin = _gpio.OpenPin(26);
    _rateFivePin.ValueChanged += RateFiveReceived;

    _notifyOfActiveBeerPin = _gpio.OpenPin(16);
    _notifyOfActiveBeerPin.SetDriveMode(GpioPinDriveMode.Output);
}

Then my rating events are just making sure the pin is HIGH, meaning it was pressed. Since this is a changed event, it will fire twice. Once for HIGH and once for LOW.

private void RateOneReceived(GpioPin sender, GpioPinValueChangedEventArgs args)
{
    if (args.Edge == GpioPinEdge.RisingEdge)
    {
        RateBeer(1);
    }
}

and for completion you can see the simple and defensive code for rating the beer.

private void RateBeer(int rating)
{
    if (_activeBeerId == 0 || rating < 1 || rating > 5)
        return;

    var ranking = new BeerRanking
    {
        BeerId = _activeBeerId,
        Stars = rating
    };

    try
    {
        SendToAzure(ranking);
    }
    catch (Exception)
    {
        // Do not ever show a failure or stop execution
    }
}

An important note here that cost me about six hours of debugging: I have each of the pins as instance variables even though the rating pins are never referenced in the code outside of the constructor. The pins are only opened to add the event handler to it. If you do not assign these pins to an instance variable, .NET will dispose of those pins after you receive your first event trigger. This behavior is not something you would ever see in any standard .NET app but for some reason in Windows 10 IoT Core running a UWP app, it will dispose of those.

3D Printing

I purchased my Monoprice Maker Select a few months ago and I've been printing trinkets ever since. This was my first opportunity to design and print something more functional. I needed a housing for the buttons and an intuitive place to put a beer on so the users know how to use it. I also needed some sort of bridge that could hold the NFC reader right to the top of the housing so that the NFC reader did not have a far distance from the tag.

I designed the housing and device holder in Autodesk's 123D Design. It is definitely not a precise tool but it is quick and it can get your designs in a "good enough" state. After some measurements of the buttons and some test prints for the button sizes, I finished designing the housing and device holder. I printed the housing with Black 1.75mm PLA with just a 2mm shell. The print took about 11 hours with a .2mm layer height.

Printing the Housing

The housing was designed pretty quickly because I had a tight deadline to complete this project. I did not anticipate the size of the Pi after the Power cable is plugged in and the USB cable from the Arduino to the Pi is plugged in. Because of this, I had to drill a few holes in my nice print so that cables could be routed. They were pretty hidden so I did not feel the need to start from scratch.

I also needed to print a beer representation because I was told the beers we would be rating would be from a keg. I did not want to NFC tag each glass, so I decided to design and print 10 oversized bottlecaps so that I could put the tags on those. These were quick 45 minute prints. They came out great. Then I used my wife's Cricut to cut out vinyl stickers with the beer names so they would look more professional.

Bottlecaps Printing

Putting it all together

All that was left was to put everything together. I installed the five buttons into the housing and tightened them down. I screwed the Pi with the NFC reader onto the top of the device holder. I screwed the Arduino underneath the Pi. I taped the breadboard down to the bottom of the device holder and wired it all up. I needed to consolidate on my wires quite a bit because of the space issues in the housing. I ended up daisy-chaining all of my grounds from the buttons (10 in total) and routed them all to 1 ground wire so that I could limit the clutter of solid core wires.

Finally, I could flip this thing over and test it out. In the device portal for Windows 10 IoT Core I made sure my beer rating app was set to be the start up project and I had WIFI configured, then I was able to set up the device with just a power cable needed. I could simply plug it in and the app would start right up.

Seeing it in action

The day of the event went great. The device was a big success. I demonstrated that you simply place a bottlecap on the device and the LED buttons light up indicating that the beer is ready to rate. You select 1 - 5 for your rating according to the buttons and that LED stays lit. You can then remove the bottlecap and rate your next beer, or leave it on for the next person to rate. These rating would then be sent to an ASP.NET Web API project hosted in Azure that would then be saved to SQL Azure.

On the tables of the brewery, I had several devices running an HTML/CSS/JS website that received realtime updates about beer ratings and would update the ratings on the page.

Rating Web App

This project took about 50 hours to complete. I wanted to do this project because I wanted to be in a situation where I was forced to spend the time to learn these technologies and how they can interact with hardware. This was a great learning experience and I'm already planning on other hardware related projects I can tackle with this knowledge.