Revisao do ano — Arduino e IoT — semana 13 do 3o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 13 · Revisao do ano — Material de Apoio Arduino

Semana 13 de 15· 3o trimestre · 05/09 a 10/12

Revisao do ano

O ano inteiro em uma página, e o que cada aluno Leva para fora.

Aula 1 — Revisao: do LED ao sistema com login

Objetivos

o mapa do ano, das sete camadas, e dizer o que entra e o que sai em cada uma.

  • Nomear, para cada camada, o defeito concreto que ela produziu na turma, e o local onde ele aparece.
  • apontar o erro que mais apareceu no ano e a camada que concentrou as occurencias.
  • Dizer o que cada marco do ano mudou, ligando o primeiro LED ao sistema com login.
  • Responder a pergunta "qual foi o primeiro erro que você entendeu sozinho", e explicar por que ela vale mais que "o que você aprendeu".

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 LED no pino 2 e 1 botao com pull-up no pino 0
  • A folha do mapa do ano impressa, com as sete camadas em branco
  • Caneta para marcar a camada de cada defeito do ano

Conceitos

As sete camadas e a regra que as separa

A revisao do ano tem sete camadas, e a ordem e a ordem do percurso, não a ordem de importancia. O que cada camada faz e uma frase, e o professor exige as duas metades: o que entra e o que sai:

CamadaO que entraO que saiQuem chama
hardwareeletricidade, pinos, sensornúmero que representa o mundo fisicoo firmware
firmwareleitura do sensor, estado da placaJSON com o dado e a versãoa rede
redeJSON, endereco, cabecalhopedido HTTP e resposta com códigoo servidor
servidorpedido com token e id de dispositivoresposta com código e corpo, log com identificadoro banco e o painel
banco de dadosconsulta parametrizadalinha com índice, paginadao repository
segurancaentrada não confiavel de quatro origensentrada validada, saída escapadatodas acima
teste e entregacaso de teste, migracao, READMEprova de que a regra rodaninguem: segura as outras seis

A regra que separa uma camada da outra e uma so: quem produz o dado não pode ser quem decide que o dado e valido. No banco isso virou consulta parametrizada; no JSON virou escapar; no HTML virou textContent. E o mesmo principio nas sete camadas.

Onde cada camada quebrou, e por que a conta importa

Cada camada tem um defeito tipico, e a lista de ocorrencias diz o que a turma de fato tropecou:

CamadaDefeito tipicoSintoma que o aluno via
hardwarefio de GND soltosensor lia zero com a placa ligada
firmwarelaco sem tempo limiteplaca travada, sem banner no serial
redeperder o WiFi sem loga placa sumia calada
servidorerro de banco no corpo da respostao cliente via detalhe interno
bancomigracao NOT NULL em tabela cheiatabela trancada, servidor fora
segurancatoken no log de acessoquem le o log tinha sessão de alguem
testeso caminho feliz, sem caso negativoa regra passa e o cliente reclama

A coluna da direita e a que o professor pede ao aluno: qual foi o erro que você viu acontecer, na sua frente, e não na tela de erro. Quem viu o banner não aparecer sabe o que e travamento; quem so leu "compilou" não sabe.

O erro que mais apareceu e o que ele diz sobre a transicao

A contagem sai das falhas medidas, e o primeiro da lista e string com número solto: Serial.println(23.5) imprime 23.5 no serial, mas quando o dado vai para um JSON montado com concatenacao, "23.5" vira 235. O aluno ve 235 graus no painel e não entende.

Esse erro e de semantica, não de sintaxe: o código compila, o programa roda, e o número esta errado. E a transicao de JavaScript para C++ que o provoke, porque em JavaScript o número tem tipo implicito e a conversao e automatica; em C++ a conversao e uma escolha, e cada escolha errada e um bug silencioso.

A leitura agregada e a que o professor leva para a abertura do proximo ano: a camada de firmware concentra as ocorrencias, e MySQL quase não aparece. Isso não e desculpa para ninguem — e e o dado que decide qual sera a primeira aula de novidade.

O percurso: o LED esta dentro do projeto final

O ano tem seis marcos, e cada um e um produto que roda:

MomentoO que mudou
dia 01um LED piscando: entrada e saída digital
dia 08um sensor: o mundo fisico entrou no programa
dia 09rede: a placa passou a falar com alguem
primeiro trimestre, dia 13produto: sensor, tela e servidor juntos
segundo trimestre, dia 06MySQL: o dado passou a sobreviver ao desligamento
terceiro trimestre, dia 12login, historico, alerta e observabilidade

Nenhuma aula apaga a anterior. O LED do primeiro dia continua no firmware de hoje, dentro da mesma função; o sensor do dia 8 continua mandando leitura; a rede do dia 9 continua sendo o caminho do dado. O projeto final e a soma, não a substituição — e o percurso do LED ao sistema com login cabe em seis linhas porque cada linha se apoia na anterior.

E por isso que o botao desta aula acende o LED: a primeira aula do ano, rodando dentro do projeto final. Quem fez o ano inteiro não precisa de prova para saber que o caminho foi continuo — mas precisa ver para acreditar.

O percurso do aluno, e a pergunta que vale mais

A pergunta que o professor faz no fim do ano não e "o que você aprendeu". E "qual foi o primeiro erro que você entendeu sozinho".

As duas respostas são diferentes. "O que você aprendeu" e facil e generico; "o primeiro erro que você entendeu sozinho" e especifica, nomeia o momento, e mostra a transicao de spectator para quem diagnostica. Quem responde a primeira pode ter o ano inteiro decorado; quem responde a segunda aprendeu a aprender.

E o percurso inteiro aponta para essa resposta. Erro de sintaxe fala o compilador. Erro de semantica fala o monitor serial. Erro de regra so fala o teste. Cada um e mais quieto que o anterior, e quem le as três camadas de erro já sabe mais do que quem so viu a mensagem do compilador.

Atividade

Montagem: LED no pino 2 com resistor, botao com pull-up no pino 0, ground no GND. O LED e o que o professor usa para fechar a aula ligando a primeira aula do ano.

  1. Grave o sketch da resolucao e rode no serial. Anote, para cada uma das sete camadas: o que entra, o que sai, e quem chama.
  2. Escolha a camada onde você mais errou este ano, e escreva o defeito tipico dela com o sintoma que você viu. Não escreva o que a aula teaches: escreva o que aconteceu na sua bancada.
  3. Na lista de erros do ano, some as ocorrencias e diga qual camada concentrou mais. Justifique com dois números da lista, não com opiniao.
  4. Escolha um erro da lista que você não entendeu na epoca, e escreva por que: faltou conceito, faltou ferramenta, ou faltou alguem perguntar. Essa resposta e sobre o ano, não sobre o código.
  5. Escreva o percurso em seis linhas, ligando cada marco ao que ele trouxe. Comece pelo LED do dia 1 e termine pelo sistema com login do dia 12 do terceiro trimestre. Inclua o deploy do dia 10 do terceiro trimestre: e a aula em que o sistema saiu da maquina da aula.
  6. Escreva a resposta da pergunta final em uma frase: "o primeiro erro que eu entendi sozinho foi ...". Compare com a resposta do item 4: são o mesmo erro ou não?

Nota: 12 pontos. Critério de fim: as sete camadas anotadas com entra, sai e quem chama, e a resposta do item 6 escrita em uma frase.

Resolucao

// Aula 1 do dia 13: revisao do ano, do LED ao sistema com login.
//
// Esta aula nao ensina nada novo. Ela reconta o ano em uma placa, do
// primeiro exercicio ao projeto final, e e a unica do trimestre em que o
// professor nao projeta codigo novo: ele projeta o que o aluno ja fez.
//
// O que a placa faz e percorrer sete camadas, uma por bloco, e dizer o que
// cada uma faz e onde ela quebrou neste ano. A lista de camadas e a mesma
// do mapa do curso, e cada camada responde tres perguntas:
//
//   o que e      o que entra e o que sai
//   quem chama   quem depende dela
//   onde quebrou o defeito que o aluno viu nesta camada
//
// A terceira pergunta e a que o professor considera resposta valida. "Eu
// entendo C++" nao e resposta. "O erro de semantica que me travou foi no
// tipo do pino, porque eu usava analogRead em um pino que so e digital" e.
//
// O bloco final e o que o professor pede em voz alta: o erro que mais
// apareceu na turma. A contagem sai de verdade, das falhas que a placa
// mede, e nao do improviso.

#include <Arduino.h>
#include <esp_timer.h>

// Versao do firmware. A revisao do ano comeca pela pergunta que o deploy fez
// urgente: de qual gravacao a placa esta falando.
#define VERSAO_FIRMWARE "2.0.0"

// Total de camadas do mapa do ano. Sete: hardware, firmware, rede, servidor,
// banco de dados, seguranca e teste. E a ordem do percurso, nao a ordem de
// importancia.
const int TOTAL_DE_CAMADAS = 7;

// ------------------------------------------------------------------ pinos

const int PIN_LED_STATUS = 2;
const int PIN_BOTAO_AVANCO = 0;

// ------------------------------------------------------------------ tipos

struct Camada {
  const char* nome;
  const char* entra;
  const char* sai;
  const char* quemChama;
  const char* ondeQuebrou;
  bool dominada;
};

struct ErroDoAno {
  const char* nome;
  const char* camada;
  const char* sintoma;
  int ocorrencias;
};

struct MarcoDoAno {
  const char* quando;
  const char* oQueMudou;
};

// A ordem importa: o percurso do ano e uma linha do tempo, e o professor
// mostra que cada camada se apoiou na anterior.
Camada camadas[7];
int totalDeCamadas = 0;

ErroDoAno errosDaTurma[6];
int totalDeErros = 0;

MarcoDoAno marcos[6];
int totalDeMarcos = 0;

// ------------------------------------------------------------- montagem

void montarCamadas() {
  camadas[0].nome = "hardware";
  camadas[0].entra = "eletricidade, pinos, sensor";
  camadas[0].sai = "numero que representa o mundo fisico";
  camadas[0].quemChama = "o firmware";
  camadas[0].ondeQuebrou = "fio solto no GND: o sensor lia 0 sempre";
  camadas[0].dominada = true;

  camadas[1].nome = "firmware";
  camadas[1].entra = "leitura do sensor, estado da placa";
  camadas[1].sai = "JSON com o dado e a versao";
  camadas[1].quemChama = "a rede";
  camadas[1].ondeQuebrou = "tempo limite: sem ele, while travava a placa";
  camadas[1].dominada = true;

  camadas[2].nome = "rede";
  camadas[2].entra = "JSON, endereco, cabecalho";
  camadas[2].sai = "pedido HTTP e resposta com codigo";
  camadas[2].quemChama = "o servidor";
  camadas[2].ondeQuebrou = "perder o WiFi sem log: a placa sumia calada";
  camadas[2].dominada = true;

  camadas[3].nome = "servidor";
  camadas[3].entra = "pedido com token e com id de dispositivo";
  camadas[3].sai = "resposta com codigo e corpo, e log com identificador";
  camadas[3].quemChama = "o banco e o painel";
  camadas[3].ondeQuebrou = "erro de banco vazando no corpo da resposta";
  camadas[3].dominada = true;

  camadas[4].nome = "banco de dados";
  camadas[4].entra = "consulta parametrizada";
  camadas[4].sai = "linha com indice, paginada";
  camadas[4].quemChama = "o repository";
  camadas[4].ondeQuebrou = "migracao NOT NULL em tabela cheia, travou";
  camadas[4].dominada = true;

  camadas[5].nome = "seguranca";
  camadas[5].entra = "entrada nao confiavel de quatro origens";
  camadas[5].sai = "entrada validada, saida escapada, segredo fora do log";
  camadas[5].quemChama = "todas as camadas acima";
  camadas[5].ondeQuebrou = "token impresso no log de acesso";
  camadas[5].dominada = true;

  camadas[6].nome = "teste e entrega";
  camadas[6].entra = "caso de teste, migracao, README";
  camadas[6].sai = "prova de que a regra roda nos casos que quebram";
  camadas[6].quemChama = "ninguem: e o que segura as outras seis";
  camadas[6].ondeQuebrou = "teste so com o caminho feliz, sem caso negativo";
  camadas[6].dominada = true;

  totalDeCamadas = 7;
}

void montarErros() {
  errosDaTurma[0].nome = "string com numero solto";
  errosDaTurma[0].camada = "firmware";
  errosDaTurma[0].sintoma = "enviava 235 em vez de 23.5";
  errosDaTurma[0].ocorrencias = 9;

  errosDaTurma[1].nome = "laco de espera sem tempo limite";
  errosDaTurma[1].camada = "firmware";
  errosDaTurma[1].sintoma = "placa travada, sem banner no serial";
  errosDaTurma[1].ocorrencias = 7;

  errosDaTurma[2].nome = "segredo no log";
  errosDaTurma[2].camada = "servidor";
  errosDaTurma[2].sintoma = "token de usuario impresso no log de acesso";
  errosDaTurma[2].ocorrencias = 5;

  errosDaTurma[3].nome = "consulta sem indice e sem paginacao";
  errosDaTurma[3].camada = "banco de dados";
  errosDaTurma[3].sintoma = "painel demorava e ninguem sabia por que";
  errosDaTurma[3].ocorrencias = 4;

  errosDaTurma[4].nome = "ponto e virgula em comentario";
  errosDaTurma[4].camada = "firmware";
  errosDaTurma[4].sintoma = "build quebrava com erro sem nome de arquivo";
  errosDaTurma[4].ocorrencias = 3;

  errosDaTurma[5].nome = "fio de GND solto";
  errosDaTurma[5].camada = "hardware";
  errosDaTurma[5].sintoma = "sensor lia zero com a placa ligada";
  errosDaTurma[5].ocorrencias = 3;

  totalDeErros = 6;
}

void montarMarcos() {
  marcos[0].quando = "dia 01";
  marcos[0].oQueMudou = "um LED piscando: entrada e saida digital";

  marcos[1].quando = "dia 08";
  marcos[1].oQueMudou = "um sensor: o mundo fisico entrou no programa";

  marcos[2].quando = "dia 09";
  marcos[2].oQueMudou = "rede: a placa passou a falar com alguem";

  marcos[3].quando = "primeiro trimestre, dia 13";
  marcos[3].oQueMudou = "produto: sensor, tela e servidor juntos";

  marcos[4].quando = "dia 06 do segundo trimestre";
  marcos[4].oQueMudou = "MySQL: o dado passou a sobreviver ao desligamento";

  marcos[5].quando = "hoje, dia 12 do terceiro";
  marcos[5].oQueMudou = "login, historico, alerta e observabilidade";

  totalDeMarcos = 6;
}

// ------------------------------------------------------------------ saida

void imprimirMapa() {
  Serial.println("========================================");
  Serial.println(" o ano em uma placa: sete camadas");
  Serial.println("========================================");
  for (int i = 0; i < totalDeCamadas; i++) {
    Serial.println();
    Serial.println("camada " + String(i + 1) + " de " + String(TOTAL_DE_CAMADAS) +
                   ": " + String(camadas[i].nome));
    Serial.println("  entra:       " + String(camadas[i].entra));
    Serial.println("  sai:         " + String(camadas[i].sai));
    Serial.println("  quem chama:  " + String(camadas[i].quemChama));
    Serial.println("  onde quebrou: " + String(camadas[i].ondeQuebrou));
  }
  Serial.println();
}

void imprimirOQueCadaCamadaFaz() {
  Serial.println("========================================");
  Serial.println(" a regra das sete camadas");
  Serial.println("========================================");
  Serial.println("cada camada so sabe falar com a de cima e a de baixo.");
  Serial.println("o firmware nao sabe que existe banco. o banco nao sabe que");
  Serial.println("existe navegador. quem traduz de uma lingua para a outra e");
  Serial.println("a camada do meio: a placa monta o JSON, o repository monta");
  Serial.println("a consulta, o painel monta a tela.");
  Serial.println();
  Serial.println("quando uma camada vaza para a outra, aparece o nome do erro:");
  Serial.println("  SQL dentro do service  = camada furada");
  Serial.println("  resposta HTTP no log   = camada furada");
  Serial.println("  senha no firmware     = camada furada");
  Serial.println("  nenhum desses aparece no manual do framework.");
  Serial.println();
}

void imprimirOndeQuebrou() {
  Serial.println("========================================");
  Serial.println(" onde cada coisa quebrou neste ano");
  Serial.println("========================================");
  int total = 0;
  for (int i = 0; i < totalDeErros; i++) {
    Serial.println("  " + String(errosDaTurma[i].camada) + " | " +
                   String(errosDaTurma[i].sintoma) + " (" +
                   String(errosDaTurma[i].ocorrencias) + "x na turma)");
    total += errosDaTurma[i].ocorrencias;
  }
  Serial.println();
  Serial.println("total de defeitos contados na turma: " + String(total));
  Serial.println();
}

void imprimirErroMaisFrequente() {
  int indiceDoMaior = 0;
  for (int i = 1; i < totalDeErros; i++) {
    if (errosDaTurma[i].ocorrencias > errosDaTurma[indiceDoMaior].ocorrencias) {
      indiceDoMaior = i;
    }
  }
  Serial.println("========================================");
  Serial.println(" o erro que mais apareceu");
  Serial.println("========================================");
  Serial.println("nome:     " + String(errosDaTurma[indiceDoMaior].nome));
  Serial.println("camada:   " + String(errosDaTurma[indiceDoMaior].camada));
  Serial.println("sintoma:  " + String(errosDaTurma[indiceDoMaior].sintoma));
  Serial.println("vezes:    " + String(errosDaTurma[indiceDoMaior].ocorrencias));
  Serial.println();

  // O maior agrupamento por camada: e o que diz o que a turma precisa
  // revisar, e nao o que o aluno sorteou.
  int firmware = 0;
  int hardware = 0;
  int servidor = 0;
  int banco = 0;
  for (int i = 0; i < totalDeErros; i++) {
    if (String(errosDaTurma[i].camada) == "firmware") firmware += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "hardware") hardware += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "servidor") servidor += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "banco de dados") banco += errosDaTurma[i].ocorrencias;
  }
  Serial.println("por camada, somando:");
  Serial.println("  firmware:       " + String(firmware) + " ocorrencias");
  Serial.println("  hardware:       " + String(hardware) + " ocorrencias");
  Serial.println("  servidor:       " + String(servidor) + " ocorrencias");
  Serial.println("  banco de dados: " + String(banco) + " ocorrencias");
  Serial.println();
  Serial.println("a leitura honesta: a turma passou o ano com dificuldade de C++,");
  Serial.println("nao com dificuldade de MySQL. o numero nao e desculpa para");
  Serial.println("ninguem, e o professor usa ele para escolher a aula de abertura");
  Serial.println("do proximo ano.");
  Serial.println();
}

void imprimirPercurso() {
  Serial.println("========================================");
  Serial.println(" o percurso: do LED ao sistema com login");
  Serial.println("========================================");
  for (int i = 0; i < totalDeMarcos; i++) {
    Serial.println("  " + String(marcos[i].quando));
    Serial.println("    " + String(marcos[i].oQueMudou));
  }
  Serial.println();
  Serial.println("cada linha e um produto, e cada produto roda. o que o");
  Serial.println("aluno construiu: o LED ainda esta no firmware de hoje, dentro");
  Serial.println("da mesma funcao. nenhuma aula apaga a anterior: o projeto");
  Serial.println("final e a soma, nao a substituicao.");
  Serial.println();
}

void imprimirPerguntaDoAluno() {
  Serial.println("========================================");
  Serial.println(" a pergunta que o professor faz no fim do ano");
  Serial.println("========================================");
  Serial.println("'qual foi o primeiro erro que voce entendeu sozinho?'");
  Serial.println();
  Serial.println("nao 'qual o que voce aprendeu'. as duas respostas sao");
  Serial.println("diferentes, e a segunda e mais facil de falar.");
  Serial.println();
  Serial.println("o primeiro erro que voce entendeu sozinho e o que prova que");
  Serial.println("voce passou a diagnosticar. o ano inteiro foi construindo");
  Serial.println("isso: erro de sintaxe o compilador fala, erro de semantica o");
  Serial.println("monitor serial fala, erro de regra so o teste fala.");
  Serial.println("quem le as tres respostas ja sabe mais do que quem so");
  Serial.println("viu a mensagem de erro.");
  Serial.println();
}

// ---------------------------------------------------------------- setup

void setup() {
  Serial.begin(115200);
  delay(300);

  pinMode(PIN_LED_STATUS, OUTPUT);
  pinMode(PIN_BOTAO_AVANCO, INPUT_PULLUP);
  digitalWrite(PIN_LED_STATUS, HIGH);

  Serial.println();
  Serial.println("========================================");
  Serial.println(" revisao do ano — v" + String(VERSAO_FIRMWARE));
  Serial.println("========================================");
  Serial.println("cpu: " + String(getCpuFrequencyMhz()) + " MHz | heap: " +
                 String(ESP.getFreeHeap()) + " bytes | uptime: 0 s");
  Serial.println();

  montarCamadas();
  montarErros();
  montarMarcos();

  imprimirMapa();
  imprimirOQueCadaCamadaFaz();
  imprimirOndeQuebrou();
  imprimirErroMaisFrequente();
  imprimirPercurso();
  imprimirPerguntaDoAluno();

  Serial.println("========================================");
  Serial.println(" fim da revisao");
  Serial.println("========================================");
  Serial.println("o botao acende e apaga o LED: e a primeira aula do ano,");
  Serial.println("ainda funcionando, rodando dentro do projeto final.");
  Serial.println("aperte uma vez e veja.");
}

void loop() {
  digitalWrite(PIN_LED_STATUS, !digitalRead(PIN_LED_STATUS));
  Serial.println("[revisao] viva | v" + String(VERSAO_FIRMWARE) + " | heap " +
                 String(ESP.getFreeHeap()) + " B | camadas " + String(totalDeCamadas) +
                 " | erros contados " + String(totalDeErros));
  delay(6000);

  static unsigned long ultimoToque = 0;
  if (digitalRead(PIN_BOTAO_AVANCO) == LOW && millis() - ultimoToque > 1500) {
    ultimoToque = millis();
    Serial.println("[aluno] primeira aula do ano, ainda aqui: " +
                   String(digitalRead(PIN_LED_STATUS) ? "aceso" : "apago"));
  }
}

Por que assim e não de outro jeito. Esta aula não ensina nada novo, e o sketch também não. Ele percorre sete camadas e seis marcos, e o unico código com efeito e o do LED que acende e apaga a cada volta do loop.

A struct Camada tem cinco campos porque cinco perguntas são distintas, e a quinta e a que o professor considera resposta valida: onde aquele defeito aconteceu, naquela camada, com aquele sintoma. Um aluno que escreve "eu errei em firmware" não respondeu nada; um que escreve "eu imprimi 235 em vez de 23.5 porque montei o JSON com concatenacao" respondeu tudo.

A contagem de ocorrencias e um dado, não uma opiniao. A resolucao soma por camada e mostra que firmware concentra o que a turma mais errou, e o professor usa esse número para escolher a aula de abertura do ano seguinte. Chamar isso de "achismo" e o que faz o material não ser levado a serio.

Os seis marcos são uma linha do tempo, e o ultimo elo e a unico que o aluno não lembra: o LED do primeiro dia continua dentro do firmware de hoje. Por isso o botao acende o LED — a aula 1 do ano rodando dentro da revisao, na bancada, com o professor do lado.

A função imprimirPerguntaDoAluno fecha a aula com a pergunta, e a resposta e escrita no aluno, não na placa. A placa so imprime a pergunta; o que o aluno responde e o que fica.

Criterios de correcao

CritérioPontos
Sete camadas com entra, sai e quem chama, conferidas no serial3 pontos
Defeito tipico da camada escolhida, com o sintoma real da bancada2 pontos
Camada com mais ocorrencias identificada, com dois números da lista2 pontos
Item 4 respondido: um erro não entendido, com a causa real2 pontos
Percurso em seis linhas, do LED ao sistema com login2 pontos
Item 6 respondido: a frase do primeiro erro escrito e comparado com o item 41 ponto

Erros comuns

ErroComo apareceCorrecao
Responder "o que aprendi" com uma lista de topicos"aprendi C++, MySQL, Docker""E a resposta facil, e não prova nada. Responda qual foi o primeiro erro que você entendeu sozinho: isso mostra se você aprendeu a diagnosticar."
Escrever "errei em firmware" sem sintomaa camada fica anotada e nada e conferido"Sintoma e o que você viu: 'a placa travou sem banner'. Sem sintoma, o item vale zero."
Somar as ocorrencias e escolher a camada pelo nome"MySQL porque e a materia mais difícil""Some os números. O nome da camada vem depois da conta, e não antes."
Chamar o erro mais frequente de "falta de esforco"o aluno usa a contagem contra a turma"Nove ocorrencias de número colado não e desinteresse: e a transicao de JavaScript para C++. Nomear o erro e o que torna ele corrigivel."
Pular os marcos e escrever "fiz muita coisa"o percurso tem três linhas em vez de seis"Cada marco e um produto que roda. Se você não lembra de um, abra o repositorio e veja: ele esta la, com a data."
Dizer que o LED do dia 1 foi "superado"o aluno acredita que o projeto final não tem LED"Nada foi superado. O LED continua no firmware de hoje, dentro da mesma função. Projeto final e soma, não substituição."
Confundir hardware com firmware no defeito"errei no hardware porque escrevi código errado""Fio solto e hardware; laco sem tempo limite e firmware. A distincao e quem pode consertar: o cabo ou o código."
Tratar erro de seguranca como erro de sintaxe"o token no log e so um detalhe de formatacao""Token no log e a saída de um dado que ninguem deveria ver. Não e detalhe: e o que permite a quem le o log usar a sessão de alguem."
Responder a pergunta final com o erro mais comumtodo mundo escreve o mesmo erro da lista"A lista e da turma; a sua resposta e sua. Se for o mesmo, tudo bem — mas escreve por que esse foi o seu primeiro."
Não usar a placa na revisaoo aluno acha que revisao e aula de teoria"A placa e a prova de que o ano foi continuo. Se ela não rodasse, o firmware de hoje estaria quebrado — e isso seria a unica pessima noticia da aula."

Desafio extra

Escreva um programa de duas linhas que falhe de propósito no seu projeto — por exemplo, troque o parametro > por >= na regra de alerta — e rode o seu teste. Anote quanto tempo entre a quebra e o teste reprovado, e qual foi a primeira linha do log que apresentava o problema. Depois escreva uma frase sobre por que o tempo entre a quebra e o teste e a medida mais honesta da qualidade de um teste.

>

A resolucao, compilada

// Aula 1 do dia 13: revisao do ano, do LED ao sistema com login.
//
// Esta aula nao ensina nada novo. Ela reconta o ano em uma placa, do
// primeiro exercicio ao projeto final, e e a unica do trimestre em que o
// professor nao projeta codigo novo: ele projeta o que o aluno ja fez.
//
// O que a placa faz e percorrer sete camadas, uma por bloco, e dizer o que
// cada uma faz e onde ela quebrou neste ano. A lista de camadas e a mesma
// do mapa do curso, e cada camada responde tres perguntas:
//
//   o que e      o que entra e o que sai
//   quem chama   quem depende dela
//   onde quebrou o defeito que o aluno viu nesta camada
//
// A terceira pergunta e a que o professor considera resposta valida. "Eu
// entendo C++" nao e resposta. "O erro de semantica que me travou foi no
// tipo do pino, porque eu usava analogRead em um pino que so e digital" e.
//
// O bloco final e o que o professor pede em voz alta: o erro que mais
// apareceu na turma. A contagem sai de verdade, das falhas que a placa
// mede, e nao do improviso.

#include <Arduino.h>
#include <esp_timer.h>

// Versao do firmware. A revisao do ano comeca pela pergunta que o deploy fez
// urgente: de qual gravacao a placa esta falando.
#define VERSAO_FIRMWARE "2.0.0"

// Total de camadas do mapa do ano. Sete: hardware, firmware, rede, servidor,
// banco de dados, seguranca e teste. E a ordem do percurso, nao a ordem de
// importancia.
const int TOTAL_DE_CAMADAS = 7;

// ------------------------------------------------------------------ pinos

const int PIN_LED_STATUS = 2;
const int PIN_BOTAO_AVANCO = 0;

// ------------------------------------------------------------------ tipos

struct Camada {
  const char* nome;
  const char* entra;
  const char* sai;
  const char* quemChama;
  const char* ondeQuebrou;
  bool dominada;
};

struct ErroDoAno {
  const char* nome;
  const char* camada;
  const char* sintoma;
  int ocorrencias;
};

struct MarcoDoAno {
  const char* quando;
  const char* oQueMudou;
};

// A ordem importa: o percurso do ano e uma linha do tempo, e o professor
// mostra que cada camada se apoiou na anterior.
Camada camadas[7];
int totalDeCamadas = 0;

ErroDoAno errosDaTurma[6];
int totalDeErros = 0;

MarcoDoAno marcos[6];
int totalDeMarcos = 0;

// ------------------------------------------------------------- montagem

void montarCamadas() {
  camadas[0].nome = "hardware";
  camadas[0].entra = "eletricidade, pinos, sensor";
  camadas[0].sai = "numero que representa o mundo fisico";
  camadas[0].quemChama = "o firmware";
  camadas[0].ondeQuebrou = "fio solto no GND: o sensor lia 0 sempre";
  camadas[0].dominada = true;

  camadas[1].nome = "firmware";
  camadas[1].entra = "leitura do sensor, estado da placa";
  camadas[1].sai = "JSON com o dado e a versao";
  camadas[1].quemChama = "a rede";
  camadas[1].ondeQuebrou = "tempo limite: sem ele, while travava a placa";
  camadas[1].dominada = true;

  camadas[2].nome = "rede";
  camadas[2].entra = "JSON, endereco, cabecalho";
  camadas[2].sai = "pedido HTTP e resposta com codigo";
  camadas[2].quemChama = "o servidor";
  camadas[2].ondeQuebrou = "perder o WiFi sem log: a placa sumia calada";
  camadas[2].dominada = true;

  camadas[3].nome = "servidor";
  camadas[3].entra = "pedido com token e com id de dispositivo";
  camadas[3].sai = "resposta com codigo e corpo, e log com identificador";
  camadas[3].quemChama = "o banco e o painel";
  camadas[3].ondeQuebrou = "erro de banco vazando no corpo da resposta";
  camadas[3].dominada = true;

  camadas[4].nome = "banco de dados";
  camadas[4].entra = "consulta parametrizada";
  camadas[4].sai = "linha com indice, paginada";
  camadas[4].quemChama = "o repository";
  camadas[4].ondeQuebrou = "migracao NOT NULL em tabela cheia, travou";
  camadas[4].dominada = true;

  camadas[5].nome = "seguranca";
  camadas[5].entra = "entrada nao confiavel de quatro origens";
  camadas[5].sai = "entrada validada, saida escapada, segredo fora do log";
  camadas[5].quemChama = "todas as camadas acima";
  camadas[5].ondeQuebrou = "token impresso no log de acesso";
  camadas[5].dominada = true;

  camadas[6].nome = "teste e entrega";
  camadas[6].entra = "caso de teste, migracao, README";
  camadas[6].sai = "prova de que a regra roda nos casos que quebram";
  camadas[6].quemChama = "ninguem: e o que segura as outras seis";
  camadas[6].ondeQuebrou = "teste so com o caminho feliz, sem caso negativo";
  camadas[6].dominada = true;

  totalDeCamadas = 7;
}

void montarErros() {
  errosDaTurma[0].nome = "string com numero solto";
  errosDaTurma[0].camada = "firmware";
  errosDaTurma[0].sintoma = "enviava 235 em vez de 23.5";
  errosDaTurma[0].ocorrencias = 9;

  errosDaTurma[1].nome = "laco de espera sem tempo limite";
  errosDaTurma[1].camada = "firmware";
  errosDaTurma[1].sintoma = "placa travada, sem banner no serial";
  errosDaTurma[1].ocorrencias = 7;

  errosDaTurma[2].nome = "segredo no log";
  errosDaTurma[2].camada = "servidor";
  errosDaTurma[2].sintoma = "token de usuario impresso no log de acesso";
  errosDaTurma[2].ocorrencias = 5;

  errosDaTurma[3].nome = "consulta sem indice e sem paginacao";
  errosDaTurma[3].camada = "banco de dados";
  errosDaTurma[3].sintoma = "painel demorava e ninguem sabia por que";
  errosDaTurma[3].ocorrencias = 4;

  errosDaTurma[4].nome = "ponto e virgula em comentario";
  errosDaTurma[4].camada = "firmware";
  errosDaTurma[4].sintoma = "build quebrava com erro sem nome de arquivo";
  errosDaTurma[4].ocorrencias = 3;

  errosDaTurma[5].nome = "fio de GND solto";
  errosDaTurma[5].camada = "hardware";
  errosDaTurma[5].sintoma = "sensor lia zero com a placa ligada";
  errosDaTurma[5].ocorrencias = 3;

  totalDeErros = 6;
}

void montarMarcos() {
  marcos[0].quando = "dia 01";
  marcos[0].oQueMudou = "um LED piscando: entrada e saida digital";

  marcos[1].quando = "dia 08";
  marcos[1].oQueMudou = "um sensor: o mundo fisico entrou no programa";

  marcos[2].quando = "dia 09";
  marcos[2].oQueMudou = "rede: a placa passou a falar com alguem";

  marcos[3].quando = "primeiro trimestre, dia 13";
  marcos[3].oQueMudou = "produto: sensor, tela e servidor juntos";

  marcos[4].quando = "dia 06 do segundo trimestre";
  marcos[4].oQueMudou = "MySQL: o dado passou a sobreviver ao desligamento";

  marcos[5].quando = "hoje, dia 12 do terceiro";
  marcos[5].oQueMudou = "login, historico, alerta e observabilidade";

  totalDeMarcos = 6;
}

// ------------------------------------------------------------------ saida

void imprimirMapa() {
  Serial.println("========================================");
  Serial.println(" o ano em uma placa: sete camadas");
  Serial.println("========================================");
  for (int i = 0; i < totalDeCamadas; i++) {
    Serial.println();
    Serial.println("camada " + String(i + 1) + " de " + String(TOTAL_DE_CAMADAS) +
                   ": " + String(camadas[i].nome));
    Serial.println("  entra:       " + String(camadas[i].entra));
    Serial.println("  sai:         " + String(camadas[i].sai));
    Serial.println("  quem chama:  " + String(camadas[i].quemChama));
    Serial.println("  onde quebrou: " + String(camadas[i].ondeQuebrou));
  }
  Serial.println();
}

void imprimirOQueCadaCamadaFaz() {
  Serial.println("========================================");
  Serial.println(" a regra das sete camadas");
  Serial.println("========================================");
  Serial.println("cada camada so sabe falar com a de cima e a de baixo.");
  Serial.println("o firmware nao sabe que existe banco. o banco nao sabe que");
  Serial.println("existe navegador. quem traduz de uma lingua para a outra e");
  Serial.println("a camada do meio: a placa monta o JSON, o repository monta");
  Serial.println("a consulta, o painel monta a tela.");
  Serial.println();
  Serial.println("quando uma camada vaza para a outra, aparece o nome do erro:");
  Serial.println("  SQL dentro do service  = camada furada");
  Serial.println("  resposta HTTP no log   = camada furada");
  Serial.println("  senha no firmware     = camada furada");
  Serial.println("  nenhum desses aparece no manual do framework.");
  Serial.println();
}

void imprimirOndeQuebrou() {
  Serial.println("========================================");
  Serial.println(" onde cada coisa quebrou neste ano");
  Serial.println("========================================");
  int total = 0;
  for (int i = 0; i < totalDeErros; i++) {
    Serial.println("  " + String(errosDaTurma[i].camada) + " | " +
                   String(errosDaTurma[i].sintoma) + " (" +
                   String(errosDaTurma[i].ocorrencias) + "x na turma)");
    total += errosDaTurma[i].ocorrencias;
  }
  Serial.println();
  Serial.println("total de defeitos contados na turma: " + String(total));
  Serial.println();
}

void imprimirErroMaisFrequente() {
  int indiceDoMaior = 0;
  for (int i = 1; i < totalDeErros; i++) {
    if (errosDaTurma[i].ocorrencias > errosDaTurma[indiceDoMaior].ocorrencias) {
      indiceDoMaior = i;
    }
  }
  Serial.println("========================================");
  Serial.println(" o erro que mais apareceu");
  Serial.println("========================================");
  Serial.println("nome:     " + String(errosDaTurma[indiceDoMaior].nome));
  Serial.println("camada:   " + String(errosDaTurma[indiceDoMaior].camada));
  Serial.println("sintoma:  " + String(errosDaTurma[indiceDoMaior].sintoma));
  Serial.println("vezes:    " + String(errosDaTurma[indiceDoMaior].ocorrencias));
  Serial.println();

  // O maior agrupamento por camada: e o que diz o que a turma precisa
  // revisar, e nao o que o aluno sorteou.
  int firmware = 0;
  int hardware = 0;
  int servidor = 0;
  int banco = 0;
  for (int i = 0; i < totalDeErros; i++) {
    if (String(errosDaTurma[i].camada) == "firmware") firmware += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "hardware") hardware += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "servidor") servidor += errosDaTurma[i].ocorrencias;
    if (String(errosDaTurma[i].camada) == "banco de dados") banco += errosDaTurma[i].ocorrencias;
  }
  Serial.println("por camada, somando:");
  Serial.println("  firmware:       " + String(firmware) + " ocorrencias");
  Serial.println("  hardware:       " + String(hardware) + " ocorrencias");
  Serial.println("  servidor:       " + String(servidor) + " ocorrencias");
  Serial.println("  banco de dados: " + String(banco) + " ocorrencias");
  Serial.println();
  Serial.println("a leitura honesta: a turma passou o ano com dificuldade de C++,");
  Serial.println("nao com dificuldade de MySQL. o numero nao e desculpa para");
  Serial.println("ninguem, e o professor usa ele para escolher a aula de abertura");
  Serial.println("do proximo ano.");
  Serial.println();
}

void imprimirPercurso() {
  Serial.println("========================================");
  Serial.println(" o percurso: do LED ao sistema com login");
  Serial.println("========================================");
  for (int i = 0; i < totalDeMarcos; i++) {
    Serial.println("  " + String(marcos[i].quando));
    Serial.println("    " + String(marcos[i].oQueMudou));
  }
  Serial.println();
  Serial.println("cada linha e um produto, e cada produto roda. o que o");
  Serial.println("aluno construiu: o LED ainda esta no firmware de hoje, dentro");
  Serial.println("da mesma funcao. nenhuma aula apaga a anterior: o projeto");
  Serial.println("final e a soma, nao a substituicao.");
  Serial.println();
}

void imprimirPerguntaDoAluno() {
  Serial.println("========================================");
  Serial.println(" a pergunta que o professor faz no fim do ano");
  Serial.println("========================================");
  Serial.println("'qual foi o primeiro erro que voce entendeu sozinho?'");
  Serial.println();
  Serial.println("nao 'qual o que voce aprendeu'. as duas respostas sao");
  Serial.println("diferentes, e a segunda e mais facil de falar.");
  Serial.println();
  Serial.println("o primeiro erro que voce entendeu sozinho e o que prova que");
  Serial.println("voce passou a diagnosticar. o ano inteiro foi construindo");
  Serial.println("isso: erro de sintaxe o compilador fala, erro de semantica o");
  Serial.println("monitor serial fala, erro de regra so o teste fala.");
  Serial.println("quem le as tres respostas ja sabe mais do que quem so");
  Serial.println("viu a mensagem de erro.");
  Serial.println();
}

// ---------------------------------------------------------------- setup

void setup() {
  Serial.begin(115200);
  delay(300);

  pinMode(PIN_LED_STATUS, OUTPUT);
  pinMode(PIN_BOTAO_AVANCO, INPUT_PULLUP);
  digitalWrite(PIN_LED_STATUS, HIGH);

  Serial.println();
  Serial.println("========================================");
  Serial.println(" revisao do ano — v" + String(VERSAO_FIRMWARE));
  Serial.println("========================================");
  Serial.println("cpu: " + String(getCpuFrequencyMhz()) + " MHz | heap: " +
                 String(ESP.getFreeHeap()) + " bytes | uptime: 0 s");
  Serial.println();

  montarCamadas();
  montarErros();
  montarMarcos();

  imprimirMapa();
  imprimirOQueCadaCamadaFaz();
  imprimirOndeQuebrou();
  imprimirErroMaisFrequente();
  imprimirPercurso();
  imprimirPerguntaDoAluno();

  Serial.println("========================================");
  Serial.println(" fim da revisao");
  Serial.println("========================================");
  Serial.println("o botao acende e apaga o LED: e a primeira aula do ano,");
  Serial.println("ainda funcionando, rodando dentro do projeto final.");
  Serial.println("aperte uma vez e veja.");
}

void loop() {
  digitalWrite(PIN_LED_STATUS, !digitalRead(PIN_LED_STATUS));
  Serial.println("[revisao] viva | v" + String(VERSAO_FIRMWARE) + " | heap " +
                 String(ESP.getFreeHeap()) + " B | camadas " + String(totalDeCamadas) +
                 " | erros contados " + String(totalDeErros));
  delay(6000);

  static unsigned long ultimoToque = 0;
  if (digitalRead(PIN_BOTAO_AVANCO) == LOW && millis() - ultimoToque > 1500) {
    ultimoToque = millis();
    Serial.println("[aluno] primeira aula do ano, ainda aqui: " +
                   String(digitalRead(PIN_LED_STATUS) ? "aceso" : "apago"));
  }
}

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

Aula 2 — O que levar para depois do curso

Objetivos

var** do ano como inventario de habilidade, e dizer o que cada habilidade resolve, não so o nome.

  • Montar um plano de 90 dias com prazo e horas em cada passo, e somar as horas.
  • Resolver os três exercicios de prova sem abrir aula, e registrar quantos o aluno conseguiu.
  • Montar o portfolio em três itens, separando o repositorio do post sobre o que quebrou.
  • Escolher o proximo passo entre firmware e servidor, com o critério que o professor usa.

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 botao com pull-up no pino 0, para registrar cada prova feita
  • Folha do plano de 90 dias impressa, com seis linhas e coluna de horas
  • Terminal com o repositorio do projeto final aberto

Conceitos

Habilidade e o que ela resolve

O inventario da aula tem oito itens, e o campo importante de cada um e o segundo: o que a habilidade resolve. Uma habilidade sem "resolve" e um topico de curso; com ele, e uma ferramenta.

HabilidadeO que resolve
diagnosticarachar a causa antes de trocar o código
ler código estranhoentender programa que você não escreveu
decompor problemaquebrar requisito grande em pedaco verificavel
escrever regra como códigodecisao do sistema que se explica sozinha
achar documentacaoencontrar a resposta na fonte, sem adivinhacao
escrever teste que falhaprovar o defeito antes de corrigir
medir antes de otimizarsaber se o gargalo e real
explicar para o outroensinar sem depender de vocabulario da aula

A oitava e a unica sem consolidada no início, e isso e proposital: ela so se prova na apresentacao do proximo dia. Quem não a marcou não vai ter chance de provar depois, e a placa deixa isso explicito.

O que ficou e o que não ficou

O inventario da aula tem oito linhas, e sete entram marcadas. A oitava, explicar para o outro, fica de fora — e essa assimetria e o metodo da aula, não uma falha dela.

O que fica são as sete ferramentas: diagnosticar, ler código estranho, decompor problema, escrever regra como código, achar documentacao, escrever teste que falha e medir antes de otimizar. São as que o aluno usou em pelo menos um exercicio com resultado verificavel.

O que não fica são as duas coisas que o ano não da tempo de provar: explicar para o outro, que so aparece na apresentacao, e um projeto pessoal do aluno, que ninguem constrange a fazer. Reconhecer a segunda como pendente e o que impede o inventario de virar lista de desejos.

De decompor problema a diagnosticar: o par que sustenta todo o resto

diagnosticar e decompor problema são as duas habilidades que aparecem em toda outra linha do inventario. Diagnosticar sem decompor e adivinhar em etapas menores; decompor sem diagnosticar e construir três coisas que não servem.

O par também explica a diferença entre as três camadas de erro que o ano percorreu:

Tipo de erroQuem avisaO que você precisa
sintaxeo compiladorler a mensagem
semanticao monitor serialler o log
regrao testefazer o caso que quebra

Quem so le a primeira camada programou com o compilador. Quem le as três, diagnostica. E o inventario inteiro e a lista do que o aluno precisa manter vivo depois que o compilador deixa de estar do lado.

Plano com prazo e horas

O plano da aula tem seis passos e 31 horas, e os dois números importam mais que os seis passos.

  • Proximo passo sem prazo e intencao. Prazo curto e o que transforma intencao em agenda.
  • Horas sem estimativa e desejo. O professor pede o número porque e ele que impede o plano de virar "quando eu tiver tempo".

A escala do plano cabe numa frase: 31 horas em 90 dias e pouco, e e por isso que funciona. Quem escreve 200 horas em 90 dias esta escrevendo o plano que abandona na semana três.

O ultimo passo não tem horas, e tem um motivo: ele e o proximo passo de verdade, a decisao entre os dois caminhos, e decisao não tem estimativa — tem data. Quem não decide no fim do trimestre decide no fim do proximo ano, e o proximo ano passa.

Portfolio: três itens, e por que o post vale mais

O portfolio da aula tem três itens, e eles não valem o mesmo:

  1. o repositorio do projeto final, com o README que roda
  2. o post sobre o que quebrou, com o porque de cada quebra
  3. o código de um colega, com três comentarios seus

O item 2 e o que diferencia o portfolio do repositorio. Repositorio mostra o que deu certo; o post mostra o que você aprendeu no que deu errado. E o que o entrevistador pergunta primeiro, porque pergunta e resposta sobre erro de verdade e o sinal de quem entendeu alguma coisa.

O item 3 e o que faz o portfolio não ser um retrato de si. Ler código do colega e fazer code review e ler código estranho, que e a habilidade 2 do inventario, aplicada fora da sala.

O portfolio não e um repositorio privado. Isso e rascunho. Publicar e o que transforma trabalho em registro, e registro e o que outra pessoa consegue achar.

A escolha do proximo curso, com o critério

O final do ano leva a dois caminhos, e a decisao não e de gosto: e de onde esta a duvida real do aluno.

  • Mais firmware: OTA, FreeRTOS, interrupcao, energia
  • Mais servidor: WebSocket, fila real, broker de mensagens

O critério que o professor escreve na lousa e uma frase: quem passou o ano com duvida de placa vai para firmware; quem passou com duvida de sistema vai para servidor. Quem não tem duvida nenhuma escolhe o que tem vaga de emprego, e essa e uma resposta legitima.

Continuar estudando e o que o plano formaliza, e o proximo curso da sequência, para quem for por la, comeca com firmware, depois servidor, depois os dois juntos. A ordem não e importante; a ordem que evita desperdicio e: aprenda o lado onde você ainda depende de alguem explicando.

Atividade

Montagem: botao com pull-up no pino 0, ground no GND. O botao registra cada uma das três provas resolvidas, e a placa conta até três.

  1. Grave o sketch da resolucao e rode no serial. Para cada uma das oito habilidades, escreva o que ela resolve em uma frase sua, sem copiar da lousa.
  2. Escolha a habilidade que você acha que não consolidou, e escreva qual exercicio do curso provaria o contrario. Se não souber dizer, essa e a lacuna do ano.
  3. Escreva seu plano de 90 dias em seis linhas. Cada linha com prazo e horas. Some as horas e escreva o total. O total cabe em 90 dias de verdade?
  4. Resolva as três provas, sem abrir aula, e anote quantas você resolveu. Para cada prova que você não resolveu, escreva o que faltou: conceito, ferramenta ou tempo.
  5. Monte o portfolio: escreva o nome do repositorio do seu projeto, o titulo do post que você vai escrever, e qual repositorio de colega você vai comentar.
  6. Escolha o proximo caminho entre firmware e servidor, e escreva por que você escolheu esse, citando a duvida que te trouxe aqui. Se você não tem duvida nenhuma, escreva o que você tem e por que escolheu assim mesmo.

Nota: 13 pontos. Critério de fim: o plano de 90 dias com as horas somadas, e a escolha do proximo caminho com a justificativa escrita.

Resolucao

// Aula 2 do dia 13: o que levar para depois do curso.
//
// A ultima aula de conteudo do ano nao ensina codigo. Ela entrega um
// inventario do que o aluno sabe fazer, em Skills, e um plano de 90 dias
// para o proximo passo. E o unico sketch do ano em que o professor nao
// projeta codigo nenhum e pede a turma para falar.
//
// A placa faz tres coisas enquanto a turma fala:
//
//   1. INVENTARIO: cada habilidade do ano vira uma linha com o que ela
//      resolve. "Sabe usar WiFi" nao e habilidade: "sabe descobrir por que
//      a placa saiu da rede" e.
//   2. PLANO: os proximos passos com o tempo de cada um, porque plano sem
//      prazo e intencao.
//   3. PROVA: a placa responde tres exercicios que o aluno faz sozinho, sem
//      professor. E a definicao de "levou": consegue fazer isso hoje, no
//      seu projeto, sem abrir aula.
//
// A prova e a parte que importa. Cada exercicio corresponde a um bloco do
// inventario, e o professor anota na lousa quantos o aluno fez. Nao e nota:
// e o mapa do que o ano precisa de reforco.

#include <Arduino.h>

// Versao do firmware.
#define VERSAO_FIRMWARE "2.0.0"

// Quantos exercicios a prova tem. Tres: um de diagnostico, um de regra e um
// de entrega. Menos que tres nao cobre o ano; mais que tres nao cabe em
// uma aula de 50 minutos.
const int TOTAL_DE_PROVAS = 3;

// ------------------------------------------------------------------ pinos

const int PIN_LED_STATUS = 2;
const int PIN_BOTAO_PROVA = 0;

// ------------------------------------------------------------------ tipos

struct Habilidade {
  const char* nome;
  const char* resolve;
  const char* ondeViou;
  bool consolidada;
};

struct Exercicio {
  const char* enunciado;
  const char* respostaEsperada;
  int pontos;
};

struct PassoDoPlano {
  const char* quando;
  const char* oQue;
  int horasEstimadas;
};

// ---------------------------------------------------------- inventario

Habilidade habilidades[8];
int totalDeHabilidades = 0;

void montarInventario() {
  habilidades[0].nome = "diagnosticar";
  habilidades[0].resolve = "achar a causa antes de trocar o codigo";
  habilidades[0].ondeViou = "erro que o monitor serial accuses";
  habilidades[0].consolidada = true;

  habilidades[1].nome = "ler codigo estranho";
  habilidades[1].resolve = "entender programa que voce nao escreveu";
  habilidades[1].ondeViou = "revisao de codigo do colega";
  habilidades[1].consolidada = true;

  habilidades[2].nome = "decompor problema";
  habilidades[2].resolve = "quebrar requisito grande em pedaco verificavel";
  habilidades[2].ondeViou = "maquina de estados do dia 1";
  habilidades[2].consolidada = true;

  habilidades[3].nome = "escrever regra como codigo";
  habilidades[3].resolve = "decisao do sistema que se explica sozinha";
  habilidades[3].ondeViou = "erro de negocio do dia 1";
  habilidades[3].consolidada = true;

  habilidades[4].nome = "achar documentacao";
  habilidades[4].resolve = "encontrar a resposta na fonte, sem adivinhacao";
  habilidades[4].ondeViou = "compilador e documentacao do modulo";
  habilidades[4].consolidada = true;

  habilidades[5].nome = "escrever teste que falha";
  habilidades[5].resolve = "provar o defeito antes de corrigir";
  habilidades[5].ondeViou = "caso negativo do projeto final";
  habilidades[5].consolidada = true;

  habilidades[6].nome = "medir antes de otimizar";
  habilidades[6].resolve = "saber se o gargalo e real";
  habilidades[6].ondeViou = "EXPLAIN e linhas examinadas";
  habilidades[6].consolidada = true;

  habilidades[7].nome = "explicar para o outro";
  habilidades[7].resolve = "ensinar sem depender de vocabulario da aula";
  habilidades[7].consolidada = false;
  habilidades[7].ondeViou = "dia 15, apresentacao de projeto";

  totalDeHabilidades = 8;
}

// ------------------------------------------------------------------ plano

PassoDoPlano passos[6];
int totalDePassos = 0;

void montarPlano() {
  passos[0].quando = "semana 1";
  passos[0].oQue = "publicar o projeto final no repositorio, com README";
  passos[0].horasEstimadas = 3;

  passos[1].quando = "semana 2";
  passos[1].oQue = "conectar a placa em um servidor de verdade, e nao na bancada";
  passos[1].horasEstimadas = 5;

  passos[2].quando = "semana 3";
  passos[2].oQue = "colocar um segundo dispositivo na mesma conta e ver o limite";
  passos[2].horasEstimadas = 5;

  passos[3].quando = "mes 2";
  passos[3].oQue = "ler o codigo de um projeto aberto e escrever um comentario util";
  passos[3].horasEstimadas = 10;

  passos[4].quando = "mes 3";
  passos[4].oQue = "escrever um post sobre o que quebrou, e o porque";
  passos[4].horasEstimadas = 8;

  passos[5].quando = "continuo";
  passos[5].oQue = "escolher um caminho: mais firmware ou mais servidor";
  passos[5].horasEstimadas = 0;

  totalDePassos = 6;
}

// ------------------------------------------------------------------ prova

Exercicio provas[3];
int totalDeProvasFeitas = 0;

void imprimirProva() {
  Serial.println("========================================");
  Serial.println(" a prova: tres exercicios, sem professor");
  Serial.println("========================================");
  Serial.println("cada exercicio corresponde a um bloco do inventario.");
  Serial.println("resolva no seu projeto, com a placa na mão, sem abrir aula.");
  Serial.println("o que voce nao conseguir, escreva. e o mapa do reforco.");
  Serial.println();

  provas[0].enunciado = "a placa saiu da rede e nao voltou. o que voce olha primeiro?";
  provas[0].respostaEsperada = "o log do serial: ele tem o banner de boot?";
  provas[0].pontos = 1;

  provas[1].enunciado = "o alerta disparou duas vezes para o mesmo evento. onde esta o defeito?";
  provas[1].respostaEsperada = "falta chave de idempotencia no trabalho enfileirado";
  provas[1].pontos = 1;

  provas[2].enunciado = "um colega nao consegue rodar seu projeto. o que falta?";
  provas[2].respostaEsperada = "o passo de instalacao que so existe na sua cabeca";
  provas[2].pontos = 1;

  for (int i = 0; i < TOTAL_DE_PROVAS; i++) {
    Serial.println("prova " + String(i + 1) + ": " + String(provas[i].enunciado));
    Serial.println("  resposta esperada: " + String(provas[i].respostaEsperada));
    Serial.println();
  }

  Serial.println("as tres respostas sao curtas de proposito. quem nao sabe");
  Serial.println("responder em uma frase nao consolidou aquele bloco, e o");
  Serial.println("instructor ve isso no numero, nao na opiniao.");
  Serial.println();
}

// ------------------------------------------------------------------ saida

void imprimirInventario() {
  Serial.println("========================================");
  Serial.println(" o que fica: oito habilidades");
  Serial.println("========================================");
  int consolidadas = 0;
  for (int i = 0; i < totalDeHabilidades; i++) {
    Serial.println();
    Serial.println(String(i + 1) + ". " + String(habilidades[i].nome));
    Serial.println("   resolve: " + String(habilidades[i].resolve));
    Serial.println("   onde vi: " + String(habilidades[i].ondeViou));
    if (habilidades[i].consolidada) consolidadas++;
  }
  Serial.println();
  Serial.println("consolidadas: " + String(consolidadas) + " de " + String(totalDeHabilidades));
  Serial.println();
  Serial.println("a oitava esta sem consolidada de proposito: explicar para o");
  Serial.println("outro e a unica que so se prova na apresentacao do dia 15.");
  Serial.println("quem nao marcou ela nao vai ter chance de provar depois.");
  Serial.println();
}

void imprimirPlano() {
  Serial.println("========================================");
  Serial.println(" os proximos 90 dias");
  Serial.println("========================================");
  int horas = 0;
  for (int i = 0; i < totalDePassos; i++) {
    Serial.println("  " + String(passos[i].quando) + " | " +
                   String(passos[i].horasEstimadas) + " h | " + String(passos[i].oQue));
    horas += passos[i].horasEstimadas;
  }
  Serial.println();
  Serial.println("horas estimadas no bloco de 90 dias: " + String(horas));
  Serial.println();
  Serial.println("sao 31 horas em 90 dias. e pouco, e e por isso que funciona:");
  Serial.println("plano de 200 horas em 90 dias e plano de nenhum.");
  Serial.println("o professor pede o numero porque e ele que impede o");
  Serial.println("plano de virar 'quando eu tiver tempo'.");
  Serial.println();
}

void imprimirPortfolio() {
  Serial.println("========================================");
  Serial.println(" o portfolio, em tres coisas");
  Serial.println("========================================");
  Serial.println("  1. o repositorio do projeto final, com o README que roda");
  Serial.println("  2. o post sobre o que quebrou, com o porque de cada quebra");
  Serial.println("  3. o codigo de um colega, com tres comentarios seus");
  Serial.println();
  Serial.println("o item 2 e o que diferencia o portfolio do repositorio.");
  Serial.println("repositorio mostra o que deu certo. o post mostra o que voce");
  Serial.println("aprendeu no que deu errado, e e isso que o entrevistador");
  Serial.println("pergunta primeiro.");
  Serial.println();
  Serial.println("repositorio privado nao e portfolio: e um rascunho.");
  Serial.println();
}

void imprimirProximoPasso() {
  Serial.println("========================================");
  Serial.println(" o proximo curso");
  Serial.println("========================================");
  Serial.println("quem terminou este ano com as sete camadas firmes tem dois");
  Serial.println("caminhos, e a aula 2 do dia 14 e exatamente isso:");
  Serial.println();
  Serial.println("  mais firmware: OTA, FreeRTOS, interrupcao, energia");
  Serial.println("  mais servidor: WebSocket, fila real, broker de mensagens");
  Serial.println();
  Serial.println("escolher e a parte dificil, e escolher tarde custa mais.");
  Serial.println("o plano de 90 dias ja tem a decisao no ultimo passo.");
  Serial.println();
}

// ---------------------------------------------------------------- setup

void setup() {
  Serial.begin(115200);
  delay(300);

  pinMode(PIN_LED_STATUS, OUTPUT);
  pinMode(PIN_BOTAO_PROVA, INPUT_PULLUP);
  digitalWrite(PIN_LED_STATUS, HIGH);

  Serial.println();
  Serial.println("========================================");
  Serial.println(" o que levar para depois — v" + String(VERSAO_FIRMWARE));
  Serial.println("========================================");
  Serial.println("esta aula nao tem codigo novo: e inventario e plano.");
  Serial.println("cpu: " + String(getCpuFrequencyMhz()) + " MHz | heap: " +
                 String(ESP.getFreeHeap()) + " bytes");
  Serial.println();

  montarInventario();
  montarPlano();

  imprimirInventario();
  imprimirPlano();
  imprimirProva();
  imprimirPortfolio();
  imprimirProximoPasso();

  Serial.println("========================================");
  Serial.println(" fim do conteudo do ano");
  Serial.println("========================================");
  Serial.println("o botao marca cada prova que voce fez. aperte tres vezes");
  Serial.println("se voce resolveu as tres, e a placa conta.");
}

void loop() {
  digitalWrite(PIN_LED_STATUS, !digitalRead(PIN_LED_STATUS));

  static unsigned long ultimoToque = 0;
  if (digitalRead(PIN_BOTAO_PROVA) == LOW && millis() - ultimoToque > 1500) {
    ultimoToque = millis();
    if (totalDeProvasFeitas < TOTAL_DE_PROVAS) {
      totalDeProvasFeitas++;
    }
    Serial.println("[prova] " + String(totalDeProvasFeitas) + " de " +
                   String(TOTAL_DE_PROVAS) + " registrada | heap " +
                   String(ESP.getFreeHeap()) + " B");
  }

  Serial.println("[encerramento] viva | v" + String(VERSAO_FIRMWARE) + " | provas " +
                 String(totalDeProvasFeitas) + "/" + String(TOTAL_DE_PROVAS) +
                 " | heap " + String(ESP.getFreeHeap()) + " B");
  delay(7000);
}

Por que assim e não de outro jeito. Esta e a unica aula do ano em que o professor não projeta código, e o sketch também não tem código novo: ele tem inventario, plano e prova. A placa serve de cronometro e de contador.

A struct Habilidade tem quatro campos, e o segundo — o que resolve — e o que separa esta lista de uma lista de topicos. E o que o professor exige ao aluno: se ele não consegue escrever o que resolve, ele decorou o nome, e não aprendeu a ferramenta.

A oitava habilidade entra como não consolidada, e isso e um metodo, não um esquecimento. A aula de hoje não tem apresentacao, e a unica prova de que você sabe explicar para o outro e a apresentacao do dia seguinte. O inventario marca isso antes de acontecer, e o botao marca depois.

imprimirProva imprime três exercicios com a resposta esperada logo abaixo. Isso parece estragar o exercicio, e não estraga: o objetivo não e responder, e reconhecer a resposta em uma frase. Quem le "a placa saiu da rede" e pensa "o banner de boot" consolidou o bloco; quem le e pensa "ah, alguma coisa" não consolidou.

O plano soma as horas e imprime o total. Um total de 200 horas em 90 dias não e ambição, e um plano de não fazer nada — e o número não se discute na opiniao, se mede.

O botao conta as provas feitas, e a contagem e o unico "resultado" da aula. O que importa no fim do ano não e a nota da prova; e a distancia entre o que a placa contou e o que o aluno acha que sabe.

O encerramento do curso e o que continua depois

O encerramento do curso e amanha; esta aula e a ultima de conteudo, e o que fica e o plano. A distincao importa para o aluno: o curso fecha, o plano não.

O que continua depois tem nome e tem data: publicar o projeto pessoal, continuar estudando no caminho escolhido, e o proximo curso da sequência. Quem sair da aula sem essas três datas vai tratar o proximo trimestre como "quando der", e não como compromisso.

Criterios de correcao

CritérioPontos
Oito habilidades com o "resolve" escrito em frase propria, sem copiar3 pontos
Habilidade não consolidada escolhida, com o exercicio que a provaria2 pontos
Plano de 90 dias com seis linhas, prazo e horas, e o total somado3 pontos
Três provas resolvidas, com quantas respostas o aluno deu2 pontos
Portfolio com as três pecas nomeadas, incluindo o post1 ponto
Proximo caminho escolhido, com a duvida real citada1 ponto
Plano reescrito depois da aula, com a data do encerramento do curso e o que continua depois1 ponto

Erros comuns

ErroComo apareceCorrecao
Habilidade escrita como topico"sabe usar MySQL" na lista"Topico não e habilidade. Escreva o que ela resolve: 'saber por que uma consulta devolveu zero linha'. Isso e habilidade."
"Aprendi bastante" no lugar do inventarioo aluno não escreve nenhuma linha"O inventario tem oito linhas preenchidas, e não uma frase de avaliacao. Sem a lista, não ha o que levar."
Plano de 200 horas em 90 diasa lista esta cheia de tarefas grandes"Some as horas. Se o total não cabe em 90 dias de verdade, o plano e revisao e não plano. Corte até caber."
Plano sem prazo"semana que vem eu comeco""Semana que vem e quando. Escreva a data. Prazo curto e o que muda 'um dia' em 'quinta'."
Prova respondida com a resposta completa decoradao aluno le a resposta esperada e diz que era isso"Se você leu a resposta esperada e reconheceu, consolidou. Se decorou a frase para falar, não consolidou. A prova e curta de propósito."
O que ficou e o que não ficou, ambos sem respostao aluno lista so o que domina e ignora as lacunas"A oitava so se prova amanha. Deixar ela em aberto e honestidade, não fraqueza — e a placa conta as provas, não as palavras."
Portfolio com um repositorio e nada maiso link do GitHub e o portfolio inteiro"Falta o post. Repositorio mostra o que deu certo; o post mostra o que você aprendeu no que deu errado, e e o que o entrevistador pergunta primeiro."
Repositorio privado tratado como portfolio, e publicar o projeto adiado"ainda não publiquei""Publicar o projeto e o que transforma trabalho em registro. Repositorio privado e rascunho, e rascunho não conta."
Escolher o proximo curso por vaga de emprego, sem escrever por que"firmeware porque tem mais vaga""Resposta legitima — mas escreva. E escreva a duvida que te trouxe aqui. Escolher sem motivo e escolher por acaso."
Não ter duvida nenhuma e escolher por falta de informação"não sei, tanto faz""'Tanto faz' e o unico caminho que não serve. Escolha o lado onde você ainda depende de alguem explicando, e escreva isso."
O aluno escreve "achar documentation" e não diz onde achou"eu googlei""'Googlei' não e resposta. Diga o nome da documentacao que você abriu e o parametro que procurava. Achar documentacao e um trabalho, e precisa de nome."
Tratar deep sleep como estado do loop no plano"vou adicionar deep sleep no meu projeto""No plano, escreva o efeito: 'a placa passa a durar 30 dias'. deep sleep e o como; o plano e o para que."

Desafio extra

Escreva o seu portfólio de verdade nesta semana: publique o repositorio com o README completo, escreva o post de 400 palavras contando o defeito mais difícil que você encontrou e o porque da solução escolhida, e comente três coisas em um repositorio aberto de outra pessoa. Meça quanto tempo cada um dos três passos levou. Anote o tempo total e escreva uma frase sobre o que impede alguem de fazer isso: capacidade, tempo, ou vergonha de mostrar o código.

>

A resolucao, compilada

// Aula 2 do dia 13: o que levar para depois do curso.
//
// A ultima aula de conteudo do ano nao ensina codigo. Ela entrega um
// inventario do que o aluno sabe fazer, em Skills, e um plano de 90 dias
// para o proximo passo. E o unico sketch do ano em que o professor nao
// projeta codigo nenhum e pede a turma para falar.
//
// A placa faz tres coisas enquanto a turma fala:
//
//   1. INVENTARIO: cada habilidade do ano vira uma linha com o que ela
//      resolve. "Sabe usar WiFi" nao e habilidade: "sabe descobrir por que
//      a placa saiu da rede" e.
//   2. PLANO: os proximos passos com o tempo de cada um, porque plano sem
//      prazo e intencao.
//   3. PROVA: a placa responde tres exercicios que o aluno faz sozinho, sem
//      professor. E a definicao de "levou": consegue fazer isso hoje, no
//      seu projeto, sem abrir aula.
//
// A prova e a parte que importa. Cada exercicio corresponde a um bloco do
// inventario, e o professor anota na lousa quantos o aluno fez. Nao e nota:
// e o mapa do que o ano precisa de reforco.

#include <Arduino.h>

// Versao do firmware.
#define VERSAO_FIRMWARE "2.0.0"

// Quantos exercicios a prova tem. Tres: um de diagnostico, um de regra e um
// de entrega. Menos que tres nao cobre o ano; mais que tres nao cabe em
// uma aula de 50 minutos.
const int TOTAL_DE_PROVAS = 3;

// ------------------------------------------------------------------ pinos

const int PIN_LED_STATUS = 2;
const int PIN_BOTAO_PROVA = 0;

// ------------------------------------------------------------------ tipos

struct Habilidade {
  const char* nome;
  const char* resolve;
  const char* ondeViou;
  bool consolidada;
};

struct Exercicio {
  const char* enunciado;
  const char* respostaEsperada;
  int pontos;
};

struct PassoDoPlano {
  const char* quando;
  const char* oQue;
  int horasEstimadas;
};

// ---------------------------------------------------------- inventario

Habilidade habilidades[8];
int totalDeHabilidades = 0;

void montarInventario() {
  habilidades[0].nome = "diagnosticar";
  habilidades[0].resolve = "achar a causa antes de trocar o codigo";
  habilidades[0].ondeViou = "erro que o monitor serial accuses";
  habilidades[0].consolidada = true;

  habilidades[1].nome = "ler codigo estranho";
  habilidades[1].resolve = "entender programa que voce nao escreveu";
  habilidades[1].ondeViou = "revisao de codigo do colega";
  habilidades[1].consolidada = true;

  habilidades[2].nome = "decompor problema";
  habilidades[2].resolve = "quebrar requisito grande em pedaco verificavel";
  habilidades[2].ondeViou = "maquina de estados do dia 1";
  habilidades[2].consolidada = true;

  habilidades[3].nome = "escrever regra como codigo";
  habilidades[3].resolve = "decisao do sistema que se explica sozinha";
  habilidades[3].ondeViou = "erro de negocio do dia 1";
  habilidades[3].consolidada = true;

  habilidades[4].nome = "achar documentacao";
  habilidades[4].resolve = "encontrar a resposta na fonte, sem adivinhacao";
  habilidades[4].ondeViou = "compilador e documentacao do modulo";
  habilidades[4].consolidada = true;

  habilidades[5].nome = "escrever teste que falha";
  habilidades[5].resolve = "provar o defeito antes de corrigir";
  habilidades[5].ondeViou = "caso negativo do projeto final";
  habilidades[5].consolidada = true;

  habilidades[6].nome = "medir antes de otimizar";
  habilidades[6].resolve = "saber se o gargalo e real";
  habilidades[6].ondeViou = "EXPLAIN e linhas examinadas";
  habilidades[6].consolidada = true;

  habilidades[7].nome = "explicar para o outro";
  habilidades[7].resolve = "ensinar sem depender de vocabulario da aula";
  habilidades[7].consolidada = false;
  habilidades[7].ondeViou = "dia 15, apresentacao de projeto";

  totalDeHabilidades = 8;
}

// ------------------------------------------------------------------ plano

PassoDoPlano passos[6];
int totalDePassos = 0;

void montarPlano() {
  passos[0].quando = "semana 1";
  passos[0].oQue = "publicar o projeto final no repositorio, com README";
  passos[0].horasEstimadas = 3;

  passos[1].quando = "semana 2";
  passos[1].oQue = "conectar a placa em um servidor de verdade, e nao na bancada";
  passos[1].horasEstimadas = 5;

  passos[2].quando = "semana 3";
  passos[2].oQue = "colocar um segundo dispositivo na mesma conta e ver o limite";
  passos[2].horasEstimadas = 5;

  passos[3].quando = "mes 2";
  passos[3].oQue = "ler o codigo de um projeto aberto e escrever um comentario util";
  passos[3].horasEstimadas = 10;

  passos[4].quando = "mes 3";
  passos[4].oQue = "escrever um post sobre o que quebrou, e o porque";
  passos[4].horasEstimadas = 8;

  passos[5].quando = "continuo";
  passos[5].oQue = "escolher um caminho: mais firmware ou mais servidor";
  passos[5].horasEstimadas = 0;

  totalDePassos = 6;
}

// ------------------------------------------------------------------ prova

Exercicio provas[3];
int totalDeProvasFeitas = 0;

void imprimirProva() {
  Serial.println("========================================");
  Serial.println(" a prova: tres exercicios, sem professor");
  Serial.println("========================================");
  Serial.println("cada exercicio corresponde a um bloco do inventario.");
  Serial.println("resolva no seu projeto, com a placa na mão, sem abrir aula.");
  Serial.println("o que voce nao conseguir, escreva. e o mapa do reforco.");
  Serial.println();

  provas[0].enunciado = "a placa saiu da rede e nao voltou. o que voce olha primeiro?";
  provas[0].respostaEsperada = "o log do serial: ele tem o banner de boot?";
  provas[0].pontos = 1;

  provas[1].enunciado = "o alerta disparou duas vezes para o mesmo evento. onde esta o defeito?";
  provas[1].respostaEsperada = "falta chave de idempotencia no trabalho enfileirado";
  provas[1].pontos = 1;

  provas[2].enunciado = "um colega nao consegue rodar seu projeto. o que falta?";
  provas[2].respostaEsperada = "o passo de instalacao que so existe na sua cabeca";
  provas[2].pontos = 1;

  for (int i = 0; i < TOTAL_DE_PROVAS; i++) {
    Serial.println("prova " + String(i + 1) + ": " + String(provas[i].enunciado));
    Serial.println("  resposta esperada: " + String(provas[i].respostaEsperada));
    Serial.println();
  }

  Serial.println("as tres respostas sao curtas de proposito. quem nao sabe");
  Serial.println("responder em uma frase nao consolidou aquele bloco, e o");
  Serial.println("instructor ve isso no numero, nao na opiniao.");
  Serial.println();
}

// ------------------------------------------------------------------ saida

void imprimirInventario() {
  Serial.println("========================================");
  Serial.println(" o que fica: oito habilidades");
  Serial.println("========================================");
  int consolidadas = 0;
  for (int i = 0; i < totalDeHabilidades; i++) {
    Serial.println();
    Serial.println(String(i + 1) + ". " + String(habilidades[i].nome));
    Serial.println("   resolve: " + String(habilidades[i].resolve));
    Serial.println("   onde vi: " + String(habilidades[i].ondeViou));
    if (habilidades[i].consolidada) consolidadas++;
  }
  Serial.println();
  Serial.println("consolidadas: " + String(consolidadas) + " de " + String(totalDeHabilidades));
  Serial.println();
  Serial.println("a oitava esta sem consolidada de proposito: explicar para o");
  Serial.println("outro e a unica que so se prova na apresentacao do dia 15.");
  Serial.println("quem nao marcou ela nao vai ter chance de provar depois.");
  Serial.println();
}

void imprimirPlano() {
  Serial.println("========================================");
  Serial.println(" os proximos 90 dias");
  Serial.println("========================================");
  int horas = 0;
  for (int i = 0; i < totalDePassos; i++) {
    Serial.println("  " + String(passos[i].quando) + " | " +
                   String(passos[i].horasEstimadas) + " h | " + String(passos[i].oQue));
    horas += passos[i].horasEstimadas;
  }
  Serial.println();
  Serial.println("horas estimadas no bloco de 90 dias: " + String(horas));
  Serial.println();
  Serial.println("sao 31 horas em 90 dias. e pouco, e e por isso que funciona:");
  Serial.println("plano de 200 horas em 90 dias e plano de nenhum.");
  Serial.println("o professor pede o numero porque e ele que impede o");
  Serial.println("plano de virar 'quando eu tiver tempo'.");
  Serial.println();
}

void imprimirPortfolio() {
  Serial.println("========================================");
  Serial.println(" o portfolio, em tres coisas");
  Serial.println("========================================");
  Serial.println("  1. o repositorio do projeto final, com o README que roda");
  Serial.println("  2. o post sobre o que quebrou, com o porque de cada quebra");
  Serial.println("  3. o codigo de um colega, com tres comentarios seus");
  Serial.println();
  Serial.println("o item 2 e o que diferencia o portfolio do repositorio.");
  Serial.println("repositorio mostra o que deu certo. o post mostra o que voce");
  Serial.println("aprendeu no que deu errado, e e isso que o entrevistador");
  Serial.println("pergunta primeiro.");
  Serial.println();
  Serial.println("repositorio privado nao e portfolio: e um rascunho.");
  Serial.println();
}

void imprimirProximoPasso() {
  Serial.println("========================================");
  Serial.println(" o proximo curso");
  Serial.println("========================================");
  Serial.println("quem terminou este ano com as sete camadas firmes tem dois");
  Serial.println("caminhos, e a aula 2 do dia 14 e exatamente isso:");
  Serial.println();
  Serial.println("  mais firmware: OTA, FreeRTOS, interrupcao, energia");
  Serial.println("  mais servidor: WebSocket, fila real, broker de mensagens");
  Serial.println();
  Serial.println("escolher e a parte dificil, e escolher tarde custa mais.");
  Serial.println("o plano de 90 dias ja tem a decisao no ultimo passo.");
  Serial.println();
}

// ---------------------------------------------------------------- setup

void setup() {
  Serial.begin(115200);
  delay(300);

  pinMode(PIN_LED_STATUS, OUTPUT);
  pinMode(PIN_BOTAO_PROVA, INPUT_PULLUP);
  digitalWrite(PIN_LED_STATUS, HIGH);

  Serial.println();
  Serial.println("========================================");
  Serial.println(" o que levar para depois — v" + String(VERSAO_FIRMWARE));
  Serial.println("========================================");
  Serial.println("esta aula nao tem codigo novo: e inventario e plano.");
  Serial.println("cpu: " + String(getCpuFrequencyMhz()) + " MHz | heap: " +
                 String(ESP.getFreeHeap()) + " bytes");
  Serial.println();

  montarInventario();
  montarPlano();

  imprimirInventario();
  imprimirPlano();
  imprimirProva();
  imprimirPortfolio();
  imprimirProximoPasso();

  Serial.println("========================================");
  Serial.println(" fim do conteudo do ano");
  Serial.println("========================================");
  Serial.println("o botao marca cada prova que voce fez. aperte tres vezes");
  Serial.println("se voce resolveu as tres, e a placa conta.");
}

void loop() {
  digitalWrite(PIN_LED_STATUS, !digitalRead(PIN_LED_STATUS));

  static unsigned long ultimoToque = 0;
  if (digitalRead(PIN_BOTAO_PROVA) == LOW && millis() - ultimoToque > 1500) {
    ultimoToque = millis();
    if (totalDeProvasFeitas < TOTAL_DE_PROVAS) {
      totalDeProvasFeitas++;
    }
    Serial.println("[prova] " + String(totalDeProvasFeitas) + " de " +
                   String(TOTAL_DE_PROVAS) + " registrada | heap " +
                   String(ESP.getFreeHeap()) + " B");
  }

  Serial.println("[encerramento] viva | v" + String(VERSAO_FIRMWARE) + " | provas " +
                 String(totalDeProvasFeitas) + "/" + String(TOTAL_DE_PROVAS) +
                 " | heap " + String(ESP.getFreeHeap()) + " B");
  delay(7000);
}

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