Over-the-Air Arduino firmware updates using Firebase, Part 1

We don’t need no stinking cables!

I mean, one cable is usually enough, but you get the idea.

In my last post, I talked about how you can use Google Firebase to control an Internet-connect Arduino project without running any servers at all — all of the data is hosted by Firebase. In this article, I’ll talk about how you can use Firebase to enable over-the-air updating of your Arduino firmware, allowing your devices to magically download new firmware and reflash themselves without plugging them into a laptop. This is a great party trick and is sure to impress all your friends. (Well, if your friends are anything like mine.)

Here’s the idea. Your Arduino code periodically calls home to Firebase over the Internet and asks it whether the firmware version it is currently running is the version it should be running. If it is, there’s nothing more to do (except re-poll sometime later). If not, it downloads a new firmware image, stores it to local flash, and once the download is complete (and verified) reboots into the new program. Voilà!

In this series of articles, I’m going to walk you through how I do this in my projects. There are no doubt other (and possibly better) ways to achieve the same result, but I’m pretty happy with it and hope others find this useful.

In this article, I’ll talk about the basic setup for using Firebase to host Arduino firmware images, and some sample code for over-the-air updates.

In Part 2, I’ll talk about supporting multiple firmware versions and automatically detecting when a firmware update is needed.

Prerequisites

I’m assuming that you’re using an Arduino device with some kind of Internet connectivity (WiFi, Ethernet cable, carrier pigeon, whatever). I use the HUZZAH32 Feather development board from Adafruit, which has built-in WiFi.

You need some library to flash a new firmware image onto the device and reboot into it. This is going to vary by device. The HUZZAH32 (based on the Espressif ESP32 chipset) has a nifty Arduino library that does just this. The library provides an interface where you write a stream of bytes representing the new firmware image. It takes care of staging the new firmware on the device’s flash, and computing an MD5 checksum to verify that the write has completed successfully.

Finally, you need to set up Firebase for your Arduino project. Check out my other tutorial on using Firebase for controlling Arduino devices for this — you just need to follow the steps under “Setting up Firebase”.

Uploading a firmware image to Firebase

First, we need a way to upload a new firmware binary to Firebase, so it can be fetched by your Arduino devices over the Internet.

One of Firebase’s key features is Firebase Cloud Storage, which lets you store arbitrary binary blobs which can be retrieved over the Internet using a simple HTTP request.

A manual way to upload a new binary to Firebase is to visit the Firebase console, click on “Storage” in the left nav bar, and then click on the “Upload file” button:

Using the Firebase console to upload files to Firebase Cloud Storage.

In this case, you want to upload the .ino.bin file that is generated by Arduino when you compile your sketch — the location of this is going to depend on your Arduino IDE setup. To simplify finding it, I recommend editing your Arduino preferences.txt file and setting the build.path parameter to a directory that is easy to find, so you don’t need to go hunting around your filesystem for the binary. For example,

build.path=/Users/mdw/arduino_builds

will store all compiled Arduino sketches to the directory /Users/mdw/arduino_builds.

Over-the-air firmware updates

Say you have a sketch called AwesomeProject.ino and have uploaded the binary to Firebase as AwesomeProject.ino.bin. Now you need your Arduino devices to pull down the new binary from Firebase and reboot into it.

First, we need to get the URL for the binary that we uploaded above. In the Firebase console, when you click on a file in the Cloud Storage list, look for the “Download URL” in the box that pops up to the right.

Getting the download URL for a file in Firebase Cloud Storage.

This is is going to give you a URL that looks something like this:

https://firebasestorage.googleapis.com/v0/b/your-project.appspot.com/o/AwesomeProject.ino.bin?alt=media&token=836b2515-2000-473f-a5ff-bf9c38b958c9

If you want, you can try downloading it by pasting the URL into your browser and confirming that the correct file is retrieved.

All we have to do is have our Arduino code pull the contents of this URL down over the Internet and use it to flash the firmware. On ESP32-based hardware like the Feather HUZZAH32, this code would look like the following:

/* Note that this code is specific to ESP32-based boards like
* the Feather HUZZAH32. Some modifications may be required
* for different boards and support libraries.
*/
#define FIRMWARE_URL \
"https://firebasestorage.googleapis.com/v0/b/your-project.appspot.com/o/AwesomeProject.ino.bin?alt=media&token=836b2515-2000-473f-a5ff-bf9c38b958c9"
#include "Update.h"
#include "WiFi.h"
#include "HTTPClient.h"
void updateFirmware() {
  // Start pulling down the firmware binary.
http.begin(FIRMWARE_URL);
int httpCode = http.GET();
if (httpCode <= 0) {
Serial.printf("HTTP failed, error: %s\n",
http.errorToString(httpCode).c_str());
return;
}
  // Check that we have enough space for the new binary.
int contentLen = http.getSize();
Serial.printf("Content-Length: %d\n", contentLen);
bool canBegin = Update.begin(contentLen);
if (!canBegin) {
Serial.println("Not enough space to begin OTA");
return;
}
  // Write the HTTP stream to the Update library.
WiFiClient* client = http.getStreamPtr();
size_t written = Update.writeStream(*client);
Serial.printf("OTA: %d/%d bytes written.\n", written, contentLen);
  if (written != contentLen) {
Serial.println("Wrote partial binary. Giving up.");
return;
}
  if (!Update.end()) {
Serial.println("Error from Update.end(): " +
String(Update.getError()));
return;
}
  if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
// This line is specific to the ESP32 platform:
ESP.restart();
} else {
Serial.println("Error from Update.isFinished(): " +
String(Update.getError()));
return;
}
}

Summary

In this first installment, I’ve shown you how to manually upload an Arduino binary to Firebase, and how to write some Arduino code to pull that file down and do an over-the-air update.

In Part 2, I’ll talk about how to support multiple firmware versions, as well as how to modify your Arduino code so it can automatically detect that it needs to perform an update.

Let me know in the comments if you have any questions or suggestions!