Fechamento do trimestre — Arduino e IoT — semana 15 do 2o trimestre
Semana 15 de 16· 2o trimestre · 01/05 a 04/09
Fechamento do trimestre
Revisao, duvida por duvida, e o que muda no trimestre seguinte.
Aula 1 — Revisao do 2o trimestre e reparo do que ficou torto
Objetivos
- Rodar o diagnóstico do caminho inteiro e apontar no monitor em qual das cinco etapas o seu projeto perde mais leitura.
- Conferir, no seu próprio projeto, se a contagem de tentativas, gravações e perdas fecha — e dizer o que fazer quando não fecha.
- Preencher a lista de checagem dos oito blocos do trimestre marcando OK, mais ou menos ou não fiz, e entregar ela no fim da aula.
- Apontar um arquivo do seu projeto que você vai apagar até o fim do trimestre e outro que você vai manter.
Material
- 1 ESP32 DevKit V1 por aluno, com o cabo USB
- Computador com o projeto do trimestre aberto e o servidor parado
- Folha de lista de checagem impressa, uma por aluno
- 1 caneta por aluno
- Projetor com o monitor serial da placa do professor
Conceitos
O caminho tem cinco etapas, e cada uma quebra de um jeito
O trimestre inteiro construiu uma cadeia. Ela não quebrou inteira: ela quebrou em algum lugar, e o lugar muda de aluno para aluno.
- Sensor: o
DHT11devolve zero quando falha, e zero é um número plausível. Sem limite de plausibilidade, o lixo entra. - Montagem do
JSON: a string sai com aspas erradas e o servidor devolve400. - Rede: a placa não acha o ponto de acesso, ou acha e não recebe resposta. É o
timeout, não o código. - Servidor: recebe e recusa com
422porque o dado não passa na validação. O dado chegou inteiro e mesmo assim foi perdido. - Banco: a consulta foi preparada, mas a tabela não existe ou a coluna mudou. É o
500.
A ordem importa na correção. Enquanto a etapa 1 não estiver boa, nenhuma das outras pode ser testada direito, porque o dado que chega no fim é o mesmo dado ruim.
Perdido e não tentado são coisas diferentes
Uma leitura que saiu da bancada e foi recusada pelo servidor foi tentada. Uma leitura que nunca saiu porque a placa reiniciou não foi tentada. As duas viram dado perdido, mas só uma delas consome tempo de rede.
O diagnóstico separa as duas porque o conserto é diferente: para recuperar a leitura recusada, o conserto é no servidor; para recuperar a leitura que nem saiu, o conserto é na placa. Somar tudo num número só esconde essa diferença.
As contas têm que fechar
A regra mais barata que existe em programa: o total é sempre a soma das partes. Tentativas são gravações mais perdas. Se o seu sistema grava 14 de 21 e o seu contador diz que perdeu 5, sobrou uma leitura que sumiu sem ser contada — e essa leitura é um erro de contagem, não do sistema.
Nenhum teste automatizado do curso descobre isso antes do professor olhar o número.
Dívida técnica é dívida, e conta nomeada
O que o aluno chama de "o meu código está meio bagunçado" tem nome e tem custo: uma função com quatro responsabilidades, uma senha escrita dentro do arquivo, um catch vazio. No fechamento do trimestre, o professor pede dois nomes, não uma opinião: o arquivo que você vai apagar e o arquivo que você vai manter. É o começo do trabalho do 3o trimestre.
Atividade
Montagem: nenhuma. A placa fica sem sensor nesta aula. O diagnóstico de hoje roda sozinho e é o que o professor projeta.
- Grave o sketch da resolução na sua placa e abra o monitor serial a 115200. Espere o relatório inteiro: são 21 ciclos, cerca de 40 segundos.
- Preencha o cabeçalho do seu relatório no caderno: quantas leituras o seu sistema gravou de verdade, e de quantas tentativas.
- Marque a tabela de perdas do seu lado com estes três números: leituras gravadas, leituras perdidas e a etapa que mais perdeu. A etapa que mais perdeu é a que a placa imprime em "comece por aqui".
- Rode a conferência de contas: ela fecha ou não fecha? Se não fecha, o erro é de contagem, e você vai corrigir isso antes de qualquer outra coisa.
- Preencha a lista de checagem dos oito blocos do trimestre, marcando
OK,mais ou menosounão fizem cada linha: - bloco 1, sensor e
JSONna placa - bloco 2, tratar erro de rede com
timeout - bloco 3, tabela e
INSERTnoMySQL - bloco 4, ponta a ponta, da placa até a linha no banco
- bloco 5, consulta por período e agregação
- bloco 6, validar e recusar dado ruim
- bloco 7, log com identificador
- bloco 8, camadas,
configpor ambiente e um teste automático - Escreva no verso da folha a lista do que você ainda não sabe fazer, em três linhas ou menos. Esta lista é o que abre a aula 2.
Nota: 10 pontos. Critério de fim: a lista de checagem preenchida nas oito linhas, com a etapa que mais perdeu escrita, e a lista do que ainda não sabe entregue. Critério de fim do professor: olha o envelope e sabe em quantos blocos a turma está forte e em quantos está fraca.
Resolucao
O sketch de hoje é a ferramenta de fechamento, não um exemplo de estilo. Ele roda o caminho inteiro simulado e determinístico — o sensor, a rede e o banco não estão ligados, e o comentário no topo avisa isso. Determinístico porque a tabela precisa ser igual na placa de todo mundo, senão a turma não compara a tela do colega com a sua.
Os contadores ficam fora do loop, declarados no topo. Um contador que nasce dentro do loop é a aula 2 de hoje inteira: ele zera a cada volta e o relatório não fecha.
// DIAGNOSTICO DO CAMINHO — dia 15, aula 1 (revisão do 2o trimestre). // // Este arquivo não é um exemplo de estilo. É a ferramenta que o aluno roda no // fechamento do trimestre para ver ONDE o caminho dele está quebrando: // quantas leituras foram gravadas, quantas foram perdidas e em quanto tempo. // // AVISO PARA O PROFESSOR: aqui não tem sensor ligado e não tem servidor. As // leituras e as respostas de rede são SIMULADAS, e simuladas de forma // DETERMINÍSTICA (divisão por número, nada de `random` sem semente). Isso é // proposital: a tabela precisa ser exatamente a mesma na placa de todo mundo, // senão a turma não consegue comparar a tela do colega com a sua. // // A aula 2 do dia 16 parte deste mesmo arquivo. #include <Arduino.h> // GPIO2 é o LED embutido na maioria dos DevKit. Aqui ele é o sinal de vida: // se a luz parar de piscar, o programa travou — e não é o WiFi, e não é o banco. const int PIN_LED = 2; // Ritmo do diagnóstico. 21 ciclos em três janelas de sete: número pequeno o // bastante para o professor ler a tabela inteira na lousa antes do intervalo. const int INTERVALO_CICLO = 1500; const int TOTAL_DE_CICLOS = 21; // Limite de plausibilidade introduzido no dia 8. Fora desta faixa o dado é lixo // e não entra no banco, mesmo que a placa "conseguiu" enviar. const float TEMP_MINIMA = -10.0; const float TEMP_MAXIMA = 60.0; // Tamanho do buffer do JSON que a placa monta, igual ao dia 3. const int TAMANHO_PAYLOAD = 96; // --- Contadores do caminho. Ficam no topo, fora do `loop`, de propósito: um // contador que nasce dentro do `loop` é a aula 2 do dia 15 inteira. --- int ciclo = 0; int leiturasTentadas = 0; int leiturasGravadas = 0; int falhasSemSinal = 0; int falhasServidor = 0; int falhasForaDoLimite = 0; int tempoAcumulado = 0; int tempoPiorCaso = 0; int cicloPiorCaso = 0; // Simula a leitura do DHT11. O valor 0 é um defeito real do sensor: quando a // leitura falha, o DHT11 devolve zero em vez de erro. É por isso que existe // limite de plausibilidade. float temperaturaSimulada() { if (ciclo % 11 == 0) { return 0.0; } return 21.0 + ((ciclo % 9) * 2.0); } // Devolve o motivo da falha do ciclo, ou 0 quando o ciclo passa inteiro. // A ordem importa e é o conteúdo da aula: o caminho quebra no PRIMEIRO ponto, // não em todos ao mesmo tempo. int motivoDaFalha(float temperatura) { if (temperatura < TEMP_MINIMA || temperatura > TEMP_MAXIMA) { return 3; // 422: o dado chegou, mas era lixo } if (ciclo % 7 == 0) { return 1; // a placa não achou o ponto de acesso } if (ciclo % 5 == 0) { return 2; // 500: o servidor recebeu e não conseguiu gravar } return 0; } const char* nomeDoMotivo(int motivo) { if (motivo == 1) { return "FALHA sem sinal"; } if (motivo == 2) { return "FALHA 500 servidor"; } if (motivo == 3) { return "FALHA fora do limite"; } return "gravada"; } // Tempo de ida e volta da rede. Simulado, como todo o resto: o valor é // determinístico para que o pior caso caia sempre no mesmo ciclo. int tempoDeRedeSimulado() { return 90 + ((ciclo % 6) * 40); } // O JSON que a placa monta no dia 3. Montar a string é trabalho real e medido // pelo `millis`; só a rede é simulada. void montarPayload(float temperatura, char* destino) { snprintf(destino, TAMANHO_PAYLOAD, "{\"id\":\"placa-01\",\"temperatura\":%.1f}", temperatura); } // Recomeça a contagem para o aluno ver o relatório inteiro mais de uma vez // sem precisar gravar o sketch de novo. void zerarContadores() { ciclo = 0; leiturasTentadas = 0; leiturasGravadas = 0; falhasSemSinal = 0; falhasServidor = 0; falhasForaDoLimite = 0; tempoAcumulado = 0; tempoPiorCaso = 0; cicloPiorCaso = 0; } void mostrarResumo() { int perdas = TOTAL_DE_CICLOS - leiturasGravadas; int percentual = (TOTAL_DE_CICLOS > 0) ? (100 * leiturasGravadas / TOTAL_DE_CICLOS) : 0; int media = (leiturasGravadas > 0) ? (tempoAcumulado / leiturasGravadas) : 0; Serial.println(); Serial.println("=== RESUMO DO CAMINHO ==="); Serial.print("leituras tentadas .. "); Serial.println(leiturasTentadas); Serial.print("leituras gravadas ... "); Serial.println(leiturasGravadas); Serial.print("leituras perdidas ... "); Serial.print(perdas); Serial.print(" ("); Serial.print(percentual); Serial.println(" % de aproveitamento)"); Serial.print("tempo medio ........ "); Serial.print(media); Serial.println(" ms por leitura"); Serial.print("pior caso ......... "); Serial.print(tempoPiorCaso); Serial.print(" ms, no ciclo "); Serial.println(cicloPiorCaso); } // A conferência que fecha o diagnóstico: se estas três contas não baterem, o // erro é de CONTAGEM, e não do sistema. É esta linha que acha o defeito da // aula 2. void conferirContas() { int perdas = TOTAL_DE_CICLOS - leiturasGravadas; Serial.println(); Serial.print("conferencia: "); Serial.print(leiturasTentadas); Serial.print(" = "); Serial.print(leiturasGravadas); Serial.print(" + "); Serial.print(perdas); Serial.println(" ?"); if (leiturasTentadas != leiturasGravadas + perdas) { Serial.println(" >>> AS CONTAS NAO FECHAM. O erro esta na contagem."); } else { Serial.println(" >>> contas fecham: o caminho estainteiro, e o sistema."); } } // O diagnóstico que o aluno lê para decidir por onde começar a consertar. void mostrarDiagnostico() { Serial.println(); Serial.println("=== ONDE O CAMINHO QUEBROU ==="); Serial.print("sensor e validacao . "); Serial.println(falhasForaDoLimite); Serial.print("sinal de rede ....... "); Serial.println(falhasSemSinal); Serial.print("servidor e banco .... "); Serial.println(falhasServidor); int maior = falhasSemSinal; const char* etapa = "sinal de rede"; if (falhasServidor > maior) { maior = falhasServidor; etapa = "servidor e banco"; } if (falhasForaDoLimite > maior) { maior = falhasForaDoLimite; etapa = "sensor e validacao"; } Serial.println(); if (maior == 0) { Serial.println("Nenhuma etapa perdeu leitura neste ciclo completo."); return; } Serial.print("Comece por aqui: "); Serial.print(etapa); Serial.print(", com "); Serial.print(maior); Serial.println(" perdas."); } void setup() { pinMode(PIN_LED, OUTPUT); Serial.begin(115200); Serial.println(); Serial.println("DIAGNOSTICO DO CAMINHO — dia 15, aula 1"); Serial.println("leituras e rede simuladas, deterministicas"); Serial.println("---------------------------------------------------"); Serial.println("ciclo | temperatura | resultado | tempo"); } void loop() { ciclo++; digitalWrite(PIN_LED, HIGH); // sinal de vida: o ciclo começou float temperatura = temperaturaSimulada(); int motivo = motivoDaFalha(temperatura); int rede = tempoDeRedeSimulado(); char payload[TAMANHO_PAYLOAD]; montarPayload(temperatura, payload); unsigned long inicio = millis(); Serial.print(ciclo); Serial.print(" | "); Serial.print(temperatura, 1); Serial.print(" C | "); if (motivo == 0) { leiturasTentadas++; leiturasGravadas++; Serial.print(nomeDoMotivo(motivo)); // O payload só é mostrado no primeiro ciclo inteiro: serve de prova de que // a string que a placa manda é o JSON do dia 3, e polui se repetir 21 vezes. if (leiturasGravadas == 1) { Serial.print(" | "); Serial.print(payload); } } else { // A leitura também conta como tentativa: ela saiu da bancada, tentou // entrar e foi recusada. Perder é diferente de nem ter tentado. leiturasTentadas++; if (motivo == 1) { falhasSemSinal++; } else if (motivo == 2) { falhasServidor++; } else { falhasForaDoLimite++; } Serial.print(nomeDoMotivo(motivo)); } int gasto = (int)(millis() - inicio); tempoAcumulado += gasto; if (gasto > tempoPiorCaso) { tempoPiorCaso = gasto; cicloPiorCaso = ciclo; } Serial.print(" | "); Serial.print(gasto); Serial.print(" ms + "); Serial.print(rede); Serial.println(" ms de rede"); if (ciclo >= TOTAL_DE_CICLOS) { mostrarResumo(); conferirContas(); mostrarDiagnostico(); Serial.println(); Serial.println("O relatorio comeca de novo em alguns segundos."); zerarContadores(); delay(5000); } delay(INTERVALO_CICLO); digitalWrite(PIN_LED, LOW); }
Por que assim e não de outro jeito. O motivoDaFalha devolve um motivo, e não uma lista. Se a placa determinasse todas as falhas de uma vez, o relatório mostraria 8 falhas na rede e 6 no banco para o mesmo ciclo, e o aluno veria um número maior que o total de ciclos sem entender por quê. Devolver só o primeiro ponto quebrado é o comportamento do sistema real: o dado parou ali e não chegou às etapas seguintes.
O payload aparece só no primeiro ciclo que passou inteiro. Ele existe para provar que a string que a placa manda é o JSON do dia 3 — mostrar 14 vezes seguidas vira ruído, e o professor perde a turma na segunda.
A conferência de contas é uma função separada de propósito. Ela não serve para o relatório bonito, serve para falhar. É a única linha do sketch que existe para dizer que o número está errado, e por isso é a que vai aparecer quebrada na aula 2.
Por que a leitura que falha conta como tentativa. Se leiturasTentadas só aumentasse no caminho feliz, o total de tentativas seria menor que o total de ciclos, e a divisão do percentual ficaria sem sentido. Mais importante: é essa escolha que separa "recusado pelo servidor" de "nunca saiu da placa", e o aluno que não faz essa distinção gasta o resto do trimestre procurando erro de rede onde o problema é de validação.
Criterios de correcao
| Critério | Pontos |
|---|---|
| Diagnóstico gravado na placa e relatório completo lido até o fim (21 ciclos) | 2 pontos |
| Tabela de perdas preenchida com gravadas, perdidas e a etapa que mais perdeu | 3 pontos |
| Conferência de contas rodada e o resultado (fecha ou não fecha) anotado | 1 ponto |
| Lista de checagem com as oito linhas preenchidas e marcadas | 2 pontos |
| Lista do que ainda não sabe escrita no verso da folha | 2 pontos |
Erros comuns
| Erro | Como aparece | Correcao |
|---|---|---|
| Subir só 20 segundos e ler o relatório pela metade | "Chegou em 'ciclo 14' e parou de aparecer" | "São 21 ciclos, uns 40 segundos. Deixa rodar. Se travar no meio, olha se o LED ainda está piscando — e uma coisa de cada vez." |
| Ler só a linha de cima e dizer que está tudo bem | Relatório com 14 gravadas e 7 perdidas, e o aluno relata "funciona" | "Funciona e não é a mesma coisa. Perdeu sete leituras: a próxima vez que o gráfico vier com buraco, é aqui que você olha." |
| Contar tentativa só no caminho feliz | "Minhas tentativas deram 14 e minhas gravações deram 14" | "A leitura que o servidor recusou também saiu da bancada. Ela conta como tentativa. Por isso o total tem 21." |
Pôr o contador dentro do loop ao refazer em casa | O relatório zera a cada volta e nunca fecha | "Contador nasce no topo, antes do setup. Se ele nasce dentro do loop, ele vale só para a última volta, e a sua conta nunca vai fechar." |
Marcar a lista de checagem inteira como OK | Oito linhas com OK e zero linha marcada como pendência | "Ponto que você fez uma vez e nunca mais não é OK, é mais ou menos. Se eu te pedir para rodar agora, você roda?" |
| Deixar a lista do que não sabe em branco | Folha entregue só com a frente preenchida | "Essa lista é o que abre a aula de amanhã. Sem ela eu não sei o que a turma precisa, e vou perder o tempo da aula com o assunto que eu achei bonito." |
| Querer consertar tudo antes de medir | Aluno abre o arquivo e começa a editar sem saber a etapa | "Meu conselho: primeiro o número. Se você não sabe onde perde, você não sabe o que consertar — e vai mexer em arquivo certo com conserto errado." |
| Comparar o resultado do colega com o seu e concluir que a placa dele está ruim | "O dele deu menos leitura que o meu, a placa dele deve estar com defeito" | "A simulação é a mesma na placa de todo mundo, e o número sai igual. Se o seu deu diferente do meu, o que é diferente é o sistema, não a placa." |
Desafio extra
A placa imprime "comece por aqui" com uma etapa. Troque o código para imprimir as duas piores etapas, em ordem, e justifique em duas linhas por que a ordem não pode ser qualquer uma. A resposta esperada: conserta-se o que perde mais primeiro só se as etapas forem independentes; quando uma etapa a montante alimenta a outra, consertar a pior conta não recupera nada, porque o dado nem chega lá. Para conferir, abra o codigo/t2/dia15/aula1.ino na pasta codigo/t2/dia15 e procure a função mostrarDiagnostico: as três comparações estão no fim dela.
A resolucao, compilada
// DIAGNOSTICO DO CAMINHO — dia 15, aula 1 (revisão do 2o trimestre). // // Este arquivo não é um exemplo de estilo. É a ferramenta que o aluno roda no // fechamento do trimestre para ver ONDE o caminho dele está quebrando: // quantas leituras foram gravadas, quantas foram perdidas e em quanto tempo. // // AVISO PARA O PROFESSOR: aqui não tem sensor ligado e não tem servidor. As // leituras e as respostas de rede são SIMULADAS, e simuladas de forma // DETERMINÍSTICA (divisão por número, nada de `random` sem semente). Isso é // proposital: a tabela precisa ser exatamente a mesma na placa de todo mundo, // senão a turma não consegue comparar a tela do colega com a sua. // // A aula 2 do dia 16 parte deste mesmo arquivo. #include <Arduino.h> // GPIO2 é o LED embutido na maioria dos DevKit. Aqui ele é o sinal de vida: // se a luz parar de piscar, o programa travou — e não é o WiFi, e não é o banco. const int PIN_LED = 2; // Ritmo do diagnóstico. 21 ciclos em três janelas de sete: número pequeno o // bastante para o professor ler a tabela inteira na lousa antes do intervalo. const int INTERVALO_CICLO = 1500; const int TOTAL_DE_CICLOS = 21; // Limite de plausibilidade introduzido no dia 8. Fora desta faixa o dado é lixo // e não entra no banco, mesmo que a placa "conseguiu" enviar. const float TEMP_MINIMA = -10.0; const float TEMP_MAXIMA = 60.0; // Tamanho do buffer do JSON que a placa monta, igual ao dia 3. const int TAMANHO_PAYLOAD = 96; // --- Contadores do caminho. Ficam no topo, fora do `loop`, de propósito: um // contador que nasce dentro do `loop` é a aula 2 do dia 15 inteira. --- int ciclo = 0; int leiturasTentadas = 0; int leiturasGravadas = 0; int falhasSemSinal = 0; int falhasServidor = 0; int falhasForaDoLimite = 0; int tempoAcumulado = 0; int tempoPiorCaso = 0; int cicloPiorCaso = 0; // Simula a leitura do DHT11. O valor 0 é um defeito real do sensor: quando a // leitura falha, o DHT11 devolve zero em vez de erro. É por isso que existe // limite de plausibilidade. float temperaturaSimulada() { if (ciclo % 11 == 0) { return 0.0; } return 21.0 + ((ciclo % 9) * 2.0); } // Devolve o motivo da falha do ciclo, ou 0 quando o ciclo passa inteiro. // A ordem importa e é o conteúdo da aula: o caminho quebra no PRIMEIRO ponto, // não em todos ao mesmo tempo. int motivoDaFalha(float temperatura) { if (temperatura < TEMP_MINIMA || temperatura > TEMP_MAXIMA) { return 3; // 422: o dado chegou, mas era lixo } if (ciclo % 7 == 0) { return 1; // a placa não achou o ponto de acesso } if (ciclo % 5 == 0) { return 2; // 500: o servidor recebeu e não conseguiu gravar } return 0; } const char* nomeDoMotivo(int motivo) { if (motivo == 1) { return "FALHA sem sinal"; } if (motivo == 2) { return "FALHA 500 servidor"; } if (motivo == 3) { return "FALHA fora do limite"; } return "gravada"; } // Tempo de ida e volta da rede. Simulado, como todo o resto: o valor é // determinístico para que o pior caso caia sempre no mesmo ciclo. int tempoDeRedeSimulado() { return 90 + ((ciclo % 6) * 40); } // O JSON que a placa monta no dia 3. Montar a string é trabalho real e medido // pelo `millis`; só a rede é simulada. void montarPayload(float temperatura, char* destino) { snprintf(destino, TAMANHO_PAYLOAD, "{\"id\":\"placa-01\",\"temperatura\":%.1f}", temperatura); } // Recomeça a contagem para o aluno ver o relatório inteiro mais de uma vez // sem precisar gravar o sketch de novo. void zerarContadores() { ciclo = 0; leiturasTentadas = 0; leiturasGravadas = 0; falhasSemSinal = 0; falhasServidor = 0; falhasForaDoLimite = 0; tempoAcumulado = 0; tempoPiorCaso = 0; cicloPiorCaso = 0; } void mostrarResumo() { int perdas = TOTAL_DE_CICLOS - leiturasGravadas; int percentual = (TOTAL_DE_CICLOS > 0) ? (100 * leiturasGravadas / TOTAL_DE_CICLOS) : 0; int media = (leiturasGravadas > 0) ? (tempoAcumulado / leiturasGravadas) : 0; Serial.println(); Serial.println("=== RESUMO DO CAMINHO ==="); Serial.print("leituras tentadas .. "); Serial.println(leiturasTentadas); Serial.print("leituras gravadas ... "); Serial.println(leiturasGravadas); Serial.print("leituras perdidas ... "); Serial.print(perdas); Serial.print(" ("); Serial.print(percentual); Serial.println(" % de aproveitamento)"); Serial.print("tempo medio ........ "); Serial.print(media); Serial.println(" ms por leitura"); Serial.print("pior caso ......... "); Serial.print(tempoPiorCaso); Serial.print(" ms, no ciclo "); Serial.println(cicloPiorCaso); } // A conferência que fecha o diagnóstico: se estas três contas não baterem, o // erro é de CONTAGEM, e não do sistema. É esta linha que acha o defeito da // aula 2. void conferirContas() { int perdas = TOTAL_DE_CICLOS - leiturasGravadas; Serial.println(); Serial.print("conferencia: "); Serial.print(leiturasTentadas); Serial.print(" = "); Serial.print(leiturasGravadas); Serial.print(" + "); Serial.print(perdas); Serial.println(" ?"); if (leiturasTentadas != leiturasGravadas + perdas) { Serial.println(" >>> AS CONTAS NAO FECHAM. O erro esta na contagem."); } else { Serial.println(" >>> contas fecham: o caminho estainteiro, e o sistema."); } } // O diagnóstico que o aluno lê para decidir por onde começar a consertar. void mostrarDiagnostico() { Serial.println(); Serial.println("=== ONDE O CAMINHO QUEBROU ==="); Serial.print("sensor e validacao . "); Serial.println(falhasForaDoLimite); Serial.print("sinal de rede ....... "); Serial.println(falhasSemSinal); Serial.print("servidor e banco .... "); Serial.println(falhasServidor); int maior = falhasSemSinal; const char* etapa = "sinal de rede"; if (falhasServidor > maior) { maior = falhasServidor; etapa = "servidor e banco"; } if (falhasForaDoLimite > maior) { maior = falhasForaDoLimite; etapa = "sensor e validacao"; } Serial.println(); if (maior == 0) { Serial.println("Nenhuma etapa perdeu leitura neste ciclo completo."); return; } Serial.print("Comece por aqui: "); Serial.print(etapa); Serial.print(", com "); Serial.print(maior); Serial.println(" perdas."); } void setup() { pinMode(PIN_LED, OUTPUT); Serial.begin(115200); Serial.println(); Serial.println("DIAGNOSTICO DO CAMINHO — dia 15, aula 1"); Serial.println("leituras e rede simuladas, deterministicas"); Serial.println("---------------------------------------------------"); Serial.println("ciclo | temperatura | resultado | tempo"); } void loop() { ciclo++; digitalWrite(PIN_LED, HIGH); // sinal de vida: o ciclo começou float temperatura = temperaturaSimulada(); int motivo = motivoDaFalha(temperatura); int rede = tempoDeRedeSimulado(); char payload[TAMANHO_PAYLOAD]; montarPayload(temperatura, payload); unsigned long inicio = millis(); Serial.print(ciclo); Serial.print(" | "); Serial.print(temperatura, 1); Serial.print(" C | "); if (motivo == 0) { leiturasTentadas++; leiturasGravadas++; Serial.print(nomeDoMotivo(motivo)); // O payload só é mostrado no primeiro ciclo inteiro: serve de prova de que // a string que a placa manda é o JSON do dia 3, e polui se repetir 21 vezes. if (leiturasGravadas == 1) { Serial.print(" | "); Serial.print(payload); } } else { // A leitura também conta como tentativa: ela saiu da bancada, tentou // entrar e foi recusada. Perder é diferente de nem ter tentado. leiturasTentadas++; if (motivo == 1) { falhasSemSinal++; } else if (motivo == 2) { falhasServidor++; } else { falhasForaDoLimite++; } Serial.print(nomeDoMotivo(motivo)); } int gasto = (int)(millis() - inicio); tempoAcumulado += gasto; if (gasto > tempoPiorCaso) { tempoPiorCaso = gasto; cicloPiorCaso = ciclo; } Serial.print(" | "); Serial.print(gasto); Serial.print(" ms + "); Serial.print(rede); Serial.println(" ms de rede"); if (ciclo >= TOTAL_DE_CICLOS) { mostrarResumo(); conferirContas(); mostrarDiagnostico(); Serial.println(); Serial.println("O relatorio comeca de novo em alguns segundos."); zerarContadores(); delay(5000); } delay(INTERVALO_CICLO); digitalWrite(PIN_LED, LOW); }
Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia15 aula1.
Aula 2 — Aula de duvidas: o que a turma não conseguiu fazer
Objetivos
- Reproduzir o seu erro na frente da turma, do começo ao fim, sem pular etapa nenhuma.
- Reduzir o caso até sobrar um caminho só, uma variável e um erro.
- Explicar para o colega o que você fez para achar a causa, em três frases, sem mostrar o código.
- Registrar o erro no quadro de erros da turma, escrito como o aluno escreve e não como o professor fala.
Material
- 1 ESP32 DevKit V1 por aluno, com o cabo USB
- Computador com o projeto do trimestre aberto
- Quadro branco limpo na frente da sala, com caneta
- Projetor, para quem estiver com a placa sem resposta
- Folha de listas da aula 1, uma por aluno
Conceitos
Depurar é reduzir, não procurar
O aluno acredita que depurar é "achar o erro". Não é. Depurar é tirar tudo até sobrar pouco: o caminho mínimo que ainda falha. Enquanto a placa, o servidor e o banco estão juntos na tela, existem dezenas de coisas que podem estar erradas, e o olho do aluno vai em todas elas ao mesmo tempo.
O caminho de redução que funciona nesta ordem:
- Qual etapa quebra? A placa imprime em qual ponto o dado parou. Isso corta cinco possibilidades para uma.
- Qual linha? O número da linha da resposta do servidor, ou a mensagem do erro do banco.
- Qual variável? O valor que entrou no lugar do que deveria ter entrado.
- Qual trecho? Meia dúzia de linhas que produzem esse valor errado.
Cada passo corta as possibilidades. Quem tenta pular direto do passo 1 para o passo 4 fica duas horas olhando o mesmo trecho.
O caso mínimo é o que se ensina
O caso mínimo é a menor explicação que ainda reproduz o erro. Se o aluno tem um projeto de trezentas linhas, o caso mínimo dele tem vinte. E essas vinte linhas são o que vai virar exemplo na próxima turma, no material e na aula de quem vier depois.
É por isso que o professor pede para reduzir em voz alta, na frente: o aluno que consegue reduzir o caso aprendeu, e o aluno que explica para o colega aprendeu duas vezes.
Explicar é o teste
O aluno diz "eu entendi" e não sabe explicar. Essa frase não vale nada. O teste é objetivo: explique para o colega o que você fez, sem mostrar o código, em três frases. Se o aluno precisa apontar a tela para explicar, ele não entendeu ainda.
Explicar para o outro é também o que transforma um erro individual em material de aula. O que a turma descobre hoje é o que entra no quadro e, depois, na próxima turma.
O erro é escrito como o aluno escreve
A lista de erros do quadro é escrita com as palavras do aluno: minha variavel ta dando null, o banco ta desconectado, ele fica travando. Reescrever isso em linguagem técnica esconde o motivo pelo qual o erro aconteceu, que foi o aluno não saber o que procurava.
O erro técnico é o mesmo para todo mundo. O que trava a turma é sempre a mesma coisa com quatro palavras diferentes.
Atividade
Montagem: a bancada fica como estiver. O projeto de cada aluno roda como está; nada se desmonta hoje, e a placa não precisa nem estar conectada quando a dúvida não for da placa.
- Escreva no quadro a sua dúvida em uma linha, com as suas palavras. Sem nome de função ainda.
- O professor escolhe uma das linhas e você reproduz o erro na frente da turma, do começo ao fim. Sem pular nenhuma etapa, mesmo a que você já sabe que não é a problema.
- A turma caça junto: cada um escreve um palpite e um número de linha no caderno, antes de o professor falar qualquer coisa.
- Reduza o caso com o professor até sobrar um caminho só: uma etapa, uma variável, um trecho de poucas linhas que ainda falha sozinho.
- Explique para o seu colega, em três frases e sem mostrar o código, o que você descobriu. Ele faz uma pergunta no fim e você responde.
- Escreva o seu caso no quadro: como o erro aparece na tela, e o que era a causa real.
Nota: 10 pontos. Critério de fim: o erro reproduzido na frente até a tela mostrar a falha, o caso mínimo escrito no quadro e a explicação de três frases dada ao colega. Quem só assistiu tira zero: esta aula não tem plateia, tem vez.
Resolucao
O sketch de hoje é a bancada de caça ao erro. A turma digita uma letra no monitor serial e a placa executa um pedaço do caminho — sensor, JSON, rede, servidor, banco — e para no ponto que o aluno mandou quebrar.
O que importa é que a placa não decide o que falha: a letra digitada decide. É assim que o caso mínimo funciona na prática — o erro é escolhido, não sorteado, e por isso a turma consegue ver o mesmo erro duas vezes na mesma aula, em placas diferentes.
// DIA 15, AULA 2 — a bancada de caça ao erro. // // Não é exemplo de estilo: é o programa que o professor usa enquanto o aluno // reproduz o erro na frente. A turma digita uma letra no monitor serial e a // placa executa UM pedaço do caminho — sensor, JSON, rede, servidor, banco. // A letra que trava diz em qual etapa o caminho do aluno morre. // // AVISO PARA O PROFESSOR: não tem sensor, não tem servidor e não tem banco // ligados. Cada etapa é simulada de forma determinística, e a etapa que // "falha" é escolhida pela LETRA digitada, não pelo acaso. É assim que o // caso mínimo funciona: uma variável, um caminho, um erro. #include <Arduino.h> // GPIO2 é o LED embutido. Ele pisca só na etapa pedida: assim a turma vê // qual trecho do caminho rodou de verdade. const int PIN_LED = 2; // Espera entre as linhas digitadas. O aluno tem tempo de ler antes de a placa // imprimir o relatório inteiro. const int INTERVALO_ETAPA = 600; const int TAMANHO_PAYLOAD = 96; // Tempo simulado de cada trecho. Servem para o professor mostrar que o mesmo // dado leva 30 ms numa etapa e 900 ms na seguinte, e que a lentidão tem nome. const int TEMPO_NO_SENSOR = 30; const int TEMPO_NO_SERIAL = 8; const int TEMPO_NA_REDE = 420; const int TEMPO_NO_SERVIDOR = 180; const int TEMPO_NO_BANCO = 260; // A etapa que vai falhar, controlada pela letra que o aluno digitar. char etapaQuebrada = 'n'; float temperaturaDoCiclo() { // 21,5 C: valor estável, longe dos limites. Numa caça ao erro, o dado tem // de estar bom; se o dado também está ruim, são dois problemas ao mesmo // tempo e a turma não enxerga nenhum. return 21.5; } void piscar(int vezes) { for (int i = 0; i < vezes; i++) { digitalWrite(PIN_LED, HIGH); delay(120); digitalWrite(PIN_LED, LOW); delay(120); } } void cabecalho(char etapa, const char* nome) { Serial.println(); Serial.print("--- ETAPA "); Serial.print(etapa); Serial.print(": "); Serial.println(nome); Serial.println("---------------------------------------------------"); } // --- As cinco etapas. Cada uma e isolada de propósito: e isso que permite // --- reproduzir o erro sem arrastar o resto do caminho junto. --- // ETAPA S: a placa lê o sensor. É a única que existe de verdade no HW. bool etapaSensor() { cabecalho('S', "sensor — leitura do DHT11"); float temperatura = temperaturaDoCiclo(); Serial.print("leitura bruta: "); Serial.print(temperatura, 1); Serial.println(" C"); Serial.print("tempo no sensor: "); Serial.print(TEMPO_NO_SENSOR); Serial.println(" ms"); Serial.print("status do sensor: "); Serial.println(temperatura == 0.0 ? "ERRO (devolveu zero)" : "ok"); piscar(1); if (etapaQuebrada == 'S') { Serial.println(">>> QUEBROU AQUI: a leitura nao saiu do sensor."); return false; } Serial.println("etapa passou."); return true; } // ETAPA J: a placa monta o JSON. É o dia 3, sem mudar nada. bool etapaJson() { cabecalho('J', "json — montar o corpo da requisição"); char payload[TAMANHO_PAYLOAD]; snprintf(payload, TAMANHO_PAYLOAD, "{\"id\":\"placa-01\",\"temperatura\":%.1f}", temperaturaDoCiclo()); Serial.println("payload: "); Serial.println(payload); Serial.print("tamanho: "); Serial.print(strlen(payload)); Serial.println(" bytes"); Serial.print("tempo no serial: "); Serial.print(TEMPO_NO_SERIAL); Serial.println(" ms"); piscar(1); if (etapaQuebrada == 'J') { Serial.println(">>> QUEBROU AQUI: o JSON saiu torto ou vazio."); return false; } Serial.println("etapa passou."); return true; } // ETAPA W: a placa procura o ponto de acesso e envia. É o dia 3 e o dia 4. bool etapaRede() { cabecalho('W', "rede — procurar o ponto de acesso e enviar"); Serial.println("SSID da sala: rede-da-sala (nome ficticio, senha ficticia)"); Serial.println("status: conectado, sinal -58 dBm"); Serial.print("tempo gasto na rede: "); Serial.print(TEMPO_NA_REDE); Serial.println(" ms"); piscar(2); if (etapaQuebrada == 'W') { Serial.println(">>> QUEBROU AQUI: a placa nao achou o ponto de acesso, ou o " "servidor nao respondeu."); return false; } Serial.println("etapa passou."); return true; } // ETAPA V: o servidor recebe e decide. É o dia 8: valida antes de gravar. bool etapaServidor() { cabecalho('V', "servidor — receber e validar"); Serial.println("POST /leituras -> 201 Created"); Serial.print("tempo no servidor: "); Serial.print(TEMPO_NO_SERVIDOR); Serial.println(" ms"); piscar(2); if (etapaQuebrada == 'V') { Serial.println(">>> QUEBROU AQUI: resposta 422, o dado foi recusado."); return false; } Serial.println("etapa passou."); return true; } // ETAPA B: o banco grava. É o dia 5 e o dia 6. bool etapaBanco() { cabecalho('B', "banco — gravar a linha"); Serial.println("INSERT INTO leituras (placa_id, temperatura) VALUES (...)"); Serial.print("tempo no banco: "); Serial.print(TEMPO_NO_BANCO); Serial.println(" ms"); Serial.println("linhas na tabela: 1284"); Serial.print("indice em (placa_id, criada_em): "); Serial.println("usado"); piscar(3); if (etapaQuebrada == 'B') { Serial.println(">>> QUEBROU AQUI: o banco recusou a gravacao."); return false; } Serial.println("etapa passou."); return true; } // Roda o caminho inteiro a partir de uma etapa. Quando o caminho morre, para // ali e nao tenta as etapas seguintes: e o mesmo comportamento do sistema real, // e por isso o relatorio nunca mente sobre onde foi o problema. void rodarCaminho(char etapaInicial) { char inicio = etapaInicial; char etapa = inicio; Serial.println(); Serial.print("rodando o caminho a partir da etapa "); Serial.print(inicio); Serial.print(", com a quebra forcada na etapa "); Serial.println(etapaQuebrada); while (true) { bool passou; if (etapa == 'S') { passou = etapaSensor(); } else if (etapa == 'J') { passou = etapaJson(); } else if (etapa == 'W') { passou = etapaRede(); } else if (etapa == 'V') { passou = etapaServidor(); } else if (etapa == 'B') { passou = etapaBanco(); } else { cabecalho(etapa, "nenhuma etapa com essa letra"); Serial.println("use S, J, W, V ou B."); return; } if (!passou) { Serial.println(); Serial.println("RESUMO: o caminho morreu na etapa que voce pediu. " "As etapas depois nunca chegaram a rodar."); Serial.println("E agora a turma corrige essa etapa e repete a letra."); return; } etapa++; if (etapa == 'X') { Serial.println(); Serial.println("RESUMO: o caminho inteiro passou com a quebra forcada " "em outra etapa."); Serial.println("Se o sistema do aluno falha e aqui passou, o defeito " "esta no ambiente dele, e nao no caminho."); return; } } } void mostrarAjuda() { Serial.println(); Serial.println("CACA AO ERRO — digite uma letra e aperte Enter:"); Serial.println(" S = sensor (DHT11)"); Serial.println(" J = json (corpo da requisicao)"); Serial.println(" W = rede (procurar a rede e enviar)"); Serial.println(" V = servidor (receber e validar)"); Serial.println(" B = banco (gravar a linha)"); Serial.println(" T = caminho todo, sem forcar quebra"); Serial.println(" Q = sair"); } void setup() { pinMode(PIN_LED, OUTPUT); Serial.begin(115200); Serial.println(); Serial.println("DIA 15, AULA 2 — cacando o erro na frente da turma"); Serial.println("---------------------------------------------------"); Serial.println("Simulado e deterministico: cada etapa e testada sozinha."); mostrarAjuda(); } void loop() { if (!Serial.available()) { // Espera o comando sem `delay` grande: com `delay`, a placa não escuta o // teclado e o aluno acha que o monitor travou. Foi o defeito do dia 3 que // trocou `delay` por espera não bloqueante, e a razão de ele ter saído do // lugar fixo nas aulas seguintes. delay(50); return; } String comando = Serial.readStringUntil('\n'); comando.trim(); if (comando.length() == 0) { return; } char letra = (char)toupper(comando.charAt(0)); etapaQuebrada = 'n'; if (letra == 'Q') { Serial.println("Ate a proxima."); return; } if (letra == 'T') { etapaQuebrada = 'n'; rodarCaminho('S'); mostrarAjuda(); return; } if (letra == 'S' || letra == 'J' || letra == 'W' || letra == 'V' || letra == 'B') { etapaQuebrada = letra; // Começa uma etapa antes da que foi pedida, para a turma ver o trecho // anterior funcionando e saber que o caso mínimo não é a placa inteira. char anterior = (letra == 'S') ? 'S' : (char)(letra - 1); rodarCaminho(anterior); mostrarAjuda(); return; } cabecalho(letra, "comando desconhecido"); mostrarAjuda(); }
Por que assim e não de outro jeito. Cada etapa é uma função separada que devolve true ou false, e o while caminha de uma para a outra. A alternativa óbvia seria um switch com o caminho inteiro dentro de cada caso, e ela é pior: quando a etapa V falha, um switch gigante ou continua rodando e imprime "banco ok" depois do erro — mentira na tela — ou o professor passa a aula inteira caçando o return certo.
A letra T existe para o caso do aluno cujo sistema funciona: o caminho inteiro passa, e a resposta é "o defeito está no ambiente dele, e não no caminho". Sem essa letra, a turma passa a aula inteira caçando erro em sistema que funciona.
O delay(50) no loop em vez de um delay grande no fim do laço é o que permite que a placa leia o teclado. É a mesma lição do dia 3, aplicada: a placa não pode passar a vida parada esperando.
Criterios de correcao
| Critério | Pontos |
|---|---|
| Erro reproduzido na frente da turma do começo ao fim, sem pular etapa | 3 pontos |
| Palpite escrito no caderno antes de qualquer fala do professor | 2 pontos |
| Caso mínimo no quadro: uma etapa, uma variável, um trecho | 3 pontos |
| Explicação de três frases ao colega, respondendo a uma pergunta | 2 pontos |
Erros comuns
ATENÇÃO, PROFESSOR: esta seção começa vazia e é preenchida por Você depois da aula, com o que aconteceu de verdade. O quadro de erros montado no bloco de 6 minutos do roteiro é exatamente o conteúdo desta tabela. Escreva cada linha com as palavras do aluno, não com as suas — é assim que o erro fica encontrável por quem vai errar do mesmo jeito no ano que vem. Nenhuma linha desta tabela é de exemplo: as cinco que já estão são os erros de estrutura desta aula, e o resto é da turma.
| Erro | Como aparece | Correcao |
|---|---|---|
| Reproduzir "só o pedaço que eu acho que é o problema", pulando o resto | O aluno reproduz o defeito, mas ninguém sabe se o resto do caminho também quebrava | "Do começo. Se você pular a etapa da rede, eu não sei se o seu erro era de rede. Roda inteiro, mesmo a parte que você tem certeza que está boa." |
| Achar que o caso mínimo é a tela com o erro | O aluno aponta a mensagem do navegador e diz "o caso é esse" | "A tela é onde o erro aparece, não é o caso. Reduz até sobrar um trecho de vinte linhas que falha sozinho, sem placa e sem banco. Esse é o caso." |
| Explicar com o dedo na tela | "Aqui ó, ó, ó, ó" e o colega não entende nada | "Sem apontar. Se você precisa apontar, você ainda não sabe o que está acontecendo. Explique a ideia, não a posição." |
| Trocar de erro no meio da explicação | O aluno começa falando de validação e termina falando de rede | "Não. Primeiro termina esse. Se tem dois erros, a gente anota os dois no quadro e resolve um por aula." |
| "Não funciona aqui no meu computador" como resposta à caça ao erro | O aluno recusa o caso mínimo porque no notebook dele funciona | "Se no seu funciona e aqui falha, o que muda é o ambiente, e aí é outro problema. Mas a gente precisa do caso primeiro: roda aqui até falhar." |
Desafio extra
Sem o professor: pegue a última linha que a turma escreveu no quadro e escreva o teste que pegaria aquele erro antes de ele acontecer. Um teste por erro, e ele tem que falhar quando você reintroduz o defeito de propósito. Se o teste passa com o defeito colado no código, o teste não está pegando o erro, e você descobriu isso na hora — que é a mesma hora em que o teste vai salvar o projeto no 3o trimestre.
>A resolucao, compilada
// DIA 15, AULA 2 — a bancada de caça ao erro. // // Não é exemplo de estilo: é o programa que o professor usa enquanto o aluno // reproduz o erro na frente. A turma digita uma letra no monitor serial e a // placa executa UM pedaço do caminho — sensor, JSON, rede, servidor, banco. // A letra que trava diz em qual etapa o caminho do aluno morre. // // AVISO PARA O PROFESSOR: não tem sensor, não tem servidor e não tem banco // ligados. Cada etapa é simulada de forma determinística, e a etapa que // "falha" é escolhida pela LETRA digitada, não pelo acaso. É assim que o // caso mínimo funciona: uma variável, um caminho, um erro. #include <Arduino.h> // GPIO2 é o LED embutido. Ele pisca só na etapa pedida: assim a turma vê // qual trecho do caminho rodou de verdade. const int PIN_LED = 2; // Espera entre as linhas digitadas. O aluno tem tempo de ler antes de a placa // imprimir o relatório inteiro. const int INTERVALO_ETAPA = 600; const int TAMANHO_PAYLOAD = 96; // Tempo simulado de cada trecho. Servem para o professor mostrar que o mesmo // dado leva 30 ms numa etapa e 900 ms na seguinte, e que a lentidão tem nome. const int TEMPO_NO_SENSOR = 30; const int TEMPO_NO_SERIAL = 8; const int TEMPO_NA_REDE = 420; const int TEMPO_NO_SERVIDOR = 180; const int TEMPO_NO_BANCO = 260; // A etapa que vai falhar, controlada pela letra que o aluno digitar. char etapaQuebrada = 'n'; float temperaturaDoCiclo() { // 21,5 C: valor estável, longe dos limites. Numa caça ao erro, o dado tem // de estar bom; se o dado também está ruim, são dois problemas ao mesmo // tempo e a turma não enxerga nenhum. return 21.5; } void piscar(int vezes) { for (int i = 0; i < vezes; i++) { digitalWrite(PIN_LED, HIGH); delay(120); digitalWrite(PIN_LED, LOW); delay(120); } } void cabecalho(char etapa, const char* nome) { Serial.println(); Serial.print("--- ETAPA "); Serial.print(etapa); Serial.print(": "); Serial.println(nome); Serial.println("---------------------------------------------------"); } // --- As cinco etapas. Cada uma e isolada de propósito: e isso que permite // --- reproduzir o erro sem arrastar o resto do caminho junto. --- // ETAPA S: a placa lê o sensor. É a única que existe de verdade no HW. bool etapaSensor() { cabecalho('S', "sensor — leitura do DHT11"); float temperatura = temperaturaDoCiclo(); Serial.print("leitura bruta: "); Serial.print(temperatura, 1); Serial.println(" C"); Serial.print("tempo no sensor: "); Serial.print(TEMPO_NO_SENSOR); Serial.println(" ms"); Serial.print("status do sensor: "); Serial.println(temperatura == 0.0 ? "ERRO (devolveu zero)" : "ok"); piscar(1); if (etapaQuebrada == 'S') { Serial.println(">>> QUEBROU AQUI: a leitura nao saiu do sensor."); return false; } Serial.println("etapa passou."); return true; } // ETAPA J: a placa monta o JSON. É o dia 3, sem mudar nada. bool etapaJson() { cabecalho('J', "json — montar o corpo da requisição"); char payload[TAMANHO_PAYLOAD]; snprintf(payload, TAMANHO_PAYLOAD, "{\"id\":\"placa-01\",\"temperatura\":%.1f}", temperaturaDoCiclo()); Serial.println("payload: "); Serial.println(payload); Serial.print("tamanho: "); Serial.print(strlen(payload)); Serial.println(" bytes"); Serial.print("tempo no serial: "); Serial.print(TEMPO_NO_SERIAL); Serial.println(" ms"); piscar(1); if (etapaQuebrada == 'J') { Serial.println(">>> QUEBROU AQUI: o JSON saiu torto ou vazio."); return false; } Serial.println("etapa passou."); return true; } // ETAPA W: a placa procura o ponto de acesso e envia. É o dia 3 e o dia 4. bool etapaRede() { cabecalho('W', "rede — procurar o ponto de acesso e enviar"); Serial.println("SSID da sala: rede-da-sala (nome ficticio, senha ficticia)"); Serial.println("status: conectado, sinal -58 dBm"); Serial.print("tempo gasto na rede: "); Serial.print(TEMPO_NA_REDE); Serial.println(" ms"); piscar(2); if (etapaQuebrada == 'W') { Serial.println(">>> QUEBROU AQUI: a placa nao achou o ponto de acesso, ou o " "servidor nao respondeu."); return false; } Serial.println("etapa passou."); return true; } // ETAPA V: o servidor recebe e decide. É o dia 8: valida antes de gravar. bool etapaServidor() { cabecalho('V', "servidor — receber e validar"); Serial.println("POST /leituras -> 201 Created"); Serial.print("tempo no servidor: "); Serial.print(TEMPO_NO_SERVIDOR); Serial.println(" ms"); piscar(2); if (etapaQuebrada == 'V') { Serial.println(">>> QUEBROU AQUI: resposta 422, o dado foi recusado."); return false; } Serial.println("etapa passou."); return true; } // ETAPA B: o banco grava. É o dia 5 e o dia 6. bool etapaBanco() { cabecalho('B', "banco — gravar a linha"); Serial.println("INSERT INTO leituras (placa_id, temperatura) VALUES (...)"); Serial.print("tempo no banco: "); Serial.print(TEMPO_NO_BANCO); Serial.println(" ms"); Serial.println("linhas na tabela: 1284"); Serial.print("indice em (placa_id, criada_em): "); Serial.println("usado"); piscar(3); if (etapaQuebrada == 'B') { Serial.println(">>> QUEBROU AQUI: o banco recusou a gravacao."); return false; } Serial.println("etapa passou."); return true; } // Roda o caminho inteiro a partir de uma etapa. Quando o caminho morre, para // ali e nao tenta as etapas seguintes: e o mesmo comportamento do sistema real, // e por isso o relatorio nunca mente sobre onde foi o problema. void rodarCaminho(char etapaInicial) { char inicio = etapaInicial; char etapa = inicio; Serial.println(); Serial.print("rodando o caminho a partir da etapa "); Serial.print(inicio); Serial.print(", com a quebra forcada na etapa "); Serial.println(etapaQuebrada); while (true) { bool passou; if (etapa == 'S') { passou = etapaSensor(); } else if (etapa == 'J') { passou = etapaJson(); } else if (etapa == 'W') { passou = etapaRede(); } else if (etapa == 'V') { passou = etapaServidor(); } else if (etapa == 'B') { passou = etapaBanco(); } else { cabecalho(etapa, "nenhuma etapa com essa letra"); Serial.println("use S, J, W, V ou B."); return; } if (!passou) { Serial.println(); Serial.println("RESUMO: o caminho morreu na etapa que voce pediu. " "As etapas depois nunca chegaram a rodar."); Serial.println("E agora a turma corrige essa etapa e repete a letra."); return; } etapa++; if (etapa == 'X') { Serial.println(); Serial.println("RESUMO: o caminho inteiro passou com a quebra forcada " "em outra etapa."); Serial.println("Se o sistema do aluno falha e aqui passou, o defeito " "esta no ambiente dele, e nao no caminho."); return; } } } void mostrarAjuda() { Serial.println(); Serial.println("CACA AO ERRO — digite uma letra e aperte Enter:"); Serial.println(" S = sensor (DHT11)"); Serial.println(" J = json (corpo da requisicao)"); Serial.println(" W = rede (procurar a rede e enviar)"); Serial.println(" V = servidor (receber e validar)"); Serial.println(" B = banco (gravar a linha)"); Serial.println(" T = caminho todo, sem forcar quebra"); Serial.println(" Q = sair"); } void setup() { pinMode(PIN_LED, OUTPUT); Serial.begin(115200); Serial.println(); Serial.println("DIA 15, AULA 2 — cacando o erro na frente da turma"); Serial.println("---------------------------------------------------"); Serial.println("Simulado e deterministico: cada etapa e testada sozinha."); mostrarAjuda(); } void loop() { if (!Serial.available()) { // Espera o comando sem `delay` grande: com `delay`, a placa não escuta o // teclado e o aluno acha que o monitor travou. Foi o defeito do dia 3 que // trocou `delay` por espera não bloqueante, e a razão de ele ter saído do // lugar fixo nas aulas seguintes. delay(50); return; } String comando = Serial.readStringUntil('\n'); comando.trim(); if (comando.length() == 0) { return; } char letra = (char)toupper(comando.charAt(0)); etapaQuebrada = 'n'; if (letra == 'Q') { Serial.println("Ate a proxima."); return; } if (letra == 'T') { etapaQuebrada = 'n'; rodarCaminho('S'); mostrarAjuda(); return; } if (letra == 'S' || letra == 'J' || letra == 'W' || letra == 'V' || letra == 'B') { etapaQuebrada = letra; // Começa uma etapa antes da que foi pedida, para a turma ver o trecho // anterior funcionando e saber que o caso mínimo não é a placa inteira. char anterior = (letra == 'S') ? 'S' : (char)(letra - 1); rodarCaminho(anterior); mostrarAjuda(); return; } cabecalho(letra, "comando desconhecido"); mostrarAjuda(); }
Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia15 aula2.
