Este post traz uma opção de instalação de sensor de chuva sem usar o comparador que vem no sensor. É uma variação desta implementação.
Essa implementação foi adaptada do repositório GitHub - hugokernel/esphome-rain-detector: ESPHome Rain Detector, o qual é o autor original do código. Realizei algumas alterações e traduções para facilitar a implementação.
Algumas vantagens dessa implemetação:
- Conseguir diferenciar quando está chovendo (o sensor está molhado) de quando a chuva já cessou e o sensor está secando. Isso é possível através da análise da resistência da placa medida pelo NodeMCU.
- Diminuição da oxidação das trilhas da placa devido a aplicação de tensão na placa ocorrer por pequenos intervalos de tempo a cada período de tempo (configurável, no código está aplicação de tensão por 2 segundos a cada 5 segundos se estiver seco ou a cada 30 segundos se estiver molhado).
Materiais utilizados:
-
NodeMCU ESP32
-
Sensor de chuva
-
1 Resistor de 10 k Ω
-
Fonte para o ESP32
Ligação do sensor no ESP:
Código para o ESPHome:
Os comentários do código ajuda a entender a lógica de funcionamento.
substitutions:
#Configurações da placa:
Plataforma: ESP32 #Altere para o modelo da sua placa
TipoPlaca: esp32dev #Altere para o tipo da sua placa
#Configurações do dispositivo:
hostname: 'casa3740espclima' #Hostname do dispositivo na rede
PrefixoNomeDevice: "Clima - "
PrefixoNomeChuvaPlaca: "Clima - Chuva Inst. - "
#Configurações da Rede:
RedeWifi: !secret RedeWifi_IoT #Nome da rede wifi que o dispositivo irá se conectar
SenhaWifi: !secret SenhaWifi_IoT #Senha da rede wifi que o dispositivo irá se conectar
SenhaWifiReconfig: !secret SenhaWifiReconfig #Senha do AP Wifi para reconfiguração do wifi do dispositivo (padrão: o nome do hostame)
EndConfig: ${hostname}.local #10.10.103.104 #${hostname}.local #Endereço para configuração (IP que o esp está acessível atualmente na rede)
WifiOculto: 'False'
WifiFastConnect: 'False'
#Senhas
SenhaAPI: !secret SenhaAPI
SenhaOTA: !secret SenhaOTA
#Pinos
PinoSensorChuvaTensaoPlaca: GPIO19
PinoSensorChuvaMedidaPlaca: GPIO33
#Logger
LoggerLevel: DEBUG
resistor_value: "9.82kOhm" #Usar um resistor de 10kOhm e medir o valor real dele
# Intervalo de resistência mínima (imerso em água) e máxima (seco)
# Igonora valores fora do intervalo
min_resistance: "2700"
max_resistance: "39154"
# Valor da resistência em que uma mudança significativa é considerada como chovendo
rain_detection_threshold: "2000"
# Se a média da resistência estiver este valor acima da última média, o sensor é considerado secando
dry_detection_threshold: "1000"
# Ao inicializar, se a resistência for inferior a este valor, suponha que o sensor esteja molhado (resistência molhada)
wet_resistance_when_booting: "35000"
# +------------------------------+
# | Delay entre 2 medida de chuva |
# +------------------------------+
# Tempo de delay se estiver no modo seco
measure_interval_dry: "5000"
# Tempo de delay se estiver no modo molhado
# Deve ser grande o suficiente para não danificar as trilhas prematuramente, mas pequeno o suficiente para ser reativo o suficiente.
measure_interval_wet: "30000"
# Estabilização antes de ler a resistência
# Um atraso muito curto não permitirá a leitura das resistências baixas
stabilization_delay: 2sec
esphome:
name: $hostname
platform: ${Plataforma}
board: ${TipoPlaca}
on_boot:
then:
- script.execute: test_resistance
- script.execute: measure_loop
- text_sensor.template.publish:
id: text_status
state: "Verificando"
wifi:
networks:
- ssid: ${RedeWifi}
password: ${SenhaWifi}
hidden: ${WifiOculto}
fast_connect: ${WifiFastConnect}
use_address: ${EndConfig}
# Ative o hotspot de fallback (captive portal) caso a conexão wifi falhe
ap:
ssid: WIFI
password: ${SenhaWifiReconfig}
#Habilita um AP Wifi para reconfigurar em caso de perda de conexão com a rede configurada
captive_portal:
#Habilita a atualização de firmware por OTA
ota:
password: ${SenhaOTA}
#Habilita as mensagens de logs pela porta serial e via MQTT
logger:
level: ${LoggerLevel}
#Habilita a api para comunicar com o Home Assistant
api:
password: ${SenhaAPI}
globals:
- id: measure_delay
type: int
restore_value: yes
initial_value: $measure_interval_dry
################################################################################
# BUTTON #
################################################################################
button:
#----------------------------------------------------------------------------#
# BUTTON - DEVICE #
#----------------------------------------------------------------------------#
#Comando reinicilizar esp remotamente
- platform: restart
id: restart_button
name: ${PrefixoNomeDevice} Reiniciar
icon: mdi:restart
#----------------------------------------------------------------------------#
# BUTTON - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
#Executar a medição
- platform: template
id: run_measure
name: "${PrefixoNomeChuvaPlaca} Executa Medição"
on_press:
- script.execute: measure
################################################################################
# BINARY SENSOR #
################################################################################
binary_sensor:
#----------------------------------------------------------------------------#
# BINARY SENSOR - DEVICE #
#----------------------------------------------------------------------------#
#Status (conectado ou desconectado)
- platform: status
id: device_status
name: ${PrefixoNomeDevice} Status
device_class: connectivity
#----------------------------------------------------------------------------#
# BINARY SENSOR - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
# Sensor binário do status chovendo
- platform: template
id: raining
name: "${PrefixoNomeChuvaPlaca} Chovendo"
device_class: moisture
# Sensor binário do status secando
- platform: template
id: drying
name: "${PrefixoNomeChuvaPlaca} Secando"
device_class: moisture
# Sensor binário do status secando
- platform: template
id: power
name: "${PrefixoNomeChuvaPlaca} Tensão Aplicada"
device_class: power
################################################################################
# TEXT SENSOR #
################################################################################
text_sensor:
#----------------------------------------------------------------------------#
# TEXT SENSOR - DEVICE #
#----------------------------------------------------------------------------#
#Sensor de tempo ligado formatado
- platform: template
name: ${PrefixoNomeDevice} Uptime
id: uptime_human
icon: mdi:clock-start
update_interval: 1s
entity_category: diagnostic
#Informações da conexão wifi
- platform: wifi_info
#Endereço IP
ip_address:
id: IP
name: ${PrefixoNomeDevice} Endereço IP
icon: mdi:ip-network
#Nome da Rede
ssid:
id: SSID
name: ${PrefixoNomeDevice} Rede Wifi
icon: mdi:wifi
#Informação da versão da compilação
- platform: version
id: versao
name: ${PrefixoNomeDevice} Versão
icon: mdi:information
#----------------------------------------------------------------------------#
# TEXT SENSOR - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
#Status do sensor de chuva instantaneo
- platform: template
id: text_status
name: "${PrefixoNomeChuvaPlaca} Status"
icon: mdi:information-outline
#lambda: |-
# return {"Verificando"};
################################################################################
# SWITCH #
################################################################################
switch:
#----------------------------------------------------------------------------#
# SWITCH - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
#Switch GPIO para controlar a aplicação de tensão na placa
#(com o objetivo de reduzir a oxidação)
- platform: gpio
id: resistance_bias
name: "${PrefixoNomeChuvaPlaca} Liga Energia Placa"
icon: "mdi:power"
internal: true
pin:
number: ${PinoSensorChuvaTensaoPlaca}
mode: OUTPUT
on_turn_on:
- binary_sensor.template.publish:
id: power
state: on
on_turn_off:
- binary_sensor.template.publish:
id: power
state: off
################################################################################
# SENSOR #
################################################################################
sensor:
#----------------------------------------------------------------------------#
# SENSOR - DEVICE #
#----------------------------------------------------------------------------#
#Sensor de tempo ligado
- platform: uptime
id: device_uptime
internal: true
update_interval: 1s
on_raw_value:
then:
- text_sensor.template.publish:
id: uptime_human
state: !lambda |-
int seconds = round(id(device_uptime).raw_state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return (
(days ? to_string(days) + "d " : "") +
(hours ? to_string(hours) + "h " : "") +
(minutes ? to_string(minutes) + "m " : "") +
(to_string(seconds) + "s")
).c_str();
#Sensor Intensidade Sinal Wifi
- platform: wifi_signal
id: wifi_sinal
name: ${PrefixoNomeDevice} Intensidade Wifi
icon: mdi:signal
update_interval: 10s
#----------------------------------------------------------------------------#
# SENSOR - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
#Leitura da resistência da placa
- platform: adc
id: source_sensor
pin: ${PinoSensorChuvaMedidaPlaca}
name: ADC
attenuation: 11db
internal: true
# É importante ter um intervalo de atualização baixo para que
# a medição tem tempo para ser feita corretamente durante
# a ativação da tensão e levando em consideração o filtro mediano
update_interval: 250ms
filters:
- multiply: 0.846153 # 3.9 (11db tensão de atenuação em escala real) -> 3.3V
- median:
window_size: 7
send_every: 4
send_first_at: 3
#Calculo da resistência real da placa
- platform: resistance
sensor: source_sensor
id: real_resistance_sensor
name: "${PrefixoNomeChuvaPlaca} Resistência"
configuration: DOWNSTREAM
resistor: $resistor_value
reference_voltage: 3.3V
internal: true
icon: "mdi:omega"
filters:
# Nenhum valor menor que 0
- lambda: 'return max((float)$min_resistance, x);'
# Nenhum valor maior que $max_resistance
- lambda: 'return min((float)$max_resistance, x);'
on_value:
then:
- if:
condition:
lambda: |-
return (
id(real_resistance_sensor).state > $min_resistance
&&
// <= é importante para forçar a resistência ao máximo
// para ter um valor para comparar se o
// queda de resistência
id(real_resistance_sensor).state <= $max_resistance
);
then:
- sensor.template.publish:
id: resistance_sensor
state: !lambda "return id(real_resistance_sensor).state;"
# Sensor da última resistência medida
- platform: template
id: latest_resistance_sensor
name: "${PrefixoNomeChuvaPlaca} Resistência - Última Medição"
icon: "mdi:omega"
unit_of_measurement: 'Ω'
disabled_by_default: true
# Sensor da última média de resistência medida
- platform: template
id: latest_average_resistance_sensor
name: "${PrefixoNomeChuvaPlaca} Resistência - Última Média"
icon: "mdi:omega"
unit_of_measurement: 'Ω'
disabled_by_default: true
# Sensor da resistência medida
- platform: template
id: resistance_sensor
name: "${PrefixoNomeChuvaPlaca} Resistência"
icon: "mdi:omega"
unit_of_measurement: 'Ω'
# Sensor da média da resistência medida
- platform: template
id: average_resistance_sensor
name: "${PrefixoNomeChuvaPlaca} Resistência - Média"
icon: "mdi:omega"
unit_of_measurement: 'Ω'
disabled_by_default: true
filters:
- sliding_window_moving_average:
window_size: 15
send_every: 15
#- heartbeat: 2min
################################################################################
# SCRIPTS #
################################################################################
script:
#----------------------------------------------------------------------------#
# SCRIPTS - SENSOR DE CHUVA INSTANTÂNEO (PLACA) #
#----------------------------------------------------------------------------#
#Script para iniciar a medição (aplica a tensão na placa e aguarda estabilizar)
- id: begin_measure
mode: single
then:
- switch.turn_on: resistance_bias
- delay: $stabilization_delay
#Script para finalizar a medição (retira a tensão da placa)
- id: end_measure
mode: single
then:
- switch.turn_off: resistance_bias
#Script que testa se a resistência é menor que quando iniciou (para detectar se está secando)
- id: test_resistance
mode: single
then:
- script.execute: begin_measure
- script.wait: begin_measure
- if:
condition:
lambda: "return id(resistance_sensor).state < $wet_resistance_when_booting;"
then:
- script.execute: its_raining
- script.execute: end_measure
- script.wait: end_measure
#Script que salva os últimos valores de resistência e média
- id: save_current_resistance
then:
- sensor.template.publish:
id: latest_resistance_sensor
state: !lambda "return id(resistance_sensor).state;"
- sensor.template.publish:
id: latest_average_resistance_sensor
state: !lambda "return id(average_resistance_sensor).state;"
# Atualmente:
# * A chuva é detectada com o valor mais recente comparado a um limite
# * Para detectar quando está secando, usamos a média
# Para ser testado:
# - A resistência mais baixa que corresponde a uma saturação completa do sensor é gravado permanentemente:
# Se este valor não for alcançado mas a resistência não diminui durante um período de tempo, sabemos que não está mais chovendo.
#Script para atualizar a média de resistência
- id: update_average_values
mode: single
then:
# Defina a resistência média
- if:
condition:
lambda: "return (id(resistance_sensor).state >= 0 && id(resistance_sensor).state <= $max_resistance);"
then:
- sensor.template.publish:
id: average_resistance_sensor
state: !lambda "return id(resistance_sensor).state;"
#Script de medição da resistência da placa
- id: measure
mode: single
then:
- script.execute: begin_measure
- script.wait: begin_measure
# Inicia o último valor de resistência se não for um número
- if:
condition:
lambda: "return isnan(id(latest_resistance_sensor).state);"
then:
- script.execute: save_current_resistance
- script.wait: save_current_resistance
- if:
condition:
lambda: "return isnan(id(latest_average_resistance_sensor).state);"
then:
- script.execute: save_current_resistance
- script.wait: save_current_resistance
# Apenas para fins de depuração
- logger.log:
level: INFO
format: "> Resistance: %.1f vs latest resistance: %.1f"
args: ['id(resistance_sensor).state', 'id(latest_resistance_sensor).state']
- script.execute: update_average_values
# Teste para chover
- if:
condition:
lambda: "return id(resistance_sensor).state + $rain_detection_threshold < id(latest_resistance_sensor).state;"
then:
- script.execute: its_raining
# Teste para secagem
- if:
condition:
lambda: "return id(average_resistance_sensor).state - $dry_detection_threshold > id(latest_average_resistance_sensor).state;"
then:
- script.execute: its_drying
# Teste para seco
# Assumimos que o sensor está seco quando a resistência atual == resistência máxima
- if:
condition:
lambda: "return id(resistance_sensor).state == $max_resistance;"
then:
- script.execute: its_dry
- script.execute: end_measure
- script.wait: end_measure
#Script para executar o loop de medição da resistência da placa
- id: measure_loop
mode: single
then:
- while:
condition:
lambda: "return true;"
then:
- logger.log:
format: "[Start measure]"
level: INFO
- script.execute: measure
- script.wait: measure
- delay: !lambda "return id(measure_delay);"
#Script para quando detectar que está chovendo
- id: its_raining
mode: single
then:
- logger.log: "It's raining !"
- script.execute: save_current_resistance
- script.wait: save_current_resistance
- homeassistant.event:
event: esphome.its_raining
- text_sensor.template.publish:
id: text_status
state: "Chovendo"
- binary_sensor.template.publish:
id: raining
state: on
- binary_sensor.template.publish:
id: drying
state: off
- globals.set:
id: measure_delay
value: $measure_interval_wet
#Script para quando detectar que a placa está secando
- id: its_drying
mode: single
then:
- logger.log: "It's drying !"
- script.execute: save_current_resistance
- script.wait: save_current_resistance
- text_sensor.template.publish:
id: text_status
state: "Secando"
- binary_sensor.template.publish:
id: raining
state: off
- binary_sensor.template.publish:
id: drying
state: on
- globals.set:
id: measure_delay
value: $measure_interval_wet
#Script para quando detectar que a placa está seca
- id: its_dry
mode: single
then:
- logger.log: "It's dry !"
- script.execute: save_current_resistance
- script.wait: save_current_resistance
- text_sensor.template.publish:
id: text_status
state: "Seco"
- binary_sensor.template.publish:
id: raining
state: off
- binary_sensor.template.publish:
id: drying
state: off
- globals.set:
id: measure_delay
value: $measure_interval_dry
Todas essas informações importantes (senhas OTA e API, configuração da rede wifi, etc) estão armazenadas no arquivo secrets.yaml
(ver aqui e aqui para mais detalhes de configuração e uso).
Exemplos de estatísticas com a informação do sensor:
sensor:
- platform: history_stats
name: Sensor de Chuva - Chovendo - N° Ativações - Hoje
entity_id: binary_sensor.clima_chuva_inst_chovendo
state: "on"
type: count
start: "{{ now().replace(hour=0, minute=0, second=0) }}"
end: "{{ now() }}"
- platform: history_stats
name: Sensor de Chuva - Chovendo - N° Ativações - Últimas 24h
entity_id: binary_sensor.clima_chuva_inst_chovendo
state: "on"
type: count
duration: 24:00:00
end: "{{ now() }}"
- platform: statistics
entity_id: binary_sensor.clima_chuva_inst_chovendo
name: Sensor de Chuva - Tempo Chovendo - Últimas 24h
state_characteristic: average_step
max_age:
hours: 24
- platform: history_stats
name: Sensor de Chuva - Secando - N° Ativações - Hoje
entity_id: binary_sensor.clima_chuva_inst_secando
state: "on"
type: count
start: "{{ now().replace(hour=0, minute=0, second=0) }}"
end: "{{ now() }}"
- platform: history_stats
name: Sensor de Chuva - Secando - N° Ativações - Últimas 24h
entity_id: binary_sensor.clima_chuva_inst_secando
state: "on"
type: count
duration: 24:00:00
end: "{{ now() }}"
- platform: statistics
entity_id: binary_sensor.clima_chuva_inst_secando
name: Sensor de Chuva - Tempo Secando - Últimas 24h
state_characteristic: average_step
max_age:
hours: 24
Ideias de uso no Lovelace:
- Além de incluir a informação em cards, fica lega incluir em alguma parte do seu dashboard a lista de eventos do status do sensor para verificar diretamente quando iniciou e parou a chuva.
Código do card:
type: logbook
entities:
- sensor.clima_chuva_inst_status
hours_to_show: 48
title: Sensor de Chuva