CtrlTemperatura: controlo de temperatura do inversor solar e do rack com ESPHome
O CtrlTemperatura mantém o inversor solar e o rack onde vive o Home Assistant dentro da faixa térmica certa: um ESP32 a correr ESPHome lê três fontes de temperatura, comanda dois ventiladores em PWM com um controlo PID com autotune e sinaliza o estado num LED RGB. Tudo local, sem nuvem e sem subscrições.
Neste artigo
Visão geral do sistema
A ideia é simples: em vez de ligar e desligar ventiladores às cegas, mede-se nos dois pontos que interessam e deixa-se um PID a decidir a velocidade. O inversor solar é o ponto quente da instalação e o rack é o ponto frágil — se a Raspberry Pi arrefecer mal, arrefece o Home Assistant todo.
O CtrlTemperatura junta dois controladores independentes na mesma placa:
- Inversor — termostato PID (entidade climate): usa a média entre o DS18B20 colado ao dissipador e a temperatura de radiador lida do inversor no Home Assistant, e comanda o ventilador do inversor em velocidade variável, com setpoint de 35 °C.
- Rack — máquina de estados: a cada 2 segundos lê a temperatura da CPU do Raspberry Pi e troca de faixa — abaixo de 40 °C, LED verde e ventilador parado; entre 40 e 45 °C, LED azul a pulsar e ventilador a 50%; acima de 45 °C, LED vermelho em alerta e ventilador a 100%.
- 2Ventiladores em PWM: inversor (PID) e rack (por estados)
- 35 °CSetpoint do PID, com deadband de −1,0 a +0,4 °C
- 3Fontes de temperatura: DS18B20, inversor e CPU (HA)
- 0Serviços externos ou dependência da nuvem
No Home Assistant o termostato aparece como um termostato a sério: cartão nativo, setpoint ajustável, ligar e desligar, e um botão de autotune que afina os ganhos PID por conta própria. Os parâmetros Kp, Ki, Kd e os limites do deadband são entidades number com persistência — dá para calibrar em produção sem regravar firmware.
Hardware e esquema de ligação
A lista de materiais cabe num punhado de componentes e usa a alimentação que o rack já tem:
- Placa ESP32 (
board: esp32dev) — com cinco saídas PWM em uso, o ESP8266 não dava conta. - DS18B20 com resistência de pull-up de 4,7 kΩ — fixo ao dissipador do inversor, com cabo blindado, é a fonte de temperatura de referência do PID.
- LED RGB (catodo comum) com três resistências de 220–330 Ω — o semáforo do rack, visível de longe.
- Dois ventiladores 12 V — um para a caixa do rack e outro para forçar o ar no inversor.
- Dois MOSFETs N de lógica (IRLZ44N, AO3400 ou módulos já isolados) — os GPIO não arrastam ventilador nenhum por si.
- Fonte 12 V com regulador a 5 V para a placa e caixa de montagem no rack.

Nota de hardware: o GPIO12 é um pino de arranque (strapping) do ESP32 — se estiver em nível alto no momento do boot, o chip arranca com a leitura da flash errada. Tira o gate do MOSFET do ventilador do rack a esse pino com um divisor de tensão (ou usa outro GPIO) e garante que o ventilador não contraalimenta a placa pela linha de 5 V.
Trabalha com o inversor desligado da rede antes de mexer no dissipador, e isola o invólucro do DS18B20 do metal com mica ou composto termocondutor — a carcaça costuma estar ligada à terra do sinal.
Mapeamento dos GPIO
Seis pinos em uso, todos fixados no YAML e sem partilhas:
| Sinal | GPIO | Direção | Função |
|---|---|---|---|
1-Wire (temp_inversorexterno) | GPIO25 | Entrada | Temperatura do dissipador do inversor (DS18B20) |
| LED Vermelho | GPIO16 | Saída PWM (LEDC 1 kHz) | Semáforo do rack: alerta ≥ 45 °C |
| LED Verde | GPIO17 | Saída PWM (LEDC 1 kHz) | Semáforo do rack: normal < 40 °C |
| LED Azul | GPIO18 | Saída PWM (LEDC 1 kHz) | Semáforo do rack: aviso 40–45 °C |
| out_vent (rack) | GPIO12 | Saída PWM 500 Hz | Ventilador da caixa, mín. 20% — 0 / 50 / 100% |
| out_inversor | GPIO14 | Saída PWM 200 Hz | Ventilador do inversor, mín. 30% — saída do PID |
Das três temperaturas, só uma tem fiação: as outras duas já existem no Home Assistant — a do inversor vem da integração do fabricante (sensor.inverter_temperature_radiator) e a da CPU do System Monitor (sensor.system_monitor_temperatura_do_processador) — e são lidas pelo componente homeassistant. Mais barato do que comprar dois sensores e mais útil, porque fica tudo no mesmo gráfico.
O YAML, bloco a bloco
O Monitor-Temp.yaml é denso, mas está organizado por funções. Estes são os excertos que interessam, do sensor ao ventilador:
A temperatura média que alimenta o PID
sensor:
- platform: dallas_temp
name: Temperatura Dissipador
id: temp_inversorexterno
update_interval: 5s
- platform: homeassistant
entity_id: sensor.inverter_temperature_radiator
id: current_temperature
name: Temp Inversor Lida
- platform: template
name: Temperatura Media
id: temperatura_media
update_interval: 5s
lambda: |-
float sum = 0;
uint16_t cnt = 0;
if (!std::isnan(id(temp_inversorexterno).state)) {
sum += id(temp_inversorexterno).state;
cnt += 1;
}
if (!std::isnan(id(current_temperature).state)) {
sum += id(current_temperature).state;
cnt += 1;
}
if (cnt == 0) {
return NAN;
} else {
return (sum / cnt);
}Code language: PHP (php)
A lambda ignora valores inválidos e só devolve NAN quando nenhum dos sensores está disponível. Assim, uma falha pontual do integrador do inversor não puxa o PID para baixo nem o faz ventilar com um número inventado.
O termostato PID com deadband
climate:
- platform: pid
name: "Ventilador Inversor"
id: console_thermostat
sensor: temperatura_media
default_target_temperature: 35°C
cool_output: proxy_output
control_parameters:
kp: 0.3
ki: 0.0015
kd: 0
max_integral: 0.0
derivative_averaging_samples: 5
deadband_parameters:
threshold_high: 0.4°C
threshold_low: -1.0°C
ki_multiplier: 0.04
deadband_output_averaging_samples: 15Code language: CSS (css)
Duas decisões aqui fazem a diferença. A primeira é a deadband: entre −1,0 °C e +0,4 °C em volta do setpoint, o controlador não reage a cada oscilação — mantém a velocidade média e só reduz a ação integral (ki_multiplier). Sem isto, o ventilador estaria a acelerar e abrandar o dia inteiro por causa de meio grau. A segunda é max_integral: 0.0, que elimina o windup: o termo integral não acumula enquanto não há espaço para resfriar.
A cada alteração de estado, o YAML publica os termos P, I, D, o erro e a saída como sensores próprios. É a diferença entre «acho que o PID está a funcionar» e ver a correção a acontecer no gráfico.
Proxy de saída: PID e controlo manual no mesmo PWM
output:
- platform: ledc
pin: GPIO14
frequency: 200 Hz
min_power: 30%
max_power: 100%
zero_means_zero: true
id: out_inversor
- platform: template
id: proxy_output
type: float
write_action:
lambda: |-
float write_val =
(id(vent_inversor).state) ?
id(vent_inversor).speed / 100.0:
write_val = state * 1.0;
id(out_inversor).set_level(write_val);
id(fan_speed_pwm_voltage).publish_state(write_val * 100.0);
fan:
- platform: speed
output: proxy_output
name: "Ventilador Inversor"
id: vent_inversor
restore_mode: RESTORE_DEFAULT_OFFCode language: JavaScript (javascript)
Quem manda no ventilador? A saída do PID passa por um proxy: enquanto a entidade Ventilador Inversor está ligada no Home Assistant, manda a velocidade que escolheres à mão; quando a desligas, o PWM volta a seguir o que o termostato calcular. É um override manual sem nunca desligar o PID. Os limites min_power: 30% e zero_means_zero garantem que o ventilador não tenta arrancar com tensão insuficiente nem gira quando devia estar parado.
Estados do rack: três faixas, três cores
O ventilador da caixa não precisa de PID — precisa de reagir depressa. Um interval de 2 segundos avalia a temperatura da CPU e só age quando a faixa muda:
interval:
- interval: 2s
then:
- lambda: |-
if (id(temperatura_sistema).state < 40 && id(muda_estado) != 1) {
id(muda_estado) = 1;
id(rgbLED).turn_on().set_rgb(0, 1, 0)
.set_brightness(1.0)
.set_effect("extra_slow_pulse").perform();
id(vent_rack).turn_off().perform();
} else if (id(temperatura_sistema).state >= 40
&& id(temperatura_sistema).state < 45
&& id(muda_estado) != 2) {
id(muda_estado) = 2;
id(rgbLED).turn_on().set_rgb(0, 0, 1)
.set_brightness(1.0)
.set_effect("slow_pulse").perform();
auto call = id(vent_rack).turn_on();
call.set_speed(50);
call.perform();
} else if (id(temperatura_sistema).state >= 45
&& id(muda_estado) != 3) {
id(muda_estado) = 3;
id(rgbLED).turn_on().set_rgb(1, 0, 0)
.set_brightness(1.0)
.set_effect("fast_pulse").perform();
auto call = id(vent_rack).turn_on();
call.set_speed(100);
call.perform();
}Code language: HTML, XML (xml)
A variável global muda_estado é o que evita o ruído: sem ela, o bloco estaria a mandar comandos de luz e de ventilador de 2 em 2 segundos sem necessidade. Com ela, cada faixa dispara exatamente uma vez — e o LED já entra no efeito certo (pulso lento verde, pulsar azul, alerta vermelho) de cada vez que a temperatura cruza um limite.
Afinação sem regravar firmware
number:
- platform: template
name: kp
icon: mdi:chart-bell-curve
restore_value: true
initial_value: 0.3
min_value: 0
max_value: 50
step: 0.001
set_action:
- lambda: id(console_thermostat).set_kp(x);
# ki, kd, Deadband Threshold Low/High e Deadband ki Multiplier
# seguem exatamente o mesmo padrão
button:
- platform: template
name: "PID Climate Autotune"
on_press:
- climate.pid.autotune: console_thermostatCode language: PHP (php)
Ordem de trabalho para afinar: carrega no botão PID Climate Autotune com o sistema estável e deixa o ESPHome correr o procedimento até dar o resultado; copia depois os valores sugeridos para os parâmetros do climate e para os initial_value dos números. Como todos têm restore_value: true, sobrevivem a reinícios — mas se os puseres só nos números, perdem-se no boot seguinte.
Rede, gestão e atualizações
Como nos outros projetos deste blog, nada aqui depende de um servidor de fabricante. A configuração de rede garante:
- IP estático — o controlador vive sempre em
192.168.1.235, com gateway e DNS em192.168.1.254. Sem reservas DHCP a mudarem o endereço e a quebrarem automações. - Access Point de emergência — com
ap: {}ecaptive_portal, se a rede principal cair o dispositivo abre o seu próprio Wi-Fi e permite reconfigurar as credenciais pelo telemóvel. - Improv Serial — provisionamento de Wi-Fi pela porta série, útil na primeira configuração ou em bancada.
- OTA — atualizações de firmware sem tocar no cabo, a partir do ESPHome no Home Assistant.
- Web Server — interface local para consultar temperatura, PID e logs sem abrir o Home Assistant.
- API nativo — ligação direta ao Home Assistant, sem MQTT obrigatório.
- dashboard_import — o dispositivo pode ser importado como pacote no ESPHome Dashboard, com o YAML completo.
wifi:
ssid: SF_24G
password: "••••••••"
manual_ip:
static_ip: 192.168.1.235
gateway: 192.168.1.254
subnet: 255.255.255.0
dns1: 192.168.1.254
ap: {}
captive_portal:
web_server:
ota:
- platform: esphomeCode language: CSS (css)
Antes de publicar o YAML: a palavra-passe do Wi-Fi está em texto no ficheiro — mantém o Monitor-Temp.yaml fora de repositórios públicos. Define ainda api: encryption_key para cifrar a ligação ao Home Assistant e ota: password para proteger as atualizações, e reserva o IP 192.168.1.235 no router para não haver conflitos.
Instalação passo a passo
Do ficheiro em branco ao rack a arrefecer sozinho, em seis passos:
- Monta em bancada. ESP32 + DS18B20 + LED RGB + os dois MOSFETs numa protoboard, com o ventilador do inversor ligado. Testa a faixa verde, a azul e a vermelha com um secador a aquecer o sensor.
- Grava o firmware. Adiciona o dispositivo no ESPHome (Add Device), corrige o SSID, a palavra-passe e o IP estático e envia por USB ou OTA.
- Aceita no Home Assistant. O dispositivo aparece em Dispositivos & Serviços; no minuto seguinte tens o termostato Ventilador Inversor, os dois ventiladores, o LED e todas as entidades de PID disponíveis.
- Confirma as temperaturas vindas do HA.
sensor.inverter_temperature_radiatoresensor.system_monitor_temperatura_do_processadortêm de existir e estar com valor — sem elas, o PID trabalha só com o DS18B20 e o rack fica sem faixas. - Instala no rack. DS18B20 no dissipador, ventiladores aos MOSFETs, LED em lugar visível e fiação afastada da potência. Verifica que a placa não é alimentada pelo ventilador.
- Afina e cria o dashboard. Carrega em PID Climate Autotune, acompanha os sensores P/I/D, e só depois monta o painel e as automações da secção seguinte.
No Home Assistant
Depois de pareado, o dispositivo entrega um conjunto de entidades pronto a usar — e é no dashboard que o projeto ganha valor:
Termostato nativo
A entidade climate.ventilador_inversor usa o cartão de termostato do Home Assistant: muda o setpoint, liga e desliga, lê a temperatura atual e vê o modo automático. Para o utilizador, é um arrefecedor como qualquer outro — só que decidido por ti.
Telemetria do controlador
Os sensores p term, i term, d term, output value, error value e is in deadband transformam o PID numa caixa transparente: dá para grafar a saída e perceber, em segundos, se a afinação está certa.
Semáforo do rack
A luz Estado Rack (verde, azul e vermelho com efeitos de pulso) e a velocidade do Ventilador Caixa num só cartão. Vês o estado do rack sem ler um único número — e podes forçar a velocidade à mão quando quiseres.
Temperaturas partilhadas
O controlador lê a temperatura do inversor e da CPU que já estão no Home Assistant e devolve a média e o estado ao mesmo sítio. Zero sensores duplicados, zero integrações extra, histórico todo no mesmo sítio.
Automações que valem a pena
Com as entidades expostas, o resto é comum:
- Alerta de calor — quando a temperatura da CPU passar dos 45 °C (LED vermelho), notificação no telemóvel e registo no logbook.
- Histórico em gráfico — exporta a output value do PID e as três temperaturas para InfluxDB/Grafana e tens a curva térmica do verão inteiro.
- Modo silêncio — à noite, limita a velocidade máxima do ventilador do rack a 50% e avisa se a temperatura continuar a subir.
- Watchdog — se o uptime do controlador reiniciar sem aviso ou o sinal Wi-Fi cair abaixo de um limiar, recebes aviso antes de o rack aquecer sem supervisão.
Diagnóstico e boas práticas
Quando o sistema não se comporta como esperado, estes cinco pontos resolvem quase tudo:
- O PID não converge ou oscila sempre. Corre o autotune com o sistema estável, verifica se
Temperatura Mediaestá a variar com naturalidade e confirma que não mexeste nos números enquanto o autotune estava a correr — ele escreve por cima. Temperatura Mediafica em unknown. Os dois sensores de origem estão emNAN: abre o log do ESPHome e confirma oentity_idde cada um, e vê se a integração do inversor e o System Monitor estão vivos no Home Assistant.- O ventilador não arranca ou ronca. Sobe o
min_power, confirma a frequência PWM e a ponte MOSFET (gate a 3,3 V exige um MOSFET de lógica). Se for um ventilador de 4 fios de computador, tens de subir a frequência para a faixa que ele espera — 200/500 Hz é para modelos de 2–3 fios. - O LED não muda de cor. Confirma o catodo comum (comum ao GND) e as resistências; se as cores estiverem trocadas, os efeitos do light aparecem invertidos.
- O rack não muda de faixa. A temperatura de origem é
sensor.system_monitor_temperatura_do_processador: se a entidade não existir, o interval não deteta transição e o LED fica preso na última cor. Confirma o nome exato da entidade.
Antes de qualquer alteração ao Monitor-Temp.yaml, exporta uma cópia. O ESPHome guarda o histórico de versões no Home Assistant, mas um ficheiro de backup em local seguro torna qualquer regressão instantânea — e a calibração feita com autotune não se refaz num minuto.
Conclusão
O CtrlTemperatura mostra que proteger o inversor solar e o rack do Home Assistant não exige um controlador industrial nem sensores caros: um ESP32, um DS18B20, um LED e dois ventiladores com MOSFET chegam para ter arrefecimento proporcional, previsível e visível — PID para quem precisa de resposta fina, estados simples para quem precisa de reagir depressa.
É um projeto replicável: copia o Monitor-Temp.yaml, ajusta os GPIO e os entity_id às tuas entidades, corre o autotune e tens o teu rack e o teu inversor monitorizados ao fim de uma tarde. Tudo local, tudo teu.
Quer montar o teu?
A documentação do ESPHome cobre o termostato PID, os ventiladores em velocidade e as saídas PWM do ESP32 — e o YAML deste artigo serve de ponto de partida.
