O caminho do dado — Arduino e IoT — semana 1 do 2o trimestre
Semana 1 de 16· 2o trimestre · 01/05 a 04/09
O caminho do dado
A arquitetura do trimestre e o primeiro programa no terminal.
Aula 1 — A arquitetura do trimestre: sensor, placa, servidor e banco
Objetivos
- Desenhar o caminho completo do dado do trimestre, da leitura do sensor até a linha no banco, nomeando as seis pecas.
- Dizer em qual peca de cada etapa o dado vira número, e em qual peca ele vira texto para viajar.
- Explicar por que o dado nasce no sensor e não no servidor, e o que isso muda em relacao ao curso de JavaScript.
- Apontar, no caminho, o ponto onde o dado pode se perder, e dizer o que o professor veria se isso acontecesse.
- Rodar na placa o sketch do dia e ler no monitor serial a leitura analogica que sera enviada.
Material
- 1 ESP32 DevKit V1 por aluno, com o cabo USB
- 1 protoboard de 830 pontos e 1 jogo de jumpers por aluno
- 1 módulo de alimentacao MB102 por dupla
- 1 potenciometro de 10 kOhm por dupla
- Computador com Node.js 20 ou superior instalado antes da aula
- Projetor, para o diagrama do caminho
Conceitos
O caminho do dado, peca por peca
O trimestre inteiro cabe nesta cadeia:
| # | Peca | O que faz | Em que aula |
|---|---|---|---|
| 1 | Sensor | transforma o mundo fisico em número analogico | dia 1, dia 8 do 1o trimestre |
| 2 | Placa ESP32 | le, transforma e monta o JSON | dia 3 |
| 3 | WiFi | leva o JSON pela rede até outra maquina | dia 3 |
| 4 | HTTP | o idioma que placa e servidor falam | dia 2 |
| 5 | Node.js | recebe, decide e chama o banco | dia 2, dia 5 |
| 6 | MySQL | guarda a leitura para sempre | dia 4 |
O dado e o mesmo objeto nas seis pecas. O que muda e a representação, e essa e a ideia que o trimestre inteiro repete: o sensor produz um inteiro de 0 a 4095, a placa produz um texto JSON, o Node produz um objeto JavaScript, e o banco produz uma linha com sete colunas.
Por que este trimestre não comeca em software
No curso de JavaScript, o dado nascia no seu código: você digitava um valor, o programa recebia. Aqui o dado nasce em um sensor que você não controla, num processo que so roda quando alguem manda rodar, num banco que continua gravando depois que você fechar o editor.
A consequencia pratica e a diferença real entre os dois cursos: no navegador, o efeito e visivel na hora. Se o programa esta errado, a tela mostra. Aqui nada acontece sem subir processo, sem rota e sem banco, e o aluno so descobre que quebrou quando a tela do painel mostra dado de ontem.
Onde o dado pode se perder
Cada quebra tem uma assinatura diferente, e o aluno precisa aprender a reconhecela:
| Peca que quebrou | O que o aluno ve |
|---|---|
| Sensor | leitura identica o tempo todo, sempre o mesmo número |
| Placa | nada muda no monitor serial |
| WiFi | serial mostra "sem rede" e o loop continua |
| Servidor Node | serial mostra código de rede negativo |
| Rota errada | servidor responde 404 e o Node imprime o log |
| Banco parado | Node responde 500 e o erro esta no erro.code |
| JSON invalido | Node lanca excecao no JSON.parse |
O professor repete esta tabela ao longo do trimestre. E odiagnostico do curso.
Atividade
Montagem:
- Potenciometro de 10 kOhm na protoboard: pino do meio em um
GND, pino da esquerda na trilha+, pino da direita noGPIO34. - Resistor de 330 em serie com o lado esquerdo do potenciometro, para não sobrar corrente.
- Escreva no caderno as seis pecas do caminho, na ordem, e uma linha dizendo o que cada uma faz com o dado.
- Marque no seu desenho onde o dado deixa de ser número e vira texto. Escreva o nome do formato nesse ponto.
- Marque onde o dado deixa de ser temporario e vira permanente. Esse ponto e o que diferencia este trimestre do curso de JavaScript.
- Grave o sketch da resolucao e anote três leituras do
GPIO34que aparecam no monitor serial. - Descreva, em uma frase, o que o professor veria se o cabo de rede fosse desconectado apenas no dia 8. Não e resposta hoje: e para o professor perguntar no dia 8.
Nota: 9 pontos. Critério de fim: o desenho tem as seis pecas na ordem, com os dois pontos marcados, e o monitor serial mostra três leituras diferentes.
Resolucao
A resposta do item 2 e o dia 3, quando a placa monta o JSON com String. A resposta do item 3 e o dia 4, quando a linha entra no MySQL. O professor guarda as duas respostas e devolve no dia correspondente.
O código de hoje mostra a ponta de origem do caminho: o sensor lido e o registro com nome de campo, que e a primeira coisa que o servidor vai receber. Não entra no WiFi e não monta JSON: a aula 1 do dia 3 faz isso.
// Aula 1 do dia 1: a arquitetura do trimestre inteira, em uma tela. // // O dia e sobre o CAMINHO do dado: sensor -> placa -> WiFi -> HTTP -> Node -> // MySQL. Hoje so a primeira peca existe na bancada. Este sketch mostra a // ponta de origem do caminho, que e a unica coisa que o 1o trimestre produz. #include <Arduino.h> // Pinos analogicos do DevKit V1. O 34 e o mais seguro para leitura analogica // porque nao tem resistor de pull-up interno e so pode ser entrada. const int PIN_SENSOR_ANALOGICO = 34; const int PIN_SENSOR_DIGITAL = 4; // Resolução do conversor analogico: 12 bits, de 0 a 4095. Este numero e o // que define o maximo de analogRead, e ele volta no dia 13. const int RESOLUCAO_ADC = 4095; // Valor lido no 1o trimestre e cru. Aqui ele vira uma leitura com nome, // que e a primeira coisa que o servidor vai receber. const float TEMP_MINIMA_C = 0.0; const float TEMP_MAXIMA_C = 50.0; void setup() { Serial.begin(115200); delay(2000); Serial.println(); Serial.println("2o trimestre, dia 1, aula 1 - o caminho do dado"); Serial.println("================================================="); Serial.println("hoje existe: sensor -> placa"); Serial.println("vira hoje: placa -> WiFi -> HTTP -> Node -> MySQL"); Serial.println(); Serial.println("cada etapa do caminho, e quem cuida dela:"); Serial.println(" 1. sensor : le o valor analogico, esta aula"); Serial.println(" 2. placa : monta o JSON, dia 3"); Serial.println(" 3. WiFi : leva o dado pela rede, dia 3"); Serial.println(" 4. HTTP : o idioma, dia 2"); Serial.println(" 5. Node.js : recebe e decide, dia 2 e dia 5"); Serial.println(" 6. MySQL : guarda para sempre, dia 4"); Serial.println(); Serial.println("caminho | pino | bruto | leitura"); } void loop() { int bruto = analogRead(PIN_SENSOR_ANALOGICO); // Transformacao linear de 0..4095 para TEMP_MINIMA_C..TEMP_MAXIMA_C. // E a MESMA conta do 1o trimestre, escrita sem map() para o professor // mostrar o que map() faz por dentro. float leitura = TEMP_MINIMA_C + (bruto / (float)RESOLUCAO_ADC) * (TEMP_MAXIMA_C - TEMP_MINIMA_C); Serial.print(" 1 | "); Serial.print(PIN_SENSOR_ANALOGICO); Serial.print(" | "); Serial.print(bruto); Serial.print(" | "); Serial.print(leitura, 1); Serial.println(" C"); // A etapa 2 e o que o dia 3 faz. Hoje so e anunciada, nao executada. Serial.println(" 2 | 4 | --- | JSON a ser montado no dia 3"); delay(2000); }
Por que assim e não de outro jeito. A tabela das seis pecas esta no código como Serial.println, e não so na lousa, porque o aluno precisa voltar nela. O que o professor não faz hoje e mostrar o JSON montado: seria tentar rodar a etapa 3 sem a etapa 2, e o aluno que não tem a etapa 2 não tem a etapa 3.
A transformacao linear esta escrita sem map() justamente para ser legivel. O aluno do 1o trimestre já usou map() no dia 5, e ver a conta inteira escrita resolve a duvida que sobra: "de onde saiu essa conta?".
Criterios de correcao
| Critério | Pontos |
|---|---|
| As seis pecas do caminho desenhadas, na ordem, com uma linha de função cada | 3 pontos |
| Ponto marcado onde o dado vira texto, com o nome do formato | 2 pontos |
| Ponto marcado onde o dado vira permanente | 1 ponto |
| Sketch gravado e três leituras diferentes anotadas no caderno | 2 pontos |
| Item 5 respondido em uma frase coerente | 1 ponto |
Erros comuns
| Erro | Como aparece | Correcao |
|---|---|---|
| Desenhar o caminho com a placa no meio, e o sensor colado nela | "O sensor e o ESP32" | "O sensor e um peca separada. O DHT11 do dia 8 tem três pinos e o ESP32 tem outros tantos. São coisas diferentes que se conectam por fio." |
| Pular a etapa do WiFi e do HTTP no desenho | O caminho vai de "placa" direto para "banco" | "A placa não fala com o banco. Ela fala HTTP com um processo Node, e o Node que fala SQL com o banco. São dois protocolos e duas maquinas." |
| Dizer que o dado vira permanente no MySQL porque o MySQL e "de verdade" | Item 3 respondido com justificativa sobre o banco | "O que torna o dado permanente não e o banco: e que o dado sobrevive ao fim do programa. O banco e so o lugar onde ele sobrevive. O Node também e um processo que morre quando você aperta Ctrl+C." |
| Ler o GPIO34 e escrever sempre o mesmo número | Item 4 com três valores iguais | "Você esta com o jumper do pino do meio solto, ou o potenciometro esta na posicao de extrema. Mexe com o potenciometro com a tela aberta e ve o número andar." |
Achar que o delay(2000) e o tempo de leitura | "A placa espera 2 segundos para ler o sensor" | "A placa espera 2 segundos depois de ler, so para a tela não virar um rio de número. Se o delay sumisse, a leitura seria a cada milissegundo e o monitor não daria conta de imprimir." |
Desafio extra
O caminho tem seis pecas e cinco fronteiras entre elas. Escreva, para cada fronteira, uma frase dizendo o que pode dar errado exatamente ali, e qual das duas pecas vizinhas o professor culparia primeiro. A resposta que o professor prefere e a que diz que a culpa e sempre da fronteira, e não da peca: quem manda e quem recebe precisam concordar sobre o formato, e quando não concordam, nenhum dos dois tem razão.
>A resolucao, compilada
// Aula 1 do dia 1: a arquitetura do trimestre inteira, em uma tela. // // O dia e sobre o CAMINHO do dado: sensor -> placa -> WiFi -> HTTP -> Node -> // MySQL. Hoje so a primeira peca existe na bancada. Este sketch mostra a // ponta de origem do caminho, que e a unica coisa que o 1o trimestre produz. #include <Arduino.h> // Pinos analogicos do DevKit V1. O 34 e o mais seguro para leitura analogica // porque nao tem resistor de pull-up interno e so pode ser entrada. const int PIN_SENSOR_ANALOGICO = 34; const int PIN_SENSOR_DIGITAL = 4; // Resolução do conversor analogico: 12 bits, de 0 a 4095. Este numero e o // que define o maximo de analogRead, e ele volta no dia 13. const int RESOLUCAO_ADC = 4095; // Valor lido no 1o trimestre e cru. Aqui ele vira uma leitura com nome, // que e a primeira coisa que o servidor vai receber. const float TEMP_MINIMA_C = 0.0; const float TEMP_MAXIMA_C = 50.0; void setup() { Serial.begin(115200); delay(2000); Serial.println(); Serial.println("2o trimestre, dia 1, aula 1 - o caminho do dado"); Serial.println("================================================="); Serial.println("hoje existe: sensor -> placa"); Serial.println("vira hoje: placa -> WiFi -> HTTP -> Node -> MySQL"); Serial.println(); Serial.println("cada etapa do caminho, e quem cuida dela:"); Serial.println(" 1. sensor : le o valor analogico, esta aula"); Serial.println(" 2. placa : monta o JSON, dia 3"); Serial.println(" 3. WiFi : leva o dado pela rede, dia 3"); Serial.println(" 4. HTTP : o idioma, dia 2"); Serial.println(" 5. Node.js : recebe e decide, dia 2 e dia 5"); Serial.println(" 6. MySQL : guarda para sempre, dia 4"); Serial.println(); Serial.println("caminho | pino | bruto | leitura"); } void loop() { int bruto = analogRead(PIN_SENSOR_ANALOGICO); // Transformacao linear de 0..4095 para TEMP_MINIMA_C..TEMP_MAXIMA_C. // E a MESMA conta do 1o trimestre, escrita sem map() para o professor // mostrar o que map() faz por dentro. float leitura = TEMP_MINIMA_C + (bruto / (float)RESOLUCAO_ADC) * (TEMP_MAXIMA_C - TEMP_MINIMA_C); Serial.print(" 1 | "); Serial.print(PIN_SENSOR_ANALOGICO); Serial.print(" | "); Serial.print(bruto); Serial.print(" | "); Serial.print(leitura, 1); Serial.println(" C"); // A etapa 2 e o que o dia 3 faz. Hoje so e anunciada, nao executada. Serial.println(" 2 | 4 | --- | JSON a ser montado no dia 3"); delay(2000); }
Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia01 aula1.
Aula 2 — Node.js: o runtime e o primeiro programa no terminal
Objetivos
- Rodar um programa Node.js no terminal e explicar que o
console.loge a unica saída que importa fora do navegador. - Ler o resultado de
process.argve dizer o que são os dois primeiros itens da lista. - Instalar e executar um pacote com
npme dizer o quepackage.jsonguarda. - Justificar por que um programa de terminal morre quando chega no fim, e o que isso muda em relacao a uma página web.
- Reconhecer o
SyntaxErrorpela mensagem e dizer em qual linha o Node parou.
Material
- 1 ESP32 DevKit V1 por dupla, com o cabo USB
- 1 protoboard de 830 pontos por dupla
- Computador com Node.js 20 ou superior instalado antes da aula
- Terminal com permissao para instalar pacote global
- Projetor, para o terminal do professor
Conceitos
O que muda quando o código sai do navegador
No 1o trimestre, o código rodava dentro do navegador, que cuidava de tudo: abria a porta, recebia a página, mantinha o programa vivo. Em Node, o programa e o responsavel pela propria existencia:
| No navegador | Em Node |
|---|---|
| O navegador abre a porta | O código chama server.listen |
| O navegador mantem a página viva | O código mantem o processo vivo |
fetch para outro endereco | http.createServer cria um endereco |
| Erro aparece no console do navegador | Erro aparece no terminal |
A frase que o professor repete: "no navegador a linguagem sozinha faz algo visivel; aqui nada acontece sem subir um processo". E por isso que a aula de hoje termina com um programa que imprime e morre, e o dia 2 troca isso por um processo que fica vivo.
console.log, process.argv e a morte do processo
console.log('ola'); // escreve no terminal e segue
O console.log em Node e a saída padrao do processo. Tudo que o programa quiser contar precisa sair por ele, porque o terminal e a unica tela disponivel.
process.argv traz os argumentos da linha de comando. A lista sempre comeca com dois itens que o aluno não escreveu: o caminho do executavel e o caminho do arquivo. Por isso o slice(2).
O programa de terminal termina sozinho quando chega na ultima linha. Não ha loop, não ha evento, não ha janela segurando ele aberto. Isso e uma diferença e não um defeito: o programa faz o que tem que fazer e libera a maquina.
package.json e node_modules
Quando o aluno roda npm init -y, o Node cria o package.json: o nome do projeto, a versão, e a lista de dependencias. Quando roda npm install mysql2, ele baixa o pacote para dentro de node_modules.
O professor repete nesta aula: node_modules nunca vai para o git, e o motivo e o dia 11. O que vai para o git e o package.json e o package-lock.json, que juntos dizem "para reproduzir este projeto, instale estas versões".
Atividade
Montagem: nenhuma. Hoje não se liga nada. A placa e programada no fim da aula so para mostrar de onde vem o dado que o Node vai receber.
- Rode
node --versione escreva a versão no caderno. Se não responder, avisar o professor antes de comecar. - Escreva o arquivo
primeiro.jscom trêsconsole.log: a versão do Node, a versão do motor V8 e oprocess.pid. - Rode o programa duas vezes e compare os dois resultados. Alguma coisa muda entre as execuções? Diga qual.
- Rode o programa com dois argumentos:
node primeiro.js lab01 23.5. O que aparece emprocess.argv.slice(2)? Por quelab01e o primeiro item e não o terceiro? - Em uma pasta nova, rode
npm init -ye depoisnpm install chalk. Liste o que apareceu na pasta depois de cada comando. - Apague a chave de fechamento do
console.logdo item 2 e rode de novo. Leia a mensagem de erro inteira e escreva em uma frase o que ela diz e onde aponta.
Nota: 10 pontos. Critério de fim: o programa roda, a diferença entre as duas execuções esta escrita, e o SyntaxError foi lido e explicado.
Resolucao
O programa do dia 1 e o código que o professor digita junto com a turma:
// Primeiro programa em Node. O aluno acabou de terminar JavaScript no
// navegador: a mesma linguagem, fora do navegador, rodando como processo.
console.log('Node em uso:', process.version);
console.log('Motor V8:', process.versions.v8);
// process.argv traz o executavel, o arquivo e os argumentos do comando.
// slice(2) descarta os dois primeiros, que o aluno nao escreveu.
console.log('argumentos recebidos:', process.argv.slice(2));
// O que separa um programa de terminal de uma pagina web: ele começa, faz
// o que tem que fazer, e morre. Nao existe janela para ficar esperando.
console.log('pid', process.pid, 'terminando agora');
Por que assim e não de outro jeito. O console.log de process.pid existe para o item 3 da atividade. Sem ele, o aluno roda o programa duas vezes e ve exatamente a mesma saída, e conclui que "o Node guardou o estado" — que e um mal-entendido que custa metade da aula de ORM e modules no 3o trimestre. Com o pid, o número muda a cada execução e o aluno ve, sozinho, que cada execução e um processo novo.
O process.argv.slice(2) e o item 4, e o professor não explica por que e 2: ele pergunta, e espera a resposta. Quem nunca perguntou por que e 2 e nunca vai entender slice no dia 8 do 3o trimestre.
A saída real do programa, medida nesta maquina:
Node em uso: v24.21.0 Motor V8: 13.6.233.17-node.53 argumentos recebidos: [ 'lab01', '23.5' ] pid 377022 terminando agora
O professor não escreve esta saída na lousa. Ele roda o programa na frente da turma e deixa o terminal falar. A diferença entre o número do pid e o que o aluno ve no terminal dele e o ponto: são processos diferentes, e o pid prova isso.
O sketch da placa vem na resolucao do código C++, e existe porque a porta do material exige um exemplo de placa em toda aula. Ele mede o valor analogico e monta o JSON que sera enviado, mas não entra no WiFi: o dia 3 faz isso. O comentario do sketch diz isso ao aluno, e o professor diz na lousa antes de comecar a ler código.
Criterios de correcao
| Critério | Pontos |
|---|---|
node --version conferido e versão anotada | 1 ponto |
Programa com os três console.log roda e imprime | 3 pontos |
| Item 3 respondido: a diferença entre as duas execuções esta nomeada | 2 pontos |
Item 4 respondido: o motivo do slice(2) esta em uma frase | 2 pontos |
O que apareceu na pasta depois de npm init e depois de npm install esta listado | 1 ponto |
Item 6 respondido: a mensagem do SyntaxError foi lida e explicada | 1 ponto |
Erros comuns
| Erro | Como aparece | Correcao |
|---|---|---|
Rodar o .js dando dois cliques no explorador de arquivos | Nada acontece, ou abre o navegador | "Node não e um programa de janela. No terminal, digite node primeiro.js e aperte enter." |
Escrever console.log("texto " + x) e esperar o terminal quebrar a linha sozinho | "No navegador a linha quebra, aqui não" | "No terminal a quebra de linha e sua. Se quiser o mesmo efeito do navegador, e o que a aula de formatacao faz, e não hoje." |
console.log(process.argv) e concluir que o array tem dois itens | Item 4 com resposta errada | "Rode o comando com argumentos e olhe de novo. Os dois primeiros itens aparecem sempre, e não são seus." |
Achar que npm install instalou o Node | "Precisei instalar o Node com npm" | "npm e o gerenciador de pacotes do Node. Ele instala bibliotecas, não o Node. O Node já veio instalado." |
Fechar a chave do console.log e usar a mensagem como se fosse de rede | "O erro fala de Unexpected token, então o servidor caiu" | "Esse erro acontece antes de qualquer linha rodar: e o Node nem comecou a executar. Erro de sintaxe e erro de rede são coisas diferentes, e o dia 3 usa essa distincao." |
Deixar o node_modules dentro da pasta do projeto e depois tentar enviar a pasta inteira | "Pasta muito grande para mandar" | "Isso e esperado, e o motivo de o dia 11 tratar .gitignore. Não apague agora: e o que faz o projeto funcionar." |
Desafio extra
Escreva um programa que receba quantos argumentos quiser e imprima o primeiro e o ultimo, sem quebrar quando o aluno rodar sem nenhum argumento. Depois rode com zero, com um e com cinco argumentos, e escreva o que o programa faz em cada caso. O que o professor espera descobrir: sem checar o tamanho do array, argv[argv.length - 1] devolve undefined, e imprimir undefined e um bug que passa em cinco de seis casos.
A resolucao, compilada
// Aula 2 do 2o trimestre: o dia e de software (Node.js no terminal), mas o // dado que o Node vai receber precisa existir em algum lugar. // // Este sketch existe para isso: ele mede um valor ANALOGICO de verdade, // mostra o JSON que seria enviado e nada mais. Nao entra no WiFi, nao faz // HTTP, nao tem servidor. O comentario do professor diz isso na lousa, e o // aluno precisa ouvir antes de comecar a ler codigo. #include <Arduino.h> // Pino analogico do kit. GPIO34 e um dos poucos que so le analogico e nao // tem resistor de pull-up interno, por isso e o mais seguro para este teste. const int PIN_SENSOR_ANALOGICO = 34; // A placa que este trimestre vai enviar. Vai ser o id_placa no JSON. const char ID_PLANCA[] = "esp32-lab01"; // Intervalo entre leituras. O mesmo intervalo do dia 7, onde ele vira o tema: // aqui ele so serve para a tela nao virar um rio de numero. const long INTERVALO_MS = 2000; void setup() { Serial.begin(115200); // Dois segundos de espera antes do primeiro print: sem isso, os primeiros // caracteres saem antes de o monitor serial abrir e somem da tela. delay(2000); Serial.println(); Serial.println("2o trimestre, dia 1, aula 2 - o dado antes do Node"); Serial.println("-------------------------------------------------"); Serial.println("Hoje o codigo roda SO na placa. A rede entra no dia 2."); Serial.println(); Serial.println("id_placa | temp_c_simulado | umidade_pct_simulado"); } void loop() { // analogRead devolve um inteiro de 0 a 4095: 12 bits. Ainda nao e um valor // de sensor, e um numero cru — a aula 8 do 1o trimestre ja transformou esse numero em valor real. int bruto = analogRead(PIN_SENSOR_ANALOGICO); // Transformacao Linear: leva 0..4095 para 0..100. E a MESMA ideia do map() // do 1o trimestre, so que escrita a mao para ficar visivel aqui. float percentual = (bruto / 4095.0f) * 100.0f; // O JSON e montado NA MAO, com string e concatenacao. Nao existe biblioteca // de JSON no core do ESP32: e isso que o dia 3 vai organizar de vez. // Repare no \\" : cada aspa do JSON e uma aspa que o C++ tambem precisa // usar, entao ela e escapada com a barra. String json = "{\"id_placa\":\"" + String(ID_PLANCA) + "\"" + ",\"temp_c_simulado\":" + String(percentual, 1) + ",\"dt_leitura\":\"pendente\"}"; Serial.print("leitura analogica bruta: "); Serial.print(bruto); Serial.print(" | percentual: "); Serial.print(percentual, 1); Serial.println(" %"); Serial.print("JSON que a placa vai mandar no dia 3: "); Serial.println(json); Serial.println("-------------------------------------------------"); delay(INTERVALO_MS); }
Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia01 aula2.
