sábado, 28 de junio de 2014

Sistema de control de temperaturas via web

En esta entrada voy a describir la construcción de un sistema de control de las temperaturas de un lugar, por ejemplo un frigorífico con medicamentos, de tal manera que podamos ver vía web las temperaturas actuales, la máxima y la mínima registradas. Usaremos un Arduino Uno, una fuente de alimentación, un sensor DS18B20 y un módulo ethernet ENC28J60, el conjunto es ridículamente barato. También necesitaremos darnos de alta en una web de registro de datos, tranquilos es gratis, que nos permitirá almacenar los datos enviados vía web por nuestro sistema de registro funcionando en el Arduino. Vamos al lío:

- Construcción del hardware:
Las conexiones del módulo ENC28J60 a arduino son las siguientes:
CS...Pin digital 8
SI...Pin digital 11
SO...Pin digital 12
SCK..Pin digital 13
VCC.... 3.3v
GND

La conexión del sensor de temperatura DS18B20 se hace colocando primero una resistencia de 4K7 Ohm uniendo el polo de datos al VCC (3.3v) como muestra el dibujo:



Las conexiones del sensor a arduino son:
 GND y VCC a GND de Arduino  y DATA a Pin digital 2.

Ya tenemos construido nuestro hardware.

- Dar de alta el servidor de datos de Xively (antes llamado Pachube):

Entramos en Xyvely.com y nos damos de alta. Vamos a "Develop" y Pulsamos "+ Add a Device". Le damos un Nombre, una Descripción , pulsamos en la selección de "Public Device", una vez hecho esto, damos a "Save Changes". Una vez hecho esto aparecerá en la parte inferior de la pagina Web unos datos importantes para poder configurar el programa que hará funcionar nuestro recolector de temperaturas, estos son:
- Free ID
- API Key
Las copiamos y las guardamos para usarlas luego.

- Programa sketch para Arduino:

Sustituye Free ID y Api Key por los valores que guardaste al dar de alta tu cuenta Xively. Copia el programa siguiente, y pégalo en tu pantalla de Arduino Uploader. Comprueba que tienes las bibliotecas requeridas instaladas. Comprueba que tienes una IP libre (cambia la 192.168.1.32, por la que tu tengas) y que la puerta de enlace (dirección IP de tu router) es la adecuada (sustituye 192.168.1.1 por la adecuada).

*********************Inicio de programa - no copiar esto**************************

// Sistema de control de temperaturas usando modulo ethernet ENC28J60 y sensor DS18B20
// 2011-07-08 <jc@wippler.nl> http://opensource.org/licenses/mit-license.php
//PIN CONNECTION FOR ENC28J60 ETHERNET CARD
//CS...8
//SI...11
//SO...12
//SCK..13
//VCC...3.3v
//GND...GND
// Conexion del sensor DS18B20:
// Se ha elegido el modo de alimentación parásita que consiste en conectar VCC (+3,3v) y GND
//juntos y unir DATA a +3,3v con una resistencia de 4K7 Ohm.Este modo permite conectar los
//tres polos a un conector GND-DATA-VCC actuando como desequivocador, ya que DATA nunca cambia
//de posicion central.
// arduino +3.3v-----R 4K7Ohm----DATA------Pin Digital 2 Arduino
// ****************************DS18B20***************************
// Arduino GND------------------VCC-GND

#include <EtherCard.h>
#include <stdio.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(9, 10, 4, 5, 6, 7);

float tempmin = 2;  //Variable donde guardamos la temperatura minima registrada
float tempmax = 8;  //Varibale donde guardamos la temperatura maxima registrada
float teston = 0;  //Variable del testeo de DNS ON
float teston1 = 0; //Variable de presentacion testeo OK
float temp01;      //Varibale donde guardamos la temperatura actual sensor 01
float temp02;      //Varibale donde guardamos la temperatura actual sensor 02
float temp03;      //Varibale donde guardamos la temperatura actual sensor 02

//Timers
unsigned long tAntes = 0;
unsigned long tAntes1 = 0;
unsigned long tAntes2 = 0;
unsigned long tAntes3 = 0;
unsigned long tAntes4 = 120000;// Timer 4 minutos estabilizacion sensores
long time_now = 0;

// change these settings to match your own setup (son de mi cuenta Xively)
#define FEED    "PONER AQUI VALOR DE FEED"
#define APIKEY  "PONER AQUI VALOR APIKEY"

// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// ethernet interface mac address, must be unique on the LAN
#define STATIC 1  // set to 1 to disable DHCP (adjust myip/gwip values below)

#if STATIC
// mac address
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
// ethernet interface ip address
static byte myip[] = { 192,168,1,32 };
// gateway ip address
static byte gwip[] = { 192,168,1,1 };
// mask address
static byte mymask[] = { 255,255,255,0 };
// dnsip address
static byte dnsip[] = { 10,44,99,225 };
#endif

char website[] PROGMEM = "api.xively.com";

#define BUFFER_SIZE 500 //antes 400

byte Ethernet::buffer[BUFFER_SIZE];
BufferFiller bfill;

Stash stash;

void setup () {
 
  lcd.begin(16, 2);              // start the library
  lcd.setCursor(0,0);
  lcd.print("Starting...."); // print a simple message
  lcd.setCursor(0,1);
  lcd.print("Max/Min delay"); //Warning about 4 min delay in Max/Min
  delay(2000);
 
   // Start up the library
  sensors.begin();
 
  Serial.begin(38400);
  Serial.println("Trying to get an IP...");
 
  Serial.print("MAC: ");
    for (byte i = 0; i < 6; ++i) {
    Serial.print(mymac[i], HEX);
    if (i < 5)
      Serial.print(':');
    }
   Serial.println("\n[webClient]");
 
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Ethernet Start"); // print a simple message
   delay(2000);
 
   if (ether.begin(sizeof Ethernet::buffer, mymac) == 0)
   {
    Serial.println( "Failed to access Ethernet controller");
    lcd.begin(16, 2);
    lcd.setCursor(0,0);
    lcd.print("Ethernet Start");
    lcd.setCursor(0,1);
    lcd.print("Ethernet Error");
   
   } else  {
    Serial.println( "Ethernet controller OK");
    lcd.begin(16, 2);
    lcd.setCursor(0,0);
    lcd.print("Ethernet Start");
    lcd.setCursor(0,1);
    lcd.print("Ethernet OK");
   
   }
    delay(2000);
   
  #if STATIC
  Serial.println( "Getting static IP.");
     if (!ether.staticSetup(myip, gwip)){
    Serial.println( "could not get a static IP");
     }
#else

  Serial.println("Setting up DHCP");
    if (!ether.dhcpSetup()){
    Serial.println( "DHCP failed");
     blinkLed();     // blink forever to indicate a problem
  }
#endif
 
  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip);
  ether.printIp("DNS: ", ether.dnsip);
  ether.printIp("SRV: ", ether.hisip);

  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Wait for DNS"); // print a simple message
 
  if (!ether.dnsLookup(website)) {
    Serial.println("DNS failed");
    //lcd.begin(16, 2);
    lcd.setCursor(0,1);
    lcd.print("DNS Error");
    //teston = 0;
   } else  {
    Serial.println("DNS OK");
    //lcd.begin(16, 2);
    lcd.setCursor(0,1);
    lcd.print("DNS OK");
   }
    delay(2000);
   
 
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("1 minute to"); //Inicio loop de publicacion en Xively
  lcd.setCursor(0,1);
  lcd.print("Start Publish.."); //Inicio loop de publicacion en Xively
  delay(2000);
}

void loop () {
 
  ether.packetLoop(ether.packetReceive());
 
  long time_now = millis();
 
  if ((long)(time_now - tAntes) > 30000) // regulador de tiempo medida datos y presentacion lcd
  {
    tAntes = time_now;
   
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
 
  Serial.print(" Requesting temperatures...\n");
  sensors.requestTemperatures(); // Send the command to get temperatures
  Serial.println("DONE");
   
 float temp01 = sensors.getTempCByIndex(0);// Why "byIndex"?
    // You can have more than one IC on the same bus.
    // 0 refers to the first IC on the wire
  float temp02 = sensors.getTempCByIndex(1);
  float temp03 = sensors.getTempCByIndex(2);
 
 
 
  teston1 = teston / 4;// Convertir pasos de 15 min en 0.25 LCD
 
  // Comparador temperaturas para saber Max y Min  
  if (temp01<tempmin)
      tempmin=temp01;
  if (temp01>tempmax)
      tempmax=temp01;
     
  //Timer para permitir estabilizarse la temperatura en el lugar donde colocar la sonda, para evitar
  // falsos maximos y minimos
  if ((long)(tAntes4 - time_now) > 1) // regulador de tiempo medida datos y presentacion lcd
  {
  tempmin=temp01;
  tempmax=tempmin;
  }
 
 
   //Presentador datos terminal serie
 
  Serial.print("Sensor 1: ");
  Serial.print(temp01);
  Serial.print("Min Sensor1: ");
  Serial.print(tempmin);
  Serial.print("Max Sensor1: ");
  Serial.print(tempmax);
  Serial.print("Sensor 2: ");
  Serial.print(temp02);
  Serial.print("Sensor 3: ");
  Serial.print(temp03);
  Serial.print("Test ON: ");
  Serial.print(teston1);
  Serial.print("\n");

  // FIN SERIE  
 
  //Escribe datos temperatura en el LCD

 if ((long)(time_now - tAntes2) > 10000) // regulador de tiempo presentacion lcd
 {
 tAntes2 = time_now;

 lcd.begin(16, 2);

 lcd.clear();
 lcd.setCursor(0,0);
 lcd.print("T.Sens2");
 lcd.setCursor(10,0);
 lcd.print(temp02);
 lcd.setCursor(0,1);
 lcd.print("T.Sens3");
 lcd.setCursor(10,1);
 lcd.print(temp03);
 delay(4000);
 lcd.clear();
 lcd.setCursor(0,0);
 lcd.print("T.Ac.");
 lcd.setCursor(5,0);
 lcd.print(temp01);
 lcd.setCursor(12,0);
 lcd.print(teston1);
 lcd.setCursor(0,1);
 lcd.print("Min");
 lcd.setCursor(3,1);
 lcd.print(tempmin);  
 lcd.setCursor(8,1);
 lcd.print("Max");
 lcd.setCursor(11,1);
 lcd.print(tempmax);
 delay(4000);
 }

 
  // Reseteador temparaturas cada 24 horas
  if (teston > 96){
      tempmax=temp01;  
      tempmin=temp01;
      teston = 0;
    }
   
   //Envio de datos a Xively
 
   if ((long)(time_now - tAntes1) > 30000) // regulador de tiempo envio datos
   {
    tAntes1 = time_now;
     
    // generate two fake values as payload - by using a separate stash,
    // we can determine the size of the generated message ahead of time
    byte sd = stash.create();
   
    stash.print("Sensor_1_T_Max,");
    stash.println(tempmax);
       
    stash.print("Sensor_1_T_Min,");
    stash.println(tempmin);
     
    stash.print("Sensor_1_Actual,");
    stash.println(temp01);
   
    stash.print("Sensor_2_Actual,");
    stash.println(temp02);
   
    stash.print("Sensor_3_Actual,");
    stash.println(temp03);
   
    stash.print("Test_ON,");
    stash.println(teston1);
   
    stash.save();
   
    // generate the header with payload - note that the stash size is used,
    // and that a "stash descriptor" is passed in as argument using "$H"
    Stash::prepare(PSTR("PUT http://$F/v2/feeds/$F.csv HTTP/1.0" "\r\n"
                        "Host: $F" "\r\n"
                        "X-PachubeApiKey: $F" "\r\n"
                        "Content-Length: $D" "\r\n"
                        "\r\n"
                        "$H"),
            website, PSTR(FEED), website, PSTR(APIKEY), stash.size(), sd);

    // send the packet - this also releases all stash buffers once done
    ether.tcpSend();
    }
 

// Testeador conexion etherner usando dnslookup
if ((long)(time_now - tAntes3) > 900000) // regulador de tiempo testeo 15 min.
  {
    tAntes3 = time_now;
     
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Wait for DNS");
 
  if (!ether.dnsLookup(website)) {
    Serial.println("DNS failed");
    lcd.setCursor(0,1);
    lcd.print("Error");
    teston = 0;
   } else  {
    lcd.setCursor(0,1);
    lcd.print("OK");
    teston = teston + 1;//Indicador creciente testeo posiivo
   }
   }
  }
}
*********************fin de programa - no copiar esto**************************

- Funcionamiento:

Tras encender el Arduino, veras que empieza a enviar datos de temperaturas. Si te conectas a la web de Xively y vas a "Develop", veras que aparece en "Development Devices" el que diste de alta anteriormente, si pulsas sobre el, accederás a las temperaturas que aparecerán como : Frigo_1, T_Max y T_Min. En la línea de la pantalla de datos donde pone "Channels", puedes escoger el modo de ver dichos datos, como un dato numérico, o como una gráfica de datos en el tiempo, junto con el valor actual, esto lo haces pulsando "Graphs". Verás que en cada gráfica puedes escoger el intervalo de tiempo de recogida de datos, pulsando en el selector de la base de cada gráfica. Si modificas en el programa de Arduino anterior los nombres de los datos (Frigo_1 por Salon, p.e.), aparecerá este nombre como una gráfica nueva. Si quieres reiniciar los datos o borrar gráficas con nombres antiguos, dale a "Delete" (dibujo de cubo de basura), tras esto, automáticamente se volverán a cargar las gráficas descritas en el programa Arduino.

- Adenda:

Este sistema nos permite controlar la temperatura de nuestro frigorífico, p.e., y nos permitirá darnos cuenta de si se ha averiado estando de vacaciones, p.e., pero no funcionará si se va la corriente de la casa, ya que el router e internet no funcionarán. Para solucionar esto, quizás sería interesante usar un módulo GRPS/3G para Arduino, junto con un sistema de SAI de energía para Arduino.El sistema SAI para Arduíno es bastante económico, ya que consiste en un "libro" de baterías recargables, que encuentras por internet por menos de 10 euros. El módulo de datos por GPRS/3G ya es algo más caro, pues implica una tarifa de datos sólo para el sistema mas el coste elevado del módulo. Existe la posibilidad de generar programas que lean los datos almacenados en Xively, de la misma manera que lo hace su página Web, incluso explotando esos datos, creando secuencias de alarma o aviso para unos determinados valores o su ausencia de actualización. Esto se escapa aún de mis conocimientos, pero espero que alguien lo pueda desarrollar, p.e. una applet para Android con un Widget de temperatura actual y una alarma de aviso configurable.