Talking plant
Give a house plant a voice. A soil moisture sensor tells you when it is thirsty, and the plant says so on the serial port.
Wiring
| From | To board | Note |
|---|---|---|
| Sensor VCC | 3V3 | 3.3 V keeps the analog output inside the ESP32's range |
| Sensor GND | GND | |
| Sensor AOUT | GPIO 34 | An ADC1 pin |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
// Talking plant: a capacitive soil sensor gives the plant a voice.
const int SOIL = 34; // an ADC1 pin
// Calibrate: read the raw value with the sensor in dry air, then in a glass of water.
const int AIR_RAW = 2800;
const int WATER_RAW = 1200;
const unsigned long EVERY_MS = 10000;
enum Mood { PARCHED, THIRSTY, HAPPY, SOGGY, UNKNOWN };
Mood mood = UNKNOWN;
int readRaw() {
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(SOIL);
delay(5);
}
return sum / 16;
}
Mood moodFor(int pct) {
// A few percent of slack around the extremes, so the plant does not dither.
const int slack = 3;
if (mood == PARCHED && pct < 20 + slack) return PARCHED;
if (mood == SOGGY && pct > 85 - slack) return SOGGY;
if (pct < 20) return PARCHED;
if (pct < 40) return THIRSTY;
if (pct <= 85) return HAPPY;
return SOGGY;
}
void say(Mood next) {
if (next == HAPPY && (mood == PARCHED || mood == THIRSTY)) Serial.println("Plant: Ahh, that's better. Thank you!");
else if (next == HAPPY) Serial.println("Plant: Feeling good.");
else if (next == THIRSTY) Serial.println("Plant: I could use a drink soon.");
else if (next == PARCHED) Serial.println("Plant: I'm parched! Water, please.");
else Serial.println("Plant: Glub. That's plenty, my roots need air too.");
}
void setup() {
Serial.begin(115200);
delay(500);
analogReadResolution(12);
Serial.println("Talking plant. Calibrate AIR_RAW and WATER_RAW in the sketch for your sensor.");
}
void loop() {
int raw = readRaw();
int pct = constrain(map(raw, AIR_RAW, WATER_RAW, 0, 100), 0, 100);
Serial.printf("moisture=%d%% raw=%d\n", pct, raw);
Mood next = moodFor(pct);
if (next != mood) {
say(next);
mood = next;
}
delay(EVERY_MS);
}Wiring
| From | To board | Note |
|---|---|---|
| Sensor VCC | 3V3 | 3.3 V keeps the analog output inside the ESP32's range |
| Sensor GND | GND | |
| Sensor AOUT | GPIO 2 | An ADC1 pin |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
// Talking plant: a capacitive soil sensor gives the plant a voice.
const int SOIL = 2; // an ADC1 pin
// Calibrate: read the raw value with the sensor in dry air, then in a glass of water.
const int AIR_RAW = 2800;
const int WATER_RAW = 1200;
const unsigned long EVERY_MS = 10000;
enum Mood { PARCHED, THIRSTY, HAPPY, SOGGY, UNKNOWN };
Mood mood = UNKNOWN;
int readRaw() {
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(SOIL);
delay(5);
}
return sum / 16;
}
Mood moodFor(int pct) {
// A few percent of slack around the extremes, so the plant does not dither.
const int slack = 3;
if (mood == PARCHED && pct < 20 + slack) return PARCHED;
if (mood == SOGGY && pct > 85 - slack) return SOGGY;
if (pct < 20) return PARCHED;
if (pct < 40) return THIRSTY;
if (pct <= 85) return HAPPY;
return SOGGY;
}
void say(Mood next) {
if (next == HAPPY && (mood == PARCHED || mood == THIRSTY)) Serial.println("Plant: Ahh, that's better. Thank you!");
else if (next == HAPPY) Serial.println("Plant: Feeling good.");
else if (next == THIRSTY) Serial.println("Plant: I could use a drink soon.");
else if (next == PARCHED) Serial.println("Plant: I'm parched! Water, please.");
else Serial.println("Plant: Glub. That's plenty, my roots need air too.");
}
void setup() {
Serial.begin(115200);
delay(500);
analogReadResolution(12);
Serial.println("Talking plant. Calibrate AIR_RAW and WATER_RAW in the sketch for your sensor.");
}
void loop() {
int raw = readRaw();
int pct = constrain(map(raw, AIR_RAW, WATER_RAW, 0, 100), 0, 100);
Serial.printf("moisture=%d%% raw=%d\n", pct, raw);
Mood next = moodFor(pct);
if (next != mood) {
say(next);
mood = next;
}
delay(EVERY_MS);
}Wiring
| From | To board | Note |
|---|---|---|
| Sensor VCC | 3V3 | 3.3 V keeps the analog output inside the ESP32's range |
| Sensor GND | GND | |
| Sensor AOUT | GPIO 3 | An ADC1 pin |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
// Talking plant: a capacitive soil sensor gives the plant a voice.
const int SOIL = 3; // an ADC1 pin
// Calibrate: read the raw value with the sensor in dry air, then in a glass of water.
const int AIR_RAW = 2800;
const int WATER_RAW = 1200;
const unsigned long EVERY_MS = 10000;
enum Mood { PARCHED, THIRSTY, HAPPY, SOGGY, UNKNOWN };
Mood mood = UNKNOWN;
int readRaw() {
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(SOIL);
delay(5);
}
return sum / 16;
}
Mood moodFor(int pct) {
// A few percent of slack around the extremes, so the plant does not dither.
const int slack = 3;
if (mood == PARCHED && pct < 20 + slack) return PARCHED;
if (mood == SOGGY && pct > 85 - slack) return SOGGY;
if (pct < 20) return PARCHED;
if (pct < 40) return THIRSTY;
if (pct <= 85) return HAPPY;
return SOGGY;
}
void say(Mood next) {
if (next == HAPPY && (mood == PARCHED || mood == THIRSTY)) Serial.println("Plant: Ahh, that's better. Thank you!");
else if (next == HAPPY) Serial.println("Plant: Feeling good.");
else if (next == THIRSTY) Serial.println("Plant: I could use a drink soon.");
else if (next == PARCHED) Serial.println("Plant: I'm parched! Water, please.");
else Serial.println("Plant: Glub. That's plenty, my roots need air too.");
}
void setup() {
Serial.begin(115200);
delay(500);
analogReadResolution(12);
Serial.println("Talking plant. Calibrate AIR_RAW and WATER_RAW in the sketch for your sensor.");
}
void loop() {
int raw = readRaw();
int pct = constrain(map(raw, AIR_RAW, WATER_RAW, 0, 100), 0, 100);
Serial.printf("moisture=%d%% raw=%d\n", pct, raw);
Mood next = moodFor(pct);
if (next != mood) {
say(next);
mood = next;
}
delay(EVERY_MS);
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | Capacitive soil moisture sensor, v1.2 or similar, with analog output | |
| 3× | Jumper wires |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
None beyond the ESP32 Arduino core.
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- Every sensor reads a little differently. Set AIR_RAW and WATER_RAW to the raw values you see in dry air and in a glass of water, and the percentages start to mean something.
- Wetter soil gives a lower raw number on capacitive sensors, which is why WATER_RAW is the smaller one.
- Only push the sensor in up to the line printed on it. The electronics at the top are not waterproof; a coat of nail varnish or hot glue on the edges helps it last.
- On a battery, read once and go to deep sleep for half an hour with esp_sleep_enable_timer_wakeup() and esp_deep_sleep_start(). Power the sensor from a GPIO so it is off while the board sleeps.
- The sensor is on an ADC1 pin, so the same wiring keeps working if you add Wi-Fi later.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.