Skip to the content
2 layers65 × 56 mmWaterPlant Pi, the printed circuit board in 3D

WaterPlant Pi

airiclenz/tethys1

Battery-powered wireless soil-moisture sensors report to a Raspberry Pi master over a 2.4 GHz nRF24 radio link. The master decides when to water, drives the pumps/valves, and serves a real-time web dashboard - all on your own LAN, no cloud.

Size
65 × 56 mm
Copper layers
2
Thickness
1.6 mm
Parts
73
Nets
38
Pads
187
Tracks, vias
223, 72
Pours
12
Routed
Yes
KiCad
KiCad 5 (file 20171130)
Created
16 Jul 2024
Updated
24 Jun 2026

Overview

WaterPlant Pi is an open-source printed circuit board by airiclenz, from the airiclenz/tethys repository on GitHub, published under the MIT licence. It is a 2-layer board of 65 × 56 mm with 73 parts on 38 nets, routed with 223 tracks and 72 vias.

It is built around the DMG3418L-7. Its parts include 20 resistors, 13 diodes, 11 capacitors, 9 connectors and 6 transistors. It belongs with the audio and music and power boards of this library.

Main components

1

README

README.md
Tethys logo

A self-hosted, multi-channel automatic plant-watering system.

Battery-powered wireless soil-moisture sensors report to a Raspberry Pi master over a 2.4 GHz nRF24 radio link. The master decides when to water, drives the pumps/valves, and serves a real-time web dashboard — all on your own LAN, no cloud.

Part 1 — What Tethys is and how to use it

The big picture

Tethys has two kinds of hardware:

  1. Sensor nodes (one per plant/pot, up to 5) — a battery-powered ATtiny84 board with a capacitive soil-moisture probe and an nRF24L01+ radio. Each node sleeps almost all the time, wakes on a configurable interval, measures soil moisture and battery voltage, transmits a reading, and goes back to sleep. Designed to run for months on a single charge (~9 µA while sleeping).

  2. The master — a Raspberry Pi with a custom HAT carrying the matching nRF24L01+ radio plus pump/valve driver outputs. The master listens for sensor readings, stores them, and when a channel's soil drops below its trigger threshold it runs that channel's pump (or opens its valve) for a configured duration. It also serves the web UI you use to configure and monitor everything.

text
   ┌─────────────┐   nRF24 2.4GHz      ┌──────────────────────────────────┐
   │ Sensor node │  (250kbps, 8-byte   │         Raspberry Pi master      │
   │  ATtiny84   │   versioned frames) │                                  │
   │  + moisture │ ◄──── reading ────► │  core daemon ─► pump/valve GPIO  │
   │  + nRF24    │      config ◄────   │              │                   │
   │  (battery)  │                     │  Django API (SQLite) ◄─► web UI  │
   └─────────────┘                     └──────────────┬───────────────────┘
        × up to 5                                     │  HTTP / WebSocket
                                                      ▼
                                              Your browser / phone

Each channel (1..5) pairs one sensor node with one pump or valve output. A channel knows its soil-moisture trigger %, its pump run duration, and how often its sensor should measure.

Repository layout

text
tethys/
├── code/
│   ├── master/            # Everything that runs on the Raspberry Pi
│   │   ├── core/          # The watering daemon (radio + pump control + action engine)
│   │   ├── api/           # Django REST API (the system's database + state)
│   │   ├── web/           # Django + Channels dashboard (browser UI)
│   │   ├── camera/        # Optional on-demand webcam snapshot service (USB/V4L2)
│   │   ├── watchdog/      # Daily service-restart / log-vacuum job
│   │   ├── globals/       # Shared config + git-ignored secrets
│   │   └── install/       # install.sh, systemd units, nginx configs
│   └── sensor/            # Arduino/PlatformIO firmware for the sensor nodes (the "wpw" project)
├── hardware/              # KiCad schematics + Gerbers for the Pi HAT and the sensor PCB
├── graphics/              # Logos, hardware labels, web-UI assets, renders
└── docs/                  # Audits, protocol review, hardening notes, handoffs

Hardware

Master — Raspberry Pi + "WaterPlant Pi" HAT (hardware/rpi hat/). GPIO assignments live in code/master/core/hardware.py:

FunctionBCM pin(s)Notes
Channel outputs16, 19, 20, 26, 21One per channel; CHANNEL_COUNT = 5
Shared pump pin6Energised together with a channel in valve mode
Water-level sensor4
nRF24 CE / CSN / IRQ22 / 0 / 17SPI radio
Status LED / Fan14 / 15

A channel of type pump drives its own pin directly. A channel of type valve drives its pin and the shared PUMP pin (one pump pushing water through whichever valves are open); the pump pin is reference-counted so it only drops once the last valve closes.

Sensor node — "Sensor" PCB (hardware/sensor/), firmware in code/sensor/:

  • MCU: ATmega/ATtiny84 @ 8 MHz (PlatformIO TX build), deep-sleep SLEEP_MODE_PWR_DOWN, woken by the 8-second watchdog timer.
  • Radio: nRF24L01+ on CE=8 / CSN=9, 250 kbps, CRC-8, auto-ACK.
  • Moisture: capacitive probe excited by a ~500 kHz square wave (Timer0 on PB2), read on ADC PA3; calibration min/max stored in EEPROM.
  • Battery: monitored on A7 against the internal 1.1 V reference; below 3.2 V the node sends a battery-alert frame instead of a normal reading.

Install (on the Raspberry Pi)

bash
sudo apt install git -y
git clone https://github.com/airiclenz/tethys.git tethys
cd tethys/code/master/install
./install.sh

The installer updates the OS, creates a Python venv, installs Redis + nginx, runs the Django migrations, registers the systemd services, enables SPI, and reboots (re-run ./install.sh --rebooted=true afterward if you decline the auto-reboot). On first run it generates a random API key into the git-ignored code/master/globals/secrets.py and prints it — copy it.

Useful flags:

  • --debug=true — developer install (also installs the TypeScript toolchain).
  • --allowed-hosts=tethys.<tailnet>.ts.net — extra ALLOWED_HOSTS for remote (VPN) access. Alternatively (and preferred per-install), list the name in the git-ignored code/master/globals/allowed_hosts.py (copy allowed_hosts.example.py) — no reinstall needed, just restart the services. See docs/remote-access-hardening.md.

After it's up: open http://<pi-host>/ in a browser → Settings (top menu) → paste the API key → Save. The key gates every request (reads included; only the CORS OPTIONS preflight is exempt), so the dashboard stays blank until it's set. If a page can't authenticate (missing or wrong key), a warning banner appears under the header with a link straight to Settings.

Daily use

  • Sensor Readings page (/) — per-channel reading history: two trend charts (moisture % and battery voltage over time) sit above a sortable table of the individual readings.
  • Channels page (/channels/) — live moisture %, battery, last action per channel; enable a channel, set its nickname, type (pump/valve), pump duration, and the moisture trigger %; manually test a pump.
  • Schedules page (/schedules/) — define silent phases (quiet hours): weekday + start time + duration windows during which all automatic watering is suppressed (e.g. overnight). The header shows a sleep icon while a silent phase is active.
  • Webcam page (/webcam/) — optional live view from a USB camera. Off by default; flip Enable to refresh a snapshot every few seconds. Live-view only — nothing is recorded, and the camera releases automatically when you leave the tab or after an idle timeout. See Webcam below.
  • Order 66 (top menu) — recovery action for when a service or the Pi itself gets wedged: confirm the popup and the Pi performs a graceful reboot (back in ~1 minute). Gated by the API key, like pump control.
  • The dashboard updates live over a WebSocket; no manual refresh needed.

Webcam (optional)

Tethys can show a live webcam view on its own Webcam tab — handy for checking on plants — but it ships off and only captures on demand:

  • USB camera (V4L2). Plug in any UVC webcam; the service grabs JPEG snapshots with v4l2-ctl (no extra Python dependency). A Pi Camera Module (CSI) is not wired up yet — the capture code keeps a seam (camera/captureBackend.py) for adding it as one new class.
  • Snapshot-only, nothing recorded. The tab fetches a fresh JPEG every few seconds (an authenticated fetch, gated by the same API key as everything else). There is no video stream and no storage — purely a live view.
  • Pick the size, view it full-screen. A dropdown lists the resolutions the camera itself reports (so it isn't tied to one model), and the live frame can be expanded to fill the screen — both handy on a phone. The choice is per-session only; nothing is saved.
  • Focus & zoom sliders. A plant sits at a fixed distance, so autofocus just hunts. The tab offers focus and zoom as sliders whose ranges are read from the camera itself — and a slider is disabled when the camera doesn't support that control. Setting focus pins it (no more hunting). Your pick is remembered in the browser; CAPTURE_FOCUS / CAPTURE_ZOOM in camera/config.py set the defaults a fresh browser starts from.
  • Fail-closed + auto-off. The service boots disabled (a snapshot while off returns 409). It auto-releases the camera after a short idle window and enforces a hard maximum on-time, mirroring the pump's auto-off safety — so the camera can never be left on by accident.
  • Least privilege. tethys-camera runs as the normal app user (no root, unlike tethys-core), with only the video group added so it can open the root:video /dev/video* nodes. The installer adds the user to that group and installs v4l-utils.

Updating / operating

From code/master/install/:

  • ./installServices.sh — re-deploy the systemd services from the current code.
  • ./services-restart.sh, ./services-stop.sh, ./services-clearLogs.sh.
  • ./deploy-static.sh — rebuild the frontend TypeScript and publish it to nginx.

The six services: tethys-core, tethys-api, tethys-web, daphne, tethys-watchdog (restarts core+api+camera nightly at 01:00 and vacuums the journal), and tethys-camera (the optional on-demand webcam, off by default).

Firmware (sensor nodes)

Built with PlatformIO from code/sensor/. Before building, set the node identity in code/sensor/include/wpw_Config.h: SENSOR_NUMBER (1–5). Flash with:

bash
cd code/sensor
pio run -t program -e tethys-sensor     # via an AVR ISP programmer

⚠️ The master and every sensor must be flashed together. The wire format is versioned and fixed-size; a node on mismatched firmware fails the version check and goes silent (cleanly — it never corrupts data). See the Known limitations / coordinated flash note in CHANGELOG.md.

Sensor node LED — calibration & status blinks

A sensor node has no screen; its single onboard LED is how it talks to you. Patterns are produced by DoSimpleBlink(onMs, offMs) and DoSoftPulse(ms) (code/sensor/src/wpw_Blinker.cpp; the soft pulse is a software-PWM glow, since the LED pin has no free hardware PWM).

How calibration works. Calibration maps the probe's raw reading onto a 0–100 % moisture scale, and it is two-point: the node captures one reading in a wet condition and one in a dry condition, then stores the lower value as MinimumValue (wettest) and the higher as MaximumValue (driest) in EEPROM (Calibrate() in code/sensor/src/wpw_Sensor.cpp). Later readings become 100 − map(raw, min, max, 0, 100) %.

  1. Trigger it from the web UI — set the channel's calibrate flag (sensorTriggerCalibration), then power-cycle or reset the node. Calibration runs only as part of the boot-time config exchange (the full GETCONFIG in setup()) — not on a normal measurement wake. A running node re-pulls its config periodically (see Periodic config pull below), but those pulls are deliberately settings-only: they carry measure-frequency / TX-power but never the calibration trigger, so they can't run calibration mid-life. The master delivers the trigger once and immediately clears the flag (core/radio.py), so if you set the flag while the node is already running, nothing happens until you reset it. (This is the usual reason a calibration "didn't start".)
  2. The node then takes two measurements, each announced by the calibration countdown below (slow blinks → fast blinks → one long blink → it measures while the LED is off). Put the probe in one condition for the first capture and the other condition for the second.
  3. Order doesn't matter — the firmware sorts the two readings into min/max. What matters is that the two conditions are genuinely different: if the two readings are too close, the calibration is rejected and the previous values are kept (signalled by the long 2 s blink).

First reading after boot. On every boot — including right after calibration — the node waits ~1 minute (STARTUP_SETTLE_SECONDS, default 60, in wpw_Config.h) before taking and transmitting its first reading. It sleeps through the wait (you'll see the heartbeat flash each ~9 s), so it costs almost no power. Put the probe in its final monitoring position before this window ends — the first reported value reflects wherever the probe is at that moment, not the calibration captures.

Periodic config pull. A running node re-pulls its config so web-UI changes (measure frequency, transmission power) reach it without a power-cycle. The cadence is PULL_CONFIG_CYCLE_SECONDS in wpw_Config.h (default 3600 = 1 h; 0 disables it for maximum battery saving). To stay gentle on the battery the pull rides an existing measurement wake — the radio is already powered up, so it adds no extra power-up, and the master answers from its warm ConfigCache so the listen window is usually a few milliseconds. The pull therefore happens on the first measurement at/after the cycle has elapsed since the last pull. These pulls are silent (no LED) and settings-only — they never carry or clear the calibration trigger (calibration stays boot-only; see above).

The patterns, all on the sensor (TX) node LED:

EventBlink patternMeaning
Power-on / node IDN slow blinks (150 ms on, 300 ms off), where N = SENSOR_NUMBERNode booted and is powered; the count identifies which node it is (1–5).
Config receiveda soft ~2 s glow (brightness ramps up then down)The master answered the config request (at boot) and the settings were applied.
No master replytwo short flashes (15 ms)Config request timed out (no answer within 500 ms); the node falls back to the settings stored in EEPROM.
Config rejectedthree short flashes (15 ms)The master replied but the frame failed the protocol-version / type check.
Settings savedaccelerating ramp — 150 → 100 → 75 → 50 → 25 → 15 ms — then one long 300 ms flash (ramps up to a strong finish)New settings differed from EEPROM and were written.
Settings unchangeddecelerating ramp — 15 → 25 → 50 → 75 → 100 → 150 ms — fading out with no final flash (the mirror of saved)Config was checked but nothing changed, so nothing was written.
Heartbeatone very short flash (5 ms) each wake (~every 9 s)Normal sleep/measure loop tick — the node is alive.
Transmit failedfour short flashes (15 ms)The radio send failed; the node backs off (~15 min) before retrying. Part of the short-flash radio-error family, counted: 2 = no reply, 3 = bad frame, 4 = send failed (a successful config exchange is the soft glow above instead).
Calibration countdown10 slow blinks (300 ms) → 20 fast blinks (100 ms) → one long blink (1 s) → LED off while it measuresCounts down to a calibration capture. Runs twice, once per measurement point.
Calibration rejectedone very long flash (2 s)The two calibration readings were too close together; calibration was discarded and the previous values kept.

Part 2 — Orientation for a contributor or coding agent

This half is the lightweight CLAUDE.md: the runtime topology, the invariants that are easy to break, and where to look. Read it before changing anything on the master.

Process & port map

Everything runs on the Pi as systemd services (units in code/master/install/assets/*.service, nginx in *.nginx):

ServiceProcessBindsReached via
tethys-corecore/tethys_core.py (asyncio)— (GPIO/radio)n/a
tethys-apigunicorn → tethys_api.wsgilocalhost:5001nginx :5000
tethys-webgunicorn → tethys_web.wsgilocalhost:8000nginx :80 /
daphneASGI → tethys_web.asgi0.0.0.0:8001nginx :80 /ws/
tethys-watchdogwatchdog/tethys_watchdog.py—nightly restart
tethys-cameracamera/tethys_camera.py (stdlib HTTP)127.0.0.1:8002nginx :80 /camera/
redis-serverChannels layer for daphnelocalhost:6379—

The API is the single source of truth. Both the core daemon and the web backend treat it as the database boundary and reach it at http://localhost:5000/api/ (nginx → gunicorn:5001). It is Django REST Framework over SQLite (api/db.sqlite3).

Data flow (one reading → one watering)

  1. A sensor wakes, measures, and transmits a 7-byte frame on its pipe.
  2. core/radio.py (checkRadioInbox → handlePayload) drains the RX FIFO, validates each frame via core/protocol.py, and POSTs the reading to sensorData/. On success it sets that channel's flag in core/config.py::FlagHandler.channelFlags.
  3. core/actionEngine.py::handleActions (called each core-loop pass) checks the silent phase first (fail-closed — see below), then for each flagged channel pulls channelSummary/<n> and, if moisture <= trigger and the channel is enabled, calls pumpController.run_pump(...).
  4. core/pumpController.py energises the line, arms a threading.Timer auto-off, and on completion writes an actionLog/ entry — all non-blocking, so the radio keeps listening while a pump runs.
  5. The web UI sees the change: web/tethys_web/jobs.py polls the API every 10 s (/lastUpdate/, /channelSummary/, /schedule/, /silentPhaseStatus/) and, on a change, broadcasts to all browsers over the /ws/tethys/ WebSocket (web/tethys_web/consumers.py).

A manual Test Channel tap uses the same hardware path, not a separate one: the API enqueues a ManualCommand (it never drives GPIO), the core drains it each loop pass and runs it through the same pumpController (rejecting an activate when another channel is already running), and writes the outcome back for the UI to poll. This is what keeps the one-channel power limit holding for manual control — see Invariants below.

When a sensor asks for its config (GETCONFIG), the master answers from an in-memory ConfigCache (warmed out-of-band by Radio.refreshConfigCache), never with a blocking HTTP call inside the sensor's short listen window. A running node's periodic, settings-only pull (GETCONFIG_PERIODIC) is answered the same way but with the calibration trigger stripped and never cleared.

Invariants — break these and something physical or silent goes wrong

  • Pump safety. Every "pump on" is clamped to MAX_PUMP_SECONDS = 300 and has a guaranteed off (timer + try/finally). The controller drives all lines LOW on construction (fail-safe boot) and on SIGTERM/atexit. Never reintroduce a watering path that bypasses pumpController. make_controller() is the only place lgpio is bound; all hardware goes through the GpioAdapter seam so the logic is testable without a Pi.
  • One channel at a time (power limit). pumpController is the single owner of the watering GPIO. Both automatic watering and the web UI's manual Test Channel taps go through it — the latter via the ManualCommand queue the core drains each loop pass — so its max_concurrent = 1 guard holds the invariant that at most one channel (plus the shared pump pin) is ever energised, which protects the power supply. A manual activate that arrives while another channel is running is rejected, not run. The API process never drives GPIO itself.
  • Silent phase is fail-closed. If the API is unreachable, isInSilentPhase() returns None and actionEngine skips watering for that pass — it must never water on unknown state. The window math lives in api/tethys_api/silentphase.py (stdlib-only, unit-tested independently of Django).
  • Timezones. globals/config.py::TIME_ZONE (Europe/Stockholm) is the single deployment/display zone, shared by core and web. The Django API deliberately keeps TIME_ZONE = 'UTC' as its storage zone — schedule start times are wall-clock times-of-day stored on a placeholder date; the deployment zone is applied only at silent-phase evaluation. Don't "fix" the API to local time. (See the silent-phase fixes in CHANGELOG.md for why.)
  • Wire protocol is versioned and fixed-size. core/protocol.py and sensor/include/wpw_RXTX.h / wpw_Config.h must stay byte-for-byte in sync: PROTOCOL_VERSION = 1, PAYLOAD_SIZE = 8, structs <BBBf> (sensor→master, 7 B) and <BBHB?> (master→sensor, 6 B). Bump PROTOCOL_VERSION on any layout change — and re-flash everything (no backward compat). Adding a new message type (the type byte, e.g. DATATYPE_CMD_GETCONFIG_PERIODIC = 8) is not a layout change, so it does not bump the version; keep the type constants in sync on both ends. An un-updated peer ignores an unknown type (the sensor's periodic pull just times out and retries), so mixed versions degrade gracefully.
  • Pipe 0 is reserved. Channels map 1:1 onto nRF24 reading pipes 1..N; pipe 0 is the ACK/writing address. This caps the link at 5 sensors. See protocol.pipe_for_channel.
  • CHANNEL_COUNT is the single source of truth (= len(Pins.CHANNELS) = 5). The DB/radio reference 6 channels historically; a 6th needs a physical GPIO pin first. Don't hardcode channel counts.
  • Secrets. globals/secrets.py (git-ignored) holds TETHYS_API_KEY, API_SECRET_KEY, WEB_SECRET_KEY, and is read by both the Django apps and the non-Django core. Template: globals/secrets.example.py. Every API request needs X-API-Key (reads included; only CORS OPTIONS is exempt) — see api/tethys_api/permissions.py::ApiKeyRequired.

Auth posture

A single shared API key gates all requests, compared constant-time, fail-closed. The core (apiInterface.py, radio.py) and the web backend (jobs.py, tools.py) send it server-side; the browser stores it in localStorage and common.ts injects it into every fetch. Transport is plain HTTP on the LAN — for remote access, front it with a VPN overlay (Tailscale/ WireGuard), not an open port. See docs/remote-access-hardening.md.

Data model (Django, api/tethys_api/models.py)

  • Channel (number PK) — enabled, nickName, channelType (pump/valve), actionTriggerPercent, pumpDurationSeconds, sensorMeasureFrequencyMinutes, sensorTriggerCalibration, sensorTransmissionPowerLevel.
  • SensorData — channel FK, moisturePercent, batteryVoltage, timestamp.
  • ActionLog — channel FK, actionType, startTime, endTime.
  • Schedule — weekday, scheduleType (silent), startTime (UTC wall-clock), durationMinutes, enabled.
  • ManualCommand — channel FK, action (activate/deactivate), status, message, requestedAt, processedAt. The web UI enqueues a manual Test Channel tap here; the core drains it through pumpController (the API never drives GPIO) and reports the outcome back for the UI to poll.
  • Lookup tables: ChannelType, ActionType, ScheduleType, TransmissionPowerLevel.
  • channelSummary/<n> is the denormalized read both the core and UI consume (channel config + latest sensor reading + counts).

Tests

Backend (core + API) — pure Python, no Pi/hardware needed:

bash
cd code/master
pip install -r install/python-requirements-dev.txt   # pytest, dev-only
pytest                                                # pytest.ini sets paths

Covers the parts that gate physical action or silent failure: core/tests/ (pump controller, GPIO adapter, protocol framing, action engine, manual-command queue) and api/tethys_api/tests/ (API-key permission, silent-phase window math, LAST_DATA_UPDATE wiring, manual-command lifecycle). All run without lgpio/pyrf24/a Pi, thanks to the GPIO and protocol seams. Add tests behind those seams when you touch the watering, radio-framing, or silent-phase paths.

The camera service has its own pure-Python suite (camera/tests/: the fail-closed snapshot gate, idle / max-on auto-off, and the X-API-Key check, via FakeSnapshotBackend). It is not in the default testpaths, so run it explicitly: pytest camera/tests.

Web UI (Playwright) — browser-driven regression tests for the channels UI:

bash
cd code/master/web
pip install -r requirements-dev.txt
playwright install --with-deps chromium   # downloads Chromium + apt libs (needs sudo)
pytest

These drive a headless Chromium against a throwaway Django live server, exercising the real compiled JavaScript (conftest.py compiles static/ts → static/js first), so they catch DOM/click-handler bugs the Python suites can't. See web/tests/README.md for what's covered and how it works. A developer install (install.sh --debug=true) installs all of the above automatically.

Frontend build

TypeScript sources in web/static/ts/ compile to web/static/js/ via compile.sh (tsc) / compileAutomatically.sh (watch). The build is strict (strict: true in tsconfig.json) and tsc -p tsconfig.json compiles cleanly. Use install/deploy-static.sh to compile + publish to the nginx static dir. The compiled web/static/js/ is git-ignored — it is rebuilt from the .ts sources on deploy and before the UI tests, so the TypeScript is the single source of truth.

⚠️ Editing the frontend? Change the .ts files in web/static/ts/, never web/static/js/ directly. The .js folder is generated output (git-ignored) and is overwritten on every tsc run / deploy, so any hand-edits to the .js are silently lost. Tip: git check-ignore <path> will tell you if a file is a build artifact before you touch it.

Known limitations / deferred (see CHANGELOG.md + docs/)

  • Low-battery flag is logged but not persisted (no SensorData column yet).
  • Webcam is live-view only — no recording / clip storage, snapshot download, or multiple cameras; the CSI/picamera2 backend is deferred (seam left in camera/captureBackend.py).

Where the design rationale lives

docs/audits/ (code audit, architecture review, protocol review) and docs/handoffs/ capture why the current shape exists — the pump-safety module, the API-key lockdown, the protocol hardening, and the silent-phase timezone fixes. CHANGELOG.md is the readable summary of each. Start there before reworking any of the invariants above.

Parts

73
QtyValueFootprintReferences
81kΩAiric_Lenz:Device_0603_HandsolderingR13, R14, R15, R16, R17, R18, R19, R20
710uF 50VCapacitor_SMD:C_1206_3216Metric_Pad1.42x1.75mm_HandSolderC1, C4, C5, C6, C7, C8, C9
7Conn_01x02_FemaleAiric_Lenz:Connector_JST_XH_02x01_angeledJ3, J4, J5, J6, J7, J8, J9
610kΩAiric_Lenz:Device_0603_HandsolderingR3, R4, R8, R9, R10, R12
6470ΩAiric_Lenz:Device_0603_HandsolderingR1, R2, R5, R6, R7, R11
6DMG3418LPackage_TO_SOT_SMD:TSOT-23_HandSolderingQ1, Q2, Q3, Q4, Q5, Q6
6LED_SmallAiric_Lenz:Diode_0805_HandSolderingD7, D8, D9, D10, D11, D12
6Schottky 2AAiric_Lenz:Diode_0805_HandSolderingD1, D2, D3, D4, D5, D6
4Drill_HoleAiric_Lenz:Hole_M3_NPTH_MountingDR1, DR2, DR3, DR4
21uF 16VAiric_Lenz:Device_0603_HandsolderingC3, C11
10.1uF 16VAiric_Lenz:Device_0603_HandsolderingC2
110uF 25VCapacitor_SMD:C_1206_3216Metric_Pad1.42x1.75mm_HandSolderC10
1Barrel_JackAiric_Lenz:POWER_JACK_2.1_SMDJ2
1Conn_02x20_Odd_EvenConnector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_VerticalJ1
1LED_SmallAiric_Lenz:Diode_0603_HandsolderingD13
1LOGOAiric_Lenz:Logo_WiFi_Silk_4mmG***
1LOGOAiric_Lenz:Logo_Tethys_PiChannels_SilkG***
1LOGOAiric_Lenz:Logo_Tethys_Silk_6mmG***
1LOGOAiric_Lenz:Logo_Tethys_PiWaterSens_SilkG***
1Logo_Airic_LenzAiric_Lenz:Logo_AiricLenz_Silk_15mmLogo1
1Logo_OSHAiric_Lenz:Logo_OSHW_Silk_10mmL1
1Module_Pololu_D36V28F5Airic_Lenz:Module_Pololu_5V_D36V28F5U1
1Module_nRF24L01Airic_Lenz:Module_nRF24L01_SMDMOD1
1TESTPOINTAiric_Lenz:Testpoint_GNDTP1
1VAL**Airic_Lenz:Logo_BatteryPolarityBatteryPolaritySign

More from airiclenz/tethys

1
  • 2 layers39.3 × 145.9 mmSensor, the printed circuit board in 3D

    Sensor

    airiclenz/tethys

    Battery-powered wireless soil-moisture sensors report to a Raspberry Pi master over a 2.4 GHz nRF24 radio link. The master decides when to water, drives the pumps/valves, and serves a real-time web dashboard - all on…

    35 parts1MIT

Similar boards

  • 4 layers150 × 120 mmEphys test board, the printed circuit board in 3D

    Ephys test board

    open-ephys/ephys-test-board

    General purpose, automated electrophysiology test board

    567 parts1
  • 4 layers83 × 86.5 mmBTS MainBoard Power, the printed circuit board in 3D

    BTS MainBoard Power

    bebenlebricolo/BTS-MainBoard-Power

    Beer thermoregulation system mainboard - power section

    94 partsGPL-3.0
  • 2 layers27.2 × 21.1 mmDumbwatch, the printed circuit board in 3D

    Dumbwatch

    BluCodeGH/dumbwatch

    An offline-first E-paper smartwatch

    52 partsMIT
  • 2 layers131.2 × 132.5 mmMacropad, the printed circuit board in 3D

    Macropad

    earthtojake/text-to-cad

    Design physical products with AI

    73 parts26kMIT
  • 2 layers76 × 112 mmLaunch heavy, the printed circuit board in 3D

    Launch heavy

    system76/launch

    System76 Launch Configurable Keyboard

    73 parts1.4kGPL-3.0
  • 2 layers152.9 × 58.6 mmMini sequencer, the printed circuit board in 3D

    Mini sequencer

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    73 parts915
  • 2 layers128.6 × 74.2 mmFUEL PANEL, the printed circuit board in 3D

    FUEL PANEL

    jrsteensen/OpenHornet

    OpenHornet 1:1 F/A-18C Simulator Repository

    73 parts431
  • 2 layers84 × 75 mmSmu amp 1, the printed circuit board in 3D

    Smu amp 1

    jaromir-sukuba/J-SMU

    DIY source-measure unit

    73 parts244

Questions

How big is the WaterPlant Pi PCB?

It measures 65 × 56 mm and has 2 copper layers, 1.6 mm thick.

What parts does the WaterPlant Pi use?

73 parts, built around the DMG3418L-7. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the WaterPlant Pi KiCad files?

From the airiclenz/tethys repository on GitHub, at the commit this page was made from: the board is hardware/rpi hat/WaterPlant Pi.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.

Can I use the WaterPlant Pi design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).