Hi, I'm Sprout. I watch a windowsill of plants and tell you, in plain words, how each one is doing β no guesswork: I read the soil myself and speak for the plant. (Watering? On my roadmap β and true to form, I won't claim it until it's calibrated.)
Sprout is a small automatic plant-care system for a windowsill: capacitive soil-moisture probes on one or more ESP32-class boards β each reporting over Wi-Fi (untethered) or a USB-serial cable β with a Python logger and analytics behind them and a served dashboard out front. It watches the soil, classifies it into seven calibrated moisture bands, and (once calibration is in) will water before a plant is ever in trouble.
The minimum Sprout is deliberately small: a microcontroller and one soil sensor is already a complete Sprout (ADR-0028) β a pump, an OLED, and extra probes are optional enhancements, never an entry bar. And it's past the bench: since the wave-1 go-live (2026-07-04) the greenhouse runs live in soil β currently 11 plants, 8 of them with sensors, on 2 boards β reporting over Wi-Fi with each session saved to the catalog.
It's an open-source learning-and-portfolio build, made to be enjoyable to run and trustworthy to read β process and tooling sized to match, not over-engineered.
Sprout is developed with AI assistance β the maintainer works alongside AI coding tools β and AI-assisted contributions are as welcome as hand-written ones. Contribute however you work, with or without AI tools; the bar is the same either way: clear, tested, and kind.
What you need first
| A computer | macOS, Linux, or Windows 10/11. All three are supported and tested. |
| Git | git --version should print a number. If not: winget install Git.Git (Windows), xcode-select --install (macOS), sudo apt install git (Debian/Ubuntu). |
| A GitHub account | Only to contribute. Reading, cloning, and running Sprout need no account. To push, either add an SSH key or install the GitHub CLI and run gh auth login. Not a collaborator here? Fork the repo and branch from your fork β the normal open-source path. |
| Python | Nothing to install. uv fetches the exact locked interpreter for you. |
| A board + probe | Optional. Sprout runs, builds and tests without hardware; you need one only to read real soil. |
Windows β where you clone matters. Windows' classic
MAX_PATHis 260 characters and applies to the whole path, so we keep tracked paths under 200 and that leaves about 60 characters for your clone location.C:\dev\sproutis ideal; a deepβ¦\OneDrive\Documents\GitHub\β¦can exhaust the budget and produce a checkout that fails β or silently lands incomplete β on a machine that has done nothing wrong.just doctormeasures yours and tells you the headroom.
macOS / Linux
git clone https://github.com/OrangePeachPink/sprout
cd sprout
./scripts/bootstrap.sh # installs uv + just if missing, syncs the env, wires the hooks
just start # run Sprout β opens the dashboard in your browser
Windows (PowerShell β works on the 5.1 that ships with Windows)
git clone https://github.com/OrangePeachPink/sprout
cd sprout
.\scripts\bootstrap.ps1
just start
That block is the whole path: bootstrap installs what's missing and then verifies it, printing
the versions it just proved on your machine. It's safe to re-run β every step checks first and skips
what's already there. Only git has to be there first (it's how you got here);
your first PR walks that and the GitHub side.
Prefer to do it by hand, or curious what bootstrap does? The two tools are
uv (env + runner) and just (the
command menu):
uv sync # reproduce the exact, locked dev environment
uv run pre-commit install # the conventions auto-apply on every commit
Or click Open in Codespaces for a ready-made env in the browser β the devcontainer installs both
tools for you. Then just lists every command, and just check runs your local gate β lint, format,
and the host test suites. Those two tools are genuinely all you need: just check never asks for a
compiler. Only firmware work needs more, and it says so on its own path (just check-firmware).
Once the dashboard is up, click βΆ Start logging β that single action begins logging every connected sensor at once, whether it's plugged in over USB or reporting over Wi-Fi. On a brand-new install with no data yet, the empty-state hands you the same Start button, so day one is never a dead-end.
probe ESP32 classifier Sprout
βββββ βββββ ββββββββββ ββββββ
capacitive β raw ADC count β seven moisture β a mood, a first-person line,
soil read (higher = drier) bands (calibrated) and β when ready β a pump
The chain is built on one rule: raw counts and the calibrated band are the reading. Any 0β100 figure is a clearly-labelled relative index between the wet/dry anchors β never presented as real volumetric water content. A plant's mood, its status color, and any watering all derive from the band, never from that index.
Today β the Workbench. One command (just start) serves this: Monitor Β· Capture Β· Lab Β·
Trial Β· Plants & Sensors Β· Diagnostics & Logs β raw ADC and the calibrated band for every probe, plus
the calibration ladder. The Workbench is where the instrument is read; Home β the glanceable
per-plant card grid β is where a plant speaks for itself, and ADR-0033 is the plan for getting
there. Plain and
unpolished on purpose, and it shows exactly what it reads: a probe sitting in open air or in a glass of water
is called out as an instrument condition, not handed a plant's mood β those aren't soil readings, and the
seven moods are reserved for probes actually in a pot.
Where we're headed. This is the design direction β Sprout as a calm, first-person character, the mood system in motion across a day. It's a concept, not a screenshot, and not built yet β a great place for a UI/UX contributor to jump in (#867).
Concept, not the app. The design system & mood system live in docs/design/.
Sprout isn't a readout β it's a character. The plant speaks for itself, in the first person, calm and plain-spoken. The full identity, voice rules, the living mark, and the seven-band mood system are in the brand guide:
- Brand guide β voice, the living mark + motion, the moodβband system, the characterβinstrument boundary.
- Design system β tokens (
sprout-tokens.css), instrument components, and the v3 personality layer. - Decisions of record: ADR-0007 (brand & voice) Β· ADR-0008 (personality layer).
A few principles behind how Sprout reads a plant:
- Raw + band are the reading; a percentage is a labelled relative index, never VWC.
- Mood & automation follow the calibrated band, never the index.
- Every number is mono, right-aligned, tabular β data looks like data.
- Gaps are surfaced, not smoothed β the dashboard shows what the capture actually contains.
| Part | Qty | Notes |
|---|---|---|
| Capacitive soil moisture sensor | 4 | Board HW-390, silk "Capacitive Soil Moisture Sensor V2.0.0". 3.3-5.5 V in, 0-3.0 V analog out, 3-pin PH2.0. QA passed - see SENSOR_QA.md. |
| Mini submersible DC water pump | 4 | DC 2.5-6 V (rated ~3 / 4.5 V), ~0.18 A, ~100 L/h, submersible. DC only - never mains. |
| 4-channel relay module | 1 | 5 V module. Active-high vs active-low and 3.3 V-drive compatibility to be bench-verified. |
| PVC vinyl tubing | ~4 m | ID ~5.54 mm / OD ~8.20 mm. |
| Microcontroller | 1+ | ESP32 (classic dual-core; SoC marked ESP-32D, ESP32-D0WD class) from the SunFounder ESP32 kit is the baseline. Firmware also builds for ESP32-S3 and ESP32-C5 boards β the supported boards, per-board serial paths, and pin maps live in docs/hardware/BOARDS.md. 3.3 V ADC matches the 0-3.0 V sensor output; 4 sensors on ADC1 (avoid ADC2 = WiFi); WiFi/BT for monitoring. |
| Status display | 1 | 1.3" SH1106 128x64 I2C OLED (Hosyond 5-pack). On the I2C bus (GPIO21/22), powered at 3.3 V. Shows status / last-watered / errors. |
(Kit provenance is recorded in the local parts inventory: UMLIFE watering kit. The SunFounder ESP32 kit
also bundled a 5th capacitive sensor β an NE555-based v1.2 variant β which is not used for this
project; see SENSOR_QA.md.)
Firmware lives in firmware/ as a PlatformIO project (ESP32, Arduino framework). Open the
firmware/ folder in VS Code with the PlatformIO IDE extension, or use the CLI from that folder:
- Build:
pio run - Upload:
pio run -t upload - Monitor:
pio device monitor(19200 baud, set inplatformio.ini)
Build failing with
Python version mismatch: penv has X.Y, current interpreter is X.Z. Recreating penv...thenuv installation via pip failed with exit code 106? You likely have two PlatformIO installs β the IDE extension's bundled core and a standalonepioon your PATH β sharing~/.platformioand rebuilding each other'spenvwith different Pythons. Native tests still pass (they skip the ESP32 platform), which hides it. The untangle is machine-specific; the fix is to let one install own the core.
Board env is esp32dev (classic ESP32); the esp32s3 and ESP32-C5 envs build from the same source for the
other supported boards β see docs/hardware/BOARDS.md for per-board serial paths and pin
maps. Pin assignments and tunables live in firmware/include/config.h.
The build cache and resolved libraries (firmware/.pio/) are git-ignored.
For data capture, prefer the host-side logger (tools/logger/plants_logger.py) over the raw monitor: it
stamps each row with UTC time and writes a rotating, self-describing CSV under logs/ per the shared
telemetry schema (docs/TELEMETRY_SCHEMA.md). Requires pyserial.
One command sets up, one command checks β the conventions help you instead of getting in your way.
uv sync # the exact, locked dev env (Python, ruff, pytest, pre-commit)
uv run pre-commit install # auto-format + lint + hygiene on every commit
just check # your local gate: pre-commit + host/DX/analytics tests (uv + just only)
just check-firmware # the above PLUS the native C tests β needs PlatformIO (firmware work only)
just doctor # is Sprout ready on THIS machine? reports, never repairs
just # list every command
pre-commit is the single definition of code quality β ruff lint + format, markdownlint, and
whitespace/EOL hygiene β run identically on your machine and in CI, so style is something the repo handles
for you, not a thing you have to remember. Per-language configs live at the repo root:
| Area | Tool | Config |
|---|---|---|
| Python | ruff β lint + format (all-in-one) | ruff.toml |
| Markdown | markdownlint-cli2 | .markdownlint.json |
| C / C++ (firmware) | clang-format + clang-tidy | .clang-format Β· .clang-tidy |
| Endings / encoding | git + EditorConfig | .gitattributes Β· .editorconfig |
Ruff is the modern all-in-one (it replaces flake8 / isort / pyupgrade / black), pinned in the locked env. The
firmware C formatter (clang-format) runs in the gate on changed lines, pinned in the same locked env;
clang-tidy static analysis stays advisory, not build-blocking.
| Area | Path |
|---|---|
| Firmware (ESP32 / PlatformIO) | firmware/ |
| Host logger & analytics | tools/ |
| Design system & brand | docs/design/ |
| Decisions of record (ADRs) | docs/adr/ |
| Wiring Β· telemetry Β· calibration | docs/ |
| What shipped, when | Releases Β· CHANGELOG.md |
Monitoring is live. Since the wave-1 go-live (2026-07-04), the greenhouse reports in soil over
Wi-Fi β currently 11 plants, 8 sensors, 2 boards, and subject to change β each session saved to the
catalog. Watering is deliberately gated: the firmware
ships a manual operator-commanded bounded pump pulse (!water / !stop), and the safety order is
make watering correct before it's possible β per-probe calibration (#170) and fail-safe actuator-off
(#93) are done; the remaining gate is bench-verifying the relay path on real hardware (#191), which
autonomous watering (#94) waits on. Current standing lives in docs/STATUS.md.
Work is proposed, tracked, and merged through GitHub β Issues are the ledger, the
project board is the working view, and
Discussions are the idea inbox. The full loop
(branch β PR with Refs #N β the review-before-close verification gate) lives in
CONTRIBUTING.md.
Looking for somewhere to jump in? Contributors Welcome is our running list of things we'd love a hand with β resistive-sensor support, board configs beyond ESP32 + Arduino, and a host-the-stack tier.
Sprout is MIT-licensed β a deliberate choice, not a default. MIT is a permissive license: you can do almost anything with the code, and you're never required to open-source your own changes β the thing that sets it apart from "copyleft" licenses like the GPL (GNU General Public License), which do require it when you share the software. About as few strings as open source has: do almost anything, just keep the notice.
As a user β use it, fork it, learn from it, build on it: for a windowsill, a classroom, or a product you sell. Commercial use is fine. No permission to ask, no fee, no catch. The one obligation is to keep the copyright line and license text with the code.
As a contributor β you keep the copyright on what you write. No CLA (Contributor License Agreement β the legal form some projects make you sign before they'll accept your code), no copyright assignment, no paperwork. Opening a PR just means your contribution ships under the same MIT terms β which is what keeps Sprout free for the next person. It's also why the copyright reads "Veronica K. Hogue and Sprout contributors": the moment you contribute, that "and contributors" is you.
No warranty β it's provided as-is. (We read the soil as best we can; we don't promise your monstera survives your vacation.)
We picked the friendliest license we could so the distance between "I found this repo" and "I'm using and improving it" is as close to zero as open source allows. Take it and grow something. π±
Built in the open by Veronica Hogue (@OrangePeachPink) Β· source under MIT.
Sprout Β· plants with a pulse Β· tend well.
