Translate

lunes, 24 de febrero de 2014

Raspberry Pi y Tornado Hola Mundo

En este tutorial se crea el archivo base del proyecto que integra Arduino y Pic con Raspberry Pi midiendo y controlado variables físicas a través de WebSockets.

Instrucciones en video:



Código Fuente en:  /charlesrct/Tornado-RasberryPi

Crear la siguiente estructura de archivos:

En la carpeta TutoTornado, crear el archivo turotor.py y agregar el siguiente código:


import tornado.web
import tornado.websocket
import tornado.httpserver
import tornado.ioloop
import tornado.options
import json
from uuid import uuid4

Después de importar las librerías, se crean las clases "Raspberry", "RenderHandler", "LedHandler", "RaspberryHandler" y "Application"

class Raspberry(object):
 callbacks = []

 def register(self, callback):
  self.callbacks.append(callback)

 def unregister(self, callback):
  self.callbacks.remove(callback)

 def ledON(self):
  self.notifyCallbacks(1, "Led Encendido")

 def ledOFF(self):
  self.notifyCallbacks(0, "Led Apagado")

 def notifyCallbacks(self, ledStdo, estado):
  for callback in self.callbacks:
   callback(ledStdo, estado)

Las funciones "register" y  "unregister" se encargan crear y destruir las sesiones cuando un cliente se conecta o desconecta, las funciones "ledON" y "ledOFF" actualizan el estado del LED y la función "notifyCallbacks" envia los mensajes en tiempo real, la página web de todos los clientes conectados. 

class RenderHandler(tornado.web.RequestHandler):

 def get(self):
  session = uuid4()
  estado = "...Iniciando"
  self.render("index.html", session=session, estado=estado)


Esta clase se encarga de mostrar la plantilla o "template" llamado index.html y permitir que la variable "estado" sea actualizada por python y javascript cada ves que ocurre un evento.  

class LedHandler(tornado.web.RequestHandler):
 def post(self):
  action = self.get_argument('action')
  session = self.get_argument('session')
  
  if not session:
   self.set_status(400)
   return

  if action == 'ledon':
   self.application.raspberry.ledON()
  elif action == 'ledoff':
   self.application.raspberry.ledOFF()
  else:
   self.set_status(400)


La clase "LedHandler" es la encargada de detectar las acciones (action) que los clientes desean ejecutar al presionar los botones "Encender el led" y "Apagar el led" de la página index.html 

class RaspberryHandler(tornado.websocket.WebSocketHandler):
 def open(self):
  self.write_message('{"estado":"Conexion abierta"}')
  self.application.raspberry.register(self.callback) 
  print("Conexion abierta ")
 
 def on_close(self): 
  self.application.raspberry.unregister(self.callback)
  print("Conexion Cerrada")

 def on_message(self, message):
  self.write_message('{"estado":"Mensaje Recibido"}')
  print("Mensaje Recibido: {}" .format(message))  

 def callback(self, ledStdo, estado):

  self.write_message(json.dumps({
   "ledStdo": ledStdo,
   "estado": estado
   }))


La clase "RaspberryHandler" se comunica via WebSockets con todos los clientes, para detectar cuando un cliente se conecta, se desconecta o para actualizar a todos los clientes los mensajes del estado del LED. En la función "callback" se reciben como parámetros el estado del Led y se trasmiten en formato json a todos los clientes utilizando los Sockets abiertos con cada cliente.  

class Application(tornado.web.Application):
 try:
  def __init__(self):
   self.raspberry = Raspberry()
  
   handlers = [
    (r'/', RenderHandler),
    (r'/led', LedHandler),
    (r'/status', RaspberryHandler)
   ]
  
   settings = {
    'template_path': 'templates',
    'static_path': 'static'
   }

   tornado.web.Application.__init__(self, handlers, **settings)
 
 except keyboardInterrupt:
  print("No se pudo realizar la Conexion")


La clase "Application" es la encargada de integrar todas las clases y preparar la app para que todo funcione, creando las URL amigables, indicando la ruta donde se encuentran las carpetas con las platillas, imágenes, archivos de estilos .CSS y arhivos de funciones JavaScript .JS.

if __name__ == '__main__':
 tornado.options.parse_command_line() 
 
 app = Application()
 server = tornado.httpserver.HTTPServer(app)
 server.listen(5000)
 tornado.ioloop.IOLoop.instance().start()


Finalmente arrancamos la app iniciando el servidor web, por el puerto 5000.

Creando la plantilla index.html y el archivo de funciones JavaScript


En la carpeta "templates" se crea el archivo "index.html". En la línea 6 incluimos jQuery y en la línea 7 el archivo de funciones "raspberry.js". 

En la línea 12 se muestra el contenido de la variable "estado" y en la línea 15 se almacena la "session" establecida para cada cliente.

<!doctype html>
<html lang="es">
<head>
 <meta charset="UTF-8">
 <title>RaspBerry Pi Real Time</title>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
  <script src="{{ static_url('scripts/raspberry.js') }}" type="application/javascript"></script>
</head>
<body>
  <div>
   <h1>RaspBerry Pi Real Time</h1>
   <p><span>Estado de la conexión: <span style="color: red;" id="estado">{{ estado }}</span> ..:: By RaspBerryPi ::..</span></p>
  </div>
  <hr />
  <input type="hidden" id="session" value="{{ session }}" />
  <div id="led-on">
   <p><input type="submit" value="Encender El Led" id="add-button" /></p>
  </div>
  <div id="led-off" style="display: none;">
   <p><input type="submit" value="Apagar El Led" id="remove-button" /></p>
  </div>
</body>
</html>

Este es el resultado de la interfaz:

En la carpeta "static/scripts" se crea el archivo "raspberry.js". La variable "ipDir" se debe actualizar con la dirección ip del Raspberry Pi o de la computadora donde se inició el servidor HTTP. El puerto es el mismo que se crea en el archivo tutotor.py, en este caso el puerto 5000.

En Windows se ejecuta el comando ipconfig y en linux el comando ifconfig para saber esta dirección.

Con jQuery adicionamos los eventos a los botones y la función "requestRaspberry" crea la comunicación para cada cliente via WebSockets.

var ipDir = '//192.168.0.6:5000' //IP del servidor asignado por el router

$(document).ready(function() {
 document.session = $('#session').val();
 setTimeout(requestRaspberry, 100);
 $('#add-button').click(function(event) {
  jQuery.ajax({
   url: ipDir + '/led',
   type: 'POST',
   data: {
    session: document.session,
    action: 'ledon'
   },
   dataType: 'json',
   beforeSend: function(xhr, settings) {
   },
   success: function(data, status, xhr) {
   }
  });
 });

$('#remove-button').click(function(event) {
 jQuery.ajax({
  url: ipDir + '/led',
  type: 'POST',
  data: {
   session: document.session,
   action: 'ledoff'
  },
  dataType: 'json',
  beforeSend: function(xhr, settings) {
  },
  success: function(data, status, xhr) {
  }
 });
 });

});

function requestRaspberry() {
 var host = 'ws:'+ipDir+'/status';
 
 var websocket = new WebSocket(host);

 websocket.onopen = function (evt) { };
 websocket.onmessage = function(evt) {

  //console.log($.parseJSON(evt.data))

  if ($.parseJSON(evt.data)['estado'] != "null") {
   $('#estado').html($.parseJSON(evt.data)['estado']);
  };

  if($.parseJSON(evt.data)['ledStdo'] == 0){
   $('#led-off').hide();
   $('#led-on').show();
   $('.count').html($.parseJSON(evt.data)['ledStdo']);
  }
  
  if($.parseJSON(evt.data)['ledStdo'] == 1){
   $('#led-off').show();
   $('#led-on').hide();
   $('.count').html($.parseJSON(evt.data)['ledStdo']);
  }

 };
 websocket.onerror = function (evt) { };
}

Finalmente, se debe ejecutar la aplicación desde la consola, ingresando a la carpeta "TutoTornado".  En la consola del Raspberry Pi con el comando:  sudo python turotor.py y en Windows con el comando python turotor.py


En la barra de direcciones de un navegador web, digitar la dirección IP asignada al servidor mas el puerto 5000. Para el ejemplo es: http://192.168.0.6:5000/


Al presionar el botón "Encender El Led" debe cambiar el mensaje en color rojo para todos los clientes conectados a la página web:


En la próxima entrada se aplicará este mismo código desarrollado al GPIO del Raspberry Pi.

No hay comentarios.:

Publicar un comentario