Tuesday 12 June 2012

Control de temperatura v2 (sin necesidad de ordenador)

En el primer post, vimos como controlar la temperatura a través de arduino y hacer que nos enviase un correo electrónico usando processing.

En esta segunda versión, vamos a prescindir del ordenador ya que no siempre tendremos la posibilidad de tener un ordenador conectado en la sala que queremos controlar la temperatura. Para ello, he adquirido una placa de arduino llamada arduino ethernet, la diferencia de esta con el arduino uno que hemos usado anteriormente, es que esta en lugar de traer un conector usb viene con una toma de red, por lo demás es todo más o menos igual.

Arduino Ethernet 
Aclarar que esto no es un shield como el que utilizamos para el lcd, sino que es una placa arduino completa, la única cosa que echaremos de menos es que no trae el puerto usb para programarla por lo que tendremos que tirar de un conversor USB a TTL que cuesta unos 8€.

Conversor USB to TTL

Como ventajas, podemos decir que si vamos a dotar de alguna funcionalidad de red a nuestro arduino, esta es la mejor opción: primero, ahorramos espacio al  no tener que colocar un shield encima; segundo, es más barato comprar esta placa que comprar el arduino y el shield por serparado; y por último, lo que me terminó de convencer y es que venden un módulo que puede soldarse a esta placa y que nos permitirá utilizar POE en caso de que nuestra red disponga de esta característica, por lo que no necesitaremos conectar una fuente de alimentación externa:

Módulo POE
Para nuestro proyecto incial habíamos utilizado un sensor de temperatura TMP36 para recoger los datos de la temperatura ambiental, en esta versión reutilizaremos el mismo sensor y puesto que las entradas analógicas del  arduino ethernet son iguales que las de arduino uno el esquema queda exactamente igual que en la versión anterior.

Antes de poner el código, cabe explicar que para poder enviar correos electrónicos desde arduino ethernet, tenemos que hacerlo utilizando telnet. Esto se hacía mucho antiguamente cuando los  servidores de correo no solían estar protegidos contra el reenvío de emails y cualquiera podía conectarse al puerto  25 de un servidor de correo y enviar un correo con un remitente falso... (aún recuerdo cuando le gastamos a un amigo la broma de que la guardia civil le había enviado un correo para reclamarle ciertas movidas :)
Hoy en día la gran mayoría de los servidores de correo requieren que nos autentiquemos por lo que se complica un poco el proceso de enviar correo a través de telnet pero esto no significa que no podamos hacerlo.
En mi caso he decidido utilizar mis cuentas de correo de gmail para enviar correo y esto presentaba otro problema añadido, google utiliza TLS como protocolo seguro para poder enviar correos y no he encontrado por ningún sitio la forma de hacerlo con arduino, si es que puede hacerse.
Para solucionarlo he tirado de una aplicación llamada stunnel, que es un software gratuito de tunneling ssl, lo que vamos a poder hacer con este programa es que nuestro arduino se conecte a él en lugar de conectarse directamente a google y usando una conexión al puerto 25 reenviar los comandos a google.

La configuración de stunnel es muy sencilla, solo hay que añadir estas líneas al archivo stunnel.conf y ya podemos enviar correos a través de conexiones al puerto 25 del ordenador donde hayamos instalado stunnel:


[ssmtp]
accept  = 25
connect = smtp.gmail.com:465

Por otro lado el proceso de autenticación no nos va a permitir utilizar nuestras credenciales utilizando texto plano, por lo que tendremos que hacer una transformación de nuestro usuario y contraseña a base64 para lo que he utilizado esta web. http://www.seguridadwireless.net/php/conversor-universal-wireless.php
Por poner un ejemplo si nuestro usuario para el correo fuese manolito.perez@gmail.com lo que tendríamos que mandar por telnet usando el arduino sería algo así: bWFub2xpdG8ucGVyZXpAZ21haWwuY29t
La opción a elegir dentro de la web de seguridadwireless es Encode64.

Bueno ya tenemos nuestro stunnel funcionando, tenemos nuestro usuario y nuestra password en base64 y tenemos una cuenta de gmail que es la que usaremos para enviar nuestros correos electrónicos. Por lo que el siguiente paso, será hacer el sketch que compruebe la temperatura y que si pasamos de Xº, nos envíe un correo electrónico:


//Como siempre empezamos llamando 
//las librerias necesarias
#include <SPI.h>
#include <Ethernet.h>
//Definimos las variables globales
//Variable de control para no enviar muchos emails seguidos
boolean correo=false;
//Pin donde conectaremos el sensor de temperatura
int anapin = 3;
//Mac address del arduinoethernet viene
//impreso por debajo de la placa, consta de 6 bytes en Hex
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xF2, 0xA6 };
//Servidor de email o en nuestro caso
//servidor donde hemos instalado el stunnel
byte server[] = { 10, 0, 0, 5 }; 
//Variable que guardara los valores leidos 
//en el sensor de temperatura
float val=0;
//Creamos una objeto de clase EthernetClient
EthernetClient client;

//iniciamos el setup
void setup()
{
  //Se inicia el puerto serie
  Serial.begin(9600);
  //Mostramos por pantalla un mensaje
  Serial.println("Conectando el arduino ethernet a la red...");
  //conectamos el ethernet a la red, solo le pasamos la mac
  //ya que tenemos un servidor de dhcp en nuestra red
  //si queremos especificar la ip tendriamos que hacerlo aqui
  if (Ethernet.begin(mac)==0) Serial.println("Fall´ la conexi´n ethernet");
  //Dejamos un pequeño retardo para que se asigne la ip y demas
  delay(5000);
}

//Iniciamos el loop
void loop(){
  //leemeos la temperatura del sensor llamando a la función getVoltage
  //y le aplicamos la formula para que aparezca en grados centígrados
  //en este sketch obviamos la conversión en grados F ya que no la vamos
  //a necesitar
  val = (getVoltage(anapin) - .5)*100;
  //Como método de control imprimimos al puerto serie el 
  //el valor del sensor
  Serial.println(val);
  //Dejamos una pausa para que las 
  //muestras se tomen cada 5 segundos
  delay(5000);
  //Comprobamos si el valor de la temperatura esta 
  //por debajo de 31ºC en cuyo caso no mandamos email
  if (val<31){
    Serial.println("NO SE MANDA EMAIL");
    if(correo) correo=false;
    //si la temp es superior mandamos el email
    //y cambiamos el valor de correo para que no vuelva a enviarse
  }else{
    //Comprobaos el valor de la variable correo
    //Si el valor de correo es falso mandamos el correo 
    //y ponemos el valor de correo a true
    if(!correo){
      //LLamamos a la función que enviara el correo
      sendEmail();
      correo=true;
      //Dejamos un retardo de 1' para que en el caso
      //de que la temperatura se esté moviendo 
      //entre 30 y 32º no envíe montones de correos
      delay(60000);
    }
  }
}

//Función que lee el voltaje del pin 3 de
//entradas analogicas
float getVoltage(int anapin){
  return (analogRead(anapin) * .004882814);
}

//Función que envia el email
byte sendEmail()
{
    //Conectamos el cliente al puerto 25
    //de nuestro servidor en caso de resultar
    //fallida la conexion nos devolverá un error
    //por pantalla
    if (client.connect(server,25)) {
      Serial.println("connected");
    } else {
      Serial.println("connection failed");
      return 0;
    }
    //Para cada comando llamamos la función
    //eRcv a fin de comprobar la respuesta del
    //servidor y de ir limpiando el buffer de recepción
    if(!eRcv()) return 0;
    //estos comandos son los que usariamos
    //en una conexion telnet, empezamos saludando
    //al servidor
    client.println("HELO google");
    if(!eRcv()) return 0;
    //Le decimos que queremos autenticarnos
    client.println("AUTH LOGIN");
    if(!eRcv()) return 0;
    //Enviamos el usuario en base64
    client.println("fjgkriemrerkEE54F=");
    if(!eRcv()) return 0;
    //Enviamos el password en base64    
    client.println("aGTvaiefSA==");
    if(!eRcv()) return 0;
    //especificamos el sender
    client.println("MAIL From:<mi@gmail.com>"); 
    if(!eRcv()) return 0;
    //especificamos el que recepciona el email
    client.println("RCPT To:<otro@gmail.com>");    
    if(!eRcv()) return 0;
    //indicamos que vamos a empezar a escribir el correo
    client.println("DATA");
    if(!eRcv()) return 0; 
    //Aquí ponemos todo el cuerpo del mensaje incluyendo
    //el from el to y el subject
    client.println("From: mi@gmail.com");                
    client.println("To: otro@gmail.com");                         
    client.println("Subject: Alerta de temperatura en el cuarto de servidores!!!!" + String(int(val)) + "ºC");
    client.println("");                              
    client.println("Se ha detectado una temperatura anormalmente alta en el cuarto de servidores, concretamente de " + String(int(val)) + "ºC");
    client.println(""); 
    //Cuando enviamos un punto indica el fin del mensaje
    client.println(".");
    if(!eRcv()) return 0;
    client.println("QUIT");
    //Cerramos la conexion telnet
    if(!eRcv()) return 0;
    //Paramos el cliente
    client.stop();
    return 1; 
}


//Esta función nos permite ir mostrando
//las respuestas del servidor para cada
//comando que enviamos, asi mismo va limpiando
//el buffer RX, hay servidores de correo que funcionan
//sin esta función pero con google es necesario.
byte eRcv()
{
  byte respCode;
  byte thisByte;
  while(!client.available()) delay(2000);
  respCode = client.peek();
  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }
  if(respCode >= '4')
  {
    efail();
    return 0;  
  }  
  return 1;
}

//Esta función nos dara un error en caso
//de que la respuesta del servidor sea un fallo
//y desconectara la sesion
void efail()
{
  byte thisByte = 0;
  client.write("QUIT\r\n");
  while(!client.available()) delay(1);
  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }
  client.stop();
  Serial.println("disconnected");
}




Pues con esto ya tendríamos nuestro controlador de temperatura avisador por email funcionando y listo para ponerlo en producción...






8 comments:

  1. hola. en las ultimas versiones del programador arduino, no se soporta "byte". por otro lado, en las anteriores versiones no consigo compilarlo pues me da el error: "EthernetClient does not name a type".
    por favor, si puedes ayudarme te lo agradeceria.
    saludos

    ReplyDelete
  2. //Servidor de email o en nuestro caso
    //servidor donde hemos instalado el stunnel
    byte server[] = { 10, 0, 0, 5 };


    No se que direccion ip, poner aqui, una ayuda??

    ReplyDelete
  3. Hola Eduardo. el problema de arduino es que para enviar emails solo es capaz de utilizar el protocolo smtp sin cifrar por lo que, como explico en el post hay que usar un programa "trampolín" al que arduino manda el email por smtp y que reenviará el correo a través de los servidores de google, este software se llama stunnel y la ip que tienes que especificar ahí es la ip que tenga el pc donde tengas instalado el stunnel.

    ReplyDelete
  4. Muy bueno el post, pero sabes que toda me va muy bien pero no recibo los mail's algo anda pasando, desearía tu apoyo gracias.

    ReplyDelete
  5. Paul, te sugeriría que miraras que salida tienes por el puerto serie ya que por ahí podríamos ver que problema es el que hay...

    ReplyDelete
  6. Muchas gracias por tu aportación. Llevaba tiempo intentandolo. Conseguia la conexión y el primer mensaje Ok, pero luego nada. Me has ahorrado mucho tiempo. Gracias d nuevo.

    ReplyDelete
  7. Hola Jose, una consulta en la parte del skecht donde especificacas las direcciones de mail cuales serian la direcion del From y la del To?

    ReplyDelete
  8. Una duda, como haces referencia al archivo config de stunnel?, o lo tiene que hacer automáticamente con la IP del servidor?

    ReplyDelete