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 (2)

publicado en: Modbus, Programación, SCADA | 0

He terminado la fastidiosa tarea de documentar el Proyecto UWS. Y de camino me ha servido para corregir un par de errores. Como comentaba, el UWS es un esbozo de SCADA muy en pañales. Corre en Python (yo uso concretamente la versión 3.5) y la interfaz es HTML estándar. De momento sólo comunica por Modbus TCP, pero todo se andará.
Los módulos y clases de los que se compone son los siguientes:

  • PLCModule:
    • PLC: Representación de un controlador genérico.
    • Memory: Representación de una de las zonas de memoria de un PLC. En el caso de un equipo Modbus, van a ser bobinas, entradas digitales, registros de retención y registros de entradas. Pero otros controladores exigirán otra distribución.
    • Area: Segmento de memoria. Su uso es interno, normalmente no va a requerir que el programador la ataque.
    • Poll: Conjunto de áreas (información) que se piden con una misma frecuencia de muestreo. Por ejemplo, valores que cambian muy rápido o son críticos requieren refresco con intervalo reducido.
  • MBPLCModule:
    • MBPLC: Controlador con comunicación Modbus. Deriva de la clase PLC.
  • EnsembleModule:
    • Ensemble: Vendría a ser una red de controladores. Pero en realidad no hay exigencia real de que se encuentren en la misma red. En el fondo es un conjunto de controladores y de grupos de muestreo.
  • UWServerModule:
    • UWServer: El SCADA propiamente dicho. En principio sólo integra los componentes.
    • WebServer: El servidor web.

Me va a perdonar el lector por el revuelto de idiomas: clases, atributos, métodos, etc. están nombrados usando el inglés, pero tengo poca soltura y mucha pereza para usar la misma lengua para los docstrings, de modo que los he redactado en español.
El siguiente gráfico muestra los componentes de un proyecto:

Componentes de UWServer
Componentes de UWServer

En la otra entrada puse un ejemplo de cómo se llevaría a cabo un proyecto, sin apenas dar explicaciones. Me gustaría repasarlo de nuevo, explicando más en detalle los pasos. Básicamente el desarrollo se realiza en dos espacios. En Python se define la estructura del proyecto (controladores, conexiones, muestreos…) y en HTML se compone la interfaz del SCADA. Comencemos por la primera, que nos va a llevar el resto de la entrada. Por lo usual, en primer lugar debemos instanciar un Ensemble, es decir, una red de controladores.

En nuestro ejemplo sólo vamos a requerir que el ensemble tenga un controlador, al que vamos a llamar plc1. Como comunicamos con él en Modbus TCP, hay que indicarle su dirección IP. También cuánto ocupan sus distintas áreas de memoria (coils, inputs, holdings y registers). La clase nos permite aportar más parámetros, como puerto de conexión (típicamente 502), método, unidad (número de esclavo), reintentos caso de fallo de comunicación. También atributos para saber si se ha establecido la comunicación, métodos para leer o escribir datos, e incluso podríamos definir áreas adicionales de memoria con un sentido algo peregrino. Pero para nuestro ejemplo no nos vamos a complicar: la longitud de todas sus áreas de memoria va a ser de 20 elementos y el resto de parámetros quedan por defecto. También aprovechamos para agregarlo al ensemble en el mismo paso:

Ahora vendría la tarea fastidiosa de definir diferentes muestreos, según periodo de refresco, y las áreas que se leen. Un ejemplo de cómo se crearía uno de estos grupos es el siguiente:

Hago notar que propiamente hablando, el tiempo de muestreo es “entre muestreos”. Es decir, el código espera a que se hayan completado las comunicaciones (o hayan devuelto error) antes de empezar a contar para las siguientes peticiones. Estamos hablando comunicación desde un SCADA, no en un bus de campo, con lo que podemos ser algo laxos en este sentido. Se evita así además saturar las comunicaciones por solapamientos. Para continuar con el ejemplo, ahora habría que iniciar las comunicaciones con cada uno de los grupos de muestreos definidos.

Aunque de esta forma se controla de forma fina cómo se van a llevar a cabo las comunicaciones, he querido incorporar un método más ágil. fast_deploy crea un único grupo de muestreo con el tiempo por defecto, añade todas las áreas de memoria al completo de todos los PLC que posea el ensemble, e inicia sus comunicaciones de una tacada:

En este momento tenemos el motor de comunicación en marcha, y podemos leer y escribir de la forma siguiente:

En estas circunstancias, tan solo nos falta lanzar el servidor web. Lo normal sería usar el puerto 80, pero se puede usar cualquier otro disponible, como el 8080. Por supuesto, tenemos libertad para emplear más de un puerto con diferentes servidores, y el mismo ensemble. Puede ser interesante conjugado con otro de los parámetros, la ruta relativa de los ficheros HTML, que por defecto es www.

Queda discutir la segunda parte, donde entraremos en el desarrollo de la interfaz web. Eso será en una próxima entrega.
Esta entrada continúa en Proyecto UWS (3).
Recursos asociados:
SCADA UWS (versión 1.0) y código de ejemplo.

Novedades 20151223

publicado en: BACnet, KNX, LonWorks, PID, Schneider, Seguridad | 0

Poco a poco se va difuminando la frontera entre autómatas y controladores domóticos. Esta primavera Schneider presentaba los Modicon M171/172, capaces de hablar BACnet y LonWorks. Hace unos días Siemens presentó el módulo CMK2000, permite a los Logo! 8 comunicarse en KNX (las versiones anteriores usan el CM EIB/KNX).
En 2009 se definió el estándar ANSI/ISA-18.2 para la gestión de alarmas en los sistemas de control. Con miras en la seguridad, que depende de una respuesta urgente ante situaciones de riesgo, y por tanto de la eficacia del sistema de alarmas, se define un ciclo de vida para éste. Comprende su optimización (filosofía o definición del proceso, identificación de alarmas, racionalización o clasificación, diseño en detalle e implementación), soporte (operación y mantenimiento), evaluación y mejora continua (gestión de cambios y auditoría).
He sabido por el blog de Martín Castillo de la aplicación LDmicro, que permite programar PIC en ladder. No he tenido ocasión de probarla, pero parece sencilla, viene acompañada de simulador y, aunque el conjunto de operaciones es bastante limitado, permite algunas funciones de cierto nivel, como manejo de tablas o comunicación vía puerto serie.
Schneider ha publicado guía sobre los protocolos abiertos más usados en inmótica, que incluye tanto exclusivos de este campo (Bacnet, LonWorks, KNX, Dali…) como más generales (Modbus, OPC, Web Services…). Están clasificados en alámbricos e inalámbricos, y aunque su descripción es muy breve, da algunas pistas sobre su idoneidad en función de la aplicación.
Hace una década me cansé de copiar y pegar código, y desarrollé una aplicación que genera el programa (la parte sistemática) a partir de un listado de equipos. Muchos fabricantes han seguido ese camino, por desgracia creando herramientas cerradas a sus productos. Nos lo cuenta Daniel B. Cardinal, que además describe las metodologías de diseño más comunes para el diseño automático de código. De ahí pasa a describir estrategias para aplicar a toda la pirámide de control, a la que idealmente se debería extender esta forma de desarrollo.
Como Vance VanDoren comenta, los lazos PID no son una solución universal y perfecta. Hace poco ha publicado la tercera parte de un artículo que comenzó a escribir ¡en 2012! acerca de sus problemas, y cómo soslayarlos (1, 2 y 3). Acumulación de la acción integral en caso de saturación o no actuación, respuesta abrupta en el arranque, regulación “del ruido”, sobreactuación cuando el proceso conlleva retraso… Yo añadiría que el ajuste se realiza para unas determinadas condiciones de proceso, que con frecuencia no se mantienen invariables; en este caso, una solución es establecer tramos con diferente parametrización.
En las últimas semanas se han publicado varias vulnerabilidades que afectan a equipos extendidos en el ámbito de la automatización. Comento las más importantes:
– Desbordamiento de buffer en productos M340 de Schneider que permite ejecutar código arbitrario o detener el PLC.
– Inyección de código con elevación de privilegios en SCADA PowerStudio (Circutor).
– Múltiples vulnerabilidades en routers eWON (inyección de código que permitiría actualiar firmware, cambiar de configuración, envío de contraseñas inseguro, etc.)
– Evasión de autenticación en dispositivos Siemens (CP343-1, CP443-1…)
– Varias vulnerabilidades en MicroLogix (Allen-Bradley): desbordamiento de buffer que permite ejecución de código, subida de archivos, inyección SQL…
Por último, una postal y una canción para felicitaros las fiestas.

Novedades 20151122

publicado en: HART, Profibus, Seguridad | 0

Llegar por comunicaciones hasta los sensores era prohibitivo hace unas décadas por los costes de la electrónica. Hoy, la situación se invierte. A pesar de que es difícil tumbar el estándar 4-20mA, no tiene mucho sentido que un instrumento digital (la mayoría de los transmisores actuales) genere una señal analógica para volver a muestrearla y codificarla en el PLC. Carl Henning se extiende en más cuestiones al comparar el lazo de corriente con Profibus (error sistemático, ruido, calibración, espacio, cableado…) y concluye que debemos ir despidiéndonos del sistema tradicional.
Andrew Ginter describe una de las soluciones que aporta su empresa para desplegar comunicaciones seguras en las plantas. Aunque hable de productos nuevos, la idea es antigua y sencilla, además de barata: establecer comunicaciones unidireccionales allí donde no es imprescindible el tráfico en los dos sentidos. En particular, considera la publicación de información desde planta hacia operación (OT), y de este departamento hacia sistemas (IT).
Hace una década se decía que ZigBee iba a protagonizar una revolución al hacer factibles las redes de sensores inalámbricas. Resolvía los problemas de consumo, alcance o ancho de banda de la WiFi (802.11) o Bluetooth. En 2015, los protocolos que en realidad están compitiendo por liderar el IIoT son WirelessHART e ISA100. Prometo extenderme, pero vayan por delante unas recomendaciones a la hora de elegir.

KNX (3)

publicado en: KNX | 0

En la entrada KNX (2) comencé a explicar el protocolo KNX a partir de una trama de ejemplo, describiendo el significado de los seis primeros bytes. Quedó pendiente el resto, que contiene la información relativa a la capa de transporte (T_PDU). Ésta se subdivide a su vez en dos partes: los seis primeros bits y el resto, que encapsula el contenido de aplicación (A_PDU).

Telegrama KNX
Telegrama KNX

El primer bit del T_PDU nos indica si el paquete es de datos (0) o de control (1). En el primer caso, el telegrama transmite una petición relacionada directamente con el funcionamiento de los dispositivos (escritura de datos, lectura, respuesta, reinicio de dispositivo, etc.). Los mensajes de control, en cambio, regulan el transporte en las conexiones punto a punto. Profundizaré en estos dos tipos de tramas más adelante.
El segundo bit especifica si el paquete está numerado (1) o no (0). KNX permite fraccionar los mensajes muy largos hasta en 16 fragmentos. El orden de la secuencia se indica mediante los cuatro bits siguientes. En el caso de los paquetes no numerados, estos bits no cumplen función y quedan a cero. Examinando los dos primeros bits del T_PDU, podemos decir que existen por tanto cuatro tipos de paquetes:

  • Paquetes de datos no numerados (UDP, Unnumbered Data Packet): 00
  • Paquetes de datos numerados (NDP, Numbered Data Packet): 01
  • Paquetes de control no numerados (UCD, Unnumbered Control Data): 10
  • Paquetes de control numerados (NCD, Unnumbered Control Data): 11

El resto del T_PDU está formado por la información de aplicación. En el caso de los paquetes de control, su función viene indicada por los dos primeros bits del A_PDU, de la siguiente forma:

  • Si el paquete no está numerado (esto es, es un UCD), indican el inicio (00) o fin (01) de una conexión punto a punto.
  • Si el paquete está numerado (NCD), se usan para confirmar positivamente (10) o negativamente (11) la recepción del último telegrama.

Este tipo de mensajes se usan para controlar una conexión orientada a conexión, punto a punto, y se emplean esencialmente para la comunicación entre el software de programación (ETS) y los dispositivos, con objeto de descargar parámetros, programa, direcciones, etc. Este enlace, aunque tiene gran consumo de ancho de banda, garantiza la transmisión íntegra de la información. El servidor es el que inicia la conexión. Por cada paquete enviado se debe recibir una confirmación positiva, y en todo momento cualquiera de los dos intervinientes puede cerrar la conexión.
Cuando el paquete enviado es de datos, son los cuatro primeros bits del A_PDU los que indican su función. Se denominan APCI (Application -Layer- Protocol Control Information). Las más usadas son las siguientes:

  • 0000 – Lectura de un objetos de un grupo (GroupValueRead). Se piden los datos a los dispositivos definidos por la dirección de grupo. Los seis bits restantes del A_PDU no cumplen función alguna.
  • 0001 – Respuesta al mensaje previo (GroupValueResponse). Si los datos no caben en los seis bits restantes del A_PDU, se amplía éste con tantos bytes como sea necesario.
  • 0010 – Escritura de los objetos de un grupo (GroupValueWrite). Los valores siguientes se escriben a todos los dispositivos de dicho grupo. Como en el caso previo, si los datos superan los seis bits, se amplía el A_PDU con los bytes necesarios.
  • 0011 – Escritura de una dirección individual (IndividualAddressWrite). Similar al anterior, pero se envían los datos a un dispositivo identificado por una dirección individual, en vez de de grupo.
  • 0100 – Lectura de una dirección individual (IndividualAddressRead).
  • 0101 – Respuesta a una lectura de una dirección individual (IndividualAddressResponse).

Los siguientes APCI, en orden, son:

  • AdcRead, AdcResponse (lectura de analógicas con filtrado)
  • MemoryRead, MemoryResponse, MemoryWrite (manejo de memoria)
  • UserMessage (intercambio de datos)
  • MaskVersionRead, MaskVersionResponse (lectura de perfil del dispositivo)
  • Restart (reinicio)
  • Escape (funciones ACPI extendidas, gracias a los seis bits restantes)