← machine room

I made my solar monitor less clever and it finally worked

I have a 48 V solar battery bank, five packs of it, and I wanted to know their voltages without walking over and squinting at each one. That’s the whole job. Read five numbers, show five numbers.

Maybe some people would do that with a $5 board and an HTTP call. Not me. I am clever. And being clever is how I spent a very sincere amount of my life reversing a Bluetooth Low Energy protocol.

Act one: the clever monster

My first monitor was an ESP32 that wirelessly decrypted the battery-sense BLE advertisements in real time. That’s not a joke — the battery packs broadcast encrypted telemetry, so the device had to run AES-CTR decryption with the right key, deal with counter endianness, and survive the fact that the vendor’s protocol is not something they ship a spec for.

It was brilliant when it worked. It worked brilliantly right up until the moment the thing that makes a system fragile, did. Vendored protocol. Reverse- engineered bytes. A whole build balanced on “I decoded the format correctly, and it will never change.” That’s not a monitor; that’s a hostage situation with a screen.

Act two: the boring one

So I deleted the BLE decryption. I deleted the ESP32. I rebuilt the whole thing on what I should’ve used from the start: a Raspberry Pi Pico — a board so cheap it’s basically a rounding error — and a 2.8” LCD.

And here’s the entire architecture, in one boring sentence:

The Pico asks the home-automation server over WiFi for the five pack voltages, then draws five gauges on the LCD.

No BLE. No AES. No protocol archaeology. Just an HTTP GET my server already handles for a dozen other things. The data was already there — the actual smart thing was refusing to reinvent the layer that lived underneath it.

What the thing actually does now

Here it is, living its best life on a wall:

SolarLCD — a Pico and LCD showing five battery voltage gauges

It boots from a wall USB plug, streams a logo to the screen (with no framebuffer, because the Pico’s RAM is a thimble — you pick one of the two framebuffers, you don’t get both), then settles into a quiet loop:

  • Five gauges, one per pack, spanning 48→55 V, color-coded — green when healthy, red when a pack’s drifting low, and an empty frame with N/A if a pack goes offline (so you can tell “low” from “gone,” which are very different problems).
  • A bottom row with the connection status, an uptime counter, and the charger state — bulk / absorption / float — that mirrors the charger’s own LED in color. You can see the whole charge cycle’s mood at a glance.

And the two details I’m genuinely proud of:

  1. The uptime counter is a moving number, not a clock. No timezone logic, no NTP, no UTC-vs-local heartbreak. A number that ticks up once a minute is proof of life that doesn’t care what timezone you’re in. Frozen number = wedged device. It’s the cheapest liveness check a human ever designed.

  2. It self-heals by rebooting. If WiFi dies and won’t come back, the thing hard-resets and boot-loops until the network returns. Some grad student would call that inelegant. I call it “it can never wedge silently.” A device that hangs quietly in a corner is worthless; a device that resets until it works is a good boy.

The grumpy-admin moral

The first version was an engineering showpiece. It decrypted a protocol nobody told me how to decrypt. And it carried the failure modes of that cleverness around like luggage.

The second version is an HTTP call and a $0.70 microcontroller. It’s still been up and correct for weeks, and it’ll keep being up because nothing about it is fragile.

Clever is fragile. Boring is durable. When a boring solution does the whole job, don’t bring a crypto-reverse-engineering project to the fight — spend the cleverness on something that actually needs it, and let the solar gauges just sit there and be right.

Also: if you ever catch yourself reading “AES-CTR counter endianness” and thinking “this is the load-bearing detail of my weekend project,” stop. Go buy a Pico. Ask the server. Go touch grass.

The gauges have never lied. That’s the whole point of the blog.