Proyecto UWS (7)

publicado en: Base de datos, Programación, SCADA | 0

Esta entrada es continuación de Proyecto UWS (6).

Entrada de datos
Entrada de datos

Mi proyecto personal de entorno de desarrollo SCADA ha tomado nuevos derroteros. Tenía pendientes bastantes temas (usuarios, otros drivers, más controles para la interfaz…) pero se ha apropiado de él una necesidad que ha orientado la nueva versión. Pongo en situación: quiero almacenar una serie de valores históricos que se van a recoger manualmente, para poder analizarlos con posterioridad: sacar tendencias, exportar tablas, hacer cálculos… Podría recurrir a una hoja de cálculo, pero no es lo ideal si la tienen que manejar varias manos y el volumen de información es grande. Lo adecuado es una base de datos, pero hay que proporcionar un método de entrada sencillo, como una página web. Y aquí es donde he pensado añadir un nuevo controlador a UWS. Puede parecer forzar algo el sentido original del SCADA, pero bien pensado, ¿qué diferencia hay entre intercambiar información con un servidor físico a hacerlo con una base de datos?
El nuevo módulo, DBPLCModule hace de driver para este tipo de comunicación. La clase derivada de PLC controla la conexión, y parece natural que una tabla se amolde a una clase Memory y cada variable Tag represente una columna. La reinterpretación de los métodos existentes es también directa. ¿Qué devuelve get()? El valor más reciente. Para eso, hay que poder ordenarlos temporalmente, y eso requiere una columna en cada tabla de tipo datetime. Es consistente con la necesidad de almacenar valores históricos. ¿Qué almacena set()? El valor actual. O uno antiguo, aprovechando el polimorfismo. ¿Qué hace el método connect() del PLC? Conectarse a la base de datos y actualizar en el motor el valor de las variables de acuerdo a su contenido. También se añade un escaneo periódico, porque otra aplicación podría estar actualizando por su cuenta la información. Aquí he añadido una funcionalidad interesante: cuando llamamos a este método, DBPLC tiene ya toda la información necesaria sobre la base de datos, tablas y columnas, así que, ¿por qué no crear la estructura si no existe previamente? Hecho, ni siquiera debemos molestarnos en levantar todo este andamiaje, basta con la clásica llamada a ensemble.deploy() para tenerlo todo funcionando.

Hasta aquí lo directo, pero sigue siendo insuficiente para responder a las necesidades iniciales. Para ello es necesario añadir algunas funciones extra:

  • Datapicker
    Datapicker

    Tenemos que insertar nuevos datos. Y no nos vale Tag.set(), porque debemos poder agregar un registro completo. Se resuelve con el método Memory.set_row(), al que se le pasa como argumento un diccionario con los valores de las columnas y, opcionalmente, la fecha y hora de registro. Esta función clama a gritos un nuevo control en la interfaz, que tiene que ser un formulario. He adoptado el mismo criterio de simplicidad que otras veces: el atributo data-table lo liga a nuestro control, y nos podemos olvidar de código extra.
    El código en j.js busca todas las variables (atributo data-dir) en su interior, actualiza los valores iniciales y añade los eventos necesarios. El campo con atributo data-datetime es opcional, y se convertirá en un calendario para seleccionar fecha y hora de inserción. Para quien se lo pregunte, no necesitamos hacer referencia a la tabla, ya que está implícita en la memoria a la que pertenecen las variables.

  • Es necesario también un método para reclamar los valores de una variable entre dos fechas. Éste es Tag.get_data(), al que se le pasan los instantes de inicio y el de fin, y devuelve un listado de pares [tiempo,valor]. Esto va a ser útil para, por ejemplo, trazar una tendencia. También va a ser sencillo para el desarrollador, basta incluir un div con las variables dentro del atributo data-trend, y UWS lo transformará en una gráfica.

    Tendencia
    Tendencia

    En este caso va a ser importante añadir un id, para en el futuro discriminar acciones sobre diferentes tendencias. Para este elemento he recurrido a la estupenda librería Flot. De momento, las tendencias de UWS están en una fase muy inicial, y necesitan controles extra para moverse, añadir y eliminar variables, ampliar y reducir, usar varias escalas, etc.

Faltan todavía más cosas. Es necesario poder exportar datos, consultar valores en una fecha determinada, quizás editar la información… Además de la gestión de usuarios. Será en próximas entregas.
Recursos asociados:
SCADA UWS (versión 1.4) y código de ejemplo.

Corrosión galvánica

publicado en: General | 0
Corrosión
Corrosión

En la fotografía de la izquierda se muestra un sensor de proximidad que no funcionaba adecuadamente. He partido la cubierta, sellada herméticamente, para poder mostrar los contactos. Como se observa, están atacados por la corrosión. También la tarjeta, aunque en su caso es fácil de entender, ya que no se encuentra tan bien protegida; pertenece a un equipo que ha sufrido durante años condiciones duras, a la intemperie y sometido a altas temperaturas. Pero volvamos al sensor de proximidad. ¿Cómo puede afectar la corrosión a un equipo perfectamente sellado? Una ayuda: las pistas de la tarjeta son de cobre, los cables del sensor, de aluminio, y el contacto, aunque no puede verse, posiblemente sea cobre recubierto de plata.
La corrosión es una reacción química redox, en la que un elemento (ánodo) cede electrones a otro (cátodo). El primero se oxida y el segundo se reduce. Por ejemplo, en el caso clásico del hierro, un átomo pierde dos electrones y se los aporta al oxígeno. Para ser precisos, esto es sólo una primera oxidación a Fe2+; para ser precisos, el oxígeno se une a dos protones (procedentes de ácido carbónico, por ejemplo) y produce agua. Pero no quiero desviarme con los detalles. Molecularmente se produce tanto esta reacción como su contraria, pero tiene más preferencia la que libera más energía, como en todo proceso termodinámico. Así que se puede determinar cómo y en qué grado va a tener lugar este proceso contabilizando energías absorbidas o liberadas por los iones. A estas energías se las llama potenciales de reducción. Volvamos al ejemplo del hierro: el potencial con que el átomo cede dos electrones es de 0’44V; el de reducir el oxígeno para llevarlo a agua, 1’23V; la suma (1’67V) es positiva y elevada, por lo que la oxidación se produce rápidamente. Obviamente, para que la corrosión tenga lugar, es necesario que el medio permita el movimiento de cargas. El agua de mar, por ejemplo, es un electrolito fenomenal, y por esta razón las instalaciones en ambientes costeros sufren un deterioro más rápido. Otros factores, como una temperatura alta o gran superficie de contacto, también aceleran la reacción.
Cuando se pone en contacto dos metales se puede sufrir un caso particular de corrosión, denominada galvánica. La causa es idéntica: el que tiene menor potencial de reducción cede electrones al otro, y se oxida. Para determinar cómo resultan afectados los metales en un electrolito se usan series galvánicas (tablas de potenciales) o el índice anódico (diferencia de potencial con el oro, que sirve de referencia). Los elementos mejor situados se dice que son más nobles. Significa que tienen tienen prevalencia para recibir electrones, luego actúan como cátodo (se reducen) frente a su pareja, que se oxida. Si se pone en contacto cinc con cobre, por ejemplo, el primero cede electrones al segundo mientras se oxida. Esta corriente eléctrica puede aprovecharse, como se hace en una pila o celda galvánica. Si los metales son plata y cobre, en este caso será el cobre, menos noble (con menor potencial) el que se oxide. Si los índices de los dos metales son muy similares, como sucede con el acero y el aluminio, la corrosión es tan lenta que prácticamente no tiene lugar.

Índice anódico
Índice anódico

Por tanto, cuando en una instalación se ponen en contacto dos metales diferentes hay que contar con que puede producirse corrosión galvánica. El más noble oxidará al que lo es menos. Más aún, si el primero está sometido a otro tipo de corrosión, atmosférica por ejemplo, su oxidación se traslada al otro metal, aunque no esté expuesto, debido a la libertad de desplazamiento de los electrones . Esto se puede aprovechar en nuestro beneficio: si se quiere proteger, por ejemplo, una tubería de cobre, se le puede conectar en un extremo una masa de cinc. Como se ha dicho, el cinc tiene menor potencial electroquímico, y va a aportar al cobre los electrones que perdería. Así, quien sufre la corrosión es el cinc, a pesar de no estar en contacto directo con el ambiente que lo está deteriorando.
El Reglamento de Baja Tensión establece como conductores permitidos en las instalaciones el cobre y el aluminio. Sin embargo, la combinación de ambos es mala en lo que toca a corrosión galvánica, debido a la gran diferencia de potencial. Por eso es importante evitar su unión o, si no es posible, limitar los efectos electroquímicos. El elemento perjudicado es el aluminio y, como se ha dicho, no basta proteger el punto donde se lleva a cabo la conexión, ya que la corrosión del cobre se desplaza hacia él. En la imagen que encabeza la entrada, la corrosión ya está afectando al cobre, como se aprecia por las manchas verdes de la tarjeta. Pero ya antes, el bimetal del contacto, a pesar de lo aislado que se encuentra, estaba deteriorado hasta el punto de no funcionar.

Proyecto UWS (6)

publicado en: OPC, Programación | 0

Esta entrada es continuación de Proyecto UWS (5).

UWS con alarmero e histórico
UWS con alarmero e histórico

El anidamiento de clases en Python no tiene otra función que la de organizar el código. ¡Pero el orden es importante! En la última versión de UWS he reestructurado el código para darle una estructura menos difusa. También he renombrado algunos métodos para hacer la nomenclatura más coherente. En lo que toca a funcionalidad, hay muchas posibilidades nuevas: alarmas, driver OPC, registro en ficheros, base de datos, etc. Voy a ir comentando de forma resumida:

  • La clase Tag ya no está aislada, sino dentro de la clase Memory, que a su vez está en PLC. Por supuesto, se puede crear una variable sin asociarla a un controlador (de hecho, las alarmas van a tener ese tratamiento), pero creo que esta estructura es más natural. Este cambio organizativo afecta varios aspectos. En primer lugar, se funden los módulos PLCModule con TagModule dentro del último. Los diferentes drivers, en módulos independientes, se expresan como derivados de la clase PLC, y redefinen si es preciso los atributos y métodos de Memory y Tag. En segundo lugar, desaparece la clase IOTag. Bien pensado no tenía mucho sentido: todas las variables se pueden escribir y leer, ¿por qué habría que hablar de variables de entrada/salida? Si es preciso, como se ha dicho, se derivará la clase Tag en el correspondiente módulo del driver.
  • Aunque no tengo espacio para detenerme a comentar todas las clases, creo que Tag merece cierta atención por su relevancia. Cada variable tiene un identificador, que debe ser único dentro del Ensemble. Puede pertenecer a un controlador, en cuyo caso formará parte de una memoria, dentro de la cual tendrá una dirección; o estar disociada. También posee una descripción, útil para ciertos registros, que comentaré más adelante. El valor de una variable se lee con get() y se modifica con set(). Al principio comencé usando métodos mágicos de Python (__get__(), __set__(), __getitem__(), __setitem__()…), pero lo he abandonado, porque interfieren con el uso de listas o diccionarios. Por poner un ejemplo, si hacemos listadetag[“tag1”]=5, posiblemente estamos deseando llamar al método __set()__ de la variable listadetag[“tag1”], pero en realidad lo que hacemos es asignar un 5 al elemento “tag1” de la lista, lo que es un destrozo mayúsculo. Para evitar confusiones, me he desecho de casi todos los métodos mágicos. Sólo he guardado una excepción con la asignación de controladores al Ensemble, donde no cabe solapamiento. Hecho este largo inciso, comentar que la clase Tag tiene también un método update(), de uso interno para los drivers. Así, una clase derivada puede redefinir set(), y llamar posteriormente a update() para las obligadas gestiones, como advertir a todos los objetos suscritos de los cambios de valores. ¿Qué es una suscripción? Lo comento en el siguiente punto.
  • Cuando queremos lanzar una serie de acciones ante los cambios de una variable, creamos un objeto derivado de la clase Subscriptor, se lo pasamos a la variable mediante Tag.subscribe() y redefinimos el método Subscriptor.update() para programar dichas acciones. El sistema es rápido, eficiente y enormemente versátil. Tanto que todo el SCADA se ha redefinido para funcionar de esta guisa. Las actualizaciones de la interfaz gráfica se hacen por suscripción; también las alarmas, las expresiones calculadas, los registros… Vayamos por partes.
  • Como comenté en la pasada entrada, una expresión es una evaluación de operaciones sobre variables; por ejemplo (nivel<5)+(nivel>90)+boya_minimo+boya_maximo. La clase Expression aprovecha la herencia múltiple para derivar tanto de Subscriptor como Tag. Esto quiere decir que será advertida de la modificación de los valores que la conforman, pero que también, al constituir una variable en sí misma, puede advertir a otros elementos de sus cambios. En concreto, el ejemplo anterior sería una alarma; pero también podríamos configurar escalados, fórmulas complejas, históricos, etc. En la inicialización de la expresión hay que pasarle un diccionario de variables, que puede cambiar a posteriori, pero que debe estar completo en el momento de llamar a su método analyze(), que es el que lanza las suscripciones.
  • Una alarma (clase Alarm) es un tipo de expresión (y por tanto, también tiene funciones de variable). Idealmente, debería asociarse a un grupo de alarmas (AlarmGroup), que contiene una lista de salidas o registros de sus cambios. Estos registros son derivados de la clase Output, que define cómo deben ser gestionados. Igual que se añadió al Ensemble un método import_tags() para importar las variables de un csv, se le ha incorporado un import_alarms() para importar las alarmas. Éste es un ejemplo de definición de alarmas:

    CSV de alarmas
    CSV de alarmas
  • La clase Output está definida en el módulo OutputModule. Su función es registrar expresiones de cualquier tipo, no necesariamente alarmas. Su método transform hace algunas sustituciones automáticas. Por ejemplo, {n.description} en el texto de la expresión es reemplazado por la descripción de la variable n, {n.value} se sustituye por su valor… Para dar un poco de juego, he incorporado varias clases derivadas que pueden ser de utilidad: LogOutput envía los cambios a la depuración de Python, SimpleFileOutput lleva a cabo el registro en un fichero y DataBaseOutput los escribe en base de datos.

    Log
    Log
  • El driver Modbus TCP se simplifica. Al instanciarlo se crean automáticamente sus diferentes memorias, y conforme se le van agregando variables, se redefinen automáticamente sus áreas de escaneo. ¡Ah, lo había olvidado! He jubilado las clases para gestionar áreas y grupos de escaneo. Que cada driver maneje sus comunicaciones como mejor convenga. MBPLC lanza una hebra independiente con ese fin. Consecuencia de ello, el método deploy() del Ensemble ha quedado reducido al análisis de expresiones y la conexión con los controladores.
  • El nuevo driver OPCPLC permite la comunicación con un servidor OPC UA. El encapsulado hace que tenga poco que contar de su funcionamiento. Tan solo en lo tocante al direccionamiento de las variables, que ataca a los nodos del protocolo, y describe la ruta a partir del nodo de objetos usando el carácter \ como separador. Como los nodos también se diferencian por tipo, es preciso anteponerles su identificador correspondiente, separado por :. Así, por ejemplo, una dirección válida puede ser 2:Data\2:Static\2:Scalar\2:DateTimeValue. Para ayudar a determinar el direccionamiento he incluido el método print_tree(), que imprime la estructura completa de un servidor OPC una vez que el driver se ha conectado.

    Driver OPC UA
    Driver OPC UA
  • Toda la gestión de alarmas que se ha definido clamaba con ansia dos elementos nuevos en la interfaz. El primero es un visualizador de alarmas. Se hace de una forma tan sencilla como insertar una tabla con el atributo data-alarmgroup=”nombre_del_grupo_de_alarmas”. El código en Javascript se encarga solo de asociarle los eventos correspondientes para ir mostrando en todo momento las alarmas presentes, con aspecto definido en la hoja de estilos. El segundo elemento es un histórico de alarmas. Su definición es idéntica, salvo que añade el atributo data-historical. A continuación se muestra cómo se insertan estos dos nuevos objetos en la interfaz:

Para mostrar cómo se levanta un proyecto con los nuevos cambios, a continuación muestro el código necesario para definir un Ensemble con un PLC Modbus y un servidor OPC, importar sus variables y alarmas, registrar estas últimas en un fichero, en el depurador de Python y en una base de datos MySQL, y por último lanzar la interfaz web:

Esta entrada continúa en Proyecto UWS (7).
Recursos asociados:
SCADA UWS (versión 1.3) y código de ejemplo.

Proyecto UWS (5)

publicado en: Programación, SCADA | 0

Esta entrada es continuación de Proyecto UWS (4).
Hora de cambiarlo todo. Era necesario para que UWS avanzase en las líneas que expuse hace poco. Las principales novedades son:

  • Se vuelca todo el peso en la clase Tag, y se eliminan mapeos de memoria de controladores. Por supuesto, debe seguir habiendo referencias a direcciones, pero a partir de ahora las memorias se gestionan como un diccionario de variables. ¿Cómo se determinan las tramas de un PLC Modbus, por buscar una pega? Sencillo, las memorias pueden ser de dos tipos: ordenadas y no ordenadas. En las primeras, el índice es numérico, y las tramas de lectura se pueden resolver con facilidad a partir de sus valores. Pero el objeto PLC no copia toda la memoria del controlador, sólo contiene las variables que se necesitan.
    Una de las consecuencias de esta decisión es que se eliminan los direccionamientos absolutos. Desde el interfaz ya no se puede acceder a áreas aleatorias de la memoria, lo cual podía ser un foco de inseguridades. A partir de ahora, las referencias sólo podrán hacerse a las variables dadas de alta en el motor.
  • Se eliminan los escaneos para actualizar valores. En vez de ello, cada variable mantiene una lista de suscriptores que son avisados ante sus cambios de valor. Un suscriptor es cualquier objeto derivado de la clase Subscriptor, y debe poseer un método update. Esto mejora la eficiencia y hace que las actualizaciones sean todo lo ágiles que se puede. Cada cambio de valor se notifica inmediatamente a todos los elementos que de ella dependen: alarmas, históricos, interfaz, cálculos… Por otro lado, muchos protocolos de comunicación con controladores funcionan de este modo, así que era una transición inevitable.
  • La interfaz es un suscriptor más de una lista de variables. Esto demanda poderosamente introducir los websockets de HTML5. La interfaz ahora usa dos puertos: uno para solicitar ficheros y otro nuevo para establecer una conexión bidireccional para el envío y recepción de información. Como antes, se gana en eficicencia, velocidad y se reduce el ancho de banda consumido por las comunicaciones.

Aunque hay algún cambio más, me voy a centrar en describir estos puntos. Respecto al primero, mi intención es que todo lo que contenga un valor derive de la clase Tag. Se me ocurre en un primer momento esta jerarquía de clases:

Jerarquía de variables
Jerarquía de variables

En bordes punteados se marcan las clases que todavía no están definidas. La intención es que existan al menos estos tipos de variables:

  • IOTag. Se lee de un controlador, y por tanto contiene una referencia a dicho equipo, el área de memoria donde se ubica y su dirección. La principal particularidad es que redefine el método set para enviar por comunicaciones los cambios de valor.
  • MemoryTag. Se aloja en memoria. Lo adecuado sería establecer un mecanismo para que algunas de estas variables preserven su valor en cada arranque de la aplicación.
  • Expression. Como he comentado, se trata como variable cualquier elemento que arroje un valor, dotado de una lista de suscriptores para advertirlos de su modificación. De acuerdo a este criterio, una expresión debe ser considerada un tipo de variable. La clase Expression permite definir un cálculo dependiente de otras variables. Tiene herencia múltiple por tanto, de Tag y Subscriptor. Por el momento sólo contempla expresiones básicas: suma, resta, multiplicación, división, negación, igualdad, inferioridad, superioridad y anidamientos mediante paréntesis.
  • Alarm. Definida como una expresión, una alarma debería llevar a cabo ciertas acciones de notificación cuando se activa: almacenarse en un fichero, mostrarse en los HMI, enviar un correo…
  • TrendTag. Nuevamente, es una expresión (aunque su contenido puede ser una variable pura) cuyo valor se almacena periódicamente en base de datos o archivos.

En lo que toca al desarrollo web, se mantiene la misma estructura del fichero html y la página tiene las idénticas funcionalidades. Sin embargo, al usar websockets, se elimina el método de refresco periódico de los valores, y se minimiza el tiempo de actualización de la información. Por comentar brevemente el código en Javascript: cuando el documento está listo, tras asociar los eventos a la página, se instancia un websocket y se le asocian eventos.

El primero, onopen, registra (se suscribe) a las variables contenidas en la página. Como decíamos en entradas previas, el nombre de la variable se indica en el atributo data-dir. La suscripción se lleva a cabo enviando un JSON que especifica la acción (subscribe) y el listado de variables:

UWS responderá con un mensaje (que se gestiona con el evento onmessage) que contiene los valores de dichas variables:

Por último, los envíos de consignas u operaciones se hacen mediante el método send de websocket, también codificados en JSON:

En próximas entradas avanzaremos en las alarmas e históricos, e incluiré algún driver más de comunicaciones.
Esta entrada continúa en Proyecto UWS (6).
Recursos asociados:
SCADA UWS (versión 1.2) y código de ejemplo.

Proyecto UWS (4)

publicado en: Programación, SCADA | 0

En la entrada previa sobre UWS terminaba proponiendo unas líneas de evolución del proyecto. Una de ellas era permitir la utilización de nombres simbólicos, en lugar de direccionamiento absoluto. Como se trata de algo casi directo de hacer (basta añadir un diccionario al ensemble, un método de importación y pequeñas modificaciones del servidor web), me he puesto manos a la obra. El uso de simbólicos permite al programador abstraerse de la estructura interna de los controladores, hace el desarrollo más inteligible, y con ello reduce las posibilidades de equivocación. En la nueva versión es posible agregar un símbolo de la siguiente manera, en Python:

Nota: Aprovecho para decir que he renombrado las áreas del controlador Modbus, y he dejado sus nombres en singular (coil, input, holding y register).
Aunque es posible incorporar los nombres simbólicos picando código, es obvio que no resulta eficiente. Lo ideal es trabajar con ellos en una hoja de cálculo, e importarlos al ensemble. Por ejemplo, podemos crear el siguiente archivo csv, en el que la primera línea contiene los encabezados y las siguientes, en orden, el nombre de la variable, controlador, área de memoria y posición. El separador usado es el punto y coma, para evitar ciertos problemas clásicos con el punto decimal en Windows y Excel.

Datos en csv
Datos en csv

Incorporar estos datos es tan sencillo como hacer una llamada al método importtags del ensemble, pasándole la ruta:

Hecho esto, la lectura y escritura de una variable se puede efectuar a través de los métodos get y set de Tag:

En este punto, existe una posibilidad de mejora del proyecto que reclama nuestra atención. En la versión previa de UWS existía el método fast_deploy, que permitía crear automáticamente los grupos de escaneo de la memoria de los controladores. Esta función permitía desentenderse de la tarea de definir una por una las zonas con información relevante. No obstante, si resolvía un problema, generaba otro, y es que, al desconocer qué datos son de interés para el usuario, se opta por mapear toda la memoria. Sin embargo ahora, una vez hemos importado todas las variables que va a usar la aplicación, se hace evidente que conocemos dónde se ubica la información útil. Sólo hay que revisar las direcciones de la tabla que hemos importado, determinar qué zonas de memoria se demandan, y dentro de cada una de ellas, cuál es el primer y último valor. Con esto, es inmediato definir las áreas y grupos de escaneo. Así, el nuevo método mejorado para lanzar las comunicaciones es smart_deploy:

Hasta aquí lo que toca al motor de comunicación con los controladores. Respecto al servidor web, las peticiones GET /?r y /?w se han redefinido para atender a nombres simbólicos. He mantenido el direccionamiento absoluto, pero con llamadas /?dr y /?dw. Es decir, podemos tener cinco respuestas a una URL:

  • http://[host]:[puerto]/?r:[símbolo] devuelve el valor de la variable con dicho símbolo.
  • http://[host]:[puerto]/?w:[símbolo][valor] escribe el valor de la variable con dicho símbolo.
  • http://[host]:[puerto]/?dr:[plc][memoria][elemento] devuelve el valor de dicho elemento en la memoria de su controlador.
  • http://[host]:[puerto]/?dw:[plc][memoria][elemento] escribe el valor de dicho elemento en la memoria de su controlador.
  • http://[host]:[puerto]/[fichero] entrega un fichero.

Al renombrar la sintaxis de las peticiones para asociar ?r y ?w a los nombres simbólicos, no es necesario actualizar j.js, que ahora trabaja directamente con símbolos en lugar de direccionamientos absolutos.
Otra tarea pendiente era mejorar la parte gráfica. En realidad esto no es una evolución de UWS propiamente hablando, ya que queda en el lado del desarrollo web. Sin embargo, he querido introducir algunas modificaciones para mostrar el potencial de un SCADA de este tipo. No me voy a entretener en describir cómo funciona el nuevo j.js (que será bastante evidente para quien se maneje con Javascript), pero sí debo comentar qué hace con los atributos que localiza en la página:

  • data-dir: Es el atributo que define un elemento como dinámico, y contiene el nombre simbólico que se va a representar. Por defecto, j.js decide qué hacer según la etiqueta que lo contenga:
    • Los campos de texto se interpretan como un espacio para introducir un valor numérico. Se usarán por tanto en la práctica para consignas.
    • Los checkbox permiten modificar valores digitales (un marcha/paro, automático/manual, etc.).
    • Si el atributo pertenece a una etiqueta p o spam, se entiende que su contenido es un valor que se debe visualizar (puede ser numérico o booleano).
    • Por último, si se aplica a una capa o div, el refresco se hace sobre su visibilidad.
  • data-css: Si está presente, las modificaciones no se hacen conforme a lo anterior, sino sobre el elemento de estilo contenido en este atributo. Podemos por tanto, a través de este atributo, alterar el tamaño de un elemento, su visibilidad de nuevo, color, etc.
  • data-transform: Permite realizar transformaciones sobre el valor. Se pueden anidar varias, separadas por punto y coma:
    • invert invierte el valor de una señal digital.
    • scale:[x0],[x1],[y0],[y1] lleva a cabo un escalado, conforme a la recta (x0,y0)-(x1,y1).

    Mediante estas nuevas funcionalidades, se puede hacer un SCADA perfectamente funcional como el que se muestra en la siguiente imagen:

    SCADA gráfico UWS
    SCADA gráfico UWS

    Para quien desee hacer pruebas, dejo enlace al código, como otras veces.
    Esta entrada continúa en Proyecto UWS (5).
    Recursos asociados:
    SCADA UWS (versión 1.1) y código de ejemplo.