martes, 31 de diciembre de 2013

Android + Arduino : Encendiendo un LED

En esta ocasión enseñaré como hacer la conexión entre un dispositivo android y un arduino Uno R3 . Si bien este modelo de arduino no tiene conexión ethernet, este no es una desventaja si tienes las ganas aprender.

Primero empezaremos programando al arduino :

PARTE 1: ARDUINO

El montaje, que es de lo más sencillo.

Ahora el código, más conocido como "sketch"
const int LED = 13; //LED conectado al pin digital 13
int inByte = 0;
 
void setup(){
    Serial.begin(9600); //Abrimos el el puerto serial
    pinMode(LED, OUTPUT); //Seteamos que el ping digital será de salida
}
 
void loop(){
    if(Serial.available() > 0){
       inByte = Serial.read(); //lee los bytes que ingresan
       if(inByte == '1')
           digitalWrite(LED, HIGH); //Enciende el LED
       else if(inByte == '0')
           digitalWrite(LED, LOW); //Apaga el LED
    }
}

PARTE 2: API

En segundo lugar pasaremos a programar el servidor, ya que no contamos con un ethernet shield para arduino. Para lograrlo he usado el concepto de API y REST, en este pequeño tutorial no usaré un token para autentificarme ya que es otro tema. En fin, lo bueno de programar en esta forma es que cualquier programa puede hacer la petición, esto significa que nuestro servidor es escalable y eso permite realizar mejores cosas =) .
Antes de empezar tiene que descargar dos librerías : Bottle,que sirve para crear el API y PySerial 2.7, que sirve para comunicarse por el puerto serial. Si estamos en Windows, debemos ir a la carpeta donde ha sido descargada y abrir la consola y ejecutar lo siguiente.
python setup.py install
De esta manera ya tenemos instalada las dos librerías.
import serial
import time
from bottle import route, run, post, get, request, template

ser = serial.Serial('COM4', 9600)    //definimos el puerto COM y los  baudios
time.sleep(2)                        //*Importante*    

@route('/arduino', method='POST')   //definimos la ruta y la operación HTTP
def arduino():
 led = request.forms.get('led') //obtenemos la variable led
 if(ser.isOpen()):
  ser.write(led)      //envíamos la data recibida al arduino
 else:
  ser.open()
  ser.write(led)
  time.sleep(2)

run(host='192.168.2.10', port=8080) //definimos el nombre del host y el puerto
Como pueden apreciar el código es sencillo, en la parte de "time.sleep(2)" es muy importante, ya que tendremos que esperar 2 segundos para que se abra el puerto serial, de lo contrario ocurre un error interno ya todavía no está listo para ser usado. No olvidar ejecutar este código desde la consola de python.

PARTE 3: ANDROID

Una de las características especiales de Android es el "Reconocimiento de Voz", para este aplicación se usará un UI sencilla que constará de un Button para activar el "Reconocimiento de Voz" y un TextView como salida.

Paso 1: Creamos un proyecto básico en Android en Eclipse
Vamos a New > Project > Android Project y le ponemos su nombre. Este aplicativo lo he llamado AndruinoLED

Paso 2: Cambiamos el Layout
Archivo: res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="vertical">
 
     <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Presione el botón para iniciar el reconocimiento de voz">
    <ImageButton
        android:id="@+id/btnHablar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_btn_speak_now"
        android:contentDescription="">
   <TextView
        android:id="@+id/tviSalida"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/salida">
    
</LinearLayout>

Paso 3: Código Java Android
Archivo: MainActivity.java


En esta parte del código declaramos y definimos las variables. Además seteamos el evento OnClickListener para que cada vez que se presione el botón se inicie el reconocimiento de voz.

protected static final int RESULT_SPEECH = 1;

 private ImageButton mBtnHablar;
 private TextView mTviSalida;
 private ArrayList mTexto;
 private String aux;
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  mTviSalida = (TextView) findViewById(R.id.tviSalida);
  mBtnHablar = (ImageButton) findViewById(R.id.btnHablar);
  mBtnHablar.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    iniciarReconocimientoVoz();  
    
   }
  });
  
 }
Este es el método que iniciara el reconocimiento de voz. Cada intent.putExtra recibe un nombre y un valor, esto luego será enviado al API de Reconocimiento de Voz de Google. En vez de RecognizerIntent.LANGUAGE_MODEL_FREE_FORM,también se podría poner un lenguaje específico como por ejemplo "en-US", en vez de "Diga Encender o Apagar" podemos cambiar el mensaje a cualquier cosa.
private void iniciarReconocimientoVoz() {
  Intent intent = new Intent(
    RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

  intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
  intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Diga Encender o Apagar");
  try {
   startActivityForResult(intent, RESULT_SPEECH);//Activia onActivityResult()
   mTviSalida.setText("");
  } catch (ActivityNotFoundException a) {
   Toast.makeText(getApplicationContext(),
     "Tu dispositivo no es compatible con el \"Reconocimiento de Voz\"",
     Toast.LENGTH_SHORT).show();
   
  }
 }
Luego de ejecutar el método iniciarReconocimientoVoz() , se dispara el método que está en @Override onActivityResult(). Si el resultado es corrector y la data recibida no es null se procede a obtener la data recibida del API de Reconocimiento de Voz y luego comparar sí es "encender" o "apagar". Finalmente ejecutamos la tarea ComunicacionServidorTask,, para decirle a nuestro servidor que encienda el LED.


@Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);

  switch (requestCode) {
  case RESULT_SPEECH: {
   if (resultCode == RESULT_OK && null != data) {

    mTexto = data
      .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
    aux = mTexto.get(0);
    if(aux.compareTo("encender")==0){
     mTviSalida.setText("LED encendido");
     aux = "1";
    }else if(aux.compareTo("apagar")==0){
     mTviSalida.setText("LED apagado");
     aux = "0";
    }else{
     mTviSalida.setText("Usted dijo : "+aux+". Diga \"enceder\" o \"apagar\"");
    }
    
    new ComunicacionServidorTask(this).execute(aux);
   }
  }
 break;
  }
 }
Finalmente la comunicación con el servidor, en postParameters.add(new BasicNameValuePair("led", aux)); seteamos el nombre y el valor que enviaremos al servidor. Y no olvidar dar el permiso <uses-permission android:name="android.permission.INTERNET"/> en el AndroidManifest.xml.

class ComunicacionServidorTask extends AsyncTask<<String, Boolean, Integer>{
  // TAG
  private static final String TAG_COMUNICACION_SERVIDOR_TASK = "ComunicacionServidorTask";
  private static final String MSG_SOCKET_EXCEPTION = "SocketException";
  private static final String MSG_PARSE_EXCEPTION = "ParseException";
  private static final String MSG_IO_EXCEPTION = "IOException";
  
  private ProgressDialog mProgressDialog;
  
  
  public ComunicacionServidorTask(Context context) {
   this.context = context;
   mProgressDialog = new ProgressDialog(context);
  }
  @Override
  protected Boolean doInBackground(String... params) {
   
   return  ComunicacionServidor();
  }
  private Context context;

     protected void onProgressUpdate(Integer... values) {
          int progreso = values[0].intValue();
          mProgressDialog.setProgress(progreso);
      }
   
  @Override
  protected void onPreExecute() {
   
   mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
   mProgressDialog.setMessage("Conectando. Espere unos momentos.");
   mProgressDialog.setCancelable(true);
   mProgressDialog.setMax(100);
   
   mProgressDialog.setOnCancelListener(new OnCancelListener() {
    
    @Override
    public void onCancel(DialogInterface dialog) {
     ComunicacionServidorTask.this.cancel(true);
     
    }
   });
   mProgressDialog.setProgress(0);
         mProgressDialog.show();
   super.onPreExecute();
  }
  
  @Override
  protected void onPostExecute(Boolean result) {
   if(result==true){
    mProgressDialog.dismiss();
    Toast.makeText(context, "Se conectó al servidor.", Toast.LENGTH_LONG).show();
   }else{
    mProgressDialog.dismiss();
    Toast.makeText(context, "No se pudo conectar al servidor.", Toast.LENGTH_LONG).show();
   }
   super.onPostExecute(result);
  }
  
  @Override
     protected void onCancelled() {
         Toast.makeText(context, "Se cancelo la conexión al servidor",
             Toast.LENGTH_SHORT).show();
     }
  private Boolean  ComunicacionServidor() {
   boolean resultado = false;
   HttpClient httpClient = new DefaultHttpClient();
   HttpPost post = new HttpPost("http://192.168.2.10:8080/arduino");
   
   try{
    ArrayList postParameters;
    postParameters = new ArrayList();
       postParameters.add(new BasicNameValuePair("led", aux));
       post.setEntity(new UrlEncodedFormEntity(postParameters));
    HttpResponse resp = httpClient.execute(post);
          String respStr = EntityUtils.toString(resp.getEntity());
    Log.i(TAG_COMUNICACION_SERVIDOR_TASK, respStr);
    if(respStr.equals("")){
     resultado = true;
    }
    
   } catch (SocketException sEx) {
    Log.e(TAG_COMUNICACION_SERVIDOR_TASK,
      MSG_SOCKET_EXCEPTION + ": " + sEx.getMessage());
   } catch (ParseException pEx) {
    Log.e(TAG_COMUNICACION_SERVIDOR_TASK,
      MSG_PARSE_EXCEPTION + ": " + pEx.getMessage());
   } catch (IOException ioEx) {
    Log.e(TAG_COMUNICACION_SERVIDOR_TASK,
      MSG_IO_EXCEPTION + ": " + ioEx.getMessage());
   }
   httpClient.getConnectionManager().shutdown();
   return resultado;
  }
 }

ScreenCapture de la aplicación :



Video demostrativo:

Este será el repositorio del blog https://github.com/IvanAliaga/EntreOtrosCodigos , ahí encontrarán todo el proyecto.

Y bueno esta es la primera vez que hago un blog, es la primera entrada, ya es 31 de enero y posteriormente realizaré otros pequeños proyectos o tutoriales como quieran llamarle, pero a pesar de todo transmitir lo que sé a todas las personas interesadas en estos temas para que integren estos pequeños conocimientos para algo más grandioso. Espero haber ayudado y Feliz Año Nuevo :D