CONTROL DE DOS LED´S VÍA WEB CON Y SIN TIEMPO REAL.
El presente trabajo está basado en los documentos de la dirección electrónica:
http://riverajefer.blogspot.com/
En la cual se desarrolla el software y se implementa el hardware para el manejo de un led, encendido y apagado, mediante una aplicación móvil Android y una aplicación Web que controlan los puertos GPIO de una Raspberry Pi.
Vamos a dejar de lado la aplicación Android y trabajaremos únicamente con la implementación Webcon la que controlaremos el encendido y apagado de dos led´s, conectados a los puertos GPIO de la Raspberry Pi y lo haremos sin utilizar el concepto de tiempo real y luego utilizando Firebase que nos permite realizar aplicaciones en tiempo real.
Queda sobreentendido que se posee los conocimientos necesarios y se está en capacidad de montar un servidor Web en la Raspberry Pi, dar permisos de usuario a las carpetas en la Raspberry Pi, que se pueda conectar remotamente al escritorio de la Raspberry Pi y que se posee una cuenta en Firebase, de no ser así se recomienda seguir los buenos tutoriales del mismo blog: http://riverajefer.blogspot.com/
CONTROL DE DOS LED´S SIN APLICAR CONCEPTO DE ACTUALIZACION EN TIEMPO REAL.
Una vez que se pueda establecer la conexión con el escritorio de la Raspberry Pi se debería tener una imagen como la siguiente:
Instalación de librerías necesarias para control de los pines GPIO:
sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install git
sudo apt-get install python-gpiozero
sudo apt-get install python-pip
sudo pip install requests==1.1.0
Instalación de servidor apache
sudo apt-get install apache2 php5 libapache2-mod-php5
sudo service apache2 restart
Procedemos a instalar la versión ligera de Firefox para Raspberry Pi llamada “iceweasel” con el siguiente comando en la terminal:
sudo apt-get install iceweasel
Una vez instalado se puede verificar el funcionamiento del servidor Apache conectándose al localhost:
Posteriormente es necesario cambiar el usuario y el grupo al directorio con el comando:
sudo chown www-data:www-data /var/www/html
La ubicación de los archivos en los cuales vamos a realizar la prueba es: /var/www/html por lo que se deben dar permisos para que se permitan copiar, crear y editar archivos o carpetas en esta dirección con el comando:
sudo chmod 775 /var/www/html
Como el usuario por defecto de la Raspberry Pi es pi añadimos este al grupo www-data que se crea al instalar el servidor Apache, con el comando:
sudo usermod -a -G www-data pi
Ejecutamos el comando:
sudo visudo
Se añade al final del archivo:
www-data ALL=(root) NOPASSWD:ALL
CTRL O para grabar
CTRL X para salir.
Reiniciamos el Servidor Apache
sudo /etc/init.d/apache2 restart
Este proceso está descrito en la dirección: http://riverajefer.blogspot.com/
Transferencia de archivos
Se realiza la transferencia del archivo comprimido led_sc.tar que se lo descargó de internet hacia el directorio /var/www/html/ y se descomprime. La manera más fácil es utilizar una memoria flash.
Hardware
Se requiere para esta aplicación que se arme el siguiente circuito:
Con una computadora con sistema Ubuntu se encuentra la dirección de la red, con el comando:
ifconfig
Se encuentra la dirección de la Raspberry Pi, con el comando:
sudo nmap –sP 192.168.100.0/24
Desde cualquier equipo conectado a la red, se escribe la dirección:
192.168.100.4/led_sc/
Nota: El valor de la IP varía para cada caso
Dos luces apagadas:
Los dos led´s están apagados
Luz de la sala prendida
Led derecha prendido led de izquierda apagado
Luz del comedor prendida
Led de la derecha apagado y led de la izquierda prendido
Luces de la sala y comedor prendidas
Los dos led´s de encuentran prendidos
Dos usuarios al mismo tiempo
Utilizando Firefox (izquierda) y Chrome (derecha) al momento de activar la luz de la sala en el panel de la izquierda, en la derecha todavía sigue apagado.
Para algunas aplicaciones este sería un caso muy crítico ya que si bien la luz de la sala está encendida, el usuario de la derecha piensa que está apagada.
Este problema se soluciona más tarde con la aplicación de Firebase y la ejecución de la aplicación en tiempo real.
Software utilizado
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Domótica sin tiempo real</title> <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> <!-- Tratamiento de botones --> <script src="js/script.js"></script> <link rel="stylesheet" href="css/estilos.css"> <link href='https://fonts.googleapis.com/css?family=Fjalla+One' rel='stylesheet' type='text/css'></head><body>
<h1>DOMÓTICA SIN PROCESO EN TIEMPO REAL</h1> <div align="center">
<h2>Luz Sala</h2> <label class="switch"> <input class="switch-input" type="checkbox" id="switch_sala" /> <span class="switch-label" data-on="On" data-off="Off"></span> <span class="switch-handle"></span> </label> </div>
<div align="center">
<h2>Luz Comedor</h2> <label class="switch"> <input class="switch-input" type="checkbox" id="switch_comedor" /> <span class="switch-label" data-on="On" data-off="Off"></span> <span class="switch-handle"></span> </label> </div></body></html>
script.js
/*@autor: Jefferson Rivera@email: [email protected] por Fabián Velasco*/
$(document).ready(function() {
/***************************************************************** Procesa pulsador sala ******************************************************************/
$('#switch_sala').on('change',function(){ if(this.checked)
{console.log("On sala");var estado=1;$.ajax({ data:{valor_estado: estado},
url:'procesa.php', type:'POST', success: function(response){
//alert("Salida: "+response ); }});
} else {
console.log("Off sala");var estado=2;$.ajax({ data:{valor_estado: estado},
url:'procesa.php', type:'POST', success: function(response){
//alert("Salida: "+response ); }});
}
});
/***************************************************************** Procesa pulsador comedor ******************************************************************/
$('#switch_comedor').on('change',function(){ if(this.checked) {
console.log("On comedor");var estado=3;$.ajax({ data:{valor_estado: estado},
url:'procesa.php', type:'POST', success: function(response){
//alert("Salida: "+response ); }});
} else {
console.log("Off comedor");var estado=4;
$.ajax({ data:{valor_estado: estado},
url:'procesa.php', type:'POST', success: function(response){
//alert("Salida: "+response ); }});
}
});
});
estilos.ccs
body{ background-color: #2E2E2E;}
h1{ font-family: 'Fjalla One', sans-serif; text-align: center; color: #FFF; font-size: 50px; text-shadow: 1px 1px 7px rgba(150, 150, 150, 1);}h2{ text-align: center; color: #FFF; font-size: 45px; text-shadow: 1px 1px 7px rgba(150, 150, 150, 1);}
.bild { position:absolute;
display:none; width:1000px; height:735px;}.bg { position: fixed; top: 0; left: 0; z-index: -99; /* Preserve aspet ratio */ min-width: 100%; min-height: 100%; z-index: -999; }.bgwidth { width: 100%; }.bgheight { height: 100%; } .switch { position: relative; display: block; vertical-align: top; width: 100px; height: 30px; padding: 3px; margin: 0 10px 10px 0; background: linear-gradient(to bottom, #eeeeee, #FFFFFF 25px); background-image: -webkit-linear-gradient(top, #eeeeee, #FFFFFF 25px); border-radius: 18px; box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05); cursor: pointer;}.switch-input { position: absolute; top: 0; left: 0; opacity: 0;}.switch-label { position: relative; display: block; height: inherit; font-size: 10px; text-transform: uppercase; background: #eceeef; border-radius: inherit; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15);}.switch-label:before, .switch-label:after { position: absolute; top: 50%; margin-top: -.5em; line-height: 1; -webkit-transition: inherit;
-moz-transition: inherit; -o-transition: inherit; transition: inherit;}.switch-label:before { content: attr(data-off); right: 11px; color: #aaaaaa; text-shadow: 0 1px rgba(255, 255, 255, 0.5);}.switch-label:after { content: attr(data-on); left: 11px; color: #FFFFFF; text-shadow: 0 1px rgba(0, 0, 0, 0.2); opacity: 0;}.switch-input:checked ~ .switch-label { background: #E1B42B; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2);}.switch-input:checked ~ .switch-label:before { opacity: 0;}.switch-input:checked ~ .switch-label:after { opacity: 1;}.switch-handle { position: absolute; top: 4px; left: 4px; width: 28px; height: 28px; background: linear-gradient(to bottom, #FFFFFF 40%, #f0f0f0); background-image: -webkit-linear-gradient(top, #FFFFFF 40%, #f0f0f0); border-radius: 100%; box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);}.switch-handle:before { content: ""; position: absolute; top: 50%; left: 50%; margin: -6px 0 0 -6px; width: 12px; height: 12px; background: linear-gradient(to bottom, #eeeeee, #FFFFFF); background-image: -webkit-linear-gradient(top, #eeeeee, #FFFFFF); border-radius: 6px; box-shadow: inset 0 1px rgba(0, 0, 0, 0.02);}.switch-input:checked ~ .switch-handle {
left: 74px; box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2);} /* Transition========================== */.switch-label, .switch-handle { transition: All 0.3s ease; -webkit-transition: All 0.3s ease; -moz-transition: All 0.3s ease; -o-transition: All 0.3s ease;}
procesa.php
<?php$valor_estado=$_POST['valor_estado'];
if($valor_estado==1){ exec('sudo python /var/www/html/led_sc/apaga_sala.py');}
if($valor_estado==2){ exec('sudo python /var/www/html/led_sc/prende_sala.py');}
if($valor_estado==3){ exec('sudo python /var/www/html/led_sc/apaga_comedor.py');}
if($valor_estado==4){ exec('sudo python /var/www/html/led_sc/prende_comedor.py');}
?>
Manejo de GPIO de Raspberry:
prende y apaga para sala y comedor
prende_sala.py
import RPi.GPIO as GPIOGPIO.setmode(GPIO.BOARD)GPIO.setup(11, GPIO.OUT)GPIO.output(11, False)
apaga_sala.py
import RPi.GPIO as GPIOGPIO.setmode(GPIO.BOARD)GPIO.setup(11, GPIO.OUT)GPIO.output(11, True)
prende_comedor.py
import RPi.GPIO as GPIOGPIO.setmode(GPIO.BOARD)GPIO.setup(12, GPIO.OUT)GPIO.output(12, False)
apaga_comedor.py
import RPi.GPIO as GPIOGPIO.setmode(GPIO.BOARD)GPIO.setup(12, GPIO.OUT)GPIO.output(12, True)
APLICACIÓN EN TIEMPO REAL
Como hemos visto, el mayor problema radica en que los usuarios que se encuentren conectados no reciben la información actualizada, una alternativa que se tiene para ejecutar en tiempo real es utilizar websockets que son tratados también en el blog http://riverajefer.blogspot.com/ adicionalmente, se tiene como alternativa la utilización de Firebase.
En cuanto a la Raspberry es necesario instalar las siguientes librerías:
sudo pip install python-firebase
Instalar demjson:
Ejecutándose como root el comando:
apt-get install python-demjson
http://www.tutorialspoint.com/json/json_python_example.htm
http://deron.meranda.us/python/demjson/download
Esta librería permite la transformación en tipo de datos json.
Código para sala y comedor:
descargar los archivos: domótica_python.tar y tiempo_real_sc.tar .
El primero se lo puede colocar en cualquier carpeta creada en la Raspberry, por ejemplo yo he creado la carpeta /paso ahí lo copiamos y extraemos de manera que el archivo llamado main.py tiene la siguiente dirección: ~/paso/domótica_python/main.py, en cuanto al segundo debe colocarse en el directorio: /var/www/html/ el mismo en el cual guardamos el archivo led_sc y en el que se crearon los permisos necesarios, copiamos y extraemos, la manera más fácil de copiar es utilizando una memoria flash.
Cuenta en Firebase
La obtención de una cuenta en Firebase es muy simple y gratuita en el modo de desarrollo y lo que nos va a interesar en los siguientes pasos es la URL que le sea asignada a la aplicación:
En mi caso particular se me ha asignado la dirección:
'https://fabian-velasco-pru01.firebaseio.com' que va a ser incluida en el script de control posteriormente.
Como se puede observar en el diagrama de la raíz sale un ramal llamado luces del que se derivan uno llamado comedor y otro llamado sala, esto para que sigua el modelo propuesto en http://riverajefer.blogspot.com/
A diferencia del primer caso donde no se aplica el modelo de tiempo real, es necesario primero ejecutar el archivo main.py desde una terminal para que se puedan controlar las salidas GPIO de la Raspberry Pi:
hay que ubicarse en el directorio en el que se guardó el archivo, en el caso de este documento: ~/paso/domótica_python/ en la terminal:
ejecutar el comando:
sudo python main.py
Usuarios conectados:
Inmediatamente se hace un cambio en cualquiera de los dos usuarios, este se refleja en los led's de la Raspberry Pi así como en el otro usuario.
Código para sala y comedor:
http://docs.python-guide.org/en/latest/scenarios/json/
main.py
#!/usr/bin/python#autor: Jefferson Rivera#modificaciones: Fabián Velascoimport sysimport signalimport jsonimport demjsonfrom gpiozero import LEDfrom clases.Conexion import Conexion
led_sala = LED(17)
led_comedor = LED(18)
def procesa(respuesta):
print respuesta respu = demjson.encode(respuesta) print respu respuesta_json = json.loads(respu) sala_01 = respuesta_json["sala"] print sala_01 comedor_01 = respuesta_json["comedor"]
if sala_01 == True: led_sala.on() print "Foco sala encendido" else: led_sala.off() print "Foco sala apagado"
if comedor_01 == True: led_comedor.on() print "Foco comedor encendido" else: led_comedor.off() print "Foco comedor apagado" sys.stdout.flush()
try:print "Inicio"t = Conexion(procesa)t.daemon=Truet.start()signal.pause()
except (KeyboardInterrupt, SystemExit):raiseprint "Salida"
Conexion.py
from firebase import firebaseimport threadingimport jsonimport time
class Conexion(threading.Thread):
def __init__(self, cb): threading.Thread.__init__(self) self.callback = cb self.fire = firebase.FirebaseApplication('https://fabian-velasco-pru01.firebaseio.com', None) self.ultimo_estado = self.fire.get('/luces', None) self.callback(self.ultimo_estado)
def run(self):E = []E.append(self.ultimo_estado)i = 0
while True:estado_actual = self.fire.get('/luces', None)
E.append(estado_actual)
if E[i] != E[-1]:self.callback(estado_actual)
del E[0]i = i+itime.sleep(0.3)
Desarrollo aplicación Web
index.html
<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8"><title>Domótica</title>
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> <!-- importamos la libreria de firebase cliente para javascript --> <script src='https://cdn.firebase.com/js/client/2.2.1/firebase.js'></script>
<!-- Logica de comunicación con firebase --> <script src="js/script.js"></script> <link rel="stylesheet" href="css/estilos.css"> <link href='https://fonts.googleapis.com/css?family=Fjalla+One' rel='stylesheet' type='text/css'></head><body>
<h1>DOMÓTICA</h1> <div align="center">
<h2>Luz Sala</h2> <label class="switch"> <input class="switch-input" type="checkbox" id="switch_sala" /> <span class="switch-label" data-on="On" data-off="Off"></span> <span class="switch-handle"></span> </label> </div>
<div align="center">
<h2>Luz Comedor</h2> <label class="switch"> <input class="switch-input" type="checkbox" id="switch_comedor" /> <span class="switch-label" data-on="On" data-off="Off"></span> <span class="switch-handle"></span> </label> </div></body></html>
estilos.css
body{ background-color: #2E2E2E;}
h1{ font-family: 'Fjalla One', sans-serif; text-align: center; color: #FFF; font-size: 50px; text-shadow: 1px 1px 7px rgba(150, 150, 150, 1);}h2{ text-align: center; color: #FFF; font-size: 45px; text-shadow: 1px 1px 7px rgba(150, 150, 150, 1);}
.bild { position:absolute; display:none; width:1000px; height:735px;}.bg { position: fixed; top: 0; left: 0; z-index: -99; /* Preserve aspet ratio */ min-width: 100%; min-height: 100%; z-index: -999; }.bgwidth { width: 100%; }.bgheight { height: 100%; } .switch { position: relative; display: block; vertical-align: top; width: 100px; height: 30px; padding: 3px; margin: 0 10px 10px 0; background: linear-gradient(to bottom, #eeeeee, #FFFFFF 25px); background-image: -webkit-linear-gradient(top, #eeeeee, #FFFFFF 25px); border-radius: 18px; box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05); cursor: pointer;}.switch-input {
position: absolute; top: 0; left: 0; opacity: 0;}.switch-label { position: relative; display: block; height: inherit; font-size: 10px; text-transform: uppercase; background: #eceeef; border-radius: inherit; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15);}.switch-label:before, .switch-label:after { position: absolute; top: 50%; margin-top: -.5em; line-height: 1; -webkit-transition: inherit; -moz-transition: inherit; -o-transition: inherit; transition: inherit;}.switch-label:before { content: attr(data-off); right: 11px; color: #aaaaaa; text-shadow: 0 1px rgba(255, 255, 255, 0.5);}.switch-label:after { content: attr(data-on); left: 11px; color: #FFFFFF; text-shadow: 0 1px rgba(0, 0, 0, 0.2); opacity: 0;}.switch-input:checked ~ .switch-label { background: #E1B42B; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2);}.switch-input:checked ~ .switch-label:before { opacity: 0;}.switch-input:checked ~ .switch-label:after { opacity: 1;}.switch-handle { position: absolute; top: 4px; left: 4px;
width: 28px; height: 28px; background: linear-gradient(to bottom, #FFFFFF 40%, #f0f0f0); background-image: -webkit-linear-gradient(top, #FFFFFF 40%, #f0f0f0); border-radius: 100%; box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);}.switch-handle:before { content: ""; position: absolute; top: 50%; left: 50%; margin: -6px 0 0 -6px; width: 12px; height: 12px; background: linear-gradient(to bottom, #eeeeee, #FFFFFF); background-image: -webkit-linear-gradient(top, #eeeeee, #FFFFFF); border-radius: 6px; box-shadow: inset 0 1px rgba(0, 0, 0, 0.02);}.switch-input:checked ~ .switch-handle { left: 74px; box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2);} /* Transition========================== */.switch-label, .switch-handle { transition: All 0.3s ease; -webkit-transition: All 0.3s ease; -moz-transition: All 0.3s ease; -o-transition: All 0.3s ease;}
script.js
Nota: en este script es donde se utiliza la dirección a signada a su proyecto por Firebase.
/*@autor: Jefferson Rivera@email: [email protected] por Fabián Velasco*/
$(document).ready(function() {
//creamos un objeto de Firebase y le pasamos la URL como parámetro var ref = new Firebase("https://fabian-velasco-pru01.firebaseio.com/luces/");
//esta es la dirección que se debe cambiar por aquella que le sea asignada a su Proyecto en// Firebase
/***************************************************************** Obtenemos el valor del último estado ******************************************************************/ ref.once("value", function(res) {
var luzSala = res.child("sala").val(); $('#switch_sala').attr('checked', luzSala); // console.log("Estado actual_sala: " +luzSala)
var luzComedor = res.child("comedor").val(); $('#switch_comedor').attr('checked', luzComedor); // console.log("Estado actual_comedor: " +luzComedor)
});
/***************************************************************** Obtenemos el valor del estado de la luz en tiempo real, cada vez que haya cambio ****************************************************************** CAMBIOS EFECTUADOS EN AL CODIGO ORIGINAL ******************************************************************/ ref.child("sala").on("value", function(snapshot){ var luz_sala = snapshot.val(); $('#switch_sala').prop('checked', luz_sala); console.log("Cambio de estado_sala: " + luz_sala); });
ref.child("comedor").on("value", function(snapshot){ var luz_comedor = snapshot.val(); $('#switch_comedor').prop('checked', luz_comedor); console.log("Cambio de estado_comedor: " + luz_comedor); });
/***************************************************************** Actualizamos el valor, cambiado el estado del Switch ******************************************************************/ $('#switch_sala').on('change', function(){ if(this.checked) { console.log("On") ref.update({ sala: true }); } else{ console.log("Off") ref.update({ sala: false }); } });
$('#switch_comedor').on('change', function(){ if(this.checked) {
console.log("On") ref.update({ comedor: true }); } else{ console.log("Off") ref.update({ comedor: false }); } });
});
Firebase Dashboard
Conforme se vaya utilizando la aplicación se va registrando su uso en el Dashboard de Firebase.
Esta es toda la información requerida, espero que sea de utilidad.
Muchas gracias.
Archivo led_schttps://www.dropbox.com/s/9e212vygjd5hsl3/led_sc.tar?dl=0
Archivo tiempo_real_schttps://www.dropbox.com/s/fdbp2be0x4j9sbh/tiempo_real_sc.tar?dl=0
Archivo domotica_python.tarhttps://www.dropbox.com/s/jn1tx7tc17irh5m/domotica_python.tar?dl=0