Integração MQTT¶
Broker¶
HiveMQ Cloud (Free Tier), protocolo MQTTS (MQTT sobre TLS), porta 8883.
A conexão usa autenticação por usuário e senha. As credenciais ficam nas variáveis de ambiente MQTT_USERNAME e MQTT_PASSWORD.
Subscriber Django¶
O subscriber é um management command Django (telemetry/management/commands/run_mqtt_subscriber.py) que usa paho-mqtt. Deve rodar como processo separado (ex: python manage.py run_mqtt_subscriber).
Comportamento obrigatório: - Reconexão automática em caso de queda de conexão. - Mensagens malformadas são descartadas com log; o processo não cai.
Leituras atrasadas (backfill): o firmware (v2/v3) tem um buffer local que reenvia leituras com o
tsoriginal ao reconectar após uma queda (verdocs/hardware.md§7) — elas são inseridas no banco commeasured_atno passado. Se a queda for mais longa que 3 dias, esse backfill pode cair fora da janela de auto-refresh dos agregados contínuos do TimescaleDB; nesse caso, reprocesse manualmente compython manage.py reprocess_readings(verdocs/DEPLOY.md§9.1).
Tópicos¶
Telemetria¶
Tópico: casa/{mqtt_id}/telemetria
Direção: ESP32 → Django
Payload (JSON):
{
"vrms": 220.5,
"irms": 4.2,
"watts": 910.1,
"fp": 0.98,
"kwh": 12.34
}
| Campo | Unidade | Model destino |
|---|---|---|
vrms |
V | TelemetryReading.voltage_rms |
irms |
A | TelemetryReading.current_rms |
watts |
W | TelemetryReading.active_power |
fp |
— | TelemetryReading.power_factor |
kwh |
kWh | TelemetryReading.energy_kwh |
Status¶
Tópico: casa/{mqtt_id}/status
Direção: ESP32 → Django
Payload offline (LWT — Last Will Testament):
{ "estado": "offline" }
Payload online (boot):
{
"estado": "online",
"uptime_segundos": 42,
"motivo_reset": "power_on"
}
Ao receber este tópico a service layer de telemetry:
1. Atualiza Meter.is_online e Meter.last_seen_at.
2. Cria um DeviceStatusEvent.
3. Infere disconnect_cause:
- uptime_segundos baixo logo após retorno → 'power_outage'
- uptime_segundos alto contínuo → 'network_failure'
Variáveis de ambiente necessárias¶
MQTT_HOST=<host.hivemq.cloud>
MQTT_PORT=8883
MQTT_USERNAME=<usuario>
MQTT_PASSWORD=<senha>
5. Validação do fluxo hardware → API (sem hardware físico)¶
Para validar o fluxo completo ESP32 → broker → subscriber → banco → dashboard sem precisar de um medidor físico em mãos, o repositório traz simulator.py (raiz do projeto): um cliente MQTT que publica payloads de telemetria e status exatamente nos mesmos tópicos e formato que o firmware real usa (casa/{mqtt_id}/telemetria, casa/{mqtt_id}/status), lendo as mesmas credenciais do .env (MQTT_HOST/MQTT_PORT/MQTT_USERNAME/MQTT_PASSWORD).
Uso¶
# Cadastre antes um medidor no sistema com o mesmo mqtt_id (ex.: medidor-01)
python simulator.py --mqtt-id medidor-01
# Intervalo customizado entre leituras (padrão: 5s, igual ao firmware)
python simulator.py --mqtt-id medidor-01 --interval 10
# Tensão e potência base customizadas (útil para simular 127V vs. 220V, ou cargas maiores)
python simulator.py --mqtt-id medidor-01 --interval 2 --voltage 127.0 --power 3000
O script publica estado: online (retido) ao conectar, uma leitura de telemetria a cada --interval segundos (com variação aleatória realista de tensão/potência/fator de potência, e kwh acumulando como o firmware faz), e estado: offline (LWT, retido) ao ser encerrado com Ctrl+C — replicando o ciclo de vida completo de um medidor real, incluindo o cenário de desconexão.
Resultado esperado (validação clara)¶
Com o backend (runserver + run_mqtt_subscriber) e o simulador rodando:
- O terminal do simulador mostra cada mensagem publicada (
[#N] Telemetria → casa/medidor-01/telemetria {...}) - O terminal do
run_mqtt_subscriberconfirma o recebimento e a persistência - O medidor aparece online no dashboard em poucos segundos, com os valores de tensão/potência atualizando a cada ciclo
- Ao interromper o simulador (
Ctrl+C), o medidor deve aparecer offline no dashboard logo em seguida
Esse é o mesmo fluxo de validação usado no Guia de Instalação como alternativa a ter o hardware físico já montado.
Cobertura automatizada¶
O parsing e a persistência dos payloads (o que o simulator.py exercita de ponta a ponta manualmente) já têm testes unitários dedicados em telemetry/tests.py (ParseTelemetryTest, StoreReadingTest, ProcessStatusPayloadTest) — cobrindo payload válido, campos ausentes, JSON inválido e payload malformado sem derrubar o processo.