import time
import dht
from machine import Pin, ADC, I2C
from ssd1306 import SSD1306_I2C
# =========================================================
# PROJETO: ESTUFA INTELIGENTE - SR JOAO
# CULTURA: REPOLHO
# =========================================================
# =========================
# CONFIGURACOES GERAIS
# =========================
# Aqui eu defino qual cultura está sendo monitorada.
CULTURA = "REPOLHO"
# Faixa de temperatura considerada adequada para a plantação.
# Se passar disso, o sistema entende que precisa agir.
TEMP_MIN = 15
TEMP_MAX = 28
# Limite mínimo aceitável para umidade do solo.
# Abaixo desse valor, o sistema entende que o solo está seco.
SOLO_SECO_LIMIAR = 40
# Limite mínimo de luz.
# Se a luz ficar abaixo disso, o sistema pode ligar a iluminação.
LUZ_BAIXA_LIMIAR = 40
# A histerese serve para evitar que os equipamentos
# fiquem ligando e desligando toda hora por pequenas variações.
HISTERESE = 5
# Valores mínimo e máximo do conversor analógico do ESP32.
# Esses números ajudam a transformar a leitura bruta em porcentagem.
ADC_MIN = 0
ADC_MAX = 4095
# Define o estado lógico do relé.
# Aqui estou considerando 1 como ligado e 0 como desligado.
RELE_ATIVO = 1
RELE_INATIVO = 0
# Tempo de troca das telas do display OLED.
INTERVALO_TELA_MS = 3000
# Tempo de espera entre cada repetição principal do sistema.
INTERVALO_LOOP_S = 1
# Intervalo mínimo entre leituras do sensor DHT22.
# Isso é importante porque esse sensor não deve ser lido rápido demais.
INTERVALO_DHT_MS = 2000
# =========================
# PINOS ESP32
# =========================
# Pino onde o sensor DHT22 está conectado.
PIN_DHT = 4
# Pinos dos sensores de umidade do solo.
PIN_SOLO_1 = 34
PIN_SOLO_2 = 35
PIN_SOLO_3 = 32
PIN_SOLO_4 = 25
# Pino do sensor de luminosidade.
PIN_LDR = 33
# Pinos dos relés que controlam os atuadores.
PIN_RELE_IRRIGACAO = 18
PIN_RELE_ILUMINACAO = 19
PIN_RELE_VENTILADOR = 5
# Pinos de comunicação do display OLED via I2C.
PIN_I2C_SDA = 21
PIN_I2C_SCL = 22
# =========================
# INICIALIZACAO DE HARDWARE
# =========================
# Aqui eu crio o sensor DHT22, que mede temperatura e umidade do ar.
sensor_dht = dht.DHT22(Pin(PIN_DHT))
# Aqui eu crio os sensores analógicos de umidade do solo.
solo1 = ADC(Pin(PIN_SOLO_1))
solo2 = ADC(Pin(PIN_SOLO_2))
solo3 = ADC(Pin(PIN_SOLO_3))
solo4 = ADC(Pin(PIN_SOLO_4))
# Sensor de luminosidade.
ldr = ADC(Pin(PIN_LDR))
# Esse ajuste melhora a faixa de leitura analógica no ESP32.
# Estou aplicando isso em todos os sensores analógicos.
for sensor in (solo1, solo2, solo3, solo4, ldr):
sensor.atten(ADC.ATTN_11DB)
# Inicialização dos relés.
# Eles começam desligados para evitar acionamento indevido ao ligar o sistema.
rele_irrigacao = Pin(PIN_RELE_IRRIGACAO, Pin.OUT, value=RELE_INATIVO)
rele_iluminacao = Pin(PIN_RELE_ILUMINACAO, Pin.OUT, value=RELE_INATIVO)
rele_ventilador = Pin(PIN_RELE_VENTILADOR, Pin.OUT, value=RELE_INATIVO)
# Inicialização do barramento I2C e do display OLED.
i2c = I2C(0, scl=Pin(PIN_I2C_SCL), sda=Pin(PIN_I2C_SDA))
oled = SSD1306_I2C(128, 64, i2c)
# =========================
# ESTADO GLOBAL
# =========================
# Controla qual tela está aparecendo no OLED no momento.
tela_atual = 0
# Guarda o tempo da última troca de tela.
ultimo_ciclo_tela = time.ticks_ms()
# Guarda o momento da última leitura do DHT.
# Já começo descontando o intervalo para permitir leitura logo no início.
tempo_ultima_leitura_dht = time.ticks_ms() - INTERVALO_DHT_MS
# Cache com valores iniciais do DHT.
# Isso ajuda o sistema a ter um valor padrão caso a leitura falhe.
dados_cache_dht = {
"temp": 20.0,
"hum": 50.0
}
# Variáveis que guardam o estado atual dos atuadores.
estado_irrigacao = False
estado_iluminacao = False
estado_ventilador = False
# =========================
# FUNCOES AUXILIARES
# =========================
# Essa função limita um valor entre um mínimo e um máximo.
# É útil para evitar porcentagens fora da faixa de 0 a 100, por exemplo.
def limitar(valor, minimo, maximo):
return max(min(valor, maximo), minimo)
# Faz várias leituras do sensor e tira uma média.
# Isso ajuda a deixar o valor mais estável e menos "nervoso".
def ler_adc_medio(sensor, amostras=8):
total = 0
for _ in range(amostras):
total += sensor.read()
time.sleep_ms(5)
return int(total / amostras)
# Converte a leitura bruta do ADC para porcentagem.
# Se invertido=True, a escala é invertida.
def mapear_adc_para_percentual(raw, invertido=False):
raw = limitar(raw, ADC_MIN, ADC_MAX)
percentual = int((raw - ADC_MIN) * 100 / (ADC_MAX - ADC_MIN))
if invertido:
percentual = 100 - percentual
return limitar(percentual, 0, 100)
# Liga ou desliga um relé de forma padronizada.
def setar_rele(rele, ligado):
rele.value(RELE_ATIVO if ligado else RELE_INATIVO)
# Converte valor booleano em texto para exibir melhor no terminal.
def bool_texto(valor):
return "LIGADO" if valor else "DESLIG"
# Apenas imprime uma linha separadora no terminal.
def linha_divisoria():
print("=" * 52)
# =========================
# TELAS OLED
# =========================
# Tela inicial de apresentação do sistema.
def tela_boot():
oled.fill(0)
oled.text("ESTUFA INTELIG.", 0, 0)
oled.text("SR JOAO", 0, 16)
oled.text("CULTURA:", 0, 34)
oled.text(CULTURA, 0, 50)
oled.show()
# Tela com os dados dos quatro sensores de solo.
def tela_solo(dados):
oled.fill(0)
oled.text("UMIDADE SOLO", 0, 0)
oled.text("S1:{} S2:{}".format(dados["solo1"], dados["solo2"]), 0, 16)
oled.text("S3:{} S4:{}".format(dados["solo3"], dados["solo4"]), 0, 30)
oled.text("MEDIA:{}%".format(dados["solo_media"]), 0, 48)
oled.show()
# Tela com informações do ambiente:
# umidade do ar, luz e temperatura.
def tela_ambiente(dados):
oled.fill(0)
oled.text("AMBIENTE", 0, 0)
oled.text("AR: {}%".format(dados["umidade_ar"]), 0, 16)
oled.text("LUZ:{}%".format(dados["luminosidade"]), 0, 32)
oled.text("TEMP:{}C".format(dados["temperatura"]), 0, 48)
oled.show()
# Tela de alertas.
# Se não houver problema, mostra que está tudo ok.
def tela_alertas(alertas):
oled.fill(0)
oled.text("ALERTAS", 0, 0)
if not alertas:
oled.text("SISTEMA OK", 0, 24)
else:
mapa = {
"solo_seco": "Solo seco",
"baixa_luz": "Baixa luz",
"temperatura_baixa": "Temp baixa",
"temperatura_alta": "Temp alta"
}
y = 16
for alerta in alertas[:3]:
oled.text("- " + mapa.get(alerta, alerta[:12]), 0, y)
y += 14
oled.show()
# Tela que mostra o estado dos equipamentos.
def tela_status(irrigacao, iluminacao, ventilador):
oled.fill(0)
oled.text("ATUADORES", 0, 0)
oled.text("BOMBA: {}".format("ON" if irrigacao else "OFF"), 0, 14)
oled.text("LUZ: {}".format("ON" if iluminacao else "OFF"), 0, 27)
oled.text("VENT: {}".format("ON" if ventilador else "OFF"), 0, 40)
oled.text("MODO: AUTO", 0, 54)
oled.show()
# Essa função decide qual tela será mostrada no OLED.
# Ela vai alternando entre as telas de tempos em tempos.
def atualizar_oled(dados, irrigacao, iluminacao, ventilador, alertas):
global tela_atual, ultimo_ciclo_tela
agora = time.ticks_ms()
if time.ticks_diff(agora, ultimo_ciclo_tela) >= INTERVALO_TELA_MS:
tela_atual = (tela_atual + 1) % 4
ultimo_ciclo_tela = agora
if tela_atual == 0:
tela_solo(dados)
elif tela_atual == 1:
tela_ambiente(dados)
elif tela_atual == 2:
tela_alertas(alertas)
else:
tela_status(irrigacao, iluminacao, ventilador)
# =========================
# LEITURA DOS SENSORES
# =========================
# Faz a leitura do DHT usando cache.
# Isso evita erro por leitura rápida demais.
def ler_dht_com_cache():
global tempo_ultima_leitura_dht
agora = time.ticks_ms()
if time.ticks_diff(agora, tempo_ultima_leitura_dht) >= INTERVALO_DHT_MS:
try:
sensor_dht.measure()
dados_cache_dht["temp"] = round(sensor_dht.temperature(), 1)
dados_cache_dht["hum"] = round(sensor_dht.humidity(), 1)
tempo_ultima_leitura_dht = agora
except Exception as erro:
print("Erro ao ler DHT22:", erro)
return dados_cache_dht["temp"], dados_cache_dht["hum"]
# Reúne a leitura de todos os sensores e devolve tudo organizado em um dicionário.
def ler_sensores():
temperatura, umidade_ar = ler_dht_com_cache()
raw_solo1 = ler_adc_medio(solo1)
raw_solo2 = ler_adc_medio(solo2)
raw_solo3 = ler_adc_medio(solo3)
raw_solo4 = ler_adc_medio(solo4)
raw_luz = ler_adc_medio(ldr)
# Aqui estou assumindo que no Wokwi os potenciômetros simulam a umidade do solo.
# Quanto maior o sinal, maior a umidade.
umidade_solo1 = mapear_adc_para_percentual(raw_solo1, invertido=False)
umidade_solo2 = mapear_adc_para_percentual(raw_solo2, invertido=False)
umidade_solo3 = mapear_adc_para_percentual(raw_solo3, invertido=False)
umidade_solo4 = mapear_adc_para_percentual(raw_solo4, invertido=False)
# No caso do LDR do Wokwi, a leitura sobe quando escurece.
# Por isso usei invertido=True, para ficar mais fácil de entender em porcentagem.
luminosidade = mapear_adc_para_percentual(raw_luz, invertido=True)
# Calcula a média da umidade do solo, considerando os quatro sensores.
solo_media = int((umidade_solo1 + umidade_solo2 + umidade_solo3 + umidade_solo4) / 4)
return {
"temperatura": temperatura,
"umidade_ar": umidade_ar,
"luminosidade": luminosidade,
"solo1": umidade_solo1,
"solo2": umidade_solo2,
"solo3": umidade_solo3,
"solo4": umidade_solo4,
"solo_media": solo_media,
"raw_solo1": raw_solo1,
"raw_solo2": raw_solo2,
"raw_solo3": raw_solo3,
"raw_solo4": raw_solo4,
"raw_luz": raw_luz
}
# =========================
# CONTROLE AUTOMATICO
# =========================
# Essa função é a parte mais importante do projeto.
# Ela decide quando ligar ou desligar irrigação, luz e ventilação.
def controlar_atuadores(dados):
global estado_irrigacao, estado_iluminacao, estado_ventilador
alertas = []
# IRRIGACAO COM HISTERESE
# Se o solo estiver seco, liga a irrigação.
# Depois só desliga quando o valor subir o suficiente.
if estado_irrigacao:
if dados["solo_media"] >= SOLO_SECO_LIMIAR + HISTERESE:
estado_irrigacao = False
else:
if dados["solo_media"] < SOLO_SECO_LIMIAR:
estado_irrigacao = True
# ILUMINACAO COM HISTERESE
# Liga a luz quando a luminosidade estiver baixa.
if estado_iluminacao:
if dados["luminosidade"] >= LUZ_BAIXA_LIMIAR + HISTERESE:
estado_iluminacao = False
else:
if dados["luminosidade"] < LUZ_BAIXA_LIMIAR:
estado_iluminacao = True
# VENTILACAO COM HISTERESE
# Liga o ventilador se a temperatura passar do máximo.
if estado_ventilador:
if dados["temperatura"] <= TEMP_MAX - HISTERESE:
estado_ventilador = False
else:
if dados["temperatura"] > TEMP_MAX:
estado_ventilador = True
# Aplica o estado calculado aos relés físicos.
setar_rele(rele_irrigacao, estado_irrigacao)
setar_rele(rele_iluminacao, estado_iluminacao)
setar_rele(rele_ventilador, estado_ventilador)
# Monta a lista de alertas para mostrar no display e no terminal.
if dados["solo_media"] < SOLO_SECO_LIMIAR:
alertas.append("solo_seco")
if dados["luminosidade"] < LUZ_BAIXA_LIMIAR:
alertas.append("baixa_luz")
if dados["temperatura"] < TEMP_MIN:
alertas.append("temperatura_baixa")
if dados["temperatura"] > TEMP_MAX:
alertas.append("temperatura_alta")
return estado_irrigacao, estado_iluminacao, estado_ventilador, alertas
# =========================
# LOG NO TERMINAL
# =========================
# Mostra no terminal tudo o que está acontecendo no sistema.
# Isso ajuda bastante a acompanhar o funcionamento durante os testes.
def imprimir_log(dados, irrigacao, iluminacao, ventilador, alertas):
linha_divisoria()
print("ESTUFA INTELIGENTE - SR JOAO")
print("CULTURA:", CULTURA)
print("Temperatura: {} C".format(dados["temperatura"]))
print("Umidade do ar: {} %".format(dados["umidade_ar"]))
print("Luminosidade: {} %".format(dados["luminosidade"]))
print("Solo 1: {} % | Solo 2: {} %".format(dados["solo1"], dados["solo2"]))
print("Solo 3: {} % | Solo 4: {} %".format(dados["solo3"], dados["solo4"]))
print("Media do solo: {} %".format(dados["solo_media"]))
print("Bomba de irrigacao:", bool_texto(irrigacao))
print("Iluminacao:", bool_texto(iluminacao))
print("Ventilador:", bool_texto(ventilador))
print("Alertas:", alertas if alertas else ["sistema_ok"])
# =========================
# LOOP PRINCIPAL
# =========================
# Essa função faz o sistema funcionar continuamente.
# Primeiro mostra uma tela inicial, depois entra em repetição infinita.
def loop_principal():
tela_boot()
time.sleep(2)
while True:
try:
# Lê os sensores
dados = ler_sensores()
# Decide o que fazer com os atuadores
irrigacao, iluminacao, ventilador, alertas = controlar_atuadores(dados)
# Atualiza o display OLED
atualizar_oled(dados, irrigacao, iluminacao, ventilador, alertas)
# Mostra informações no terminal
imprimir_log(dados, irrigacao, iluminacao, ventilador, alertas)
except Exception as erro:
# Caso aconteça algum problema, mostra erro no display e no terminal.
oled.fill(0)
oled.text("ERRO NO SISTEMA", 0, 0)
oled.text(str(erro)[:16], 0, 20)
oled.show()
print("Erro:", erro)
# Espera um pouco antes de repetir todo o processo
time.sleep(INTERVALO_LOOP_S)
# Inicia o sistema
loop_principal()SENSOR TEMP. / AR
OLED
SENSOR DE LUZ
RELE IRRIGACAO
RELE ILUMINACAO
RELE VENTILADOR
SENSOR SOLO 1
SENSOR SOLO 2
SENSOR SOLO 3
SENSOR SOLO 4