HTTP: o idioma que o ESP32 fala — Arduino e IoT — semana 2 do 2o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 2 · HTTP: o idioma que o ESP32 fala — Material de Apoio Arduino

Semana 2 de 16· 2o trimestre · 01/05 a 04/09

HTTP: o idioma que o ESP32 fala

Request, response, método e status. O que a placa precisa saber para não travar.

Aula 1 — O que e HTTP: metodo, caminho e código de status

Objetivos

  • Nomear as quatro partes de uma requisicao HTTP: metodo, caminho, cabecalho e corpo.
  • Escolher o metodo certo para cada operacao: GET para ler, POST para criar, e dizer por que a leitura não usa POST.
  • Reconhecer um código de status pelo número e dizer o que a placa precisa fazer com cada um.
  • Explicar o que o cabecalho Content-Type faz e por que JSON.parse falha sem ele.
  • Montar, na placa, o texto exato de uma requisicao e comparar com o que o Node vai receber.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 protoboard de 830 pontos por dupla
  • Computador com Node.js 20 ou superior e curl disponivel
  • Projetor, para o terminal do professor
  • Folha de papel por aluno, para o mapeamento metodo/função/status

Conceitos

As quatro partes

Toda requisicao HTTP tem quatro partes, nesta ordem:

POST /leitura HTTP/1.1
Host: 192.168.0.10:3000
Content-Type: application/json
Content-Length: 92

{"id_placa":"esp32-lab01","temp_c":26.5}
ParteOnde estaPara que serve
MetodoPOSTo que você quer fazer com o recurso
Caminho/leituraem qual recurso
CabecalhoContent-Typecomo interpretar o que vem depois
Corpoo JSONos dados proprios ditos

A ordem importa: o servidor le o metodo e o caminho para escolher a rota, e so depois olha o cabecalho para decidir como interpretar o corpo. Inverter essa ordem quebra a leitura.

Metodo: o verbo da operacao

MetodoSignificadoIdempotenteNo curso
GETler, não muda nadasimdia 6, consultar dados
POSTcriar algo novonãodia 2 e dia 5, receber leitura
PUTsubstituir tudosimnão usado neste trimestre
DELETEapagarsimdia 7, limpar dado velho

GET e idempotente: chamar dez vezes deixa o servidor no mesmo estado. POST não e: chamar dez vezes cria dez leituras. E por isso que a placa usa POST para enviar e nunca GET: mandar dez vezes precisa gerar dez linhas, e se fosse GET o servidor poderia muito bem ignorar a segunda.

Código de status: o que a placa precisa ler

O servidor responde com três digitos. O primeiro digito já diz a familia inteira:

FamiliaSignificadoExemplosO que a placa faz
2xxdeu certo200, 201considera gravado
4xxo pedido esta errado400, 404, 422não tenta de novo, arruma o dado
5xxo servidor falhou500, 503pode tentar de novo depois

A distinção pratica que o aluno precisa ter na cabeça: 4xx não adianta tentar de novo, porque o problema esta no que a placa mandou. 5xx adianta, porque o problema esta do outro lado. O curl da abertura da aula mostra as duas: um 404 e um 500.

Content-Type: a frase que diz do que se trata

O cabecalho Content-Type: application/json diz ao servidor que o corpo e JSON. Sem ele, o servidor tem três opcoes: adivinhar, recusar, ou tentar interpretar como texto. O Node faz a primeira, e quando adivinha errado o JSON.parse lanca excecao.

O professor repete: Content-Type e o cabecalho que o dia 2 aula 2 cobra da placa, e o dia 3 aula 1 monta.

Atividade

Montagem: nenhuma. A placa não entra no WiFi hoje. O sketch monta a requisicao e mostra na tela, sem enviar.

  1. Escreva no caderno as quatro partes de uma requisicao, em ordem, com uma frase explicando para que serve cada uma.
  2. Preencha a tabela: para criar uma leitura nova, ler as ultimas 10 leituras e apagar leituras de ontem, qual metodo você usaria e por que. Depois circule na propria tabela as duas operacoes que não precisam de corpo.
  3. Para cada código de status abaixo, escreva o que a placa deve fazer: 200, 201, 404, 422, 500, -1.
  4. Grave o sketch da resolucao. Escreva no caderno, exatamente como aparece no monitor serial, o texto que a placa vai escrever na rede.
  5. Apague o Content-Type do texto que você escreveu no item 4 e explique em uma frase o que o servidor faz quando o recebe.
  6. O professor mostra um curl que devolveu 404 e outro que devolveu 500. Escreva a diferença entre "arrumar o código da placa" e "esperar e tentar de novo", e qual dos dois se aplica a cada caso.

Nota: 10 pontos. Critério de fim: a tabela de metodos esta preenchida com justificativa, os seis codigos de status tem resposta, e o item 4 esta transcrito sem erro.

Resolucao

O curl da abertura, rodado nesta maquina contra um endereco que não responde:

$ curl -X POST http://192.168.0.10:3000/leitura
curl: (7) Failed to connect to 192.168.0.10 port 3000: Connection refused

O curl: (7) e o código de erro do proprio curl, e não um status HTTP. O aluno precisa distinguir as duas coisas: um status e uma resposta de um servidor, mesmo ruim; um erro de curl e "não houve resposta nenhuma". Essa distincao reaparece no dia 3 aula 2, no código negativo do HTTPClient.

O sketch de hoje não entra no WiFi e não envia. Ele monta o texto da requisicao e mostra na tela, porque o aluno precisa ver o que vai sair antes de a rede existir:

// Aula 1 do dia 2: o que e HTTP, visto de dentro da placa.
//
// HTTP e um protocolo de REGRAS, nao de biblioteca. Nao existe
// HTTPClient.h no dia 2: ele so aparece na aula 2. Aqui o aluno ve que
// uma requisicao e so um texto com forma combinada, e que o ESP32 sabe
// montar esse texto com String e escrever pela rede.

#include <Arduino.h>

// O mesmo "caminho" que a placa vai usar contra o servidor Node. Mudar o
// metodo e o caminho e mudar a frase que a placa fala.
const char* METODO  = "POST";
const char* CAMINHO = "/leitura";
const char* SERVIDOR = "192.168.0.10";
const int   PORTA   = 3000;

// Cabecalho que o Node exige para fazer o JSON.parse sem reclamar.
const char* CABECALHO_CHAVE   = "Content-Type";
const char* CABECALHO_VALOR   = "application/json";

// O que a placa vai mandar. Mesmo formato do JSON do dia 3.
const char* CORPO = "{\"id_placa\":\"esp32-lab01\",\"temp_c\":26.5}";

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("2o trimestre, dia 2, aula 1 - a anatomia de uma requisicao");
  Serial.println("=================================================");
  Serial.println("uma requisicao HTTP e um texto com forma combinada.");
  Serial.println("quatro partes, nesta ordem:");
  Serial.println("  1. metodo    : o que voce quer fazer");
  Serial.println("  2. caminho   : em qual recurso");
  Serial.println("  3. cabecalho : como interpretar o corpo");
  Serial.println("  4. corpo     : os dados");
  Serial.println();

  // Monta a requisicao de verdade, so que NAO envia. A aula mostra o texto
  // antes de ele sair na rede, e essa diferenca e o que separa "erro meu"
  // de "erro do outro".
  String requisicao = String(METODO) + " " + CAMINHO + " HTTP/1.1";
  String cabecalho  = String(CABECALHO_CHAVE) + ": " + CABECALHO_VALOR;
  String endereco   = String("servidor: ") + String(SERVIDOR) + ":" + String(PORTA);

  Serial.println("--- o que a placa vai escrever na rede ---");
  Serial.println(requisicao);
  Serial.println(endereco);
  Serial.println(cabecalho);
  Serial.println(String("Content-Length: ") + String(strlen(CORPO)));
  Serial.println();
  Serial.println(CORPO);
  Serial.println();
  Serial.println("--- o que o servidor responde ---");
  Serial.println("HTTP/1.1 201 Created");
  Serial.println("Content-Type: application/json");
  Serial.println("{\"id\":1,\"gravado\":true}");
  Serial.println();
  Serial.println("201 e resposta de CRIADO. 200 e de OK. 404 e de NAO ACHEI.");
  Serial.println("O codigo de status e a unica coisa que a placa precisa ler");
  Serial.println("para saber se o dado entrou. A aula 2 do dia 3 le esse numero.");
}

void loop() {
  // Hoje a placa nao envia nada. Ela so lembra a frase, a cada volta, para
  // o professor poder apontar para o monitor durante a explicacao.
  Serial.println("--- (recapitulando, sem enviar) ---");
  Serial.print(METODO);
  Serial.print(" ");
  Serial.print(CAMINHO);
  Serial.print("  ->  servidor ");
  Serial.print(SERVIDOR);
  Serial.print(":");
  Serial.println(PORTA);
  delay(4000);
}

Por que assim e não de outro jeito. O sketch mostra o Content-Length calculado com strlen(CORPO), e não escrito a mao. Se o aluno mudar o corpo e esquecer de mudar o Content-Length, o servidor le um número de bytes diferente do que foi enviado, e a conexão quebra no meio do corpo — sem erro visivel do lado do Node. Mostrar o strlen e o que fecha essa porta.

O CORPO esta em const char* e não em String de propósito: e assim que o professor mostra que o JSON e texto, e que o strlen so existe porque e texto. No dia 3, a aula inverte: o JSON passa a ser String, porque ai ele precisa ser construido com valor dentro.

Criterios de correcao

CritérioPontos
As quatro partes da requisicao escritas em ordem, com a função de cada uma3 pontos
Tabela de metodos preenchida com justificativa, e as duas operacoes sem corpo circuladas3 pontos
Os seis codigos de status com a ação correta da placa2 pontos
Item 4: o texto da requisicao transcrito no caderno sem erro1 ponto
Item 5 e item 6 respondidos em uma frase cada1 ponto

Erros comuns

ErroComo apareceCorrecao
Usar GET para enviar a leitura"Usei GET porque e para pegar informação""GET e para ler. E idempotente: se a placa mandar dez vezes, o servidor pode ignorar a segunda. Para gerar dez linhas, o verbo tem de ser POST."
Escrever POST para consultar as ultimas leiturasA tabela de metodos toda com POST"Consultar não cria nada, então e GET. Se você usar POST para ler, o dia 6 vai mostrar que cada consulta cria uma linha nova no log do servidor."
Achar que 404 significa "o servidor caiu"Item 3 com 404 e "não gravou""404 e "não achei o caminho". O servidor esta de pe e respondeu. Ele so não conhece /leitura. Servidor caido e curl: (7), sem status nenhum."
Achar que 500 e culpa do código da placaItem 3 com 500 e "arrumar o código""5xx e problema do servidor. O pedido chegou inteiro e o servidor falhou ao processar. O 4xx e o oposto: seu pedido que estava errado."
Colocar o Content-Type depois do corpo"Cabecalho vai embaixo""O servidor le o cabecalho para decidir como ler o que vem depois. Cabecalho embaixo do corpo não serve para nada: o corpo já chegou sem explicacao."
Ler Content-Length como "o tamanho da resposta"Item 4 com o número trocado"Content-Length e quantos bytes o corpo que você mandou ocupa. A resposta tem o Content-Length dela, e e outro número."

Desafio extra

Escreva, na placa, a requisicao que consulta as ultimas 10 leituras: qual metodo, qual caminho, quais cabecalhos, e o que vem no corpo. Depois escreva a requisicao que apaga as leituras de ontem. Em seguida, escreva qual das duas, segundo você, tem risco de ser repetida sem querer se a placa reiniciar no meio do envio, e por que. A resposta que o professor espera: as duas, porque POST e DELETE podem ser repetidos, e e por isso que o dia 7 precisa de idempotencia e de chave para deduplicar.

>

A resolucao, compilada

// Aula 1 do dia 2: o que e HTTP, visto de dentro da placa.
//
// HTTP e um protocolo de REGRAS, nao de biblioteca. Nao existe
// HTTPClient.h no dia 2: ele so aparece na aula 2. Aqui o aluno ve que
// uma requisicao e so um texto com forma combinada, e que o ESP32 sabe
// montar esse texto com String e escrever pela rede.

#include <Arduino.h>

// O mesmo "caminho" que a placa vai usar contra o servidor Node. Mudar o
// metodo e o caminho e mudar a frase que a placa fala.
const char* METODO  = "POST";
const char* CAMINHO = "/leitura";
const char* SERVIDOR = "192.168.0.10";
const int   PORTA   = 3000;

// Cabecalho que o Node exige para fazer o JSON.parse sem reclamar.
const char* CABECALHO_CHAVE   = "Content-Type";
const char* CABECALHO_VALOR   = "application/json";

// O que a placa vai mandar. Mesmo formato do JSON do dia 3.
const char* CORPO = "{\"id_placa\":\"esp32-lab01\",\"temp_c\":26.5}";

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("2o trimestre, dia 2, aula 1 - a anatomia de uma requisicao");
  Serial.println("=================================================");
  Serial.println("uma requisicao HTTP e um texto com forma combinada.");
  Serial.println("quatro partes, nesta ordem:");
  Serial.println("  1. metodo    : o que voce quer fazer");
  Serial.println("  2. caminho   : em qual recurso");
  Serial.println("  3. cabecalho : como interpretar o corpo");
  Serial.println("  4. corpo     : os dados");
  Serial.println();

  // Monta a requisicao de verdade, so que NAO envia. A aula mostra o texto
  // antes de ele sair na rede, e essa diferenca e o que separa "erro meu"
  // de "erro do outro".
  String requisicao = String(METODO) + " " + CAMINHO + " HTTP/1.1";
  String cabecalho  = String(CABECALHO_CHAVE) + ": " + CABECALHO_VALOR;
  String endereco   = String("servidor: ") + String(SERVIDOR) + ":" + String(PORTA);

  Serial.println("--- o que a placa vai escrever na rede ---");
  Serial.println(requisicao);
  Serial.println(endereco);
  Serial.println(cabecalho);
  Serial.println(String("Content-Length: ") + String(strlen(CORPO)));
  Serial.println();
  Serial.println(CORPO);
  Serial.println();
  Serial.println("--- o que o servidor responde ---");
  Serial.println("HTTP/1.1 201 Created");
  Serial.println("Content-Type: application/json");
  Serial.println("{\"id\":1,\"gravado\":true}");
  Serial.println();
  Serial.println("201 e resposta de CRIADO. 200 e de OK. 404 e de NAO ACHEI.");
  Serial.println("O codigo de status e a unica coisa que a placa precisa ler");
  Serial.println("para saber se o dado entrou. A aula 2 do dia 3 le esse numero.");
}

void loop() {
  // Hoje a placa nao envia nada. Ela so lembra a frase, a cada volta, para
  // o professor poder apontar para o monitor durante a explicacao.
  Serial.println("--- (recapitulando, sem enviar) ---");
  Serial.print(METODO);
  Serial.print(" ");
  Serial.print(CAMINHO);
  Serial.print("  ->  servidor ");
  Serial.print(SERVIDOR);
  Serial.print(":");
  Serial.println(PORTA);
  delay(4000);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia02 aula1.

Aula 2 — Servidor HTTP em Node que recebe dado do ESP32

Objetivos

  • Subir um servidor HTTP em Node com http.createServer e listen, e dizer o que cada argumento significa.
  • Receber o corpo de um POST acumulando os pedacos em req.on('data') e fechando em req.on('end').
  • Responder com res.writeHead e res.end, escolhendo o código de status certo para cada desfecho.
  • Reproduzir, e depois corrigir, o erro do end faltando, e explicar por que o servidor "fica carregando" sem ele.
  • Apontar, no log do servidor, em qual das quatro etapas o caminho parou.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 protoboard de 830 pontos por dupla
  • Computador com Node.js 20 ou superior
  • Terminal com dois paineis: um para o servidor, um para o curl
  • Projetor, para o terminal do professor

Conceitos

http.createServer e listen

const servidor = http.createServer((req, res) => { /* roda a cada pedido */ });
servidor.listen(3000);

O createServer não abre nada: ele so registra a função que sera chamada. O listen e que abre a porta e faz o processo ficar vivo. Um programa com createServer e sem listen imprime e morre, exatamente como o programa de ontem.

O detalhe que quebra a aula: listen(3000) com a porta fixa falha se a porta já estiver ocupada, e o aluno ve o erro EADDRINUSE e conclui que "o Node esta quebrado". Na aula, use listen(0) e pegue a porta com servidor.address().port, que e o que o exemplo faz.

O corpo chega em pedacos

Este e o coracao da aula. Uma requisicao com corpo não chega inteira: o Node entrega o corpo em eventos, e o programador tem de juntar:

let corpo = '';
req.on('data', (pedaco) => { corpo += pedaco.toString(); });
req.on('end',   () => { /* aqui o corpo esta inteiro */ });

O req e um emissor de eventos: on('data') e chamado cada vez que chega um pedaco, e on('end') e chamado uma vez, quando acabou. Sem o on('end'), o trecho que responde nunca executa, e o pedido fica parado até a conexão morrer sozinha.

Para um JSON de 92 bytes isso quase nunca aparece, porque a resposta cabe em um pacote. Para um JSON de 200 KB, aparece sempre. O professor usa o curl com corpo pequeno e explica que o problema esta la, escondido, esperando a aula 13.

writeHead e end

Toda resposta tem duas partes: o status com cabecalhos, e o corpo.

res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 1, gravado: true }));

writeHead não envia nada ainda: ele memoriza. E end que fecha e envia. Um writeHead sem end deixa o navegador esperando para sempre — o mesmo sintoma do end faltando no req, e por isso o professor trata os dois como o mesmo defeito.

O Content-Type: application/json na resposta não e enfeite: e o que diz ao cliente que o corpo pode ser lido com JSON.parse e não como texto.

Atividade

Montagem: a placa não se conecta hoje. Ela fica gravada, com o sketch do dia 2 aula 1, so para o aluno ter a frase da requisicao na frente dos olhos.

  1. No terminal, crie o arquivo servidor.js e digite o código da resolucao. Rode com node servidor.js e não feche o terminal.
  2. Em um segundo terminal, rode curl -X POST http://127.0.0.1:3000/leitura -H "Content-Type: application/json" -d '{"id_placa":"lab01","temp_c":26.5}'. Anote o status e o corpo da resposta.
  3. Confira no log do servidor: quantos bytes de corpo apareceram? O número bate com o tamanho do JSON que você mandou?
  4. Apague a linha do req.on('end', ...) e rode o curl de novo, com --max-time 5. O que o curl imprime? Quanto tempo ele esperou? O que o log do servidor mostra?
  5. Restaure a linha, troque o status de 201 para 404 e mantenha o caminho /leitura. Rode o curl. Quem esta errado agora, o cliente ou o servidor? Justifique com uma frase.
  6. Adicione uma segunda rota, GET /status, que responde 200 com um JSON de texto fixo. Teste com curl e depois no navegador, digitando o endereco na barra. O que muda entre os dois clientes?
  7. Escreva, no caderno, o que você precisaria trocar no código para a placa conseguir falar com esse servidor. Quem fornece o endereco: a placa ou o servidor?

Nota: 12 pontos. Critério de fim: o servidor responde 201 ao POST /leitura, responde 200 ao GET /status, e o item 4 foi executado e anotado com o tempo de espera.

Resolucao

O exemplo foi executado nesta maquina com Node 24, e e o que o professor projeta:

// O servidor que recebe a leitura do ESP32.
// O ponto da aula: o corpo chega em PEDACOS, e so depois do evento 'end'
// que ele esta inteiro. Sem o 'end', o servidor nunca responde.
const http = require('http');

const servidor = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/leitura') {
    let corpo = '';   // acumulador: comeca vazio

    req.on('data', (pedaco) => {
      corpo += pedaco.toString();   // cada pedaco e acrescentado
    });

    req.on('end', () => {
      console.log('corpo completo, tamanho', corpo.length);
      const leitura = JSON.parse(corpo);
      console.log('JSON interpretado:', leitura);

      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ id: 1, gravado: true }));
    });
    return;
  }

  res.writeHead(404, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ erro: 'rota nao existe' }));
});

(async () => {
  await new Promise((r) => servidor.listen(0, '127.0.0.1', r));
  const url = 'http://127.0.0.1:' + servidor.address().port + '/leitura';
  console.log('servidor em', url);

  // Este POST imita exatamente o que a placa faz com HTTPClient.
  const resposta = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id_placa: 'esp32-lab01', temp_c: 27.4, umidade_pct: 58 }),
  });
  console.log('status', resposta.status, await resposta.text());

  servidor.close();
})();

A saída real, medida nesta maquina:

servidor em http://127.0.0.1:33275/leitura
corpo completo, tamanho 57
JSON interpretado: { id_placa: 'esp32-lab01', temp_c: 27.4, umidade_pct: 58 }
status 201 {"id":1,"gravado":true}

O professor aponta dois números nessa saída: o 33275 da porta e o 57 do tamanho do corpo. A porta muda a cada execução porque o exemplo usa listen(0), que pede uma porta livre ao sistema. O 57 e fixo, e e o tamanho exato do JSON enviado — id_placa com 10 caracteres, temp_c com 4, umidade_pct com 2, mais as chaves e as virgulas. Se o aluno contar, ele chega no mesmo número, e ai o item 3 da atividade tem resposta.

O sketch da placa mostra o lado de dentro do HTTPClient, para o aluno ver que a biblioteca faz exatamente o que a aula 2 descreveu:

// Aula 2 do dia 2: agora a placa tem um destino. O servidor HTTP em Node que o
// professor subiu e o servidor que o dia 2 aula 1 explicou. A placa fala
// HTTP e o professor mostra o log do Node receiving cada POST.
//
// WiFi e um nome e uma senha FICTICIOS: a rede de teste do professor troca de
// nome toda aula, e nenhum segredo real entra em material. O aluno troca
// pelas credenciais da rede da escola.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// GPIO2 e o LED embutido na maioria dos DevKit: serve de indicador de vida.
const int PIN_LED = 2;

// Credenciais da rede. FICTICIAS de proposito, com este comentario.
const char* WIFI_SSID     = "rede-teste-lab01";
const char* CHAVE_REDE = "ficticia-lab01";

// Endereco do PC que roda o Node. O IP e da maquina do professor, nunca do
// roteador: o ESP32 conversa com o processo, nao com a internet.
const char* IP_SERVIDOR = "192.168.0.10";
const int   PORTA_SERVIDOR = 3000;
const char* CAMINHO = "/leitura";

const long INTERVALO_MS = 5000;

WiFiClient cliente;
HTTPClient http;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("2o trimestre, dia 2, aula 2 - a placa fala com o servidor");
  Serial.println("-------------------------------------------------");

  Serial.print("conectando em ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, CHAVE_REDE);

  // Nao existe "esperar o wifi": a placa precisa checar o status num laco.
  // Esperar um tempo fixo e a causa numero um de "a placa nao enviou nada".
  int tentativas = 0;
  while (WiFi.status() != WL_CONNECTED && tentativas < 40) {
    delay(500);
    Serial.print(".");
    tentativas++;
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("conectado. IP da placa: ");
    Serial.println(WiFi.localIP());
    digitalWrite(PIN_LED, HIGH);
  } else {
    Serial.println("FALHA: nao conectou. Verifique o nome da rede e a senha.");
    Serial.println("O codigo segue rodando: e o que a aula 3 aula 2 vai tratar.");
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("sem rede: envio pulado, o loop continua");
    delay(INTERVALO_MS);
    return;
  }

  digitalWrite(PIN_LED, LOW);

  // O corpo do POST. A String monta o JSON inteiro, chave por chave.
  // O primeiro operando da soma precisa ser String. Duas strings do C
  // ("abc" + "def") NAO concatenam em C++ — o compilador recusa, e o aluno
  // que tentar isso na lousa aprende o motivo.
  String json = String("{\"id_placa\":\"esp32-lab01\"")
              + ",\"temp_c_simulado\":26.5"
              + ",\"umidade_pct_simulado\":59"
              + ",\"dt_leitura\":\"2026-09-30 10:00:00\"}";

  String url = "http://" + String(IP_SERVIDOR) + ":" + String(PORTA_SERVIDOR) + CAMINHO;

  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  int codigo = http.POST(json);

  Serial.println("-------------------------------------------------");
  Serial.print("POST ");
  Serial.print(url);
  Serial.println();
  Serial.print("enviado: ");
  Serial.println(json);

  if (codigo > 0) {
    Serial.print("resposta do servidor, codigo ");
    Serial.print(codigo);
    Serial.print(" (");
    Serial.print(codigo == 201 ? "criado" : "outro");
    Serial.println(")");
    Serial.print("corpo: ");
    Serial.println(http.getString());
  } else {
    // O codigo NEGATIVO vem da propria pilha de rede, nao do servidor.
    Serial.print("falha de rede, codigo ");
    Serial.print(codigo);
    Serial.println(" (o servidor nao respondeu)");
  }

  http.end();   // sempre: sem isso a conexao fica presa e a proxima falha
  digitalWrite(PIN_LED, HIGH);
  delay(INTERVALO_MS);
}

Por que assim e não de outro jeito. O await new Promise(...) antes do fetch e obrigatorio: sem ele, o fetch dispara antes do servidor terminar de abrir a porta, e o exemplo falha de forma intermitente — a falha mais cara de depurar que existe. O servidor.close() no fim também e obrigatorio: sem ele, o processo fica vivo e o terminal parece travado.

O http.end() da placa e o mesmo cuidado pelo outro lado. Sem ele, cada envio deixa a conexão presa, e a placa trava na terceira ou na quarta tentativa com um código de rede negativo que não tem nada a ver com o servidor.

Criterios de correcao

CritérioPontos
Servidor sobe, responde e fica rodando no terminal2 pontos
Corpo acumulado em req.on('data') e fechado em req.on('end')3 pontos
POST /leitura responde 201 e GET /status responde 2002 pontos
Item 4 executado: o comportamento sem o end foi medido e anotado com o tempo3 pontos
Item 5 respondido: cliente ou servidor, com justificativa1 ponto
Item 7 respondido: quem fornece o endereco, e por que1 ponto

Erros comuns

ErroComo apareceCorrecao
Esquecer o req.on('end')O curl fica carregando até dar timeout, e o log mostra o corpo chegando mas nenhuma resposta"O corpo chegou inteiro, e você so avisou o Node de cada pedaco. Falta avisar que acabou. Toda vez que você junta pedacos, precisa do evento de fim."
Chamar res.writeHead e esquecer o res.endO navegador fica carregando, o status nem aparece"writeHead so anota. E o end que fecha e envia. E o mesmo defeito do end do req, visto do outro lado."
Usar listen(3000) e bater em EADDRINUSE"Node quebrou""A porta 3000 já esta ocupada, por outro programa seu. Use listen(0) e pegue a porta com servidor.address().port."
Fazer o fetch antes do listen resolverFunciona as vezes e falha outras"Você deu fetch antes do servidor estar pronto. Espere o listen resolver com await new Promise(...)."
Comparar req.url com /leitura quando o cliente mandou /leitura?placa=lab01Cai no 404 com o cliente mandando a URL certa"O req.url traz o caminho com a query. Compare com startsWith ou corte no ? antes de comparar."
Enviar o JSON sem o cabecalho e culpar o Node"JSON.parse deu erro""Sem Content-Type: application/json o servidor não sabe o que e isso. O curl precisa de -H, e a placa precisa de http.addHeader."
Achar que o 201 e "o Node gravou no banco"Item 5 com essa resposta"Hoje o Node respondeu 201 e não gravou nada: o banco entra no dia 4. Status 201 quer dizer que o servidor aceitou o pedido, e não que o dado foi salvo."

Desafio extra

Faca o servidor aceitar dois corpos de tamanho bem diferente: um POST com 57 bytes de JSON e um com 200 KB de JSON. Conte quantas vezes o evento data dispara em cada caso, usando um contador no log. Depois escreva em uma frase por que o mesmo código funciona para os dois, e qual e o tamanho em que um corpo += pedaco sem join comecaria a doer. A resposta que o professor espera: o data dispara uma vez para corpo pequeno e muitas vezes para corpo grande, e o += em laco fica caro porque cada concatenacao cria uma string nova.

>

A resolucao, compilada

// Aula 2 do dia 2: agora a placa tem um destino. O servidor HTTP em Node que o
// professor subiu e o servidor que o dia 2 aula 1 explicou. A placa fala
// HTTP e o professor mostra o log do Node receiving cada POST.
//
// WiFi e um nome e uma senha FICTICIOS: a rede de teste do professor troca de
// nome toda aula, e nenhum segredo real entra em material. O aluno troca
// pelas credenciais da rede da escola.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// GPIO2 e o LED embutido na maioria dos DevKit: serve de indicador de vida.
const int PIN_LED = 2;

// Credenciais da rede. FICTICIAS de proposito, com este comentario.
const char* WIFI_SSID     = "rede-teste-lab01";
const char* CHAVE_REDE = "ficticia-lab01";

// Endereco do PC que roda o Node. O IP e da maquina do professor, nunca do
// roteador: o ESP32 conversa com o processo, nao com a internet.
const char* IP_SERVIDOR = "192.168.0.10";
const int   PORTA_SERVIDOR = 3000;
const char* CAMINHO = "/leitura";

const long INTERVALO_MS = 5000;

WiFiClient cliente;
HTTPClient http;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("2o trimestre, dia 2, aula 2 - a placa fala com o servidor");
  Serial.println("-------------------------------------------------");

  Serial.print("conectando em ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, CHAVE_REDE);

  // Nao existe "esperar o wifi": a placa precisa checar o status num laco.
  // Esperar um tempo fixo e a causa numero um de "a placa nao enviou nada".
  int tentativas = 0;
  while (WiFi.status() != WL_CONNECTED && tentativas < 40) {
    delay(500);
    Serial.print(".");
    tentativas++;
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("conectado. IP da placa: ");
    Serial.println(WiFi.localIP());
    digitalWrite(PIN_LED, HIGH);
  } else {
    Serial.println("FALHA: nao conectou. Verifique o nome da rede e a senha.");
    Serial.println("O codigo segue rodando: e o que a aula 3 aula 2 vai tratar.");
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("sem rede: envio pulado, o loop continua");
    delay(INTERVALO_MS);
    return;
  }

  digitalWrite(PIN_LED, LOW);

  // O corpo do POST. A String monta o JSON inteiro, chave por chave.
  // O primeiro operando da soma precisa ser String. Duas strings do C
  // ("abc" + "def") NAO concatenam em C++ — o compilador recusa, e o aluno
  // que tentar isso na lousa aprende o motivo.
  String json = String("{\"id_placa\":\"esp32-lab01\"")
              + ",\"temp_c_simulado\":26.5"
              + ",\"umidade_pct_simulado\":59"
              + ",\"dt_leitura\":\"2026-09-30 10:00:00\"}";

  String url = "http://" + String(IP_SERVIDOR) + ":" + String(PORTA_SERVIDOR) + CAMINHO;

  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  int codigo = http.POST(json);

  Serial.println("-------------------------------------------------");
  Serial.print("POST ");
  Serial.print(url);
  Serial.println();
  Serial.print("enviado: ");
  Serial.println(json);

  if (codigo > 0) {
    Serial.print("resposta do servidor, codigo ");
    Serial.print(codigo);
    Serial.print(" (");
    Serial.print(codigo == 201 ? "criado" : "outro");
    Serial.println(")");
    Serial.print("corpo: ");
    Serial.println(http.getString());
  } else {
    // O codigo NEGATIVO vem da propria pilha de rede, nao do servidor.
    Serial.print("falha de rede, codigo ");
    Serial.print(codigo);
    Serial.println(" (o servidor nao respondeu)");
  }

  http.end();   // sempre: sem isso a conexao fica presa e a proxima falha
  digitalWrite(PIN_LED, HIGH);
  delay(INTERVALO_MS);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia02 aula2.