Pump2Status — Raspberry Pi monitoring of three rural water pumps

July 18, 2022

A Raspberry Pi 4 with sensor wiring on a workbench

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.

1. System overview

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

2. Bill of materials

Total approximate cost: 280 USD per controller, June 2022.
ComponentQtyUnit cost (USD)
Raspberry Pi 4 (4 GB)155
Custom HAT PCB (designed in KiCad)118
MCP3008 ADC (8-channel SPI)24
CT clamp 30A 1V312
Pressure transducer 0-100 psi 4-20mA328
Hall effect flow meter G1/239
24V to 5V buck converter16
IP65 enclosure122
Misc. wiring, terminals, fuses30

3. Wiring notes

  1. Run all sensor wires in twisted pair, shielded if possible. The pump motors put significant noise on long wire runs.
  2. Tie the shield to ground at the controller end only. Never both ends.
  3. Use a separate fuse for each sensor power line. A shorted sensor should not take down the whole system.
  4. Mount the controller enclosure away from the pump motors themselves. At least 2 meters minimum.
  5. Run a separate ground wire back to the controller from each pump. Do not rely on the conduit or the pump frame for the ground reference.

4. Software (Python 3.9)

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)

5. Configuration

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 }

6. Lessons learned

  1. Version 1 of the controller caught fire because the buck converter was undersized. Use a 5V buck rated for at least 3A.
  2. Version 2 had ground loop issues that produced false readings on the current sensors. Star grounding (every sensor returns to a single ground point at the controller) fixed this.
  3. Version 3 logged data to a microSD card that wore out after four months. SQLite on tmpfs with hourly flush to a USB SSD is much better.
  4. Version 4 (current) has been stable since June 2022.

7. References

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