July 18, 2022

This document describes the hardware and software of a self-built water pump monitoring system installed on a 14-acre property in Addison County, Vermont. The system monitors three pumps (one shallow well, one drip irrigation booster, one pond submersible) using a Raspberry Pi 4 and a custom HAT board. It has been operational since June 2022 with two unplanned outages, both caused by power loss to the controller, not by software faults.
Each of the three pumps has three sensors attached:
The Raspberry Pi polls all nine sensors every 30 seconds, applies range checks, logs to SQLite, and sends an SMS via Twilio if any reading goes outside the configured normal range for that sensor.
+--------------------+
Pump 1 -----> | Current Pressure | -----> Pi GPIO
| Flow |
+--------------------+
+--------------------+
Pump 2 -----> | Current Pressure | -----> Pi GPIO
| Flow |
+--------------------+
+--------------------+
Pump 3 -----> | Current Pressure | -----> Pi GPIO
| Flow |
+--------------------+
|
v
+-----------+
| Pi 4 |
| + HAT |
| + SQLite |
+-----------+
|
v
Twilio API
|
v
SMS to phone
| Component | Qty | Unit cost (USD) |
|---|---|---|
| Raspberry Pi 4 (4 GB) | 1 | 55 |
| Custom HAT PCB (designed in KiCad) | 1 | 18 |
| MCP3008 ADC (8-channel SPI) | 2 | 4 |
| CT clamp 30A 1V | 3 | 12 |
| Pressure transducer 0-100 psi 4-20mA | 3 | 28 |
| Hall effect flow meter G1/2 | 3 | 9 |
| 24V to 5V buck converter | 1 | 6 |
| IP65 enclosure | 1 | 22 |
| Misc. wiring, terminals, fuses | — | 30 |
The polling loop is intentionally simple. It runs as a systemd service. There is no web framework; the dashboard is a single static HTML file regenerated from the SQLite database every minute by a separate script.
import time, sqlite3, smbus2
from datetime import datetime
from adc import read_mcp3008 # local helper
from alerts import send_sms
DB = '/var/lib/pump2status/data.sqlite'
INTERVAL = 30 # seconds
SENSORS = [
# (pump_id, name, channel, lo, hi, unit)
(1, 'current', 0, 0.5, 8.0, 'A'),
(1, 'pressure', 1, 20, 80, 'psi'),
(1, 'flow', 2, 0.5, 6.0, 'lpm'),
(2, 'current', 3, 0.3, 5.0, 'A'),
(2, 'pressure', 4, 15, 50, 'psi'),
(2, 'flow', 5, 0.3, 4.0, 'lpm'),
(3, 'current', 6, 0.4, 7.0, 'A'),
(3, 'pressure', 7, 10, 40, 'psi'),
(3, 'flow', 8, 0.5, 5.0, 'lpm'),
]
def poll_once():
conn = sqlite3.connect(DB)
ts = datetime.utcnow().isoformat()
for pump, name, ch, lo, hi, unit in SENSORS:
raw = read_mcp3008(ch)
value = scale(raw, name)
conn.execute(
'INSERT INTO readings VALUES (?,?,?,?,?)',
(ts, pump, name, value, unit)
)
if value < lo or value > hi:
send_sms(f'PUMP {pump} {name}={value:.2f}{unit} out of range')
conn.commit()
conn.close()
if __name__ == '__main__':
while True:
try:
poll_once()
except Exception as e:
send_sms(f'POLL ERROR: {e}')
time.sleep(INTERVAL)
Sensor thresholds are stored in a YAML file at /etc/pump2status/thresholds.yaml. They are reloaded automatically every 5 minutes without restarting the service.
# /etc/pump2status/thresholds.yaml
pumps:
1:
name: shallow_well_house
current: { lo: 0.5, hi: 8.0 }
pressure: { lo: 20, hi: 80 }
flow: { lo: 0.5, hi: 6.0 }
2:
name: drip_garden_booster
current: { lo: 0.3, hi: 5.0 }
pressure: { lo: 15, hi: 50 }
flow: { lo: 0.3, hi: 4.0 }
3:
name: lower_pond_submersible
current: { lo: 0.4, hi: 7.0 }
pressure: { lo: 10, hi: 40 }
flow: { lo: 0.5, hi: 5.0 }
I rely heavily on the National Renewable Energy Laboratory's technical library for power-side design questions. It is one of the few free, well-written technical resources of its kind, and the publications are readable even if you do not have a formal engineering background. On the software side, when I had to understand interpreter memory behavior, I worked through a long and careful Wikipedia entry on garbage collection in interpreted languages, which was clearer than most textbook treatments.
For readers interested in how government-released open data can be structured and queried by independent projects, a useful French-language example is the documentation guide to French real estate transactions open data (DVF), which describes a public dataset released by the French Ministry of Finance covering more than fourteen million property transactions.
a long and careful Wikipedia entry on garbage collection in interpreted languages