Robótica com C — Bloco 3: Projetos Práticos
Uma lâmpada (ou qualquer carga elétrica de 110V/220V) que você liga e desliga pelo celular, de qualquer lugar dentro do alcance do Bluetooth. Este projeto combina os dois componentes introduzidos nos projetos anteriores — o relay do Projeto 01 e o módulo Bluetooth HC-05 do Projeto 02 — num sistema de automação residencial completo.
O que este projeto acrescenta: nos Projetos 01 e 02 você aprendeu relay e Bluetooth separadamente. Aqui eles se integram — o celular é o controle remoto do relay. Além disso, o projeto adiciona feedback de estado (o Arduino responde ao celular confirmando o que fez) e um modo timer (desliga automaticamente após X minutos).
Situação semelhante ao Projeto 02: o Wokwi possui o componente relay nativamente (você pode arrastar e ver o circuito completo), mas não simula o Bluetooth HC-05. O rádio e o pareamento com o celular não têm equivalente no simulador.
A estratégia é a mesma do Projeto 02: use o Monitor Serial como substituto do celular para validar toda a lógica, e reserve o HC-05 físico para a etapa de hardware.
O que você consegue simular no Wokwi:
Para simular, use a versão com Serial no lugar de bluetooth — idêntica ao que você fez no Projeto 02. A versão com HC-05 físico está na Seção 5.
Relay no Wokwi: adicione o componente Relay Module no diagrama. Conecte o pino IN ao Arduino. O Wokwi mostra visualmente o contato NO abrindo e fechando ao chamar digitalWrite() — ótimo para entender a lógica invertida antes de usar o hardware físico.
| Componente | Conexão | Observação |
|---|---|---|
| HC-05 VCC/GND | → 5V / GND | — |
| HC-05 TX | → pino 10 (RX software) | Direto — HC-05 envia 3,3V, OK para o Arduino |
| HC-05 RX | → divisor R1/R2 → pino 11 (TX software) | R1=1kΩ, R2=2kΩ — obrigatório! |
| Relay IN | → pino 7 | Lógica invertida: LOW = relay aciona |
| Relay VCC/GND | → 5V / GND | — |
| LED status + 220Ω | pino 8 → R → LED → GND | Indica visualmente se a lâmpada está ligada |
| Lâmpada (ou LED de teste) | Relay COM + NO → carga → neutro | Para testes iniciais: substitua por LED + resistor |
⚠️ Segurança com 110V/220V: para validar o código, use um LED de 5V no lugar da lâmpada real. Só conecte a rede elétrica quando o código estiver totalmente testado, em bancada adequada e com o circuito desenergizado durante a fiação. Nunca mexa nas conexões com tensão aplicada.
Este projeto expande o protocolo do Projeto 02 com novos comandos — incluindo o timer e consulta de estado:
| Comando | Ação | Resposta do Arduino |
|---|---|---|
L |
Liga a lâmpada | LAMP:1 |
D |
Desliga a lâmpada | LAMP:0 |
T |
Toggle — inverte o estado atual | LAMP:1 ou LAMP:0 |
S |
Status — consulta o estado atual sem mudar | LAMP:1 ou LAMP:0 |
Tnnn |
Timer — liga por nnn minutos e desliga | TIMER:nnn |
X |
Cancela o timer ativo | TIMER:OFF |
? |
Lista de comandos | Texto com os comandos |
Por que prefixo chave:valor nas respostas? "LAMP:1" é parseável pelo app — você pode configurar o Serial Bluetooth Terminal para mudar a cor de um botão quando receber "LAMP:1". Texto livre como "Lâmpada ligada!" é legível para humanos mas difícil de processar programaticamente. Adote sempre o formato estruturado em projetos reais.
/* * ===================================================== * Projeto 03 — Lâmpada controlada pelo celular * Relay + Bluetooth HC-05 * ===================================================== * Comandos: L=liga D=desliga T=toggle S=status * T001-T999=timer (minutos) X=cancela timer * ?=ajuda * Respostas: LAMP:0/1 | TIMER:nnn | TIMER:OFF * ===================================================== */ #include <SoftwareSerial.h> // ── Bluetooth ────────────────────────────────────── SoftwareSerial bluetooth(10, 11); // RX=10, TX=11 // ── Pinos ────────────────────────────────────────── const int PINO_RELAY = 7; const int PINO_STATUS = 8; // ── Estado ───────────────────────────────────────── bool lampLigada = false; // ── Timer ────────────────────────────────────────── bool timerAtivo = false; unsigned long timerFim = 0; // millis() em que o timer expira unsigned int timerMinutos = 0; // ════════════════════════════════════════════════════ // Controla relay + LED de status // Relay: LOW = aciona (lógica invertida) // ════════════════════════════════════════════════════ void setLampada(bool ligar) { lampLigada = ligar; digitalWrite(PINO_RELAY, ligar ? LOW : HIGH); digitalWrite(PINO_STATUS, ligar ? HIGH : LOW); bluetooth.print("LAMP:"); bluetooth.println(ligar ? "1" : "0"); Serial.print("LAMP:"); Serial.println(ligar ? "1" : "0"); } // ════════════════════════════════════════════════════ // Ativa o timer: liga a lâmpada por X minutos // ════════════════════════════════════════════════════ void ativarTimer(unsigned int minutos) { timerMinutos = minutos; timerAtivo = true; timerFim = millis() + ((unsigned long)minutos * 60000UL); setLampada(true); bluetooth.print("TIMER:"); bluetooth.println(minutos); Serial.print("TIMER:"); Serial.println(minutos); } void cancelarTimer() { timerAtivo = false; bluetooth.println("TIMER:OFF"); Serial.println("TIMER:OFF"); } // ════════════════════════════════════════════════════ // Verifica se o timer expirou // ════════════════════════════════════════════════════ void verificarTimer() { if (!timerAtivo) return; if (millis() >= timerFim) { timerAtivo = false; setLampada(false); bluetooth.println("TIMER:EXPIROU"); Serial.println("TIMER:EXPIROU"); } } // ════════════════════════════════════════════════════ // Processa comandos recebidos pelo Bluetooth // ════════════════════════════════════════════════════ void processarMensagem(String msg) { if (msg.length() == 0) return; char cmd = msg[0]; switch (cmd) { case 'L': case 'l': cancelarTimer(); // liga manual cancela timer setLampada(true); break; case 'D': case 'd': cancelarTimer(); setLampada(false); break; case 'T': case 't': if (msg.length() > 1) { // "T005" → timer de 5 minutos int minutos = constrain(msg.substring(1).toInt(), 1, 999); ativarTimer(minutos); } else { // "T" sozinho → toggle cancelarTimer(); setLampada(!lampLigada); } break; case 'S': case 's': bluetooth.print("LAMP:"); bluetooth.println(lampLigada ? "1" : "0"); if (timerAtivo) { unsigned long restante = (timerFim - millis()) / 60000UL; bluetooth.print("TIMER:"); bluetooth.println(restante + 1); } break; case 'X': case 'x': cancelarTimer(); break; case '?': bluetooth.println("Cmds: L D T S T001-T999 X ?"); break; case '\n': case '\r': return; default: bluetooth.print("ERR:"); bluetooth.println(cmd); } } // ════════════════════════════════════════════════════ // SETUP e LOOP // ════════════════════════════════════════════════════ void setup() { pinMode(PINO_RELAY, OUTPUT); pinMode(PINO_STATUS, OUTPUT); digitalWrite(PINO_RELAY, HIGH); // garante relay desligado no boot digitalWrite(PINO_STATUS, LOW); Serial.begin(9600); bluetooth.begin(9600); Serial.println("=== Lampada Bluetooth ==="); Serial.println("Aguardando conexao..."); } void loop() { // Verifica timer antes de processar comandos verificarTimer(); // Lê comandos do Bluetooth if (bluetooth.available() > 0) { String msg = bluetooth.readStringUntil('\n'); msg.trim(); processarMensagem(msg); } // Espelho para debug pelo Monitor Serial if (Serial.available() > 0) { bluetooth.write(Serial.read()); } }
=== Lampada Bluetooth ===
Aguardando conexao...
[celular envia "L"] LAMP:1
[celular envia "S"] LAMP:1
[celular envia "T10"] LAMP:1 / TIMER:10
[celular envia "S"] LAMP:1 / TIMER:10
... 10 minutos depois ...
TIMER:EXPIROU
LAMP:0
[celular envia "D"] LAMP:0
Substitua bluetooth por Serial e envie os comandos pelo Monitor Serial. A lógica de relay, timer e estados é 100% idêntica:
// ── Versão WOKWI ───────────────────────────────────
// Remove SoftwareSerial — usa Serial direto
// Envie pelo Monitor Serial: L D T T005 S X ?
// O relay e o LED de status funcionam normalmente
const int PINO_RELAY = 7;
const int PINO_STATUS = 8;
bool lampLigada = false;
bool timerAtivo = false;
unsigned long timerFim = 0;
void setLampada(bool ligar) {
lampLigada = ligar;
digitalWrite(PINO_RELAY, ligar ? LOW : HIGH);
digitalWrite(PINO_STATUS, ligar ? HIGH : LOW);
Serial.print("LAMP:"); Serial.println(ligar ? "1" : "0");
}
void ativarTimer(unsigned int minutos) {
timerAtivo = true;
timerFim = millis() + ((unsigned long)minutos * 60000UL);
setLampada(true);
Serial.print("TIMER:"); Serial.println(minutos);
}
void cancelarTimer() {
timerAtivo = false;
Serial.println("TIMER:OFF");
}
void verificarTimer() {
if (timerAtivo && millis() >= timerFim) {
timerAtivo = false;
setLampada(false);
Serial.println("TIMER:EXPIROU");
}
}
void processarMensagem(String msg) {
if (msg.length() == 0) return;
char cmd = msg[0];
switch (cmd) {
case 'L': case 'l': cancelarTimer(); setLampada(true); break;
case 'D': case 'd': cancelarTimer(); setLampada(false); break;
case 'T': case 't':
if (msg.length() > 1) ativarTimer(constrain(msg.substring(1).toInt(), 1, 999));
else { cancelarTimer(); setLampada(!lampLigada); }
break;
case 'S': case 's': Serial.print("LAMP:"); Serial.println(lampLigada ? "1" : "0"); break;
case 'X': case 'x': cancelarTimer(); break;
case '?': Serial.println("Cmds: L D T S T001-T999 X ?"); break;
case '\n': case '\r': return;
default: Serial.print("ERR:"); Serial.println(cmd);
}
}
void setup() {
pinMode(PINO_RELAY, OUTPUT);
pinMode(PINO_STATUS, OUTPUT);
digitalWrite(PINO_RELAY, HIGH);
Serial.begin(9600);
Serial.println("=== Lampada Wokwi — envie: L D T T005 S X ?");
}
void loop() {
verificarTimer();
if (Serial.available() > 0) {
String msg = Serial.readStringUntil('\n');
msg.trim();
processarMensagem(msg);
}
}
Teste o timer no Wokwi assim: envie T1 (1 minuto). O Wokwi roda em tempo real, então espere 60 segundos — ou reduza temporariamente 60000UL para 10000UL (10 segundos) durante o teste e restaure antes de gravar no hardware físico.
cancelarTimer() é chamado antes de L e D?Se o timer está ativo e o usuário envia L ou D, o comando manual tem prioridade. Sem cancelar o timer, a lâmpada seria apagada pelo timer mesmo depois de o usuário tê-la ligado manualmente — comportamento confuso e indesejado.
unsigned long e o sufixo UL no timer?O cálculo minutos * 60000 pode ultrapassar 32767 (limite de int de 2 bytes no Uno) para valores acima de ~32 minutos. O sufixo UL força o literal para unsigned long (4 bytes, até ~49 dias), evitando overflow silencioso — o mesmo problema de tipos que você viu na Aula 02.
readStringUntil('\n') em vez de read()?O comando de timer tem múltiplos caracteres ("T010"). Com read() você leria um byte por vez e precisaria montar a string manualmente. readStringUntil('\n') coleta tudo até o Enter e entrega a string completa — mais simples e sem risco de ler a metade de um comando.
Evolução do protocolo: o Projeto 02 usava read() com um byte por comando. O Projeto 03 usa readStringUntil() com strings. Ambos os padrões têm uso — read() é mais rápido e consome menos memória, readStringUntil() é mais flexível para comandos com parâmetros. Você vai escolher o mais adequado caso a caso nos projetos seguintes.
Configure os botões de macro no Serial Bluetooth Terminal para controle com um toque:
| Botão | Rótulo | Macro enviada |
|---|---|---|
| M1 | 💡 Ligar | L |
| M2 | 🌑 Desligar | D |
| M3 | 🔄 Toggle | T |
| M4 | ⏱️ 30 min | T030 |
| M5 | ⏱️ 60 min | T060 |
| M6 | ❌ Cancel timer | X |
| M7 | 📊 Status | S |
| Conceito | Aula de origem | Onde aparece neste projeto |
|---|---|---|
| Relay com lógica invertida | Projeto 01 | setLampada() — LOW aciona, HIGH desaciona |
| Bluetooth HC-05 + SoftwareSerial | Projeto 02 | Canal de comunicação — substituível por Serial no Wokwi |
| Protocolo com prefixo | Aula 46 | Comando "T010" → timer de 10 minutos |
Temporizador com millis() |
Aula 41b / Projeto 01 | timerFim + verificarTimer() |
Overflow de int / unsigned long |
Aula 02 | Sufixo UL em 60000UL para evitar overflow |
| Feedback estruturado (chave:valor) | Aula 46 | "LAMP:1", "TIMER:10", "ERR:cmd" |
| Arquitetura em camadas | Aula 47 | setLampada(), verificarTimer(), processarMensagem() |
Teste sua compreensão do projeto de lâmpada com timer: