lunes, 21 de mayo de 2012

Proyecto de Integrados - Arduino Facebook RSS Reader

Descripción:

Mediante una pequeña pantalla LCD de 16x2, el Arduino muestra las notificaciones de un determinado usuario de Facebook(en este caso yo). Estas notificaciones son descargadas por un script de Python, que usando la librería feedparser graba las últimas 10 notificaciones, y las envía mediante conexión serial al Arduino, que después las mostrará en el LCD.

Materiales:
  • Arduino UNO R3
  • Protoboard
  • Display LCD 16x2(JHD 162A)
  • Potenciómetro rotacional lineal.
  • Cable para conexiones
  • Cable USB-Arduino
Diagrama de Conexiones

Circuito

Para conectar el display LCD al arduino, se conectan los siguientes pines:
  • Pin LCD RS al pin digital 12
  • Pin LCD Enable al pin digital 11
  • Pin  LCD D4 al pin digital 5
  • Pin  LCD D5 al pin digital 4
  • Pin  LCD D6 al pin digital 3
  • Pin  LCD D7 al pin digital 2
  • Pin LCD Bklt - a GND.
  • Pin LCD Bklt + al pin 13 del Arduino.
Adicionalmente, se conecta un potenciómetro de rotación lineal a +5V y GND del Arduino, con su salida al pin VO(pin 3) del display LCD.

Funcionamiento

Funcionamiento del Display LCD

El LCD tiene un interfaz paralelo, significando esto que el microcontrolador tiene que manipular varios pines del interfaz a la vez para controlarlo. El interfaz consta de los siguientes pines:
  • Un pin de selección de registro (RS) que controla en qué parte de la memoria del LCD estás escribiendo datos. Puedes seleccionar bien el regisro de datos, que mantiene lo que sale en la pantalla, o un registro de instrucción, que es donde el controlador del LCD busca las instrucciones para saber cual es lo siguiente que hay que hacer.
  • El pin de lectura/escritura (R/W)que selecciona el modo de lectura o el de escritura.
  • Un pin para habilitar (enable) que habilita los registros.
  • 8 pines de datos (D00-D07). Los estados de estos pines (nivel alto o bajo) son los bits que estás escribiendo a un registro cuando escribes, o los valores de lectura cuando estás leyendo.
Hay también un pin de contraste del display (Vo), pines de alimentación (+5V y GND) y pines de retro-iluminación (Bklt+ y Bklt-), que te permiten alimentar el LCD, controlar el contraste del display, o encender y apagar la retro-iluminación, respectivamente.

El proceso de controlar el display involucra la colocación de los datos que componen la imagen de lo que quieres mostrar, en los registros de datos, y luego, colocar las instrucciones, en el registro de instrucciones. La librería LiquidCrystal te simplifica todo este proceso de forma que no neesitas saber las instrucciones de bajo nivel.

Los LCD-s compatibles con Hitachi pueden ser controlados de dos modos: 4 bits u 8 bits. El modo de 4 bits requiere siete pines de E/S de Arduino, mientras el modo de 8 bits requiere 11 pines. Para mostrar texto en la pantalla, puedes hacer la mayoría de las cosas en modo 4 bits.

Funcionamiento del Arduino
El Arduino sirve de interfaz entre el script de python que descarga las notificaciones, y como se muestran en el display. El IDE de Arduino tiene una librería llamada LiquidCrystal, que ayuda bastante al manejo de un display conectado con el Arduino. Esta librería permite a la placa Arduino controlar displays LCD basados en el chipset Hitachi HD44780 (o compatibles), que se encuentra en la mayoría de LCDs de texto. La biblioteca trabaja en modo 4-bit o en 8-bit (es decir, por medio de 4 u 8 líneas de datos, además de RS, ENABLE, y, opcionalmente, las líneas de control RW).

Contiene funciones como:
  • begin(). Usada para definir las dimensiones del display.
  • clear(). Usada para borrar los contenidos del display.
  • write(), print(). Usadas para mostrar contenido en el display.
  • setCursor(). Usada para definir donde escribir en el display.
También cuenta con una función para poder hacer scroll al texto, es decir moverlo fuera de la pantalla para poder mostrar largas cadenas de texto que normalmente no podrían ser mostradas. Pero yo no hice uso de esta función, ya que el scroll que hace es total, y al mover todo el texto, cosas que yo deseo sean estáticas también se moverían por lo tanto tuve que implementar un scroll propio, que será explicado el el código.

Entonces, la función del Arduino es recibir las notificaciones desde el script de python mediante una conexión serial, para después imprimirlas en la pantalla LCD. La conexión serial me permite enviar una cadena de texto u otra información char por char, entonces para que el Arduino pueda identificar que información se está enviando, antes de enviar cualquier cosa, el primer char es un identificador, como la letra "N" para las notificaciones, o la letra "T" para actualizar el reloj. Con esto podemos asegurarnos que no se confunda la información. 

Funcionamiento del Código Python

El código en Python es un script simple, que usando la librería feedparser, descarga las notificaciones de mi Facebook personal(copiando el link rss de mi Facebook, por lo cual EN EL CÓDIGO QUE PUBLICARÉ SERÁ OMITIDO, Y ES IMPORTANTE SI DESEAN PROBARLO ESPECIFICAR SU PROPIO LINK RSS DE FACEBOOK).

Después de descargarlas, les realizo varias funciones a cada notificación, como quitarles acentos para evitar problemas en el display, cortarlos a un tamaño que pueda ser mostrado, y demás.Primero el código establece la conexión con el Arduino, después actualiza la hora con la hora de la computadora, entonces las notificaciones son enviadas al Arduino. Y el código espera hasta que se cumple un tiempo determinado(5 minutos aproximadamente por defecto) para poderse reiniciar y actualizar las notificaciones.


Imágenes del Circuito en Funcionamiento





Video


Código Python:

#!usr/bin/python
import serial, time
import feedparser
import unicodedata
  
#Funcion usada para quitar los acentos de las notificaciones, para poder evitar errores al
#mostrar caracteres en el display LCD
def reemplazar_acentos(s):
   return ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn'))
 
#Funcion que maneja operaciones para cortar las notificaciones a una longitud que pueda ser
#mostrada por el display LCD.
def cortar_notificacion(notif):
 string = ""
 list_string = notif.split()
  
 if "evento" in notif:
  for i in range(len(list_string)):
   if list_string[i] == "evento":
    string+=list_string[i]
    string+=" "
    break
   else:
    string+=list_string[i]+" "
  string+=list_string[i+1]+" "
  string+=list_string[i+2]+"."
  
  
 elif "solicitud" in notif:
  for i in range(len(list_string)):
   if list_string[i] == "solicitud":
    string+=list_string[i]
    string+="."
    break
   else:
    string+=list_string[i]+" "
   
 else:
  for i in range(len(notif)):
   if notif[i] == ":":
    string+="."
    break
   elif notif[i] == ".":
    string+=notif[i]
    break
   else:
    string+=notif[i]
 if len(string) > 60:
  new_string = ""
  for i in range(57):
   new_string+=string[i]
  new_string+="..."
  string = new_string
 return string
 
#Funcion que descarga las notificaciones y las guarda en una lista, con el string de la notificacion, y su fecha(no usada actualmente)
def get_notificaciones():
 parser = feedparser.parse('Link al RSS de Facebook')
 notificaciones = []
 for i in range(0, 10):
  notificaciones.append(cortar_notificacion(reemplazar_acentos(parser.entries[i].title)))
 return notificaciones
 
#Funcion que da formato a la fecha descargada
def formatear_fecha(fecha):
 datos = fecha.split()
 dia = datos[2]
 mes = datos[3]
 hora = datos[4]
 new_hora = ""
 for i in range(len(hora)-2):
  new_hora += hora[i]
 hora = new_hora
 datos = "%s/%s %s"%(dia, mes, hora)
 return datos
  
if __name__ == "__main__":
 try:
  #Abrimos el puerto serial, este puede variar dependiendo de la conexion, en este ejemplo es
  #COM3
  print "Abriendo puerto serial"
  ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
  #Esperamos 2 segundos, es importante o se puede perder la primera escritura en la conexion
  while True:
   time.sleep(2)
   #Actualizamos el reloj y obtenemos las notificaciones
   print "Actualizando el reloj"
   notificaciones = get_notificaciones()
   #Damos el formato de fecha que deseamos al tiempo actual, y lo enviamos al Arduino
   tiempo = time.strftime("%H:%M:%S", time.localtime())
   ser.write("T")
   ser.write(tiempo)
   print tiempo
   #Obtenemos el tiempo actual para compararlo despues y reiniciar cuando llegue a un
   #determinado tiempo para actualizar las notificaciones
   t = time.time()
   control = True
   i=0
   #Repetimios hasta que control cambie a false(se cumpla el tiempo de actualizacion)
   while(control):
    #Leemos un string enviado desde el arduino, este string debe ser un 'YES' o un
    #'NO', esto para poder saber si el Arduino esta listo para recibir una nueva
    #notificacion
    string = ser.read(100)
    #Si esta listo, y aun no enviamos las 10 notificaciones, escribimos una notificacion
    if "YES" in string and i<10:
     print 'yes'
     ser.write("N")
     ser.write(notificaciones[i].encode('utf-8'))
     print "Escribiendo notificacion: %s"%notificaciones[i]
     i+=1
    #Comparamos el tiempo actual con el inicial, si se cumplio el tiempo solicitado salimos
    #del while
    if "REINICIAR" in string:
        control = False
       #Abrimos y cerramos el puerto para reiniciar la conexion, y actualizar, esperamos de nuevo 2 segundos
       print "Reiniciando"
       ser.write('R')
    
 except serial.SerialException as e:
  print e

Código Arduino:

#include <TimedAction.h>
#include <LiquidCrystal.h>
//Variable LCD que usaremos para controlar la pantalla
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
//Variables para controlar el reloj
byte hora = 12, minu = 0, seg = 0;
//Variable usada para contar el tiempo que paso desde
//otra comparacion de tiempo, y saber cuando se cumple
//1 segundo sin usar delays
long previousMillis = 0; 
//Strings usadas para almacenar las notificaciones
String string = "";
String notifications[10];
//Variables para controlar cual notificacion se
//muestra actualmente
int currentString = 0, maxim = 0, num = 0;
//Especie de threads, que corren cada cierto tiempo
TimedAction scrollAction = TimedAction(250, scroll);
TimedAction readSerialAction = TimedAction(1, readSerial);
 
//Setup del Arduino, definimos el LCD, y encendemos el pin 13
//que esta conectado al backlight del display LCD
//Tambien inicializamos una conexion serial con 9600 de baud rate
//e imprimimos YES para dar a conocer al script de python que
//es posible leer  una notificacion
void setup(){
  lcd.begin(60, 2);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  Serial.begin(9600);
  Serial.print("YES");
}
 
//Funcion simple que converte los numeros del reloj(ints)
//en un string, agregandole un 0 si es menor a 10 para poder
//mostrarlo correctamente.
String getClockShowNumber(int number){
  if(number < 10){
    return ("0"+number);
  }
  else{
   return (""+number);
  }
}
//Funcion que actualiza el reloj si ya se cumplio 1 segundo
//desde la ultima vez que se actualizo
void updateDisplay(){
    delay(250);
    unsigned long currentMillis = millis();
    if(currentMillis - previousMillis > 1000) {
      previousMillis = currentMillis;
      Clock();
      char text_buffer[51];
      sprintf(text_buffer,"<%02d/%02d>%02d:%02d:%02d                                   ",
      num+1, 10, hora, minu, seg);
      lcd.setCursor(0, 0);
      lcd.print(text_buffer);
      text_buffer[8] = 0;
    }
}
//Funcion que checa los valores del reloj para sumarlos
//correctamente
void Clock(){
    //updateDisplay();
    seg = seg + 1;
    if(seg > 59){
      seg = 0;
      minu = minu + 1;
      if(minu > 59){
        minu = 0;
        hora = hora + 1; 
        if(hora>23){
          hora = 1;
        }
      }
    }
  }
  
//Funcion que hace un scroll especial al LCD, dando scroll
//solamente a la segunda linea del display, y dejando la
//primera intacta
void scroll(){
  String temp_string = notifications[num] + "   ";
  for(int i = 0; i < temp_string.length()-16; i++){
    lcd.setCursor(0, 1);
    for(int j=i; j<16+i; j++){
        //readSerialAction.check();
        lcd.print(temp_string[j]);
    }
    updateDisplay();
  }
}
 
//Funcion que actualiza el reloj despues de recibir la hora
//actualizada desde el script de python en la computadora
void updateClock(String time){
   //Arreglo[hora, min, seg]
   String temp[3] = {"", "", ""};
   int currentString = 0;
   for(int index = 0; index < time.length(); index++){
     if(time[index] == ':'){
       currentString++;
     }
     else{
       temp[currentString] += time[index];
     }
   }
   //Serial.print(temp[0]+":"+temp[1]+":"+temp[2]);
    hora = temp[0].toInt();
    minu = temp[1].toInt();
    seg = temp[2].toInt();
}
 
//Funcion que lee trafico desde la conexion serial, identifica
//que tipo de datos se recibiran, y los almacena o actua dependiendo
//del tag especificado.
void readSerial(){
  int i = 0;
  boolean control = true;
if(Serial.available()){
          switch(Serial.read()){
            case 'R':
              Serial.print("NO");
              //Serial.print("Reset");
              for(int i=0; i<10; i++){
                notifications[i] = "";
              }
              //lcd.clear();
              currentString = 0;
              maxim = 0;
              num = 0;
              Serial.print("YES");
              break;
               
            case 'N':
              Serial.print("NO");
              //Serial.print("Notification");
              while(control){
                updateDisplay();
                string = "";
                char c;
                while(Serial.available() > 0){
                     control = false;
                     c = Serial.read();
                     string+=c;
                }
              }
              notifications[currentString] = string;
              currentString++;
              maxim = currentString;
              if(maxim == 10){
                Serial.print("REINICIAR");
                delay(100);
              }else{
                 Serial.print("YES"); 
              }
              break;
            case 'T':
              Serial.print("NO");
              //Serial.print("Clock");
              String time = "";
              while(control){
                updateDisplay();
                char c;
                while(Serial.available() > 0){
                     control = false;
                     c = Serial.read();
                     time+=c;
                }
              }
              updateClock(time);
              Serial.print("YES");
              break;
          }
        }
        else{
         if(maxim > 0){
            if(num < maxim-1){
                num++;
            }
            else{
                num = 0;
            }
            updateDisplay();
            scrollAction.check();
             
          }
        }
}
 
//Loop, simplemente checa cada cierto tiempo si hay trafico serial
void loop(){
        readSerialAction.check();
 
  }

Referencias: 

2 comentarios:

  1. Como puedo conectar este tipo de pantallas a un alcoholimetro con sensor de alcohol mq-3 usando arduino uno. tomando en cuenta que el nivel de alcohol lo estoy viendo mediante los leds.

    ResponderEliminar
  2. oye me puedes ayudar en esta linea de codigo de arduino

    void updateClock(String time){
    //Arreglo[hora, min, seg]
    String temp[3] = {"", "", ""};
    int currentString = 0;
    for(int index = 0; index < time.length(); index++){
    if(time[index] == ':'){
    currentString++;
    }
    else{
    temp[currentString] += time[index];
    }
    }
    //Serial.print(temp[0]+":"+temp[1]+":"+temp[2]);
    hora = temp[0].toInt();
    minu = temp[1].toInt();
    seg = temp[2].toInt();
    }
    me marca error en toInt y en void updateClock(String time) porfavor, estoy haciendo una copia de tu proyecto por que un profesor me lo pidio. :)

    ResponderEliminar