Guías · 19 min de lectura

App Inventor Arduino Bluetooth: crea tu app Android con HC-05

Controla Arduino por Bluetooth con App Inventor: conexión del HC-05, ListPicker, bloques de botones y deslizadores, LED, relé, servo, sensores, APK, Android 12.

Controlar Arduino por Bluetooth con App Inventor es la forma más rápida de crear tu propia app Android sin escribir ni una línea de Java o Kotlin. En MIT App Inventor, la herramienta gratuita del MIT que funciona en el navegador, arrastras botones, deslizadores y etiquetas de texto a la pantalla y defines qué hacen con bloques que encajan como piezas de un puzle. El móvil se conecta a un módulo Bluetooth HC-05 unido al Arduino; cada toque en la app viaja hasta el Arduino como un comando de texto corto, y el Arduino devuelve sus lecturas de sensores al móvil por el mismo camino.

Al terminar esta guía tendrás una app de control remoto que funciona de verdad: un ListPicker que se conecta al HC-05 desde una lista, botones que encienden y apagan un LED y un relé, dos deslizadores (Slider) para el brillo del LED y el ángulo del servo, y etiquetas que muestran en directo la temperatura y la humedad de un DHT11. Verás paso a paso el código de Arduino, cada bloque de App Inventor, cómo generar el APK y la solución al problema de permisos Bluetooth que llegó con Android 12. Si nunca has usado el HC-05, echa antes un vistazo a la guía del módulo Bluetooth HC-05.

Módulo Bluetooth HC-05
El HC-05 es un módulo Bluetooth clásico (SPP), justo el tipo con el que habla el componente BluetoothClient de App Inventor; crea un enlace serie inalámbrico entre el móvil y el Arduino.

¿Qué es App Inventor y cómo se comunica con Arduino?

MIT App Inventor es un entorno gratuito de desarrollo de apps creado por el Instituto Tecnológico de Massachusetts (MIT); inicias sesión con tu cuenta de Google y trabajas en el navegador. Tiene dos vistas: en el Designer (diseñador) preparas el aspecto de la app arrastrando componentes, y en el editor de Blocks (bloques) escribes reglas como «cuando se pulse el botón, haz esto» con bloques de colores. Para probar la app sin instalarla se usa la app MIT AI2 Companion de Play Store; al terminar, un clic genera un archivo APK instalable.

En la parte Bluetooth, el trabajo lo hace el componente BluetoothClient. Usa el perfil SPP (Serial Port Profile, perfil de puerto serie) del Bluetooth clásico, que es justo el que habla el HC-05. El resultado es un cable serie invisible entre el móvil y el Arduino: cuando la app envía «L1» con el bloque SendText, el Arduino lee esos dos caracteres con bt.read(), como si las hubieras escrito en el Monitor Serie. Todo lo que el Arduino envía con bt.print() llega al bloque ReceiveText del móvil. En el Arduino no hace falta ninguna librería específica de Bluetooth.

CapaFunciónEn este proyecto
Interfaz (Designer)Componentes visibles en pantallaListPicker, 5 Button, 2 Slider, Label
Lógica (Blocks)Bloques que reaccionan a eventosEventos Click, PositionChanged y Timer
BluetoothClientConexión SPP, envío y recepción de datosConnect, SendText, ReceiveText
HC-05Puente entre Bluetooth y UART9600 baudios, PIN 1234
ArduinoInterpreta comandos, maneja salidas, lee el sensorLED, relé, servo, DHT11

Materiales necesarios

MaterialCantidadNota
Arduino Uno (o Nano)1El código y los pines son iguales en ambos
Módulo Bluetooth HC-05 o HC-061Bluetooth clásico; el HM-10 (BLE) no funciona de esta forma
Resistencias de 1 kΩ y 2 kΩ1 de cadaDivisor de tensión para el pin RXD del HC-05 (2,2 kΩ también sirve)
LED y resistencia de 220 Ω1 de cadaLED con brillo regulable (D5, PWM)
Módulo de relé de 5 V1Para encender cargas como una lámpara o un ventilador
Servomotor SG901Control de 0 a 180° con un deslizador
Sensor de temperatura y humedad DHT111Datos que se mostrarán en el móvil
Móvil Android1El HC-05 no funciona con iPhone
Protoboard y cables jumper—Una GND común es obligatoria
Arduino Uno
Arduino Uno
HC-06
HC-06
SG90
SG90
DHT11
DHT11
LED
LED

Circuito: conexión del HC-05, LED, relé, servo y DHT11

La placa del HC-05 lleva un regulador, así que su pin VCC admite 5 V, pero los pines de datos trabajan con lógica de 3,3 V. El Arduino lee sin problemas la señal de 3,3 V que sale del pin TXD del módulo; en cambio, la señal TX de 5 V del Arduino debe llegar al pin RXD a través de un divisor de tensión: D11 → 1 kΩ → RXD, RXD → 2 kΩ → GND. Así llegan a RXD 5 × 2 / 3 ≈ 3,3 V. Conectamos el HC-05 a D10 y D11 con SoftwareSerial en lugar de a los pines serie por hardware del Uno (0 y 1); así puedes subir código con el módulo conectado y seguir usando el Monitor Serie para depurar. Puedes comprobar la resistencia del LED con la calculadora de resistencia para LED.

ConexiónArduino UnoESP32Raspberry Pi Pico
HC-05 VCC5VNo hace falta (Bluetooth integrado)VBUS (5 V)
HC-05 GNDGND—GND
HC-05 TXDD10 (RX de SoftwareSerial)—GP1 (UART0 RX)
HC-05 RXDD11, con divisor 1 kΩ / 2 kΩ—GP0 (UART0 TX), sin divisor
LED (+ 220 Ω)D5 (PWM)GPIO 2 (LED de la placa)GP15
IN del módulo de reléD7GPIO 26GP14
Señal del servo (naranja)D9— (se puede añadir con ESP32Servo)GP13
Datos del DHT11D2GPIO 4GP16
Respeta tres reglas: todas las GND (Arduino, HC-05, relé, servo y la fuente externa, si la hay) deben estar unidas entre sí. No apliques nunca 5 V sin divisor al pin RXD del HC-05; el módulo puede aguantar un tiempo, pero acabar estropeándose. Un servo en movimiento puede consumir durante un instante varios cientos de miliamperios; si el Arduino se alimenta por USB, ese pico hace caer la tensión, el HC-05 pierde la conexión o el Arduino se reinicia. Alimentar el servo y el relé desde una fuente de 5 V aparte resuelve el problema de raíz.

Protocolo de comunicación: ¿qué enviará el móvil?

Antes de escribir código hay que decidir qué «idioma» hablarán el móvil y el Arduino. En este proyecto cada comando es una letra, seguida de un número y, al final, de un carácter de salto de línea (\n, ASCII 10). El salto de línea es clave: los datos Bluetooth pueden llegar por partes, y si «S1» y «35» llegan por separado, el Arduino espera al salto de línea y procesa «S135» como un solo comando. Como los comandos son texto legible, también puedes probarlos a mano con cualquier app de terminal Bluetooth.

ComandoSentidoSignificado
L1 / L0Móvil → ArduinoEncender el LED al máximo / apagarlo
P0 … P255Móvil → ArduinoBrillo del LED (valor de analogWrite)
R1 / R0Móvil → ArduinoActivar / desactivar el relé
S0 … S180Móvil → ArduinoÁngulo del servo (grados)
24.5;41Arduino → móvilTemperatura (°C) y humedad (%), separadas por punto y coma

En lugar de texto, App Inventor también puede enviar un único byte (0–255) con el bloque Send1ByteNumber, que el Arduino lee directamente como número. Es corto y rápido, pero solo sirve para un único deslizador: si dos deslizadores y cuatro botones envían bytes, el Arduino no sabe si un 90 que llega es un ángulo del servo o un brillo. El formato letra + número + salto de línea ocupa unos bytes más, pero a 9600 baudios pasan unos 960 caracteres por segundo, así que no notarás la diferencia.

El código de Arduino

Las librerías Servo y SoftwareSerial vienen con el Arduino IDE. Para el DHT11, instala DHT sensor library (Adafruit) desde el Gestor de Librerías; cuando pregunte por las dependencias, elige Install all para añadir también Adafruit Unified Sensor. Como no usamos los pines 0 y 1, no hace falta desconectar el HC-05 al subir el código.

Arduino (C++)
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}
  • Búfer de línea: loop() añade cada carácter recibido a la variable komut (comando); al llegar \n pasa la línea a komutIsle() y vacía el búfer. Lo que supere los 16 caracteres se descarta, así los datos basura no llenan la memoria.
  • trim(): Algunas apps de terminal añaden también \r al final de la línea; trim() lo elimina junto con los espacios, de modo que «L1\r» se sigue reconociendo como «L1».
  • Letra y número: charAt(0) da la letra del comando y substring(1).toInt() el número que la sigue. constrain() limita el valor a un rango seguro; aunque el móvil envíe 300 por error, el servo nunca se fuerza más allá de 180°.
  • Envío sin bloqueos: El sensor se lee cada 2 segundos con millis(). Como no hay delay(), los comandos que llegan mientras tanto se procesan sin retraso; más detalles en millis() frente a delay().
  • print('\n') en lugar de println: println() termina la línea con \r\n. App Inventor solo usa \n como delimitador, así que el \r quedaría pegado al final del valor de humedad; enviar solo \n mantiene limpio el análisis en el móvil.
  • Relé activo a nivel bajo: La mayoría de los módulos de relé se activan cuando IN pasa a LOW. Si el tuyo se activa con HIGH, basta con intercambiar los valores de las constantes ROLE_ACIK (encendido) y ROLE_KAPALI (apagado).
Antes de pasar a App Inventor, prueba el lado de Arduino por separado: conéctate al HC-05 con una app como Serial Bluetooth Terminal de Play Store, escribe «L1» y envíalo; el LED debería encenderse y L1 aparecer en el Monitor Serie. Si cada 2 segundos van apareciendo líneas como «24.5;41», los datos de vuelta también funcionan. Así, si más adelante algo falla, sabrás enseguida si el error está en la app o en el circuito.

Diseñar la interfaz en App Inventor (Designer)

  1. Entra en ai2.appinventor.mit.edu desde el navegador, inicia sesión con tu cuenta de Google y crea un proyecto llamado BlueGraysRemote con Projects > Start new project.
  2. Arrastra un HorizontalArrangement desde el grupo Layout de la Palette y coloca dentro un ListPicker (Text: Conectar), un Button (Text: Desconectar) y un Label (Text: No conectado).
  3. Añade debajo dos HorizontalArrangement más: en el primero, los botones Encender LED y Apagar LED; en el segundo, Encender relé y Apagar relé. Si pones Width en Fill parent, los botones son más fáciles de pulsar.
  4. Añade dos Slider: para el brillo MinValue 0, MaxValue 255, ThumbPosition 0; para el servo MinValue 0, MaxValue 180, ThumbPosition 90. Pon encima de cada uno un Label que diga para qué sirve.
  5. Añade otros dos Label para la temperatura y la humedad y sube su FontSize a unos 20.
  6. Desde el grupo Connectivity añade un BluetoothClient; en el panel Properties pon DelimiterByte a 10 y marca la casilla NoLocationNeeded.
  7. Desde el grupo Sensors añade un Clock; pon TimerInterval a 200 y desmarca TimerEnabled. Activaremos el temporizador con bloques cuando haya conexión.
ComponenteNombre (Rename)Ajuste importante
ListPickerLP_ConnectText: Conectar
Button × 5BTN_Disconnect, BTN_LedOn, BTN_LedOff, BTN_RelayOn, BTN_RelayOffWidth: Fill parent
SliderSLD_BrightnessMinValue 0, MaxValue 255
SliderSLD_ServoMinValue 0, MaxValue 180, ThumbPosition 90
LabelLBL_Status, LBL_Temp, LBL_HumFontSize 20 (para los valores)
BluetoothClientBluetoothClient1DelimiterByte 10, NoLocationNeeded marcado
ClockClock1TimerInterval 200, TimerEnabled desactivado

Conectarse al HC-05 con un ListPicker

Primero empareja el HC-05 desde los ajustes Bluetooth del móvil (el PIN suele ser 1234; si no, 0000). Este paso es imprescindible: BluetoothClient1.AddressesAndNames solo muestra dispositivos emparejados y no busca otros nuevos cerca. Cada elemento de la lista es un texto con la dirección MAC primero y el nombre del dispositivo después, como «98:D3:31:F5:2A:10 HC-05». Puedes pasar el texto completo al bloque Connect; App Inventor descarta por sí solo lo que va después del primer espacio. El bloque devuelve true si la conexión se establece y false si falla.

  1. En el evento when LP_Connect.BeforePicking, coloca set LP_Connect.Elements to BluetoothClient1.AddressesAndNames. La lista se vuelve a llenar con los dispositivos emparejados cada vez que se abre.
  2. En el evento when LP_Connect.AfterPicking, añade un bloque if y encaja en su condición call BluetoothClient1.Connect address: LP_Connect.Selection.
  3. En la parte then del if, pon LBL_Status.Text a «Conectado» y empieza a leer datos con set Clock1.TimerEnabled to true; en la parte else, escribe «Error de conexión».
  4. En el evento when BTN_Disconnect.Click, primero set Clock1.TimerEnabled to false, luego call BluetoothClient1.Disconnect y, por último, LBL_Status.Text a «No conectado». Parar antes el temporizador evita una lectura y un mensaje de error después de cerrar la conexión.
Cuando se establece la conexión, el LED del HC-05 deja de parpadear rápido y pasa a dar dos destellos cortos cada 2 segundos. Así sabes el estado de la conexión sin mirar la app; si el LED vuelve a parpadear rápido, la conexión se ha perdido.

Enviar datos: botones y deslizadores

Para enviar basta un solo bloque: call BluetoothClient1.SendText text. Cuando escribes «L1\n» en un bloque de texto, App Inventor convierte el \n final en un carácter de salto de línea. Enviar sin conexión provoca el error 515, así que es buena costumbre envolver cada envío en una comprobación if BluetoothClient1.IsConnected. En un deslizador, unes el número a la letra y al salto de línea con un bloque join: join("S", round(thumbPosition), "\n").

Evento (event)Texto enviadoResultado en el Arduino
BTN_LedOn.ClickL1\nEl LED se enciende al máximo
BTN_LedOff.ClickL0\nEl LED se apaga
BTN_RelayOn.ClickR1\nEl relé se activa y la lámpara conectada se enciende
BTN_RelayOff.ClickR0\nEl relé se desactiva
SLD_Brightness.PositionChangedjoin("P", round(thumbPosition), "\n")analogWrite(5, valor)
SLD_Servo.PositionChangedjoin("S", round(thumbPosition), "\n")servo.write(ángulo)

Mientras arrastras un deslizador con el dedo, el evento PositionChanged se dispara decenas de veces por segundo y thumbPosition devuelve valores con decimales como 87,4. Si envías en cada evento, el mismo ángulo sale una y otra vez, el búfer del HC-05 se llena y el servo se mueve a tirones y con retraso. La solución es sencilla: crea una variable global lastAngle; envía solo si el valor redondeado con round(thumbPosition) es distinto de ella y luego actualiza la variable. Así solo se envían datos cuando el ángulo cambia de verdad. Aplica el mismo truco al deslizador de brillo con global lastBrightness.

Recibir datos: mostrar los valores del sensor en el móvil

El Arduino envía una línea «24.5;41» cada 2 segundos. En App Inventor no hay ningún evento que se dispare solo cuando llegan datos, así que usamos el componente Clock para comprobar a intervalos regulares si hay datos nuevos. Como pusimos DelimiterByte a 10 en el Designer, al dar -1 como numberOfBytes al bloque ReceiveText, este lee hasta el salto de línea y devuelve la línea sin el \n. Un temporizador de 200 ms comprueba mucho más a menudo de lo que llegan los datos (cada 2000 ms), así que en el búfer no se acumula nada.

  1. Coloca una condición if BluetoothClient1.IsConnected dentro de when Clock1.Timer.
  2. Añade dentro una segunda condición: call BluetoothClient1.BytesAvailableToReceive > 0.
  3. Guarda la línea recibida en una variable local con initialize local line to call BluetoothClient1.ReceiveText numberOfBytes: -1.
  4. Convierte la línea en una lista con el bloque split text line at ";": «24.5;41» se convierte en la lista de dos elementos [24.5, 41]. Guarda el resultado en una variable local llamada parts.
  5. if length of list parts = 2, pon LBL_Temp.Text a join("Temperatura: ", select list item parts index 1, " °C") y LBL_Hum.Text a join("Humedad: ", select list item parts index 2, " %"). Comprobar el número de elementos evita errores con líneas recibidas a medias o dañadas.
Si pasas -1 a ReceiveText pero olvidas poner DelimiterByte a 10 (el valor por defecto es 0), el bloque espera un byte 0 que nunca llega y la app parece colgada. Y si el Arduino envía su línea con println(), el carácter \r acaba al final del segundo valor; por eso el código envía solo '\n'.
No te quedes en mostrar los valores como texto: puedes poner en rojo LBL_Temp.TextColor cuando la temperatura pase de 30 °C, abrir un aviso con el componente Notifier o dibujar en un Canvas una gráfica de líneas con las últimas lecturas.

El problema de permisos Bluetooth en Android 12 y posteriores

Con Android 12 (API 31) cambiaron los permisos de Bluetooth. El antiguo permiso BLUETOOTH, que se concedía automáticamente al instalar, se sustituyó por BLUETOOTH_CONNECT y BLUETOOTH_SCAN, que el usuario debe aprobar mientras la app está en marcha; en los ajustes del móvil aparecen como el permiso Dispositivos cercanos. Si no se ha concedido, App Inventor abre el diálogo de permiso en el primer intento, pero detiene el bloque que se estaba ejecutando. El resultado son síntomas conocidos: la lista se abre vacía la primera vez que pulsas Conectar, aparece el mensaje «Error 908: The permission ... has been denied» (con BLUETOOTH_CONNECT o BLUETOOTH_SCAN como nombre) o, si el usuario lo rechazó una vez, la app ya no vuelve a preguntar.

  1. En el evento when Screen1.Initialize, comprueba la versión de Android: si select list item (split Screen1.PlatformVersion at ".") index 1 ≥ 12, pasa a call Screen1.AskForPermission el valor BluetoothConnect de la lista Permission. En Android 11 y anteriores estos permisos no existen, y pedirlos allí también provoca el error 908, por eso importa la comprobación de versión.
  2. En el evento when Screen1.PermissionGranted, si el permiso concedido es BLUETOOTH_CONNECT (contains text: permissionName, piece: "CONNECT"), pide a continuación BluetoothScan. AskForPermission solo admite un permiso cada vez, así que hay que encadenarlos de esta forma.
  3. En el evento when Screen1.PermissionDenied, muestra con un Notifier un mensaje con la ruta «Ajustes > Aplicaciones > BlueGraysRemote > Permisos > Dispositivos cercanos > Permitir». Gestionar este evento muestra al usuario una explicación clara en lugar de una ventana de error en inglés.
  4. Deja marcada la casilla NoLocationNeeded del BluetoothClient en el Designer; indica a Android que la app no usa el Bluetooth para averiguar la ubicación.
  5. Prueba siempre también con el APK: los permisos del AI2 Companion pertenecen a la app Companion, y tu APK pide sus propios permisos después de instalarse.
ErrorSignificadoSolución
Error 507: Unable to connect. Is the device turned on?No se pudo conectar con el móduloComprueba la alimentación del HC-05, que no esté conectado a otro móvil y el emparejamiento; si sigue fallando, desmarca Secure en el BluetoothClient y vuelve a probar
Error 515: Not connected to a Bluetooth device.Envío o lectura sin conexiónEnvuelve los bloques de envío y del Clock en una comprobación IsConnected
Error 516: Unable to write: Broken pipeLa conexión se cortó a mitad (sin alimentación, fuera de alcance)Para el Clock, llama a Disconnect y vuelve a conectar; alimenta aparte el servo y el relé
Error 908: The permission … has been deniedFalta el permiso Dispositivos cercanos en Android 12+Pídelo con bloques o concédelo a mano en Ajustes
La lista se abre vacíaMódulo sin emparejar o permiso recién solicitadoEmpareja el HC-05 en los ajustes del móvil y, tras conceder el permiso, vuelve a pulsar Conectar

Probar la app y generar el APK

Durante el desarrollo no hace falta instalar un APK tras cada cambio. Instala la app MIT AI2 Companion desde Play Store; con el ordenador y el móvil en la misma red Wi-Fi, elige Connect > AI Companion en App Inventor y escanea el código QR o escribe el código de 6 caracteres que aparece en pantalla. Cada cambio que hagas en el Designer o en los bloques aparece al instante en el móvil. Cuando la app esté terminada, genera el archivo instalable así:

  1. Selecciona Screen1 en el Designer y rellena AppName (el nombre que se ve en el móvil), Icon (un PNG cuadrado), VersionCode y VersionName.
  2. En el menú superior elige Build > Android App (.apk). La compilación suele tardar uno o dos minutos.
  3. Al terminar se abre una ventana con un código QR y un botón de descarga. Puedes escanear el QR con el móvil para descargar el archivo directamente, o guardar el APK en el ordenador y pasarlo al móvil por cable. El enlace del QR solo es válido durante poco tiempo (unas 2 horas).
  4. Al abrir el APK en el móvil, Android te pide que permitas al navegador o al gestor de archivos instalar aplicaciones desconocidas. Si Play Protect muestra un aviso, puedes seguir con la opción de instalar de todos modos en los detalles; este aviso es habitual en apps de fuera de Play Store con una firma desconocida.
  5. En el primer arranque de la app, acepta el permiso Dispositivos cercanos, pulsa Conectar y elige el HC-05 en la lista.
Haz una copia de tu proyecto con Projects > Export selected project (.aia) to my computer; puedes compartir el archivo .aia con un amigo o pasarlo a otra cuenta. Si quieres publicar la app en Google Play, usa Build > Android App Bundle (.aab) y guarda sin falta tu clave de firma con Projects > Export keystore: las actualizaciones deben firmarse con la misma clave. No olvides subir VersionCode en 1 con cada actualización.

La misma app con un ESP32, sin HC-05

Placa de desarrollo ESP32
El ESP32 clásico lleva Bluetooth Classic integrado; el BluetoothClient de App Inventor se conecta a él igual que a un HC-05.

El ESP32 clásico integra tanto Bluetooth clásico como BLE. La librería BluetoothSerial presenta el ESP32 al móvil como un dispositivo SPP, así que puedes usar la app que acabas de crear sin cambiar nada. Empareja en los ajustes Bluetooth del móvil el dispositivo llamado «BlueGrays-ESP32», elígelo en la lista de la app y listo. Ojo: el ESP32-S3, el ESP32-C3 y el ESP32-C6 solo admiten BLE; no tienen BluetoothSerial y en App Inventor necesitas la extensión BluetoothLE. Para trabajar con BLE, consulta la guía ESP32 Bluetooth y BLE.

Arduino (C++)
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}

El código entiende los mismos comandos que la versión para Uno y envía los datos con el mismo formato «temperatura;humedad»; ignora los comandos S porque no hay ningún servo conectado. La librería Servo de Arduino no funciona en el ESP32; para añadir un servo, instala la librería ESP32Servo y usa las mismas llamadas attach() y write(), por ejemplo en el GPIO 13. Alimenta el módulo de relé desde el pin VIN (5 V); si su entrada IN no conmuta de forma fiable con la señal de 3,3 V del ESP32, elige un módulo de relé compatible con 3,3 V. Más detalles en ¿Qué es un módulo de relé?

Con la Raspberry Pi Pico y MicroPython

Raspberry Pi Pico
La Raspberry Pi Pico funciona a 3,3 V, así que se conecta directamente al pin RXD del HC-05; no necesita divisor de tensión.

La Raspberry Pi Pico estándar no tiene Bluetooth, así que conectas el HC-05 a los pines UART0 (GP0 y GP1). Como los pines de la Pico trabajan a 3,3 V, esta vez no hace falta divisor de tensión; alimenta el módulo desde el pin VBUS, que da los 5 V del USB. La Pico W y la Pico 2 W tienen Bluetooth integrado, pero MicroPython solo admite BLE en ellas; para hablar con el BluetoothClient clásico de App Inventor, el HC-05 sigue siendo el camino más sencillo. El código siguiente entiende los mismos comandos, mueve el servo con PWM de 50 Hz y envía los datos con el mismo formato; el módulo dht viene incluido en el firmware MicroPython de la Pico.

MicroPython
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)

Ideas de proyectos con App Inventor y Arduino

  • Coche controlado por Bluetooth: Asigna los botones de dirección a las letras F, B, L y R y mueve los motores con un L298N; tienes todos los pasos en la guía cómo hacer un coche robot con Arduino.
  • Panel de casa inteligente: Controla una lámpara, un enchufe y un ventilador desde una sola pantalla con un módulo de relés de cuatro canales; para ideas, mira el proyecto de casa inteligente con Arduino.
  • Lámpara con selector de color: Ajusta por separado el rojo, el verde y el azul de un LED RGB con tres deslizadores.
  • Brazo robótico: Añade un deslizador por articulación y maneja los servomotores desde el móvil.
  • Monitor de invernadero: Muestra en el móvil los datos del DHT11 y del sensor de humedad del suelo y añade un botón que active la bomba cuando la tierra esté seca.
  • Control por voz: Con el componente SpeechRecognizer de App Inventor, crea una app que envíe L1 cuando digas «enciende la luz».

Errores frecuentes y cómo solucionarlos

  • La app se conecta pero el LED no se enciende: Los cables TXD y RXD del HC-05 están intercambiados o los pines de SoftwareSerial del código no coinciden con el circuito. El TXD del módulo debe ir al RX del Arduino (D10).
  • Los comandos no se procesan: Falta el \n en el bloque de texto; el Arduino espera el salto de línea. Comprueba si los comandos aparecen en el Monitor Serie.
  • Caracteres sin sentido: La velocidad del HC-05 no coincide con la de bt.begin(). El módulo viene de fábrica a 9600 baudios; si se cambió con comandos AT, ajusta el código.
  • El servo tiembla o se corta la conexión: El servo hunde la línea de 5 V del Arduino; aliméntalo con una fuente de 5 V aparte y une las GND. También puede haber un ligero temblor porque SoftwareSerial bloquea las interrupciones un instante mientras recibe; como solución definitiva, usa un Arduino Mega o un ESP32, que tienen puertos serie por hardware libres. De vez en cuando el servo también puede saltar un instante a un ángulo erróneo: la librería DHT desactiva las interrupciones unos 5 ms en cada lectura y un comando que llegue justo entonces puede corromperse; el siguiente comando del deslizador lo corrige.
  • Los valores del móvil llegan con retraso: El intervalo del Clock es más largo que el de envío del Arduino y las líneas se acumulan. Mantén TimerInterval por debajo del intervalo de envío.
  • La app se cuelga: DelimiterByte sigue en 0 o el Arduino no termina sus líneas con \n; ReceiveText(-1) espera un delimitador que nunca llega.
  • No funciona en iPhone: El HC-05 usa Bluetooth clásico, que iOS no permite. Para un iPhone hace falta un módulo BLE (HM-10) o un ESP32.
Al buscar un fallo, divide la cadena en dos: prueba primero el lado de Arduino con Serial Bluetooth Terminal y después la app de App Inventor. Si el terminal funciona pero la app no, el problema está en los bloques; si el terminal tampoco funciona, revisa el cableado o el código de Arduino. Para confirmar los números de pin, usa la herramienta de pinout.

Combinar App Inventor y Arduino es la forma más divertida de aprender a la vez desarrollo de apps y electrónica. Una vez montada la conexión y el protocolo, el mismo esqueleto sirve para el mando de un coche, de una casa inteligente o de un invernadero; lo único que cambia son las letras que envían los botones. En la app BlueGrays encontrarás paso a paso, con esquemas y código, los proyectos de encender un LED desde el móvil por Bluetooth y del coche controlado por Bluetooth con L298N.

Preguntas frecuentes

¿Una app de App Inventor con Bluetooth funciona en iPhone?

Con un HC-05 o un HC-06, no. Estos módulos usan Bluetooth clásico (SPP) y iOS no permite que las apps normales abran ese tipo de conexión serie. Para comunicarte con un iPhone tienes que pasar a un módulo BLE como el HM-10 o a un ESP32; en proyectos con HC-05, un móvil Android es la opción más sencilla.

¿Por qué la lista Bluetooth sale vacía en App Inventor?

AddressesAndNames solo muestra los dispositivos ya emparejados con el móvil, así que primero empareja el HC-05 en los ajustes Bluetooth con el PIN 1234. En Android 12 y posteriores, la lista también sale vacía la primera vez si no se ha concedido el permiso Dispositivos cercanos; concédelo y vuelve a pulsar Conectar. Asegúrate también de que el Bluetooth esté activado.

¿Cómo soluciono el error 507 de App Inventor?

El error 507 significa «Unable to connect. Is the device turned on?»: el móvil no ha podido abrir la conexión con el módulo. Comprueba que el HC-05 tiene alimentación, que su LED parpadea rápido y que no está conectado a otro móvil. Si el problema sigue, borra el emparejamiento y vuelve a emparejar, o prueba a desactivar la propiedad Secure del BluetoothClient.

¿Cómo se genera el APK en App Inventor?

Abre el proyecto y haz clic en Build > Android App (.apk) en el menú superior. Cuando termine la compilación, escanea el código QR con el móvil o descarga el APK en el ordenador y pásalo al móvil. Durante la instalación tendrás que permitir la instalación de aplicaciones desconocidas.

¿App Inventor se puede conectar a un ESP32 por Bluetooth?

Sí. Con la librería BluetoothSerial, el ESP32 clásico se comporta como un dispositivo SPP y el BluetoothClient de App Inventor se conecta a él igual que a un HC-05. El ESP32-S3 y el ESP32-C3 solo admiten BLE, así que en esas placas necesitas la extensión BluetoothLE.

¿Puedo publicar mi app de App Inventor en Google Play?

Sí. La opción Android App Bundle (.aab) del menú Build genera un archivo que puedes subir a Google Play; para ello necesitas una cuenta de desarrollador de Google Play. Las actualizaciones deben firmarse con la misma clave, así que guarda tu keystore con Projects > Export keystore.

Ver todos los proyectos
Parpadeo de LED
#1Principiante

Parpadeo de LED

El primer hola de la electrónica: encender y apagar un solo LED. El Arduino enciende y apaga el LED a intervalos cortos, así que la luz parpadea rápido. Ves de la forma más sencilla cómo la placa cont

4.6(179)
978
Temperatura y humedad con DHT11
#55Intermedio

Temperatura y humedad con DHT11

¿Qué temperatura hace en tu habitación, y el aire está húmedo o seco? El sensor DHT11 mide la temperatura y la humedad, y el Arduino escribe los valores en una pantalla LCD de dos líneas, actualizándo

4.7(47)
64
Controlar un LED desde el móvil por Bluetooth
#34Principiante
Premium

Controlar un LED desde el móvil por Bluetooth

Un pequeño ensayo para encender y apagar las luces de tu habitación desde el móvil. El módulo Bluetooth pasa al Arduino las letras que envías desde el teléfono; una letra enciende el LED rojo, otra en

4.8(68)
88
Coche controlado por Bluetooth con L298N
#80Avanzado
Premium

Coche controlado por Bluetooth con L298N

Convierte tu móvil en un mando y conduce tu propio coche robot. El Arduino recibe por un módulo Bluetooth las órdenes de una app de control remoto en el móvil y mueve las ruedas con un controlador de

5.0(38)
65
Uso del servomotor
#35Principiante
Premium

Uso del servomotor

El brazo de un pequeño servomotor gira por turnos a tres posiciones: al principio del todo, justo al centro y al final del todo, esperando dos segundos en cada parada. El Arduino le dice al motor en q

4.7(48)
67
Uso de Led RGB
#39Principiante
Premium

Uso de Led RGB

Dentro de un solo LED se esconden tres colores: el LED RGB se enciende en rojo, luego en verde y luego en azul, cada color medio segundo encendido y medio segundo apagado, y la secuencia vuelve a empe

4.9(73)
98
Bombilla 220V con Pulsador (Push)
#19Avanzado
Premium

Bombilla 220V con Pulsador (Push)

Mientras mantienes pulsado el botón, una bombilla real de 220 voltios se queda encendida, y se apaga en cuanto lo sueltas. La pequeña señal del Arduino no puede encender la bombilla directamente, así

4.4(30)
38
Comunicación unidireccional entre 2 Arduino
#57Intermedio
Premium

Comunicación unidireccional entre 2 Arduino

Haces que dos Arduino hablen por un solo cable: pulsas el botón de uno y se enciende el LED del otro, vuelves a pulsar y se apaga. La placa que envía avisa del botón con un mensaje corto, y la que rec

4.4(37)
50