I must confess, I am not a big “smart home” guy. I have read my fair share of terrible, horrible, no-good, very bad news about consumer IoT products bricking themselves and/or pwning their human caretakers. I use the term “caretakers” because you don’t actually own most of these products. If the manufacturer can push over-the-air firmware updates to your IoT device, and they don’t give you the choice to decline those updates, then you don’t actually own the device. They own it.

And you know what? I’m sorry.

I write firmware - not the shitty kind that helps shitty companies do shitty things, but I’m still in this industry. I certainly haven’t done much to encourage my counterparts to take the high road. I also can’t really blame the folks who are writing all that terrible code - there’s a lot of money in selling out.

Smart Home Taxonomy

Smart home products can generally be sorted into two categories: “hubs,” and “gadgets.” Gadgets do the “smart” stuff, and hubs make sure the gadgets can talk to the outside world, and to each other.

You can also organize smart home products based on their dystopian qualities. This grouping is somewhat more subjective, so I made a diagram to explain:

A diagram resembling the “political compass” meme. The Y-axis ranges from “sinister” to “ethical,” and the X-axis ranges from “gadget” to “hub.”

Although there are a handful of seemingly-ethical smart home gadget companies, most of them use closed-source hardware and software that is difficult to vet independently. ESP-Home is the only exception I have found. Their products look to be elegant and highly hackable, but their hardware presents some limitations that I’ll get into later.

Shitty Gadgets

The only “smart home” product I own is an air purifier that my fiancee bought after getting influenced on TikTok. I hate that stupid thing. It uses those stupid not-a-button touch controls, it doesn’t support 5GHz Wi-Fi, and for some godforsaken reason it needs an internet connection for the timer to work. My router spits out PTP packets, there’s absolutely no reason why a cloud service is necessary here. The fact that anyone puts up with this nonsense is mind-boggling.

The thing is, I want the things promised by a “smart” home. I want to be able to monitor power usage of the various appliances in my home. I want to control those appliances remotely. I want a robust home security system. I want to tell HAL to open the pod bay door. I want these things to be renter-friendly, durable, and secure.

My industry experience tells me that my desires are within the technical limits of existing IoT technology. Unfortunately, the existing ecosystem would require me to choose one of the following options:

  1. Have some FAANG company pilfer thousands of dollars worth of PII and behavioral data from me, and pay them for the privilege.
  2. Same thing as #1, except it’s a no-name Chinese company with a dubious privacy policy (or no policy at all).
  3. Do it Golden Corral-style, cobbling together my smart home using bits and pieces from dozens of small vendors, and pray that none of those vendors are doing anything shady behind my back.

There must be a better way.

I want a flexible, scalable, hackable smart gadget platform with good ethics. That doesn’t really seem to exist. So you know what? I’ll just make my own!

Designing the Platform

I’m writing this post after putting in a few dozen hours of work on the thing. So, the rest of this post will be in the past tense.

With a blank slate and no exact goals in mind, the obvious place to start was with ESP32. These Chinese SoCs are very popular among hobbyists, and for good reason: they can do simultaneous Bluetooth and Wi-Fi, and they’re dirt cheap. A basic ESP32 module with a built-in antenna can be bought for less than $2, and a full-fledged development kit is less than $10.

My thought process was simple: “Besides the wireless functionality, the ESP32 is a totally normal microcontroller, so I’ll just strap everything to that!” I bought a couple of ESP32 dev kits (two ESP32-C3, and an ESP32-S2), and started fiddling with them…

…and I quickly realized this wasn’t going to work. I had never used an ESP32 before setting out on this adventure. As it turns out, there are some good reasons why they’re so cheap.

First of all, there’s the blobs. The entire Bluetooth/Wi-Fi stack is shipped as a binary blob. You load it onto the board separately from your bootloader/firmware, and pray that it does what it says it does. Besides making debugging annoying, the combination of “inexplicably cheap” and “weird binary blobs” doesn’t sit well with me.

Then, there’s the limited resources. By the time I loaded it with Bluetooth and Wi-Fi drivers, enabled some logs, and flipped on some drivers for I2C or SPI peripherals, I was already running out of RAM. Sure, there are higher-end ESP32 chips than what I’m using, but the ESP32 series offers a pretty narrow range of speed/RAM/storage profiles. This isn’t a deal-breaker, but it’s also not a great sign for scalability.

The deal-breaker was ESP32’s lack of modern features. As far as I can tell, they don’t support WPA3. Without access to the Wi-Fi stack’s source code, there’s nothing I can do to change that. Almost none of the ESP32 SoCs support 5GHz Wi-Fi - only 2.4GHz is well-supported.

You can make an acceptable IoT product with an ESP32 at the center of your hardware’s universe, it’s just not going to be an excellent product. Earlier I mentioned that ESP-Home has some limitations; this is what I was talking about. As the name implies, ESP-Home hardware is centered around the ESP32. Thus, it inherits ESP32’s low cost and ease of use, but it also inherits ESP32’s technical shortcomings.

Back to the Drawing Board

Hardware-wise, Bluetooth and Wi-Fi are the toughest requirements to fulfill. There are tons of options out there that support Bluetooth: Nordic’s nRF5 series, Silicon Labs’ EFR32 series, and STMicro’s STM32WB series all come to mind. These are all very good multiprotocol 2.4GHz SoCs, but Wi-Fi isn’t one of the supported protocols. That means I would need an external Wi-Fi modem, and a second 2.4GHz antenna specifically for Wi-Fi. I really, really want to avoid having two 2.4GHz antennas, because interference patterns are black magic and I’m not an RF engineer.

Besides the ESP32, there just aren’t a whole lot of fully integrated Bluetooth/Wi-FI SoCs out there. Plenty of companies have promised that one is coming out soon, but I have yet to see them for sale. (update 1/1/2024: You can finally find Silicon Labs’ SiWG917 Wi-Fi/BT SoCs on DigiKey, but they’re only available in bulk, and the dev kits are insanely expensive).

So, I’m left with one option: take a normal microcontroller and strap a combination Bluetooth/Wi-Fi controller to it. As it turns out, this is actually a pretty elegant solution! By eliminating the need for built-in wireless, I can choose from virtually any microcontroller under the sun.

It also opens up lots of options for wireless controller chips. To me, the most intriguing one is Infineon’s CYW4373. It boasts Bluetooth 5.4, 2.4GHz and 5GHz Wi-Fi, and WPA3 support. FCC/CE certified modules with built-in antennas are readily available on Digi-Key, and BT/Wi-Fi driver code is easy to find.

Strap that Infineon chip to a garden-variety STM32 microcontroller, and we have a working hardware platform:

Diagram of the prototype hardware architecture

This architecture splits up duties between the CYW4373 and an everyday microcontroller. The CYW4373 will handle the Bluetooth and Wi-Fi responsibilities, and an STM32 microcontroller will house all the business logic and I/O. There are thousands of microcontrollers that could fulfill this role, but I like the STM32 series. STM32 includes a vast spread of hardware options, and the DIY/open source community surrounding them is enormous. As an added bonus, STMicro has some of the best documentation in the industry.

Great! The hardware is totally, 100% figured out. Nothing can possibly go wrong.

Now I had to choose a firmware platform. Nowadays, the obvious place to start is with the Zephyr OS. It’s actively maintained, regularly updated, scalable, and highly versatile. It’s the only firmware development platform I’ve ever used where it’s easy to run the same firmware across totally disparate hardware architectures. It comes out-of-the-box with extensive driver support for the STM32 microcontroller series. Zephyr also shares my affinity for security and open-source. This choice was very easy.

Money Troubles

At this point, I felt I had a solid hardware platform figured out. So, I hopped on DigiKey to pick up a CYW4373 development kit, and…

Screenshot from DigiKey’s website, showing the price of a CYW4373 development kit.

Yikes. After taxes and shipping, I’d be shelling out almost $80 for this thing. That’s a hefty chunk of change for a project that I’ve barely even started.

Let’s tweak that diagram a bit…

Revised diagram of the hardware architecture, with CYW4374 scribbled out and replaced with ESP32-C3.

I begrudgingly decided to keep the ESP32 around, for now. It doesn’t fulfill my requirements for this project, but it’s close enough for prototyping purposes. Here’s the thing… how the heck do I move the wireless stuff back and forth from the ESP32 to the STM32?

As it turned out, Espressif (ESP32’s manufacturer) has a solution: ESP-AT. Yes, that’s “AT” like your dial-up modem from the 1990s. AT is still in widespread use today for GSM and satellite modems, among other things, but in this context, it’s being used for Bluetooth and Wi-Fi.

ESP-AT is a hideous mountain of partially-configurable binary blobs. However, with very little effort on my part, it turned my ESP32-C3 into a combination Bluetooth+Wi-Fi modem that I can control using an external microcontroller. I haven’t really disposed of the ESP32, but at least I’ve relegated it to a corner where it can easily be replaced in the future.

Drawing the Rest of the Owl

Here’s the joke, for the uninitiated:

A diagram showing how to draw an owl in two steps. The first step is simple, only two circles. The second step says “draw the rest of the owl” and shows an extremely detailed drawing of an owl.

The next step was simple! Just assemble some hardware, open and merge a pull request into Zephyr, write a Bluetooth driver, then test and debug everything. Easy peasy. Here’s what I have on my desk right now:

Picture of my desk, showing a large STM32 development board attached by jumper wires to a several smaller circuit boards.

There’s a lot going on in that picture, so here’s a quick breakdown:

  • The big white board in the background is an ST Nucleo Cortex-M7 development kit. This 216MHz monster is far more powerful than I’m ever going to need, which makes it a great choice for this early prototyping stage.
  • The tiny guy on the breadboard is a W25Q128 NOR FLASH chip, providing an extra 16MB of Flash memory.
  • The little black board resting on its side is an ESP32-C3 development kit, running ESP-AT.
  • The little blue board in the bottom left corner of the image is an SD card slot, because why not?

There were several unexpected problems along the way. Here’s a quick breakdown of the most interesting issues:

Board Support

Tons of STM32 development boards are supported by the standard Zephyr distribution, but I am not a lucky person. So naturally, my favorite STM32 board, the Nucleo F722ZE, wasn’t one of the supported boards. So, I opened a pull request to the Zephyr project to add support for it.

This was my first time contributing to the Zephyr project, and it was a great learning experience. If you want to learn Zephyr the hard way, adding support for a new board is a great way to get comfortable with Kconfig and DeviceTree.

ESP-AT Driver Support

Zephyr ships with a driver for ESP-AT, but it only supports Wi-Fi. This driver is remarkably simple, so it wasn’t terribly difficult to shoehorn some rudimentary Bluetooth support into the works.

Unlike the board support code, I will not be trying to get this code merged into the main Zephyr repository. First of all, it’s hideous and probably buggy. Second of all, it only implements the bare minimum Bluetooth features that I need for my specific use case - it is not a general solution whatsoever.

QSPI Flash Snags

That W25Q128 chip is a Quad-SPI (QSPI) NOR Flash memory. The datasheet’s summary says that it can run at up to 133MHz, but this isn’t actually true for several important commands. If you dig a little deeper into the datasheet, you’ll see that many commands must run at 50 MHz. Since Zephyr doesn’t let you toggle between clock frequencies on the fly, I am running all commands at 50 MHz. Using frequencies higher than that caused the bootloader to explode. Why? The bootloader validates all its partitions before running the application. Naturally, when the flash chip is returning a clock-induced garbled mess instead of a standardized magic number, bad things happen.

What it Does (so far)

I threw together a simple RPC interface on top of the Bluetooth GATT protocol, which provides most of the functionality I’ve implemented so far. Right now, I can:

  1. Connect to the board via Bluetooth.
  2. Tell the modem to scan for Wi-Fi networks, and report back the networks it finds.
  3. Connect to a Wi-Fi network. IP addresses are automatically assigned via DHCP.
  4. Read back the current connection status.

The bootloader and main application image are stored on internal flash memory. The NOR Flash chip stores the bootloader’s scratch partition, as well as persistent firmware settings. There’s still a healthy amount of space for other stuff.

The SD card doesn’t do much right now, I just check to see if there’s a FAT-formatted card available at boot. I’ll probably remove the SD card code until I find a good purpose for it.

So, now what?

I have a functional prototype hardware platform that has working Bluetooth and Wi-Fi, plus the necessary memory and processing power to do basically anything I could think of.

… Now, what the heck should I do with this thing?