Corso su Android – Lavoriamo con il Bluetooth del nostro smartphone

titleBT

Il Bluetooth è uno standard universale per la trasmissione dei dati a corto raggio, permette lo scambio di informazioni tra dispositivi diversi, computer, cellulari, fotocamere, etc. Ormai questa tecnologia è presente nella maggior parte di cellulari e smartphones. A chi non è mai venuto in mente di sfruttare il Bluetooth del telefono per pilotare apparati elettronici? In questo articolo vedremo come sfruttare il Bluetooth del nostro smartphone Android per creare una nostra applicazione in grado di pilotare i relè a bordo della scheda relè Open Source presentata sul nostro sito.

Iniziamo a fare la lista della spesa e vedere cosa ci serve :

  • telefono Android con Bluetooth (ancora non ho trovato un telefono con sistema operativo Android che non abbia il Bluetooth);
  • Arduino UNO;
  • scheda relè;
  • modulo Bluetooth (ho usato un RN41) certificato con il BT2.1 quindi con telefoni che hanno Bluetooth 4.0 potrebbe non funzionare.

Cosa andremo a realizzare:

realizzeremo un'applicazione in grado da accendere/spegnere i 4 relè messi a disposizione sulla scheda. L'applicazione si connetterà al modulo Bluetooth  e scambierà dei dati con Arduino che azionerà i relè.
Ci concentreremo solo su questo aspetto, lasciando come esercizio, al lettore, il compito di recuperare lo stato dei relè alla partenza dell'applicazione ed impostando gli stati dei bottoni.
Prima di iniziare lo sviluppo vero e proprio occorre collegare i componenti. Per Arduino non ci sono problemi la scheda utilizzata ha un alloggiamento con il quale è semplice assemblarlo, mentre per il modulo Bluetooth occorre prendere saldatore e stagno e collegare i piedini:

RN-41 Piedini Arduino
Rx D2
Tx D3
3,3v 3,3v
Gnd Gnd

Di seguito uno schema di massima:

Di seguito la scheda fronte e retro dopo i collegamenti:

Una volta che la scheda è pronta, possiamo iniziare ad implementare la nostra applicazione che avrà un’interfaccia grafica molto semplice, sarà costituita da 4 pulsanti che attiveranno/disattiveranno i relè sulla scheda e da quattro "LED" che indicheranno lo stato dei relè:

  • verde: acceso
  • rosso: spento

Iniziamo guardando la parte di codice relativa ad Arduino. Utilizzeremo la classe  SoftwareSerial per comunicare con il modulo Bluetooth, quindi, all'inizio del nostro sketch, ci sarà l'istruzione:

SoftwareSerial bluetooth(rxPin, txPin); // RX, TX

dove

const int rxPin = 3; //SoftwareSerial RX pin
const int txPin = 2; //SoftwareSerial TX pin

definiamo i pin collegati ai relè:

const int RELE_K1  = 7;    //pin collegato al relè K1
const int RELE_K2  = 6;    //pin collegato al relè K2
const int RELE_K3  = 4;    //pin collegato al relè K3
const int RELE_K4  = 5;    //pin collegato al relè K4

e li andiamo a definire come pin di OUTPUT.

   pinMode(RELE_K1, OUTPUT);
   pinMode(RELE_K2, OUTPUT);
   pinMode(RELE_K3, OUTPUT);
   pinMode(RELE_K4, OUTPUT);

Una cosa da tenere in considerazione: il modulo Bluetooth utilizzato ha per default un baud rate di 115200 che risulta essere troppo elevato, per questo motivo viene utilizzato il metodo :

  void setupBT(){
    bluetooth.begin(115200);  // Di default è settato a 115200bps
    // manda il comando di per far entrare il modulo bt in "command mode"
    bluetooth.print("$");
    bluetooth.print("$");
    bluetooth.print("$");  
    delay(100);  // Ritardo per aspettare la risposta del modulo btD
    bluetooth.println("U,9600,N");  // cambia temporaneamente il baudrate a 9600, 
no parity
    
    bluetooth.begin(9600);  // Setta il Bluetooth seriale a 9600
  }

che imposta il baud rate a 9600.

Dobbiamo ora definire un protocollo per attivare disattivare i relè. Si può pensare di inviare una stringa "kx_ys" dove x indica il numero del relè che va da 1 a 4 ed y indica se acceso o spento (0,1). Mentre il carattere "s" indica un carattere di fine comando, infatti vengono recuperati i valori provenienti dal modulo e assegnati ad una stringa, e quando arriva il carattere "s" il ciclo si ferma perché "s" indica la fine del comando.
Andiamo a vedere il corpo dello sketch.

Nel metodo loop troviamo:

if(bluetooth.available() > 0){
     Serial.print("Ricevuto: ");
            
      char c = Bluetooth.read();
      if (c == 's'){
        Serial.println( "stop" );
      }else {
        strsValues += c;
        Serial.println( strsValues );
      }
      delay(5);
    }else if (strsValues == "k1_0"){
      Serial.println( "set k1 off" );
      digitalWrite(RELE_K1, LOW);
      resetValues();
     
    }else if (strsValues == "k1_1"){
      Serial.println( "set k1 on" );
      digitalWrite(RELE_K1, HIGH);
      resetValues();
    }else if (strsValues == "k2_0"){
      Serial.println( "set k2 off" );
      digitalWrite(RELE_K2, LOW);
      resetValues();
     
    }else if (strsValues == "k2_1"){
      Serial.println( "set k2 on" );
      digitalWrite(RELE_K2, HIGH);
      resetValues();
    }else if (strsValues == "k3_0"){
      Serial.println( "set k3 off" );
      digitalWrite(RELE_K3, LOW);
      resetValues();
     
    }else if (strsValues == "k3_1"){
      Serial.println( "set k3 on" );
      digitalWrite(RELE_K3, HIGH);
      resetValues();
    }else if (strsValues == "k4_0"){
      Serial.println( "set k4 off" );
      digitalWrite(RELE_K4, LOW);
      resetValues();
     
    }else if (strsValues == "k4_1"){
      Serial.println( "set k4 on" );
      digitalWrite(RELE_K4, HIGH);
      resetValues();
    }
    delay(100);
}

Viene utilizzata la connessione Serial solo per vedere cosa viene inviato dalla nostra applicazione.

Una volta definito lo sketch, passiamo al codice Android.
Quando dobbiamo connetterci ad un dispositivo Bluetooth dobbiamo fare prima una ricerca dei dispositivi disponibili per poi scegliere nostro modulo BT e fare il pairing con questo. Quindi creiamo un'Activity che fa questo:

Creiamo il progetto BTArduinoComunicator che sarà costituito da tre classi:

  • MainActivity: activity principale;
  • BluetoothService: classe che si occupa della connessione Bluetooth;
  • DeviceListActivity: activity che visualizza la lista di dispositivi disponibili.

Per gestire lo stato dei relè è stato usato un array

private int[] aReleStatus    = new int[4];
che può assumere i valori:
private int RELE_OFF    = 0;
private int RELE_ON    = 1;

che verranno modificati quando si attivano/disattivano i relè.
Come detto all'inizio, assumiamo che i relè siamo spenti e quindi utilizziamo il metodo initReleStatus per settare lo stato:

/**
     * inizializza i rele come spenti
     */
    private void initReleStatus(){
        for (int i=0; i<4; i++){
            aReleStatus[i]    = RELE_OFF;
        }
    }

Nella MainActivity come prima cosa da fare è recuperare l'adapter
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
dopodichè  sul metodo onStart verifichiamo se è attivo; se non lo è, viene effettuato un controllo per attivarlo

if (!mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);

        } else {
            if (btService == null) enableToSend();
        }
    }

Nel metodo enableToSend()  andiamo a definire i listener per i bottoni e andiamo ad instanziare la classe BluetoothService

    private void enableToSend() {
        Log.d(TAG, "enableToSend");
   
        btnK1 = (Button) findViewById(R.id.idBtnK1);
        btnK1.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
//                sendMessage("k1_0s");
                Log.d(TAG, "k1 pressed");
            
                sendMessage(calcNewState("k1",K1));
                
            }
        });
        
        btnK2 = (Button) findViewById(R.id.idBtnK2);
        btnK2.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
//                sendMessage("k2_0s");
                
                sendMessage(calcNewState("k2",K2));
            }
        });
        
        btnK3 = (Button) findViewById(R.id.idBtnK3);
        btnK3.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                
                sendMessage(calcNewState("k3",K3));
            }
        });
        
        btnK4 = (Button) findViewById(R.id.idBtnK4);
        btnK4.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                
                sendMessage(calcNewState("k4",K4));
            }
        });

        // Initialize the BluetoothService to perform Bluetooth connections
        btService = new BluetoothService(this, mHandler);

        // Initialize the buffer for outgoing messages
        mOutStringBuffer = new StringBuffer("");
    }

Sul metodo onResume va controllato se il servizio è partito o meno:

if (btService != null) {
        
            if (btService.getState() == BluetoothService.STATE_NONE) {
          
              btService.start();
            }
        }

Mentre nel metodo onDestroy ci accertiamo di fare lo stop del servizio:

if (btService != null) btService.stop();

Il metodo sendMessage invia una stringa al dispositivo:

private void sendMessage(String message) {
       
        if (btService.getState() != BluetoothService.STATE_CONNECTED) {
            Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
            return;
        }

       
        if (message.length() > 0) {
          
            byte[] send = message.getBytes();
            btService.write(send);

           
            mOutStringBuffer.setLength(0);
       
        }
    }

Mentre il metodo calcNewState serve per calcolare lo stato del relè e quindi restituisce il valore da inviare alla scheda:

private String calcNewState(String who, int rele){
        
         if (aReleStatus[rele] == RELE_ON){
             Log.d(TAG, who + " actual status ON --> set to OFF");
             //setta il nuovo stato del relè
             aReleStatus[rele] = RELE_OFF;
             setImages(who, false);
             return who + "_" + RELE_OFF + "s";
         }else {
             Log.d(TAG, who + " actual status OFF --> set to ON");
            //setta il nuovo stato del relè
             aReleStatus[rele] = RELE_ON;
             setImages(who, true);
             return who + "_" + RELE_ON + "s";
         }
    }

Se non è presente nessun accoppiato,occorre effettuare il pairing, tramite la classe DeviceListActivity  (che non è altro che una lista)  richiamata dal menu nella MainActivity:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Intent serverIntent = null;
        switch (item.getItemId()) {
        case R.id.secure_connect_scan:
            serverIntent = new Intent(this, DeviceListActivity.class);
            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE);
            return true;

        case R.id.discoverable:
            
            ensureDiscoverable();
            return true;
        }
        return false;
    }

Il discover dei dispositivi viene fatto tramite la classe BluetoothAdapter, infatti, viene recuperato l'adapter di default:

mBtAdapter = BluetoothAdapter.getDefaultAdapter();

e viene subito controllato se ci sono dispositivi già accoppiati, e se presenti vengono aggiunti ad una lista:

Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();

Per il discovery viene utilizzato il metodo che richiama doDiscovery:

    private void doDiscovery() {
        Log.d(TAG, "doDiscovery()");
    
        setProgressBarIndeterminateVisibility(true);
        setTitle(R.string.scanning);
       
        findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
        
        if (mBtAdapter.isDiscovering()) {
            mBtAdapter.cancelDiscovery();
        }

        mBtAdapter.startDiscovery();
    }

che lancia la ricerca in modo asincrono e quando vengono trovati dei dispositivi viene lanciato un intent BluetoothDevice.ACTION_FOUND quindi occorre registrare un BroadcastReceiver a tale intent:

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);

dove mReceiver è definito come:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
             
                BluetoothDevice device = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE);
             
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + 
device.getAddress());
                }
           
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                setProgressBarIndeterminateVisibility(false);
                setTitle(R.string.select_device);
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found)
.toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };

una volta ottenuta la lista, quando si seleziona un dispositivo, il controllo torna alla classe MainActivity e precisamente nel metodo onActivityResult da cui viene chiamato il metodo connectDevice che grazie alla classe BluetoothService si connette al dispositivo:

  private void connectDevice(Intent data) {
       
        String address = data.getExtras()
            .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
    
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
       
        btService.connect(device);
    }

A questo punto analizziamo la classe BluetoothService. Quando si vogliono connettere due dispositivi uno deve funzionare da server e l'altro da client. Nel nostro caso, il modulo Bluetooth sarà il server ed il telefono funzionerà da client. Il codice sorgente avrà al suo interno entrambi i modi di funzionamento, ovvero il telefono funziona da server e il modulo Bluetooth da client. Di seguito vedremo la prima modalità.
Per far questo viene implementato un thread:

private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        private String mSocketType           = "Secure";// indica connessione sicura

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
           

            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
                
            } catch (IOException e) {
                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
            setName("ConnectThread" + mSocketType);

          
            mAdapter.cancelDiscovery();

           
            try {
               
                mmSocket.connect();
            } catch (IOException e) {
               
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() " + mSocketType +
                            " socket during connection failure", e2);
                }
                connectionFailed();
                return;
            }

           
            synchronized (BluetoothService.this) {
                mConnectThread = null;
            }

        
            connected(mmSocket, mmDevice, mSocketType);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
            }
        }
    }

questo aspetta finchè non viene connesso un device mmSocket.connect();
appena il device viene connesso, viene notificato alla classe MainActivity.

Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.DEVICE_NAME, device.getName());
        msg.setData(bundle);
        mHandler.sendMessage(msg);

A questo punto premendo sui bottoni per attivare spegnere viene chiamato il metodo

public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(out);
    }

Il quale invia i dati al modulo Bluetooth e vengono analizzati da Arduino.

Quando viene impiegato il Bluetooth, occorre inserire nel maifest i permessi:

<uses-permission android:name="android.permission.bluetooth_ADMIN" />
<uses-permission android:name="android.permission.bluetooth" />

Abbiamo visto come sia possibile pilotare i relè della scheda.

Lascio alla fantasia del lettore i possibili utilizzi per esempio utilizzarla per accendere le luci di casa, per aprire il garage, etc.

Come detto e visto, nell'articolo non si tiene conto dello stato dei relè: si presume che allo start dell'applicazione i relè siano tutti spenti. Chi vuole può analizzare lo stato dei relè alla partenza dell'app e modificare il metodo initReleStatus per settare lo stato attuale.
Nel prossimo articolo vedremo come è possibile comunicare con Arduino utilizzando un modo alternativo al Bluetooth: la portaUSB.

Allegati

readBT

BTArduinoCommunicator

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

7 Comments

  1. Boris L. 11 giugno 2014
  2. Antonello Antonello 16 giugno 2014
  3. Mauro16 15 agosto 2014
  4. sergio_arduino48 sergio_arduino48 25 gennaio 2015
  5. xDuKeN xDuKeN 29 gennaio 2015
  6. plotino plotino 6 ottobre 2015

Leave a Reply