Hello
i have the below ESP32 DHT11 CP2104 WIFI Bluetooth Temperature Humidity Soil Moisture Sensor:
Can somebody help me how to integrate it with Home Assistant?
I cannot find any code
Thanks in advance
Hello
i have the below ESP32 DHT11 CP2104 WIFI Bluetooth Temperature Humidity Soil Moisture Sensor:
Can somebody help me how to integrate it with Home Assistant?
I cannot find any code
Thanks in advance
It shouldn’t be too difficult. There is an instructables tutorial with arduino. You can use it to figure out, to which pins the sensors are connected.
I can share with you yaml file which I’m using to integrate soil moisture sensor readings into Home Assistant. It’s going to need some adjustments/removal of unnecessary code to make it work with your module. I’m using standalone sensors, 3 of them connected to single ESP32. My YAML file has also integrated calibration, which requires OLED display and rotary encoder. I’m also using a mosfet to cut power from display and sensors while in deep sleep, connected to GPIO12.
esphome:
name: soil2
platformio_options:
upload_speed: 921600
on_boot:
- priority: 1000
then:
lambda: |-
# turn on power to display and sensors
pinMode(12, OUTPUT);
digitalWrite(12, LOW);
delay(50);
// ESP_LOGI("on_boot", "prio1000");
esp32:
board: lolin32_lite
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
api:
encryption:
key: !secret api_encryption_key
ota:
password: !secret ota_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
# ap:
# ssid: "Blink Fallback Hotspot"
# password: !secret fallback_ap_password
globals:
- id: gcal_wet
type: float
restore_value: true
initial_value: "1.2"
- id: gcal_dry
type: float
restore_value: true
initial_value: "2.8"
- id: last_menu_action
type: int
deep_sleep:
id: deep_sleep_1
run_duration: 10s
sleep_duration: 30min
# wakeup_pin:
# number: GPIO25
# inverted: true
# output:
# - platform: gpio
# id: status_led
# pin: GPIO22
time:
- platform: sntp
id: sntp_time
timezone: Europe/Prague
servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
font:
- file: "fonts/Roboto-Thin.ttf"
id: roboto
size: 12
i2c:
sda: GPIO16
scl: GPIO17
frequency: 800kHz
# const auto display_width = it.get_width();
# const auto display_height = it.get_height();
# // This will render the menu to the right half of the screen leaving the left half for other drawing purposes
# // Arguments: it.menu(x, y, menu, width, height);
# it.menu(display_width, 0, id(my_menu), display_width, display_height);
text_sensor:
- platform: template
name: "${name}_MAC_Address"
lambda: 'return {WiFi.macAddress().c_str()};'
icon: mdi:fingerprint
id: soil2_mac
update_interval: 1d
- platform: wifi_info
ip_address:
name: Adress_IP
id: soil2_ip
display:
- platform: ssd1306_i2c
id: my_oled
model: "SSD1306 128x64"
#auto_clear_enabled: false
address: 0x3C
lambda: |-
//it.printf(0, 0, id(roboto), "Vb:%.2f", id(vbat).state);
//it.printf(0, 14, id(roboto), "MC:%s", id(soil2_mac).state.c_str());
//it.printf(0, 26, id(roboto), "IP:%s", id(soil2_ip).state.c_str());
//it.printf(0, 38, id(roboto), "MC:%s", id(soil2_mac).state.c_str());
//it.printf(0, 50, id(roboto), "IP:%s", id(soil2_ip).state.c_str());
if (id(my_menu).is_active()) {
const auto width = it.get_width();
const auto height = it.get_height();
it.menu(0, 0, id(my_menu), width, height);
} else {
if (id(sntp_time).now().is_valid()) {
it.strftime(0, 0, id(roboto), "%H:%M:%S", id(sntp_time).now());
} else {
it.print(0, 0, id(roboto), "sync in progress ...");
}
// it.printf(0, 20, id(roboto), "Vbat:%.2f", id(vbat).state);
it.printf(0, 20, id(roboto), "%.0f %.0f %.0f Vb:%.2f", id(soil1).state, id(soil2).state, id(soil3).state, id(vbat).state);
//it.printf(0, 20, id(roboto), "%.0f %.0f %.0f Vb:%.2f", id(soil1).state, id(soil2).state, id(soil3).state, id(vbat).state);
//it.printf(0, 40, id(roboto), "%.2f %.2f %.2f", id(soil1).raw_state, id(soil2).raw_state, id(soil3).raw_state);
}
#it.strftime(0, 0, id(roboto), "%Y-%m-%d", id(sntp_time).now());
switch:
- platform: template
id: sleep_switch
optimistic: true
script:
- id: menu_closer
then:
- while:
condition:
display_menu.is_active: my_menu
then:
- lambda: |-
int mtime = id(uptime1).state - id(last_menu_action);
ESP_LOGI("menu_closer", "menu_idle_time=%d", mtime);
- delay: 1s
graphical_display_menu:
id: my_menu
font: roboto
active: false
mode: rotary
on_redraw:
then:
component.update: my_oled
items:
- type: command
text: Cal DRY
on_value:
then:
lambda: |-
id(gcal_dry)=id(soil1).raw_state;
ESP_LOGI("menu_item", "Cal DRY: %f", id(gcal_dry));
id(my_menu).hide();
- type: command
text: Cal WET
on_value:
then:
lambda: |-
id(gcal_wet)=id(soil1).raw_state;
ESP_LOGI("menu_item", "Cal WET: %f", id(gcal_wet));
id(my_menu).hide();
- type: command
text: 'Hide'
on_value:
then:
- display_menu.hide: my_menu
- lambda: |-
ESP_LOGI("display_menu", "menu leave");
id(last_menu_action)=id(uptime1).state;
- script.stop: menu_closer
- deep_sleep.allow: deep_sleep_1
# Encoder to provide navigation
sensor:
- platform: uptime
update_interval: 1s
id: uptime1
- platform: rotary_encoder
id: rotary_encoder1
restore_mode: ALWAYS_ZERO
pin_a:
number: GPIO23
mode:
input: true
pullup: true
pin_b:
number: GPIO18
mode:
input: true
pullup: true
on_anticlockwise:
- display_menu.up: my_menu
- lambda: 'id(last_menu_action)=id(uptime1).state;'
on_clockwise:
- display_menu.down: my_menu
- lambda: 'id(last_menu_action)=id(uptime1).state;'
- platform: adc
pin: GPIO32
name: "soil1"
id: soil1
attenuation: auto
unit_of_measurement: '%'
update_interval: 3s
filters:
lambda: |-
if (x<=id(gcal_wet)) return 100;
if (x>=id(gcal_dry)) return 0;
return (id(gcal_dry)-x)*100/(id(gcal_dry)-id(gcal_wet));
- platform: adc
pin: GPIO35
name: "soil2"
id: soil2
attenuation: auto
unit_of_measurement: '%'
update_interval: 10s
filters:
lambda: |-
if (x<=id(gcal_wet)) return 100;
if (x>=id(gcal_dry)) return 0;
return (id(gcal_dry)-x)*100/(id(gcal_dry)-id(gcal_wet));
- platform: adc
pin: GPIO34
name: "soil3"
id: soil3
attenuation: auto
unit_of_measurement: '%'
update_interval: 10s
filters:
lambda: |-
if (x<=id(gcal_wet)) return 100;
if (x>=id(gcal_dry)) return 0;
return (id(gcal_dry)-x)*100/(id(gcal_dry)-id(gcal_wet));
- platform: adc
pin: GPIO33
name: "vbat"
id: vbat
attenuation: auto
unit_of_measurement: 'V'
update_interval: 10s
filters:
lambda: |-
return x*2;
# A de-bounced GPIO is used to 'click'
binary_sensor:
- platform: gpio
id: rotary_encoder_btn
pin:
number: GPIO19
mode:
input: true
pullup: true
filters:
- invert:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
- lambda: 'id(last_menu_action)=id(uptime1).state;'
- if:
condition:
display_menu.is_active: my_menu
then:
- display_menu.enter: my_menu
else:
- display_menu.show: my_menu
- lambda: |-
ESP_LOGI("display_menu", "menu enter");
id(last_menu_action)=id(uptime1).state;
- deep_sleep.prevent: deep_sleep_1
- script.execute: menu_closer
Hey, since you got it running already in HA, what is your average battery / rechargeable battery lifetime?
Thinking of trying these. I suppose it is also possible to set the interval when the data is read, right?
Is all that config really necessary? I thought once connected to the ESP32 with wifi you just set the MQTT broker and receive the data
I got it working, based on Tasmota config
GPIO16 LEDLink
GPIO22 DHT11
GPIO32 Humidity Sensor
GPIO34 Battery Voltage/Level
esphome:
name: soil
friendly_name: "DIY MORE ESP32 DHT11 Soil Sensor"
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "VoKBaLID9YyLMnm/OKelMY9WMRWhHF4FHSNhKc0iGXY="
ota:
- platform: esphome
password: "97ab7d88b6087dfd43c80440fc9129da"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Soil Fallback Hotspot"
password: "ufgw71Q348ny"
# LEDLink
output:
- platform: gpio
pin: GPIO16
id: led_output
sensor:
# DHT 11
- platform: dht
pin: GPIO22
model: DHT11
temperature:
name: "DHT11 Temperature"
humidity:
name: "DHT11 Humidity"
update_interval: 60s
# SOIL MOISTURE
- platform: adc
pin: GPIO32
name: "Soil Moisture Sensor Voltage"
id: soil_moisture_voltage
unit_of_measurement: "V"
device_class: MOISTURE
update_interval: 5s
attenuation: 11db
accuracy_decimals: 3
- platform: template
name: "Soil Moisture"
id: smoothed_voltage
unit_of_measurement: "%"
lambda: |-
return id(soil_moisture_voltage).state;
update_interval: 1s
filters:
- calibrate_linear: #set your own values here
- 1.29 -> 100.00
- 2.8 -> 0.00
- lambda: |
if (x < 0) return 0;
else if (x > 100) return 100;
else return (x);
accuracy_decimals: 0
# Battery
- platform: adc
pin: GPIO34
name: "Battery Voltage"
id: battery_voltage
update_interval: 60s
accuracy_decimals: 2
filters:
- multiply: 35 # Adjust this based on your voltage divider circuit
- platform: template
name: "Battery Level"
unit_of_measurement: '%'
accuracy_decimals: 0
lambda: |-
float voltage = id(battery_voltage).state;
// Customize the voltage range according to your battery
if (voltage > 4.2) return 100;
else if (voltage < 3.0) return 0;
else return (voltage - 3.0) / (4.2 - 3.0) * 100;
# update_interval: 60s
icon: "mdi:battery"
captive_portal:
web_server:
port: 80
However, the calibration of humidity and battery level should be tweaked more.
Next is to implement deep sleep in a way.
I’m pretty much new to Esphome, so things take time. If someone can contribute or improve it, would be most welcome.
Thank you
Br
I think you should rethink your battery level calculation. Li-ion battery voltage is far from linear. Your code would give 60% level when your battery is at 90% charge.
Thank you, will try to find a better solution.
You cold use calibrate_linear like you do with your moisture sensor.
Something like this:
filters:
- calibrate_linear:
method: exact
datapoints:
- 3.0 -> 0
- 3.4 -> 10
- 4.1 -> 99
- clamp:
min_value: 0
max_value: 100
maybe something like this:
lambda: |-
float voltage = id(battery_voltage).state;
if (voltage > 4.2) {
return 100.0;
} else if (voltage > 4.1) {
return 90.0 + 10.0 * (voltage - 4.1) / 0.1;
} else if (voltage > 3.9) {
return 80.0 + 10.0 * (voltage - 3.9) / 0.2;
} else if (voltage > 3.7) {
return 60.0 + 20.0 * (voltage - 3.7) / 0.2;
} else if (voltage > 3.5) {
return 30.0 + 30.0 * (voltage - 3.5) / 0.2;
} else if (voltage > 3.3) {
return 10.0 + 20.0 * (voltage - 3.3) / 0.2;
} else if (voltage > 3.0) {
return 0.0 + 10.0 * (voltage - 3.0) / 0.3;
} else {
return 0.0;
}
Yep.
Just look at your battery datasheet to match with the curve of lowest discharge current. Or tune it by actual measurements…
will do thanks.
Right now I’m figuring out how to use on_boot so it:
Since this is a few second operation I would need to add a longer deep sleep run_duration on the first boot or reset (EN button) 2 minutes for an example, so I can do OTA.
Thank you
You could make a condition to check if OTA is asked and prevent deep sleep is so.
You can trigger that from HA or Mqtt.
Maybe that"s the best option, as I dont know how can I separate deep sleep wakeup from power on boot or by pressing N3 reset button.
So, then how would esp know when OTA is asked? Im missing that part.how does the process work?
How can I trigger that from HA?
Thank you
You could use deep_sleep.prevent
There is Mqtt example.
On home assistant you could make binary sensor or switch for OTA.
Then check that state on your on_boot component after other stuff you have there, and if state is true, keep awake, if not go to sleep.
Just don’t ask me how to create Home assistant switch, I use Esphome barebones without HA.