•
10 min read

My Automation Was On. It Just Never Did Anything.

tanksync home-automation mqtt homebridge homelab
Table of contents

Two Tata Power switches and a TankSync hub

I have two Tata Power EZ Home smart switches wired to water pumps at home, and a TankSync hub watching the roof tank they fill. Both of those matter for this story, so a proper introduction before I get to the part that broke.

The Tata Power switches are the pump control side. I wrote about reverse-engineering them a couple of days before this: no cloud, no login, no authentication of any kind, just a UDP beacon and a TCP command port sitting wide open on the LAN. That work turned into homebridge-ogemray, so both pumps show up in HomeKit as ordinary switches.

TankSync is the sensor side, and it deserves more than a passing mention. It’s a solar powered tank monitor made by SmartGhar, an Indian company, and it’s genuinely one of the better pieces of IoT hardware I own. A small ESP32 hub sits inside the house with a round display and an LED ring, talking LoRa to a solar and battery powered ultrasonic sensor mounted on the tank itself, no wiring across the roof needed. Firmware is AGPL, hardware is CC BY-SA, and the hub works fully offline, it keeps showing levels and alarming on overflow with the internet, the ISP, and any cloud entirely gone. Their own tagline is “own your home, without owning a cloud,” and for once that isn’t just marketing copy, it’s actually how the thing is built.

That’s a rare thing to find right now, especially from an Indian hardware company. A lot of what gets sold here as “smart,” the Tata Power switches from the last post included, means a permanent phone-home to somebody’s cloud and a privacy policy nobody’s read. TankSync ships with a proper Home Assistant integration and native MQTT, no account required anywhere, and it publishes the same data locally whether or not you ever open their app.

The hardware itself is good, just not fully polished yet. The enclosure I have is clearly 3D printed rather than injection molded, the print lines are visible if you look closely, and it’s noticeably rougher than the firmware running inside it. None of that affects how it actually works, the electronics and the LoRa range have both held up fine, but it’s the one place where a genuinely well engineered piece of open hardware still reads like a small team’s first production run rather than a finished consumer product.

What TankSync doesn’t ship with is anything for HomeKit or Homebridge. I don’t run Home Assistant, I run HomeKit for everything, so on paper that’s a dead end.

Except TankSync still hands you a plain MQTT connection, credentials and all, whether or not you use their Home Assistant integration. That’s not a HomeKit integration, but it’s a door.

HomeKit didn’t get an official integration. It got an MQTT socket and an evening.

So earlier the same day as everything below, I had Claude Code stand up a small Mosquitto broker, point TankSync at it, and wire a Homebridge mqttthing accessory to read the tank level out of it. The one wrinkle worth mentioning: the Home app has no generic 0-100% sensor type, so the tank shows up as a light sensor, reporting the tank percentage as lux, with a deliberate +1 added on the way in. Without that offset, an “above 100” automation can never fire, because the raw value never exceeds exactly 100. A full tank reports 101 lux instead. Petty, but it’s the only way to get a real threshold automation out of the Home app’s fixed presets.

With that in place I had exactly what I wanted: two pumps and a tank level, all inside HomeKit, none of it touching anyone’s cloud. I built a Home app automation on top of it, below 20% turns the pumps on, at 100% turns them off, and moved on with my day.

Homebridge’s tank sensor had silently stopped updating

A while later the tank was reading under 15% and the pumps weren’t running. I opened Claude Code and asked it why, and told it to walk the whole chain rather than guess. It came back with the answer: TankSync’s own API said the hub was fine, actively reporting. Mosquitto had the same numbers, actively publishing. Homebridge was the one that had quietly stopped listening, its sensor frozen on a value from two hours earlier, no error, no disconnect message, just stuck. It restarted Homebridge, and I could see the tank level updating correctly in the Home app again.

Looked solved. It wasn’t.

The automation had never sent a pump a single command

The pumps still weren’t running, and the sensor fix had nothing to do with it. I had Claude Code check the pumps directly, listening for the beacon they broadcast every second regardless of who’s asking, and both relays were off. Homebridge’s own logs settled it: no switch command had ever been sent, not before the fix, not after. The automation, sitting right there in the Home app, enabled and pointed at the right accessories, had never fired once.

An automation that’s enabled, correctly configured, and has simply never fired looks exactly like one that’s working. Right up until you go check.

Why not is genuinely something I can’t answer from a terminal. Home app automations live in iCloud and on whatever’s acting as a home hub, with no API to inspect from the homelab side. Missing hub, misconfigured trigger, something that just never got finished, could be any of it.

Rebuilding the automation as its own service

Rather than debug a system I can’t see into, I asked Claude Code to move the decision logic somewhere I can see it: a small Python service running directly on the homelab. Homebridge kept its job, showing the tank, letting me flip a pump by hand from my phone, but it stopped owning any automation. The service subscribes to the same MQTT feed TankSync already publishes, and drives both pumps together since they fill the same tank, below 20% on, at the top off.

The first version worked immediately, correctly turned both pumps on the moment I tested it against a genuinely low tank. Then I looked at how it was getting its readings and didn’t love the answer.

Switching from polling the hub to subscribing over MQTT

It was polling TankSync’s HTTP endpoint every 60 seconds. When I asked Claude Code why, given the whole point of the Mosquitto broker was to push updates, its honest answer was that it hadn’t been a deliberate choice, just the endpoint it had already confirmed worked while diagnosing the sensor. Fair enough as a diagnostic step, not a good reason to build a service around.

Shipping something that works isn’t the same as having a reason it works that way.

So I had it rebuild around the actual MQTT subscription, with its own broker login rather than reusing Homebridge’s. Push instead of poll, no thirty second lag on a decision that’s meant to stop water overflowing, and one less recurring request hitting a hub that’s running off a solar panel.

A race condition in how TankSync publishes its readings

First live test threw a warning I didn’t expect: the tank’s own online/offline state coming back empty on the very first reading. TankSync publishes roughly twenty fields per tank as a retained burst on every reporting cycle, water level, battery, sensor health, and so on, with no guaranteed order between them. Evaluating the instant any single field arrived meant sometimes acting before the health fields had landed.

Claude Code watched the raw MQTT traffic directly to see the actual pattern, one dense burst every reporting cycle, then silence, and fixed it with a short debounce: evaluate only once nothing new has arrived for two seconds. It made sure that timer runs on its own thread rather than blocking the connection to the broker while it works, since the pump control step itself takes a few real seconds.

An overflow fail-safe that doesn’t care who started the pump

Once it was running for real, I asked for one more thing: no matter who turns a pump on, by hand, from the Home app, whatever, it has to come back off once the tank is full. Overflow wasn’t something I was willing to leave to chance.

Half of that was already true and just needed proving. Every decision gets re-derived from the live reading with no memory of who started a pump, so a manually started one gets shut off the moment the tank next reports full. I had Claude Code prove that live by forcing a pump on outside the automation and watching the next reading kill it.

Overflow doesn’t care who left the pump running. Neither does the check that’s supposed to stop it.

The other half was a real gap. If TankSync stops reporting entirely, sensor fault, a dead battery, the hub rebooting, no new reading ever arrives, so nothing re-evaluates, and a pump left running would just keep running. Claude Code added a separate check on a fixed timer, independent of whether any message shows up at all, that force stops any running pump once there’s been no trustworthy reading for a sustained stretch. It tuned that so a single passing sensor glitch doesn’t trip it, only sustained data loss does, and checked all three states against the real hardware: the normal shutoff, the glitch that correctly gets ignored, and the actual failure that correctly kills the pump.

Recalculating the shutoff threshold from the real fill rate

A few days of running it for real surfaced the last problem. These pumps move water fast enough that the tank can go from comfortably low to completely full between one TankSync reading and the next, which meant stopping at 100% wasn’t leaving any margin at all.

Before changing anything, I asked Claude Code to check the actual numbers rather than just guess at a fix. It used a figure from earlier in the process: both pumps together move the tank at roughly 4% a minute, timed off a real fill. At that rate, TankSync’s five minute reporting interval meant the tank could climb around 20 points between two consecutive readings, more than enough to blow straight past a 100% cutoff with the sensor never seeing it coming in time.

A cutoff only means something if the sensor can actually see it coming.

Two changes, done together. I tightened TankSync’s own reporting interval to two and a half minutes myself through the hub’s UI, deliberately not lower than that since the hub runs on solar and faster reporting drains the battery. Claude Code brought the stop threshold down from 100% to 85%, sized against the measured fill rate rather than picked at random, and tightened the stale data timeout to match the faster reporting. It re-checked every new boundary against the real pumps afterward, not just read back in a diff.

The setup as it runs today

Two pumps, one tank sensor with an official Home Assistant integration and nothing for HomeKit out of the box, bridged over the MQTT access it does provide, and a small always-on service making the actual decisions instead of an automation I couldn’t see inside. Below 20% turns both pumps on. 85% turns them off, whoever turned them on. If the sensor goes quiet for too long while a pump is running, it stops regardless.

None of this needed anything from TankSync or Tata Power beyond what they already expose, MQTT from one, an unauthenticated LAN protocol from the other. Just needed something in between willing to actually watch both.