Viernes, 24 de Octubre de 2025

Aplicación Android con funcionalidad bluetooth

30 de octubre de 2022
m

Como el DIY (do it yourself) es uno de los conceptos clave de este blog, en este artículo veremos cómo podemos integrar la funcionalidad de comunicación bluetooth en una aplicación Android.

En este artículo vimos que bluetooth es una tecnología idónea para su uso con smartphones e hicimos una pequeña prueba encendiendo y apagando los LEDs de la plataforma experimental construida con una aplicación de Google Play, que nos permitía enlazar nuestro smartphone con el módulo bluetooth del montaje y enviarle mensajes, los cuales eran interpretados por el programa cargado en la placa Arduino.

Esto es genial, porque, conociendo únicamente los fundamentos de comunicación serie para Arduino, nos permite hacer infinidad de cosas y manipular nuestros dispositivos a voluntad.

Pero la voluntad de un maker siempre va más allá y esto se nos puede quedar un poco corto en lo que a customización del proyecto se refiere. ArduController o cualquiera de las aplicaciones similares que encontremos en un Google Play nos vendrá excelente para testear alguna funcionalidad porque nos permita una conexión rápida y la posibilidad de enviar cualquier mensaje, pero lo habitual es que, cuando hayamos probado todas las funcionalidades de nuestro proyecto, deseemos tener una aplicación de control diseñada ex profeso para el montaje construido, donde los detalles de la comunicación entre dispositivos permanezcan opacos al usuario, aunque ese usuario seamos nosotros mismos.

Eligiendo un IDE: Android Studio

Hace ya algunos años desarrollé mi primera aplicación Android en Eclipse. Por aquel entonces, curiosa paradoja, ni siquiera tenía un smartphone donde probar esta aplicación y me valía de lentísimos emuladores.

Cuando por fin adquirí un smartphone allá por 2013 y tuve la posibilidad de aplicar en un dispositivo real las aplicaciones que había diseñado, descubrí Android Studio, un IDE (entorno de desarrollo integrado) específicamente diseñado para desarrollar aplicaciones Android y el flechazo fue inmediato. Sencillo, fluido, visual… todo lo que un IDE debe tener para que hasta los más torpes como yo podamos hacer nuestros pinitos en el lenguaje de programación en cuestión.

No explicaré en este artículo cómo instalar Android Studio ni sus entresijos. Existen infinidad de cursos y tutoriales en internet que lo explican con todo lujo de detalles. Hablaremos exclusivamente de cómo implementar comunicación bluetooth y qué consideraciones debemos tener.

La aplicación Android

Para los menos expertos tan sólo decir que, a grandes rasgos, los elementos más importantes de una aplicación Android son la interfaz gráfica, en formato .xml, y las «actividades».

Estas «actividades» son el código que se ejecuta y que dota a la aplicación de las funcionalidades y la interactividad con el usuario. Tienen extensión .java, por lo que podéis intuir que su sintaxis es Java, de modo que si manejáis este lenguaje no tendréis dificultades para progresar muy rápido en el aprendizaje de desarrollo Android.

Así pues, recomiendo que creéis un nuevo proyecto y aceptéis todas las opciones por defecto para que se os cree la estructura de archivos necesaria para el despliegue de la aplicación. Una vez tenemos el «esqueleto» de nuestra app, todo va a ser muy sencillo.

La interfaz gráfica

Empecemos por la parte fácil. Bueno, en realidad tan fácil o difícil como la queramos hacer. Una buena interfaz gráfica muchas veces marca el éxito de una aplicación, así que sois muy exigentes podéis diseñar algo muy muy complejo.

En mi caso, me conformaré con una interfaz parecida a la de la aplicación Java de https://jjosemayorga.com/comunicacion-bluetooth/, esto es, una pantalla compuesta por cuatro botones: dos para encender/apagar el LED rojo y dos para encender/apagar el LED amarillo.

Además voy a colocar una imagen que replica el estado de la luz. Justamente por ser Android Studio tan visual, esto será muy sencillo y eso va a traducirse en una interfaz mucho más amigable que la que cree en la aplicación Java.

Y por último, he colocado dos switches que permitirán: 1) encender o apagar la funcionalidad bluetooth del smartphone y 2) conectar con el módulo bluetooth cuya MAC hemos indicado.

Interfaz de aplicación Android para control de LEDs por bluetooth

Para construir esta interfaz, tan sólo debemos ir en la estructura de nuestra aplicación a app > res > layout y abrir el archivo .xml correspondiente a la «actividad» principal. Siempre que creemos una nueva «actividad» se creará en esta carpeta un layout asociado con su interfaz gráfica.

Una vez ahí podemos construir la interfaz de manera visual desde la pestaña Design. Cualquier cosa que hagamos de manera visual se refleja en el código del archivo .xml, que es lo que vais a ver en la pestaña Text, y viceversa.

Os dejo el código del archivo .xml, para que podáis pegarlo directamente en vuestra app.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:padding="16sp">


    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

            <Switch
                android:text="Bluetooth"
                android:layout_width="wrap_content"
                android:id="@+id/swt_bluetooth"
                android:layout_gravity="center_vertical|center_horizontal"
                android:layout_height="0dp"
                android:layout_weight="1" />
        </LinearLayout>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

            <Switch
                android:text="Conectar"
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:id="@+id/swt_conectar"
                android:layout_gravity="center_vertical|center_horizontal"
                android:layout_weight="1" />
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:gravity="center_horizontal"
        android:layout_height="0dp"
        android:layout_weight="2">


        <ImageView
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/redoff"
            android:id="@+id/redled"
            android:layout_width="0dp"
            android:layout_weight="1" />

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:layout_gravity="center_vertical|center_horizontal">

            <Button
                android:text="Red LED ON"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/btn_red_on"
                android:layout_gravity="center_vertical|center_horizontal" />
            <Button
                android:text="Red LED OFF"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/btn_red_off"
                android:layout_gravity="center_vertical|center_horizontal" />
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:gravity="center_horizontal"
        android:layout_weight="2">


        <ImageView
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/yellowoff"
            android:id="@+id/yellowled"
            android:layout_width="0dp"
            android:layout_weight="1" />

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:layout_gravity="center_vertical|center_horizontal">

            <Button
                android:text="Yellow LED ON"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/btn_yellow_on"
                android:layout_gravity="center_vertical|center_horizontal" />
            <Button
                android:text="Yellow LED OFF"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/btn_yellow_off"
                android:layout_gravity="center_vertical|center_horizontal" />
        </LinearLayout>


    </LinearLayout>

</LinearLayout>

Los recursos gráficos

El código anterior os dará un error relacionado con el uso de los elementos gráficos. Si vais a usar imágenes u otros recursos gráficos, debéis ubicarlos en la carpeta app > res > drawable.

Así eliminaréis ese error.

El manifiesto

Si aún con esto, vuestra aplicación no tiene la misma apariencia que la que yo os he mostrado, se debe al «manifiesto» de la app.

Este archivo AndroidManifest.xml lo vais a encontrar en la carpeta app > manifests > AndroidManifest.xml y permite manejar algunas configuraciones de la aplicación.

Las más importantes son el icono y el tema, entre otros, y, en este caso, los permisos.

En nuestra aplicación es FUNDAMENTAL habilitar los permisos sobre la funcionalidad bluetooth de nuestro smartphone.

Este es el código que tenéis que tener en vuestro manifiesto para conseguirlo.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="juanjo.ledblog">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.NoActionBar">

        <activity android:name=".LEDcontrol">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

La actividad

Y, finalmente, aquí reside el meollo de la cuestión. Si ya hemos codificado adecuadamente todo lo anterior, quedaría ver cómo implementar la funcionalidad en la «actividad».

El código base lo encontré hace mucho tiempo y desde entonces lo he empleado profusamente en muchas de mis aplicaciones. En este caso, la principal consideración a tener en cuenta es el uso de los switches para canalizar adecuadamente la activación de bluetooth y la conexión con el dispositivo.

Podéis optar porque estas acciones sean inmediatas al iniciar la aplicación, pero no os lo aconsejo.

package juanjo.ledblog;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.Toast;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.UUID;

public class LEDcontrol extends AppCompatActivity
{

    private static final String TAG = "bluetooth";

    Button btn_red_on, btn_red_off, btn_yellow_on, btn_yellow_off;
    ImageView redled, yellowled;
    Switch swt_bluetooth, swt_conectar;

    int rstate=0;
    int ystate=0;
    int conectado=0;

    private BluetoothAdapter btAdapter = null;
    private BluetoothSocket btSocket = null;
    private OutputStream outStream = null;

    // SPP UUID service
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    // MAC-address of Bluetooth module (you must edit this line)
    private static String address = "00:06:66:4B:E4:25"; //para el modem blueSmirf
    //private static String address = "98:D3:31:90:8E:68"; //para el modulo HC-05

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ledcontrol);

        btn_red_on = (Button) findViewById(R.id.btn_red_on);
        btn_red_off = (Button) findViewById(R.id.btn_red_off);
        btn_yellow_on = (Button) findViewById(R.id.btn_yellow_on);
        btn_yellow_off = (Button) findViewById(R.id.btn_yellow_off);
        swt_bluetooth=(Switch)findViewById(R.id.swt_bluetooth);
        swt_conectar=(Switch)findViewById(R.id.swt_conectar);

        redled = (ImageView) findViewById(R.id.redled);
        yellowled = (ImageView) findViewById(R.id.yellowled);


        btAdapter = BluetoothAdapter.getDefaultAdapter();
        //checkBTState();

        swt_bluetooth.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v) {
                //is chkIos checked?
                if (swt_bluetooth.isChecked())
                {
                    checkBTState();
                }
                else
                {
                    if(conectado==1)
                    {
                        swt_conectar.setChecked(false);
                        desconectarRobot();
                    }
                    btAdapter.disable();
                }
            }
        });

        swt_conectar.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v) {
                //is chkIos checked?
                if (swt_conectar.isChecked())
                {
                    conectarRobot();
                }
                else
                {
                    desconectarRobot();
                }
            }
        });

        btn_red_on.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v)
            {
                sendData("c");
                Toast.makeText(getBaseContext(), "Encendiendo LED rojo", Toast.LENGTH_SHORT).show();
                redled.setImageResource(R.drawable.redon);
                rstate=1;
            }
        });

        btn_red_off.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v)
            {
                sendData("d");
                Toast.makeText(getBaseContext(), "Apagando LED rojo", Toast.LENGTH_SHORT).show();
                redled.setImageResource(R.drawable.redoff);
                rstate=0;
            }
        });

        btn_yellow_on.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v)
            {
                sendData("a");
                Toast.makeText(getBaseContext(), "Encendiendo LED amarillo", Toast.LENGTH_SHORT).show();
                yellowled.setImageResource(R.drawable.yellowon);
                ystate=1;
            }
        });

        btn_yellow_off.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v)
            {
                sendData("b");
                Toast.makeText(getBaseContext(), "Aagando LED amarillo", Toast.LENGTH_SHORT).show();
                yellowled.setImageResource(R.drawable.yellowoff);
                ystate=0;
            }
        });

    }

    private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
        if(Build.VERSION.SDK_INT >= 10){
            try {
                final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class });
                return (BluetoothSocket) m.invoke(device, MY_UUID);
            } catch (Exception e) {
                Log.e(TAG, "Could not create Insecure RFComm Connection",e);
            }
        }
        return  device.createRfcommSocketToServiceRecord(MY_UUID);
    }

    public void conectarRobot() {
        super.onResume();

        Log.d(TAG, "...onResume - try connect...");

        // Set up a pointer to the remote node using it's address.
        BluetoothDevice device = btAdapter.getRemoteDevice(address);

        // Two things are needed to make a connection:
        //   A MAC address, which we got above.
        //   A Service ID or UUID.  In this case we are using the
        //     UUID for SPP.

        try {
            btSocket = createBluetoothSocket(device);
        } catch (IOException e1) {
            errorExit("Fatal Error", "In onResume() and socket create failed: " + e1.getMessage() + ".");
        }

        /*try {
          btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
          errorExit("Fatal Error", "In onResume() and socket create failed: " + e.getMessage() + ".");
        }*/

        // Discovery is resource intensive.  Make sure it isn't going on
        // when you attempt to connect and pass your message.
        btAdapter.cancelDiscovery();

        // Establish the connection.  This will block until it connects.
        Log.d(TAG, "...Connecting...");
        try {
            btSocket.connect();
            Log.d(TAG, "...Connection ok...");
            conectado=1;
        } catch (IOException e) {
            try {
                btSocket.close();
            } catch (IOException e2) {
                errorExit("Fatal Error", "In onResume() and unable to close socket during connection failure" + e2.getMessage() + ".");
            }
        }

        // Create a data stream so we can talk to server.
        Log.d(TAG, "...Create Socket...");

        try {
            outStream = btSocket.getOutputStream();
        } catch (IOException e) {
            errorExit("Fatal Error", "In onResume() and output stream creation failed:" + e.getMessage() + ".");
        }
    }

    public void desconectarRobot() {
        super.onPause();

        Log.d(TAG, "...In onPause()...");

        if (outStream != null) {
            try {
                outStream.flush();
            } catch (IOException e) {
                errorExit("Fatal Error", "In onPause() and failed to flush output stream: " + e.getMessage() + ".");
            }
        }

        try     {
            btSocket.close();
            conectado=0;
        } catch (IOException e2) {
            errorExit("Fatal Error", "In onPause() and failed to close socket." + e2.getMessage() + ".");
        }
    }

    private void checkBTState()
    {
        // Check for Bluetooth support and then check to make sure it is turned on
        // Emulator doesn't support Bluetooth and will return null
        if(btAdapter==null) {
            errorExit("Fatal Error", "Bluetooth not support");
        } else {
            if (btAdapter.isEnabled()) {
                Log.d(TAG, "...Bluetooth ON...");
            } else {
                //Prompt user to turn on Bluetooth
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, 1);
            }
        }
    }

    private void errorExit(String title, String message)
    {
        Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();
        finish();
    }

    private void sendData(String message)
    {
        byte[] msgBuffer = message.getBytes();

        Log.d(TAG, "...Send data: " + message + "...");

        try {
            outStream.write(msgBuffer);
        } catch (IOException e) {
            String msg = "In onResume() and an exception occurred during write: " + e.getMessage();
            if (address.equals("00:00:00:00:00:00"))
                msg = msg + ".\n\nUpdate your server address from 00:00:00:00:00:00 to the correct address on line 35 in the java code";
            msg = msg +  ".\n\nCheck that the SPP UUID: " + MY_UUID.toString() + " exists on server.\n\n";

            errorExit("Fatal Error", msg);
        }
    }

}

Demo

Para finalizar el artículo, os dejo la demo de la aplicación desarrollada.

Publicaciones relacionadas

Controla una Roomba a tu voluntad

Controla una Roomba a tu voluntad

En este artículo te muestro una característica poco conocida de los robots de limpieza Roomba de iRobot: la interfaz abierta de Roomba (o ROI, Roomba Open Interface, por sus siglas en inglés). Esta interfaz es un puerto de conexión serie que permite a Roomba intercambiar mensajes con otros dispositivos como microcontroladores, ordenadores o smartphones. El protocolo de información es público y gracias a él podemos añadir a Roomba cuantas funcionalidades se nos ocurran.

0 comentarios

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *