Make a plant-biosignal sensor with M5Stack and ESPHome
Plants make small electrical signals, from microvolts to some millivolts. The signals change when the light comes on, when the water becomes low, or when the plant has stress. This guide shows how to make the sensor that records these signals. The sensor has an M5Stack ESP32, an ECG front-end chip and ESPHome, and the cost is approximately NZ$110. You will record the raw trace in Home Assistant and know how to read the rhythm of each day and the stress events. The sensor is not a validated meter for water or nutrients, but it shows the electrical activity of the plant in real time.
Purpose and scope
Plants make small electrical signals. When a plant changes in response to light, water, a wound or a change in nutrients, ions move across the cell membranes. This movement makes a voltage of less than one millivolt. You can read this voltage with electrodes on the stem[3]. Commercial units, for example the Vivent VITA1, do the same. They then use trained models to calculate the water condition and the stress condition from the drift of the signal[5].
To read a plant is the same electrical problem as to read a heartbeat. The voltage is small, it has noise, and it has high impedance. You must amplify it and not increase the noise. Thus the AD8232, a standard ECG front-end chip with a low cost, is correct for plants without changes[1]. Connect this chip to an ESP32 and ESPHome, and you have a plant-biosignal sensor that records data. The cost is approximately NZ$110.
- This sensor does the same data acquisition. It records the raw signal and the rhythm of each day. It also records the large deflections after a stress event and the change when the light starts and stops. One investigation gave a detection rate of approximately 85% for the light changes, with its protocol. It is not sure that you get the same value with this type of hardware[3]. The sensor records all of this in Home Assistant.
- This sensor does not give the model of the commercial product. The N, P, K and Ca readings of a VITA1 come from a trained model and a library of selected signals. You get the millivolts. With time, you can find your correlations.
- ESPHome reads the sensor at set intervals. It does not record waveforms. To see fast spikes of less than one second, you must have the high-rate sketch at the end of this guide. For trends, the reading at set intervals is the correct method. It samples at a higher rate than the dashboard of a VITA1, which shows a value each 5 minutes.
Definitions
Evidence and limitations
We want each paper to be correct. Thus we examine each paper for information that is not solid. This information comes from the view of one grower, from a small number of sources, or from grower methods that have no controlled test. We identify this information, and we do not show it as solid.
Frequently there is no paper for the decision that you must make. Then we use the reports of other growers and the methods that work in the rooms that we know. This information can help you, but it is not the result of a laboratory test. Use the methods that work for your plants, for your room and for your meters. If a table does not agree with your crop, use the data from your crop and record the difference.
- Plants make biopotentials that you can measure. Noise at the electrode is an important problem in engineering.
- A DIY hobby system with an AD8232 and ESPHome to record data.
- The estimate of NPK, of the irrigation that is necessary, or of the stress condition, from only the signal in millivolts of a DIY system.
- The outputs of models of commercial products that you make again without the trained library of the product.
- Papers on the classification of emotional states for decisions about cultivation.
If you find an error, make a report. We will correct it. Make the report on GitHub, with the name of the paper and the information that is incorrect. If you have a source, include it: Make a report of an accuracy problem. The regulations, labels and licenses of your area always override each procedure in these papers. The label weak in a paper shows the points where the risk of an error is highest.
The signal chain
The sensor is a chain from the plant to the dashboard. Two electrodes measure the stem and one electrode is the reference in the soil. The AD8232 amplifies and filters the signal. The ADS1115 digitizes it. The ESP32 sends it to Home Assistant.
Plant signals are a maximum of some millivolts. The plant cannot supply much current. Thus each resistance between the plant and the ADC decreases the signal before it goes to the ADC. The ADS1115 without an amplifier has no common-mode rejection and no reference drive. Thus the mains hum is larger than the plant signal in the reading.
The AD8232 gives you the gain. It has a band-pass filter that lets only the frequencies of the plant signals go through. It also has a driven reference electrode in the soil, and this electrode holds the reading stable[1]. Thus the reading shows the signal and not only noise.
Bill of materials
Provisional and experimental: This sensor records biopotentials as relative values, for education and to find correlations. It is not a validated meter for water, nutrients or stress. Do not use only this signal to make a decision about irrigation or feed.
The cost is approximately NZ$110. The M5Stack parts are Grove modules that you connect together. The two parts that are not from M5Stack are the AD8232 front-end and the ADS1115 ADC. These two parts are the usual alternatives with a low cost, because there is no biopotential amplifier in the Grove modules. The prices are approximate values for the middle of 2026.
| Part | Function | Where to get it | Approximate NZD |
|---|---|---|---|
| M5StickC PLUS2 ESP32 with screen and LiPo battery | Controller and display | M5Stack shop (end of life, EOL: M5StampS3 replaces it) | 32 |
| ENV IV Unit (SHT40 + BMP280) | Air temperature, humidity, pressure | M5Stack shop | 14 |
| DLight Unit (BH1750) | Light intensity (lux) | M5Stack shop | 12 |
| 1-to-3 HUB Unit (passive I2C splitter) | Connect three devices to one bus | M5Stack shop | 6 |
| AD8232 ECG module (SparkFun SEN-12650 or an equivalent) | Biopotential front-end [not from M5Stack] | SparkFun · equivalent on AliExpress, approximately NZ$9 | 9 |
| ADS1115 breakout (16-bit I2C ADC) | Digitize the analog output [not from M5Stack] | Adafruit product 1085 · equivalent, approximately NZ$6 | 6 |
| Electrodes: Ag/AgCl EEG cups or stainless probes with snap leads | Skin contact [not from M5Stack] | SparkFun sensor cable and electrode pads, or a medical set of Ag/AgCl electrodes | 14 |
| Electrode gel (Ten20) and micropore tape | Contact with low impedance | Pharmacy | 9 |
| DuPont jumper wires, a small prototype board, heatshrink | Connections between the AD8232 and the ADS1115 | Your spare parts, or an electronics shop | 8 |
| Total: approximately NZ$110. If the controller is an M5StampS3 and not the StickC, the total is less than NZ$95. The alternative does not have the screen that is on the board of the StickC. | |||
M5Stack stopped the production of the StickC PLUS2 and the ENV IV. But resellers (RobotShop, Botland, TinyTronics) continue to have them in stock, and they have the most tutorials. If you want current parts, the M5StampS3 replaces the controller and the ENV Pro replaces the ENV IV. In the firmware, only two I2C pin numbers change.
Wiring
After the controller, the sensor has one I2C bus. The HUB connects this bus to three devices. The only analog wire in the sensor goes from the output of the AD8232 to the A0 pin of the ADS1115. The electrodes connect to the header with three pins of the AD8232.
| From | Pin | To | Pin or target |
|---|---|---|---|
| M5 Grove Port A | SDA (G32) | HUB → all I2C devices | SDA |
| M5 Grove Port A | SCL (G33) | HUB → all I2C devices | SCL |
| M5 Grove Port A | 5V / GND | HUB → ENV and DLight. 3.3V to ADS1115 and AD8232 | VDD / GND |
| AD8232 | OUTPUT | ADS1115 | A0 |
| AD8232 | 3.3V / GND | controller 3.3V / GND | — |
| AD8232 | LA / RA / RL | electrodes | upper stem, lower stem, soil |
Use the 3.3 V rail for the chip. Do not use the 5 V USB rail, because it has more noise. The center point of the output and the noise floor change when the supply changes. Keep the electrode leads short and twisted. Put the leads away from lights, ballasts and pumps. A battery (the LiPo of the StickC) gives less noise than USB power.
Electrodes and their position
Contact impedance is the resistance between the metal of the electrode and the tissue of the plant. A bad connection decreases the signal and lets noise go in before the signal goes to the amplifier. The AD8232 does all the work after this point. The quality of the electrode contact is the one variable that you control. You can use one of these two alternatives[4]:
This alternative gives the best signal quality. Fill the cup with conductive gel and attach it to the stem with tape. It does not cause a wound in the plant, but the gel becomes dry. Replace the gel after some days.
Put the probe 2 to 3 mm (0.08 to 0.12 in) below the epidermis. This alternative is more stable. It is almost the same as the method of commercial pin contacts, which measure the signal in the plant. But it causes a wound in the plant. Thus put the probe in the plant one time only, and use a clean sterilized probe.
- 1Put the pair of electrodes along the stemPut LA (sense +) on the upper stem near a node. Put RA (sense −) 5 to 15 cm (2 to 6 in) below LA on the same stem. This pair of electrodes records the signal that moves along the stem.
- 2Put the reference in the soilPut RL (the driven reference) in the moist soil of the root zone. It removes the common-mode hum. Make sure that you connect RL.
- 3Gel and tapePut a small quantity of Ten20 below each surface contact. Then attach the contact with micropore tape, which gives a light and stable pressure. If the baseline increases slowly and has more noise, the cause is usually that the electrode becomes dry. The cause is not usually a problem in the plant.
- 4Wait until the signal is stableWhen metal touches wet tissue, ion exchange occurs at the surface and makes a small voltage. This voltage is a half-cell potential. Wait 10 to 30 minutes for the voltage to become stable. The first drift comes from the chemistry of the electrode and not from the plant.
Make one clean insertion only, with a probe that you sterilized with alcohol. Do not make a row of holes around the stem, because a row of holes can girdle the stem. A new insertion causes a variation potential. You can use it one time as a known stimulus. Then let the wound close.
ESPHome firmware
The full configuration follows. Put your Wi-Fi, API and OTA secrets in the file secrets.yaml of ESPHome. Install the firmware from the ESPHome dashboard. Home Assistant then finds the sensor automatically. For a StampS3 or a Core2, change the board setting. If it is necessary, also change the two I2C pins.
esphome:
name: plant-biosignal
friendly_name: Plant Biosignal
esp32:
board: m5stick-c # Core2 -> m5stack-core2 ; Stamp S3 -> m5stack-stamps3
framework: { type: arduino }
logger:
api:
encryption: { key: !secret api_key }
ota:
- platform: esphome
password: !secret ota_pw
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# ---- I2C bus (Grove Port A on StickC PLUS2 / Core2) ----
i2c:
sda: 32
scl: 33
frequency: 400kHz
scan: true
# ---- 16-bit ADC hub ----
ads1115:
- address: 0x48
sensor:
# === PLANT BIOPOTENTIAL ===
- platform: ads1115
multiplexer: 'A0_GND' # AD8232 OUTPUT on A0, single-ended
gain: 4.096 # FSR covers the 0-3.3V swing
name: "Plant Biopotential (raw)"
id: bio_raw
unit_of_measurement: "V"
accuracy_decimals: 4
update_interval: 1s
filters:
- median: { window_size: 5, send_every: 1 } # kill spikes
# Centre on the ~1.5V bias -> signed millivolts (the number you watch)
- platform: template
name: "Plant Biopotential"
id: bio_mv
unit_of_measurement: "mV"
accuracy_decimals: 2
update_interval: 1s
lambda: |-
return (id(bio_raw).state - 1.5) * 1000.0; // trim 1.5 to your measured baseline
# Slow envelope = the "trend" a commercial unit charts every 5 min
- platform: template
name: "Plant Signal Trend"
unit_of_measurement: "mV"
accuracy_decimals: 2
lambda: |- return id(bio_mv).state;
filters:
- exponential_moving_average: { alpha: 0.02, send_every: 30 }
# === ENVIRONMENT (ENV IV Unit) ===
- platform: sht4x
address: 0x44
temperature: { name: "Grow Air Temp", id: air_t }
humidity: { name: "Grow Humidity", id: air_rh }
- platform: bmp280_i2c # older ESPHome: use "bmp280"
address: 0x76
temperature: { name: "Grow Baro Temp" }
pressure: { name: "Grow Pressure" }
# === LIGHT (DLight Unit) ===
- platform: bh1750
address: 0x23
name: "Grow Light"
update_interval: 10s
# === DERIVED: VPD (kPa) ===
- platform: template
name: "Grow VPD"
unit_of_measurement: "kPa"
accuracy_decimals: 2
update_interval: 10s
lambda: |-
float t = id(air_t).state;
float rh = id(air_rh).state;
float es = 0.6108 * exp(17.27 * t / (t + 237.3)); // saturation vapour pressure
return es * (1.0 - rh / 100.0);
# === OPTIONAL: electrode lead-off detection ===
binary_sensor:
- platform: gpio
pin: 26 # AD8232 LO+ ; pick a free GPIO for your board
name: "Electrode Lead-off"
device_class: problemAttach the electrodes and wait until they are stable. Then read Plant Biopotential (raw) in Home Assistant. The stable voltage that you read is your baseline. Replace the 1.5 in the lambda with this number. Then Plant Biopotential reads approximately 0 mV when there is no stimulus. It gives positive and negative values around this value[6].
How to read plant-biosignal data
The values are changes of the potential. They are not a calibrated physiological unit. Compare a plant with the same plant at other times. Do not compare plants with each other in raw millivolts.
| Signal that you see | Possible condition |
|---|---|
| The signal increases and decreases smoothly each day, at the same time as the light sensor | The circadian rhythm of a plant in good condition. This rhythm is the baseline. |
| A fast deflection, then the signal goes back to the baseline slowly, after you touch or move the plant | A variation potential. It is the usual signal of a wound or stress. |
| A step change at the same time as the lights come on or go off | Response to light. The data in the reference show a detection rate of approximately 85% with this hardware[3] |
| The baseline increases slowly and has more noise, and there is no plant event | The electrode becomes dry. Put new gel on it. It is not a plant problem. |
| 50/60 Hz noise that is larger than all the other signals | Mains pickup. Make the leads shorter, twist them, and make the soil reference better. |
| A signal that does not change, at a rail (0 or full scale) | A lead is off or a contact has no connection. Examine the binary sensor. |
To get close to the inference of a commercial unit, record data for some weeks. Record the raw trace, the VPD, the light and the irrigation events. Then find your correlations that occur again. For example, the amplitude of the signal can decrease strongly before you see wilt, as a first sign of water stress. The commercial product includes this library of correlations. Send the trace to the plant-state dashboard to use it together with your other telemetry.
Limits, calibration and safety
- Relative values, not absolute values. The values are good for trends and events on one plant. They are not good to compare plants or to read a calibrated number.
- Set the baseline again each time that you attach the electrodes. The half-cell offsets are different each time that you put the electrodes in position. Thus do the calibration at the first start again.
- The noise floor is higher than in commercial units. The items that give the largest decrease in noise are a battery supply, short shielded leads and a small enclosure.
- ESPHome is for trends. To record waveforms, install an Arduino sketch on the controller. The sketch must sample the ADS1115 at its maximum rate of 860 SPS. Or it must sample the AD8232 on an ESP32 ADC pin at 400 Hz. The investigation of plant signals used this rate[2]. The wiring is the same and the firmware is different.
Keep the sensor at a low voltage. Supply it with a battery or with USB. Do not connect the plant electrodes to an item that has a connection to the mains. Use one common ground only. Thus there is no ground loop through the plant.
References
- Marques JAL et al. (2023). From AD8232 to biopotentials sensors: open-source project and benchmark. Electronics (MDPI), 12(4):833. https://www.mdpi.com/2079-9292/12/4/833
- AD8232 bioelectric signal processing with ESP32 (2025). arXiv:2505.18173. (source not from a journal, for example from a manufacturer) https://arxiv.org/pdf/2505.18173
- Plant bioelectrical signals for environmental and emotional state classification (2024). PMC. (AD8232 at 400 Hz; ~85% lamp on/off detection accuracy.) https://www.ncbi.nlm.nih.gov/pmc/articles/PMC12649952/
- Verma A. Interacting with the Flora: an ESP32 + AD8232 + BH1750 + DS18B20 plant-signal rig. Hackster.io. (source not from a journal, for example from a manufacturer) https://www.hackster.io/ankurverma608/interacting-with-the-flora-7ef65f
- Vivent Biosignals, VITA1 plant-driven cultivation sensor (manufacturer documentation). (source not from a journal, for example from a manufacturer) https://vivent-biosignals.com/plant-driven-cultivation/
- ESPHome, ADS1115 4-channel 16-bit A/D converter component (documentation). (source not from a journal, for example from a manufacturer) https://esphome.io/components/sensor/ads1115/
Each citation in the paper, for example [1], refers to an item in this list. Each item is a primary source or an instruction from an authority, unless the item shows a different type. The results of cannabis tissue culture change much with the genotype. Before you use the dilutions, hormone doses and local regulations, make sure that they agree with the primary sources.