devices.esphome.io

Smart Plant

Smart Plant

Made for ESPHome Logo

Device Type: sensor
Electrical Standard: global
Board: esp32
Difficulty: Plug-n-flash, 2/5

Smart Plant

Pinout

ESP32-S2Sensore-PaperOther
GPIO 00Flash button
GPIO 01Soil moisture
GPIO 02Battery volts
GPIO 03Solar charge
GPIO 04Sensor powering
GPIO 10CS
GPIO 11MOSI
GPIO 12CLK
GPIO 13DC/MISO
GPIO 14BUSY
GPIO 15RST
GPIO 33SDA
GPIO 34SCL

Basic Configuration

substitutions:
device_name: "smart-plant"
friendly_name: "Smart Plant"
project_name: "smart.plant"
project_version: "2.2"
ap_pwd: "smartplant"
esphome:
name: "${device_name}"
name_add_mac_suffix: true
project:
name: "${project_name}"
version: "${project_version}"
# Initialize the IIC bus immediatelly after the powering the sensors
on_boot:
priority: 600
then:
- lambda: |-
Wire.begin();
delay(100);
- script.execute: consider_deep_sleep
esp32:
board: esp32-s2-saola-1
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
# Enable Over The Air updates
ota:
#Public location of this yaml file
dashboard_import:
package_import_url: github://JGAguado/Smart_Plant/docs/source/files/configuration.yaml@V2R1
import_full_config: false
# Enable fallback hotspot (captive portal) in case wifi connection fails
captive_portal:
improv_serial:
wifi:
ap:
password: "${ap_pwd}"
i2c:
scl: GPIO34
sda: GPIO33
scan: false
id: bus_a
spi:
clk_pin: GPIO12
mosi_pin: GPIO11
image:
- file: "https://smart-plant.readthedocs.io/en/v2r1/_images/Lemon_tree_label_page_1.png"
id: page_1_background
font:
- file: "gfonts://Audiowide"
id: font_title
size: 20
- file: "gfonts://Audiowide"
id: font_subtitle
size: 15
- file: "gfonts://Audiowide"
id: font_parameters
size: 15
- file: 'gfonts://Material+Symbols+Outlined'
id: font_icon
size: 20
glyphs:
- "\U0000ebdc" # battery empty
- "\U0000ebd9" # battery 1 bar
- "\U0000ebe0" # battery 2 bar
- "\U0000ebdd" # battery 3 bar
- "\U0000ebe2" # battery 4 bar
- "\U0000ebd4" # battery 5 bar
- "\U0000e1a4" # battery full
- "\U0000e627" # sync
time:
- platform: homeassistant
id: esptime
switch:
- platform: gpio
pin: GPIO4
id: exc
name: "Excitation switch"
icon: "mdi:power"
restore_mode: ALWAYS_ON
sensor:
# Battery level sensor
- platform: adc
name: "Battery Voltage"
id: batvolt
pin: GPIO2
accuracy_decimals: 2
update_interval: 1s
unit_of_measurement: "V"
attenuation: 12db
icon: mdi:battery-medium
filters:
- multiply: 2.15
- median:
window_size: 7
send_every: 7
send_first_at: 7
on_value:
then:
- component.update: batpercent
- platform: template
name: "Battery %"
id: batpercent
lambda: return id(batvolt).state;
accuracy_decimals: 0
unit_of_measurement: "%"
icon: mdi:battery-medium
filters:
- calibrate_linear:
method: exact
datapoints:
- 0.00 -> 0.0
- 3.30 -> 1.0
- 3.39 -> 10.0
- 3.75 -> 50.0
- 4.11 -> 90.0
- 4.20 -> 100.0
- lambda: |-
if (x <= 100) {
return x;
} else {
return 100;
}
if (x <0) {
return 0;
}
# Temperature and humidity sensor
- platform: aht10
variant: AHT20
i2c_id: bus_a
temperature:
name: "Temperature"
id: temp
icon: "mdi:thermometer"
humidity:
name: "Air Humidity"
id: hum
icon: "mdi:water-percent"
update_interval: 3s
# Light sensor
- platform: veml7700
address: 0x10
update_interval: 1s
ambient_light:
name: "Ambient light"
id: light
icon: "mdi:white-balance-sunny"
actual_gain:
name: "Actual gain"
# Capacitive soil moisture sensor
- platform: adc
pin: GPIO1
name: "Soil Moisture"
id : soil
icon: "mdi:cup-water"
update_interval: 1s
unit_of_measurement: "%"
attenuation: 12db
filters:
- median:
window_size: 5
send_every: 5
- calibrate_linear:
- 1.25 -> 100.00
- 2.8 -> 0.00
- lambda: if (x < 1) return 0; else if (x > 100) return 100; return (x);
accuracy_decimals: 0
display:
- platform: waveshare_epaper
cs_pin: GPIO10
dc_pin: GPIO13
busy_pin: GPIO14
reset_pin: GPIO15
rotation: 270
model: 2.90inv2
id: my_display
update_interval: never
full_update_every: 1
pages:
- id: page1
lambda: |-
#define H_LEFT_MARGIN 4
#define H_RIGHT_MARGIN 280
#define H_CENTER 128
#define V_WEATHER 0
#define V_CLOCK 1
#define V_WIFI 30
#define V_VOLTAGE 60
#define V_BATTERY 90
it.image(0, 0, id(page_1_background));
// // Battery
float battery_perc = id(batpercent).state;
int battery_range = battery_perc / 16 ;
battery_range = (battery_range > 6) ? 6 : battery_range;
battery_range = (battery_range < 0) ? 0 : battery_range;
const char* battery_icon_map[] = {
"\U0000ebdc", // battery empty
"\U0000ebd9", // battery 1 bar
"\U0000ebe0", // battery 2 bar
"\U0000ebdd", // battery 3 bar
"\U0000ebe2", // battery 4 bar
"\U0000ebd4", // battery 5 bar
"\U0000e1a4" // battery full
};
it.printf(278, 1, id(font_icon), TextAlign::TOP_LEFT, battery_icon_map[battery_range]);
it.printf(278, 1, id(font_subtitle), TextAlign::TOP_RIGHT,
"%3.0f%%", battery_perc);
// Date
it.strftime(278, 18, id(font_subtitle), TextAlign::TOP_RIGHT,
"%H:%M %d/%m", id(esptime).now());
it.printf(278, 18, id(font_icon), TextAlign::TOP_LEFT, "\U0000e627");
// Parameters
// Drawing the marker over the gauge
float pi = 3.141592653589793;
float alpha = 4.71238898038469; // Defined as the gauge angle in radians (270deg)
float beta = 2*pi - alpha;
int radius = 22; // Radius of the gauge in pixels
int thick = 7; // Size of the marker
// *** Moisture ***
int min_range = 0;
int max_range = 100;
int xc = 80;
int yc = 50;
float measured = id(soil).state;
if (measured < min_range) {
measured = min_range;
}
if (measured > max_range) {
measured = max_range;
}
float val = (measured - min_range) / abs(max_range - min_range) * alpha;
int x0 = static_cast<int>(xc + radius + radius * cos(pi / 2 + beta / 2 + val));
int y0 = static_cast<int>(yc + radius + radius * sin(pi / 2 + beta / 2 + val));
int x1 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val + 0.1));
int y1 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val + 0.1));
int x2 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val - 0.1));
int y2 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val - 0.1));
it.line(x0, y0, x1, y1);
it.line(x1, y1, x2, y2);
it.line(x2, y2, x0, y0);
it.printf(xc + radius, yc + 1.7*radius, id(font_parameters), TextAlign::TOP_CENTER,
"%.0f%%", id(soil).state);
// *** Light ***
min_range = 0;
max_range = 3775;
xc = 134;
yc = 70;
measured = id(light).state;
if (measured < min_range) {
measured = min_range;
}
if (measured > max_range) {
measured = max_range;
}
val = (measured - min_range) / abs(max_range - min_range) * alpha;
x0 = static_cast<int>(xc + radius + radius * cos(pi / 2 + beta / 2 + val));
y0 = static_cast<int>(yc + radius + radius * sin(pi / 2 + beta / 2 + val));
x1 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val + 0.1));
y1 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val + 0.1));
x2 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val - 0.1));
y2 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val - 0.1));
it.line(x0, y0, x1, y1);
it.line(x1, y1, x2, y2);
it.line(x2, y2, x0, y0);
it.printf(xc + radius, yc + 1.7*radius, id(font_parameters), TextAlign::TOP_CENTER,
"%.0flx", id(light).state);
// *** Temperature ***
min_range = -10;
max_range = 50;
xc = 188;
yc = 50;
measured = id(temp).state;
if (measured < min_range) {
measured = min_range;
}
if (measured > max_range) {
measured = max_range;
}
val = (measured - min_range) / abs(max_range - min_range) * alpha;
x0 = static_cast<int>(xc + radius + radius * cos(pi / 2 + beta / 2 + val));
y0 = static_cast<int>(yc + radius + radius * sin(pi / 2 + beta / 2 + val));
x1 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val + 0.1));
y1 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val + 0.1));
x2 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val - 0.1));
y2 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val - 0.1));
it.line(x0, y0, x1, y1);
it.line(x1, y1, x2, y2);
it.line(x2, y2, x0, y0);
it.printf(xc + radius, yc + 1.7*radius, id(font_parameters), TextAlign::TOP_CENTER,
"%.0f°C", id(temp).state);
// *** Humidity ***
min_range = 20;
max_range = 80;
xc = 242;
yc = 70;
measured = id(hum).state;
if (measured < min_range) {
measured = min_range;
}
if (measured > max_range) {
measured = max_range;
}
val = (measured - min_range) / abs(max_range - min_range) * alpha;
x0 = static_cast<int>(xc + radius + radius * cos(pi / 2 + beta / 2 + val));
y0 = static_cast<int>(yc + radius + radius * sin(pi / 2 + beta / 2 + val));
x1 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val + 0.1));
y1 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val + 0.1));
x2 = static_cast<int>(xc + radius + (radius+thick) * cos(pi / 2 + beta / 2 + val - 0.1));
y2 = static_cast<int>(yc + radius + (radius+thick) * sin(pi / 2 + beta / 2 + val - 0.1));
it.line(x0, y0, x1, y1);
it.line(x1, y1, x2, y2);
it.line(x2, y2, x0, y0);
it.printf(xc + radius, yc + 1.7*radius, id(font_parameters), TextAlign::TOP_CENTER,
"%.0f%%", id(hum).state);
deep_sleep:
id: deep_sleep_control
# run_duration: 5s
sleep_duration: 1h
script:
- id: consider_deep_sleep
mode: queued
then:
- delay: 5s
- component.update: my_display
- delay: 5s
- if:
condition:
sensor.in_range:
id: batpercent
above: 95
then:
- deep_sleep.prevent: deep_sleep_control
else:
- deep_sleep.enter: deep_sleep_control
- delay: 25s
- script.execute: consider_deep_sleep
Edit this page on GitHub