Skip to main content

Command Palette

Search for a command to run...

From ESP32 side projects to a reusable device platform

Published
9 min read
From ESP32 side projects to a reusable device platform
A
"Good enough" isn't always good enough. If a thing is worth doing, it's worth doing well, right now! Everything I do, I strive for excellence. Alan Tai is a technology advocate, proficient in software architecture, web and mobile application development, covering a wide-ranging knowledge domains including FinTech, AI, Telcom, and social media. He has extensive experience in delivering project at different scales of complexity, and is most enthusiastic about building ideas from the grounds-up into full-scale value-generating operations. As a champion of cloud infrastructures and DevOps, he is also recognised and certified as a Professional Cloud Architect by Google, a Certified Developer by AWS, and a Certified Azure Administrator by Microsoft.

Why did the second ESP32 project feel harder than the first?

I didn’t set out to build a device platform.

I just wanted a smart thermostat that worked the way I wanted it to. Then a door sensor. Then a mailbox detector. All ESP32-based. All simple. All were supposed to be “quick weekend projects”.

Except they never stayed quick.

The timer-based open-door alerting device with a PIR sensor and buzzer

By the second project, I noticed something uncomfortable: I was spending more time rebuilding everything around the device than working on the device itself. Provisioning flows, telemetry schemas, backend APIs, admin UIs, MQTT wiring - over and over again, slightly differently each time.

The hardware changed. The sensors changed. But the surrounding system barely did.

That was the moment I realised this wasn’t really about ESP32 anymore.

This article explains what happens when repeated side projects quietly turn into a platform, and what you learn when you decide to embrace that instead of fighting it.

(You don’t need to care about ESP32 to get value from this - but if you’ve ever built the same thing three times and wondered why, this story is for you.)

The moment I stopped treating these as one-off projects

At that point, the obvious reaction would have been to “clean things up” or “refactor later”.

But experience has taught me that repetition is rarely a code smell; it’s usually a design signal.

When the same patterns keep reappearing across projects, it’s not because you haven’t abstracted enough yet. It’s because the system boundary hasn’t been made explicit.

So instead of asking how I can implement this faster next time, I started asking a more architectural question:

What part of this problem is stable, and what part is genuinely application-specific?

That question reframed everything.

Sensors became interchangeable. Business logic became thin. And the real complexity - device identity, lifecycle, communication, and control - moved into focus as a system of its own.

The first moment this shift became concrete wasn’t in a design document or a diagram. It happened while building something very ordinary.

A smart thermostat.

The mains-powered ESP32-based smart thermostat mounted on the wall

The smart thermostat that exposed the platform

The smart thermostat itself was unremarkable.

A fleet of ESP32-C3 boards, SHT20 sensors, a latching relay, and a bit of MicroPython. It read the room temperature, reported it periodically, and allowed a remote setpoint change. Nothing novel. I’d built versions of this before.

What was different was how little of the code was actually about temperature.

Most of my time went into things that had nothing to do with heating: registering the device, pushing configuration, validating parameters, storing telemetry, exposing controls in the UI, handling reconnects, and making sure I could tell whether the device was alive.

Halfway through, I realised something slightly embarrassing: if I removed the sensor and relay, 80% of the system would still exist. That was the moment the abstraction boundary snapped into focus.

One of the battery-powered ESP32-based temperature and humidity sensor device fleets

The thermostat wasn’t “an IoT app”. It was just one expression of a much more stable system: a device that declares capabilities, reports state, accepts commands, and is managed remotely.

Once I accepted that framing, the design became calmer. Instead of asking what this device does, I asked:

  • What does every device need, regardless of purpose?

  • What decisions should the device make locally?

  • What responsibilities belong to the central system, not the firmware?

The answers were consistent across projects. The smart thermostat simply made them impossible to ignore. And once you see that boundary clearly, it becomes very hard to unsee it.

In the next project - a door sensor - that boundary held. In the one after that - a mailbox detector - it held again.

That’s when I stopped treating this as a collection of ESP32 applications and started treating it as a device platform with multiple personalities.

The internals of one of the battery-powered ESP32-based temperature and humidity sensor device fleets

Drawing the line between devices, backend, and UI

Once the device stopped being “the centre of the system”, the rest of the architecture became easier to reason about.

I stopped thinking in terms of features and started thinking in terms of responsibilities.

The ESP32 had one clear job: interact with the physical world and communicate its state. Everything else - identity, history, coordination, and visibility - belonged somewhere else.

That decision immediately shaped the backend. The backend wasn’t an “API for devices”. It was a source of truth for the system. Devices registered themselves, declared what they could do, and reported telemetry. The backend stored that information, validated it, and exposed it in a way that was stable - even as individual devices came and went.

Once that boundary was clear, the frontend almost designed itself.

The UI didn’t need to know what a thermostat is. It needed to know what a device can do. If a device reported a temperature, the UI showed a number. If it is relative humidity, the UI rendered a percentage. The same screens worked for all three projects - not because they were generic, but because the model underneath was.

The reusable device management portal showing the registered devices

This separation wasn’t about microservices or frameworks. It was about making change cheap in the right places.

I could rewrite the device firmware without touching the UI. I could change UI flows without reflashing devices. And I could add a new type of ESP32 project without redesigning the system.

At that point, I knew I’d crossed an important line: this was no longer an application architecture. It was a platform architecture.

And platforms, whether you intend them or not, impose opinions.

The next decision was whether to embrace that - and be explicit about those opinions - or pretend the system was more flexible than it really was.

That choice led directly to why I deliberately avoided existing frameworks like Home Assistant and ESPHome.

Choosing opinions to improve developer experience

Once I accepted that I was building a platform, the next question was unavoidable: what should this platform have strong opinions about?

In my experience, developer experience doesn’t improve by removing constraints. It improves by removing decisions.

So instead of trying to support every possible device, protocol, or workflow, I made a few deliberate choices and committed to them:

  • ESP32-C3 only

  • MicroPython only

  • MQTT only

  • A small, predictable device model

  • A narrow set of workflows

Those constraints weren’t limitations - they were load-bearing walls.

The reusable device management portal showing the collected telemetry

Because the rules were clear, the system could do more on behalf of the developer. Device provisioning stopped being a custom script. Telemetry stopped being a bespoke schema. Remote actions stopped being one-off endpoints. The “right way” was no longer something to document; it was simply the default path.

The result was that new projects became boring to start in the best possible way. I wasn’t thinking about infrastructure anymore. I was thinking about behaviour.

That shift - from “how do I wire this up” to “what should this device do” - was the real productivity gain.

Why not Home Assistant or ESPHome

At this point, it’s reasonable to ask why I didn’t use Home Assistant or ESPHome. They are mature, widely adopted, and solve many problems well.

The short answer is: they optimise for a different centre of gravity.

Home Assistant is exceptional at integrating heterogeneous consumer devices into a cohesive smart home. ESPHome excels at declaratively describing device behaviour and flashing firmware without writing much code.

My use cases were different.

I needed:

  • a backend I could evolve as an application, not configure as a system;

  • a UI that was part of the product, not an add-on; and

  • devices that participated in workflows I defined, not ones inferred from YAML.

Most importantly, I wanted the device management layer to be reusable across projects, not the device logic itself. That inversion matters. In my setup, adding a new device type doesn’t require teaching the system what the device is. It only needs to know what the device can do.

Home Assistant and ESPHome make excellent sense when your primary problem is integration. They were less suited to a scenario where the system itself is the thing being built.

This wasn’t a rejection of those tools. It was an acknowledgement that architectural fit matters more than the number of features.

By embracing a narrower, opinionated scope, the platform became simpler to reason about, easier to extend, and - crucially - harder to misuse.

And that’s often the quiet goal of good platform design: not to enable everything, but to make the right thing the easiest thing.

The Raspberry Pi based temperature control of the smart thermostat system

Knowing when you’re no longer building an app

Most platforms don’t start as platforms. They start as solutions.

You build something to solve a concrete problem. Then you solve a second problem that looks suspiciously similar. By the third time, you’re no longer moving faster - you’re just repeating yourself with better tools.

That’s usually the signal.

Not that you need more abstraction, or a new framework, or a grand rewrite. But that the unit of value has shifted. The thing worth investing in is no longer the application itself, but the system that keeps appearing around it.

For me, that realisation didn’t come from diagrams or architecture reviews. It came from boredom. From noticing that the most predictable parts of each project were also the most time-consuming. From seeing that the “interesting” logic was consistently the smallest part of the codebase.

Recognising that shift matters, because platforms come with different responsibilities. You stop optimising for features and start optimising for stability. You stop chasing flexibility and start choosing constraints carefully. You think less about what this project needs, and more about what the next five will demand.

There’s also a subtle mindset change. When you’re building an app, friction is acceptable - it’s often temporary. When you’re building a platform, friction compounds. Every awkward decision gets paid repeatedly, by you or by others.

That’s why the most important decision I made wasn’t a technical one. It was simply to name the thing for what it had become.

Once I did that, the rest followed naturally: clearer boundaries, stronger opinions, fewer features, better defaults.

If there’s one takeaway from this work, it’s this: when your “side projects” keep converging on the same shape, it’s worth asking whether you’re still building applications - or quietly assembling a platform.

That question, asked early enough, can save an enormous amount of effort later.

And sometimes, it leads you somewhere more interesting than the original project ever intended.

References

  1. The reusable device management framework mentioned in this article: https://github.com/ayltai/espark

  2. The single project that supports three different ESP32 projects: https://github.com/ayltai/Espartan

More from this blog

Making things happen, making things right, because it matters - Alan Tai's blog

19 posts

Is "good" good enough? Not always. If a thing is worth doing, it's worth doing well, right now! I am a technology advocate, proficient in all aspects of software engineering.

From ESP32 side projects to a reusable device platform