Controlador de Temperatura [Arduino + Ethernet Shield + Relays + MySQL + PHP]

Como primer proyecto me dispuse a hacer un control simple de temperatura que activa 2 relays en base a la temperatura obtenida y a su vez guarda en MySQL la temperatura para futuro procesamiento... es decir todo el control de setpoint's, horarios y demas puede ser realizado en PHP para asi dejar libre al Arduino de espacio.

Esta simple y espero a alguno de ustedes le ayude en algo.

El codigo esta hecho para un sensor DS18B20 (http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf)

Requerimientos:

  • Arduino c/ Ethernet Shield
  • Sensor DS18B20 (Pero lo puedes adaptar a tus necesidades) y resistencia de 4.7k
  • 2 Relays de 5v de operación
  • Servidor PHP y MySQL
  • Tener la libreria OneWire - Arduino Playground - OneWire

Conexiones:

  • Sensor de Temperatura - Digital Pin 2
  • Relay 1 - Digital Pin 3
  • Relay 2 - Digital Pin 4

Sketch de Arduino

/*************************************************************
Project: Temperature Controller
Author: Agustin Marmolejo (Twitter @eljamz)
Date: 18-December-2012
Description: A temperature controller who save's all the data into MySQL
and from there it can activate some relays.
*************************************************************/
#include <Ethernet.h>
#include <SPI.h>
#include <OneWire.h>

//Ethernet Configuration
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xEE}; // Ethernet Shield MAC
byte ip[] = { 192,168,1,249 }; // Ethernet Shield IP
byte server[] = { 192,168,1,136 }; // Server IP

EthernetClient client;
float value;

//Settings for Relays
String location = "http://192.168.1.136/arduino_sensor.php HTTP/1.0";
char inString[32]; // string for incoming serial data
int stringPos = 0; // string index counter
boolean startRead = false; // is reading?

//Declare Relay Pinouts
int rel1Pin = 3;  // Setup Realy 1 pin 
int rel2Pin = 4; // Setup Realy 2 pin 

//Define where to read the Sensor
int DS18S20_Pin = 2; //DS18S20 Signal pin on digital 2

//Temperature chip i/o
OneWire ds(DS18S20_Pin); // on digital pin 2

//void setup
void setup(void) {
  Serial.begin(9600);
  Ethernet.begin(mac, ip); // Init Ethernet Shield
  pinMode(rel1Pin, OUTPUT); // Make relPin and OUTPUT
  pinMode(rel2Pin, OUTPUT); // Make relPin and OUTPUT
  delay(5000); // Wait 5 seconds to Init Program
}


//void loop
void loop(void) {
 float temperature = getTemp(); // Get the temperature from sensor and INSERT to MySQL.
 Serial.println(temperature); // print out temperature.
 delay(500); //just here to slow down the output so it is easier to read
 String pageValue = connectAndRead(); //Connect to server and get action to follow!.
 Serial.println(pageValue); //print out the findings.
 delay(500); //just here to slow down the output so it is easier to read
}


/*
FUNCTION FOR RELAYS
*/
String connectAndRead(){
  //connect to the server

  Serial.println("connecting...");

  //port 80 is typical of a www page
  if (client.connect(server, 80)) {
    Serial.println("connected");
    client.print("GET ");
    client.println(location);
    client.println();

    //Connected - Read the page
    return readPage(); //go and read the output

  }else{
    return "connection failed";
  }

}



String readPage(){
  //read the page, and capture & return everything between '<' and '>'

  stringPos = 0;
  memset( &inString, 0, 32 ); //clear inString memory

  while(true){

    if (client.available()) {
      char c = client.read();

      if (c == '<' ) { //'<' is our begining character
        startRead = true; //Ready to start reading the part 
      }else if(startRead){

        if(c != '>'){ //'>' is our ending character
          inString[stringPos] = c;
          stringPos ++;
        }else{
          //got what we need here! We can disconnect now
          startRead = false;
          client.stop();
          client.flush();
          Serial.println("disconnecting.");
          
          int inRel = atoi (inString);
          if (inRel == 1) {
            digitalWrite(rel1Pin, HIGH);
            digitalWrite(rel2Pin, LOW);
          } else if (inRel == 2) {
            digitalWrite(rel1Pin, LOW);
            digitalWrite(rel2Pin, HIGH);
          }
          return inString;

        }

      }
    }

  }

}



/*
FUNCTION FOR TEMPERATURE.
*/
float getTemp(){
 //returns the temperature from one DS18S20 in DEG Celsius

 byte data[12];
 byte addr[8];

 if ( !ds.search(addr)) {
   //no more sensors on chain, reset search
   ds.reset_search();
   return -1000;
 }

 if ( OneWire::crc8( addr, 7) != addr[7]) {
   Serial.println("CRC is not valid!");
   return -1000;
 }

 if ( addr[0] != 0x10 && addr[0] != 0x28) {
   Serial.print("Device is not recognized");
   return -1000;
 }

 ds.reset();
 ds.select(addr);
 ds.write(0x44,1); // start conversion, with parasite power on at the end

 byte present = ds.reset();
 ds.select(addr);  
 ds.write(0xBE); // Read Scratchpad

 
 for (int i = 0; i < 9; i++) { // we need 9 bytes
  data[i] = ds.read();
 }
 
 ds.reset_search();
 
 byte MSB = data[1];
 byte LSB = data[0];

 float tempRead = ((MSB << 8) | LSB); //using two's compliment
 float TemperatureSum = tempRead / 16;
 
 //Display in Serial Monitor
 Serial.print(TemperatureSum); //Return temperature to Monitor
 Serial.println(" Celsius");
 
 //Save Temperature to MySQL
  Serial.println("Connecting...");

  if (client.connect(server, 80)>0) {  // Connect to server
    client.print("GET /arduino_sensor.php?type=Celsius&value="); // Send the DATA with GET
    client.print(TemperatureSum);
    client.println(" HTTP/1.0");
    client.println("User-Agent: Arduino 1.0");
    client.println();
    Serial.println("Connected");
  }
  else
  {
    Serial.println("Connection FAIL");
  }
  if (client.connected()) {}
  else {
    Serial.println("Disconnected!");
  }
  client.stop();
  client.flush();
}

arduino_sensor.php

<?php
include('db.php');
//Get parameter sended by Arduino.
$type = $_GET["type"];
$value = $_GET["value"];


// Validate parameters if any and INSERT.
if (($type != "") && ($value != "")) {
	mysql_query("INSERT INTO sensor (date, type, value) values (NOW(),'$type','$value')");
} else {

//If no parameters run script for RELAYS.
$result = mysql_query("SELECT value FROM sensor ORDER BY date DESC LIMIT 1");
while($row = mysql_fetch_array($result))
  {
	$value = $row['value'];
	$value = (int)$value;
  }
}

if ($value >= 28) { //this could be changed to anything, if you return <1> start Relay 1 in PIN3 and <2> start your second Relay in PIN 4.
	echo "<1>";
} else {
	echo "<2>";
}


mysql_close($con);
?>

db.php

<?php

$con = mysql_connect("localhost","root","");
	if (!$con){
		die('Connection fail: ' . mysql_error());
	}
mysql_select_db("arduino", $con);
?>

MySQL Create Code

CREATE TABLE `sensor` (
	`id` INT(10) NOT NULL AUTO_INCREMENT,
	`date` DATETIME NOT NULL,
	`type` VARCHAR(100) NOT NULL COLLATE 'latin1_spanish_ci',
	`value` FLOAT NOT NULL,
	PRIMARY KEY (`id`),
	UNIQUE INDEX `id` (`id`)
)
COLLATE='latin1_spanish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=0;

Espero les sirva, saludos!

Muy buen trabajo!

Me gustaría hacerte una recomendación en el código arduino_sensor.php, ya que es vulnerable a una inyección SQL y un graciosillo que encuentre la pagina puede fastidiarte la base de datos.

Simplemente es añadir mysql_real_escape_string() a las variables que van a interactuar con la base de datos para evitar una posible intrusión.

mysql_query("INSERT INTO sensor (date, type, value) values (NOW(),'mysql_real_escape_string($type**)','mysql_real_escape_string($value)**')");

Un saludo :wink:

estupendo aporte !!

hace tiempo que tengo este mismo post pendiente de compartir con la comunidad, me lo has ahorrado jeje

luego lo pruebo y comento si algo va mal pero tiene muy buena pinta.

Gracias por sus comentarios, con respecto al Inyección SQL, Se me ocurre agregarle a las cadenas un user y una pwd, y validar de esta manera, digo ya para que sepan también el usuario y contraseña estará medio difícil supongo!

Esto además de poner este comando para limpiar las variables...

Saludos!

Si quieres seguridad a tope usa md5 en el servidor arduino y en el script php. Es como funcionan las API simple y muy eficaz.

Por ejemplo en el servidor arduino que te genere el md5 de la string de temp seguido de tu key (tempKEY). Se lo envías al php como .php?temp=22&md5=(lo que genere arduino), entonces con la misma key que la de arduino y la temp que recibes en el php vuelves a generar el md5 y los comparas, que son iguales sigues adelante que no puede ser una intrusión.

Un saludo!

Buen trabajo, este me lo guardo en la carpeta de recortes.
Y buen apunte de JRodrigo.

JRodrigo:
Muy buen trabajo!

Me gustaría hacerte una recomendación en el código arduino_sensor.php, ya que es vulnerable a una inyección SQL y un graciosillo que encuentre la pagina puede fastidiarte la base de datos.

Simplemente es añadir mysql_real_escape_string() a las variables que van a interactuar con la base de datos para evitar una posible intrusión.

mysql_query("INSERT INTO sensor (date, type, value) values (NOW(),'mysql_real_escape_string($type**)','mysql_real_escape_string($value)**')");

Un saludo :wink:

JRodrigo:
Si quieres seguridad a tope usa md5 en el servidor arduino y en el script php. Es como funcionan las API simple y muy eficaz.

Por ejemplo en el servidor arduino que te genere el md5 de la string de temp seguido de tu key (tempKEY). Se lo envías al php como .php?temp=22&md5=(lo que genere arduino), entonces con la misma key que la de arduino y la temp que recibes en el php vuelves a generar el md5 y los comparas, que son iguales sigues adelante que no puede ser una intrusión.

Un saludo!

Creo que con el puro md5(tempKEY); es suficiente no?, digo a final de cuentas la idea es que no se sepan la tempkey... y el valor de la temperatura es irrelevante una vez que adivinen el (tempKEY)

He añadido el post en Arduino Playground - HomePage

Saludos y gracias,

Igor

eljamz:
Creo que con el puro md5(tempKEY); es suficiente no?, digo a final de cuentas la idea es que no se sepan la tempkey... y el valor de la temperatura es irrelevante una vez que adivinen el (tempKEY)

Lo del md5 era una idea por si te podía interesar, es también valida tu idea de utilizar un usuario con su contraseña, ya que pienso que nadie se va a dedicar a meterte datos falsos (alguien que se aburra mucho puede que si jajjaja), la única brecha de seguridad bastante grave era al introducir los datos en la DB pero lo arreglas con la función mysql_real_escape_string() y listo!

Gracias a ti! es un honor aparecer ahi!

JRodrigo:
Lo del md5 era una idea por si te podía interesar, es también valida tu idea de utilizar un usuario con su contraseña, ya que pienso que nadie se va a dedicar a meterte datos falsos (alguien que se aburra mucho puede que si jajjaja), la única brecha de seguridad bastante grave era al introducir los datos en la DB pero lo arreglas con la función mysql_real_escape_string() y listo!

De hecho tienes razon, pero lo del MD5 tempkey tambien es una "buena" practica, lo ideal seria utilizar una session, y esto es posible pero nos quitaria mucho espacio... lo que podemos hacer es:

Como ya sabemos la direccion IP del arduino, podemos desde PHP saber el IP del cliente, y entonces limitar a que solo el arduino tenga acceso...

Agregar esto en "arduino_sensor.php"

<?php
$arduino_ip = "192.168.1.1";
$client_ip = @$REMOTE_ADDR; 

if ($arduino_ip == $client_ip) {

EL CODIGO AQUI

} else {
echo "No estas autorizado para usar este sitio";
}
?>

creo que deberia de ser

$_SERVER["REMOTE_ADDR"]

al menos en mi servidor remoto.

Lo malo es que si se reinicia el router y cambia la ip, ya no funcionara.

Por supuesto me refiero si el servidor esta fuera de la red de casa.

Justo eso quise decir
Red Local:

$ip = $_SERVER["REMOTE_ADDR"];

Si estas dentro de la red el IP esta definido, y si estas trabajando sobre internet puedes utilizar un "No-IP.org" o "DynDNS" asi tu ip publica si cambia el PHP la resuelve en automatico...

$ip = gethostbyname("user.no-ip.org");

Saludos!

eljamz:
Justo eso quise decir
Red Local:

$ip = $_SERVER["REMOTE_ADDR"];

Si estas dentro de la red el IP esta definido, y si estas trabajando sobre internet puedes utilizar un "No-IP.org" o "DynDNS" asi tu ip publica si cambia el PHP la resuelve en automatico...

$ip = gethostbyname("user.no-ip.org");

Es mas, algunos routers tienen la opcion de añadirle la cuenta y que sea el propio router el que la actualice automaticamente.
Saludos!

Jossema106:
Es mas, algunos routers tienen la opcion de añadirle la cuenta y que sea el propio router el que la actualice automaticamente.
Saludos!

Si! creo que ya la mayoria lo tiene como "default" solo busquen DynDNS y ahi apareceran varios servicios y ya lo configuran, esto para poder actualizar el IP en automatico.

Up

Puestos a ser sobrados, ya de paso por que no hacerlo vía HTTPS XD

anakinsw:
Puestos a ser sobrados, ya de paso por que no hacerlo vía HTTPS XD

Sobrados ?

Para que quieres https???

Yo tampoco entendi lo de sobrados... y sobre HTTPS yo creo que es irrelevante si la seguridad la metes en PHP.

Lo que falta agregarle (trabajando en eso) es que funcione con DHCP para que podamos hacer el request a "mipagina.com", limpiar un poco el codigo y le queria agregar que sea datalogger en la SD...

Alguna idea o comentario sobre esto ultimo, por más que he luchado no puedo hacer funcionar ethernet y SD de manera correcta, ni si quiera tratando de prender ethernet, correrlo, apagar ethernet y encender SD... secuencias similares.

eljamz:
Yo tampoco entendi lo de sobrados... y sobre HTTPS yo creo que es irrelevante si la seguridad la metes en PHP.

Lo que falta agregarle (trabajando en eso) es que funcione con DHCP para que podamos hacer el request a "mipagina.com", limpiar un poco el codigo y le queria agregar que sea datalogger en la SD...

Alguna idea o comentario sobre esto ultimo, por más que he luchado no puedo hacer funcionar ethernet y SD de manera correcta, ni si quiera tratando de prender ethernet, correrlo, apagar ethernet y encender SD... secuencias similares.

Pon tu codigo.

Yo hice el código original, me refería a todos colaborar con esto y/o esperen este fin de semana para mi nuevo código...

Saludos!