Cloud Integration & Alerts: The Dedicated Firebase Bridge
Saurab ThakurIn the previous articles detailing the Dual-ESP32 UPS architecture, we’ve focused heavily on local reliability. The Brawn node handles the physical reality of voltages and relays, while the Brain node manages local logic, pinging, and the local Astro dashboard.
But a modern smart home system feels incomplete without remote access. If I am at the office, I want to know instantly if my house lost power, how long the batteries will last, and when the servers have safely shut down.
To achieve this, we need to push data to the cloud. I chose Google Firebase Realtime Database because it offers instant websocket-based sync to frontend web apps and mobile apps.
However, adding cloud integration introduced the biggest technical hurdle of the entire project: TLS Memory Fragmentation.
The Danger of TLS on Microcontrollers
Communicating with modern cloud APIs (like Google Firebase, AWS IoT, or Azure) requires secure, encrypted connections using TLS/SSL (HTTPS).
On a desktop computer, opening an SSL socket is trivial. On an ESP32 with limited RAM, it is an incredibly heavy operation. The TLS handshake requires memory for cryptographic algorithms, large certificate chains, and secure buffers.
When I initially integrated the Firebase SDK directly into the Brain node (ESP32-S3), I ran into severe issues. The Brain was already using its RAM to host an asynchronous web server, parse large JSON payloads from the Brawn node, and ping the network. When the Firebase SDK fired up to push data to Google, it requested a massive chunk of contiguous RAM to negotiate the TLS handshake. Over hours of operation, as strings were created and destroyed, the ESP32’s heap became fragmented. Eventually, the TLS handshake couldn’t find a large enough block of memory, causing a heap allocation failure and crashing the Brain node entirely.
A UPS controller that crashes because it’s trying to update a cloud database is fundamentally broken.
The Solution: The Polling Bridge Pattern
To solve this, I completely removed the Firebase SDK from the Brain node. The Brain node returned to being a purely local powerhouse, unburdened by the complexities of the outside internet.
I introduced the third microcontroller in our architecture: the Firebase Sync Node (ESP32).
This dedicated node has only one purpose: act as a secure bridge between the local network and Google’s servers. Since it doesn’t run a web server or parse heavy serial data, it has massive amounts of free RAM, meaning it can maintain persistent, heavy TLS sockets with Firebase effortlessly without fragmenting its memory.
How the Bridge Works
The architecture relies on a local polling mechanism:
- The Brain Node’s API: The Brain node exposes a simple, unauthenticated local HTTP endpoint (
http://<brain_ip>/api/sync). When queried, it spits out a lightweight, minified JSON object containing the current state of every relay, voltage, and system timer. - The Sync Node’s Loop: Every 5 seconds, the Firebase Sync node fires a fast, local, unencrypted HTTP
GETrequest to the Brain node. - The Push to Cloud: The Sync node takes that JSON payload and pushes it up to the Firebase Realtime Database over its persistent, encrypted TLS connection.
This segregation of duties creates a perfect firewall. If the internet goes down, the Sync node might throw connection errors and loop indefinitely trying to reach Google. Meanwhile, the Brain node is completely unaffected, unaware, and continues to manage the battery logic and local dashboard flawlessly.
Push Notifications via Cloudflare Workers
Data logging is great, but proactive alerts are better. I needed the system to push notifications directly to my Android phone when the power grid failed.
Using Firebase Cloud Messaging (FCM) to send push notifications requires authenticating with a Google Service Account JSON key. This key is massive and requires OAuth2 token generation—a task far too heavy for an ESP32.
To solve this, I built a serverless pipeline:
- The Trigger: When the Brain node detects a power loss, it flags
alert: "grid_down"in its local API payload. - The Relay: The Firebase Sync node reads this during its next 5-second poll. Noticing the alert flag, it fires a lightweight HTTP request to a custom Cloudflare Worker URL.
- The Serverless Execution: The Cloudflare Worker (running on the edge, for free) holds my secret Google Service Account keys in its encrypted environment variables. It rapidly generates an OAuth2 token, constructs an FCM payload, and hits the Google Firebase API.
- The Delivery: The Google API routes the message directly to my custom Android app, waking up my phone with an immediate notification: “⚠️ Grid Power Lost. Running on Network Battery.”
The Beauty of Segregation
By isolating the cloud connectivity into its own dedicated hardware node, we achieved the holy grail of IoT design: a cloud-connected system that is not cloud-dependent.
If Google’s servers go down, the Sync node hangs, but the UPS functions normally. If my fiber ISP goes down, the Sync node hangs, but I can still access the Brain’s local Astro dashboard over Wi-Fi. If the Wi-Fi router crashes, the Brain node loses local UI access, but the Brawn node (connected via hardware serial) continues to seamlessly switch battery relays.
In the final part of this series, we’ll look at an interactive schematic mapping out exactly how these nodes, optocouplers, and batteries are physically wired together!