Projetos de eletrónica, PCB, firmware e domótica

esp-home

Estação Termo: estação ambiental redonda com ESP32, BME280 e display GC9A01

14 min de leitura

A Estação Termo mede o ambiente da casa num só sítio: temperatura, humidade relativa e absoluta, ponto de orvalho, pressão e altitude, calculadas a partir de um único BME280 e apresentadas num display redondo de 1,28 polegadas. Um ESP32 com ESPHome desenha tudo, sincroniza a hora com o Home Assistant e apaga o ecrã quando ninguém está a ver. Tudo local, sem nuvem e sem subscrições.

ProjetoEstação Termo (Estação RC)
HardwareESP32 · BME280 · Display GC9A01 1,28" · Tecla
FirmwareESPHome (min. 2024.6.0)
PlataformaHome Assistant
Ficheiroestacao_termo.yaml
RedeIP estático 192.168.1.238 · captive portal
NuvemNenhuma — 100% local

Visão geral do sistema

A ideia é ter uma estação meteorológica de interior que responda a duas perguntas: como está o ar da casa agora, e o que é preciso fazer a respeito disso. Um único BME280 dá temperatura, pressão e humidade relativa; o YAML faz o resto — humidade absoluta em g/m³, ponto de orvalho e altitude calculadas a partir desses três valores, sem sensores adicionais.

O que distingue o projeto não é o sensor, é a forma como apresenta os dados: um display redondo de 1,28 polegadas onde o fundo é uma imagem desenhada à mão (setores, ícones, zonas) e o ESPHome escreve apenas os números por cima. O resultado lê-se de relance — temperatura em destaque no topo, hora por baixo, humidade à esquerda, g/m³ e ponto de orvalho à direita, pressão e altitude na base — com o estado Online/Offline sempre visível.

  • 240×240Display redondo GC9A01A de 1,28", SPI a 20 MHz
  • 6Grandezas no ecrã: temperatura, humidade, orvalho, g/m³, pressão e altitude
  • 60sIntervalo de leitura do BME280 (com offset de calibração)
  • 0Serviços externos ou dependência da nuvem

O ecrã não está sempre ligado: uma tecla acorda a retroiluminação a 100%, e depois de 20 segundos o display volta a baixar e a apagar-se sozinho. A retroiluminação é também uma entidade light no Home Assistant, portanto dá para ligar, desligar e regular a luminosidade à distância — ou por automação, sem tocar em nada.

Hardware e esquema de ligação

A lista de materiais é curta — o projeto é barato porque é essencial:

  • Placa ESP32 (board: esp32dev) — sobram pinos para o SPI, o I²C, a tecla e a retroiluminação.
  • BME280 — temperatura, pressão e humidade relativa num único chip, por I²C. Cuidado: o BMP280 é quase igual e não mede humidade — não confundir as siglas na compra.
  • Display redondo TENSTAR ROBOT de 1,28" — 240×240 pixels, controlador GC9A01A, interface SPI até 20 MHz, com retroiluminação própria.
  • Tecla de contacto com pull-up externo de 10 kΩ — o GPIO usado é de entrada pura e não tem resistências internas.
  • Dois ficheiros de assets ao lado do YAML: a imagem de fundo do ecrã (icons/imageround.png) e a fonte Helvetica.ttf. Sem eles, o ESPHome nem compila.
  • Fonte 5 V — o regulador 3,3 V da própria placa alimenta o display e o sensor; CAIXA com janela redonda ou suporte de bancada.
Esquema de ligação da Estação Termo: fonte de 5 V, ESP32, BME280 por I²C, display redondo GC9A01A por SPI, tecla, retroiluminação e ligação ao Home Assistant.
Ligação completa: ESP32 ao centro, sensor à esquerda, display e tecla à direita
!

Três erros de bancada que custam uma tarde: alimentar o display a 5 V (os módulos GC9A01 queimam — usa o 3,3 V da placa); esquecer o pull-up da tecla no GPIO35, que é input-only e não tem resistência interna; e montar o BME280 coladinho ao ESP32, onde lê sempre 2–3 °C a mais.

Mapeamento dos GPIO

Nove pinos em uso, divididos por barramentos — SPI para o ecrã, I²C para o sensor e dois pinos avulsos para a retroiluminação e a tecla:

SinalGPIODireçãoFunção
SPI MOSIGPIO27SaídaDados do display (sem MISO — é write-only)
SPI CLKGPIO14SaídaClock SPI a 20 MHz
SPI CSGPIO33SaídaChip select do GC9A01A
SPI DCGPIO25SaídaDistinção dados/comandos
SPI RSTGPIO26SaídaReset do controlador
I²C SDAGPIO21BidirecionalBME280 (endereço 0x76)
I²C SCLGPIO22SaídaRelógio do barramento
BacklightGPIO32Saída PWM (LEDC)Retroiluminação — entidade light
TeclaGPIO35Entrada (inverted)Acorda e apaga o ecrã

Não há fio MISO no SPI porque o display nunca devolve dados: escreve e cala-se, o que também permite correr o barramento ao ritmo máximo sem seguranças. O I²C é o clássico SDA 21 / SCL 22 com scan: true, para que o endereço apareça no log na primeira arrancada.

O YAML, bloco a bloco

O estacao_termo.yaml separa-se bem: primeiro o ecrã, depois os sensores, depois as contas e por fim a retroiluminação. Estes são os excertos que interessam:

O display redondo: GC9A01A sobre SPI

spi:
mosi_pin: GPIO27
clk_pin: GPIO14
spi_device:
id: spidev
data_rate: 20MHz
display:
- platform: ili9xxx
model: gc9a01a
invert_colors: true
auto_clear_enabled: false
id: gca901_display
cs_pin: GPIO33
dc_pin: GPIO25
reset_pin: GPIO26
rotation: 180
lambda: |-
it.image(0, 0, id(img_back));
it.strftime(33, 65, id(helvetica_18), id(my_green), TextAlign::LEFT,
"%Y-%m-%d  %H:%M:%S", id(esptime).now());
it.printf(97, 25, id(helvetica_24), "%.1f °C", id(bme280_temperature).state);
it.printf(46, 112, id(helvetica_24), "%.1f %%", id(bme280_humidity).state);
if (id(system_status).state) {
it.print(135, 1, id(helvetica_12), id(my_green), TextAlign::TOP_RIGHT, "Online");
} else {
it.print(135, 1, id(helvetica_12), id(my_red), TextAlign::TOP_RIGHT, "Offline");
}Code language: PHP (php)
OK

Porquê auto_clear_enabled: false? Porque o fundo do ecrã é uma imagem que não muda: em vez de o ESPHome limpar o buffer a cada refresh e voltar a desenhar tudo (visível como um relampejo), mantém-se o buffer e escrevem-se apenas os números que mudam. Menos trabalho de CPU, ecrã mais estável — e é por isso que a lambda começa sempre com it.image(...).

As restantes linhas do ecrã seguem o mesmo padrão — pressão em (20, 160), altitude em (135, 160), humidade absoluta em (160, 105) e ponto de orvalho em (160, 130) — todas com it.printf e as coordenadas afinadas para assentar nas zonas do fundo.

O fundo é uma imagem, não código

image:
- file: "icons/imageround.png"
id: img_back
type: RGB
transparency: alpha_channel
font:
- file: "Helvetica.ttf"
id: helvetica_24
size: 24
# helvetica_18, helvetica_14 e helvetica_12 no mesmo padrão
color:
- id: my_green
red: 0%
green: 100%
blue: 0%
# my_red, my_yellow, my_blue e my_gray no mesmo padrãoCode language: PHP (php)

A imagem de fundo traz os setores, os ícones (termómetro, gota, montanha, bússola) e as molduras prontas — o ESPHome só recebe a tarefa de pôr o texto certo em cada zona. Fazer o desenho num editor e deixar ao firmware apenas os números é muito mais simples do que tentar construir a interface toda com it.line() e it.rectangle().

Ecrã da Estação Termo: imagem de fundo com temperatura, humidade, ponto de orvalho, humidade absoluta, pressão, altitude, data e hora.
O resultado no ecrã: imagem de fundo com os valores do BME280 escritos por cima
OK

O ficheiro de imagem tem de ficar na pasta do projeto, em icons/imageround.png, e a fonte em Helvetica.ttf ao lado do YAML — o ESPHome copia ambos para a placa na compilação. Se mudares o fundo, mantém a mesma janela circular de 240×240 e refaz só as coordenadas do texto.

O BME280 e o que ele dá de borla

i2c:
sda: 21
scl: 22
scan: true
id: bus_a
sensor:
- platform: bme280_i2c
temperature:
name: "BME Temperatura"
oversampling: 1x
filters:
- offset: -2
id: bme280_temperature
pressure:
name: "BME Pressão"
oversampling: 1x
id: bme280_pressure
humidity:
name: "BME280 Humidade Relativa"
oversampling: 1x
id: bme280_humidity
address: 0x76
update_interval: 60sCode language: JavaScript (javascript)

Três valores por um chip só, com oversampling: 1x para leituras rápidas e um offset: −2 aplicado à temperatura — o suficiente para corrigir o calor que o próprio módulo devolve. O endereço 0x76 é o do módulo em questão; muitos BME280 vêm de fábrica em 0x77, por isso vale a pena deixar o scan: true ligado pelo menos na primeira arrancada.

Grandezas derivadas: altitude, g/m³ e ponto de orvalho

sensor:
- platform: template
name: "Altitude"
id: bme_altitude
lambda: |-
const float STANDARD_SEA_LEVEL_PRESSURE = 1013.25; // em hPa
return ((id(bme280_temperature).state + 273.15) / 0.0065) *
(powf((STANDARD_SEA_LEVEL_PRESSURE / id(bme280_pressure).state), 0.190234) - 1);
update_interval: 60s
unit_of_measurement: 'm'
icon: 'mdi:signal'
- platform: absolute_humidity
name: "Humidade Absoluta"
id: bme_humidadeabs
temperature: bme280_temperature
humidity: bme280_humidity
- platform: template
name: "Ponto Orvalho"
id: bme_orvalho
lambda: |-
return (243.5*(log(id(bme280_humidity).state/100)+((17.67*id(bme280_temperature).state)/
(243.5+id(bme280_temperature).state)))/(17.67-log(id(bme280_humidity).state/100)-
((17.67*id(bme280_temperature).state)/(243.5+id(bme280_temperature).state))));
unit_of_measurement: °C
icon: 'mdi:thermometer-alert'Code language: JavaScript (javascript)
i

Porque o ponto de orvalho? É a temperatura a que o ar satura e a água passa a condensar — janelas molhadas de manhã, espelhos embaçados, bolor nos cantos. Se a temperatura de uma superfície ficar abaixo do ponto de orvalho medido, aí é que a condensação acontece. É a grandeza que transforma um mostrador bonito numa ferramenta útil, e sai de graça das mesmas duas medições. O mesmo com a humidade absoluta em g/m³: é ela que diz quanto é que o ar realmente leva de água, independentemente da temperatura.

Retroiluminação e tecla

output:
- platform: ledc
pin: GPIO32
id: backlight
light:
- platform: monochromatic
output: backlight
name: "Backlight"
id: luzback
binary_sensor:
- platform: status
name: "Node Status"
id: system_status
- platform: gpio
pin:
number: GPIO35
inverted: true
name: "Tecla"
id: tdisplay_button_input_1
on_click:
then:
- light.turn_on:
id: luzback
brightness: 100%
transition_length: 2s
- delay: 20s
- light.turn_on:
id: luzback
brightness: 15%
transition_length: 60sCode language: JavaScript (javascript)

O truque é o LEDC: o mesmo GPIO que alimenta a retroiluminação é gerido como uma entidade light, o que dá três coisas de uma vez — brilho regulável no Home Assistant, transições suaves e uma sequência por automação. Aqui a sequência é simples: um clique acende a 100% com transição de 2 s e, 20 segundos depois, o ecrã começa a baixar e a apagar-se — sem ninguém precisar de se lembrar de o desligar.

OK

Nota de implementação: no ficheiro original a sequência termina com um light.turn_off imediatamente a seguir ao fade de 60 s, o que o corta antes de terminar. Se quiseres que o ecrã afade até 0 ao longo de um minuto, remove o turn_off e deixa só o light.turn_on com brightness: 0% e transition_length: 60s.

Rede, gestão e atualizações

Como nos outros projetos deste blog, a estação vive inteiramente na rede local:

  • IP estático — 192.168.1.238, com gateway e DNS em 192.168.1.254. O endereço nunca muda, o que torna o dispositivo previsível para automações e para o web server.
  • Captive portal — configurável por telemóvel se o Wi-Fi não estiver presente (com a ressalva do ponto seguinte).
  • Improv Serial — provisionamento de Wi-Fi pela porta série, a primeira vez ou depois de um reset.
  • OTA — atualizações sem cabo, direto do ESPHome no Home Assistant.
  • Web Server — página local com sensores e logs, útil para diagnóstico sem abrir o HA.
  • API nativo — ligação direta ao Home Assistant, incluindo o relógio usado no ecrã.
  • dashboard_import — o dispositivo pode ser importado como pacote no ESPHome Dashboard.
wifi:
ssid: SF_24E
password: "••••••••"
manual_ip:
static_ip: 192.168.1.238
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)
!

Duas notas de rede: o Access Point de emergência está comentado (#ap: {}) — sem ele, o captive_portal não tem por onde servir a página de configuração e, se trocares de router ou mudares a palavra-passe, só recuperas o dispositivo por USB ou pela porta série. E a palavra-passe do Wi-Fi está em texto no ficheiro: mantém o estacao_termo.yaml fora de repositórios públicos e define api: encryption_key e ota: password antes de o publicares.

Instalação passo a passo

Do ficheiro em branco à estação montada, em seis passos:

  1. Junta os assets. Cria a pasta icons/ ao lado do estacao_termo.yaml, mete lá o imageround.png e copia o Helvetica.ttf para a raiz do projeto. Sem estes dois ficheiros, a compilação falha antes de chegar ao resto.
  2. Liga a fiação. Display por SPI (5 fios + 3,3 V e GND), BME280 por I²C (2 fios + 3,3 V e GND) e tecla entre o GPIO35 e o GND, com o pull-up de 10 kΩ para os 3,3 V.
  3. Grava e confirma o barramento. Adiciona o dispositivo no ESPHome, ajusta SSID, palavra-passe e IP e envia. No log, procura a linha do i2c scan — tem de lá estar o 0x76.
  4. Aceita no Home Assistant. Surgem no minuto seguinte seis sensores, o light da retroiluminação, a tecla e o estado do nó.
  5. Calibra. Compara a temperatura com um termómetro de referência e ajusta o offset; se a altitude der errada, troca os 1013,25 hPa do padrão pela pressão média do teu local.
  6. Monta e testa. BME280 afastado do calor do ESP32, display à vista e tecla acessível. Clica: o ecrã acende, e sozinho apaga-se passados os 20 segundos.

No Home Assistant

A estação não é um gadget que só serve para olhar: cada valor que aparece no ecrã é uma entidade no Home Assistant, pronta para gráficos, automações e alertas.

01

Seis sensores, uma entidade de estado

BME Temperatura, BME Pressão, BME280 Humidade Relativa, Altitude, Humidade Absoluta e Ponto Orvalho — mais Wifi signal, Uptime e Node Status para diagnóstico. É o suficiente para montar um cartão de ambiente completo sem comprar mais nada.

02

A retroiluminação é uma luz

A entidade light.backlight aceita brilho como qualquer lâmpada inteligente. Dá para a ligar quando alguém entra na sala, a escurecer à noite ou a acender quando a humidade passa de um limite — sem escrever uma linha no ESPHome.

03

A hora no ecrã vem do HA

O time usa a plataforma homeassistant: o relógio do display é o mesmo do HA, sem NTP e sem desvios entre o que o ecrã mostra e o que as automações registam no logbook.

04

Estado visível e página web local

O Online/Offline no canto do ecrã é a entidade status, e o web server do ESPHome permite abrir 192.168.1.238 no browser e ver sensores e logs mesmo quando o Home Assistant está em manutenção.

Automações que valem a pena

  • Alerta de condensação — se o Ponto Orvalho subir acima de uma temperatura de referência (paredes frias, vidros), notificação para arejar ou ligar o exaustor.
  • Conforto de humidade — abaixo de 35% avisa para ar seco; acima de 65%, sugere ventilação antes de o bolor aparecer.
  • Backlight automático — liga o ecrã à hora de acordar e apaga-o à noite, em vez de esperar pelo clique da tecla.
  • Histórico do ar — exporta temperatura, humidade e orvalho para InfluxDB/Grafana e tens a curva do inverno inteiro, com a condensação a aparecer nos gráficos antes de aparecer nas janelas.

Diagnóstico e boas práticas

Se algo não bater certo, estes seis pontos cobrem os problemas reais deste projeto:

  1. O ecrã fica preto ou com as cores trocadas. Confirma os 3,3 V, os cinco fios SPI e os pinos cs_pin, dc_pin e reset_pin. Se aparecer a imagem mas invertida, mexe em invert_colors e rotation — o módulo montado de cabeça para baixo precisa dos 180°.
  2. Communication with BME280 failed no log. O endereço padrão do ESPHome é 0x77 e o do módulo pode ser 0x76 (ou vice-versa); lê a saída do scan: true e corrige. Verifica também as resistências de pull-up no SDA/SCL e, em modo I²C, que o pino CSB do chip está nos 3,3 V.
  3. A temperatura lê sempre alta. É o clássico self-heating: o BME280 perto do ESP32 e da fonte. Afasta-o alguns centímetros, mede contra um termómetro de referência e afina o offset em vez de acreditar no número que sai da caixa.
  4. A altitude dá 0 m. A fórmula divide pela pressão: se o sensor de pressão estiver em NAN, o resultado cai a zero. Confirma a entidade id(bme280_pressure) e, se for de propósito, ajusta o STANDARD_SEA_LEVEL_PRESSURE à pressão média do teu local.
  5. A tecla não acorda o ecrã. O GPIO35 é de entrada pura, sem pull-up interno: sem a resistência externa de 10 kΩ, o pino flutua e o inverted: true nunca dispara.
  6. A compilação falha à procura de icons/imageround.png ou de Helvetica.ttf. Os caminhos são relativos à pasta do projeto do ESPHome — confirma a estrutura antes de suspeitar do YAML.
OK

O backup deste projeto são três coisas: o estacao_termo.yaml, o imageround.png e o Helvetica.ttf. Guarda os três juntos — recuperar a estação noutro ESP32 é só copiar a pasta e gravar.

Conclusão

A Estação Termo prova que um mostrador de ambiente não precisa de ser um ecrã retangular com números empilhados: com um display redondo de poucos euros, uma imagem de fundo bem desenhada e a lambda certa, obtém-se um painel que se lê de relance e que qualquer pessoa em casa sabe interpretar — temperatura, humidade, hora e basta.

Por baixo da capa está o que interessa: um BME280 a medir de 60 em 60 segundos, três grandezas calculadas no próprio firmware, retroiluminação que se apaga sozinha e todas as entidades no Home Assistant para as automações que forem precisas. É replicável ao fim de uma tarde — copia o estacao_termo.yaml, mete os dois assets na pasta certa e ajusta o offset ao teu caso. Tudo local, tudo teu.

Quer montar o teu?

A documentação do ESPHome cobre o controlador GC9A01, o sensor BME280 e o componente de imagem — e o YAML deste artigo serve de ponto de partida.