Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

JSN-SR04T with Raspberry Pi Pico 2 W — water tank level with phone alerts

Measure an overhead water tank with a waterproof JSN-SR04T on a Pico 2 W: 5 V from VBUS, Echo through a 1 kΩ / 2 kΩ divider to GP15, % full and ntfy alerts.

MicroPythonVerified — micropython (mpy-cross v1.29.0), 3 Nov 2026Compile-only

Community draft — report a mistake

Every Indian home with an overhead tank has had it overflow or run dry. The JSN-SR04T is a waterproof ultrasonic probe made for this job, and the Pico 2 W reads it and sends alerts over Wi-Fi.

Power the sensor from VBUS, send Trig from GP14, and bring Echo down to 3.3 V with a 1 kΩ / 2 kΩ divider into GP15. Enter your tank’s depth, and the script prints the level every 10 seconds and alerts your phone when the tank is low or full.

Wiring

Raspberry Pi Pico 2 WOnly the pins used are shown · 3.3 V logicJSN-SR04T waterproof ultrasonic sensor1k│2kGNDGP14GP15VBUS5VTrigEchoGND
Also needed: 1 kΩ resistor, 2 kΩ resistor, USB power supply that stays on (the Pico 2 W reads VBUS only while USB is connected).
Every connection
Raspberry Pi Pico 2 WJSN-SR04T waterproof ultrasonic sensorWireNotes
VBUS5VredVBUS (pin 40), USB 5 V — the most reliable supply for this sensor.
GP14TrigpurpleGP14 is physical pin 19. A 3.3 V pulse is enough.
GP15EchogreenVia 1 kΩ / 2 kΩ divider — Echo is a 5 V pulse. 1 kΩ from Echo to GP15, 2 kΩ from GP15 to GND. GP15 is physical pin 20.
GNDGNDblack

5 V (V3.0 boards: 3.0–5.5 V) part, 3.3 V board. Echo swings to the supply voltage. Powered from 5 V (most reliable), send Echo to an ESP32 or Pico through a 1 kΩ / 2 kΩ divider. A 3.3 V Trig pulse is enough.

Board quirks

  • The Pico 2 W’s GPIOs tolerate 5 V only while the board is powered (and the ADC pins never do), so this guide keeps the divider — a power cut that leaves the sensor powered first can’t hurt the board.
  • The probe has a blind zone of about 20–25 cm. Mount it at least 25 cm above the overflow level and set FULL_GAP_CM to that distance, TANK_DEPTH_CM to sensor-to-floor.
  • A 20 µs trigger pulse and the median of seven pings ride out the echoes from tank walls and the inlet pipe.
  • Optional ntfy alerts at 20 % (“tank low”) and 95 % (“switch the motor off”). Switching a 230 V pump automatically needs a contactor and an electrician — this sketch only alerts.

Code

main.pyMicroPython
# Water-tank level with a JSN-SR04T waterproof ultrasonic sensor on a Raspberry Pi Pico 2 W
# (MicroPython), with optional "tank low" / "tank full" phone alerts through ntfy.sh.
# Wiring: 5V -> VBUS (pin 40), GND -> GND, Trig -> GP14 (pin 19),
# Echo -> 1 kOhm -> GP15 (pin 20), and 2 kOhm from GP15 to GND (Echo is a 5 V pulse).
from machine import Pin, time_pulse_us
import network
import time
import json

TANK_DEPTH_CM = 120   # sensor face to the tank floor        <- measure yours
FULL_GAP_CM = 25      # sensor face to the water when full (>= 25 cm: the sensor's blind zone)
LOW_PCT = 20
FULL_PCT = 95
WIFI_SSID = "your-wifi"            # optional
WIFI_PASS = "your-password"
NTFY_TOPIC = "your-private-topic"  # https://ntfy.sh/<topic> - pick something unguessable

# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
    with open("/settings.json") as f:
        _s = json.load(f)
except (OSError, ValueError):
    _s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
NTFY_TOPIC = _s.get("ntfy_topic") or NTFY_TOPIC
ALERTS = WIFI_SSID != "your-wifi" and NTFY_TOPIC != "your-private-topic"
if not ALERTS:
    print("Phone alerts off - no Wi-Fi name or ntfy topic set. Flash from flashready.in or type them into main.py.")

trig = Pin(14, Pin.OUT, value=0)
echo = Pin(15, Pin.IN)
led = Pin("LED", Pin.OUT)


def ping_cm():
    trig.value(1)
    time.sleep_us(20)  # the JSN-SR04T wants a longer trigger than the HC-SR04
    trig.value(0)
    t = time_pulse_us(echo, 1, 40000)
    return None if t < 0 else t * 0.0343 / 2


def median_cm(n=7):
    vals = []
    for _ in range(n):
        d = ping_cm()
        if d is not None and d > 20:  # inside the blind zone = not trustworthy
            vals.append(d)
        time.sleep_ms(100)
    vals.sort()
    return vals[len(vals) // 2] if vals else None


def notify(msg):
    if not ALERTS:
        return
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        wlan.connect(WIFI_SSID, WIFI_PASS)
        for _ in range(30):
            if wlan.isconnected():
                break
            time.sleep(0.5)
    if not wlan.isconnected():
        print("Wi-Fi failed - no alert sent")
        return
    try:
        import requests
    except ImportError:
        import urequests as requests
    try:
        requests.post("https://ntfy.sh/" + NTFY_TOPIC, data=msg).close()
        print("Phone alert sent")
    except OSError as e:
        print("ntfy failed:", e)


print("Tank depth {} cm, full at {} cm from the sensor".format(TANK_DEPTH_CM, FULL_GAP_CM))
state = None
while True:
    d = median_cm()
    if d is None:
        print("No echo - check the sensor and divider")
    else:
        water = TANK_DEPTH_CM - d
        pct = max(0, min(100, int(water * 100 / (TANK_DEPTH_CM - FULL_GAP_CM))))
        print("Distance {:.1f} cm  ->  water {:.0f} cm  ({} %)".format(d, water, pct))
        led.value(pct <= LOW_PCT or pct >= FULL_PCT)
        if pct <= LOW_PCT and state != "low":
            state = "low"
            notify("Water tank low: {} %".format(pct))
        elif pct >= FULL_PCT and state != "full":
            state = "full"
            notify("Water tank full: {} % - switch the motor off".format(pct))
        elif LOW_PCT + 10 < pct < FULL_PCT - 10:
            state = "mid"  # re-arm both alerts
    time.sleep(10)

Expected serial output

REPL / Thonny shellOutput
Tank depth 120 cm, full at 25 cm from the sensor
Distance 74.2 cm  ->  water 46 cm  (48 %)
Distance 29.8 cm  ->  water 90 cm  (95 %)
Phone alert sent

Common errors

Reads about 20 cm when the tank is nearly full

The water is inside the blind zone. Raise the sensor so the full level is at least 25 cm below it.

“No echo” every time

Check the divider orientation (1 kΩ next to the sensor), the 20 µs trigger, and that the R27 mode resistor on V3.0 boards is open (Trig/Echo mode).

Level jumps in a narrow tank

The wide beam hits the walls. Mount the probe in the centre, away from the inlet pipe.

↑↓ to move · Enter to open