Using Firebase to control your Arduino project over the web

I have seen the light, and it is Firebase.

Here at Team Sidney Enterprises, we wanted to develop a programmable light display for our house for the holidays — consisting of Arduino-controlled RGB LED strips that can be programmed to display arbitrary patterns (rainbows, sparkles, seizure-inducing strobes, you name it). Check out the final result:

Oooh! Sparkly!

I wanted to make it possible to control the light display not just from my phone, but ideally any web-capable device anywhere in the world. And I wanted to avoid writing a custom native app to control it — a web browser should be all that I need. Finally, I wanted to avoid standing up my own web server or database to manage things — “serverless” is the way to go these days.

Enter Firebase. Firebase makes it super easy to build mobile and web apps that maintain state in the cloud — without running your own servers. And in this case, Firebase’s REST API makes it trivial to access data from embedded devices without a full Android/iOS app stack or web browser. In this article I’ll walk through how it works.

(It’s worth noting that while I work at Google, I don’t have any connection to Firebase — I’m just a fan.)

The hardware

Custom PCBs for connecting the DotStar strip (left) to the Feather HUZZAH32 (right).

For this project, I settled on the following hardware:

  • DotStar LED strips from Adafruit. These are strips of 30, 60, or 144 individually addressable RGB LEDs, and they are amazing, and wicked bright. (One can use NeoPixels as well, but DotStars are less finicky when it comes to the signal timing.) They require four wires: Power, ground, clock, and data, and the LEDs are daisy-chained along the strip. As long as you have enough power and memory, you can control an arbitrary number of LEDs from a single microcontroller.
  • The HUZZAH32 microcontroller board, also from Adafruit. This is an Arduino-compatible board based on the ESP32 chipset, with 520KB SRAM and a 240MHz dual-core LX6 processor. The key thing about this board is that it has built-in WiFi, so there’s no need for separate hardware to connect to the Internet. As I’ll show below, standard libraries let you connect to WiFi networks and initiate HTTP connections — you can even update the device’s firmware over the Internet.
  • I designed a custom PCB to connect the HUZZAH32 to the DotStar LED strip with a four-pin header. This is a passive board and only routes signals through to the correct pins, so you can accomplish the same thing by soldering wires to the HUZZAH32 directly.

This tutorial should be relevant to any web-based IoT or Arduino project, though, since very little is specific to the hardware that I used.

Software overview

Here’s an overview of how the software works. The HUZZAH32 boards connect to my home WiFi network and run a program that periodically “calls home” to the Firebase REST API, using a simple HTTP request. The response is a JSON object that tells the board what its current configuration is — that is, what pattern to show on the lights (as well as parameters such as color, speed, and brightness). The board then drives the DotStar LED strip using this pattern. Easy!

To set the device configuration in Firebase, I wrote a simple web app that uses the Firebase JavaScript API to read and write values from Firebase.

Note that this basic design should work for any Internet-connected device that you want to control over the web, as long as it can issue REST requests over HTTP.

Part One: Setting up Firebase

Firebase has a lot of great documentation covering a wide range of use cases, however, those tutorials are generally more involved than we care about for a simple IoT-style project. So I’ll walk you through a really basic setup here.

To get started, go to https://firebase.google.com and sign in with a Google account, then go to the Firebase Console. You then create a Firebase “project” under which your Firebase settings will live. Call it anything you like — for example, “IoT Demo.”

Firebase’s free “Spark” tier is pretty generous and likely enough for most hobbyist projects; if you have a lot of devices or are going to store a lot of data, you will need to sign up for one of the other plans.

The primary Firebase feature we are going to use is the Realtime Database, which is a cloud-hosted key-value store. In our case, the web app writes data to the database, and the IoT devices read data from the database to configure themselves. (Note that Firebase has a new offering, currently in beta, called the Cloud Firestore, which is pretty similar but uses a slightly different API. For the sake of this tutorial I’ll stick with the original Realtime Database API.)

Adding some data to the database

Once you’ve created your Firebase project, you’ll find yourself in the Firebase Console for this project. On the left sidebar, you’ll see a link to “Database”. There you will find a button inviting you to Create database (and make sure you are creating this as a “Realtime Database”, rather than “Cloud Firestore”, since this tutorial covers the former.)

You’ll now have an empty database. To add data to it, hover your mouse over the root note in the database (e.g., “iot-demo-3a7b9”) and click the + button that pops up there. You can then add a key/value pair. For now, use the key “test” and enter a value such as “Hello Firebase!”

Creating an entry in Firebase’s Realtime Database.

We’ll talk later about how to add values to the database from your own custom web app, but in a pinch, you can always use the Firebase console to do it by hand.

Reading data from your Arduino device

Firebase has APIs for Android, iOS, and JavaScript-enabled web apps. But if your embedded devices are anything like mine, none of those will be options for you. Fortunately, Firebase can also be accessed through a simple REST API from any device that can issue simple HTTP requests to Firebase’s servers.

In the below example, I’ll show how to do this from an Arduino-compatible device (specifically the HUZZAH32, but should work with any Arduino-like board with WiFi).

First, we need to get the device on our home WiFi. Assuming a WiFi network called “ssid” with password “password”, we’d do something like:

#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;
HTTPClient http;
void setup() {
// Fill in your SSID and password below
wifiMulti.addAP(“ssid”, “password”);
}

To read from the Firebase database, we need to issue an HTTP GET request to the URL https://YOUR-PROJECT-NAME.firebaseio.com/KEY.json, where YOUR-PROJECT-NAME is the name of the Firebase project you created above, and KEY is the database key — test in this case. So, we need to access:

https://iot-demo-3a7b9.firebaseio.com/test.json

Keeping in mind that your project name will be different than mine.

From Arduino, the HTTP request code will look like this:

String url = "https://iot-demo-3a7b9.firebaseio.com/test.json";  http.setTimeout(1000);
http.begin(url);
// Issue the HTTP GET request.
int status = http.GET();
if (status <= 0) {
Serial.printf("HTTP error: %s\n",
http.errorToString(status).c_str());
return;
}
// Read the response.
String payload = http.getString();
Serial.println("Got HTTP response:");
Serial.println(payload);

If all goes well, this should print the value stored in the database: "Hello, Firebase!"

We now have the trappings of a basic cloud-controlled IoT app. For now, we can only read a single string value from the database, but what about something more sophisticated? What about reading a different value for each device? And what about reading more than a simple string — what about structured data? Fortunately, both are pretty easy.

Using different database keys for different devices

In many cases, you may want the database values read by one device to differ from that of other devices. The simplest approach is for each device to access data from the database at a key associated with some unique identifier. While there are many ways of doing this, I was looking for a “zero config” option that would prevent me from having to manually assign an identifier to each device on my network.

A good solution in this case is to use the hardware MAC address of the WiFi radio on the board as a unique identifier: these are supposed to be globally unique (although, in practice they may not be!) six-byte IDs associated with the hardware on board the device.

To use this approach, all we have to do is use WiFi.macAddress() in the URL for the REST request, as so:

String url = "https://iot-demo-3a7b9.firebaseio.com/config/" +    
WiFi.macAddress() +".json";
http.setTimeout(1000);
http.begin(url);
int status = http.GET();
// And so on, as above

This will request a URL such as

https://iot-demo-3a7b9.firebaseio.com/config/30:AE:A4:1B:58:A0.json

where the 6 bytes of the URL are unique to each device on the network.

Storing structured data in Firebase

The value stored at a given Firebase database key can actually be a structure containing a mixture of numeric and string fields, which is useful for representing richer data structures. For example, let’s say we wanted each device to have a configuration consisting of a human-readable name, a color (represented as separate red, green, and blue values), and a brightness value. We might organize the data like so:

iot-demo-3a7b9
|
+- config
+
|
+- 30:AE:A4:1B:58:A0
| |
| +- name: "Porch left"
| +- red: 162
| +- green: 0
| +- blue: 255
| +- brightness: 100
|
+- 30:AE:A4:1C:1A:B0
|
+- name: "Porch right"
+- red: 140
+- green: 45
+- blue: 0
+- brightness: 80

You can manually create these nested structures using the Firebase console, again using the + button that pops up when you hover over a database key to add new database entries. (Later I’ll show how to do this in a more elegant way via a web app.)

Reading structured data

Now that we’re storing richer data structures in Firebase, reading it back from an Arduino program is a little more challenging. Accessing the Firebase REST API, you will get back a JSON-encoded string containing the database entry, like so:

{"blue":255,"brightness":100,"green":0,"name":"Porch left","red":162}

It is of course possible to parse this string by hand in C code, but not likely your favorite way to spend an afternoon (especially since the ordering of the keys is not guaranteed!).

An alternate, albeit brute-force, approach is to read each field as a separate JSON request. For example, the URL

https://iot-demo-3a7b9.firebaseio.com/config/30:AE:A4:1B:58:A0/red.json

returns the value 162, and likewise for green.json, blue.json, and so forth. This works, and saves you the trouble of parsing JSON, but requires a lot of round-trip HTTP requests, any one of which might individually fail or time out.

A better approach is to use a library to parse the JSON for you, and the ArduinoJson library is great for this. Here’s some code that reads the values out of the JSON object returned by the REST call:

#include <ArduinoJson.h>
// Allocate a 1024-byte buffer for the JSON document.
StaticJsonDocument<1024> jsonDoc;
void readConfig() {
  String url = "https://iot-demo-3a7b9.firebaseio.com/test.json";        
http.setTimeout(1000);
http.begin(url);
  // Issue the HTTP GET request.
int status = http.GET();
if (status <= 0) {
Serial.printf("HTTP error: %s\n",
http.errorToString(status).c_str());
return;
}
  String payload = http.getString();
  // Parse the JSON response.
DeserializationError err = deserializeJson(jsonDoc, payload);
Serial.print("Deserialize returned: ");
  // Cast the response to a JSON object.
JsonObject jobj = jsonDoc.as<JsonObject>();
  // Read each JSON object field.
int red = jobj["red"];
int green = jobj["green"];
int blue = jobj["blue"];
int brightness = jobj["brightness"];
  char name[64];
memcpy(name, (const char *)jobj["name"], sizeof(name));
}

With this, you can then take the values (red, green, blue, etc.) and use them to control your LED strip. Cool, right?

Here’s the complete Arduino code with all of the bells and whistles: https://github.com/mdwelsh/sidney-projects/tree/master/arduino/Blinky

Using a web app to control your devices

The other great thing about Firebase is that it’s easy to write a web page that can read and write values in the Firebase database. Since this approach does not require you to host your own database or custom server code, it works on any web server that can serve up static pages — no need for PHP and the like. I use GitHub Pages to host my web apps.

To get started, you first need to include the Firebase JavaScript library on your page, and use some boilerplate initialization code. On the Firebase console, click on Develop on the left navbar, and then Web setup in the upper right. This will spit out some code to paste on your web page’s HTML to configure Firebase. In my case, the initialization code looks like this:

<script src="https://www.gstatic.com/firebasejs/5.8.2/firebase.js"></script>
<script>
// Initialize Firebase
// NOTE!!! The below is specific to your Firebase project --
// use "Web Setup" from the Firebase "Develop" pane to get this
// code for your app.
 var config = {
apiKey: "AIzaSyABCaBBY04zpvIl1efmOPrKwNtPkgTXfqs",
authDomain: "team-sidney.firebaseapp.com",
databaseURL: "https://team-sidney.firebaseio.com",
projectId: "team-sidney",
storageBucket: "team-sidney.appspot.com",
messagingSenderId: "395332355872"
};
firebase.initializeApp(config);
</script>

Writing data to a Firebase database entry is easy. First, you get a reference to the entry in the database you want to modify, and then you call the .set() method on that reference object with the data you want to write, like so:

var mac = '30:AE:A4:1B:58:A0';
// Get a reference to the Firebase database entry at the given key.
var dbRef = firebase.database().ref('config/' + mac);
// The config object we want to write.
var config = {
name: 'Device name',
red: 100,
green: 0,
blue: 100,
brightness: 50,
};
// Write the config to the database.
dbRef
.set(config)
.then(function() {
console.log('Success!');
})
.catch(function(error) {
console.log('Error: ' + error.message);
});

That’s it!

You can also read data from Firebase by registering a callback that is invoked whenever a value in the database changes. This can also be applied to a whole set of database entries so you get a callback whenever anything changes. For example:

// Get a database reference to all config/ keys.
dbRef = firebase.database().ref('config/');
// Set callback to be invoked when a child node is added or changes.
dbRef.on('child_added', configChanged, dbErrorCallback);
dbRef.on('child_changed', configChanged, dbErrorCallback);
// Callback invoked when database entry is added or changed.
function configChanged(snapshot) {
var key = snapshot.key;
var newValue = snapshot.val();
console.log('Database entry ' + key + ' changed, new value: ' +
newValue);
}
// Callback invoked on error.
function dbErrorCallback(err) {
console.log('Error reading database: ' + err.message);
}

For a complete example of my light controller web app, check out the code here: https://github.com/mdwelsh/blinky

Security and Authentication

So far, we’ve assumed that the Firebase database you’ve configured can be read from and written to by any device on the Internet that knows the correct URL to use. This isn’t very secure, so of course Firebase has a set of powerful controls to handle user authentication and request authorization.

The full details on setting this up are beyond the scope of this little tutorial, but in brief, you can configure Firebase to limit requests to your database from certain users using a wide range of methods, including email address and password, Google/Facebook/Twitter accounts, and more. You can then configure database access rules that define which users can access which parts of your database under different conditions. To learn more, check out the docs at https://firebase.google.com/docs/database/security/.

Putting it all together

Purdy.

For the holidays, we put several LED strips on the front porch of our house, and even wrapped a few around the Christmas tree, giving me the ability to remotely drive my entire family nuts with a stunning light display at the touch of a button.

I also hacked together a simple Google Assistant app to let me control the lights (“Hey Google, Tell Blinky control to set the Christmas tree to rainbow!”). But that’s a story for another post.

Lemme know in the comments if you have any questions and I’ll do my best to answer. Happy hacking!