Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
da4a2f0
feat: expose the running ESPHome version and native config hash
flowcool Oct 20, 2026
872485a
feat: expose the filtered soil voltage without changing calibration
flowcool Oct 20, 2026
beb95c6
fix: preserve raw gauge labels while clamping needles
flowcool Oct 20, 2026
da8d0c9
fix: recover the missing KiCad symbol library from schematic caches
flowcool Oct 20, 2026
186d477
feat: add transport-independent low-battery protection
flowcool Oct 20, 2026
2d97269
fix: bound standalone display recovery and invalid rendering
flowcool Oct 20, 2026
f40e66f
Merge pull request #34 from flowcool/prep/72-07-version-hash
JGAguado Oct 21, 2026
f336762
Merge pull request #33 from flowcool/prep/72-06-soil-voltage
JGAguado Oct 21, 2026
c66dbc3
Merge pull request #32 from flowcool/prep/72-05-raw-gauges
JGAguado Oct 21, 2026
f44115a
Merge pull request #35 from flowcool/prep/72-09-kicad-library
JGAguado Oct 21, 2026
47ff160
ci: add firmware tests workflow and CODEOWNERS
JGAguado Oct 21, 2026
bcc7a82
Merge remote-tracking branch 'upstream/V2R1' into prep/72-01-low-battery
flowcool Oct 21, 2026
7c39512
Merge remote-tracking branch 'upstream/V2R1' into prep/72-03-standalone
flowcool Oct 21, 2026
e6b9175
docs: keep low-battery guide focused on user behavior
flowcool Oct 21, 2026
18b8957
docs: remove trailing empty line from battery guide
flowcool Oct 21, 2026
68e5b84
fix: keep recovered KiCad symbol libraries LF on Windows
flowcool Oct 21, 2026
f46ad69
fix: align core display recovery and support native tests without UBSan
flowcool Oct 21, 2026
9828f6e
test: guard shared recovery parity and fresh timer budgets
flowcool Oct 21, 2026
59e55f7
test: make battery display recorder format-security safe
flowcool Oct 21, 2026
d56ce6a
Merge pull request #30 from flowcool/prep/72-01-low-battery
flowcool Oct 21, 2026
56fa1f1
docs: keep recovery compatibility comments shared between entry points
flowcool Oct 21, 2026
e3970b0
Merge upstream battery protection and preserve bounded recovery sleep…
flowcool Oct 21, 2026
166fb09
Merge pull request #36 from flowcool/fix/kicad-library-eol
flowcool Oct 21, 2026
5f8f955
fix: make public examples self-contained upstream and validate both t…
flowcool Oct 21, 2026
a73f123
Limit test CI checkout credentials
flowcool Oct 21, 2026
a861d64
Merge pull request #40 from flowcool/align/upstream-examples-ci
flowcool Oct 21, 2026
e6480b9
Merge pull request #31 from flowcool/prep/72-03-standalone
flowcool Oct 21, 2026
1c7b055
feat: add retained MQTT maintenance and storage modes
flowcool Oct 20, 2026
c5ea999
test: adapt mode checks to merged soil diagnostics and portable libra…
flowcool Oct 21, 2026
086faa3
fix: reconcile maintenance/storage modes with the shared display reco…
flowcool Oct 21, 2026
0a80461
Merge pull request #39 from flowcool/publish/73-maintenance-storage
flowcool Oct 21, 2026
453f95d
feat: generate generic label bands from explicit scales and inputs
flowcool Oct 20, 2026
998270c
ci: install pinned label renderer dependencies for unit tests
flowcool Oct 21, 2026
0fb84e7
Merge pull request #38 from flowcool/publish/73-label-generator
flowcool Oct 21, 2026
8278f14
docs: refresh project entry points and first-install guides
flowcool Oct 21, 2026
ead2840
docs: provide firmware-matched label input and clarify defaults
flowcool Oct 21, 2026
90a3be6
Merge pull request #37 from flowcool/docs/public-docs-refresh
flowcool Oct 21, 2026
312ebd2
Merge upstream/V2R1 into V2R1
flowcool Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .claude/rules/esphome-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ paths:
- Packages are fetched from GitHub by each build server — after pushing changes, run `scripts/esphome_fleet_update.py reset` (NAS + paired VPS builder) before compiling; a NAS-only purge leaves the VPS clone stale.
- Production packages rely on ESPHome's native deep-sleep OTA guard
(`>=2026.8.0`): an orderly `deep_sleep.enter` confirms the app image via
`on_safe_shutdown` -> `confirm_app_image_`, so `smart_plant_core.yaml` no
longer calls `safe_mode.mark_successful` (retired in `infra-3rr.47`). The
public `configuration.yaml` example keeps the explicit call because it targets
arbitrary ESPHome versions.
`on_safe_shutdown` -> `confirm_app_image_`. `smart_plant_core.yaml` and the
public `configuration.yaml` example also call `safe_mode.mark_successful`
before sleep (upstream-reviewed shared recovery; redundant and harmless on
`>=2026.8.0`; `infra-3rr.47` had retired it from the core, restored by the
upstream sync `infra-3rr.78`).
- To read deployed fleet ESPHome version, trust the HA discovery config `dev.sw`
field or the current function-only topic `<mac-prefix>/sensor/esphome_version/state`,
cross-checked with `esphome/discover/<current-mac-name>`. To prove a package
Expand Down
7 changes: 4 additions & 3 deletions .claude/rules/nas-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
an OTA upload does not update the bootloader.
- ESPHome's native guard (`>=2026.8.0`) confirms the app image on any orderly
`deep_sleep.enter`, so a short measurement cycle is no longer misclassified as
a failed OTA boot; `smart_plant_core.yaml` no longer calls
`safe_mode.mark_successful` (retired in `infra-3rr.47`; the public
`configuration.yaml` example keeps it for version-agnostic safety).
a failed OTA boot; `smart_plant_core.yaml` calls
`safe_mode.mark_successful` again before sleep (redundant and harmless on
`>=2026.8.0`; `infra-3rr.47` retired it, restored by the upstream sync
`infra-3rr.78`).
- OTA detection: `nc -z -w1 <ip> 3232` — ICMP ping is unreliable on ESP32.
- Flash: `docker exec esphome esphome upload /config/<device>.yaml --device <ip>`
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Treat PDF documents as binary to preserve their encoded content.
*.pdf binary

# Preserve deterministic recovered symbol libraries on Windows.
# Keep the generated symbol library identical to the recovery output on Windows.
Design/*.kicad_sym text eol=lf
11 changes: 11 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changes to these paths need the maintainer's approval before merging.
# Firmware, examples, docs and tests are not listed, so collaborators can
# merge those PRs themselves once CI passes.

# Hardware design and manufacturing outputs
/Design/ @JGAguado
/Enclosure/ @JGAguado
/Documentation/ @JGAguado

# Repository governance: CI and this file
/.github/ @JGAguado
7 changes: 5 additions & 2 deletions .github/workflows/firmware-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ jobs:
with:
python-version: "3.12"

# Keep the native and package tests on the validated ESPHome release.
# The soil-voltage tests require this exact ESPHome release.
- name: Install ESPHome
run: pip install esphome==2026.9.0 pyyaml

- name: Install Cairo build dependencies
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config

- name: Install label renderer dependencies
- name: Install label generator dependencies
run: pip install -r examples/multi-device/label_maker/requirements.txt

- name: Unit tests
run: python -B -m unittest discover -s tests -v

- name: Validate standalone configuration
run: esphome config docs/source/files/configuration.yaml

- name: Validate public API and MQTT examples
run: python scripts/validate_public_examples.py
13 changes: 7 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@
USB flash of `firmware.factory.bin`; OTA does not update the bootloader.
- ESPHome `>=2026.8.0` natively confirms the app image on every orderly
`deep_sleep.enter`, covering the normal cycle that finishes before ESPHome's
default 60-second OTA validation window, so production packages no longer call
`safe_mode.mark_successful` (retired in `infra-3rr.47`; the public
`configuration.yaml` example keeps it for version-agnostic safety).
default 60-second OTA validation window. The core and the public example still
call `safe_mode.mark_successful` before sleep (upstream-reviewed shared
recovery; redundant and harmless; `infra-3rr.47` had retired it from the core,
restored by the upstream sync `infra-3rr.78`).
- Rollback for shared-package changes: merge a revert PR into `V2R1`, purge
the package cache, and flash the last validated factory/OTA
image. USB recovery is the final fallback.
Expand Down Expand Up @@ -133,9 +134,9 @@ version/config hash live on the devices. Read them from the source every time:
generic hardening changes. Read their live review state from GitHub/Beads.
- MAX17048 native model — gate `infra-3rr.45` (defer 2026-10-15) on ESPHome PR #18594
reaching a stable release; implementation `infra-3rr.46` hard-blocked until then.
- `safe_mode.mark_successful` retired from `smart_plant_core.yaml` — `infra-3rr.47`
(native `>=2026.8.0` guard); the public `configuration.yaml` example keeps the
defensive call. Compile/explicit-IP upload split — `infra-3rr.27`.
- `safe_mode.mark_successful` was retired from `smart_plant_core.yaml` by
`infra-3rr.47` (native `>=2026.8.0` guard) and restored by the upstream sync
`infra-3rr.78`. Compile/explicit-IP upload split — `infra-3rr.27`.
- **OTA**: Device Builder push only (`ota: platform: esphome` + `scripts/esphome_fleet_update.py`,
maintenance-window gated by `ota_min_battery`). Pull-OTA removed 2026-09-03 (`infra-3rr.42`)
as over-engineered for 8 devices. Historical eval `docs/pull-ota-eval.md` (`infra-3rr.22`).
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ not evidence that every probe was calibrated individually.
- **Old bootloader**: `Bootloader too old for OTA rollback` requires one USB
factory flash. OTA does not update the bootloader.
- **Deep-sleep OTA validation**: ESPHome `>=2026.8.0` natively confirms the app
image on every orderly `deep_sleep.enter`, so production packages no longer
call `safe_mode.mark_successful` (retired in `infra-3rr.47`; the public
`configuration.yaml` example keeps it for version-agnostic safety).
image on every orderly `deep_sleep.enter`. The upstream-reviewed shared recovery
calls `safe_mode.mark_successful` again before sleep, in the core and the public
example (redundant and harmless on `>=2026.8.0`; `infra-3rr.47` had retired it
from the core, restored by the upstream sync `infra-3rr.78`).
- **OTA path**: Device Builder push only (`ota: platform: esphome` +
`scripts/esphome_fleet_update.py`, triggered in the maintenance window).
Pull-OTA was removed 2026-09-03 (`infra-3rr.42`) as over-engineered for an
Expand Down
113 changes: 85 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,99 @@
# Smart Plant

[![Documentation Status][documentation-shield]][documentation]
[![License][license-shield]](LICENSE)
[![Project Maintenance][maintenance-shield]][maintenance]
[![buyit][buyit-shield]][buyit]
**See how your plant is doing — on its own e-paper label and in Home Assistant.**

# Smart Plant
The Smart Plant is a board that can be plugged directly on the soil, close to the plant, that allows the measurement of the environment (soil moisture, environment temperature & illuminance), and presents the information on an ePaper display.
Smart Plant measures soil moisture, light, temperature and humidity. Place it
in your plant pot, read the measurements on the 2.9-inch e-paper display,
and connect it to Home Assistant with ESPHome.

![Five Smart Plant devices with custom plant labels and coloured enclosures](docs/source/_static/Slide%202.png)

**[Get started](https://smart-plant.readthedocs.io/en/v2r1/getting_started.html)**
· **[Create your plant label](https://github.com/JGAguado/Label-maker)**
· **[Get a board](https://www.elecrow.com/smart-plant.html)**
· **[Read the documentation](https://smart-plant.readthedocs.io/en/v2r1/)**

[![Documentation](https://readthedocs.org/projects/smart-plant/badge/?version=v2r1)](https://smart-plant.readthedocs.io/en/v2r1/)
[![Firmware tests](https://github.com/JGAguado/Smart_Plant/actions/workflows/firmware-tests.yml/badge.svg?branch=V2R1)](https://github.com/JGAguado/Smart_Plant/actions/workflows/firmware-tests.yml)
[![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/license-CC_BY--NC--SA_4.0-lightgrey)](LICENSE)

## A useful label for every plant

- **Check the soil and surroundings.** Read soil moisture, ambient light,
air temperature and relative humidity.
- **Read it at the pot.** The e-paper screen shows the last displayed readings
while the device sleeps.
- **Make it yours.** Give each plant its own name, illustration and gauge
background with the companion Label-maker project.
- **Connect to Home Assistant.** Choose the native ESPHome API or retained
MQTT readings for devices that spend most of their time asleep.
- **Run on battery.** The firmware sleeps between measurement cycles.
The hardware also supports an optional solar cell.

## Start with one plant

1. [Get a board](https://www.elecrow.com/smart-plant.html), or use the
[hardware design files](Design/) to explore the board.
2. Follow the [getting-started guide](https://smart-plant.readthedocs.io/en/v2r1/getting_started.html)
and [ESPHome programming guide](https://smart-plant.readthedocs.io/en/v2r1/programming.html).
3. Set your Wi-Fi credentials and calibrate the soil sensor for your setup.
4. Add your own label background, then install the firmware.

The [standalone configuration](docs/source/files/configuration.yaml) is the
starting point for a single device. Read the
[safety instructions](https://smart-plant.readthedocs.io/en/v2r1/safety.html)
before assembly and flashing.

## Create your plant label

The companion **[Label-maker](https://github.com/JGAguado/Label-maker)**
generates printable plant labels and e-paper backgrounds from JSON or YAML
templates. Use it to add a plant name and illustration to your display.

[![Example lemon-tree e-paper background](docs/source/images/programming/Lemon_tree_label_page_1.png)](https://github.com/JGAguado/Label-maker/tree/main/1_Plants)

Follow the [step-by-step label guide](docs/source/labels.rst).
Start with the [plant examples and generator](https://github.com/JGAguado/Label-maker/tree/main/1_Plants),
then follow the programming guide to use the resulting background image.
The firmware draws the live readings over that image.

<p float="left">
<img src="./Documentation/Images/Top.png" width="49%">
<img src="./Documentation/Images/Bottom.png" width="49%">
</p>
## Connect one plant or several

## Project tree
| Setup | Starting point | During deep sleep |
| --- | --- | --- |
| One device with the native ESPHome API | [Standalone configuration](docs/source/files/configuration.yaml) | No network response; entity availability depends on Home Assistant setup. |
| Several devices with retained MQTT readings | [MQTT example](examples/multi-device/my-lemon-tree.yaml) | Last published readings remain available. |
| Shared packages with the native ESPHome API | [API example](examples/multi-device/my-lemon-tree-api.yaml) | No network response; entity availability depends on Home Assistant setup. |

<li><a href="./Design/">Design</a></li>
<li><a href="./Documentation/">Documentation</a></li>
The [multi-device guide](examples/multi-device/README.md) explains the shared
hardware core and transport profiles. Each device sets its identity and
credentials. Calibration and label images use shared defaults unless overridden.

<li><a href="./README.md">README.md</a></li>
## Explore the hardware

## ESPHome firmware paths
Smart Plant combines an ESP32-S2, a soil-moisture probe, environmental sensors
and an e-paper display. The repository includes board design files and
printable enclosure models.

- The [programming guide](./docs/source/programming.rst) and its standalone
configuration are the simplest starting point for one device.
- The [multi-device example](./examples/multi-device/README.md) documents the
shared core plus native-API/MQTT profiles. Its inventory, generated metadata,
fleet updater, and NAS/VPS procedures are specific to the `flowcool` deployment
and are not required for a normal installation.
- [Board design](Design/)
- [Enclosure models](Enclosure/)
- [Schematics and supporting files](Documentation/)
- [Technical specifications](https://smart-plant.readthedocs.io/en/v2r1/specs.html)
- [Assembly guide](https://smart-plant.readthedocs.io/en/v2r1/assembly.html)

## Questions and contributions

Use [Discussions](https://github.com/JGAguado/Smart_Plant/discussions) for
questions and ideas. Report reproducible problems in
[Issues](https://github.com/JGAguado/Smart_Plant/issues).

[documentation-shield]: https://readthedocs.org/projects/smart-plant/badge/?version=v2r1&style=for-the-badge
[documentation]: https://smart-plant.readthedocs.io/
Contributions to firmware, documentation and examples are welcome. Describe
the problem, the change and how you verified it in your pull request.

[license-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg?style=for-the-badge
Created by [J. G. Aguado](https://github.com/JGAguado), with contributions
from the community.

[maintenance-shield]: https://img.shields.io/badge/maintainer-J.%20G.%20Aguado-2e48a7.svg?style=for-the-badge
[maintenance]: https://github.com/JGAguado
## License

[buyit-shield]: https://img.shields.io/badge/get%20it%20on-elecrow-399BD7.svg?style=for-the-badge
[buyit]: https://www.elecrow.com/smart-plant.html
This project uses [CC BY-NC-SA 4.0](LICENSE). See the license for attribution,
non-commercial use and sharing requirements.
Loading
Loading