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.









5 comments:

  1. Muy bueno el post, pero tengo un gramn problema al querer correrlo he colocado todas las librerias por haber como el mail.jar, ipcontrolP5.jar y me arroja dos errores, por favor no se si puedes ayudarme, gracias.
    Estoy con el IDE de arduino v1.0.3
    El error es import javax.mail.Authenticator; (variable or field 'controlEvent' declared void)
    El otro error es Serial port; (variable or field 'controlEvent' declared void).
    De ante mano muchas gracias.

    ReplyDelete
  2. hola lo he probado y el error que me da es sobre properties dice que ese nombre no existe
    soy Antonio jose

    ReplyDelete
  3. te falla el processing o la partr de arduino?

    ReplyDelete
  4. hola
    cuando la temp. llega a 30 EL processing se atora y me genera errores.
    cuando quiere enviar el correo

    saben a que se debe?

    ReplyDelete
  5. Hola, estoy desarrollando un curso de processing para android, orientado a proyectos con arduino, el enlace es:

    https://www.youtube.com/channel/UCyWRJgfJMu3fiW4xO0FmB1w/videos?sort=dd&shelf_id=0&view=0

    ReplyDelete