Wednesday 30 May 2012

LCD Shield with keypad para Arduino

Hola de nuevo, tengo en mente la creación de un sistema de control de accesos para la sala de servidores utilizando RFID para cuando sepa mejor como funciona arduino; una de las cosas que quiero ponerle es una pequeña pantalla LCD que muestre el nombre de la persona que se identifica, por lo que el otro día pedí una LCD Shield como la de la foto en dealextreme por unos 9$ que viene a ser unos 6€.


Un shield de arduino es una placa con una funcionalidad (las hay para wifi, controlador de motores, bluetooth, etc) que encaja perfectamente sobre la placa de arduino. por lo que no es necesario poner ningún tipo de cable para poder usar la funcionalidad inherente del shield.

Este tipo de pantalla no me servirá para mi proyecto ya que al ser un shield me limita las posibilidades de colocación de la pantalla, pero puede serme útil a la hora de entender como funcionan las pantallas LCD y como se comunica con Arduino.

La shield viene además de con la pantalla con una serie de botones que van todos conectados a una entrada analógica la A0 de arduino. Cada vez que se pulsa un botón arduino recibe un valor entre 0 y 1100 aproximadamente  en función del botón pulsado. Esto está muy bien ya que solo usaremos una entrada analógica para manejar un total de 5 botones si excluimos el reset.


Lo primero que intenté hacer al sacar el shield del sobre fue intentar probar el ejemplo que viene con el compilador de arduino que es tal que así:




// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
}

void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis()/1000);
}

Al ver que no funcionaba, entendí que el problema eran los pines que utiliza el lcd, el sketch del ejemplo utiliza (12,11,5,4,3,2), pero con mi shield no venía info alguna que dijera que pines de arduino utilizaba, por lo que el siguiente paso fue buscar info en la página de delaextreme que pines utilizan y encontré que los pines a usar eran (8, 9, 4, 5, 6, 7).

Sin embargo tras cambiar el código del programa seguía sin funcionar... El shield trae un potenciómetro que regula el contraste de las letras que aparecen en la pantalla y tras chequear lo con el polímetro ví que el valor que devolvía siempre era el mínimo... la placa estaba defectuosa. Por suerte, tenía a mano un potenciómetro que soldé en lugar del antiguo y el programa de hola mundo, empezó a mostrar la frase por la pantalla del arduino..





Ahora que todo funciona, solo queda hacer un programita para poder explorar un poco las posibilidades de la pantalla y de los botones de nuestro shield.








//Incluimos la librería de LCD que trae arduino
#include <LiquidCrystal.h>
//Indicamos los pins que vamos a usar para 
//comunicarnos con la pantalla
//En mi caso, al usar una lcd shield los botones
//son los siguientes
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
//Creamos una constante para tomar 
//indicar a que pin está conectado
const int analogInPin = A0; 
//Creamos una variable para almacenar los 
//valores tomados por los botones
int sensorValue = 0;  

//Inicia el void setup que solo se ejecuta
//una vez
void setup() {
  //iniciamos el puerto serie
  //lo hemos usado para debugginh
  //y así ver los valores que devuelve 
  //al pulsar cada uno de los botones
  Serial.begin(9600); 
  //iniciamos también la pantalla lcd y le 
  //indicamos el número de columnas x el número de filas
  lcd.begin(16, 2);
  //Imprimimos por pantalla la frase HOLA MUNDO!
  lcd.print("HOLA MUNDO!");
  //Movemos el cursor a la segunda línea
  lcd.setCursor(0, 2);
  //Ponemos un mensaje en la segunda línea
  //que nos pide que pulsemos un botón
  lcd.print("Pulse un boton");
}

//Inicia el loop que se ejecutará 
//mientras el arduino esté conectado
void loop() {
  //Recogemos el valor en el pin analógico
  //que nos dirá que botón hemos pulsado
  sensorValue = analogRead(analogInPin);
  //Para hacer el debuggin hemos usado esta instrucción
  //que nos va mostrando en la pantalla del ordenador
  //el valor devuelto al pulsar cada botón  
  Serial.println(sensorValue); 
  //Ponemos un pequeño retardo para que 
  //podamos leer los valores sin marearnos
  delay(100); 
  //En el debuggin hemos visto que al no tener ningún botón
  //pulsado los valores devueltos están entre 
  //1020 y 1025 por lo que cuando se den valores distintos 
  //a ese rango se ejecutará lo que hay dentro del if
  if (sensorValue <1000 ||  sensorValue>1100) {
    //llamamos a la función limpiar línea que veremos 
    //un poco más abajo
    limpiarLinea();
    //Imprimimos por pantalla lo que nos devuelve
    //la función opcion(int) que veremos más abajo
    //y que comprueba a cuanto equivale el valor del sensor 
    //y en función de éste, nos devuelve una cadena de texto
    lcd.print(opcion(sensorValue));
  }  
}


//Esta función comprobará el valor 
//del pin analógico conectado a los botones
//para determinar que botón hemos pulsado
//y devolver un texto en función de este
String opcion(int sensor){
  //Solo explicaré el primero, ya que el resto de ifs
  //son iguales.
  //En el debug vimos que al pulsar la tecla left, nos devolvía
  //un valor de aprox 479 por lo que cuando el valor del sensor esté 
  //entre 400 y 500 devolverá la cadena de texto izquierda
  if(sensor > 400 && sensor<500){
    return("Izquierda");
  }
  if(sensor > 600 && sensor<750){
    return("Select");
  }
  if(sensor > 300 && sensor<350){
    return("Abajo");
  }
  if(sensor > 100 && sensor<150){
    return("Arriba");
  }
  if(sensor < 10){
    return("Derecha");
  }
}

//Esta función la llamamos cuando queremos borrar 
void limpiarLinea(){
  //Colocamos el cursor la principio de la segunda
  //línea
  lcd.setCursor(0, 2);
  //Con este bucle recorremos toda la línea y la ponemos
  //en blanco
  for (int i = 0; i < 16; i = i + 1) {
    lcd.print(" ");
  }
  //Volvemos a colocar el cursor
  //al principio de la línea
  //para cuando volvamos a escribir algo
  //salga desde el principio de la línea
  lcd.setCursor(0, 2);
}


El resultado del programa es el siguiente:



Como veis es bastante soso, pero a partir de aquí podemos empezar a hacer otros sketchs o mezclar los resultados mostrados por pantalla con algún sensor como el de temperatura...


Thursday 17 May 2012

Control de temperatura usando arduino y processing

Hace un par de días, el aire acondicionado del cuarto de servidores dejó de funcionar por un problema de condesación; por suerte, me dí cuenta antes de irme a casa y pudimos instalar una máquina de aire acondicionado portátil para refrigerar la habitación mientras el equipo principal se reparaba. 


Las altas temperaturas pueden estropear desde los ventiladores hasta los procesadores o discos duros del servidor. Por tanto y ya que llevo un tiempo experimentando con arduino y sus sensores, me dispuse a crear un sistema que nos avisara mediante un email cuando la temperatura del cuarto de servidores suba por encima de lo normal, pudiendo así tomar medidas lo antes posible.


Seguramente, habrá formas mucho más eficientes y sencillas de hacer esto, pero con los conocimientos y los materiales que tengo a mano ésta me pareció la forma más adecuada.

Lo primero era utilizar arduino para que tomase la temperatura de ambiente. Por lo que preparamos el circuito, que queda de la siguiente forma:






Como vemos en el esquema se ha utilizado una placa de montaje, un arduino uno y un sensor de temperatura. El sensor de temperatura no es más que una resistencia que varía en función de la temperatura ambiental, por lo que la cantidad de corriente que pasa desde la pata izquierda que va pinchada a 5v hasta la pata central que va conectada a la entrada analógica A0 del arduino va a estar determinada por la temperatura lo que permite calcular los grados en función del voltaje.

Para que podamos enviar el correo electrónico de forma automática cuando la temperatura sobrepase el máximo marcado, necesitamos crear un programa en arduino que nos de la temperatura y nos la envía a través del puerto serie a un ordenador.

El programa en la parte de arduino queda de la siguiente forma:

int anapin = 0;
int gcent=0;
int gfar=0;
float val=0;
void setup(){
  Serial.begin(9600);
}
void loop(){
  val = getVoltage(anapin);
  val = (val - .5)*100;
  gcent=val;
  gfar=gcent*1.8+32;
  Serial.print(String(gcent)+","+String(gfar)+".");
  delay(50);
}
float getVoltage(int anapin){
  return (analogRead(anapin) * .004882814);
}


Como vemos, lo único que hace el programa es iniciar el uso del puerto serie dentro del void setup, y a contuniación dentro del void loop leer continuamente el valor del pin analógico y convertirlo mediante una sencilla fórmula a grados centigrados y luego mandar esto más los grados convertidos a farenheit al puerto serie separando los centigrados de los farenheit por una coma y el siguiente par de valores por un punto; si vemos el resultado en el puerto serie se verá algo como esto:




Hay otra forma de hacerlo que es usando la librería Wire donde no tendríamos que convertir el voltage en grados por lo que el código sería aún más sencillo.

Una vez tenemos nuestro arduino programado, pasaremos a processing, este lenguaje es mayormente gráfico por lo que lo vamos a utilizar para poder mostrar los datos recogidos por el puerto serie en la pantalla y así mismo usaremos la libería de mail de java para enviar el correo electrónico cuando se supere la temperatura umbral.

No he tenido tiempo de aprender mucho de processing, pero es un lenguaje muy intuitivo y en la página web del proyecto (http://www.processing.org/) hay mucha información además de foros donde resolver todas las dudas que puedan surgirnos.

La estructura de processing es muy similar a la de arduino, teniendo un void setup() que se ejecuta una vez y un void draw() que se ejecutará de forma recurrente y que será el encargado de dibujar en pantalla lo que queramos.

El código para coger los datos del puerto serie y mostrarlos por pantalla es el siguiente, intentaré poner comentarios para que el código se entienda:


//Importamos la librería para el uso del puerto serie, 
//en arduino se carga sola, aquí hay que llamarla.
import processing.serial.*; 
//Definimos todas las variables
Serial port;
String tempC="";
String tempF="";
String Data="";
PFont font;
int index=0;
//Este void setup() es similar al de arduino solo se ejecuta al inicio
void setup() 
{
  //Aquí definimos el tamaño de la ventana que 
  //mostraremos por pantalla
  size(600,500);
  //Inicializamos el puerto serie y decimos 
  //que se haga lectura hasta encontrar el punto
  port = new Serial(this, puertoCom, 9600);
  port.bufferUntil('.');
  //Esto es para cargar la fuente por defecto que usaremos en el programa, 
  //la fuente tendrá que estar en la carpeta data del proyecto
  font = loadFont("AgencyFB-Reg-200.vlw");
  textFont(font, 200);}
//Este evento se llama cuando hay entrada en el puerto serie
void serialEvent (Serial port)
{
  //Leemos hasta que se encuentre un punto y guardamos lo leido en Data
  Data = port.readStringUntil('.');
  //Este comando nos sirve para eliminar 
  //el punto del final ya que si no nos lo mostraría por pantalla   
  Data = Data.substring(0,Data.length()-1);
  //buscamos la posición de la coma que separa 
  //los dos valores y guardamos la posición en index
  index = Data.indexOf(",");
  //almacenamos la primera parte del texto en tempC
  tempC= Data.substring(0,index);
  //Almacenamos la segunda parte en tempF
  tempF= Data.substring(index+1,Data.length());
}

//Éste sería el equivalente a void loop() de arduino,
//se ejecuta como un loop constante
void draw()
{
  //definimos el color del fondo negro 0,0,0 es el color negro en RGB
  background (0,0,0);
  //Si la temperatura es inferior a 30 º C
  if (parseInt(tempC)<=30){
    //Pongo el color de los textos en verde fill usa un valor en RGB
    fill(0, 204, 51);
    //Coloco el texto Temp Ok en la posición 20x 150y
    text("Temp OK",20,150); 
  //Si la temperatura es superior a 30ºC
  }else{
    //Pongo el color del texto en ROJO 
    //como señal de advertencia y cambio el  texto
    fill(204, 51, 0);
    text("TEMP!! SM",20,150);
  }
  //Por último colocamos los valores de 
  //temperatura en ºC y ºF, el color será el que traigamos
  //de arriba, es decir en Rojo si es mayor de 30ºC y en Verde si es menor.
  text(tempF+"F",300,340);
  text(tempC+"C",300,499);
}




A continuación una prueba de la salida del programa para valores superiores e inferiores a 30º:



Una vez hecho esto, solo nos quedaría implementar el envío de un correo electrónico. Así mismo, y preveyendo que cuando lo pongamos en producción la conexión se hará mediante cable serie a uno de los servidores en lugar de por usb, vamos a dejar preparada la opción de poder seleccionar el puerto com8 que es el que nos está cogiendo por defecto arduino para el usb, para un puerte serie suele usarse los puertos de com1 a com3 ,por lo que dotaremos al programa de un botón radio que permita elegir entre los puertos com 1, 2, 3 y 8.

Para la primera parte, el envío del correo, encontré en la red esta página (http://www.shiffman.net/2007/11/13/e-mail-processing/) en la que se explica de forma detallada como implementar la librería de mail de java dentro de processing. Para ello vamos a la página de oracle y descargamos la api de email (http://www.oracle.com/technetwork/java/javamail/index.html)l Una vez descargado esto pasamos el fichero mail.jar al processing pinchando en Sketch-> add file.



Una vez hecho esto ya podremos importar las librerías de javax.mail. Shiffman nos da 3 funciones de las que solo usaremos dos auth y sendMail, la tercera CheckMail no la necesitamos para nuestro proyecto por lo que no la usaremos. El código que agregaremos a nuestro programa será el que copiamos de la web de shiffman quedando lo siguiente:


//importamos las librerías
import processing.serial.*;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.*;import javax.mail.internet.*;
//Creamos una variable que nos sirva de interruptor para no 
//enviar infinitos emails cuando la temperatura esté por encima de los 30º
boolean correo=false;

//Creamos la función que servirá para autenticarnos
public class Auth extends Authenticator { 
public Auth() {
super();
}
public PasswordAuthentication getPasswordAuthentication() {   
String username, password;
username = "mi@correo.es";   
password = "MiPasswordDeCorreo";   
//System.out.println("authenticating. . ");   
return new PasswordAuthentication(username, password);
}
}

//Esta función es la que se encarga de enviar el correo
void sendMail() {  
// Create a session String host="smtp.gmail.com"; Properties props=new Properties(); // SMTP Session props.put("mail.transport.protocol", "smtp"); props.put("mail.smtp.host", host); props.put("mail.smtp.port", "587"); props.put("mail.smtp.auth", "true"); // We need TTLS, which gmail requires props.put("mail.smtp.starttls.enable","true"); // Create a session Session session = Session.getDefaultInstance(props, new Auth());try { // Make a new message MimeMessage message = new MimeMessage(session); // Who is this message from message.setFrom(new InternetAddress("mi@correo.es", "Nombre del que envía")); // Who is this message to (we could do fancier 
// things like make a list or add CC's)
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("it@sis.gl", true));   
// Subject and body
message.setSubject("Problema en el cuarto de servidores");
message.setText("La temperatura está demasiado alta, ha superado los 30º");
// We can do more here, set the date, the headers, etc.    
Transport.send(message);
}  catch(Exception e)  {
e.printStackTrace();
}
}


Luego modificamos la función de void draw() para que llame a la función sendMail cuando supere la temperatura límite. Quedando así:

void draw(){
background (0,0,0); 
if (parseInt(tempC)<=30){ 
fill(0, 204, 51);
text("Temp OK",20,150);
//si el boolean correo estaba en  true lo pongo a false para que no se envíen correos
 if (correo==true){
    correo = false; 
  }
}else{
//Aqui la temp>30
  if (correo){
    fill(204, 51, 0);
    text("TEMP!! SM",20,150);
  }else{
    //Envio el correo y pongo la variable a false
    sendMail();   
    correo=true;
    //retardo para evitar que el mensaje se envíe muchas veces por estar
    // la temperatura en valores entre 30 y 31ºC
    delay(100000);
  } 
}
text(TempF+"F",300,340);
text(tempC+"C",300,499);
}

Como vemos usamos el boolean correo para que cuando pasemos de por ejemplo 20 a 31ºC solo se envíe un email, y no volverá a mandarse otro hasta que vuelva a darse tal circunstancia, de otra forma, enviaría correos continuamente cuando se pasaran los 30ºC. 

Así mismo tras el envío, dejamos un delay de 100 segundos ya que hay veces que la temperatura puede bailar entre los 30 y 31ºC durante un rato, de esta forma nos aseguramos que mínimo pasarán 100 segundos hasta que volvamos a recibir otra notificación.

Con esto prácticamente tenomos nuestro proyecto terminado, pero como dijimos antes vamos a dotar a nuestro programa de un botón radio que nos permita elegir el puerto com que queremos usar para la recogida de datos. Para ello, vamos a utilizar otra librería llamada controlP5, con esta librería tal como se detalla en su web podemos crear multitud de botonos, deslizadores y botones radio.

Siguiendo las instrucciones del creador de la librería, importamos usando igual que antes la opción Sketch-> add file de processing para importar el fichero controlP5.jar acto seguido, ya podremos importar la librería en nuestro programa usando el comando import controlP5.*; además de el código correspondiente en void setup() y una función llamada controlEvent() que controlará cuando cambiamos el radio de posición. A continuación os dejo el código completo de la aplicación:

//Importamos la librería para el uso del puerto serie, 
//en arduino se carga sola, aquí hay que llamarla.
import processing.serial.*;
//Importamos las librerias de mail y controlP5
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.*;
import javax.mail.internet.*;
import controlP5.*;

//Definimos todas las variables
Serial port;
String tempC="";
String TempF="";
String Data="";
PFont font;
int index=0;
boolean correo=false;
String puertoCom="COM8";
ControlP5 cp5;
RadioButton r;

//Este void setup() es similar al de arduino solo se ejecuta al inicio
void setup() {
    //Aquí definimos el tamaño de la ventana que mostraremos por pantalla
    size(600,500);
    //Inicializamos el puerto serie y decimos que se haga lectura hasta encontrar el punto
    port = new Serial(this, puertoCom, 9600);
    port.bufferUntil('.');
    cp5 = new ControlP5(this);
    r = cp5.addRadioButton("radioButton")         
        .setPosition(20,400)         
        .setSize(20,20)         
        .setColorForeground(color(020))         
        .setColorActive(color(255))         
        .setColorLabel(color(255))         
        .setItemsPerRow(1)         
        .setSpacingColumn(50)         
        .addItem("serial 1",1)         
        .addItem("serial 2",2)         
        .addItem("serial 3",3)         
        .addItem("serial 8",8)         
    ;
    //Esto es para cargar la fuente por defecto que usaremos en el programa,
    //la fuente tendrá  que estar en la carpeta data del proyecto
    font = loadFont("AgencyFB-Reg-200.vlw");
    textFont(font, 200);
}

//Función que detecta cuando cambiamos el botón radio 
void controlEvent(ControlEvent theEvent) 
{   
    //Cogemos el valor del botón  y lo guardamos en opcion   
    int opcion = parseInt(theEvent.getValue());     
    //Cambiamos el valor de puertoCom a COM1, COM2, COM3 o COM8   
    puertoCom = "COM" + opcion;    
    //Paramos la lectura por puerto serie    
    port.stop();    
    //Limpiamos el caché    
    port.clear();    
    //Volvemos a inicializar el puerto com ahora con el puerto cambiado    
    port = new Serial(this, puertoCom, 9600);    
    port.bufferUntil('.'); 
}


//Creamos la función que servirá para autenticarnos
public class Auth extends Authenticator 
{ 
     public Auth() 
     {  
       super();
     }
     public PasswordAuthentication getPasswordAuthentication() 
     {  
       String username, password;    username = "mi@correo.es";  
       password = "MiPasswordDeCorreo";  
       //System.out.println("authenticating. . ");  
       return new PasswordAuthentication(username, password);
     }
}

//Esta función es la que se encarga de enviar el correo
void sendMail() {  
     // Create a session  
     String host="smtp.gmail.com";  
     Properties props=new Properties();  
     // SMTP Session  
     props.put("mail.transport.protocol", "smtp");  
     props.put("mail.smtp.host", host);  
     props.put("mail.smtp.port", "587");  
     props.put("mail.smtp.auth", "true"); 
    // We need TTLS, which gmail requires  
    props.put("mail.smtp.starttls.enable","true");  
    // Create a session  
    Session session = Session.getDefaultInstance(props, new Auth());
    try  {    
      // Make a new message    
      MimeMessage message = new MimeMessage(session);    
      // Who is this message from    
      message.setFrom(new InternetAddress("mi@correo.es", "Nombre del que envía"));     
      // Who is this message to  
      //(we could do fancier things like make a list or add CC's)  
      message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("it@sis.gl", true));  
      // Subject and body    
      message.setSubject("Problema en el cuarto de servidores");    
      message.setText("La temperatura está demasiado alta, ha superado los 30º");    
      // We can do more here, set the date, the headers, etc.    
      Transport.send(message);  
    }  catch(Exception e)  {    
      e.printStackTrace();  
    }
}

//Este evento se llama cuando hay entrada en el puerto serie
void serialEvent (Serial port){
    //Leemos hasta que se encuentre un punto y guardamos lo leido en Data
    Data = port.readStringUntil('.');
    //Este comando nos sirve para eliminar 
    //el punto del final ya que si no nos lo mostraría por pantalla   
    Data = Data.substring(0,Data.length()-1);
    //buscamos la posición de la coma 
    //que separa los dos valores y guardamos la posición en index
    index = Data.indexOf(",");
    //almacenamos la primera parte del texto en tempC
    tempC= Data.substring(0,index);
    //Almacenamos la segunda parte en tempF
    tempF= Data.substring(index+1,Data.length());
}

//Éste sería el equivalente a void loop() de arduino,
//se ejecuta como un loop constante
void draw()
{
    background (0,0,0);  
    //Si la temperatura no sobrepasa los 30º
    if (parseInt(tempC)<=30){  
      fill(0, 204, 51);
      text("Temp OK",20,150);
      //si el boolean correo estaba en  
      //true lo pongo a false para que no se envíen correos
      if (correo==true){  
          correo = false;  
      }
    }else{
        //Aqui la temp>30
        if (correo){  
            fill(204, 51, 0);  
            text("TEMP!! SM",20,150);
        }else{  
            //Envio el correo y pongo la variable a false  
            sendMail();      
            correo=true;  
            //retardo para evitar que el mensaje se envíe muchas veces por estar     
            // la temperatura en valores entre 30 y 31ºC  
            delay(100000);
        }
    }
    text(TempF+"F",300,340);
    text(tempC+"C",300,499);
}


Pues creo que con esto ya está todo, espero que os haya gustado y que se entienda todo bien, si tenéis alguna duda no dudéis en preguntar. Os dejo con un pequeño vídeo del funcionamiento del programa.




Un saludo y hasta la próxima.