esp-home

CtrlTemperatura: controlo de temperatura do inversor solar e do rack com ESPHome

14 min de leitura

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.

ProjetoCtrlTemperatura
HardwareESP32 · DS18B20 · LED RGB · 2 ventiladores PWM
FirmwareESPHome (min. 2024.6.0)
PlataformaHome Assistant
FicheiroMonitor-Temp.yaml
RedeIP estático 192.168.1.235 · AP + captive portal
NuvemNenhuma — 100% local

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.
Esquema de ligação do CtrlTemperatura: fonte de 12 V, ESP32, DS18B20 no dissipador, LED RGB, dois ventiladores em PWM e ligação ao Home Assistant.
Ligação completa: ESP32 ao centro, sensores à esquerda, saídas PWM à direita
!

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:

SinalGPIODireçãoFunção
1-Wire (temp_inversorexterno)GPIO25EntradaTemperatura do dissipador do inversor (DS18B20)
LED VermelhoGPIO16Saída PWM (LEDC 1 kHz)Semáforo do rack: alerta ≥ 45 °C
LED VerdeGPIO17Saída PWM (LEDC 1 kHz)Semáforo do rack: normal < 40 °C
LED AzulGPIO18Saída PWM (LEDC 1 kHz)Semáforo do rack: aviso 40–45 °C
out_vent (rack)GPIO12Saída PWM 500 HzVentilador da caixa, mín. 20% — 0 / 50 / 100%
out_inversorGPIO14Saída PWM 200 HzVentilador 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)
OK

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)
i

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)
OK

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 em 192.168.1.254. Sem reservas DHCP a mudarem o endereço e a quebrarem automações.
  • Access Point de emergência — com ap: {} e captive_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:

  1. 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.
  2. 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.
  3. 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.
  4. Confirma as temperaturas vindas do HA. sensor.inverter_temperature_radiator e sensor.system_monitor_temperatura_do_processador têm de existir e estar com valor — sem elas, o PID trabalha só com o DS18B20 e o rack fica sem faixas.
  5. 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.
  6. 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:

01

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.

02

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.

03

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.

04

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:

  1. O PID não converge ou oscila sempre. Corre o autotune com o sistema estável, verifica se Temperatura Media está a variar com naturalidade e confirma que não mexeste nos números enquanto o autotune estava a correr — ele escreve por cima.
  2. Temperatura Media fica em unknown. Os dois sensores de origem estão em NAN: abre o log do ESPHome e confirma o entity_id de cada um, e vê se a integração do inversor e o System Monitor estão vivos no Home Assistant.
  3. 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.
  4. 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.
  5. 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.
OK

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.