Pablo Durán

Proyecto Meteo

1. Antes de empezar

1.1. Propósito del proyecto

Crear una estación metereológica que conecte con una base de datos y así mostrar los datos en esta página web.

1.2. ¿Que necesitamos?
  • Una raspberry pi con linux, python3 y php instalados
  • Sensores con el código fuente
  • Conocimientos básicos de python, SQL y PHP
  • Un servidor local o alquilado
  • VNC viewer para ver de forma gráfica la raspberry pi

2. Explicación

2.1. Instalar librerías

Para empezar, necesitamos instalar las librerías de nuestro sensor, en mi caso el sensor DHT11, para que python pueda recoger los valores que nos devuelve el sensor. Para ello necesitamos tener la raspberry pi en la última versión y python actualizado. Lo conseguiremos con los siguientes códigos:

sudo apt-get update

sudo apt-get upgrade

sudo pip3 install --upgrade setuptools

2.1.1. Habilitar I2C y SPI

Ahora para habilitaremos las interfaces I2C y SPI en la raspberry pi.

Para poder usar instrucciones I2C ejecutamos los comandos sudo apt-get install -y python-smbus y sudo apt-get install -y i2c-tools. Después usamos sudo raspi-config lo cual no abrirá un menú gráfico con las opciones de configuración de la raspberry pi vamos a "opciones de interfaz>opciones avanzadas>I2C" nos preguntará si queremos habilitarlo y le diremos que sí. Reiniciamos la raspberry y comprobamos si funciona. sudo i2cdetect -y 1 lo cual si todo ha ido bien nos mostrará una tabla como ésta, indicando que ya está instalado.

					                   
pi@raspberrypi ~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --
					                   
				                    

Una vez tenemos el I2C habilitamos el SPI es que es más rápido. Abrimos de nuevo el menú de configuración de la raspberry pi y vamos a "opciones de interfaz>opciones avanzadas>SPI" y lo habilitamos. Y comprobamos si funciona con ls -l /dev/spidev*. Si lo hemos hecho bien nos aparecerá lo siguiente:

                                        
crw-rw---- 1 root spi 153, 0 may  6 09:17 /dev/spidev0.0
crw-rw---- 1 root spi 153, 1 may  6 09:17 /dev/spidev0.1
                                        
                                    
2.1.2. Instalar bibliotecas del sensor
Con los pasos previos ya podemos instalar las librerías que necesitamos para nuestros sensores. En el terminal escribimos pip3 install RPI.GPIO, seguido de pip3 install adafruit-blinka. Este último nos instalará varias librerías aparte de la que le hemos dicho para poder funcionar como son:adafruit-pureio (una librería para comunicar con I2C), spidev (para la interfaz SPI) y Adafruit-GPIO (para detectar nuestra placa).

Para comprobar que todo funciona creamos un archivo llamado blinkatest.py e introducimos lo siguiente en el documento:

					                   
pi@raspberrypi ~ $
import board
import digitalio
import busio
 
print("Hello blinka!")
 
# Try to great a Digital input
pin = digitalio.DigitalInOut(board.D4)
print("Digital IO ok!")
 
# Try to create an I2C device
i2c = busio.I2C(board.SCL, board.SDA)
print("I2C ok!")
 
# Try to create an SPI device
spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
print("SPI ok!")
 
print("done!")
					                   
				                    

Guardamos el documento. Vamos a la ruta en la que has guardado el documento desde el terminal y ejecutas python3 blinkatest.py. Si todo va bien veremos el siguiente mensaje:

                                        
pi@raspberrypi ~ $ python3 blinkatest.py
Hello blinka!
Digital IO ok!
I2C ok!
SPI ok!
done!
                                        
                                    
2.2. Archivos Python

Vamos a tener tres archivos para que hacer que todo funcione y esté ordenado el código. El primero será __init__.py, el cual se encarga de coordinar todo el programa, después tenemos el archivo Sensores.py, que nos devolverá lo recogido por los sensores y la fecha y por último GeneradorJson.py, escribe los datos de los sensores en un archivo Json.

Si entramos en el archivo Sensores.py vemos que solo hay métodos get para devolver el valor de cada variable en formato String y con constructor de clase el cual indica las variables con sus respectivos valores. Para que todos esto funcione necesitamos importar la librería de nuestros sensores import Adafruit_DHT indicándole el sensor que tenemos y el pin en el que lo hemos instalado. También necesitamos importar la fecha from datetime import datetime para así saber el momento exacto en el que se recogen los datos.

					           
import Adafruit_DHT
from datetime import datetime

sensor = Adafruit_DHT.DHT11
pin = 17


class Sensores:

    #constructor
    def __init__(self):
        self.__humidity, self.__temperature = Adafruit_DHT.read_retry(sensor, pin)
        self.timeDate = datetime.now()

    #methods
    def getTemperature(self):
        return str(self.__temperature)

    def getHumidity(self):
        return str(self.__humidity)

    def getTimeNow(self):
        return self.timeDate.strftime("%Y/%m/%d %H:%M:%S")
					           
				            

En el archivo GeneradorJson.py únicamente tenemos un constructor al que le pasamos todos los parámetros que necesitamos escribir en el documento Json y el método que lo escribe. Se puede observar que las "variables" las iniciamos como con constantes, a igual que en el archivo Sensores.py, para que el valor qué hemos recogido de los sensores no se vea alterado. El constructor de clase nos pide los parámetros de entrada para poder trabajar y una vez los tiene puede ejecutar el método escribirJson() el cual crea un objeto con los valores que tenemos y lo escribe en el archivo datos.json. El programa está pensado para sobrescribir el dato anterior y guardar solo el último ya que para mi caso era lo único que necesitaba.

					           
import json


class GeneradorJson:

    # constructor
    def __init__(self, temperaturaStr, humedadStr, fechaStr):
        self.__temperaturaStr = temperaturaStr
        self.__humedadStr = humedadStr
        self.__fechaStr = fechaStr

    # methods
    def escribirJson(self):
        datos = {
            'temperatura': self.__temperaturaStr,
            'humedad': self.__humedadStr,
            'fecha': self.__fechaStr
        }

        cadena_json = json.dumps(datos)
        print(cadena_json)

        # Escritura
        with open('enviarDatos/datos.json', 'w') as f:
            json.dump(datos, f)
					           
				            

Quedando guardado en el archivo datos.json los datos que le hemos pasado:

					           
{"temperatura": "26.0", "fecha": "2020/05/10 08:08:07", "humedad": "45.0"}

					           
				            

Por último, nos queda ver el interior del archivo más importante, __init__.py, en el que empezamos importando las clases que necesitamos para poder trabajar como: from Sensores import Sensores para poder acceder a los valores del sensor, from GeneradorJson import GeneradorJson para acceder al generador de Json, import time para controlar los tiempos de ejecución y import shlex, subprocess para poder ejecutar comandos de terminal.

En el interior de la clase empezamos con un while True: para tener un bucle infinito. Continuado de la creación de un objeto sensores de la clase Sensores para poder así acceder a los métodos que tiene esta clase en su interior. Guardamos cada valor en su variable correspondiente y lo enviamos al archivo GeneradorJson. Ejecutamos el método que nos permite escribir en el documento y ya tenemos los valores exportados a un documento externo.

Ahora el programa ejecutará unas sentencias de terminal para poder enviarlo a la web. Para que funcione es necesario tener los archivos PHP necesarios así que no ejecutes el script de python aún. La primera sentencia es chromium-browser http://localhost:8082/ la cual abrirá el navegador chromium con la página predeterminada en el puerto 8082, que tenemos que configurar nosotros a mano. Después dejamos que el programa se duerma durante 3500 segundos y cierre después el navegador con pkill -o chromium. El programa se detiene otros 99 segundos y vuelve a ejecutar todo el código. De esta forma recibimos un valor cada hora.

					           
from Sensores import Sensores
from GeneradorJson import GeneradorJson
import time
import shlex, subprocess

datos = "sin datos"


class Main:
    while True:
        sensores = Sensores()
        temperaturaStr = sensores.getTemperature()
        humedadStr = sensores.getHumidity()
        fechaStr = sensores.getTimeNow()

        generador = GeneradorJson(temperaturaStr, humedadStr, fechaStr)
        generador.escribirJson()

        command_line = 'chromium-browser http://localhost:8082/'
        args = shlex.split(command_line)
        subprocess.run(args)

        time.sleep(3500.0)

        command_line2 = 'pkill -o chromium'
        args2 = shlex.split(command_line2)
        subprocess.run(args2)

        time.sleep(99.0)
					           
				            
2.3. Archivos PHP

Ahora que ya tenemos todos los archivos python vamos a proceder a crear los archivos PHP necesarios para enviar los datos al servidor. Son solo 2 por parte de la raspberry y uno por parte del servidor que almacenará nos datos, son sencillos de hacer. Hay que tener en cuenta que en este caso el programa está adaptado para que solo se envíe un paquete de datos por cada ejecución que se hace.

Empezaremos con el archivo que leer el documento json, el cual es leer.php el cual simplemente guarda los datos del json en sus respectivas variables.

					           
<?php
$datos = file_get_contents("datos.json");
$products = json_decode($datos, true);

$objeto = array_chunk($products,3);
$temperatura = $objeto[0][0];
$humedad = $objeto[0][2];
$fecha = $objeto[0][1];
?>
					           
				            

El archivo index.php es el que configuramos como archivo principal en el puerto 8082 de nuestro localhost, se puede configurar en otros puertos. Añadimos leer.php a nuestro archivo con lo que ya tenemos los valores de los datos. Creamos un formulario en el que se van a introducir los datos gracias a php haciendo un echo de la variable ej: value="<?php echo $temperatura ?>". Ponemos el link de la página a la que enviamos los datos en la apertura del formulario con un action action="tuWeb.com/puente.php". Con esto solo nos falta que el formulario se envíe automáticamente, para ello creamos un script que le de al botón enviar una vez se rellene todos los datos. Quedando el archivo de esta forma:

					           
<!DOCTYPE html>
<html lang="es-ES">
<head>
  <meta charset="UTF-8">
  <title>Document</title>    
    <script>
    window.onload=function(){
                // Una vez cargada la página, el formulario se enviara automáticamente.
		document.forms["form1"].submit();
    }
    </script>
</head>
<body>
    <?php
    include 'leer.php';
    ?>
    <form id="form1" name="form1" action="tuWeb.com/puente.php" method="POST">
        <fieldset>
            <legend><strong>temperatura</strong></legend>          
            <input type="text" name="temperatura" value="<?php echo $temperatura ?>" >
        </fieldset>
        <fieldset>
            <legend><strong>humedad</strong></legend>          
            <input type="text" name="humedad" value=">?php echo $humedad ?>" >
        </fieldset>
        <fieldset>
            <legend><strong>fecha</strong></legend>          
            <input type="text" name="fecha" value="<?php echo $fecha ?>">
        </fieldset>
        <div align="center">
        <input name="enviar" type="submit" value="enviar"/>
      </div>
    </form>
</body>
</html>
					           
				            

Solo nos falta el archivo puente.php por parte del servidor. Este se encarga de recoger los datos de los formularios que le enviamos y guardarlos en nuestra base de datos. Para coger los datos creamos un condicional para ver si tiene los datos ej: isset($_POST['temperatura']) ?. Ahora tenemos que decirle que hacer en caso de que tengamos algún valor en ese campo o no por lo que le ponemos $_POST['fecha'] : null lo que recogerá el valor en caso de "true" y dejará null en caso de "false".

Con esto ya tenemos los datos en el servidor, pero no están guardados en una base de datos. En el siguiente punto explico cómo crear DB. Para pasar los datos a la base de datos lo haremos mediante PDO para evitar ciertos problemas que podrían surgir, como puede ser interpretar ciertos caracteres dentro del texto como sentencia SQL.

Lo primero que necesitamos es almacenar los datos necesarios para poder entrar a la DB los cuales son: usuario, contraseña, servidor y base de datos. Creamos la conexión con mysqli_connect lo guardamos en $conexion y seleccionamos entramos a la DB que queremos con mysqli_select_db. Ya estamos conectados a la base de datos, solo nos queda generar una consulta. En nuestro caso solo queremos introducir los datos por lo que la consulta será un INSERT INTO diciéndole la tabla a usar, columnas a rellenar y pasar los valores por orden con esto hemos terminado el guardado de la información en nuestro servidor, pero si quieres ver en la misma ventana en la que recibe los datos el servidor y le he puesto un echo con la sentencia para asegurarme que le pasa parámetros válidos.

					           
<?php

//datos
$usuario = "root";
$contrasena = "";  
$servidor = "localhost";
$basededatos = "meteo";

//recojo los datos con un formulario, sino me llegan bien no seguiré
//recibo la lista a pedir
    $temperatura="";
    $humedad="";
    $fecha="";
	//recibo los datos de emisora
    $temperatura = isset($_POST['temperatura']) ? $_POST['temperatura'] : null;
    $humedad = isset($_POST['humedad']) ? $_POST['humedad'] : null;
    $fecha = isset($_POST['fecha']) ? $_POST['fecha'] : null;

//conectar
$conexion = mysqli_connect( $servidor, $usuario, $contrasena ) or die ("No se ha podido conectar al servidor de Base de datos");
$db = mysqli_select_db( $conexion, $basededatos ) or die ( "Upps! Pues va a ser que no se ha podido conectar a la base de datos" );

//sentencia SQL
$consulta = "INSERT INTO `meteo`( `temperatura`, `humedad`, `fecha`) VALUES ('".$temperatura."','".$humedad."','".$fecha."')";
echo "Petición SQL: ". $consulta;
$resultado = mysqli_query( $conexion, $consulta ) or die ( "Algo ha ido mal en la consulta a la base de datos");

//cerrar la conexion
mysqli_close( $conexion );
?>
					           
				            
2.4. Creación de la Base de Datos

Vamos a crear la base de datos necesaria para almacenar todos los datos de nuestra estación meteorológica en phpMyAdmin. En mi caso no voy a crear una base de datos nueva porque mi host no me lo permite, pero no genera ningún tipo de problema.

nuestra tabla debe de cumplir las siguientes condiciones:

  • 4 campos llamados id, temperatura, humedad, fecha.
  • Id de tipo numérico entero.
  • Temperatura y humedad números decimales con un decimal.
  • Fecha de tipo datetime para que guarde la hora
  • queremos que el id sea la clave primaria y aumente automáticamente.
  • InnoDB como motor a usar en la tabla
  • Todos los campos serán no nulos.
					           
CREATE TABLE `meteo` (
    `id` int(20) NOT NULL AUTO_INCREMENT,
    `temperatura` decimal(3,1) NOT NULL,
    `humedad` decimal(3,1) NOT NULL,
    `fecha` datetime NOT NULL,
    constraint meteo_id PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
					           
				            
2.5. Arranque del programa

Ya hemos terminado el programa y solo nos falta arrancarlo para hacer que todo esto funcione. para ellos vamos a la consola de nuestra raspberry pi, nos situamos en la carpeta en la que están nuestros archivos python y escribimos python __init__.py. Veremos que abre el navegador y envía los datos. Y no hará nada más hasta la siguiente hora que cerrará el navegador y volverá a empezar.

Ahora si nos vamos a la base de datos veremos que se han guardado exactamente los mismos números que le hemos registrado con el sensor y que nos confirma que nuestro trabajo aquí ha terminado.

3. Conclusión

El programa sufre de muchas carencias y no es capaz de gestionar errores, pero ya se irá mejorando poco a poco y añadiéndole más sensores para una mayor diversidad de datos meteorológicos.

Problemas del programa a tener en cuenta para futuras revisiones:

  • Auto inicio del programa en caso de pérdida de corriente.
  • Guardado de datos a nivel local en caso de pérdida de conexión.
  • enviar datos mirando la hora del sistema en vez de una cuenta atrás.