devices.esphome.io
Smart Plant
Smart Plant
Device Type: sensorElectrical Standard: globalBoard: esp32Difficulty: Plug-n-flash, 2/5Project URL: https://smart-plant.readthedocs.io
      
  
        
Pinout
| ESP32-S2 | Sensor | e-Paper | Other | 
|---|---|---|---|
| GPIO 00 | Flash button | ||
| GPIO 01 | Soil moisture | ||
| GPIO 02 | Battery volts | ||
| GPIO 03 | Solar charge | ||
| GPIO 04 | Sensor powering | ||
| GPIO 10 | CS | ||
| GPIO 11 | MOSI | ||
| GPIO 12 | CLK | ||
| GPIO 13 | DC/MISO | ||
| GPIO 14 | BUSY | ||
| GPIO 15 | RST | ||
| GPIO 33 | SDA | ||
| GPIO 34 | SCL | 
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 logginglogger:
# Enable Home Assistant APIapi:
# Enable Over The Air updatesota:
#Public location of this yaml filedashboard_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 failscaptive_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