"on" — which relay closes, what interlocks apply, what the pump actually does. PumpLink decides who’s allowed to send it, for how long, and keeps the record.
The division of labor
The device is deliberately dumb about policy. It never receives a duration, a quota, or a user identity — it responds to
"on" and "off" and acks both. Every decision about permission and time lives server-side, which is what makes the record trustworthy. See the device contract for the full handshake.
Sessions are a first-class concept
A session isn’t a side effect of two messages. It’s an object with a lifecycle:1
Start
A request to
POST /activate is authenticated and attributed to a user. The backend publishes "on" and starts its own timer.2
Ack
The device confirms it received the command within 10 seconds. The session row isn’t written until that ack lands — if it never does, the backend publishes
"off", returns a 504, and no session is recorded at all.3
Run
The session is live and visible. The backend holds the duration; the device is quota-blind and just runs.
4
End
The session ends for exactly one recorded reason, and the reason is stored alongside it.
5
Confirm
The device must ack
"off" too. Usage is credited once the outcome is known — acked or timed out — so a slow ack doesn’t undercount actual on-time.
Sessions also record who ran them, whether the run was
manual or scheduled, the duration requested, and the duration actually measured. GET /activations returns that history filtered by user, device, activation type, reason, or date range.
Quota is enforced before dispatch, not during a run. Each device has its own rolling 24-hour budget. The full requested duration is reserved up front, and if it doesn’t fit, the activation is rejected with a
429 and the device is never commanded. A running session is never cut short by quota — so a request either fits or it doesn’t, and nobody gets a pump that stops halfway through.A device that doesn’t confirm
"off" is recorded as UNKNOWN, not OFF. PumpLink won’t assume a device stopped just because it went quiet, and it refuses to re-activate a device in that state. It clears when the device publishes its real state on device/{id}/status, or when an admin physically checks the pump and calls POST /device/:id/confirm-off. An honest “I don’t know” is more useful than a wrong “off”.PumpLink and Home Assistant
Most comparison pages are slanted. This one tries not to be, because you’ll find out either way in about twenty minutes.When Home Assistant is the better choice
Home Assistant is genuinely better than PumpLink in most of the situations people evaluate us for:- You already run it. A working HA instance is a large sunk investment in exactly the thing PumpLink would replace. Don’t tear it out.
- You’re automating one home. HA is built for this and does it well.
- You want local control. HA runs entirely on your hardware with no cloud dependency. PumpLink does not.
- You need sensor-driven automation. Soil moisture triggers, tank levels, temperature thresholds, and conditional logic across devices — HA has a mature rule engine. PumpLink has none.
- You want the integration ecosystem. Thousands of integrations, Zigbee and Z-Wave, an add-on store, and a large community. Nothing we could build in a year comes close.
When PumpLink is the better choice
PumpLink is narrower on purpose. It’s a better fit when:- There’s no server to run. A cabin, a farm, a remote pump site — nowhere to put a Pi, and nobody on-site to maintain it when it stops booting. PumpLink has no home server to keep alive.
- Multiple people share the hardware. Each person gets their own login, an admin grants or revokes access per device, and every activation is attributed to whoever caused it. Not one shared password taped to the pump house.
- You’re managing devices across locations. Not one home — many sites, from one place.
- Accountability matters more than flexibility. When the question “who ran the pump for forty minutes on Tuesday” needs a real answer, the record has to be a first-class feature and not a log file you hope was enabled.
The one thing HA handles poorly
Timed runs with a clean interrupt. There’s a recurring pattern in the r/homeassistant community: someone builds a timed pump run as an HA script — turn on,delay, turn off — and then discovers they can’t cleanly interrupt the wait. Stopping the script mid-delay means killing the script, and the turn-off step goes with it. You can work around it, and experienced HA users do: helper booleans, timer entities, wait_for_trigger instead of delay, a companion automation to catch the stop. It’s assemblable. It’s just assembled.
Two things fall out of that:
- It’s fragile. The session lives across a script, a timer helper, and an automation or two. Any one of them failing leaves a pump running or a state flag stuck.
- Nothing records why it ended. You get state history, not session history. There’s no single record saying this run started at 14:02, was started by this person, ran for eleven minutes, and ended because an operator stopped it early.
What PumpLink does not do today
Naming these matters more than anything above it. If one of them is a dealbreaker, you should find out now, not after you’ve wired a relay.No sensor-driven automation
There’s no rule engine and no conditions. PumpLink can run a pump at 6am; it can’t run one because a moisture reading dropped. Sensor logic lives in your firmware.
No telemetry ingestion
PumpLink doesn’t collect, store, or chart sensor readings. The
status topic reports device state, not measurements.No dashboard widgets or pin binding
You don’t drag a toggle onto a canvas and bind it to GPIO 26. The dashboard manages devices, users, and sessions.
No local-only operation
It’s cloud by design. If the device can’t reach
mqtt.pumplink.dev, it can’t be commanded. There’s no LAN fallback.What happens when the connection drops
Losing the broker is two different failure modes, and they are not equally serious.Unreachable at activation
This one fails closed, and it’s the benign case. If the device isn’t connected when someone hits activate, the backend publishes"on" and then waits. No ack arrives within 10 seconds, so it aborts the session, publishes "off", and returns a 504 to the caller. Nothing is recorded as running, no quota is consumed, and the pump was never energized. The user sees a failure immediately rather than a session that silently did nothing.
Disconnected mid-session
This one is the scary one, and it deserves a straight answer: PumpLink cannot stop your pump if it can’t reach it. The broker detects the lost session — clean disconnect, keepalive timeout, or dropped TCP — and reports it to the backend. Two separate things then happen, and keeping them separate is the point:- Reachability flips to unreachable immediately. Your clients are told the device is off the network.
- State is not touched by that signal. The backend force-ends the session, publishes
"off"into the void, gets no ack, and records the device asUNKNOWN.
device_broker_disconnect, but the relay is whatever it was, and nothing server-side can change that. If the pump was running, it is still running.
A device that drops while idle is a different case, and PumpLink deliberately leaves its state alone. Losing a broker session says nothing about whether a pump is spinning, so a device everyone agreed was
OFF stays OFF and can be activated the moment it’s back. Only a session that was actually running resolves to UNKNOWN.What fail-safe firmware actually looks like
Four things, none of which depend on the network:- A local max-runtime cutoff. Keep an independent timer in firmware that opens the relay after a fixed maximum, regardless of MQTT. Set it comfortably above your longest legitimate session but below the point where a runaway does damage. This is the single most important one — it’s the only thing standing between a dropped connection and an unbounded run.
- Relay opens on connection loss. If the MQTT client reports it’s no longer connected while a run is active, treat that as a stop rather than waiting for a command that can’t arrive.
- Relay defaults to open on boot. Drive the pin low in
setup()before anything else, so a brownout or reboot never comes back up with the pump energized. The ESP32 quickstart sketch already does this. - A hardware watchdog. A wedged firmware loop holds the relay in whatever state it was in. A watchdog reboots into the safe default instead.
Use a clean MQTT session. The backend doesn’t enforce this either way, but a persistent session lets the broker queue commands and replay them on reconnect — so a device that reconnects an hour later could receive an
"on" from a session the backend already aborted and closed. The quickstart firmware connects with a clean session by default.ON as if it were current. Knowing is not the same as controlling. Build the cutoff into the firmware.
When not to use PumpLink
If you’re running a single home setup on Home Assistant and it’s working, stay there. You’d be trading a mature rule engine and local control for a session log you probably don’t need at one site. Use PumpLink when there’s no server to run, when several people share the same hardware, or when you need to know who did what. Those are narrow conditions. They’re also the ones where nothing else fits well.ESP32 quickstart
Wire and flash a device in under 30 minutes.
Device contract
The MQTT topics and handshake any device must implement.