Proyecto UWS (1)

publicado en: Diseño web, Raspberry Pi, SCADA | 0

Este año los reyes me llegaron con unos días de retraso, pero han valido la pena. Traían una flamante Raspberry Pi 2 B, con la que me he puesto a trastear un poco. Tenía ganas de programar un mini SCADA, que pudiese correr en un pequeño controlador como éste y que fuese accesible desde un navegador web. Sobra decir que es una primerísima versión todavía en pañales, posiblemente plagada de fallos y con una documentación escasa, por no decir ausente. Por tanto, no aconsejo de momento su uso para aplicaciones sensibles. Poco a poco iré mejorándola en los ratos libres.

Raspberry Pi 2 B
Raspberry Pi 2 B

Me gustaría en primer lugar dar una idea rápida del objetivo, así que voy a arrancar con una exposición en caliente. El único requisito de partida es un controlador con puerto Ethernet que pueda correr Python. He usado la Raspberry pero bien podría servir un PC con Windows, o Linux, o incluso algunas tablets. El SCADA, al que he dado en llamar Universal Web Server, tiene de momento poco de universal, ya que sólo comunica en Modbus TCP. Por tanto, también hará falta un servidor que comunique en este protocolo. Lo hacen muchos autómatas; yo cuento con un simulador. En estas circunstancias, nuestro SCADA consiste en un breve código en Python que da de alta dicho esclavo, inicia las comunicaciones con él y va sirviendo la información vía web. Quedaría como sigue:

Al ejecutar este código se hace todo lo que hemos comentado. He preparado una página donde se pueden ver los primeros valores de las áreas de memoria del PLC (coils, discrete inputs, holdings y register inputs). Como es de esperar, también es posible modificar datos cuando están accesibles para escritura.
Por supuesto, como SCADA es algo esquemático. El motivo es mi pereza, no la funcionalidad de UWS. Por terminar de mostrar el código del ejemplo, que pasaré a comentar en sucesivas entradas, copio a continuación el HTML de la página. Espero que, como creo haber hecho con la parte de Python, la sintaxis sea lo bastante simple como para, incluso sin describir los detalles, el contenido sea intuitivo. Se hace notar que no es necesario ejecutar código adicional, ya que el script en j.js se encarga de localizar todas aquellas etiquetas con atributo data-dir, interpretarlas como elementos dinámicos, y asociarles los eventos necesarios, que varían en función del tipo de etiqueta. Por tanto, podemos incluir tantas páginas como queramos con un desarrollo web al uso. Basta incluir los archivos, como index.html, dentro de la carpeta www, que debe estar situada en la ruta desde la que estamos ejecutando la aplicación.

Por último, hacer notar que las comunicaciones Modbus de este ejemplo hacen uso del paquete pymodbus3. Se puede instalar a través de pip con:

A continuación incluyo el enlace a la aplicación en Python, los módulos de que depende y la página web de este ejemplo. En las siguientes entradas iré documentando la API y espero que ampliando la funcionalidad y protocolos de comunicación. Espero que os sea de utilidad.
Esta entrada continúa en Proyecto UWS (2).
Recursos asociados:
SCADA UWS (versión 1.0) y código de ejemplo.

Librería Manager.js (2). Uso

publicado en: Diseño web | 0

manager-ejemploEn la entrada previa presenté la librería Manager.js, que he desarrollado para la creación de ciertas aplicaciones web sencillas. El requisito necesario, como comentaba, era que la información que maneja tenga una estructura jerárquica, relacionándose las tablas entre sí, y en último término con la del usuario que se ha registrado. Este requisito se cumple con bastante frecuencia, y con esta librería la programación en el lado del cliente puede hacerse muy ágil. Para describir la API escogí un ejemplo: una página web en la que los usuarios pueden gestionar proyectos, cada uno de ellos compuesto por uno o varios controladores, a los que se le puede introducir una configuración dando de alta señales. En la imagen de la derecha se muestra un caso posible, con un controlador que registra una temperatura.
La aplicación, para gestionar la información, debe replicar su estructura a tres niveles. El primero es la base de datos, donde se han de crear las tablas y relaciones correspondientes. En nuestro caso, sería una tabla user para gestionar los usuarios, cosa que hacíamos con la librería User.js, que comenté en su momento; y las tablas project, controller y channel. Cada una de ellas posee un campo (user_id, project_id, controller_id) que enlaza con la clave principal de la tabla previa.
En dicha entrada también vimos cómo se introduciría la configuración necesaria para instanciar la representación de las tablas de la base de datos. Se hacía proporcionando al constructor de Manager información acerca del nombre de la tabla, su denominación en la web, información sobre su contenido y representación, y por último, su relación con la tabla padre. El código necesario era el siguiente:

En realidad, hecho esto, hay poco más que programar, puesto que la definición de las tablas contiene el grueso de la información que se necesita para determinar el comportamiento en la página web. Veámoslo.
Lo primero que tendríamos que desarrollar es el código HTML para el navegador. Habría que construir los formularios correspondientes para introducir o editar la información, botones para enviarla, borrar elementos, cancelar la edición, etc. Ésta es una labor tediosa, que los entornos de programación como Django o Ruby on Rails agilizan enormemente con el uso de plantillas. Por supuesto, para nuestra aplicación sería posible el recorrido largo, y llevar a cabo un desarrollo personalizado del código, pero la librería proporciona una alternativa extremadamente rápida. El método form() genera el código HTML correspondiente a cada tabla. Podemos crear el contenido de nuestra página tan solo con estas líneas:

Las capas project, controller y channel sirven para posicionar dicho contenido dentro de la página. Con esto, ya tendríamos los elementos que se muestran en la imagen superior. Ahora es necesario asociarles eventos. Manager trabaja con los siguientes:

  • populate(): Pide al servidor las claves principales y descripciones de cada elemento de la tabla, para rellenar los desplegables.
  • change(): Cuando se selecciona un registro en un desplegable, este método actualiza las descendientes.
  • edit(): Pide un registro de la tabla, para mostrarlo o editarlo.
  • destroy(): Elimina un elemento.
  • create(): Muestra el formulario para crear un nuevo registro.
  • close(): Cierra el formulario sin hacer modificaciones.
  • submit(): Envía los datos tanto para la creación como para la edición de un registro.

Los métodos populate(), change, edit(), destroy() y submit() actúan contra el servidor. No voy a detallar aquí la comunicación, que dejo para una documentación más formal, pero baste decir que Manager.js trabaja de forma similar lo hacía la librería User.js. Es decir, se hacen llamadas asíncronas a una URL en cuyos parámetros se especifica la acción a realizar y los datos necesarios, y con la respuesta se actualiza la página.
Para animar la página, sería necesario asociar al botón project_create el método create(), a project_collection el método change(), etc. Nuevamente podemos hacerlo paso a paso, o optar por una vía rápida. El método events() asocia de una tacada todos los eventos necesarios, con lo cual nuestro código quedaría así:

Por último, es necesario un pequeño empujoncito para que todo eche a andar. Se trata tan solo de poblar las tablas hijas de la de gestión de usuarios en el momento en que éste se identifica:

Esto es todo. Código minimalista para un desarrollo rápido y sin florituras. Se puede embellecer con CSS o programar por separado lo que la librería no contempla (como crear máscaras para los campos de texto), pero eso es un capítulo aparte.
Recursos asociados:
Librería Manager.js para gestión de información con estructura de árbol
Ejemplo de uso de la librería Manager.js

Librería Manager.js (1). Instanciación

publicado en: Diseño web | 0

En aplicaciones de servicios es enormemente corriente una estructura particular de base de datos en la que las tablas se relacionan de forma jerárquica. Por ejemplo, una organización que trabaja con proyectos, que se subdividen en tareas. O un deportista, que ejercita rutinas, que contienen series agrupadas por días. Podría seguir describiendo casos que, si no se amoldan exactamente a esta forma de organizar la información, sí lo hacen en su parte troncal. Cuando se quiere afrontar una aplicación de este tipo con arquitectura cliente-servidor, es preciso manejar esta estructura jerárquica al menos en tres ámbitos: la base de datos, el código en el lado del servidor y el código en el lado del cliente. Si lo que se pretende es desarrollar una aplicación web, existen muchos entornos de desarrollo que permiten agilizar la tarea, como Django, Ruby on Rails, Symfony… He usado y probado varios y, si bien son prácticos y ahorran mucho tiempo, tenía la espina de construir algo aún más ágil para estos casos sencillos en los que se trabaja con tablas enlazadas en forma de árbol. Algo con la inmediatez de Django, pero que permita trabajar directamente sobre el código final y sin riesgo de echar a perder la información almacenada por alterar una tabla. En cualquier caso, es un ejercicio modesto para aplicaciones sencillas, sin la ambición de acercarse a las capacidades de los entornos de desarrollo existentes. A aquellos lectores que desconozcan el tema, antes de seguir les recomiendo leer una breve introducción a la programación web.

Hecha la introducción, voy a replantear la situación que deseamos afrontar. Se quiere construir una aplicación web que gestione información estructurada en tablas que se enlazan en árbol. La tabla raíz contiene el usuario que se ha identificado, y que se va a gestionar en el lado del cliente con la librería Users.js, que ya comenté anteriormente. El resto de tablas tendrán un campo que indexará a la clave principal de la tabla de usuarios, o de otra. En el navegador se representan los datos de estas tablas, según se vayan seleccionando los valores de las claves principales. No tiene por qué poder accederse necesariamente a todas (en particular, queda excluida la tabla de usuarios por motivos de seguridad), ni a todos los datos. La gestión de la información, mediada por el código en el lado del servidor, va a ser sencilla: consulta, inserción, borrado, modificación… Por supuesto, se debe vigilar la seguridad (un usuario no puede ver o alterar los datos de otro, por ejemplo). Este tipo de aplicaciones básicas, como decía, son muy comunes, y es sencillo crear librerías genéricas para acometer su desarrollo. En esta entrada voy a describir la librería Manager.js, que va a simplificar la programación en el lado del cliente. Lo ideal es disponer de una librería similar en el servidor, e incluso mantener relacionadas las estructuras que manejan ambas con las de la propia base de datos.
A diferencia de la descripción pormenorizada que hice de Users.js, en este caso me voy a ceñir al API. En Manager.js, cada tabla de la base de datos (menos la de usuarios) viene representada por una instancia de la clase. Los parámetros del constructor van a contener tanto información de dicha tabla como de su relación con las otras y su modo de representación:

El parámetro tabla recoge el nombre de la tabla (real o traducido por el servidor, si se quiere), y nombre es el texto con el cual se va a identificar en la página web. Le sigue un array con los campos de información que la tabla va a contener; más adelante me detendré en su formato, puesto que almacena información adicional relativa a la representación. Por último, padre es el objeto que representa a la tabla a la que se indexa. Si fuese una de las tablas inmediatamente descendientes de la raíz (la tabla de usuarios), este último parámetro es nulo. Por ejemplo, vamos a imaginar que desde nuestra aplicación se gestionan proyectos. La construcción de la instancia asociada de Manager se haría tal como sigue:

Es decir, los proyectos se van a almacenar en la tabla de nombre project, cada una de sus filas es un “Proyecto”, no contienen información adicional y no dependen sino del usuario que los crea. Obviamente, esta simplicidad oculta varios sobreentendidos. El primero es que la tabla project (todas nuestras tablas en realidad) debe contener los tres siguientes campos:

  • id: es la clave principal; debe ser un valor entero con autoincremento.
  • descriptor: es la descripción de cada elemento; en nuestra tabla de proyectos, será el nombre que el usuario da al proyecto; si fuese una tabla con recetas, por ejemplo, sería el título de cada receta.
  • [tablapadre]_id, donde [tablapadre] es el nombre de la tabla enlazada. Así, en la tabla project este campo se llama user_id, puesto que un usuario puede tener varios proyectos. Su contenido es, como es fácil adivinar, las claves principales del campo indexado.

Vamos a crear otra tabla que dependa de ésta. Imaginemos que nuestros proyectos son instalaciones que poseen varios controladores. Creamos por tanto una segunda tabla llamada controller. Sus campos van a ser los mínimos obligatorios: id, descriptor y project_id. Y el manejador de esta tabla se instancia en Javascript con el siguiente código:

Con lo dicho, es posible representar la jerarquía de tablas que comentaba al principio. Sin embargo, para darle riqueza a nuestra construcción, es preciso poder almacenar información adicional. Me dejé antes por describir el tercer parámetro del constructor, que va a tener información de los campos adicionales de las tablas, además de su representación. Dicho parámetro es un array de arrays. El primer nivel hace referencia a cada campo, y el segundo contiene lo siguiente:

  1. El nombre del campo en la tabla.
  2. El nombre del campo tal y como se representa en el navegador.
  3. Un objeto con información sobre su representación. Por defecto, la edición se hará mediante un campo de texto. Si el objeto contiene un elemento class, se usará una entrada de otro tipo:
    • Si class vale “checkbox”, se muestra una casilla de selección.
    • Si class es “select”, se usa un desplegable; los valores a elegir serán los contenidos en el elemento values, que debe guardar un objeto con parejas valor/representación.

Por ejemplo, si queremos guardar por cada controlador su IP (o dominio) y su MAC, haríamos la siguiente instanciación:

Como no se ha especificado un elemento class, tanto la dirección como la MAC se van a introducir y editar mediante un cuadro de texto.
Vamos a hacer algo más compleja nuestra base de datos, y a cada controlador le asociaremos una serie de señales, en la tabla channel. Estas señales podrán ser de entrada/salida o de memoria, y queremos que se elija un tipo u otro mediante un desplegable. El objeto que describe esta representación será entonces el siguiente:

Es decir, indicamos mediante “select” que queremos usar un desplegable, y sus valores posibles serán “E/S” o “Memoria”, que se almacenarán en la base de datos como “F” y “M” respectivamente. También vamos a crear campos para almacenar información adicional de la señal, como si se historiza o no (campo checkbox):

Esta entrada continúa en Librería Manager.js (2). Uso.
Recursos asociados:
Librería Manager.js para gestión de información con estructura de árbol
Ejemplo de uso de la librería Manager.js

Librería Users.js (2). Identificación

publicado en: Diseño web | 0

Anteriormente he descrito cómo se construye un formulario de registro de usuarios. Comentaba que, terminado el envío, era tarea del servidor enviar un correo electrónico de confirmación, lo que permite asegurar que quien aporta los datos es efectivamente el dueño de la cuenta. Pero es necesario un paso adicional para completar este chequeo: el usuario debe proporcionar al servidor un código contenido en dicho correo que sirva como confirmación. Lo usual es generar una clave cuya devolución se pide, por lo general incluyéndola como una variable en una URL. El servidor la recibe mediante el método GET y completa la validación del usuario. Y en tal caso, lo adecuado es enviar un segundo correo para informar de ello. No se describen estos pasos en detalle, ya que el propósito de estas entradas es presentar la librería Users.js, que recoge el código en Javascript para la gestión de usuarios.
Terminado el registro, podemos continuar con la identificación. Para llevarla a cabo, es necesario que la página web contenga inicialmente dos campos: un identificador (que en nuestro caso es el alias que hemos introducido en el registro, pero bien podría valer el correo electrónico) y la contraseña. También se debe dar posibilidad a restaurar dicha contraseña en caso de olvido, y de registrarse en caso de no haberlo hecho antes. Para estos dos fines se incluyen los botones correspondientes.

Como en otras ocasiones, se ha reservado una etiqueta span para mostrar mensajes de error. El más importante es la advertencia de que nos hemos equivocado al introducir el nombre o la contraseña.

Hago notar que el contenido del formulario de identificación no se muestra inicialmente, sino que se ha mantenido oculto. La razón es que se pretende usar la misma página para el acceso inicial y el contenido una vez ya nos hemos identificado. Haciendo esto se evita que el navegador salte a una página nueva, con el consiguiente retraso en la recarga. Esto mejora la experiencia de usuario, pero nos enfrenta a un problema: cuando se muestra la página por primera vez, es necesario averiguar si el usuario se ha identificado o no previamente. Como esta información no la almacena el navegador, sino el servidor en una variable de sesión, debemos de gestionar desde Javascript una consulta inicial para conocer este valor. Esta tarea, y la actualización de la página que lleva asociada, la he llevado a la función refresh_user que se ve ha visto al final del código del evento de envío de datos identificativos.

La función refresh_user hace visible (parte else) el formulario de identificación caso de que no se haya iniciado sesión. Pero si ya lo ha hecho, lo que se visibiliza es la capa registered, donde se muestra todo lo que debe verse al estar ya dentro. Esto incluye varios elementos:

  • El nombre de usuario.
  • El botón para cerrar sesión.
  • El botón para modificar datos del perfil. No voy a entrar en detalles sobre esta parte, para no hacer farragosa la entrada pero, como puede observarse, aunque los campos estén ocultos, se han rellenado con el código previo.
  • Y todo lo demás, que no se muestra por ser específico de cada proyecto, pero que estaría recogido dentro de la capa registered.

Por último, y me dejo algunos aspectos como la modificación de datos o la restauración de contraseña, es importante incluir la desconexión, que como se ve en el código de arriba se hace desde el botón logout. Dicho cierre se realiza desde el servidor, liberando todas las variables de sesión que se hayan podido crear, de modo que el código Javascript sólo debe hacer la llamada correspondiente y refrescar la página:

Recursos asociados:
Librería User.js para gestión de usuarios
Ejemplo de uso de la librería User.js