viernes, 31 de agosto de 2012

Utilizando el LCDKeypad para controlar la radio (I)

Una vez que hemos conseguido que se muestren artista y canción en nuestro display, vamos a conseguir pausar y reanudar la reproducción cuando se pulse uno de los botones del LCDKeypad.

El LCDKeypad tiene 5 botones programables por el usuario y uno que resetea el Arduino, como se puede ver en la imagen:

IMG_0414

Básicamente tenemos un botón de selección (SELECT) y una cruceta de control (UP, DOWN, LEFT, RIGHT). Mediante la librería LCDKeypad se puede detectar la pulsación de dichos botones. Lo que tenemos que hacer, básicamente, es conseguir que el Arduino detecte que hemos pulsado un botón (en nuestro caso el botón SELECT) y envíe una señal al RPi. Éste a su vez pausará o reanudará la reproducción al recibir la señal y mandará una actualización por pantalla del estado de la misma.

Detectando las pulsaciones con Arduino

Lo primero será detectar cuándo se ha pulsado cualquiera de los botones programables utilizando el Arduino. Mirando la librería, en concreto el fichero LCDKeypad.h, vemos que hay 6 macros, 5 asignadas a cada uno de los botones y la restante a cuando no hay ningún botón pulsado:

  1: // library interface description
  2: #define KEYPAD_NONE -1
  3: #define KEYPAD_RIGHT 0
  4: #define KEYPAD_UP 1
  5: #define KEYPAD_DOWN 2
  6: #define KEYPAD_LEFT 3
  7: #define KEYPAD_SELECT 4

Veamos los cambios necesarios en el sketch de Arduino:

  1: #include <LiquidCrystal.h>
  2: #include <LCDKeypad.h>
  3: 
  4: LCDKeypad lcd;
  5: String serialIn;
  6: String serialOut;
  7: boolean serialComplete = false;
  8: int buttonActive;
  9: 
 10: void setup(){
 11:   Serial.begin(9600);
 12:   
 13:   lcd.begin(16,2);
 14:   lcd.clear();
 15:   lcd.print("Arduino Radio");
 16:   lcd.setCursor(0,1);
 17:   lcd.print("V 0.0");
 18:   serialIn.reserve(200);
 19:   
 20:   Serial.println("INITOK"); 
 21: }
 22: 
 23: void loop(){
 24:   buttonActive = buttonPressed();
 25:   
 26:   if(serialComplete){
 27:      if (serialIn.startsWith("UP")){
 28:        lcd.setCursor(0,0);
 29:        lcd.print("                ");
 30:        lcd.setCursor(0,0);
 31:        lcd.print(serialIn.substring(3));
 32:        
 33:        Serial.println("OK");
 34:      }
 35:      
 36:      else if (serialIn.startsWith("DW")){;
 37:        lcd.setCursor(0,1);
 38:        lcd.print("                ");
 39:        lcd.setCursor(0,1);
 40:        lcd.print(serialIn.substring(3));
 41:        
 42:        Serial.println("OK");
 43:      }
 44:      
 45:      else{
 46:        Serial.println("KO");
 47:      }
 48: 
 49:      serialIn = "";
 50:      serialComplete = false;
 51:   }
 52:   
 53:   // Checking buttons
 54:   if(buttonActive!=KEYPAD_NONE){
 55:     if(buttonActive == KEYPAD_SELECT){
 56:       Serial.println("BTNSEL");
 57:     }
 58:     else if(buttonActive == KEYPAD_RIGHT){
 59:       Serial.println("BTNRGT");
 60:     }
 61:     else if(buttonActive == KEYPAD_LEFT){
 62:       Serial.println("BTNLFT");
 63:     }
 64:     else if(buttonActive == KEYPAD_UP){
 65:       Serial.println("BTNUP");
 66:     }
 67:     else if(buttonActive == KEYPAD_DOWN){
 68:       Serial.println("BTNDWN");
 69:     }
 70:   }
 71:   else{
 72:     //Serial.println("LOOP");
 73:   }
 74: }
 75: 
 76: 
 77: // Reads the serial input and stores in serialIn
 78: void serialEvent() {
 79:   while (Serial.available()) {
 80:     // get the new byte:
 81:     char inChar = (char)Serial.read(); 
 82:     // add it to the inputString:
 83:     if(inChar != '\n'){
 84:       serialIn += inChar;
 85:     }
 86:     // if the incoming character is a newline, set a flag
 87:     // so the main loop can do something about it:
 88:     if (inChar == '\n') {
 89:       serialComplete = true;
 90:     }
 91:   }
 92: }
 93: 
 94: // Returns the pressed button
 95: int buttonPressed(){
 96:   int aux = lcd.button();
 97:   if(aux != KEYPAD_NONE){
 98:     while(lcd.button() != KEYPAD_NONE){
 99:     }
100:     return aux;
101:   }
102:   return KEYPAD_NONE;
103: }
104: 
105: 

Como podemos ver, el mayor cambio respecto al código anterior son las líneas 54 a 70, donde se realiza el chequeo de cualquier botón que se haya podido pulsar y se envía un código identificativo por el puerto serie. Para ello se compara la variable global buttonActive con cada uno de los botones y, si coincide con alguno, se envía su identificador al RPi.


La pregunta lógica es, ¿de dónde sale esta variable? Se actualiza en cada ciclo del loop principal (línea 24) llamando a la función buttonPressed. Esta función está implementada al final del código (líneas 95 a 103). buttonPressed comprueba que se haya pulsado un botón y, si es así, espera a que se deje de pulsar y devuelve la macro del botón que se ha pulsado. Si no se ha pulsado nada, devuelve la macro KEYPAD_NONE). La función es muy útil, ya que evita que si dejamos pulsado un botón se estén enviando continuamente las pulsaciones al RPi. En nuestro caso lo que haríamos es que el RPi estuviese pausando y reanudando la reproducción contínuamente hasta que soltásemos el botón. Con buttonPressed conseguiremos que todo funcione como se espera.


Hay algún cambio menor en el código, pero básicamente lo añadido es la parte que acabo de describir. Si probamos el código con el RPi conectado al PC podremos ver como se envían los códigos de cada botón en el Serial Monitor al pulsarlos.


Ampliando el programa controlador de la radio Wifi


En la entrada anterior teníamos un programa rudimentario que simplemente preguntaba a MPC qué canción estaba sonando y enviaba las información de artista y álbum al Arduino para que éste la representase en el display. Ahora tenemos que controlar eso y, además, estar preparados para recibir información sobre las pulsaciones de los botones para poder pausar y reanudar la reproducción. Para ello vamos a utilizar dos hilos de ejecución. Uno se encargará de enviar la información de la canción, pero sólo cuando se esté reproduciendo (ya que si la radio está en pausa es obvio que la canción no va a cambiar). El otro hilo se encargará de estar pendiente de los envíos del Arduino sobre el estado de sus botones.


En principio se podría hacer todo en un solo hilo, pero en mi opinión queda más limpio así y permite ampliar el programa más adelante de manera sencilla. Vamos con el código:

  1: import serial, subprocess, time, shlex, threading, sys
  2: 
  3: # Sets the serial connection
  4: if(sys.platform == 'win32'):
  5: 	ser = serial.Serial('COM4', 9600)
  6: else:
  7: 	ser = serial.Serial('/dev/ttyUSB0', 9600)
  8: 
  9: ser.timeout = 4
 10: 
 11: # Globals
 12: isPlaying = False
 13: 
 14: '''This function receives button interaction from Arduino and respond to them'''	
 15: def button_receiver():
 16: 	print "Receiver started"
 17: 	global isPlaying
 18: 	while(True):
 19: 		serial_in = ser.readline();
 20: 		serial_out = ""
 21: 		print "[RECEIVER] Arduino -> RPi:",serial_in
 22: 		
 23: 		if(serial_in == 'BTNSEL\r\n'):
 24: 			args = shlex.split("mpc toggle")
 25: 			subprocess.check_output(args)
 26: 			isPlaying = not isPlaying
 27: 			if(isPlaying):
 28: 				serial_out = "DW Play"
 29: 			else:
 30: 				serial_out = "DW Pause"
 31: 			
 32: 		elif(serial_in == 'BTNLFT\r\n'):
 33: 			serial_out = "DW BTNLFT OK"
 34: 		elif(serial_in == 'BTNRGT\r\n'):
 35: 			serial_out = "DW BTNRGT OK"
 36: 		elif(serial_in == 'BTNUP\r\n'):
 37: 			serial_out = "DW BTNUP OK"
 38: 		elif(serial_in == 'BTNDWN\r\n'):
 39: 			serial_out = "DW BTNDWN OK"
 40: 			
 41: 		if(serial_out != ""):
 42: 			print "[RECEIVER] RPi -> Arduino:",serial_out
 43: 			serial_out += '\n'
 44: 			ser.write(serial_out)
 45: 		else:
 46: 			print "No data"
 47: 		#time.sleep(2)
 48: 	
 49: '''This function sends display updates to arduino'''	
 50: def display_sender():
 51: 	global isPlaying
 52: 	
 53: 	print "Sender started"
 54: 	
 55: 	# Strings to ask things to mpc
 56: 	com_radio = 'mpc current -f "%name%"'
 57: 	com_song = 'mpc current -f "%title%"'
 58: 	
 59: 	# String to send over serial
 60: 	serial_out = ""
 61: 	
 62: 	while(True):
 63: 		if (isPlaying):
 64: 			args = shlex.split(com_song)
 65: 			str_song = subprocess.check_output(args)
 66: 			list = str_song.split(' - ')
 67: 
 68: 			serial_out = "UP "
 69: 			try:
 70: 				serial_out += list[0]
 71: 			except IndexError:
 72: 				serial_out += "Unknown artist"
 73: 			serial_out += '\n'
 74: 			ser.write(serial_out)
 75: 			print "RPi->Arduino:",serial_out
 76: 
 77: 			
 78: 			serial_out = "DW "
 79: 			try:
 80: 				serial_out += list[1]
 81: 			except IndexError:
 82: 				serial_out += "Unknown song"
 83: 			serial_out += '\n'
 84: 			ser.write(serial_out)
 85: 			print "[SENDER] RPi -> Arduino:",serial_out
 86: 		
 87: 		time.sleep(1)
 88: 		
 89: if __name__ == "__main__":
 90: 	rec = threading.Thread(target=button_receiver,name='ButtonReceiverTh')
 91: 	sen = threading.Thread(target=display_sender,name='DisplaySenderTh')
 92: 	rec.start()
 93: 	sen.start()
 94: 	

En la línea 1 he añadido la librería sys, que nos servirá para determinar la plataforma en la que se está ejecutando el programa; y la librería threading necesaria para la creación y control de hilos en Python.


Las líneas 4 a 7 determinan si estamos ejecutando el programa en Windows o en el RPi, y establece la conexión serie según el caso. Esto lo he añadido para no tener que cambiarlo cada vez que modifico el programa (estoy programándolo en Windows y luego lo copio al RPi por SCP).


La línea 12 define la variable booleana isPlaying, que establece si estamos o no reproduciendo en cada momento.


Las líneas 15 a 47 contienen la función button_receiver, que es lo que ejecutará el hilo encargado de recibir el estado de los botones del LCDKeypad. Como vemos es un bucle infinito que comprueba si se ha recibido alguna pulsación y actúa en consecuencia. Por ahora, si recibe una pulsación de cualquiera de los botones que no sean el SELECT, simplemente muestra un mensaje de confirmación en el display del Arduino. Si recibe una pulsación del botón SELECT, lanza un subproceso con el comando necesario para pausar/reanudar MPD y actualiza la variable isPlaying.


Por último, comprueba si se ha recibido otra orden del Arduino que no esté actualmente implementada y la imprime por la salida estándar. Si no se ha recibido nada y se ha llegado al timeout, simplemente imprime “No data”.


Algo curioso de Python es que obliga a definir explícitamente que queremos utilizar una variable como global dentro de una función. Es el caso de isPlaying, que definimos como global al principio del programa, y que tenemos que indicar que queremos utilizarla en esta función como se ve en la línea 17.


Las líneas 50 a 87 contienen la función display_sender, que es lo que ejecutará el hilo encargado de actualizar el display del Arduino. Contiene básicamente el mismo código que el programa de la entrada anterior, pero se han introducido algunas modificaciones, en concreto:



  • En las líneas 69 a 72 y 79 a 82 he introducido un control de excepciones para que en caso de que no se haya parseado correctamente el artista o el álbum, ya sea por un error en el MPC, en el formato de la cadena o en cualquier otra cosa, se muestre una línea indicando artista o canción desconocida, según el caso.
  • Se han eliminado las esperas entre envíos ya que he comprobado que la librería se encarga de evitar que se solapen dos envíos.
  • Se ha reducido el tiempo de espera a 1 segundo entre comprobaciones.
  • La comprobación de la canción y actualización sólo se lleva a cabo cuando la música está sonando.

Por último, en las líneas 89 a 93 está la función main del programa. En ella se crean dos hilos con las dos funciones que acabamos de describir y se lanzan, por lo que se ejecutarán simultáneamente (en realidad no, pero nos sirve perfectamente) y se encargarán de verificar los botones del LCDKeypad y de mantener actualizado el display.


Podéis ver cómo funciona todo en un pequeño vídeo que he grabado (la música se oye muy baja, pero se oye):



Ahora que ya funciona, es el turno de implementar el cambio de emisora. Tengo algunas ideas, pero lo dejo para el siguiente post.

martes, 28 de agosto de 2012

Atajos de teclado en la búsqueda de Google

Desde hace tiempo es posible utilizar la búsqueda de Google utilizando tan sólo el teclado. Pruébalo: abre una ventana con la página de Google, escribe algo para buscar y pulsa Intro. Hasta ahí todo normal, lo bueno viene ahora.

Si pulsas Tabulador, aparece una flecha azul a la izquierda del primer resultado, y podemos movernos entre resultados mediante las flechas del teclado (arriba y abajo). Si queremos acceder a un resultado concreto basta con poner la flecha sobre él y pulsar Intro, y si queremos volver a buscar, basta con seguir escribiendo y automáticamente el cursor vuelve a la caja de búsqueda.

Lo que puede pasar es que nada de esto ocurra, que es lo que me pasó a mí hace unos meses, coincidiendo con unos cambios en la interfaz de Google, y supuse que habrían eliminado esta característica por falta de uso. Sin embargo lo que pasaba es que tenía deshabilitado Google Instant (es decir, que Google empiece a buscar resultados mientras escribimos y no cuando pulsemos en buscar) y se ve que esto también deshabilita los atajos de teclado.

Para volver a activar Google Instant, basta con buscar algo en Google y pulsar sobre el icono de configuración de la parte superior derecha de la página (el del engranaje). En el menú seleccionamos Ajustes de búsqueda (la primera opción) y seleccionamos "Mostrar siempre resultados de Google Instant". Sólo queda guardar los cambios y ya se podrá usar sin problemas.

Lo ideal sería que estos atajos viniesen habilitados por defecto, ya que ahora hay que tener la sesión iniciada para que Google recuerde tus preferencias, pero al menos ya puedo volver a usarlos, que no es poco.

Mostrando canción en LCD con Arduino (II)

En el post anterior conseguimos mostrar en el LCD conectado al Arduino cadenas de caracteres enviadas desde la conexión serie del mismo. Nos falta la parte correspondiente al RPi, es decir, generar las cadenas correspondientes a la canción que está sonando en MPD y enviarla al Arduino por puerto serie. Para ello vamos a utilizar Python, un lenguaje de programación que nos permite hacer mucho con muy poco, como veremos a continuación.

Conectando el Arduino y el Raspberry Pi por puerto serie

Lo primero que debemos hacer es establecer una comunicación serie entre el Arduino y nuestro RPi. Para ello utilizaremos el puerto USB, que es una conexión serie al fin y al cabo, así que conectamos un cable USB entre el RPi (HUB USB) y el Arduino. Si todo va bien, el Arduino debe encenderse al igual que cuando lo conectábamos a un PC convencional.

Recordemos además que en el programa que escribimos en el post anterior, el Arduino enviaba por el puerto serie una cadena “INITOK” después de inicializarse, y otra cadena “Loop” en cada iteración de la función loop del programa, así que si todo ha salido bien, el RPi debe de estar recibiéndolas. Comprobémoslo:

  • Desconectamos el Arduino del RPi.
  • Escribimos en una terminal: ls /dev/tty*
  • Nos saldrá algo parecido a esta imagen:
  • image

  • Conectamos el Arduino al RPi y esperamos unos segundos.
  • Volvemos a escribir en la terminal ls /dev/tty*
  • Nos saldrá algo parecido a esta imagen:
  • image

    Si nos fijamos, en la segunda imagen hay una nueva entrada (en mi caso /dev/ttyUSB0). Ésta es la conexión serie de nuestro RPi. Para poder monitorizar esta conexión vamos a instalar un pequeño programa llamado minicom, para ello, en la terminal escribimos:

  • sudo apt-get install minicom
  • Y ahora lo configuramos para que monitorice nuestra interfaz serie. De nuevo en la terminal:

  • sudo minicom –s
  • Nos saldrá una interfaz de configuración en modo texto. Básicamente tenemos que cambiar el dispositivo a monitorizar, que en nuestro caso es /dev/ttyUSB0 y la velocidad de la conexión, que en el Arduino la establecimos a 9600 baudios.

    Para ello seleccionamos “configuración de la puerta serial” como aparece en la imagen:

    image

    Y lo configuramos modificando el Dispositivo serial al que nos haya salido antes (en mi caso /dev/ttyUSB0) y el Bps/Paridad/Bits para establecerlo a 9600 8N1:

    image

    Guardamos la configuración como dfl, que es la configuración que cargará minicom por defecto y salimos de minicom.

    Ahora sólo queda ejecutar minicom sin parámetros para comprobar que todo ha salido bien, y deberíamos ver los envíos del Arduino (podemos cerrar minicom pulsando Control+A Q):

    image

    Reproduciendo música con MPD/MPC y obteniendo datos de la canción

    En este momento voy a hacer un inciso para explicar por encima el manejo de MPD y MPC. En anteriores posts dijimos que MPD era un demonio que permitía reproducir todo tipo de audio, y MPC era un cliente que facilitaba el manejo de MPD. Para probar nuestro programa, vamos a añadir una radio online a MPD, vamos a reproducirla, y vamos a obtener los datos de la emisora y de la canción. En mi caso he optado por una radio que he descubierto hace poco y me ha gustado mucho, ya que tiene buena música y ninguna publicidad: Radio Paradise.

    MPD necesita la dirección del stream que queramos reproducir. Buscando en la página de Radio Paradise encontramos varios streams según la calidad de reproducción que queramos en la pestaña “Listen”. En mi caso he elegido la opción de 128 Kbps con el códec AAC+, que da mayor calidad de el bitrate equivalente en MP3. El link en este caso es:

    http://www.radioparadise.com/musiclinks/rp_128aac.m3u

    ¿Cómo reproducir esto en MPD? Muy sencillo: primero lo añadimos a la lista de reproducción escribiendo en consola:

    mpc load http://www.radioparadise.com/musiclinks/rp_128aac.m3u

    Y a continuación iniciamos la reproducción escribiendo:

    mpc play

    Si tenemos el RPi conectado a internet y a unos altavoces externos debería empezar a sonar la radio. De todas maneras podemos comprobar qué esta sonando escribiendo simplemente:

    mpc

    image

    Existen muchos otros comandos para MPC: podemos pausar y reanudar la reproducción con mpc toggle, pararla con mpc stop, eliminar la lista de reproducción actual con mpc clear, eliminar todas los elementos de la lista menos el actual con mpc crop, etc. Para más información se puede consultar el manual con man mpc.

    Un comando que nos interesa, sin embargo, es mpc current, que muestra información sobre la canción actualmente en reproducción. Además puede configurarse con máscaras, así, si escribimos en la terminal:

    mpc current –f “%name%”

    Obtendremos el nombre de la emisora de radio. Si escribimos:

    mpc current –f “%title%”

    Obtenemos el artista y la canción que está sonando en ese momento. Perfecto para nuestros propósitos.

    image

    Lamentablemente, las máscaras de artista y álbum no devuelven nada. Supongo que depende de lo bien (o mal en este caso) que lo hayan implementado en cada emisora. De todas formas para una primera prueba nos sobra.

    Integrando todo en Python

    Ya que tenemos la conexión serie entre el Arduino y el RPi funcionando correctamente y el MPD configurado y reproduciendo música, usaremos Python para obtener el artista y la canción que esté sonando en MPD y la enviaremos al Arduino. En mi caso he optado por mostrar el artista en la línea superior del display y la canción en la línea superior.

    Python viene instalado por defecto en Raspbian, pero no así la librería PySerial, que necesitaremos para enviar y recibir información de forma sencilla por la conexión serie del RPi. La podemos instalar escribiendo en la terminal:

    sudo apt-get install python-serial

    Solo queda crear el programa en Python. Copio el código del programa y lo comento a continuación:

      1: import serial, subprocess, time, shlex
      2: 
      3: ser = serial.Serial('/dev/ttyUSB0', 9600)
      4: 
      5: serial_out = ""
      6: serial_in = ""
      7: com_radio = 'mpc current -f "%name%"'
      8: com_song = 'mpc current -f "%title%"'
      9: 
     10: 
     11: while True:
     12: 	args = shlex.split(com_song)
     13: 	str_song = subprocess.check_output(args)
     14: 	list = str_song.split(' - ')
     15: 
     16: 	serial_out = "UP "
     17: 	serial_out += list[0]
     18: 	serial_out += '\n'
     19: 	ser.write(serial_out)
     20: 	print "RPi->Arduino:",serial_out
     21: 
     22: 	time.sleep(1)
     23: 
     24: 	serial_out = "DW "
     25: 	serial_out += list[1]
     26: 	serial_out += '\n'
     27: 	ser.write(serial_out)
     28: 	print "RPi->Arduino:",serial_out
     29: 	
     30: 	time.sleep(5)

     


    En la línea 1 importamos las librerías que usaremos en el programa. En concreto usaremos serial para la conexión serie con el Arduino, subproccess para poder invocar a MPC desde el programa y recoger el artista y la canción que está sonando en cada momento, time para poder establecer un tiempo de espera entre comprobaciones y shlex para ayudarnos a manipular cadenas de caracteres.


    En la línea 3 abrimos la comunicación serie. Tenemos que indicar el dispositivo con el que nos queremos comunicar (en mi caso /dev/ttyUSB0) y la velocidad de transmisión, que son los 9600 baudios.


    En las líneas 5 a 8 declaramos las cadenas que vamos a utilizar para enviar y recibir información (serial_in y serial_out) y las cadenas que vamos a utilizar para obtener el nombre de la estación, el artista y el título de la canción (com_radio y com_song)


    En las líneas 11 a 30 tenemos un bucle infinito. En él comprobamos el nombre de la canción y lo enviamos al Arduino: en la línea 12 separamos la cadena de comandos necesaria para obtener el artista y canción para adaptarlo al formato que necesita Python y lo guardamos en args que es una lista de los parámetros que ejecutaremos.


    En la línea 13 ejecutamos un subproceso y almacenamos el resultado en str_song. Es exactamente igual que si ejecutásemos el contenido de com_song en el terminal y almacenásemos lo que devuelve el terminal en la cadena str_song. En este caso se estamos pidiendo el campo title a MPC, que ya hemos visto antes que nos devuelve una cadena con [nombredelartista] – [nombredelacancion]. Eso mismo es lo que contendrá str_song.


    En la línea 14 lo que hacemos es separar esa cadena en una lista en la que el primer elemento sea el nombre del artista, y el segundo el nombre de la canción. Para ello usamos el método split y le especificamos que nos separe cuando encuentre un espacio, un guión y un espacio.


    En las líneas 16 a 20 construimos la cadena que enviaremos al Arduino: como queríamos mostrar el artista en la línea superior del display, tendremos que escribir una cadena que comience con “UP “, concatenar el nombre del artista (que esta en list[0]) y añadir un carácter de fin de línea. Por último, en la línea 19 enviamos la cadena que hemos construido al Arduino. Como veis, la sintaxis de Python es muy sencilla, clara y concisa; y permite abstraernos de muchos detalles de bajo nivel.


    En la línea 22 esperamos un segundo para dar tiempo a que el Arduino reciba y procese el artista (antes de que me lo digáis, se que es una chapuza, y más cuando el propio Arduino está respondiendo con un OK cuando recibe los datos correctamente, lo cambiaré más adelante).


    En las líneas 24 a 28 hacemos exactamente el mismo proceso pero con la canción y lo enviamos también al Arduino. En ambos casos imprimimos las cadenas que estamos enviando por la terminal para detectar posibles errores.


    En la última línea simplemente esperamos 5 segundos antes de volver a comprobar si ha cambiado la canción para evitar estar comprobando todo el tiempo.


    Probando todo


    Ya sólo queda probar que todo funcione correctamente, así que comprobamos que MPC esté reproduciendo, que el Arduino esté conectado y que el display esté mostrando Arduino Radio v0.0. Podemos también arrancar el minicom para monitorizar qué se envía y se recibe por el puerto serie. Sólo queda ejecutar en programa de Python (yo lo he llamado radio.py) con:


    python radio.py


    image




    IMG_0412


    Parece que efectivamente todo funciona perfectamente.


    Con esto ya tenemos la base de lo que será nuestra radio. En resumen, hemos conseguido configurar el RPi, controlar desde línea de comandos un reproductor de audio, añadir streams de radios online y reproducirlos, obtener los metadatos de las canciones que están sonando y enviarlos a un LCD controlado por un Arduino mediante conexión serie. No está nada mal. Ahora toca pulir los códigos y ampliar funcionalidades. En concreto, mi objetivo ahora es conseguir que al pulsar un botón del LCDKeypad (el shield del Arduino) podamos detener y reanudar la reproducción de la radio. ¡Nos vemos en el siguiente post!

    domingo, 26 de agosto de 2012

    Mostrando canción en LCD con Arduino (I)

    En la última imagen del post anterior se podía ver cómo la canción que se reproducía actualmente en la radio online se mostraba en una pantalla LCD. En este post explicaré cómo lo estoy haciendo y aprovecharé para introducir la comunicación en serie entre el RPi y el Arduino.

    No me voy a extender mucho describiendo el Arduino, ya que hay muchísima información disponible en la red. Básicamente es una plataforma basada en un microcontrolador AVR cuyo diseño es completamente libre así como su IDE y SDK. Se utiliza mucho en multitud de proyectos relacionados con la electrónica e incluso con la robótica, por ejemplo para controlar impresoras 3D, resolver cubos de Rubik o realizar mediciones mediante diversos sensores. Su consumo es muy reducido y puede funcionar incluso con una pila de 9V.

    Debido a la gran cantidad de librerías que existen para Arduino, es relativamente sencillo controlar muchos dispositivos como pantallas LCD, matrices de LEDs, sensores de todo tipo, motores, etc. Además, es posible extenderlo con otros circuitos denominados shields, que le dotan de más posibilidades. Existen shields que incluyen conexión de red, conexión Bluetooth, lector de tarjetas SD, incluso conexión GSM.

    En mi caso, para la prueba he usado un Arduino Duelillamove (el de toda la vida) con un shield LCD Keypad, que incluye un pequeño display LCD de 16 caracteres y 2 líneas y 4 botones programables.

    IMG_0405

    Ambos dispositivos los compré en eBay, que suelen salir más baratos que en tiendas de electrónica online. Para conectarlos basta con encajar el shield en la parte superior del Arduino, como muestra la imagen:

    IMG_0411

    En realidad, el shield lo único que hace es conectar de forma sencilla y sin soldaduras la pantalla y los botones con el Arduino, lo que es bastante más cómodo para las pruebas que conectar todo a mano, ya que queda más compacto. Seguramente más adelante usaré directamente una pantalla y unos botones o un receptor de IR para controlar la radio con un mando a distancia. Las pantallas de este tipo se pueden encontrar muy baratas por eBay. Yo tengo una pequeña colección Risa.

    IMG_0406

    Aquí podéis ver una pantalla de 20x4 caracteres en la parte de abajo a la derecha, y varias de 16x2 de diferentes colores. Seguramente acabe utilizando la de 20x4, que permite mostrar más información al usuario de la radio.

    También existen otro tipo de pantallas que pueden ser más apropiadas para otros proyectos. Por un lado están las matrices de LEDs, que son más complicadas de programar, ya que las librerías no están tan depuradas y se trabaja a muy bajo nivel. En mi caso utilicé este tipo de pantalla para un proyecto ya acabado de reloj despertador con Arduino.

    IMG_0407

    Como se puede ver en la imagen, la pantalla es simplemente una matriz de LEDs que pueden estar o no iluminados. También existen matrices de LEDs RGB que permiten elegir el color de cada LED, aunque son bastante más caras.

    Otro tipo de pantallas son las de segmentos, parecidas a las de las calculadoras.

    IMG_0408

    La imagen de arriba es simplemente un termómetro usando un Arduino y un sensor de temperatura LM35 (a la derecha del display).

    Existen aún más tipos de pantalla. Las hay incluso gráficas, como la de muchas calculadoras avanzadas. Sin embargo, para la radio, las pantallas de caracteres me parecen mas adecuadas y me permiten aprovechar material ya comprado.

    Para empezar a trabajar con el Arduino, simplemente hay que descargarse el IDE desde la página oficial, descomprimirlo en donde deseemos y ejecutarlo. Ahora podemos conectar al PC el Arduino y se instalará automáticamente. Por último podemos cargar un programa de prueba desde Archivo –> Ejemplos. Yo he elegido Blink:

    image

    Con el Arduino conectado basta con pulsar en cargar (el icono con la flecha hacia la derecha de la parte superior de la interfaz) y el IDE se encarga de programar el Arduino de reiniciarlo para ejecutar el programa.

    Una vez probado el funcionamiento, he realizado un pequeño programa para permitir que el Arduino muestre el artista y la canción que se está reproduciendo en el display del shield. Pongo el código y lo comento a continuación:

      1: #include <LiquidCrystal.h>   
      2: #include <LCDKeypad.h>   
      3:     
      4: LCDKeypad lcd;   
      5: String serialIn;   
      6: String serialOut;   
      7: boolean serialComplete = false;   
      8:     
      9: void setup(){  
     10:   Serial.begin(9600);  
     11:     
     12:   lcd.begin(16,2);  
     13:   lcd.clear();  
     14:   lcd.print("Arduino Radio");  
     15:   lcd.setCursor(0,1);  
     16:   lcd.print("V 0.0");  
     17:   serialIn.reserve(200);  
     18:     
     19:   Serial.println("INITOK");   
     20: }  
     21:    
     22: void loop(){  
     23:   if(serialComplete){  
     24:      Serial.println(serialIn);  
     25:      if (serialIn.startsWith("UP")){  
     26:        lcd.setCursor(0,0);  
     27:        lcd.print("                ");  
     28:        lcd.setCursor(0,0);  
     29:        lcd.print(serialIn.substring(3));  
     30:          
     31:        Serial.println("OK");  
     32:      }  
     33:        
     34:      else if (serialIn.startsWith("DW")){;  
     35:        lcd.setCursor(0,1);  
     36:        lcd.print("                ");  
     37:        lcd.setCursor(0,1);  
     38:        lcd.print(serialIn.substring(3));  
     39:          
     40:        Serial.println("OK");  
     41:      }  
     42:        
     43:      else{  
     44:        Serial.println("KO");  
     45:      }  
     46:    
     47:      serialIn = "";  
     48:      serialComplete = false;  
     49:   }  
     50:   Serial.println("Loop");  
     51:   delay(1000);  
     52: }  
     53:    
     54:    
     55: // Reads the serial input and stores in serialIn  
     56: void serialEvent() {  
     57:   while (Serial.available()) {  
     58:     // get the new byte:  
     59:     char inChar = (char)Serial.read();   
     60:     // add it to the inputString:  
     61:     if(inChar != '\n'){  
     62:       serialIn += inChar;  
     63:     }  
     64:     // if the incoming character is a newline, set a flag  
     65:     // so the main loop can do something about it:  
     66:     if (inChar == '\n') {  
     67:       serialComplete = true;  
     68:     }  
     69:   }  
     70: }



    Las dos primeras líneas cargan las librerías necesarias para controlar la pantalla LCD y el Shield LCDKeypad. Esta versión del LCDKeypad hereda de LiquidCrystal, así que es posible utilizar todas las funciones de LiquidCristal desde la librería LCDKeypad). En principio LiquidCrystal.h no es necesario incluirlo, aunque habrá que hacerlo cuando cambiemos el shield por una pantalla independiente.


    Las líneas 4 a 7 declaran las variables globales que vamos a utilizar, en concreto un objeto LCDKeypad que representa nuestro shield, dos cadenas para poder recibir y enviar texto a través del puerto serie y un booleano que nos indique si la transmisión de datos ha terminado o no.


    La función setup (líneas 9 a 20) se ejecuta una sola vez en cuanto se enciende el Arduino, y se suele usar para inicializar el sistema. En mi caso, inicio la conexión serie a 9600 baudios (línea 10), inicializo el display (línea 12) indicando que el display es de 16 caracteres y dos líneas.


    En la línea 13 borro cualquier carácter que pudiera haber en la pantalla y a continuación escribo Arduino Radio (por defecto el cursor está en la posición 0,0 después de un clear), me desplazo a la columna 1 y escribo la versión.


    En la línea 17 reservo 200bytes para la cadena de entrada y envío un INITOK por el puerto serie para que el dispositivo receptor (en este caso el RPi) sepa que el Arduino ya se ha inicializado.


    Una vez ejecutada la función setup, Arduino ejecuta de forma periódica la función loop (líneas 22 a 52). En ella primero comprobamos que se haya recibido un envío por puerto serie (de lo que se encarga la función serialEvent que se puede ver más abajo). En ese caso, mira los dos primeros caracteres:


    Si los caracteres son “UP” escribe el resto de la cadena en la línea superior de la pantalla y envía un “OK”.


    Si los caracteres son “DW” escribe el resto de la cadena en la línea inferior de la pantalla y envía un “OK”.


    Si los caracteres no son ni “UP” ni “DW”, no hace nada y envía “KO” por el puerto serie, para indicar que no se ha procesado el mensaje.


    Por último, borra la cadena de entrada para poder utilizarla de nuevo, y pone la variable serialComplete a false para prepararse para el siguiente envío del RPi. La última línea de la función es un delay de 1s para que no esté ejecutando continuamente el bucle, sino que espere un segundo entre ejecuciones.


    La función serialEvent (líneas 56 a 70) se activa cuando Arduino detecta datos entrando por el puerto serie. En este caso lo que hacemos es almacenar los datos en la cadena serialIn y activar la variable serialComplete una vez que hayamos terminado la cadena (en nuestro caso, al recibir un carácter de salto de línea).


    La idea de este programa de ejemplo, es que podamos parsear el artista y la canción (o lo que queramos) desde el RPi, y lo vayamos enviando al Arduino por el puerto serie, actuando el Arduino como un simple controlador de la pantalla, que procese las órdenes que le llegan y muestre la información en el display. Más adelante podremos introducir otras órdenes más complejas y dejar que el Arduino se encargue de actualizar el display. Por ejemplo podríamos utilizar un comando desde el RPi para obtener la cobertura Wifi, y enviar un comando al Arduino como:


    WF 78%


    Y que éste de encargase de actualizar sólo la zona de la pantalla donde se muestra la cobertura Wifi, evitando actualizar toda la pantalla.


    Si ejecutamos este programa en el IDE de Arduino, podemos activar el monitor serie y ver cómo efectivamente, se recibe el INITOK, el Loop en cada bucle y como procesa las órdenes (que le podemos escribir directamente en el monitor):


    image


    En el siguiente post veremos cómo reproducir emisoras con el MPD y MPC, cómo extraer los datos de la canción y cómo enviarlos a RPi utilizando Python.


    Aprovecho para comentar que lo más seguro es que los códigos que vaya colgando no sean lo más correcto en cuanto a programación, tolerancia a fallos y demás. Conforme vaya avanzando en el proyecto iré puliendo los códigos, pero se agradece cualquier comentario y/o crítica constructiva que pueda servir para mejorar.