MIT App Inventor com Arduino via Bluetooth: crie um app Android para o HC-05
Controle o Arduino por Bluetooth com o MIT App Inventor: ligação do HC-05, ListPicker, blocos de botão e slider, LED, relé, servo, dados de sensor, APK e correção para o Android 12.
Controlar o Arduino por Bluetooth com o App Inventor é o jeito mais rápido de criar seu próprio app Android sem escrever uma única linha de Java ou Kotlin. No MIT App Inventor, gratuito e que roda no navegador, você arrasta botões, sliders e rótulos de texto para a tela e define o que eles fazem com blocos que se encaixam como peças de quebra-cabeça. O celular se conecta a um módulo Bluetooth HC-05 ligado ao Arduino; cada toque no app viaja até o Arduino como um comando de texto curto, e o Arduino envia as leituras dos sensores de volta ao celular do mesmo jeito.
Ao fim deste guia você terá um app de controle remoto que funciona de verdade: um ListPicker que se conecta ao HC-05 a partir de uma lista, botões que ligam e desligam um LED e um relé, dois sliders que ajustam o brilho do LED e o ângulo do servo, e rótulos que mostram a temperatura e a umidade ao vivo de um DHT11. Passo a passo, você vai ver o código do Arduino, cada bloco do App Inventor, a geração do APK e a correção para o problema de permissão de Bluetooth que surgiu no Android 12. Se você nunca usou o HC-05, dê antes uma olhada rápida no guia módulo Bluetooth HC-05.

O que é o App Inventor e como ele conversa com o Arduino?
O MIT App Inventor é um ambiente gratuito de desenvolvimento de apps criado pelo Massachusetts Institute of Technology (MIT); você entra com sua conta Google e usa tudo no navegador. Ele tem duas telas: no Designer você monta o app arrastando componentes, e no editor de Blocks (blocos) você escreve regras como “quando o botão for clicado, faça isto” com blocos coloridos. Para testar o app sem instalar, você usa o app MIT AI2 Companion da Play Store; quando terminar, um único clique gera um arquivo APK instalável.
No lado do Bluetooth, quem faz o trabalho é o componente BluetoothClient. Ele usa o SPP (Serial Port Profile) do Bluetooth clássico, que é exatamente o que o HC-05 fala. O resultado é um cabo serial invisível entre o celular e o Arduino: quando o app envia “L1” com o bloco SendText, o Arduino lê esses dois caracteres com bt.read(), como se você os tivesse digitado no Monitor Serial. Tudo o que o Arduino envia com bt.print() chega ao bloco ReceiveText do celular. Não é preciso nenhuma biblioteca específica de Bluetooth no Arduino.
| Camada | Função | Neste projeto |
|---|---|---|
| Interface (Designer) | Componentes mostrados na tela | ListPicker, 5 Buttons, 2 Sliders, Labels |
| Lógica (Blocks) | Blocos que reagem a eventos | Eventos Click, PositionChanged e Timer |
| BluetoothClient | Conexão SPP, envio e recebimento de dados | Connect, SendText, ReceiveText |
| HC-05 | Ponte entre Bluetooth e UART | 9600 baud, PIN 1234 |
| Arduino | Interpreta comandos, aciona saídas, lê o sensor | LED, relé, servo, DHT11 |
Do que você vai precisar
| Peça | Qtd. | Observação |
|---|---|---|
| Arduino Uno (ou Nano) | 1 | Mesmo código e mesmos pinos nos dois |
| Módulo Bluetooth HC-05 ou HC-06 | 1 | Bluetooth clássico; o HM-10 (BLE) não funciona deste jeito |
| Resistores de 1 kΩ e 2 kΩ | 1 de cada | Divisor de tensão para o pino RXD do HC-05 (2,2 kΩ também serve) |
| LED e resistor de 220 Ω | 1 de cada | LED com brilho ajustável (D5, PWM) |
| Módulo relé 5 V | 1 | Para ligar cargas como uma lâmpada ou uma ventoinha |
| Servo motor SG90 | 1 | Controle de 0–180° com um slider |
| Sensor de temperatura e umidade DHT11 | 1 | Dados para mostrar no celular |
| Celular Android | 1 | O HC-05 não funciona com iPhone |
| Protoboard e jumpers | — | Um GND comum é obrigatório |





Ligação: HC-05, LED, relé, servo e DHT11
A placa do HC-05 tem um regulador, então o pino VCC aceita 5 V, mas os pinos de dados usam lógica de 3,3 V. O Arduino lê sem problemas o sinal de 3,3 V que sai do pino TXD do módulo; já o sinal TX de 5 V do Arduino deve chegar ao pino RXD por meio de um divisor de tensão: D11 → 1 kΩ → RXD, RXD → 2 kΩ → GND. Isso coloca 5 × 2 / 3 ≈ 3,3 V no RXD. Ligamos o HC-05 em D10 e D11 com SoftwareSerial em vez dos pinos seriais de hardware do Uno (0 e 1), para que você possa carregar o código com o módulo conectado e ainda usar o Monitor Serial para depurar. Você pode conferir o resistor do LED com a calculadora de resistor para LED.
| Ligação | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC do HC-05 | 5V | Não precisa (Bluetooth embutido) | VBUS (5 V) |
| GND do HC-05 | GND | — | GND |
| TXD do HC-05 | D10 (RX da SoftwareSerial) | — | GP1 (RX da UART0) |
| RXD do HC-05 | D11, por divisor de 1 kΩ / 2 kΩ | — | GP0 (TX da UART0), sem divisor |
| LED (+ 220 Ω) | D5 (PWM) | GPIO 2 (LED da placa) | GP15 |
| IN do módulo relé | D7 | GPIO 26 | GP14 |
| Sinal do servo (laranja) | D9 | — (dá para adicionar com ESP32Servo) | GP13 |
| Dados do DHT11 | D2 | GPIO 4 | GP16 |
Protocolo de comunicação: o que o celular vai enviar?
Antes de escrever o código, decida qual “língua” o celular e o Arduino vão falar. Neste projeto, cada comando é uma letra, seguida de um número e, por fim, de um caractere de nova linha (\n, ASCII 10). A nova linha é muito importante: os dados Bluetooth podem chegar em pedaços, e quando “S1” e “35” chegam separados, o Arduino espera a nova linha e trata “S135” como um só comando. Como os comandos são texto legível, você também pode testá-los à mão com qualquer app de terminal Bluetooth.
| Comando | Direção | Significado |
|---|---|---|
| L1 / L0 | Celular → Arduino | Acender o LED no máximo / apagar |
| P0 … P255 | Celular → Arduino | Brilho do LED (valor do analogWrite) |
| R1 / R0 | Celular → Arduino | Acionar / soltar o relé |
| S0 … S180 | Celular → Arduino | Ângulo do servo (graus) |
| 24.5;41 | Arduino → celular | Temperatura (°C) e umidade (%), separadas por ponto e vírgula |
Em vez de texto, o App Inventor também pode enviar um único byte (0–255) com o bloco Send1ByteNumber, que o Arduino lê direto como número. É curto e rápido, mas só serve para um slider: se dois sliders e quatro botões enviarem bytes, o Arduino não consegue saber se um 90 que chegou é um ângulo do servo ou um nível de brilho. O formato letra + número + nova linha custa alguns bytes a mais, mas a 9600 baud passam cerca de 960 caracteres por segundo, então você nunca vai notar a diferença.
O código do Arduino
As bibliotecas Servo e SoftwareSerial vêm com o Arduino IDE. Para o DHT11, instale a DHT sensor library (Adafruit) pelo Gerenciador de Bibliotecas; quando ele perguntar sobre as dependências, escolha Instalar tudo para adicionar também a Adafruit Unified Sensor. Como não usamos os pinos 0 e 1, não é preciso desconectar o HC-05 para carregar o código.
1#include <SoftwareSerial.h>
2#include <Servo.h>
3#include <DHT.h>
4
5SoftwareSerial bt(10, 11); // RX, TX → HC-05 TXD, RXD (bölücü üzerinden / via divider)
6Servo servo;
7DHT dht(2, DHT11); // DHT11 veri pini D2 / data pin D2
8
9const int LED_PIN = 5; // PWM destekli pin / PWM-capable pin
10const int ROLE_PIN = 7;
11const int SERVO_PIN = 9;
12const int ROLE_ACIK = LOW; // çoğu röle modülü LOW ile çeker / most relay modules are active LOW
13const int ROLE_KAPALI = HIGH;
14
15String komut = ""; // gelen satırın tamponu / buffer for the incoming line
16unsigned long sonGonderim = 0;
17
18void komutIsle(String k) {
19 k.trim(); // boşluk ve \r temizle / strip spaces and \r
20 if (k.length() == 0) return;
21 Serial.println(k); // hata ayıklama / debugging
22 char tur = k.charAt(0); // komut harfi / command letter
23 int deger = k.substring(1).toInt(); // harften sonraki sayı / number after the letter
24
25 switch (tur) {
26 case 'L': // L1 / L0 → LED aç, kapat / LED on, off
27 analogWrite(LED_PIN, deger == 1 ? 255 : 0);
28 break;
29 case 'P': // P0..P255 → parlaklık / brightness
30 analogWrite(LED_PIN, constrain(deger, 0, 255));
31 break;
32 case 'R': // R1 / R0 → röle / relay
33 digitalWrite(ROLE_PIN, deger == 1 ? ROLE_ACIK : ROLE_KAPALI);
34 break;
35 case 'S': // S0..S180 → servo açısı / servo angle
36 servo.write(constrain(deger, 0, 180));
37 break;
38 }
39}
40
41void setup() {
42 pinMode(LED_PIN, OUTPUT);
43 pinMode(ROLE_PIN, OUTPUT);
44 digitalWrite(ROLE_PIN, ROLE_KAPALI); // açılışta röle kapalı / relay off at start
45 servo.attach(SERVO_PIN);
46 servo.write(90); // orta konum / centre position
47 dht.begin();
48 Serial.begin(9600); // Seri Monitör / Serial Monitor
49 bt.begin(9600); // HC-05 veri modu hızı / HC-05 data mode baud rate
50}
51
52void loop() {
53 // 1) Telefondan gelen komutları satır satır oku / read commands from the phone line by line
54 while (bt.available()) {
55 char c = bt.read();
56 if (c == '\n') { // satır bitti / line complete
57 komutIsle(komut);
58 komut = "";
59 } else if (komut.length() < 16) { // aşırı uzun satırı kes / cap overlong lines
60 komut += c;
61 }
62 }
63
64 // 2) 2 saniyede bir sensör verisini gönder / send sensor data every 2 seconds
65 if (millis() - sonGonderim >= 2000) {
66 sonGonderim = millis();
67 float t = dht.readTemperature();
68 float h = dht.readHumidity();
69 if (!isnan(t) && !isnan(h)) {
70 bt.print(t, 1); // örnek / example: 24.5
71 bt.print(';');
72 bt.print(h, 0); // örnek / example: 41
73 bt.print('\n'); // println değil, \r gitmesin / not println, no \r
74 }
75 }
76}- Buffer de linha: o loop() acrescenta cada caractere que chega à variável komut (comando); quando chega \n, ele passa a linha para komutIsle() e limpa o buffer. Tudo o que passar de 16 caracteres é descartado, então dados lixo não conseguem encher a memória.
- trim(): alguns apps de terminal também acrescentam \r no fim da linha; o trim() remove esse caractere junto com os espaços, então “L1\r” continua sendo reconhecido como “L1”.
- Letra e número: charAt(0) dá a letra do comando e substring(1).toInt() o número depois dela. constrain() limita o valor a uma faixa segura; mesmo que o celular envie 300 por engano, o servo nunca é empurrado além de 180°.
- Envio sem bloqueio: o sensor é lido a cada 2 segundos usando millis(). Como não há delay(), os comandos que chegam nesse meio-tempo são tratados sem atraso; veja millis ou delay para os detalhes.
- print('\n') em vez de println: o println() termina a linha com \r\n. O App Inventor só usa \n como delimitador, então o \r grudaria no fim do valor de umidade; enviar só \n mantém limpa a interpretação no celular.
- Relé ativo em LOW: a maioria dos módulos relé liga quando o IN vai para LOW. Se o seu liga com HIGH, basta trocar os valores das constantes ROLE_ACIK (ligado) e ROLE_KAPALI (desligado).
Desenhando a interface no App Inventor (Designer)
- Acesse ai2.appinventor.mit.edu no navegador, entre com sua conta Google e crie um projeto chamado BlueGraysRemote em Projects > Start new project (Projetos > Iniciar novo projeto).
- No grupo Layout da Palette (paleta), arraste um HorizontalArrangement; coloque dentro dele um ListPicker (Text: Conectar), um Button (Text: Desconectar) e um Label (Text: Não conectado).
- Adicione mais dois HorizontalArrangements abaixo: coloque os botões Ligar LED e Desligar LED no primeiro, e Ligar relé e Desligar relé no segundo. Definir a Width (largura) dos botões como Fill parent deixa mais fácil tocá-los.
- Adicione dois Sliders: para o brilho, MinValue 0, MaxValue 255, ThumbPosition 0; para o servo, MinValue 0, MaxValue 180, ThumbPosition 90. Coloque acima de cada um um Label dizendo o que ele faz.
- Adicione mais dois Labels para temperatura e umidade e aumente o FontSize deles para cerca de 20.
- No grupo Connectivity (conectividade), adicione um BluetoothClient; no painel Properties, defina DelimiterByte como 10 e marque NoLocationNeeded.
- No grupo Sensors (sensores), adicione um Clock; defina TimerInterval como 200 e desmarque TimerEnabled. Vamos ligar o temporizador com blocos quando a conexão estiver feita.
| Componente | Nome (Rename) | Configuração principal |
|---|---|---|
| ListPicker | LP_Connect | Text: Conectar |
| Button × 5 | BTN_Disconnect, BTN_LedOn, BTN_LedOff, BTN_RelayOn, BTN_RelayOff | Width: Fill parent (preencher) |
| Slider | SLD_Brightness | MinValue 0, MaxValue 255 |
| Slider | SLD_Servo | MinValue 0, MaxValue 180, ThumbPosition 90 |
| Label | LBL_Status, LBL_Temp, LBL_Hum | FontSize 20 (para os valores) |
| BluetoothClient | BluetoothClient1 | DelimiterByte 10, NoLocationNeeded marcado |
| Clock | Clock1 | TimerInterval 200, TimerEnabled desmarcado |
Conectando ao HC-05 com um ListPicker
Primeiro pareie o HC-05 nas configurações de Bluetooth do celular (o PIN normalmente é 1234, senão 0000). Esse passo é essencial: BluetoothClient1.AddressesAndNames só lista dispositivos pareados e não procura dispositivos novos por perto. Cada item é um texto com o endereço MAC primeiro e o nome do dispositivo depois, como “98:D3:31:F5:2A:10 HC-05”. Você pode passar o texto inteiro ao bloco Connect; o App Inventor descarta sozinho tudo o que vem depois do primeiro espaço. O bloco retorna true se a conexão der certo e false se falhar.
- No evento when LP_Connect.BeforePicking, coloque set LP_Connect.Elements to BluetoothClient1.AddressesAndNames. A lista é preenchida de novo com os dispositivos pareados toda vez que se abre.
- No evento when LP_Connect.AfterPicking, adicione um bloco if e encaixe call BluetoothClient1.Connect address: LP_Connect.Selection na condição dele.
- Na parte then do if, defina LBL_Status.Text como “Conectado” e comece a ler os dados com set Clock1.TimerEnabled to true; na parte else, escreva “Falha na conexão”.
- No evento when BTN_Disconnect.Click, primeiro set Clock1.TimerEnabled to false, depois call BluetoothClient1.Disconnect e, por fim, defina LBL_Status.Text como “Não conectado”. Parar o temporizador primeiro evita uma leitura e uma mensagem de erro depois que a conexão é fechada.
Enviando dados: botões e sliders
Enviar exige um único bloco: call BluetoothClient1.SendText text. Quando você digita “L1\n” em um bloco de texto, o App Inventor transforma o \n final em um caractere de nova linha. Enviar sem estar conectado causa o erro 515, então envolver cada envio em uma verificação if BluetoothClient1.IsConnected é um bom hábito. Para um slider, você junta o número à letra e à nova linha com um bloco join: join("S", round(thumbPosition), "\n").
| Evento | Texto enviado | Resultado no Arduino |
|---|---|---|
| BTN_LedOn.Click | L1\n | O LED acende com brilho máximo |
| BTN_LedOff.Click | L0\n | O LED apaga |
| BTN_RelayOn.Click | R1\n | O relé clica e a lâmpada ligada nele acende |
| BTN_RelayOff.Click | R0\n | O relé solta |
| SLD_Brightness.PositionChanged | join("P", round(thumbPosition), "\n") (letra + valor + nova linha) | analogWrite(5, valor) |
| SLD_Servo.PositionChanged | join("S", round(thumbPosition), "\n") (letra + ângulo + nova linha) | servo.write(ângulo) |
Enquanto você arrasta um slider com o dedo, o evento PositionChanged dispara dezenas de vezes por segundo e thumbPosition retorna valores fracionários como 87.4. Se você enviar a cada evento, o mesmo ângulo sai várias vezes, o buffer do HC-05 enche e o servo se move aos trancos e atrasado. A solução é simples: crie uma variável chamada global lastAngle; envie só se o valor arredondado com round(thumbPosition) for diferente dela e depois atualize a variável. Assim, os dados só saem quando o ângulo realmente muda. Aplique o mesmo truque ao slider de brilho com global lastBrightness.
Recebendo dados: mostrando os valores do sensor no celular
O Arduino envia uma linha “24.5;41” a cada 2 segundos. O App Inventor não tem um evento que dispara sozinho quando os dados chegam, então usamos o componente Clock para verificar “chegou dado novo?” em intervalos regulares. Como definimos DelimiterByte como 10 no Designer, passar -1 como numberOfBytes ao bloco ReceiveText faz ele ler até o caractere de nova linha e retornar a linha sem o \n. Um temporizador de 200 ms verifica com muito mais frequência do que os dados chegam (a cada 2000 ms), então nada se acumula no buffer.
- Coloque uma condição if BluetoothClient1.IsConnected dentro de when Clock1.Timer.
- Adicione uma segunda condição dentro dela: call BluetoothClient1.BytesAvailableToReceive > 0.
- Guarde a linha que chegou em uma variável local com initialize local line to call BluetoothClient1.ReceiveText numberOfBytes: -1.
- Transforme a linha em uma lista com o bloco split text line at ";": “24.5;41” vira a lista de dois itens [24.5, 41]. Guarde o resultado em uma variável local chamada parts.
- if length of list parts = 2, defina LBL_Temp.Text como join("Temperatura: ", select list item parts index 1, " °C") e LBL_Hum.Text como join("Umidade: ", select list item parts index 2, " %"). Verificar a quantidade de itens impede que linhas recebidas pela metade ou corrompidas causem erros.
O problema de permissão de Bluetooth no Android 12 e posteriores
As permissões de Bluetooth mudaram com o Android 12 (API 31). A antiga permissão BLUETOOTH, concedida automaticamente na instalação, foi substituída por BLUETOOTH_CONNECT e BLUETOOTH_SCAN, que o usuário precisa aprovar com o app em execução; nas configurações do celular, elas aparecem como a permissão Dispositivos por perto. Se ela não tiver sido concedida, o App Inventor abre a janela de permissão na primeira tentativa, mas interrompe o bloco que estava rodando. O resultado é um conjunto de sintomas conhecidos: a lista abre vazia na primeira vez que você toca em Conectar, aparece a mensagem “Error 908: The permission ... has been denied” (citando BLUETOOTH_CONNECT ou BLUETOOTH_SCAN) ou, se o usuário recusou uma vez, o app nunca mais pergunta.
- No evento when Screen1.Initialize, verifique a versão do Android: se select list item (split Screen1.PlatformVersion at ".") index 1 ≥ 12, passe ao call Screen1.AskForPermission o valor BluetoothConnect da lista Permission. Essas permissões não existem no Android 11 e anteriores, e pedi-las lá também gera o erro 908, então a verificação da versão é importante.
- No evento when Screen1.PermissionGranted, se a permissão concedida for BLUETOOTH_CONNECT (contains text: permissionName, piece: "CONNECT"), peça em seguida a BluetoothScan. O AskForPermission só aceita uma permissão por vez, então você as encadeia assim.
- No evento when Screen1.PermissionDenied, use um Notifier para mostrar uma mensagem com o caminho “Configurações > Apps > BlueGraysRemote > Permissões > Dispositivos por perto > Permitir”. Tratar esse evento mostra ao usuário uma explicação clara em vez de uma caixa de erro crua.
- Mantenha NoLocationNeeded marcado no BluetoothClient, no Designer; ele informa ao Android que o app não usa o Bluetooth para descobrir a localização do usuário.
- Teste sempre também com o APK: as permissões do AI2 Companion pertencem ao app Companion, enquanto o seu APK pede as próprias permissões depois de instalado.
| Erro | Significado | Solução |
|---|---|---|
| Error 507: Unable to connect. Is the device turned on? (não foi possível conectar) | Não conseguiu se conectar ao módulo | Confira a alimentação do HC-05, se ele não está conectado a outro celular e o pareamento; se ainda falhar, desmarque Secure no BluetoothClient e tente de novo |
| Error 515: Not connected to a Bluetooth device. (sem conexão) | Envio ou leitura sem conexão | Envolva os blocos de envio e do Clock em uma verificação IsConnected |
| Error 516: Unable to write: Broken pipe (conexão interrompida) | A conexão caiu no meio (falta de energia, fora do alcance) | Pare o Clock, chame Disconnect e reconecte; dê ao servo e ao relé uma alimentação própria |
| Error 908: The permission … has been denied (permissão negada) | Sem a permissão Dispositivos por perto no Android 12+ | Peça-a com blocos ou conceda-a manualmente nas Configurações |
| A lista abre vazia | Módulo não pareado, ou a permissão acabou de ser pedida | Pareie o HC-05 nas configurações do celular; depois de conceder a permissão, toque em Conectar de novo |
Testando o app e gerando o APK
Você não precisa instalar um APK a cada mudança enquanto desenvolve. Instale o app MIT AI2 Companion da Play Store; com o computador e o celular na mesma rede Wi-Fi, escolha Connect > AI Companion no App Inventor e leia o QR code ou digite o código de 6 caracteres que aparece na tela. Cada mudança que você faz no Designer ou nos blocos aparece na hora no celular. Quando o app estiver pronto, gere o arquivo instalável assim:
- Selecione Screen1 no Designer e preencha AppName (o nome que aparece no celular), Icon (um PNG quadrado), VersionCode e VersionName.
- No menu de cima, escolha Build > Android App (.apk). A geração normalmente leva um ou dois minutos.
- Quando terminar, aparece uma janela com um QR code e um botão de download. Você pode ler o QR code com o celular para baixar o arquivo direto ou salvar o APK no computador e copiá-lo para o celular pela USB. O link do QR só vale por pouco tempo (cerca de 2 horas).
- Quando você abrir o APK no celular, o Android vai pedir para permitir que o navegador ou o gerenciador de arquivos instale apps desconhecidos. Se o Play Protect mostrar um aviso, você pode continuar com a opção de instalar mesmo assim nos detalhes; esse aviso é comum para apps de fora da Play Store com uma assinatura desconhecida.
- Na primeira vez que abrir o app, aprove a permissão Dispositivos por perto, toque em Conectar e escolha o HC-05 na lista.
O mesmo app com um ESP32, sem precisar de HC-05

O ESP32 clássico tem Bluetooth clássico e BLE embutidos. A biblioteca BluetoothSerial apresenta o ESP32 ao celular como um dispositivo SPP, então você pode usar o app que acabou de criar sem mudar nada. Pareie o dispositivo chamado “BlueGrays-ESP32” nas configurações de Bluetooth do celular, escolha-o na lista do app e pronto. Atenção: o ESP32-S3, o ESP32-C3 e o ESP32-C6 só suportam BLE; eles não têm BluetoothSerial, e no lado do App Inventor você precisa da extensão BluetoothLE. Para trabalhar com BLE, veja o guia Bluetooth e BLE no ESP32.
1#include "BluetoothSerial.h"
2#include <DHT.h>
3
4BluetoothSerial SerialBT;
5DHT dht(4, DHT11); // DHT11 veri pini GPIO 4 / data pin GPIO 4
6
7const int LED_PIN = 2; // çoğu DevKit'te kart üstü LED / onboard LED on most DevKits
8const int ROLE_PIN = 26;
9
10String komut = "";
11unsigned long sonGonderim = 0;
12
13void setup() {
14 pinMode(ROLE_PIN, OUTPUT);
15 digitalWrite(ROLE_PIN, HIGH); // aktif LOW röle kapalı / active-LOW relay off
16 dht.begin();
17 SerialBT.begin("BlueGrays-ESP32"); // telefonda görünecek ad / name shown on the phone
18}
19
20void loop() {
21 while (SerialBT.available()) {
22 char c = SerialBT.read();
23 if (c == '\n') {
24 komut.trim();
25 int deger = komut.substring(1).toInt();
26 if (komut.startsWith("L")) {
27 analogWrite(LED_PIN, deger == 1 ? 255 : 0); // LED aç, kapat / LED on, off
28 } else if (komut.startsWith("P")) {
29 analogWrite(LED_PIN, constrain(deger, 0, 255)); // parlaklık / brightness
30 } else if (komut.startsWith("R")) {
31 digitalWrite(ROLE_PIN, deger == 1 ? LOW : HIGH); // röle / relay
32 }
33 komut = "";
34 } else if (komut.length() < 16) {
35 komut += c;
36 }
37 }
38
39 if (millis() - sonGonderim >= 2000) { // Uno koduyla aynı biçim / same format as the Uno
40 sonGonderim = millis();
41 float t = dht.readTemperature();
42 float h = dht.readHumidity();
43 if (!isnan(t) && !isnan(h)) {
44 SerialBT.print(t, 1);
45 SerialBT.print(';');
46 SerialBT.print(h, 0);
47 SerialBT.print('\n');
48 }
49 }
50}O código entende os mesmos comandos da versão do Uno e envia os dados no mesmo formato “temperatura;umidade”; ele ignora os comandos S porque não há servo ligado. A biblioteca Servo do Arduino não funciona no ESP32; para adicionar um servo, instale a biblioteca ESP32Servo e use as mesmas chamadas attach() e write() em, por exemplo, o GPIO 13. Alimente o módulo relé pelo pino VIN (5 V); se a entrada IN dele não acionar de forma confiável com o sinal de 3,3 V do ESP32, escolha um módulo relé compatível com 3,3 V. Para os detalhes, veja O que é um módulo relé?
Com o Raspberry Pi Pico e MicroPython

O Raspberry Pi Pico padrão não tem Bluetooth, então você liga o HC-05 nos pinos da UART0 (GP0 e GP1). Como os pinos do Pico trabalham em 3,3 V, desta vez não é preciso divisor de tensão; alimente o módulo pelo pino VBUS, que traz os 5 V da USB. O Pico W e o Pico 2 W têm Bluetooth embutido, mas o MicroPython só suporta BLE neles; para conversar com o BluetoothClient clássico do App Inventor, o HC-05 continua sendo o caminho mais fácil. O código abaixo entende os mesmos comandos, aciona o servo com PWM de 50 Hz e envia os dados no mesmo formato; o módulo dht já vem embutido no firmware MicroPython do Pico.
1from machine import UART, Pin, PWM
2import dht
3import time
4
5bt = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1)) # GP0 → HC-05 RXD, GP1 ← HC-05 TXD
6led = PWM(Pin(15))
7led.freq(1000)
8role = Pin(14, Pin.OUT, value=1) # aktif LOW röle kapalı / active-LOW relay off
9servo = PWM(Pin(13))
10servo.freq(50) # servo 50 Hz ister / servos need 50 Hz
11sensor = dht.DHT11(Pin(16))
12
13def komut_isle(k):
14 if not k:
15 return
16 tur, deger = k[0], int(k[1:] or 0) # harf + sayı / letter + number
17 if tur == 'L':
18 led.duty_u16(65535 if deger == 1 else 0)
19 elif tur == 'P':
20 led.duty_u16(min(max(deger, 0), 255) * 257) # 0-255 → 0-65535
21 elif tur == 'R':
22 role.value(0 if deger == 1 else 1)
23 elif tur == 'S':
24 aci = min(max(deger, 0), 180)
25 servo.duty_ns(544000 + aci * 1856000 // 180) # 544-2400 µs darbe / pulse
26
27tampon = b''
28son = time.ticks_ms()
29while True:
30 if bt.any():
31 tampon += bt.read()
32 while b'\n' in tampon: # tam satır geldi mi? / full line received?
33 satir, tampon = tampon.split(b'\n', 1)
34 try:
35 komut_isle(satir.decode().strip())
36 except ValueError:
37 pass # bozuk komutu atla / skip malformed command
38 if len(tampon) > 32:
39 tampon = b'' # satır sonu hiç gelmedi / no newline arrived
40 if time.ticks_diff(time.ticks_ms(), son) >= 2000:
41 son = time.ticks_ms()
42 try:
43 sensor.measure()
44 bt.write('{:.1f};{}\n'.format(sensor.temperature(), sensor.humidity()))
45 except OSError:
46 pass # sensör okunamadı / sensor read failed
47 time.sleep_ms(10)Ideias de projetos com App Inventor e Arduino
- Carrinho controlado por Bluetooth: associe os botões de direção às letras F, B, L e R e acione os motores com um L298N; todos os passos estão no guia carrinho robô com Arduino.
- Painel de casa inteligente: controle uma lâmpada, uma tomada e uma ventoinha em uma só tela com um módulo relé de quatro canais; para ideias, veja o projeto de casa inteligente com Arduino.
- Luminária com seletor de cor: ajuste separadamente os valores de vermelho, verde e azul de um LED RGB com três sliders.
- Braço robótico: adicione um slider por junta e comande os servo motores pelo celular.
- Monitor de estufa: mostre no celular as leituras do DHT11 e do sensor de umidade do solo e adicione um botão que liga a bomba quando o solo secar.
- Controle por voz: use o componente SpeechRecognizer do App Inventor para criar um app que envia L1 quando você diz “acender luz”.
Erros comuns e como resolvê-los
- O app conecta, mas o LED não acende: os fios TXD e RXD do HC-05 estão trocados, ou os pinos da SoftwareSerial no código não correspondem ao circuito. O TXD do módulo tem que ir no RX do Arduino (D10).
- Os comandos são ignorados: falta o \n no bloco de texto; o Arduino espera a nova linha. Confira se os comandos que chegam aparecem no Monitor Serial.
- Caracteres embaralhados: o baud rate do HC-05 é diferente do bt.begin(). O módulo sai de fábrica com 9600 baud; se foi mudado com comandos AT, ajuste o código para combinar.
- O servo treme ou a conexão cai: o servo está derrubando o barramento de 5 V do Arduino; alimente-o por uma fonte de 5 V separada e una os GNDs. Uma tremida leve também pode vir da SoftwareSerial bloqueando as interrupções por um instante enquanto recebe; para resolver de vez, use um Arduino Mega ou um ESP32, que têm portas seriais de hardware sobrando. De vez em quando o servo também pode pular por um instante para um ângulo errado: a biblioteca DHT desliga as interrupções por cerca de 5 ms a cada leitura, e um comando que chegue bem nessa hora pode se corromper; o próximo comando do slider corrige isso.
- Os valores no celular ficam atrasados: o intervalo do Clock é maior que o intervalo de envio do Arduino, então as linhas se acumulam. Mantenha o TimerInterval menor que o intervalo de envio.
- O app trava: o DelimiterByte ainda está em 0, ou o Arduino não termina as linhas com \n; o ReceiveText(-1) espera um delimitador que nunca chega.
- Não funciona no iPhone: o HC-05 usa Bluetooth clássico, que o iOS não permite. Um iPhone precisa de um módulo BLE (HM-10) ou de um ESP32.
Combinar App Inventor e Arduino é o jeito mais divertido de aprender desenvolvimento de apps e eletrônica em um só projeto. Depois de montar a conexão e o protocolo, o mesmo esqueleto pode virar o controle de um carrinho, de uma casa inteligente ou de uma estufa; a única coisa que muda é quais letras os botões enviam. No app BlueGrays você encontra, passo a passo e com esquemas do circuito e código, os projetos de controle de LED por Bluetooth e de carrinho controlado por Bluetooth com L298N.
Perguntas frequentes
Um app Bluetooth do App Inventor funciona no iPhone?
Não com um HC-05 ou HC-06. Esses módulos usam Bluetooth clássico (SPP), e o iOS não deixa apps comuns abrirem esse tipo de conexão serial. Para conversar com um iPhone, você precisa passar para um módulo BLE como o HM-10 ou para um ESP32; para projetos com HC-05, um celular Android é a escolha que dá menos dor de cabeça.
Por que a lista de dispositivos Bluetooth fica vazia no App Inventor?
O AddressesAndNames só lista dispositivos já pareados com o celular, então primeiro pareie o HC-05 nas configurações de Bluetooth usando o PIN 1234. No Android 12 e posteriores, a lista também abre vazia na primeira vez se a permissão Dispositivos por perto não tiver sido concedida; conceda-a e toque em Conectar de novo. Confira também se o Bluetooth está ligado.
Como resolvo o erro 507 do App Inventor?
O erro 507 significa “Unable to connect. Is the device turned on?”: o celular não conseguiu abrir uma conexão com o módulo. Confira se o HC-05 está alimentado, se o LED dele está piscando rápido e se ele não está conectado a outro celular. Se o problema continuar, remova o pareamento e pareie de novo, ou tente desligar a propriedade Secure do BluetoothClient.
Como gero um APK no App Inventor?
Abra o projeto e clique em Build > Android App (.apk) no menu de cima. Quando a geração terminar, leia o QR code com o celular ou baixe o APK no computador e copie-o para o celular. Durante a instalação, você vai precisar permitir a instalação de apps desconhecidos.
O App Inventor consegue se conectar a um ESP32 por Bluetooth?
Sim. Com a biblioteca BluetoothSerial, o ESP32 clássico se comporta como um dispositivo SPP, e o BluetoothClient do App Inventor se conecta a ele do mesmo jeito que a um HC-05. O ESP32-S3 e o ESP32-C3 só suportam BLE, então nessas placas você precisa da extensão BluetoothLE.
Posso publicar meu app do App Inventor no Google Play?
Sim. A opção Android App Bundle (.aab) do menu Build gera um arquivo que você pode enviar ao Google Play; você vai precisar de uma conta de desenvolvedor no Google Play. As atualizações precisam ser assinadas com a mesma chave, então faça backup do seu keystore com Projects > Export keystore.
Projetos para montar depois desta leitura
Ver todos os projetos
LED piscante
O primeiro contato com a eletrônica: acender e apagar um único LED. O Arduino acende e apaga o LED em intervalos curtos, fazendo a luz piscar rapidamente. Você vê da forma mais simples como a placa co

Temperatura e umidade com DHT11
Qual é a temperatura do seu quarto? O ar está úmido ou seco? O sensor DHT11 mede a temperatura e a umidade, e o Arduino mostra os valores em um display LCD de duas linhas, atualizando a cada poucos se

Controle de LED pelo celular via Bluetooth
Um pequeno ensaio para acender e apagar as luzes do seu quarto pelo celular. O módulo Bluetooth repassa ao Arduino as letras enviadas pelo celular; uma letra acende o LED vermelho, outra acende o verd

Carrinho controlado por Bluetooth com L298N
Transforme seu celular em um controle remoto e dirija seu próprio carrinho robô. O Arduino recebe os comandos de um aplicativo de controle remoto no celular por meio de um módulo Bluetooth e aciona as

Uso de servo motor
O braço de um pequeno servo motor gira para três posições em sequência: o início, o meio e o fim do percurso, esperando dois segundos em cada parada. O Arduino informa ao motor em qual ângulo ficar, e

Uso de LED RGB
Três cores se escondem em um único LED: o LED RGB acende primeiro em vermelho, depois em verde e, por fim, em azul. Cada cor fica acesa por meio segundo e apagada por meio segundo antes de o ciclo rec

Acender uma lâmpada de 220V com botão de pressão
Enquanto você mantém o botão pressionado, uma lâmpada de 220 volts fica acesa e apaga assim que você solta. O sinal fraco do Arduino não consegue acender a lâmpada diretamente, então um relé, que é um

Comunicação unidirecional entre 2 Arduinos
Faça dois Arduinos conversarem por um único fio: ao pressionar o botão de um, o LED do outro acende; ao pressionar de novo, ele apaga. A placa transmissora informa que o botão foi pressionado com uma
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

