Germinion - DIY Plant Environment Tracker 🌱

by ElfenStomp in Circuits > Microcontrollers

401 Views, 9 Favorites, 0 Comments

Germinion - DIY Plant Environment Tracker 🌱

germinion_image.png
box1.PNG
IMG_20250909_210300.jpg
IMG_20250805_154749.jpg

The goal of this project was to gather information about the environment of my outdoor plants—such as air temperature and humidity, soil moisture, and sunlight exposure—and to visualize these measurements on a smartphone app or a Home Assistant dashboard.

In this guide, I will focus exclusively on designing the electronic enclosure that collects all the plant-related data. Below is the list of features I aimed to include in this project:

  1. šŸŒ”ļø Measure air temperature and humidity
  2. 🌼 Measure soil moisture to know when to water the plant
  3. ā›… Monitor sunlight exposure
  4. šŸ’» Send all readings to a remote server
  5. šŸ’¾ Store readings locally when the server is unavailable
  6. ⚔ Power the device from a battery and keep track of the battery charge level
  7. ā˜€ļø Recharge the battery using solar panels
  8. šŸ’§ Optionally control a water pump to automatically water a plant


There are many commercial solutions that perform similar environmental measurements, with varying levels of cost and reliability. My intention with this project was not to compete with existing products, but rather to learn how to design a PCB that meets my specific constraints.


I learned so much while working on this project, especially from the many articles and forum discussions shared by passionate electronics and tech enthusiasts who generously shared their knowledge. I hope my little contribution can be helpful and maybe even inspire you to create something of your own šŸ¤—

Measuring Soil Moisture

resistive_sensor.jpg
oxidation.jpg
capacitive_sensor.jpg
capacitive_sensor_schematic.PNG
capacitive_sensor_hack.PNG

Most inexpensive soil moisture sensors available on the market are resistive sensors. These use two exposed metal probes that are inserted into the soil. The sensor works on the principle that wet soil conducts electricity better than dry soil.


Measuring Soil Moisture


How Resistive Sensors Work:

  1. The probe consists of two open conductors.
  2. The resistance between the probes varies depending on the soil's moisture level:
  3. High moisture → lower resistance
  4. Low moisture → higher resistance
  5. The module (e.g., FC-28 / YL-69) includes an LM393 comparator and provides:
  6. Analog output (A0) proportional to resistance
  7. Digital output (D0) when moisture crosses a threshold


āš ļø Resistive soil moisture sensors suffer from rapid probe degradation in wet environments due to corrosion of the exposed electrodes. This significantly reduces their lifespan. The way out of this situation is to use a homemade soil moisture sensor, in which the probes can be changed very quickly and inexpensively if necessary.

Measuring Soil Moisture


Luckily, there’s a better option: capacitive soil moisture sensors. Capacitive sensors offer a more robust and accurate solution:

  1. Measure soil moisture using capacitive sensing, not conductivity.
  2. Built from corrosion-resistant materials, giving a longer service life.
  3. Provide more stable and accurate readings.


Measuring Soil Moisture


How Capacitive Sensors Work:

  1. The sensor detects changes in the dielectric constant of the surrounding medium.
  2. Water, being a strong dielectric, affects the measured capacitance more than dry soil.
  3. The output is analog and must be interpreted using an ADC (Analog-to-Digital Converter).


āš ļø These sensors do not measure water content directly, but instead infer moisture from dissolved ions and soil composition. For example, adding fertilizer will change the reading even if water content stays the same.


Here are two great articles that explain in details how most of the capacitive sensors available on the market work:

  1. https://lastminuteengineers.com/capacitive-soil-moisture-sensor-arduino/
  2. https://thecavepearlproject.org/2020/10/27/hacking-a-capacitive-soil-moisture-sensor-for-frequency-output/


Basically, these modules use a 555 timer configured as an oscillator, which acts as a pulse generator producing a square wave. The frequency and shape of the square wave depend on the values of two resistors, Ra and Rb, as well as a capacitor.

In this design, the oscillator produces a 1.5 MHz square wave. The square wave output from the 555 timer is then fed into an RC integrator circuit, where the capacitor is formed by the soil probe itself. The integrator converts the square wave into a more triangular wave, which is subsequently sent to a rectifier followed by a smoothing capacitor. This process generates a stable DC voltage output proportional to the soil’s moisture level.

Measuring Soil Moisture


In this project, the key hack is to use only a TLC555 timer, with the soil probe acting directly as the capacitor in the oscillator circuit. This means that the oscillation frequency becomes (almost) directly proportional to the soil’s moisture level, since the dielectric constant of the soil (and thus its capacitance) varies with moisture.

Measuring Soil Moisture


Instead of generating a DC voltage output like traditional analog sensors, this setup produces a square wave signal, and the moisture level is inferred by measuring the frequency of that signal. As the soil becomes wetter, its capacitance increases, which lowers the frequency of the square wave—and vice versa. You can see in the attached video the changes in the square wave signal frequency when the soil probe is placed in a glass of water (the experiment was conducted using a test PCB designed during the project).


With this setup, a calibration phase is required. It involves measuring the frequency of the square wave signal at the output of the TLC555 when the soil probe is in open air and when the soil probe is submerged. From these two frequencies, the minimum capacitance Cdry and maximum capacitance Cwet of the soil probe can be determined.

  1. Cdry = 1.44 / ((Ra + 2 * Rb) * fdry)
  2. Cwet = 1.44 / ((Ra + 2 * Rb) * fwet)


Once calibrated, you can calculate the measured capacitance and determine the soil moisture percentage using the following formulas:

  1. Cmeasured = 1.44 / ((Ra + 2 * Rb) * fmeasured)
  2. Soil moisture (%) = ((Cmeasured - Cdry) / (Cwet - Cdry)) * 100


(The formula to determine the capacitance from the frequency comes from the TLC555 datasheet.)


In conclusion, I chose to use a TLC555, along with the necessary resistors and capacitors in the circuit, to measure soil moisture. In the next section, we will look at the list of other sensors I selected to perform the remaining measurements. Good news—things will be much quicker from here! No more epic TLC555 stories, I promise šŸ˜‡

Components and Sensors

sht40.PNG
veml7700.PNG
max17048.PNG
ds3231.PNG
mb85RC256v.PNG
esp32_mini.PNG

SHT40

The SHT4x is a digital sensor platform designed to measure relative humidity and temperature with high precision available in different accuracy classes. It communicates via an I²C interface, offering several preconfigured I²C addresses for flexibility in multi-device setups. The sensor is optimized for ultra-low power consumption, drawing as little as 0.4 μA, making it ideal for battery-powered or energy-efficient applications.

Components and Sensors

  1. Humidity measurement range: 0 - 100% HR
  2. Temperature range: -40°C - +125°C
  3. Accuracy: ±1.5% HR (humidity), ±0.1°C (temperature)
  4. Operating voltage: 1.08V - 3.6V
  5. Current consumption: 260µA (measurement), 80nA (sleep)
  6. Communication interface: I²C
  7. Price: $1.70


VEML7700

The VEML7700 is a high-accuracy ambient light sensor with a 16-bit resolution. It integrates a photodiode, low noise amplifier, and ADC in a compact package. With I²C communication, it is ideal for precise ambient light measurements in various lighting conditions.

Components and Sensors

  1. Ambiant light range: 0lx - 140,000lx
  2. Ambiant light resolution: 0.0042lx
  3. Operating voltage: 2.5V - 3.6V
  4. Current consumption: 100µA (active), 0.5µA (sleep)
  5. Communication interface: I²C
  6. Price: $1.00


MAX17048

The MAX17048 is a compact, low-power fuel gauge IC designed for lithium-ion batteries in handheld and portable applications. It monitors battery voltage and estimates the state of charge (SoC) with minimal power consumption.

Components and Sensors

  1. Voltage measurement accuracy: ±7.5mV
  2. Operating voltage: 2.5V - 4.5V
  3. Current consumption: 23µA (active), 3µA (sleep)
  4. Communication interface: I²C
  5. Price: $3.00


DS3231

The DS3231 is a low-cost, highly accurate I²C real-time clock. It maintains time even when the main power is off, using a backup battery. In this project, the RTC allows each environmental sample to be timestamped directly by the microcontroller. This ensures time accuracy even if the server is offline, and allows reliable storage of timestamped data into the FRAM.

The CR1220 coin cell battery is used as a backup power source for the DS3231 RTC to maintain timekeeping when the main power is off. It has a nominal capacity of 40 mAh.

Given:

  1. Battery capacity = 40 mAh
  2. RTC current draw in sleep mode = 1 µA = 0.001 mA
  3. Self-discharge rate ā‰ˆ 1% per year (at 20 °C)

Estimated theoretical lifetime (ignoring self-discharge):

lifetime = (40 Ɨ 10āˆ’3 Ah) / (1 Ɨ 10āˆ’6 A) = 40000 / 8766 years ā‰ˆ 4.56 years

Components and Sensors

  1. Timekeeping accuracy: ±2ppm from 0°C to +40°C (ā‰ˆ ±1 minute/year)
  2. Operating voltage: 2.3V - 5.5V
  3. Current consumption: 200µA (active), 110µA (stand by), 1µA (sleep)
  4. Battery Backup: Supports CR2032 (or similar coin cell)
  5. Communication interface: I²C
  6. Price: $3.00


MB85RC256V

The MB85RC256V is a non-volatile FRAM (Ferroelectric Random Access Memory) chip organized as 32,768 Ɨ 8 bits. Unlike traditional flash or EEPROM memory, FRAM combines fast write speeds, high endurance, and ultra-low power consumption. In this project, it's used to temporarily store environment data when the server is unreachable.

The MB85RC256V FRAM has a total capacity of 32,768 bytes. At the beginning of memory, I store metadata related to the calibration and storage system:

  1. Cdry capacitance (uint32_t): 4 bytes
  2. Cwet capacitance (uint32_t): 4 bytes
  3. Number of stored reports (uint16_t): 2 bytes
  4. Index of the first report (uint16_t): 2 bytes
  5. Timestamp of the last synchronization with the DS3231 RTC (uint32_t): 4 bytes

This totals 4 + 4 + 2 + 2 + 4 = 16 bytes used for metadata.

Each report has the following structure:

struct __attribute__((packed)) Report {
uint32_t timestamp;
int16_t temperature;
uint8_t humidity;
uint8_t soil_moisture;
uint16_t light;
uint8_t battery;
};

This structure uses 11 bytes per report: 4 bytes (timestamp) + 2 bytes (temperature) + 1 bytes (humidity) + 1 bytes (soil moisture) + 2 bytes (lux) + 1 bytes (battery) = 11 bytes

I decide to store a maximum of 2976 reports. So the total memory used is 16 bytes + 2976 Ɨ 11 bytes = 32,752 bytes, which fits within the 32,768-byte limit.

At a rate of one report every 15 minutes, that's 96 reports per day (4 per hour Ɨ 24 hours). Thus, 2976 / 96 = 31 days of data can be stored — allowing up to one month of reporting history.

In the project code, a rolling (circular buffer) system is implemented to manage report storage in the FRAM. This means that once the maximum number of 2976 reports is reached, new reports will overwrite the oldest ones, ensuring that the memory always contains the most recent data.

Reports are only written to the FRAM if the server is not reachable at the time the report is generated. However, if there are reports already stored in the FRAM and the server becomes reachable again, then those stored reports are sent to the server and subsequently deleted from the FRAM. This guarantees data persistence during server outages and ensures the memory is cleared as soon as data is successfully synchronized.

Components and Sensors

  1. Memory Type: FRAM (Ferroelectric Random Access Memory)
  2. Capacity: 256Kbit = 32KB
  3. Operating voltage: 2.7V - 5.5V
  4. Current consumption: 130µA (active), 56µA (sleep)
  5. Read/write endurance: 1012 write cycles per byte
  6. Data retention: > 200 years at + 35°C
  7. Communication interface: I²C
  8. Price: $2.00


ESP32 C6 MINI

The ESP32‑C6 Mini is a compact and efficient microcontroller developed by Espressif, specifically designed for connected (IoT) applications. It operates on a stable 3.3 V power supply, typically provided by an onboard LDO regulator sourced from USB or battery power. During development, the board is easily programmable via its integrated USB-to-serial/JTAG interface, requiring no external programmer.

In this project, all peripheral components — FRAM, DS3231 RTC, SHT40 temperature/humidity sensor, VEML7700 light sensor, and MAX17048 battery monitor — communicate with the ESP32‑C6 Mini over the I²C bus.

Components and Sensors

  1. Operating voltage: 3V - 3.6V
  2. Wireless connectivity: Wi‑Fi 6, Bluetooth 5.x, Thread, Zigbee
  3. Programming interface: USB‑serial via built-in USB port
  4. Price: $5.00

PCB Specification

firebeetle2.png
PCB_schematic.PNG
PCB.PNG
PCB_3D.PNG

The PCB design is based on the FireBeetle 2 ESP32-C6, a low-power IoT main control board designed based on the ESP32-C6 chip. This board is optimized for energy-efficient applications, with an ultra-low deep sleep current of just 16µA. Moreover, the board can be powered with a lithium battery, and supports battery charging using solar panels.

PCB Specification

The project also integrates the CN3165 chip to manage lithium-ion battery charging, particularly suited for solar-powered systems.

Instead of using the HM6245 as a low-power LDO regulator with a quiescent current of 2 µA, I opted for the TPS62840 — a high-efficiency step-down (buck) converter that offers an ultra-low quiescent current of typically 60 nA, making it ideal for ultra-low-power applications.

To minimize power consumption during deep sleep, the sensors are powered through a NTS2101PT1G P-channel MOSFET, which allows the ESP32 to completely cut off their power when not in use. The same approach is used to control power to the water pump.

PCB Specification

PCB Specification PCB Specification

Current Consumption

For the FireBeetle 2 ESP32-C6, the 16 µA deep sleep current is due to the following components:

  1. CN3165 charge leakage current: 1µA
  2. CN3165 BAT pin current in sleep mode: 3µA
  3. HM6245 LDO regulator quiescent current: 2µA
  4. Zero gate voltage drain current of MOSFETs (AO3407A and AO3400A): 3 Ɨ 1µA = 3µA
  5. ESP32 deep sleep current: 7µA


In my current design, the deep sleep current is composed of:

  1. CN3165 charge leakage current: 1µA
  2. CN3165 BAT pin current in sleep mode: 3µA
  3. TPS62840 operating quiescent current: 60nA (negligible)
  4. Zero gate voltage drain current of MOSFETs (AO3407A, AO3400A, and NTS2101PT1G): 5 Ɨ 1µA = 5µA
  5. ESP32 deep sleep current: 7µA
  6. MAX17048 supply current in hibernate mode: 3µA

⇒ Total estimated deep sleep current: ~19µA


āš ļø You can find on internet that the ESP32 consumes only 10µA in deep sleep mode. This power consumption refers to the ESP32 as a standalone chip. If you’re using a development board, they have passive components that use more current. For exemple, classic nodeMCU ESP32 consumes 10mA in deep sleep. It's 1000 times more current than announced 10µA! You can find more accurate information about ESP32 boards here.


When the board is acquiring data, the power consumption is approximately:

  1. ESP32 normal operation: 20mA
  2. Wi-Fi RX and TX peak current: up to 300mA
  3. Sensors total consumption: about 1mA (963µA), broken down as follows:
  4. SHT40: 260 µA
  5. TLC555: 250 µA
  6. VEML7700: 100 µA
  7. MAX17048: 23 µA
  8. DS3231: 200 µA
  9. MB85RC256V (FRAM): 130 µA


Assuming the board consumes an average of 100mA during each data acquisition and transmission, and that each acquisition lasts 5 seconds, we can calculate the daily active time as follows:

  1. Readings are taken every 15 minutes, which means 4 times per hour Ɨ 24 hours = 96 readings per day.
  2. Total active time per day = 96 Ɨ 5 seconds = 480 seconds = 8 minutes.


During these 8 minutes, the board draws 100mA. For the remaining time of the day (24 hours - 8 minutes), the board is in deep sleep mode, consuming only 19µA. If a solar panel can provide 100mA, it only needs to be fully illuminated for about 10 minutes to fully recharge the battery and cover the board’s energy consumption for the entire day.

The average power consumption over one hour is calculated as follows:

  1. Active time per hour: The board is active for 4 data acquisitions per hour, each lasting 5 seconds. So, the total active time is 4 Ɨ 5s = 20s.
  2. Sleep time per hour: 3600s āˆ’ 20s = 3580s


So the average current over one hour is:

(20s / 3600s) Ɨ 100mA + (3580s / 3600s) Ɨ 19µA = 0.574mA

With this average current consumption of 0.574mA, a 1500 mAh LiPo battery would last approximately:

1500mAh / 0.574mA = 2613h ā‰ˆ 110days

Water Pump Control

pump.PNG

You can see on the PCB that a MOSFET can be used to power a 3 V water pump, or alternatively a relay can be used to control a larger pump. The code integrates support for driving such a pump to water plants. Keep in mind that using this feature will also reduce the overall battery lifetime.

Create an Enclosure

box1.PNG
box2.PNG
box3.PNG
box4.PNG

The final important point is to protect the electronic circuit and the battery in a waterproof enclosure. However, the enclosure must also allow light to pass through at the location of the light sensor. Even though some of the light rays will be reflected, the sensor will still provide indicative values of the light exposure the plant receives. I chose an enclosure printed in transparent UTR-8100 resin. Moreover, to protect the edges of the PCB that are inserted into the soil, nail polish can be used, as suggested in the following article: Protecting Capacitive Soil Moisture Sensors

The enclosure consists of two parts secured together using 10mm spacers and M3 screws. Ventilation is provided by a 3 mm diameter hole located at the bottom of the enclosure and a 5 mm diameter hole positioned on the rear face, allowing airflow through the housing.

The power supply is provided by a 1500 mAh Li-Po battery, specified as 6 mm (thickness) Ɨ 55 mm (length) Ɨ 32 mm (width), equipped with a JST PH 2.0 mm connector. The battery occupies the maximum available volume inside the enclosure; therefore, the use of a battery with greater width or thickness is not permissible, as it would not fit within the housing tolerances.

The Code

germinion_archi_1.png

And now for the part you’ve all been waiting for — how to actually upload the code and make everything come to life! 🤩


The code provided here enables the microcontroller to generate and send measurement reports to an MQTT broker. This broker can run on many platforms—one of the most convenient options being Home Assistant installed on a Raspberry Pi. With the system configured, each report sent by the device can be processed, stored, and visualized through your preferred smart home dashboard.


Install Arduino IDE

I used the Arduino IDE to uplaod the code to the ESP32 C6. After installing the IDE, the additionnal esp32 board package must be added. All the steps required to install this board are detailed here.


Edit the setting file

Once you’ve installed the Arduino IDE, and added support for the ESP32-C6 board, you need to edit some files before uploading the code:

  1. setup_germinion.ino: Set Timezone

Replace the value in this line with your local timezone:

const int timezone_offset = -2; // -2 for GMT+2
  1. env.h: Set Wi-Fi Credentials and Network Configuration

This file configures the ESP32's Wi-Fi connection and associates it with your plant’s topic. Update the following values:

  1. SSID and PASSWORD: Your Wi-Fi credentials.
  2. CHANNEL, IP, GATEWAY, SUBNET, BSID: related to your Wi-Fi network configuration and should be set accordingly.
  3. topic: The unique MQTT topic assigned to your plant, where all its data reports will be published to the MQTT broker.
// WIFI
const char* SSID = "your-ssid";
const char* PASSWORD = "your-wifi-password";
int CHANNEL = 6;

IPAddress IP(192, 168, 1, 120);
IPAddress GATEWAY(192, 168, 1, 1);
IPAddress SUBNET(255, 255, 0, 0);
const uint8_t BSID[] = {0x38, 0xB5, 0xC9, 0xC5, 0x23, 0xA0};

// TOPIC OF THE PLANT
const String topic = "your-topic-id";


Upload the setup script

The first step is to upload setup_germinion.ino to the board. This script initializes the internal time of the DS3231, then prompts you to place the moisture sensor in dry air and water to calibrate it. The calibration values are stored in the FRAM, along with the report counter and the first report index, both initialized to 0.


Upload the main code

You can now upload germinion_mqtt.ino to the board.


āš ļø In settings.h, ENABLE_DEEPSLEEP is set at false to disable deep sleep (useful during development), and DEBUG is set at true to enable serial output for real-time monitoring via the Serial Monitor. Once your setup is finalized and ready, you can switch to #define ENABLE_DEEPSLEEP true and #define DEBUG false.


After completing all these steps, you should start seeing the reports arriving on your MQTT broker by subscribing to your topic šŸ˜Ž

Results

IMG_20250917_125633.jpg
IMG_20250916_161052.jpg
IMG_20250822_133154.jpg
IMG_20250822_133200.jpg
Capture.PNG
app2.jpg
app3.jpg

I installed my PCB, protected inside its enclosure, on a planter on my balcony in mid-July. I didn’t power the device using solar panels because I wanted to test the battery’s lifespan. I also didn’t connect a water pump to the PCB.

The battery began showing signs of weakness in mid-November during a cold spell, eventually dropping to 6% after four months without any recharging.

All the data was successfully sent to my Home Assistant dashboard, which runs locally on a Raspberry Pi.

Results

I also wanted to access the measurements from my phone, wherever I was. So, I developed a React Native app that lets me visualize all the data. To make this work, I modified the ESP32 code so it would send data to a Flask API instead of the MQTT broker.

Results Results

And I have to say — I’m really happy with these results! šŸŽ‰

This project took me almost two years to complete, but it taught me a tremendous amount about PCB design, building a Flask API, and developing an Android app with React Native. A long journey, but absolutely worth it.

I also installed a second unit in my office to monitor indoor temperature and humidity. It was deployed at the same time as the outdoor unit and is currently still at 32% battery. So for indoor plants — where the battery isn’t exposed to freezing temperatures — the system can run for roughly six months on a single charge, which is pretty great! šŸŒ±šŸ”‹


If you found this project helpful and would like to support my work, you can buy me a coffee — it really means a lot! ā˜•šŸ˜Š

https://buymeacoffee.com/elfenstomp

Bonus: Experiments and Failures

v1.jpg
v2.jpg
v3.jpg
test1.jpg
IMG_20250808_111232.jpg
IMG_20250822_133224.jpg

Over the two years I spent on this project, I went through several PCB iterations, ran countless tests, and—of course—made my fair share of mistakes. Here’s a short summary of my adventures.

I started with a first version built from easily available modules (DHT22, off-the-shelf capacitive moisture sensor, etc.). It worked, but it consumed way too much power.

So I designed a shield containing all the sensors that plugged directly onto a FireBeetle C2 S6 board. The shield worked great, but I really wanted everything—including the microcontroller and voltage regulators—to live on a single PCB.

After spending way too much time selecting components, studying the ESP32-C6 Mini documentation, and designing the board, I ordered my ā€œcompleteā€ version. And then… after hours of trying to flash it, I discovered that I had wired USB P and USB N to the wrong GPIO pins... Version four finally did the job šŸŽ‰

I also made a dedicated test PCB just to verify the TLC555 setup for measuring soil moisture.

For the resin enclosure, I needed ventilation holes to prevent condensation and to allow the temperature and humidity sensor to be exposed to ambient air. To test the enclosure’s watertightness, I placed a tissue inside and ran it under the tap. On the first version, water climbed inside through the holes via capillary action… not ideal. The second iteration solved the problem, and I took the opportunity to slim the enclosure down from 3 cm to 2 cm thick.

And of course, there were plenty of bugs—both in the ESP32 code and in the React Native app. But hey, that’s just the developer’s curse when working on a big DIY project like this šŸ˜„