Guia de configuração — ESP32 Master com autenticação HMAC
Documentação central para integração da API e configuração do firmware no ESP32 (Master). Inclui visão geral para desenvolvedores e detalhes de campo para operação IoT com HMAC.
Conteúdo alinhado ao ficheiro docs/guia_esp32_hmac.md no repositório, com secção adicional de integração geral da API.
Integração geral da API
Base URL: /api/v1/. A ingestão de sensores aceita HMAC, JWT de aplicação, JWT de utilizador e HTTP Basic (ordem de tentativa definida no backend).
Para dispositivos, o fluxo recomendado é HMAC com X-Api-Key, X-Api-Timestamp e X-Api-Signature. Para integrações server-to-server, JWT de aplicação tende a simplificar gestão de acesso por escopo.
Autenticação (JWT, Basic e HMAC)
- JWT de utilizador:
POST /api/v1/auth/login/ePOST /api/v1/auth/refresh/. - JWT de aplicação: criar
Clientno admin e gerar token emPOST /api/v1/applications/auth/token/com{"api_key":"...","api_secret":"..."}. - Clientes API (staff):
GET /api/v1/application-clients/eGET /api/v1/application-clients/{id}/. - HMAC (IoT): exige escopo
sensors:write; tenant é resolvido peloClientautenticado (não vai no JSON da leitura). - Basic: compatível para cenários legados com utilizador Django quando permitido pelo endpoint.
Endpoints principais
POST /api/v1/sensors/readings/— ingestão de leitura (HMAC/JWT app/JWT user/Basic).GET /api/v1/sensors/readings/list/— listagem paginada no tenant ativo.POST /api/v1/applications/auth/token/— troca API Key + Secret por JWT de aplicação.
OpenAPI e contrato de respostas
Schema OpenAPI em /api/schema/. Documentação interativa em /api/docs/ e /api/redoc/ quando habilitado no ambiente.
Opcionalmente, API_SUCCESS_ENVELOPE=true envolve respostas de sucesso em {"data": ..., "meta": {...}}. Paginação mantém metadados próprios.
Quando configurado em REST_FRAMEWORK, o TenantScopedUserRateThrottle combina utilizador + tenant na chave de limitação.
1. Visão geral
O ESP32 Master lê sensores (DHT22, MQ4) e recebe dados de escravos na rede local. Com WiFi externo, NTP e credenciais HMAC configuradas, cada leitura nova é enviada diretamente à API por HTTPS assim que é registada (registrarLeitura → enviarDiretoParaAPI). Se esse envio falhar ou a rede não estiver disponível, a leitura entra na fila no cartão SD. A cada ~5 minutos o firmware tenta drenar essa fila (linhas pendentes em /dados.jsonl).
As leituras em fila no SD usam NDJSON (/dados.jsonl, uma linha JSON por registo, mesmo formato do corpo da API). O ficheiro legado /dados.csv é migrado automaticamente para JSONL no arranque. Quando todas as linhas pendentes são enviadas com sucesso, dados.jsonl é removido e o offset de envio é zerado — evita o cartão encher indefinidamente.
Se o cartão SD falhar mas o WiFi externo, NTP e credenciais HMAC estiverem OK, as leituras novas continuam a ir direto para a API sem passar pela fila no SD. Sem SD e sem condições para falar com a API, os registos podem ser perdidos (mensagem no Serial, prefixo [REG]).
A autenticação usa HMAC-SHA256 (sem JWT, sem expiração de credencial). O dispositivo assina cada pedido com API Key + API Secret; o servidor valida e associa a leitura ao tenant (fazenda) correto.
O corpo JSON da leitura não indica fazenda nem slug de tenant: o Django resolve o tenant pelo Client encontrado pela X-Api-Key (campo client.tenant). Por isso o par Key+Secret no ESP deve ser o do Client dessa fazenda.
ESP32 Servidor Django
│ │
│ POST /api/v1/sensors/readings/ │
│ Headers: │
│ X-Api-Key: <api_key> │
│ X-Api-Timestamp: <unix_utc> │
│ X-Api-Signature: <hmac_hex> │
│ Body: JSON da leitura │
│ ──────────────────────────────────► │
│ │ Valida HMAC, timestamp, escopo
│ │ Grava no tenant do Client
│ ◄──────────────────────────────── 201 {"ok": true}
2. Credenciais necessárias
Criadas no Django Admin (menu «Clientes API»). Cada fazenda tem um Client com tenant e escopos.
2.1 Dashboard do ESP (http://192.168.4.1)
| Campo no dashboard | Valor (exemplo) | De onde vem |
|---|---|---|
| URL | https://adilson.cc/api/v1/sensors/readings/ | Fixo (o teu domínio + path) |
| API Key | c3e78ab1059fd1573d05eadd7bca424a300abfecf269e25a5c3061ebe0e0d116 | Coluna API KEY no admin (sempre visível) |
| API Secret | 64 caracteres hex (0–9, a–f) | Mensagem de aviso no topo após criar o Client (Key + Secret numa caixa destacada); não reaparece em claro |
Os exemplos são de desenvolvimento; em produção cada fazenda tem o seu par Key + Secret.
2.2 Detalhe por campo
| Campo | Exemplo | Onde obter |
|---|---|---|
| URL | https://…/api/v1/sensors/readings/ | Domínio + path da API |
| API Key | hex longo | Admin, coluna API KEY |
| API Secret | 64 hex | Aviso único após criar (admin redireciona para edição e mostra credenciais) |
Ao criar o Client, em Escopos permitidos use JSON com "sensors:write" (ex.: ["sensors:write"]), necessário para a ingestão HMAC.
3. Configurar o ESP32 (provisionamento)
Pré-requisitos: firmware COD_MASTER_V6.ino (Arduino IDE ou PlatformIO, board esp32); telemóvel ou PC com WiFi.
- Ligar o ESP32 — AP: SSID
FAZENDA_METANO, senha12345678. - Ligar o telemóvel a esse WiFi.
- Abrir
http://192.168.4.1. - No card «Servidor remoto (HMAC)»: URL, API Key e API Secret (como na secção 2).
- «Salvar configuração».
- No «WiFi externo», rede da fazenda e «Conectar».
- NTP sincroniza; leituras enviam-se à API quando há rede; a fila no SD é drenada automaticamente a cada ~5 minutos (se existir ficheiro pendente).
- No painel de estado aparece Modo API (SD + API, Direto sem SD ou Sem SD / API indisponível) conforme o cartão e a rede.
4. Assinatura HMAC (referência técnica)
4.1 Chave HMAC
chave = SHA-256(api_secret em UTF-8).digest() → 32 bytes
No servidor: bytes.fromhex(client.api_secret_hash).
4.2 String canónica
Quatro linhas separadas por \n (sem \r):
POST\n/api/v1/sensors/readings/\n1712955600\n<sha256_hex_do_body>
| Parte | Descrição |
|---|---|
POST | Método em maiúsculas |
/api/v1/sensors/readings/ | Path sem domínio nem query |
| timestamp | Igual ao header X-Api-Timestamp |
| sha256 do body | Hex minúsculo, 64 caracteres |
4.3 Assinatura e cabeçalhos
assinatura = HMAC-SHA256(chave, string_canónica).hexdigest()
X-Api-Key: <api_key> X-Api-Timestamp: <unix_utc> X-Api-Signature: <hex 64 chars> Content-Type: application/json
4.4 Janela de tempo
O servidor rejeita se |agora − timestamp| > 300 s (predefinição). O firmware adia o envio se o relógio não estiver sincronizado (NTP); verifica o tempo antes do POST (ex.: valor Unix < 1000000000 → adia).
5. Corpo JSON esperado
{
"id": "master",
"mac": "192.168.4.1",
"data": "2026-04-12",
"hora": "14:30:00",
"temp": "25.0",
"hum": "60",
"ppm": "400",
"origem": "master"
}
Todos os campos são strings (pode ser "ERRO" em falhas). origem: master ou slave. Os valores de id e mac vêm do firmware (ex.: master + IP do AP para o master; escravos enviam o seu id e MAC).
O campo data é uma string livre (o master usa tipicamente AAAA-MM-DD; outros clientes podem usar outro formato, dentro dos limites do serializer na API).
6. Alterações no firmware
Ficheiro: arduino/COD_MASTER_V6/COD_MASTER_V6.ino
- Includes:
mbedtls/sha256.h,mbedtls/md.h. - Autenticação na API:
apiKey/apiSecret(Preferences, namespaceservidor), sem Basic Auth. - Funções
sha256Hex,computeHMAC— o path passado à assinatura coincide com o fixo/api/v1/sensors/readings/(ver nota na secção 4.2). - Fila no SD em NDJSON (
/dados.jsonl); migração automática de/dados.csvlegado; remoção do ficheiro quando a fila é totalmente enviada. enviarDiretoParaAPI(payload): POST HTTPS + HMAC para uma linha JSON já serializada; aceita HTTP 200, 201 ou 204 como sucesso.registrarLeitura(...): tenta envio direto; se falhar ou não houver rede, grava na fila SD; log[REG]se não houver SD nem API.enviarParaServidor(): drenadados.jsonlusandoenviarDiretoParaAPI; guardas para credenciais vazias e NTP.- Disparo periódico (~5 min) com WiFi externo, sem exigir SD montado (sem cartão, a função sai logo se não houver ficheiro).
- Dashboard: API Key / Secret; campo Modo API; GET mascara o secret.
- Rotas
/api(objetomaster) e/statusincluemmodo_envio(normal,direto_sem_sd,sem_sd_sem_api).
7. Múltiplas fazendas
Um Client por fazenda (tenant + escopo sensors:write). Novo ESP: novo Client, provisionar no dashboard com as credenciais desse Client. Isolamento por tenant.
8. Troubleshooting
| Sintoma | Causa provável | Solução |
|---|---|---|
| HTTP 401 | Key/Secret ou Client inativo | Dashboard + admin |
| 401 timestamp | Relógio ESP | WiFi, NTP, Serial [NTP] |
| HTTP 403 | Sem sensors:write | Escopos no admin |
| Painel mostra outra fazenda | API Key de outro Client | python manage.py iot_verify_client --api-key … (confirma tenant e escopo) |
| Sem envio | Credenciais vazias | Configurar em 192.168.4.1 |
Sem envio + Serial [REMOTO] NTP nao sincronizado | Sem WiFi externo ou NTP bloqueado | WiFi, firewall e NTP; prefixo [NTP] no Serial |
| SD com falha mas dados na API | Envio direto ativo | Esperado se WiFi + NTP + HMAC OK; painel mostra «Direto sem SD» |
[REG] leitura perdida | Sem SD e sem POST à API (rede/NTP/credenciais) | Restaurar SD ou WiFi + NTP + credenciais |
| Log NTP | Rede / firewall | WiFi externo, NTP permitido |
Serial Monitor 115200: prefixos [SETUP], [NTP], [REMOTO], [SERVIDOR], [REG].
9. Segurança
api_secretna NVS; flash encryption opcional.- O secret não vai na rede em claro — só a assinatura HMAC.
- HTTPS em produção; firmware pode usar
setInsecure()— ideal fazer pin de certificado. - Credencial não expira; revogar no admin ou
iot_rotate_client_secret+ atualizar o dashboard do ESP.