scieee AI-readable full text Open interactive document viewer

Interconexión de gemelo digital de célula de fabricación flexible con PLC

Torres Muñoz, Alejandro

Abstract

Los Gemelos Digitales se presentan en la actualidad no sólo como un paso más en la simulación de sistemas físicos de manera más cercana a la realidad; sino también como un elemento clave en la cuarta revolución industrial. Este nuevo concepto permitirá la monitorización de todos las herramientas y secuencias empleadas en cualquier proceso de producción. Así, se presenta como un elemento clave para tal propósito la implementación de un sistema de comunicaciones que permita el intercambio de datos en tiempo real entre el sistema físico y su Gemelo Digital. Por tanto, el objetivo de de este proyecto ha sido el de proponer una solución capaz de garantizar no sólo el intercambio de datos entre ambos sistemas; sino también el tratado correcto de dicha información así como la extracción de datos para el entrenamiento de la simulación digital. Para ello, se ha trabajado junto con el Gemelo Digital desarrollado por el proyecto de DENIM, basado en una planta industrial conformada por varios PLC’s de Schneider Electrics. Por tanto, para conseguir la comunicación entre ambos elementos, se ha desarrollado una librería de comunicaciones basada en el protocolo de comunicaciones MODBUS TCP/IP, haciendo uso de sockets.

Full text

Proyecto Fin de Carrera Ingeniería de Telecomunicación Formato de Publicación de la Escuela Técnica Superior de Ingeniería Autor: F. Javier Payán Somet Tutor: Juan José Murillo Fuentes Dep. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2013 Trabajo Fin de Grado Ingeniería Electrónica, Robótica y Mecatrónica Interconexión de gemelo digital de célula de fabricación flexible con PLC Autor: Alejandro Torres Muñoz Tutor: Juan Manuel Escaño González Cotutor: Javier Gómez Jiménez Dpto. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2022 Trabajo Fin de Grado Ingeniería Electrónica, Robótica y Mecatrónica Interconexión de gemelo digital de célula de fabricación flexible con PLC Autor: Alejandro Torres Muñoz Tutor: Juan Manuel Escaño González Profesor Contratado Doctor Cotutor: Javier Gómez Jiménez Investigador Dpto. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2022 Trabajo Fin de Grado: Interconexión de gemelo digital de célula de fabricación flexible con PLC Autor: Alejandro Torres Muñoz Tutor: Juan Manuel Escaño González Cotutor: Javier Gómez Jiménez El tribunal nombrado para juzgar el trabajo arriba indicado, compuesto por los siguientes profesores: Presidente: Secretario: acuerdan otorgarle la calificación de: El Secretario del Tribunal Fecha: Agradecimientos Atodos mis compañeros de DENIM. En especial a Javi, por haber tenido la enorme de paciencia de responder todas mis preguntas; y a Miguel, por haberme echado siempre una mano y haber sido un compañero de proyecto ejemplar. A mi familia, por haberme apoyado siempre y haber tenido más confianza en mí que yo mismo durante todos estos años. Y a Juanma, por haber sido un tutor ejemplar y haberme ofrecido siempre su ayuda y los mejores medios para poder realizar este proyecto. Con usted me he sentido verdaderamente útil. Alejandro Torres Muñoz Escuela Técnica Superior de Ingeniería Sevilla, 2022 I Resumen Los Gemelos Digitales se presentan en la actualidad no sólo como un paso más en la simulación de sistemas físicos de manera más cercana a la realidad; sino también como un elemento clave en la cuarta revolución industrial. Este nuevo concepto permitirá la monitorización de todos las herramientas y secuencias empleadas en cualquier proceso de producción. Así, se presenta como un elemento clave para tal propósito la implementación de un sistema de comunicaciones que permita el intercambio de datos en tiempo real entre el sistema físico y su Gemelo Digital. Por tanto, el objetivo de de este proyecto ha sido el de proponer una solución capaz de garantizar no sólo el intercambio de datos entre ambos sistemas; sino también el tratado correcto de dicha información así como la extracción de datos para el entrenamiento de la simulación digital. Para ello, se ha trabajado junto con el Gemelo Digital desarrollado por el proyecto de DENIM, basado en una planta industrial conformada por varios PLC’s de Schneider Electrics. Por tanto, para conseguir la comunicación entre ambos elementos, se ha desarrollado una librería de comunicaciones basada en el protocolo de comunicaciones MODBUS TCP/IP, haciendo uso de sockets. 43 Solución al estado de movimiento del pistón posterior 79 44 Valor devuelto por el Sist.Virtual 79 Índice de Tablas 1 Tipos de elementos de información 16 2 Caracteres disponibles de datos para la versión Modbus ASCII 17 3 Clases disponibles para IPV4 19 4 Códigos de error 23 5 Regiones de control 30 6 Sensores Stacker para bandejas 41 7 Sensores Stacker para pistones 42 8 Estados algoritmo principal Stacker 44 9 Estados algoritmo para almacenar bandejas 45 10 Estados algoritmo para servir bandejas 48 11 Estimaciones de retrasos en las comunicaciones tras 30 ciclos de comunicación en operaciones de escritura 71 12 Estimaciones de retrasos en las comunicaciones tras 30 ciclos de comunicación en operaciones de lectura 71 13 Número de elementos de información para cada operación 71 14 Valores obtenidos de velocidades en las cintas, tras cuatro vueltas de la bandeja 73 XI Índice de Códigos 1 Parámetros Modbus TCP/IP 57 2 Parámetros internos de la comunicación 57 3 Parámetros programación Main PLC 58 4 Parámetros de programación del Stacker PLC 58 5 Parámetros de entrenamiento 62 6 Parámetros a elección para el entrenamiento 62 7 Parámetros de distancia 63 8 Estructura para las cintas 64 9 Estructura de tiempo para los sensores 64 10 Estructura de tiempo para los sensores 65 11 Estructura de velocidades para los archivos .JSON 65 12 Estructura de velocidades para los archivos .JSON 66 13 Parámetros de comunicación 80 14 Librería de comunicación 82 15 Algoritmo de comunicación en el entorno del DT 90 16 Algoritmo de traducción para el proceso de comunicación en el entorno del DT 102 17 Parámetros para el entrenamiento 107 18 Librería desarrollada para el entrenamiento 109 19 Algoritmo de entrenamiento en el entorno del DT 112 20 Librería para los carteles asociados a los actuadores en el entorno del DT 118 XIII List of Algorithms 1 Algoritmo de comunicación 60 2 Algoritmo de entrenamiento 68 3 Acciones de entrenamiento dentro de la rutina de comunicación 69 XV 1 Introducción 1 1 Introducción 1.1 Gemelos Digitales. Estado del arte El término de "Gemelo Digital", o también conocido como Digital Twin(DT a partir de ahora), se refiere a la representación virtual de cualquier sistema físico, incorporando, en tiempo real, datos sobre los parámetros de dicho sistema [1]. Dicho término fue expuesto por primera vez por el Dr.Michael Grieves en 2003 en la Universidad de Florida, siendo un concepto demasiado revolucionario para la tecnología disponible en aquellos años, ya que en el mismo se establecía que un producto virtual debía ser virtualmente indistinguible respecto al produc- to físico, y las tecnologías presentes en aquella época no permitían desarrollar en su plenitud este concepto. [ 2 ]. Sin embargo, la estructura de dicho concepto se ha mantenido constante a lo largo del tiempo, siendo ésta la siguiente: 1. Un sistema físico en el espacio real. 2. Un sistema virtual en un entorno digital. 3. Una conexión que permita la comunicación para el intercambio de datos entre los dos componentes anteriores. No obstante, dicha terminología fue puesta en práctica por primera vez por la NASA, con el objetivo de mejorar las simulaciones de los modelos físicos de sus naves espaciales. Es por ello por lo que dicha expresión no fue realmente acuñada hasta 2012. En este caso el concepto de DT fue redefinido como: "Una simulación multifísica, multiescala, probabilística y de extrema fidelidad ejecutada en tiempo real; basándose en un histórico de datos, así como información recogida por sensores y el modelo físico del sistema" [3]. Además, dicha definición no debe confundirse con una simple simulación de un proceso. Lo que diferencia a un DT son las múltiples ventajas que posee gracias a sus múltiples aplicaciones. Entre ellas, se deben destacar las siguientes: 1. Monitorización del proceso : Detección de problemas y averías. 2. Aplicación de modelos de control alternativo, evitando poner en riesgo el sistema físico. 3. Modelo de entrenamiento para control de una IA sobre plantas automatizada, con el objetivo de predicción de futuras averías. 4. Mejora de los parámetros de diseño a través del "feedback" de los datos intercambiados entre producto físico y producto virtual [4]. 5. Aumento de la efectividad de las líneas de producción en un entorno industrial [5]. Por tanto, el flujo de datos entre ambos sistemas se puede realizar de manera bidireccional: Desde el sistema físico hacia el DT, recogiendo datos de los distintos sensores y actuadores del sistema, con el objetivo de replicar la realidad en la simulación. Dichos datos pueden ser almacenadas para realizar un procesado posterior, con el objetivo de analizar el comportamiento del sistema y tratar de mejorar sus prestaciones. Desde el DT hacia el sistema físico, tratando de aplicar los valores recogidos dentro de la simulación a la realidad. Dicha configuración puede ser útil para estimar de manera más precisa el comportamiento de un nuevo sistema de control que se desee implementar sobre la realidad y que haya sido desarrollado sobre la simulación. Más allá de esto, las posibilidades que ofrece esta solución sobre su aplicación en el ámbito del Big Data y la Inteligencia Artificial hacen de ella unas de las tecnologías más emergentes en la Industria 4.0. Es por ello por lo que se debe considerar como una de las herramientas a ser más utilizadas por la industria en los próximos años. 2Capítulo 0. 1.2 Marco del proyecto : Digital Intelligence for collaborative for Energy management in Manufacturing (DENIM) Este proyecto ha sido desarrollado dentro del marco del proyecto DENIM[ 6 ] desarrollado por el departamento de Sistemas y Automática de la Universidad de Sevilla. El objetivo de dicho proyecto es el desarrollo de una plataforma de inteligencia digital interoperable que permita un enfoque colaborativo de la energía industrial. Por tanto, el objetivo establecido es ser capaz de proveer un toolchain 1 que permita hacer uso de servicios digitales tales como : Conectividad segura orientada al IoT, análisis de datos, empleo de un DT, modelado de consumos de energía y automatización. Con ello, el producto final deseado será una solución que permita evaluar el impacto energético de una industria, así como el control de dicha energía y la optimización de la producción, procesos y uso de máquinas. Por ello, el proyecto hace uso de tecnologías tales como el concepto de Digital Twin, o el Machine Learning, con el objetivo de no sólo ofrecer servicios avanzados que permitan un mejor procesado dato y prácticas comerciales; sino también mejorar el entendimiento, capacidad y eficiencia de los procesos de producción actuales. Para conseguir tal objetivo, se deben aunar la información proveniente de distintos sectores presentes en un proceso de fabricación, tales como plantas, máquinas, planificación, calidad o mantenimiento. Para ello, el Gemelo Digital se presenta como la alternativa para poder unir y procesar toda esa información, de manera centralizada. De manera más concreta, en el ámbito de este trabajo, el objetivo dentro del grupo de DENIM ha sido el desarrollo de programas para la automatización de la planta física ubicada en los laboratorios del departamento de Sistemas y Automática de la Universidad de Sevilla, así como crear la comunicación e interacción entre los PLC’s disponibles en dicha planta y el DT de la misma desarrollado por el grupo de DENIM. Sin embargo, de manera previa a comentar las motivaciones y objetivos del proyecto, se han de señalar ciertos inconvenientes comunes que surgen en el desarrollo de gemelos digitales [3]: 1. Fallos en la comunicación entre la realidad y la simulación. 2. Errores en la parametrización de la simulación respecto a las propiedades físicas de los elementos que componen la planta física. 3. Falta de un protocolo de conexión y comunicación estandarizado. 4. La no existencia de un framework 2 universal para establecer un DT como proceso por defecto aún disponible. 5. El gran coste computacional que suele implicar la simulación(especialmente la renderización), por lo que surge la necesidad de mejorar la computación así como la capacidad de ejecutar múltiples procesos en paralelo. 6. El riesgo de seguridad al que se encuentran expuestos los DT’s, tanto por el aspecto de la simulación conectada a la nube como por la posibilidad de ataques informáticos sobre las plantas industriales. Por tanto, una de las motivaciones de este trabajo ha sido colaborar con el proyecto DENIM con el objetivo de establecer un lazo de unión entre la realidad y la simulación. Con ello se busca que en un futuro se puedan recoger datos de la realidad para algoritmos cuyo funcionamiento se basen en datos de la misma; así como que puedan implementarse sobre el sistema físico algoritmos desarrollados dentro del entorno de la simulación. Por otro lado, otra de las motivaciones para la realización del proyecto ha sido simplificar, en el futuro, la recogida de datos del sistema físico para poder ser aplicados sobre el sistema virtual. De esta manera, se podrá mejorar el procesado de físicas en la simulación. 1Toolchain : Conjunto de herramientas informáticas empleadas para el desarrollo de un producto. 2Framework : Entorno de trabajo con un conjunto de prácticas y criterios estandarizados. 1 Introducción 3 1.3 Objetivos del proyecto Los objetivos planteados para la realización de este proyecto han sido los siguientes, citados de manera en los que se han desarrollado de manera cronológica: 1. Programación y coordinación de tareas entre distintos PLC’s en un entorno industrial. 2. Comunicación y coordinación de tareas entre distintos PLC’s en un entorno industrial. 3. Creación de una librería de comunicación que pueda ser usada de manera genérica PLC-computador a través de sockets, basado en el protocolo de comunicación MODBUS TCP/IP. 4. Traducción de datos entre componentes del DT y elementos de memoria de los PLC’s. 5. Lectura de elementos de temporización de los PLC’s para realizar correcciones sobre parámetros del DT, tales como la velocidad. 6. Mejora de la experiencia del usuario dentro de la simulación. Respecto a la librería de comunicación, los códigos de archivo han sido subidos al siguiente repositorio con el objetivo de poder ser usados por cualquier persona para establecer comunicaciones con cualquier PLC que trabaje sobre el modo de direccionamiento IPv4 y el protocolo de comunicación Modbus TCP/IP. Figura 1 Imagen del DT desarrollado por DENIM. 4Capítulo 0. 2 Herramientas usadas Para el desarrollo de este trabajo han sido empleadas, principalmente, las siguientes dos herramientas software: •EcoStruxure Control Expert, para la programación y configuración de los PLC’s. •Unity3D, como entorno de desarrollo del DT. 2.1 Unity3D Las herramientas software Unity3D es una solución multiplataforma creada para desarrollo de videojuegos. Dicha solución permite aplicar el motor de físicas que se aplicaría en un videojuego, pero de manera gratuita. Dicho motor de físicas resulta indispensable para que un DT pueda replicar de la manera más precisa la realidad, por lo que se plantea como una solución para el desarrollo del mismo. Aparte de lo anterior, dicho software permite la integración de objetos a través de modelos 3D importados, sobre los que se podrá simular tanto las físicas; así como se podrá programar su comportamiento a través de scripts. En concreto, para la programación de dichos objetos viene implementado a través del entorno de desarrollo MonoDevelop, entorno de trabajo libre basado en .NET Framework. En concreto, para Unity3D, son soportados los siguientes lenguajes : UnityScript, BOO y C#, siendo este último es escogido para la realización del proyecto. C# es un lenguaje de programación creada por Microsoft. Dicho lenguaje es orientado a objetos, basando su sintaxis básica en C/C++ y que emplea un sistema de objetos derivado en .NET. En concreto, respecto a este lenguaje, ha resultado verdaderamente útil el concepto de namespace 3 . Gracias a su uso resulta más sencillo la implementación de clases y tipos de estructura de datos que vayan a ser usados por otros scripts. Dicho software es el encargado de ejecutar todas las animaciones necesarias, así como, al inicio de su ejecución será quién decida el sentido de la comunicación. Por tanto: 1. En el caso de que el sentido de la comunicación sea desde el DT hacia los PLC’s, se ejecutará la automatización interna desarrollada por el equipo de DENIM. 2. En el caso de que el sentido de la comunicación sea desde los PLC’s hacia el DT, se ejecutará el algoritmo de automatización externa cargada sobre ambos PLC’s. Para comprender mejor la inicialización de los parámetros necesarios para la comunicación, así como el funcionamiento de dicho software, se deben resaltar las siguientes funciones de ejecución propias del entorno de Unity3D, ordenadas en orden cronológico de ejecución: 1. El método Awake es la que se ejecuta en primer lugar al arrancar la simulación. Es por ello que, al implementar diferentes clases, dicha función se suele llamar para inicializar objetos; así como para dar valor a parámetros de las clases a emplear. Es llamada una única vez en durante la ejecución. 2. El método Start suele ser empleado dentro de las clases para la lectura de parámetros que han sido validados en la función anterior; así como para dar valor a parámetros de objetos también creados en la llamada de la función anterior. Es llamada una única vez durante la ejecución. 3. El método Update suele implementar los distintos algoritmos necesarios para la correcta ejecución de la simulación. A diferencia de las otras dos anteriores, esta función sí se ejecuta de manera periódica. 4. El método FixedUpdate, el cuál es similar al anterior, pero posee una frecuencia fija de ejecución. Es por ello que este método se presenta como la mejor solución para la simulación de las físicas. 5. Y en último lugar, el método LateUpdate, en la cual se suelen implementar todos los cálculos necesarios para la correcta simulación de la física de los distintos objetos. Al igual que la anterior, esta función también se ejecuta de manera periódica. 3 Namespace : Espacio de trabajo. Se usa para designar un ámbito donde aglutinar distintos tipos de objetos relacionados entre sí, como clases, enumeraciones, estructuras... 3 Arquitecturas planteadas 11 3.2 Arquitectura de las comunicaciones Respecto a la arquitectura que se ha seguido para la comunicación de los distintos elementos de la planta, se planteó originalmente el siguiente esquema: Figura 6 Esquema de comunicaciones original. En dicho esquema se planteaba la posibilidad de que el Gemelo Digital estableciera un sólo canal de comunicación con el "Main PLC", mientras que dicho PLC se encargaría además de gestionar la comunicación con el "Stacker PLC". Sin embargo, dicho esquema fue descartado por las siguientes razones: • En primer lugar, supondría una mayor carga computacional sobre el "Main PLC", que debería gestionar, aparte de la escritura/lectura de datos con el Gemelo Digital; la escritura/lectura de datos con el "Stacker PLC"; así como la gestión de sus propios elementos de entrada y salida. • En segundo lugar, este esquema suponía un gran riesgo en caso de fallo, ya que en caso de hubiera algún fallo en el "Main PLC", el Gemelo Digital no podría acceder a los datos recogidos por el "Stacker PLC". Esto podría suponer un fallo crítico en el sistema planteado. • En tercer lugar, la cantidad de búfer que tendría que recorrer un dato que saliera desde el "Stacker PLC", pasando por un total de 4: 1. Búfer de salida del "Stacker PLC". 2. Búfer de entrada del "Main PLC". 3. Búfer de salida del "Main PLC". 4. Búfer de entrada del "Digital Twin". 12 Capítulo 0. Es por ello por lo que se decidió implantar el siguiente esquema: Figura 7 Esquema de comunicaciones implantado. En dicha solución se plantea un esquema en "V", donde el Gemelo Digital es el encargado de establecer dos canales de comunicación con sendos PLC’s. Las ventajas de usar este modelo, con el respecto al anterior, son las siguientes: • En primer lugar, se ponía fin al problema de fallo en las comunicaciones. Con ello, en caso de que alguno de los PLC’s entrara en bloqueo o sufriera algún fallo, el Gemelo Digital sería capaz de seguir comunicándose con el resto de elementos de la planta. • En segundo lugar, se liberaba al "Main PLC" de cierta carga computacional. Sin embargo, ésta es transferida al Gemelo Digital. • En tercer lugar, el establecimiento de las comunicaciones y gestión de datos desde el Gemelo Digital hacia el "Stacker PLC" se realiza de manera análoga a la forma en la que se realiza con el "Main PLC", por lo que resulta más sencilla su implementación. • Y en cuarto lugar, se agilizaba la velocidad a la que un dato viajaría desde el "Stacker PLC" hacia el "Digital Twin", reduciéndose a un total de 2 búfer que recorrer. 1. Búfer de salida del "Stacker PLC". 2. Búfer de entrada del "Digital Twin". 3 Arquitecturas planteadas 13 3.3 Arquitectura dentro del Gemelo Digital En la subsección anterior se ha planteado el esquema final de comunicaciones que ha sido implantando para la realización del proyecto. En esta subsección se plantea el esquema seguido dentro del Gemelo Digital para la gestión de datos, así como la toma de decisiones respecto a las acciones a realizar. Por ello, dentro del ámbito del Gemelo Digital, se ha implementado el siguiente esquema: Figura 8 Esquema interno dentro del Gemelo Digital. De la distribución anterior se han de comentar los siguientes aspectos: • En primer lugar, se puede observar que existe una capa superior formada por las rutinas de Comunicaciones, Entrenamiento y Automatización. Dichas rutinas serán ejecutadas dentro de las propias del entorno de Unity expuestas anteriormente : Awake, Start, Update y LateUpdate. Los objetivos principales de dichas rutinas serán: – Comunicaciones : Establecer los canales de comunicación con los PLC’s y servir como elemento intermedio para la correcta adaptación de los datos entre planta física y simulación digital. – Entrenamiento : Cuando sea activada, ejecutar la rutina necesaria para, a través de datos recogidos de los PLC’s, corregir parámetros del Gemelo Digital. – Automatización : Recoger los datos de entrenamiento para corregir los parámetros necesarios sobre los objetos correctos del Gemelo Digital. Se debe destacar que dicho archivo ya fue creado originalmente por el equipo de DENIM. Dentro de la realización de este trabajo, dicho archivo sólo ha sido modificado para el correcto funcionamiento con los nuevos módulos creados. • Tanto para la rutina de comunicaciones, como para la rutina de entrenamiento, se han creados archivos donde son almacenados los parámetros necesarios para su correcta ejecución : "Parámetros Comunicación" y "Parámetros Entrenamiento", respectivamente. Dichos archivos son ejecutados en modo lectura desde ambas rutinas. • De manera análoga a la anterior, han sido creados otros dos archivos que funcionan como librerías tanto para las comunicaciones como para el entrenamiento. En dichas librerías se almacenan las clases con los métodos necesarios para la realización del proyecto, así como las estructuras de datos necesarias 14 Capítulo 0. para la agilización de los algoritmos. Dichos archivos son ejecutados en modo lectura desde ambas rutinas. • Para la rutina de comunicaciones, se tiene acceso a otra librería denominada "Traducción". En ella se almacenan las clases con los métodos y estructura de datos necesarias para poder realizar la conversión entre los datos de memoria de ambos PLC’s y los datos de los objetos presentes en la simulación. Dicho archivo es ejecutado en modo lectura desde dicha rutina. • De manera global, tanto las rutinas de comunicaciones y entrenamiento como la de automatización tienen acceso a archivos tipo "JSON" donde son almacenados valores de velocidad para el Gemelo Digital. Es por ello que la rutina de automatización acceda a ellos en modo lectura, para poder actuar a continuación sobre los parámetros de los objetos presentes en la simulación. Así, otra manera de visualizarlo consiste en dividir el sistema en un sistema de tres niveles, que puede ser útil para comprender la jerarquía dentro del sistema virtual. Así, se puede aproximar de la siguiente manera la arquitectura interna empleada4: Figura 9 Estructura interna DT. Así, lo desarrollado en cada bloque es : 1. Primer nivel : Se corresponde a la primera capa de abstracción del sistema desarrollado. Está formado por : •Main : Se encarga de instanciar las clases del nivel inferior. 2. Segundo nivel : Se corresponde a la segunda capa de abstracción del sistema desarrollado. Está formado por : a) Automation : Se encarga de ejecutar la automatización interna en caso de que haya sido escogida; o bien respeta la ejecución de la automatización externa. 4En la figura 9 sólo aparecen los bloques empleados para el desarrollado de este proyecto 3 Arquitecturas planteadas 15 b) Communication : Se encarga de realizar la comunicación con los PLC’s del sistema físico, bien escribiendo sobre ellos; o bien leyendo de ellos. c) Training : Se encarga de ejecutar el algoritmo de entrenamiento en base a los datos aportados por el algoritmo de comunicación, siempre que el entrenamiento haya sido habilitado. 3. Tercer nivel : Se corresponde a la tercera capa de abstracción del sistema desarrollado. Está formado por : a) Objects : Se encarga de gestionar las físicas y cinemáticas de los objetos presentes en el sistema virtual. b) PLC’s : Se corresponden con los PLC’s presentes en la planta. Son algo externo al propio sistema virtual. 16 Capítulo 0. 4 Protocolo Modbus sobre TCP/IP. 4.1 Protocolo Modbus. Modbus es un protocolo de comunicación abierto soportado por Modbus Organization que trabaja sobre los niveles 1,2 y 7 del modelo OSI diseñado en 1979 por la empresa Modicon para la comunicación con sus PLC’s. Dicho protocolo de comunicaciones ha sido adoptado ampliamente por la industria en las últimas décadas, siendo usado por prácticamente la totalidad de los PLC’s disponibles. Inicialmente fueron creadas las versiones RTU y ASCII [7]. Dicho protocolo fue concebido con la idea de permitir el control de un conjunto de dispositivos que se encuentren interconectados dentro de una misma red, haciendo uso de bloques de datos para el envío de información entre los mismos. La relación entre dispositivos para la comunicación fue basada en las arquitecturas maestro/esclavo, o bien cliente/servidor. Es por ello por lo que cada dispositivo dentro de la red es identificado con una dirección única dentro de la misma. Así, en el envío de un paquete de datos, el contenido del mensaje debe contener la dirección del destinatario con el que se desea comunicar. Todos los dispositivos de la red recibirán dicho mensaje, pero sólo aquel que ha sido designado con dicha dirección lo ejecutará. Por tanto, resulta de vital importancia conservar la estructura de mensaje Modbus para que los controladores de los dispositivos puedan procesar dicha información de manera correcta. Debe destacarse que dicho protocolo distingue entre los siguientes elementos de información con los que se trabajará en la comunicación: Tabla 1 Tipos de elementos de información. Tipo de dato Permiso de acceso Tamaño Entrada discreta Solo lectura 1 bit Coil Lectura/Escritura 1 bit Registro de entrada Solo lectura 2 bytes Registro de memoria Lectura/Escritura 2 bytes Para evitar confusiones, debe quedar claro que la palabra coil hace referencia a una dirección de memoria interna del PLC, de tamaño 1 bit. Originalmente dicho protocolo fue diseñado en base serie, por lo que no podía ser divido en distintas capas. Con el paso del tiempo, fueron introducidos distintas versiones, entre las cuales se pueden destacar: •Modbus RTU : Empleado para comunicación serie, soportado para los protocolos eléctricos RS232, RS485 y RS422 y creado para la arquitectura maestro/esclavo. Los componentes de un mensaje de este tipo se pueden dividir en : –ID del esclavo, de longitud 1 byte. –Unidad de datos de protocolo (PDU): ∗Código de Función, de longitud 1 byte. ∗Datos adicionales, hasta 252 bytes de longitud. – Comprobación de redundancia cíclica, de longitud 2 bytes, para garantizar la fiabilidad del mensaje. Además, debe destacarse que el inicio y final de cada mensaje viene determinado por cuadros de silencio de al menos 3.5 caracteres. En esta versión, un carácter es equivalente a 1 byte, por lo tanto, dichos cuadros de silencio tendrán una longitud de al menos 28 bits. Dicho valor en segundos vendrá determinado por los baudios definidos para la comunicación. 4 Protocolo Modbus sobre TCP/IP. 17 • Modbus ASCII : Empleado para la comunicación serie, hace uso de caracteres ASCII para el protocolo de comunicación. En este caso, la estructura del mensaje sería la siguiente: – Primer byte identificador (:), cuyo valor en hexadecimal es 0x3A. Sirve para identificar el inicio de un mensaje, por lo que pueden darse largos periodos de espera entre la recepción de bytes, evitando que el software interprete que se está iniciando un nuevo mensaje, como ocurría en el protocolo RTU. –2 bytes para la dirección del esclavo con el que se está realizando la comunicación. –Unidad de datos de protocolo (PDU): ∗Código de función, de longitud 2 bytes. ∗ Datos a enviar. En el caso de esta versión, el rango de datos disponibles a intercambiar son los comprendidos entre el 0 y el 9; y del carácter A al F, dentro de los valores del código ASCII. Es decir, el rango es el siguiente: Tabla 2 Caracteres disponibles de datos para la versión Modbus ASCII. Caracter Valor hexadecimal Valor binario 0 30 011 0000 1 31 011 0001 2 32 011 0010 3 33 011 0011 4 34 011 0100 5 35 011 0101 6 36 011 0110 7 37 011 0111 8 38 011 1000 9 39 011 1001 A 41 100 0001 B 42 100 0010 C 43 100 0011 D 44 100 0100 E 45 100 0101 F 46 100 0110 Como se puede observar, en dicho protocolo el tamaño de los bytes de datos es reducido a 7 bits. Además, debe añadirse el bit de inicio y el bit de parada, además de la posibilidad de añadir el bit de paridad. Por lo tanto, el tamaño final de cada byte de datos es de 10 bits. Además, dichos caracteres no representan más que valores en hexadecimal. Por lo tanto, en el caso de que se deseara enviar dentro de los datos el valor 7C, dicho dato sería convertido a lo siguiente: 7C−→ 7 C −→ 3743 –Control de redundancia longitudinal, LRC, de 2 bytes de longitud. –Caracteres terminadores de mensaje. ∗Retorno de carro : 0x0D. ∗Salto de línea : 0x0A. • Modbus TCP/IP : Se trata de la versión Modbus empleada sobre redes TCP/IP, en la cual se usa de manera general el puerto 502 de los dispositivos. Dicha versión está basada en la arquitectura servidor/cliente, siendo esta última versión la empleada para la realización de este proyecto[8]. Así, para comprender mejor el funcionamiento de esta versión, deben quedar claro los siguientes conceptos: 18 Capítulo 0. – TCP : Las siglas TCP hacen referencia en inglés al protocolo de control de transmisión. Dicho protocolo es el que garantiza la entrega de paquetes de información entre dos máquinas en el mismo orden en el que han sido enviados. La capacidad de asegurar el recibo de un mensaje es gracias al acuse de recibo, más conocido por sus siglas en inglés ACK. Dicho protocolo es el que nos permite que que la información de/hacia diversas fuentes pueda circular por la misma línea de manera simultánea, resultando fundamental para poder trabajar con diversos PLC’s a la vez. Dicho protocolo es conocido por su funcionamiento 3-way handshake al establecer la conexión. Es decir, al iniciar la comunicación entre dos máquinas se sigue la siguiente secuencia: 1. En primer lugar, el cliente trata de establecer una conexión con el servidor, a través del envío de un SYN(Synchronize Sequence Number). 2. En segundo lugar, el servidor procesa la petición anterior y devuelve una señal tipo SYN-ACK, indicando al cliente que la petición anterior fue recibida correctamente. 3. Finalmente, el cliente envía un ACK de vuelta al servidor para indicar que su confirmación fue recibida correctamente. Tras esto, la conexión es considerada como establecida. Figura 10 Establecimiento de conexión para MODBUS TCP/IP. Tras esto, es iniciada la transferencia de datos entre ambas máquinas. Para asegurar la fiabilidad y seguridad de los datos enviados, se suelen aplicar los siguientes criterios en este protocolo: 1. Uso de checksum, para asegurar que el contenido del paquete de datos no ha sido alterado de alguna forma física durante su transmisión. Dicho criterio es el empleado por Modbus sobre TCP/IP, mientras que el Modbus TCP/IP no lo incorpora. 2. Uso de asentimientos por cada paquete de información enviado. A cada paquete de información enviado por el emisor, el receptor debe responder con un ACK, indicando la correcta recepción de la secuencia de información que ha sido enviada. 3. Uso de temporizadores, para evitar largas esperas de confirmación. Finalmente, en el cierre de conexión, se usa un four-way handshake. En él, se sigue la siguiente secuencia: 4 Protocolo Modbus sobre TCP/IP. 19 1. El cliente envía un paquete de tipo FIN, para indicar la finalización de la conexión. 2. El servidor recibe dicho paquete, y envía un ACK como respuesta a dicho mensaje. 3. Tras esto, el servidor un paquete FIN hacia el cliente, indicando que el también procede a finalizar la conexión. 4. Finalmente, el cliente recibe este último paquete y responde con un ACK, indicando que lo ha recibido correctamente al servidor. Este concepto definirá la capa de transporte en nuestro sistema. – IP : Las siglas IP hacen referencia en inglés al protocolo de internet. Dicho protocolo es aquel que realiza todo el proceso de enrutamiento, es decir, definir las direcciones IP de origen y destino, siendo dichas direcciones empleadas por lo routers para definir el camino dentro de la red que debe seguir un paquete de información. Este concepto definirá la capa de red en nuestro sistema. Dentro de este concepto, se distinguen dos versiones: ∗ IPV4 : El empleado para este proyecto, este es el protocolo de direccionamiento más antiguo. En él, las máquinas son definidas por direcciones de 32 bits, divididas en 4 bytes separados por puntos. 192.168.3.15/16 Ejemplo de dirección IPV4 de clase C. A su vez, existen ciertos rangos de direcciones que están reservados para otro uso. Dentro de esta versión, se deben distinguir tres clases empleados exclusivamente para redes privadas: Tabla 3 Clases disponibles para IPV4. Clase Rango máscara Número direcciones IP disponibles Clase A 255.0.0.0 16.777.215 Clase B 255.127.0.0 1.048.576 Clase C 255.255.0.0 65.535 Por lo tanto, si tomamos el ejemplo de dirección anterior, y suponemos que el tipo de clase de la red es de tipo C. la dirección de la red quedaría definida por el siguiente valor: 192.168.3.15/16&255.255.0.0−→ 192.168.0.0/16 Ejemplo de aplicación de máscara de red. En concreto, el valor de dirección 192.168.0.0/16 es una dirección reservada para redes privadas. Dicho valor será el empleado dentro de la planta. ∗IPV6 : La versión sucesora de la anterior, provee a las máquinas direcciones de 128 bits. Dicha versión comenzó a ser aplicada debido a la creciente demanda de nuevas direcciones y a la limitación que presentaba la versión anterior. Así, cualquier intercambio de mensajes entre cliente y servidor será iniciado, como se ha indicado anteriormente, por el cliente, de la siguiente forma : 20 Capítulo 0. Figura 11 Ejemplo de intercambio de mensajes para MODBUS TCP/IP. Como se puede observar, a cada mensaje enviado por el cliente debe haber una respuesta por parte del servidor. Incluso en el caso de escritura, deberá haber una respuesta de confirmación por parte del servidor, para indicar que todo se ha realizado correctamente. En caso de que no hubiera respuesta, el cliente debería de enviar de nuevo el mensaje hasta que obtenga una respuesta antes del timeout 5 especificado en la creación del socket por parte del cliente. 5 Timeout : Tiempo máximo que puede pasar desde que el cliente envía un mensaje al servidor y recibe el mensaje de respuesta del mismo. 4 Protocolo Modbus sobre TCP/IP. 27 (2.). Por tanto, desde el punto de vista del mensaje enviado por el cliente, se observa que la estructura de la PDU sería la siguiente: Figura 16 Estructura PDU de un mensaje enviado por el cliente en la función 0x17. Por tanto, como podemos observar, los campos dirección ytamaño se ven multiplicado por dos, debido a que se van a ejecutar una acción de lectura y otra de escritura con la posibilidad de emplear tamaños diferentes y ejecutarlas sobre direcciones de memoria diferentes. Además, se incluye un campo extra, Tam.Datos que indica el número de bytes de datos restantes. Es decir, el número de registros de memoria/2 que se van a escribir. Dicho campo de datos se construye de la misma manera que en la función 0x10. Respecto al mensaje de respuesta del servidor tendrá la misma estructura que el mensaje que en la función 0x03. Por tanto, la única respuesta por parte del servidor se corresponderá a la acción de lectura. Finalmente, a la hora de procesar y ejecutar las acciones correspondientes al mensaje por parte del servidor, se ejecutará en primer lugar la acción de escritura, y posteriormente la acción de lectura. 28 Capítulo 0. 5 Programación en el entorno de los PLC’s. La programación de los PLC’s disponibles en la planta ha sido realizada con el software EcoStruxure Control Expert, tal y como se mencionó. Así, dentro de este ámbito, se ha desarrollado un programa para cada PLC, con el objetivo de que puedan reaccionar tanto en el ámbito de automatización interna como en el de automatización externa. 5.1 Programación del Main PLC Como fue comentado anteriormente, el objetivo de este PLC es de servir de "PLC central" para el resto de PLC’s y máquinas que se encuentran disponibles en la planta. Sin embargo, para la realización de este proyecto, la funcionalidad de este PLC se ha limitado al ámbito de lo que a día de hoy se encuentra disponible en la simulación. Por tanto, el objetivo principal de este componente será tener la capacidad de mover múltiples bandejas a través de sus cintas, evitando colisiones, así como recoger datos temporales e cuando pasen bandejas por determinados sensores(esta última funcionalidad será activada y desactivada desde el Gemelo Digital). Así, para comprender el razonamiento establecido para la gestión del movimiento de las bandejas, conviene observar antes la siguiente figura : Figura 17 Vista superior de la planta desarrollada en el Gemelo Digital. En la imagen anterior se observa todo el entorno digital desarrollado hasta la fecha por el grupo DENIM. En dicha imagen se puede observar como la planta se puede dividir en dos componentes principales: •El conjunto de cintas, sensores y pistones establecidos para simular el movimiento de las bandejas. •El conjunto para almacenar bandejas formado por el Stacker y el Stacker Controller. 5 Programación en el entorno de los PLC’s. 29 Así, la nomenclatura establecida con respecto a los pistones y sensores del primer conjunto se puede observar en la siguiente imagen : Figura 18 Nomenclatura de los sensores y pistones. Por tanto, se procede a explicar con más detalle lo desarrollado para el PLC encargado del control de dichos elementos : 30 Capítulo 0. 5.2 Programación Main PLC En primer lugar, se ha de tener en cuenta la presencia del primer sensor S0 como elemento de emergencia. Dicho sensor tendrá un valor normalmente alto para cuando no esté pulsado. La pulsación de dicha seta significará la desactivación del compresor de aire de los pistones, así como la parada de los motores de cada una de las cuatro cintas. Dicha desactivación está enlazada al circuito eléctrico con el que se accionan dichos elementos. Por lo tanto, en el caso de que no haya sido programada y dicha seta sea pulsada, o bien pierda la corriente, todo el sistema quedará detenido igualmente. A su vez, con respecto al resto de los sensores, todos ellos son sensores inductivos que pueden ser activados por las distintas chapas que poseen las bandejas. Dentro de los mismos, debemos distinguir tres subconjuntos : • Sensores asociados a pistones : Son los sensores que se encuentran unidos a su vez a pistones en el interior de las cintas. Dicho conjunto está formado por los sensores S1, S4, S6, y S9. • Sensores libres : Son los sensores que se encuentran en el interior de las cintas y que no se encuentran unidos a ningún pistón de manera física. Dicho conjunto está formado por los sensores S3 y S8. • Sensores de las esquinas : Son los sensores que se encuentran en las esquinas y supondrán el elemento activador para el cambio de cinta de una bandeja. Dentro de este conjunto, distinguimos dos grupos : – Sensores activados por bajo nivel : Son los sensores que indicarán la presencia de una bandeja cuando su valor lógico sea 0. Dicho grupo está formado por los sensores S5 y S10. – Sensores activados por alto nivel : Son los sensores que indicarán la presencia de una bandeja cuando su valor lógico sea 1. Dicho conjunto está formado por los sensores S2 y S7. En segundo lugar, se ha de distinguir entre dos tipos de pistones : • Pistones interiores : Son los pistones que se encuentran en la parte interior de las cintas. La activación de dichos pistones supondrá la bajada de dichos pistones, por lo que permitirá el paso de las bandejas. Este conjunto está formado por los elementos : P1, P3, P5 y P7. • Pistones en esquina : Son los pistones que se encuentra en las esquinas de transición entre las cintas. La activación de dichos pistones supone la elevación de la plataforma a la que están unidos, haciendo posible el paso de una bandeja de una cinta a otra. Este conjunto está formado por los elementos : P2, P4, P6 y P8. Tras conocer esto, se ha de indicar que el sentido del movimiento de las bandejas será en sentido antihorario con respecto a lo establecido en la Figura 18, así como que las cintas izquierda y derecha de la imagen se encuentran a una altura superior con respecto a las cintas superior e inferior. Conociendo toda esta información, se procedió a tratar de establecer una estrategia para el movimiento de las bandejas a través de las cintas que tratara de evitar colisiones entre bandejas en el caso de que se cargaran varias; así como se tratara de aprovechar al máximo el espacio disponible y tratar de permitir la mayor fluidez posible en el movimiento. El motivo de la creación de tales regiones es, principalmente, para evitar problemas de colisiones en las esquinas, ya que el uso de los pistones elevadores en el caso de que dos bandejas se encontraran demasiado cercanas entre sí podría suponer que alguna llegara a salirse de los raíles. Con esto, se decidió marcar 4 zonas de control en el recorrido : Tabla 5 Regiones de control. Región Sensor de entrada Sensor de salida Pistón de entrada Región 1 S1 S3 P1 Región 2 S4 S5 P3 Región 3 S6 S8 P5 Región 4 S9 S10 P7 5 Programación en el entorno de los PLC’s. 31 En el interior de cada una de estas zonas se ha establecido sólo podrá circular una sola bandeja. Por tanto, la secuencia de pasos en cada una de las regiones será la correspondiente : 1. Llegada de una bandeja al sensor de entrada. Se dejará pasar a la bandeja. 2. Se marcará la región como ocupada y se elevará el pistón de entrada para evitar la entrada de otras bandejas en tal zona. Por tanto, se desactivará la variable asociada a tal pistón. 3. Una vez la bandeja que ocupaba la región llegue al sensor de salida, se marcará la región como libre y se volverá al paso 1, dejando paso a posibles bandejas que estuvieran esperando bloqueadas por el pistón de entrada. Así, un de manera programada, se obtiene lo siguiente : Figura 19 Ejemplo de programación de una región de control. En la imagen anterior se observa cómo se ha hecho uso de contadores para marcar dichas zonas como ocupadas o libres. Así, cuando una bandeja active el sensor de entrada y aún no haya ninguna bandeja en el interior de la región, se incrementará el contador, quedando dicha zona marcada como ocupada (el valor del contador indica el número de bandejas en su interior). En el caso de que una bandeja llegara al sensor de entrada, pero ya hubiera una bandeja en su interior (valor del contador >=1 ), no se incrementará el valor del marcador hasta que dicho valor sea 0 de nuevo. Es decir, hasta que la bandeja que se encontraba en el interior alcance el sensor de salida, y por tanto decremente el valor del contador de nuevo a 0. Y con respecto a la activación de los pistones, queda lo siguiente : Figura 20 Ejemplo de activación de los pistones de las zonas de control. Se observa como para la activación del pistón de entrada de la región(permitir el paso de una bande- ja), el valor del contador de la región asociada debe ser <1 , es decir, que no haya ninguna bandeja en tal región. Sin embargo, una vez el contador de la región sea incrementado a uno y el valor del pistón tenga que valer 0, se deja un tiempo de 150 milisegundos extras en el que el pistón permanecerá aún activo, con el objetivo de dejar pasar de manera completa la zona de colisión de la bandeja y evitar una activación inmediata que suponía el rebote de la bandeja y el riesgo de que esta descarrilase. Dicho valor temporal tampoco podría tomar valores muchos más altos, ya que podría darse el caso de que transcurriera demasiado tiempo hasta la desactivación del pistón y se permitiera el paso de una segunda bandeja. 32 Capítulo 0. A su vez, respecto a los pistones que se encuentran en las esquinas, se ha programado lo siguiente : Figura 21 Ejemplo de programación de un pistón de esquina. Y en el caso particular de la esquina de unión de las cintas superior e izquierda : Figura 22 Ejemplo de programación de un pistón de esquina 2. Así, en la figura 21 se observa el ejemplo de programación de un pistón de esquina que tenga asociado un sensor cuyo valor sea normalmente alto (pistones P4 y P8). Así, dicho pistón estará activado (elevado) siempre que no haya sido pulsada la seta de emergencia (S0); así como nos encontremos bien en modo simulación (automatización interna), que en dicho caso el valor será establecido desde Gemelo Digital; o bien no haya bandeja sobre la esquina (S5 activado). Por otro lado, en la figura 22 se observa el ejemplo de programación de un pistón de esquina que tenga asociado un sensor cuyo valor sea normalmente bajo (pistones P2 y P6). En este caso, se mantiene la condición de la seta de emergencia, así como se añade el caso de que se encuentre una bandeja en dicha esquina (sensor correspondiente activado) y que el sensor del final de la región a la que pertenece se encuentre libre (aún no haya llegado la bandeja a tal zona).Esto se realiza así con el objetivo de bajar de nuevo dicho pistón una vez que estemos completamente seguros de que la bandeja ha abandonado la esquina. Cuando la bandeja abandone la esquina, se desactivará la variable del pistón y este descenderá de nuevo, a la espera de la llegada de otra bandeja. Sin embargo, en el caso del pistón P2, la programación se tuvo que realizar finalmente de manera ligeramente diferente, debido a que la ubicación del sensor asociado a tal esquina (S2) no se encontraba alineado de manera precisa, y por tanto la activación inmediata de tal pistón una vez dicho sensor obtenía un valor lógico alto suponía que las bandejas descarrilaran en su entrada a la cinta izquierda. Por lo tanto, se debió usar la siguiente solución : 5 Programación en el entorno de los PLC’s. 33 Figura 23 Programación pistón P2. Para poder solucionar tal problema, se aplicó lo establecido en la Figura 23. Así, en la primera línea, lo que se busca es guardar la activación del sensor S2, ya que la bandeja lo activaba una vez que pasaba por delante, pero sin embargo ésta aún no se encontraba del todo alineada con la cinta izquierda. En la segunda línea, una vez es seguro que la bandeja ha pasado el sensor, se espera un ligero tiempo (50 milisegundos) de margen para permitir que la bandeja llegue al final de la cinta superior. Tras esto, en la tercera línea, una vez ha trascurrido ese tiempo, y se sigue cumpliendo la condición anterior así como la no pulsación de la seta de emergencia, se activa la variable del pistón, haciendo que éste se eleve. No volverá a bajar hasta que S3 se active, dándonos la seguridad que la bandeja ha abandonado completamente la esquina. Tras esto, lo siguiente a resaltar es como se ha establecido el guardado del estado de los distintos elementos que conforman la planta. Así, como se mencionó anteriormente y como se ha podido ya observar en imágenes anteriores, la programación se realiza de manera completa haciendo uso de coils que almacenan el valor de los sensores y actuadores. Figura 24 Ejemplo de programación del coil asociado a un pistón. 34 Capítulo 0. Figura 25 Ejemplo de programación del coil asociado a un sensor. Se observa que para el caso de los pistones, Figura 24, se distinguen dos coils : • En el caso de que nos encontremos en modo simulación, con la automatización interna del Gemelo Digital activada, se encontrará activada la línea inferior, y la activación del pistón dependerá del valor escrito por el Gemelo Digital sobre el coil de nombre PXSimulation, siendo Xel índice del pistón. • En el caso de que se encuentre activada la automatización propia del PLC, con la automatización externa del Gemelo Digital activada, se encontrará la línea superior activada. Por tanto, la activación del pistón dependerá del valor escrito por el propio PLC sobre el coil de nombre MemoryPX, siendo X el índice del pistón. A su vez, en el caso de los sensores, se almacenará de manera directa el valor del sensor sobre el coil de nombre MemorySX, siendo Xel índice del sensor. En el caso de los motores de las cintas, su programación resulta más simple : Figura 26 Ejemplo de programación de una cinta. Para que las cintas estén activas, debe activarse el coil Start, así como la seta de emergencia no deben estar pulsadas. En caso de que estén activadas y se desactive el valor del coil Start, los motores de las cintas permanecerán activas. En este caso, no se ha creado otro set de coils de memoria para el caso de simulación, ya que el valor de los motores durante la simulación se puede considerar estático (no cambian, permanecen siempre activos). 5 Programación en el entorno de los PLC’s. 35 Figura 27 Ejemplo de programación de memoria d una cinta. Finalmente, para que todos el sistema formado por los elementos anteriores se ponga en marcha, se deben activar dos coils : MemoryAIRE yStart. Dichos elementos de memoria serán activados por parte del Gemelo Digital en el inicio de la simulación. Figura 28 Coils MemoryAIRE yStart. El uso de elementos de tipo Set y Reset son usados para que, en caso de que sean pulsadas algunas de las setas de emergencia y sean liberadas posteriormente, se retome el control de planta y se vuelvan a activar todos los motores de las cintas y la entrada de aire del compresor. El único caso en el que se detendrá toda la planta será cuando sea pulsado el botón PARO que se encuentra en el panel frontal del PLC, cuyo valor es normalmente alto. 36 Capítulo 0. Así, a modo de resumen, el uso e interacción del Main PLC con el Sistema Virtual se puede resumir en la siguiente imagen : Figura 29 Interacción Main PLC. En primer lugar observamos el uso que se ha realizado de los distintos tipos de variables de memoria : “Coils”, “Words” o “Palabras de 16 bits”. Así, en el caso de los coils, de cara a lo que interesa el Sistema Virtual, distinguimos entre “Coils Físicos” y “Coils Virtuales”. En el caso de los coils físicos, estos se utilizan a modo de lectura para el Sistema Virtual. Sobre ellos escribirá la lógica de la automatización propia de los PLC’s, y será el Sistema Virtual quien leerá además el valor de dichos coils para aplicarlo sobre el modelo 3D. Dicho información es enviada a través de “PLCValues”. Para que dichos coils sean usados, se debe ejecutar la automatización externa, es decir, la variable “Auto.Externa” debe ser true. Por otro lado, en el caso de los coils virtuales, estos se utilizan para que ser escritos por parte del Sistema Virtual. Dicha información es enviada por los “DTValues”, y la lógica de los PLC’s lo aplica sobre los actuadores. Para que dichos coils sean usados, se debe ejecutar la automatización interna, es decir, la variable “Auto.Externa” debe ser false. Así, el uso de uno u otros coils vendrá dictaminado por el Sistema Virtual, que activará uno u otro camino. Por otro lado, en el caso de las palabras de 16 bits, estas se utilizan para almacenar los valores de tiempo y activación de ciertos sensores, que resultan útiles para la rutina de entrenamiento. Para ello, el Sistema Virtual será el que dictaminará si se deben recoger dichos valores o no, a través de “EnableTraining. Dicha información será leída a través de “TimerValues”. Además, el Sistema Virtual será el que dictaminará cuando se debe realizar un reset de dichos registros a través de “ResetTimers”. 5 Programación en el entorno de los PLC’s. 43 Figura 34 Algoritmo principal desarrollado para el Stacker. 44 Capítulo 0. A continuación se procede a comentar cada uno de los estados y transiciones : Tabla 8 Estados algoritmo principal Stacker. Estado Significado Estado Estado 0 Se corresponde al estado de partida del algoritmo. En dicho estado el usuario puede operar en el modo manual. Estado 1 En dicho estado se activa un temporizador para que el usuario puede elegir entre modo remoto o modo local. Es decir, que la activación del servir bandeja sea realizada o bien a través de su selección en el panel físico; o bien por escritura sobre los bits de memoria BitAlmacenarBandeja o BitDescargarBandeja. Estado 2 En dicho estado se estará sólo durante un ciclo de reloj del PLC. Representa el punto de división entre el modo manual y el modo remoto. Estado 3 Corresponde al estado de espera una vez se ha seleccionado el modo local. Se permanecerá en espera en este estado hasta que uno de los dos botones de almacenar o servir bandeja sea pulsado. Estado 4 Corresponde al estado de espera una vez se ha seleccionado el modo remoto. Permanecerá en espera en este estado hasta que uno de los dos bits de almacenar o servir bandeja sean activados. Estado 5 Corresponde al estado en el cual se ejecuta la acción de almacenar una bandeja de la cinta sobre la jaula del Stacker. Estado 6 Corresponde al estado en el cual se ejecuta la acción de servir una bandeja sobre de la jaula del Stacker sobre la cinta. Estado 7 Se corresponde a ejecutar la acción de almacenar una bandeja cuando se ha activado el coil de memoria BitAlmacenarBandeja. No es más que un salto al estado 5 para realizar su mismo algoritmo. Estado 8 Se corresponde a ejecutar la acción de servir una bandeja cuando se ha activado el coil de memoria BitServirBandeja. No es más que un salto al estado 6 para realizar su mismo algoritmo. Estado 9 No es más que el estado al que se accede tras finalizar las acciones de almacenar o servir. Representa un salto de vuelta al estado 1. Como se puede observar, en el Estado 1 se hace uso de un temporizador para que el usuario pueda elegir si operar el modo local o remoto. Esto es así debido a que la entrada asociada a elegir bien modo local o modo automático, establecía por defecto el modo remoto cuando se estaba en modo local. Por lo tanto, en el instante en el que se decidiera seleccionar el modo automático, el PLC interpretaba que también estaba seleccionado el modo remoto. Es por ello por lo que se dan unos segundos para que el usuario pueda elegir el modo local o remoto. Además, los estados 5y6representan macrosteps. Por lo tanto, en cada uno de estos pasos se ejecutará un algoritmo completo, que representan las secuencias que se han de seguir para almacenar o servir bandejas. 5.3.1 Algoritmo para almacenar bandeja : El algoritmo desarrollado para almacenar bandejas en el interior de la jaula del Stacker es el siguiente : 5 Programación en el entorno de los PLC’s. 45 Tabla 9 Estados algoritmo para almacenar bandejas. Estado Significado Estado Estado 0 Se corresponde con el inicio del algoritmo, no se ejecuta ninguna acción en dicho estado. Estado 1 En dicho estado se lanza un temporizador, para dar tiempo a posibles bandejas que se pudieran encontrar debajo del Stacker a que salgan de dicha zona. Por tanto, el Pistón Posterior no debe ser activado; mientras que el Pistón Anterior se debe activar para no permitir a nuevas bandejas entrar en la zona. Obviamente, el Pistón Principal debe encontrarse abajo. Estado 2 En dicho estado se mantienen los actuadores de la misma manera que en el caso anterior, pero se permanece a la espera de la llegada de una nueva bandeja al Pistón Anterior. Estado 3 En dicho estado se libera el Pistón Anterior y se activa el Pistón Posterior, con el objetivo de dejar entrar en la zona del Stacker a la bandeja que va a ser almacenada. Se lanza un temporizador para estimar el tiempo en el que el Pistón Anterior debe estar desactivado(lo suficiente para dejar pasar a la bandeja sin colisiones, pero evitando que pudiera adentrarse otra bandeja que se encontrara detrás. Estado 4 En dicho estado se vuelve a activar el Pistón Anterior, para evitar que otras bandejas pudieran entrar en la zona del Stacker mientras se está operando. Se esperará a que la bandeja llegue a la posición del Pistón Posterior. Estado 5 En dicho estado se eleva el Pistón Principal y se activa un temporizador, para dar tiempo a que el pistón llegue a la parte superior. También se comprobará que el sensor de la parte superior del Pistón Principal se active. Estado 6 En dicho estado se hace girar la jaula del Stacker hacia la izquierda. Se activa un temporizador para que la jaula tenga tiempo suficiente para girar. También se comprobará que el sensor de la parte izquierda se active. Estado 7 En dicho estado se hace girar la jaula del Stacker hacia la derecha. Se activa un temporizador para que la jaula tenga tiempo suficiente para girar. También se comprobará que el sensor de la parte derecha se active. Estado 8 En dicho estado se baja el Pistón Principal, una vez ya se ha almacenado la bandeja dentro de la jaula. Se activa un temporizador para que tenga tiempo suficiente para descender. También se comprobará que el sensor de la parte inferior del Pistón Principal se active. Estado 9 En dicho estado se liberar todos los pistones, permitiendo de nuevo la libre circulación de bandejas dentro de la zona del Stacker. A continuación se adjunta una muestra visual del algoritmo implementado. 12 12 Se han debido usar dos imágenes debido a que la longitud del propio algoritmo impedía tener una buena resolución en una sola imagen. 46 Capítulo 0. Figura 35 Algoritmo para almacenar bandejas I. 5 Programación en el entorno de los PLC’s. 47 Figura 36 Algoritmo para almacenar bandejas II. 48 Capítulo 0. 5.3.2 Algoritmo para servir bandeja : El algoritmo desarrollado para servir bandejas sobre la cinta derecha de la planta es el siguiente : Tabla 10 Estados algoritmo para servir bandejas. Estado Significado Estado Estado 0 Se corresponde con el inicio el algoritmo, no se ejecuta ninguna acción en dicho estado. Estado 1 En dicho estado se lanza un temporizador, para dar tiempo a posibles bandejas que se pudieran encontrar debajo el Stacker a que salgan de dicha zona. Por tanto, el Pistón Posterior no debe ser activado; mientras que el Pistón Anterior se debe activar para no permitir a nuevas bandejas entrar en la zona. Obviamente, el Pistón Principal debe encontrarse abajo. Estado 2 En dicho estado se eleva el Pistón Principal para extraer la bandeja. Se lanza un temporizador para que tenga tiempo suficiente para que llegue a la parte superior. También se comprobará que el sensor de la parte superior del Pistón Principal se active. Estado 3 En dicho estado se hace girar la jaula del Stacker hacia la derecha. Se activa un temporizador para que la jaula tenga tiempo suficiente a realizar el giro completo. También se comprobará que el sensor de la parte derecha se active. Estado 4 En dicho estado se hace girar la jaula del Stacker hacia la izquierda. Se activa un temporizador para que la jaula tenga tiempo suficiente a realizar el giro completo. También se comprobará que el sensor de la parte izquierda se active. Estado 5 En dicho estado se hace descender al Pistón Principal, para dejar la bandeja sobre la cinta. Estado 6 En dicho estado, una vez la bandeja ha sido depositada sobre la cinta, se desactivan todos los pistones para permitir de nuevo la libre circulación de bandejas dentro de la zona del Stacker. 5 Programación en el entorno de los PLC’s. 49 El ejemplo visual de dicha algoritmo es el siguiente : Figura 37 Algoritmo para servir bandejas. 5.3.3 Correcciones realizadas para la correcta operación en modo manual Sin embargo, el sistema implantado sobre el Stacker PLC para el manejo manual del mismo presentó un problema a la hora de recoger información del mismo y ser aplicada sobre el Gemelo Digital. En particular, el panel de botones para el manejo manual representa un circuito eléctrico que no está conectado a las entradas del PLC. Por lo tanto, no era posible saber el estado de los actuadores del Stacker. A su vez, la transición de los pistones tanto en el caso del modo manual como del modo automático implicaba que en ciertos momentos no fueran activados ninguno de los dos sensores que posee cada uno, por lo que existían pequeños períodos de tiempos en los que el estado de un pistón era desconocido. 50 Capítulo 0. Con el objetivo de solucionar estos problemas, se han implementado las siguientes máquinas de estado : Figura 38 Solución al estado de movimiento del pistón de elevación. Con ello, de esta manera, si el sensor inferior del Pistón Elevación se encuentra activado, se almacena el dato de que dicho pistón se encuentra abajo, y por lo tanto, se conoce el estado de las salidas de su doble pistón asociado. Dicha información es trasladada al Gemelo Digital, el estado CilDown. Así, una vez dicho sensor se deje de encontrar activado, se encuentra en el estado GoingUp, y por lo tanto la configuración de las salidas de su doble pistón se habrá invertido. Tras ello, una vez se active el sensor superior del Pistón Elevación, se encontrará en el estado CilUp. Finalmente, la secuencia de estados para el proceso de bajada del pistón es análoga al proceso de subida descrito. De la misma manera se han implementado máquinas de estado similares para el resto de pistones. En el caso del rotor : 5 Programación en el entorno de los PLC’s. 51 Figura 39 Solución al estado de movimiento del pistón de rotación. 52 Capítulo 0. En el caso del pistón anterior : Figura 40 Solución al estado de movimiento del pistón de anterior. 6 Programación en el entorno del Gemelo Digital. 59 6.4 Namespace para la traducción De manera análoga a lo expuesto en el apartado 6.2, se ha creado otro namespace dedicado a la programación de todos los elementos necesarios para realizar la correspondencia entre información proveniente de los PLC’s e información proveniente del Gemelo Digital. Está formada por los siguientes cuatro tipos de clases, que pueden ser clasificadas en dos grupos : 1. Clases para el Main PLC : Dichas clases tienen la misma morfología de propiedades, que es : a) Valores de pistones intermedios. b) Valores de las cintas. c) Valores de los pistones de las esquinas. A su vez, tendrán tres métodos más el propio constructor de la clase, con funcionamientos invertidos una respecto a la otra dependiendo de si la comunicación en PLC−→DT, o bien DT−→PLC : a) Constructor : Establece los ID’s de los elementos con los que se va a trabajar en el entorno del Gemelo Digital. b) Aplicación de los valores de los pistones sobre el PLC/DT : Método para aplicar los datos de los pistones intermedios en las cintas. Lo aplica sobre el DT si la automatización es externa; o bien los extrae del DT para ser aplicados posteriormente sobre el PLC. En el caso del traductor DT−→Main PLC se encargará de recoger también los valores de los pistones de esquina. c) Aplicación de los valores de las cintas sobre el PLC/DT : Método para aplicar los datos de las cintas. Lo aplica sobre el DT si la automatización es externa; o bien los extrae del DT para ser aplicados posteriormente sobre el PLC. d) Aplicación de los cintas de los pistones de esquina sobre el PLC/DT : Método para aplicar los datos de las cintas de los pistones de las esquinas. Este método sólo estará presente para el traductor Main PLC −→ DT. Se encarga de aplicar los datos de las cintas de la planta sobre las pequeñas cintas superiores que tienen asociados los pistones de esquina en el Gemelo Digital, que se muestran como independientes a los valores de las cintas largas. e) Reset de actuadores en el DT en caso de emergencia : Este método sólo se encuentra disponible para el caso del traductor DT −→ PLC. Esto es debido a que en el caso de que se estuviera ejecutando la automatización interna, pero fuera pulsada alguna seta de emergencia en la planta física, es necesario notificarlo para detener los actuadores dentro de la simulación. En el ámbito de la realidad, el circuito eléctrico ya deshabilita todos los actuadores, por lo que no es necesario desactivarlos desde el DT. 2. Clases para el Stacker PLC : Dichas clases tienen la misma morfología de propiedades, que es : a) Valores de los actuadores/pistones. A su vez, este tipo de clases sólo tendrán un método, más allá del propio constructor de la clase : a) Constructor : Establece los ID’s de los elementos con los que se va a trabajar en el entorno del Gemelo Digital. b) Aplicación de los valores de los actuadores sobre el PLC/DT : Método para aplicar los datos de los actuadores. En el caso de que la automatización sea externa, aplicará dichos valores sobre el Gemelo Digital; o bien, si la automatización es interna, extraerá dichos valores del Gemelo Digital para ser aplicados posteriormente sobre el PLC. c) Reset de actuadores en el DT en caso de emergencia : Dicha función cumple el mismo objetivo que la expuesta para el Main PLC, pero deshabilitando los actuadores asociados al Stacker PLC dentro de la simulación. En el caso de la traducción, no han sido necesarios definir un script para almacenar los parámetros necesarios, pues la única dependencia de las funciones que se emplean en este namespace son los ID’s de los actuadores en el entorno del Gemelo Digital, y que vienen definidos según el proyecto. Dichos parámetros son dados directamente en el constructor de cada una de las clases anteriormente expuestas, tal y como se verá en el anexo. 60 Capítulo 0. 6.5 Realización de la comunicación en el entorno del Gemelo Digital La aplicación de los namespace de comunicación y traducción sigue el siguiente pseudocódigo : Algorithm 1 Algoritmo de comunicación function Awake ▷Función ejecutada antes del inicio Crear instancia de clase para comunicación con el Main PLC. if Si conexión realizada correctamente. then if Si comunicación PLC −→ DT then Crear instancia de traductor Main PLC−→DT. end if if Si comunicación DT −→ PLC then Crear instancia de traductor DT−→Main PLC. end if end if Crear instancia de clase para comunicación con el Stacker PLC. if Si conexión realizada correctamente. then if Si comunicación PLC−→DT then Crear instancia de traductor Stacker PLC−→DT. end if if Si comunicación DT−→PLC then Crear instancia de traductor DT−→Stacker PLC. end if end if end function function Update ▷Función ejecutada en cada frame if !Primer loop realizado then if Comunicación PLC−→DT then Main PLC : Escribir true sobre los coils de inicialización y aire; false sobre el de simulación. Stacker PLC : Escribir false sobre el coil de simulación. end if if Comunicación DT−→PLC then Main PLC : Escribir true sobre los coils de inicialización, aire y simulación. Stacker PLC : Escribir true sobre el coil de simulación y el de rearme. end if end if if Primer loop realizado then if Comunicación PLC−→DT then Main PLC : Lectura coils actuadores en planta. Main PLC : Llamada traductor Main PLC−→DT con los valores leídos. Stacker PLC : Lectura actuadores Stacker. Stacker PLC : Llamada traductor DT−→Main PLC con los valores leídos. if Usuario desea conocer el número de cajas apiladas then Lectura sensor caja apiladas. end if end if if Comunicación DT−→PLC then Main PLC : Check emergencia en la realidad. Main PLC : Extracción datos del DT con llamada al traductor DT−→Main PLC. Main PLC : Escritura de los datos anteriores sobre el Main PLC. Stacker PLC : Check emergencia en la realidad. Stacker PLC : Extracción datos del DT con llamada al traductor DT−→Stacker PLC. Stacker PLC : Escritura de los datos anteriores sobre el Stacker PLC. end if end if end function 6 Programación en el entorno del Gemelo Digital. 61 Como se observa, los pasos a realizar se centran en : •En la función Awake : 1. Crear dos instancias de la clase de comunicación, una para el Main PLC y otra para el Stacker PLC. 2. Crear dos traductores para la relación entre los datos en los actuadores del PLC y los actuadores del Gemelo Digital; una para para el Main PLC, y otra para el Stacker PLC. •En la función Update : –En el caso de que se encuentre sobre el primer loop : 1. Inicializar los coils necesarios del Main PLC. 2. Inicializar los coils necesarios del Stacker PLC. –En el caso de que no se encuentre sobre el primer loop : ∗En el caso de comunicación PLC−→DT : 1. Leer los coils de actuadores del PLC correspondiente. 2. Emplear el traductor para aplicarlos sobre el DT. 3. Además, se añade la posibilidad de poder leer el número de cajas almacenadas siempre que el usuario lo desee (sobre la entrada analógica 0 del Stacker PLC). ∗En el caso de comunicación DT−→PLC : 1. Comprobar que en la realidad no se haya pulsado alguna seta de emergencia. Esto se debe realizar ya que en el caso de que ocurriera, en la realidad el circuito eléctrico estaría cortado y los actuadores desactivados, mientras que en la simulación no. 15. 2. Emplear el traductor para extraer los datos de los actuadores correspondientes en el DT. 3. Escribir dichos valores sobre los coils de los actuadores del PLC correspondiente. Así, se podría dar la opción de que la escritura en los coils de inicialización de ambos PLC’s se realizara en la función Start. Se ha decidido aplicar esta otra manera para dar un mayor tiempo de preparación a los PLC’s, así como evitar el arranque de la planta en caso de que salte algún fallo en el primer frame de la simulación. También se consigue de esta manera que planta y simulación comience a actuar y tener movimiento a la vez. 15 En el caso de que ocurriera cuando la comunicación fuera PLC −→ DT no habría problema, pues el DT replicaría el estado desactivo de los actuadores en la realidad. 62 Capítulo 0. 6.6 Aplicación de la comunicación : Obtención de datos para futuro entrenamiento Una vez desarrollados los apartados anteriores, más allá de tener la posibilidad de replicar la realidad en la simulación, y viceversa, se desarrolló un nuevo apartado de programación orientado a la obtención de datos de la realidad, para poder ser aplicados sobre el Gemelo Digital. Por ello, el objetivo es demostrar las posibilidades de haber establecido canales de comunicación con los diversos PLC’s para, a través de diversas herramientas, poder obtener parámetros más exactos en la simulación. Dicho esto, el principal objetivo de este apartado ha sido obtener los datos de velocidades de las cintas. Para ello, se ha desarrollado todo un nuevo apartado denominado Training. A continuación, se procede a mostrar los distintos elementos que componen esta nueva parte. 6.7 Parámetros del entrenamiento Para la realización correcta de la rutina de entrenamiento, ha sido necesario definir un script que contenga todos los parámetros necesarios para su correcta realización. Para ello, y de manera análoga a lo establecido en el apartado 6.3, se ha creado la clase Training- Parameters, en la que se almacena dicha información. Se muestran los parámetros empleados por partes : Código 5 Parámetros de entrenamiento. 1public class TrainingParameters :MonoBehaviour 2{ 3//Training parameters 4 5static public float[] defaultVelocityOnBelts = {0.5f,1f,0.5f,1f}; //Default values of velocity established on the DT at the beginning 6static public float[] defaultVelocityOnBeltsInReality = {0f,0f,0f,0f}; //Default values of velocity established as real velocity of the belts at the beginning 7public const string FileVelPath =@".\Assets\Scripts\Training\JSONFiles\VelValues. json";//Path where the velocity value in the simulation is stored 8public const string FileVelRealityPath =@".\Assets\Scripts\Training\JSONFiles\ VelValuesReality.json";//Path where the velocity value in the reality is stored 9. 10 . 11 . En el código anterior se puede observar cómo se definen dos vectores de valores por defecto para las cintas tanto en la realidad como en la simulación, por si es necesario asignar tales valores. Tras ello, se definen unas direcciones dentro de directorio, donde se van a almacenar los valores tras un entrenamiento, y que serán consultados al inicio de cada simulación. Como se puede observar, dichos valores quedarán almacenados en archivos .JSON. Como se puede observar, este set de parámetros será fijo y el usuario no deberá modificar sus valores Continuando con los parámetros establecidos : Código 6 Parámetros a elección para el entrenamiento. 1. 2. 3. 4//Modificable parameters 5 6 Programación en el entorno del Gemelo Digital. 63 6static public bool enableTraining =true;//Boolean variable to activate or desactivate the algorythm to estimate the velocity from the belts in the reality and apply it on the DT 7static public bool resetTimeValues;//To indicate if the time values on the PLC should be reset 8static public int howManyLoops =4;//How many loops must the tray do to estimate the velocity of the belts 9. 10 . 11 . Así, el parámetro enableTraining nos sirve para habilitar o deshabilitar la rutina de entrenamiento. A su vez, el parámetro resetTimeValues es una variable booleana para indicar que se reseten los registros donde se almacenan los valores temporales que se van a emplear para la obtención de las velocidades. Y por último, el parámetro howManyLoops sirve para indicar cuantas vueltas va a tener que dar una bandeja al circuito completo para obtener un valor medio de velocidad en cada cinta. Continuando, por tanto, quedan ciertos parámetros estáticos, que serán los valores de las distancias entre los elementos que se van a emplear sobre cada cinta para medir las velocidades : Código 7 Parámetros de distancia. 1. 2. 3. 4//Distance values in the reality 5static public float top_distance = 0.30f;//Distance for the top conveyor,between S1 and S2 6static public float left_distance = 1.42f;//Distance for the left conveyor, between S3 and S4 7static public float bottom_distance = 0.32f;//Distance for the left conveyor, between S6 and S7 8static public float right_distance = 1.72f;//Distance for the right conveyor, between S8 and S9 9 10 //Distance in the DT 11 static public float top_distance_DT =Math.Abs((GameObject.Find("LongBeltConveyor0/ StopActuatorModule/InductiveSensor0").transform.position.x)-GameObject.Find(" LongBeltConveyor0/InductiveSensorModule/InductiveSensor2").transform.position.x); 12 static public float left_distance_DT =Math.Abs((GameObject.Find("ShortBeltConveyor1/ InductiveSensorModule/InductiveSensor1").transform.position.z)-GameObject.Find(" ShortBeltConveyor1/StopActuatorModule/InductiveSensor0").transform.position.z); 13 static public float bottom_distance_DT =Math.Abs((GameObject.Find("LongBeltConveyor1/ StopActuatorModule/InductiveSensor0").transform.position.x)-GameObject.Find(" LongBeltConveyor1/InductiveSensorModule/InductiveSensor2").transform.position.x); 14 static public float right_distance_DT =Math.Abs((GameObject.Find("ShortBeltConveyor0/ InductiveSensorModule/InductiveSensor1").transform.position.z)-GameObject.Find(" ShortBeltConveyor0/StopActuatorModule/InductiveSensor0").transform.position.z); 15 . 16 . 17 . Así, en el código anterior se distinguen dos grupos : 1. Valores de la planta física : Este primer grupo almacena los valores de distancia entre los sensores que se van a emplear en para tomar los tiempos de pasada y obtener las velocidades. Como se observa, dichos elementos serán los sensores S1 a S2, S3 a S4, S6 a S7, y S8 a S9. Para conocer mejor su ubicación, sirve de ayuda tener en cuenta la figura 18. 2. Valores del entorno digital : A su vez, el segundo grupo aglutina los valores de distancia entre dichos elementos dentro del Gemelo Digital. Para ello se debe extraer la posición del GameObject correspondiente a cada uno. Y por último, se da forma a una estructura para almacenar los valores temporales de los dos sensores que se van a emplear sobre cada cinta para medir las velocidades : 64 Capítulo 0. Código 8 Estructura para las cintas. 1. 2. 3. 4//Structure to be used by the training functions 5public struct BeltStruct 6{ 7//Struct to save the needed data for the training 8public string BeltName;//Name of the belt 9public int numberPreviousSensor;//Number of the previous sensor 10 public int numberRearSensor;//Number of the rear sensor 11 12 public SensorsTimeValue previousSensorTime;//To save the time value of the previous sensor 13 public SensorsTimeValue rearSensorTime;//To save the time value of the rear sensor 14 15 public BeltStruct(string Name,int numberFirstSensor,int numberSecondSensor) 16 { 17 //Constructor of the structure 18 BeltName =Name; 19 numberPreviousSensor =numberFirstSensor; 20 numberRearSensor =numberSecondSensor; 21 previousSensorTime =new SensorsTimeValue(numberFirstSensor); 22 rearSensorTime =new SensorsTimeValue(numberSecondSensor); 23 } 24 } 25 . 26 . 27 . Donde los parámetros de dicha estructura son : 1. Identificadores : Es el grupo compuesto por el nombre de la cinta, así como el número de los sensores en la planta que se van a emplear para la lectura de datos. 2. Tiempo del sensor previo : Es otra estructura para almacenar los valores de tiempo registrados por el sensor previo. Su forma se mostrará en el apartado 6.8. 3. Tiempo del sensor posterior : Estructura de la misma forma que el campo anterior, pero para el sensor posterior. Código 9 Estructura de tiempo para los sensores. 1public struct SensorsTimeValue 2{ 3//Structure for saving the time’s values of the different sensors. Create one structure for each sensor 4public int sensorNumber; 5public UInt16 MiliValue;//Value of milliseconds 6public UInt16 Sec16LeastSig;//Value of the 16 least significant bits for the seconds value 7public UInt16 Sec16MostSig;//Value of the 16 most significant bits for the seconds value 8 9public SensorsTimeValue(int numberOfTheSensor) 10 { 11 sensorNumber =numberOfTheSensor; 12 MiliValue = 0; 13 Sec16LeastSig = 0; 14 Sec16MostSig = 0; 15 } 16 } Como se observa, tal estructura almacenará los valores del número del sensor, así como el dato de tiempo que marcará. Se ha destacar que los datos temporales están medidos en formato POSIX desde el 1 de enero de 1980 a las 00:00, con una resolución de milisegundos, tal y como se observa. Además, el valor en segundos transcurridos se almacena en dos palabras de 16 bits. 6 Programación en el entorno del Gemelo Digital. 65 6.8 Namespace para el entrenamiento A su vez, de la misma manera que en los apartados 6.2 y 6.4, se ha creado un nuevo namespace a modo de "librería” para almacenar los elementos necesarios para poder realizar el entrenamiento de manera correcta. Así, este nuevo elemento está formado por : • Estructura temporal para los sensores : Es la estructura necesaria para almacenar los valores de tiempo que van a registrar cada uno de los sensores cuando la bandeja pase por ellos. Tiene la siguiente forma : Código 10 Estructura de tiempo para los sensores. 1public struct SensorsTimeValue 2{ 3//Structure for saving the time’s values of the different sensors. Create one structure for each sensor 4public int sensorNumber; 5public UInt16 MiliValue;//Value of milliseconds 6public UInt16 Sec16LeastSig;//Value of the 16 least significant bits for the seconds value 7public UInt16 Sec16MostSig;//Value of the 16 most significant bits for the seconds value 8 9public SensorsTimeValue(int numberOfTheSensor) 10 { 11 sensorNumber =numberOfTheSensor; 12 MiliValue = 0; 13 Sec16LeastSig = 0; 14 Sec16MostSig = 0; 15 } 16 } Como se observa, tal estructura almacenará los valores del número del sensor, así como el dato de tiempo que marcará. Se ha destacar que los datos temporales están medidos en formato POSIX desde el 1 de enero de 1980 a las 00:00, con una resolución de milisegundos(cuyo valor se almacena en una palabra de 16 bits), tal y como se observa. Además, el valor en segundos transcurridos se almacena en dos palabras de 16 bits. • Estructura para almacenar los valores de velocidad : Es la estructura necesaria para el correcto guardado de los valores de velocidad. Contiene la forma de los datos que van a albergar los archivos .JSON donde se almacenan de manera permanente los valores de los entrenamientos . Su forma es la siguiente : Código 11 Estructura de velocidades para los archivos .JSON. 1public struct VelValue 2{ 3//Class to handle the vel.data in the JSON files 4//Both of the following arrays contains the data. It’shape is : 5/* 6Element 0 --> Top belt 7Element 1 --> Left blet 8Element 2 --> Bottom belt 9Element 3 --> Right belt 10 */ 11 public float[] velocity;//Value of the average values of velocity in each belt. 12 public float[] desv;//Standard desviation of each one of the measures 13 public int num_loops;//Number of loop required to obtain the previous data 14 public VelValue(int num_elements)//The number of elements must be 4 in the case of the plant, due to there’s 4 belts 15 { 16 //Constructor of the class 17 velocity =new float[num_elements]; 66 Capítulo 0. 18 desv =new float[num_elements]; 19 num_loops = 0; 20 } 21 } Como resulta obvio, la función de cada elemento es : – public float[] velocity : Vector de flotantes que almacena los valores medios de velocidad de cada cinta. – public float[] desv : Vector de flotantes que almacena los valores de desviación estándar respecto a la medida anterior para cada cinta. – public int num_loops : Entero para almacenar el número de vueltas que ha tenido que dar la bandeja para que se extraigan los valores anteriores. • Clase para el almacenamiento : Es la clase que contiene los métodos necesarios para poder trabajar de manera correcta con los archivos .JSON. Se distinguen los siguientes métodos : 1. Inicialización de archivo : En caso de que el archivo no existe, lo crea sobre la dirección indicada y le administra unos valores por defecto que se reciben como entrada de la función. 2. Escritura del archivo : Método para escribir sobre el archivo indicado los valores que se les indique también por entrada. 3. Lectura del archivo : Método para extraer los datos de un archivo .JSON en la dirección indicada por parámetro de entrada. • Clase para la estimación de la velocidad : Es la clase que contiene las propiedades y métodos necesarios para el cálculo de las velocidades. Así, los métodos que contiene son : 1. Constructor : Inicializa las propiedades de la clase a falso, que son : Código 12 Estructura de velocidades para los archivos .JSON. 1private bool times_top_calculated;//Top velocity already calculated 2private bool times_left_calculated;//Left velocity already calculated 3private bool times_bottom_calculated;//Bottom velocity already calculated 4private bool times_right_calculated;//Right velocity already calculated 2. Estimación de la velocidad en el Gemelo Digital : Hace uso de los valores de tiempo de los sensores, así como de los valores de distancia entre los sensores tanto en la realidad como en la simulación. Así, el objetivo es obtener cuál debería ser la velocidad en la simulación(dado su escalado) respecto a la obtenida en la realidad. Para ello, se ha de cumplir que : Time_Between_Sensors_In_Simulation =Time_Between_Sensors_In_Reality16 (0.12) TimeBetweenSensorsInSimulation Conociendo el simple cálculo de la velocidad : Velocity =Distance/Time (0.13) Velocity Nos queda lo siguiente : Velocity_In_Simulation =Velocity_In_Reality ∗(Digital_Distance/Real_Distance) (0.14) VelocityInSimulation 16Se debe cumplir que se tarde lo mismo en recorrer la planta en la realidad que en la simulación. 6 Programación en el entorno del Gemelo Digital. 67 Así, se debe recalcar que dicha función devolverá una velocidad nula si alguno de los tiempos de ambos sensores es 0. Es decir, la bandeja todavía no ha pasado por ambos sensores. Dicho valor será considerado en la rutina de entrenamiento como que aún no se puede considerar dicha velocidad. 3. Estimación de la velocidad en la realidad : Hace uso de los valores de tiempo leídos de los sensores y de la distancia en la realidad obtenida para obtener la velocidad en la realidad de las cintas. El motivo de haber creado esta función vendrá explicado en el apartado 8. Para ello, se hace uso de la ecuación 0.13. Al igual que en la función anterior, devolverá un 0 en velocidad si alguno de los registros de tiempo de los sensores es 0. Así, la rutina de entrenamiento considerará dicho caso como que tal valor de velocidad no puede ser aún considerado. 68 Capítulo 0. 7 Realización del entrenamiento en el ámbito del Gemelo Digital La aplicación del namespace del entrenamiento se ha realizado siguiendo el siguiente pseudocódigo : Algorithm 2 Algoritmo de entrenamiento function Awake ▷Función ejecutada en el inicio Nada. end function function Start ▷Función ejecutada tras el inicio if Entrenamiento habilitado then Inicializar instancia de clase para la estimación de las velocidades. Número de loops realizados a 0. Inicialización de las estructuras tipo BeltStruct para las 4 cintas. end if end function function Update ▷Función ejecutada en cada frame if Entrenamiento habilitado then Estimación de las velocidades en la realidad. Estimación de las velocidades correspondientes en la simulación. if Todas las velocidades leídas en la realidad son distintas de 0 then ▷La bandeja ha pasado por todos los sensores en dicho loop Almacenar los valores de las velocidades en la realidad. Almacenar los valores de las velocidades correspondientes en la simulación. Incrementar en 1 el número de loops realizados. Resetear los registros de tiempo de los sensores en el Main PLC. end if end if if Número de loops realizados >= Parámetro de número de loops necesarios then ▷ Se considera que ya se ha completado el entrenamiento Obtención de valor medio y desviación típica de las velocidades en la realidad. Guardar los valores sobre el archivo .JSON correspondiente. Obtención de valor medio y desviación típica de las velocidades correspondientes en la simulación. Guardar los valores sobre el archivo .JSON correspondiente. Deshabilitar entrenamiento end if end function Observando el pseudocódigo anterior, vemos que en ningún momento se indica cuándo obtener los valores de tiempo de los registros asociados a los sensores. Esto es debido a que tal acción se realiza dentro de la rutina de comunicación, definida en el pseudocódigo 1. Es por tanto que a dicho algoritmo se añaden las siguientes acciones : Bibliografía [1] E.Negri (2017) : "A review of the roles of Digital Twin in CPS-based production systems".. Procedia Manufacturing. 11: 939–948. [2] M.Grieves : "Digital twin: Manufacturing excellence through virtualfactory replication.", 2014. [3] F. Tao, H. Zhang, A. Liu and A. Y. C. Nee, "Digital Twin in Industry: State-of-the-Art," in IEEE Transactions on Industrial Informatics, vol. 15, no. 4, pp. 2405-2415, April 2019, doi: 10.1109/TII.2018.2873186 - Pag 2. [4] A. Canedo, "Industrial IoT lifecycle via digital twins," 2016 International Conference on Hardware/Software Codesign and System Synthesis (CODES+ISSS), 2016, pp. 1-1. [5] H.Zhang, Q.Liu, X.Chen , D.Zhang y J.Leng, "A Digital Twin-Based Approach for Designing and Multi-Objective Optimization of Hollow Glass Production Line", in IEEE Special Section On Key Technologies For Smart Factory Of Industry 4.0, 2017. [6] DENIM Project, Coordinador MUNSTER TECHNOLOGICAL UNIVERSITY(1 Noviembre 2020-31 Octubre 2024). [7] MODBUS Protocol Specification (2012, 26 de abril). Modbus ORG. https://www.modbus.org/specs.php [8] MODBUS TCP/IP (2006, 24 de octubre). Modbus ORG. https://www.modbus.org/specs.php [9] M. Xue and C. Zhu, "The Socket Programming and Software Design for Communication Based on Client/Server", 2009 Pacific-Asia Conference on Circuits, Communications and Systems, 2009, pp. 775-777, doi: 10.1109/PACCS.2009.89. 75 Anexos 77 1 Valores obtenidos para el sensor contador de bandejas 79 1 Valores obtenidos para el sensor contador de bandejas Figura 43 Solución al estado de movimiento del pistón posterior. En esta figura se puede observar los valores obtenidos por el sensor contador de bandejas presente en el Stacker. En dicho gráfico se puede observar como el comportamiento de dicho sensor no es lineal. Sin embargo, con la correcta parametrización y filtrado, se ha conseguido que sea directamente el PLC quien estime el valor de las bandejas apiladas. Sin embargo, se presentaba el problema de que cuando el pistón principal del apilador se encontraba elevado, el valor devuelto por el sensor incrementaba ligeramente. Por lo tanto, podría interpretarse un número mayor de bandejas de las que realmente había almacenadas. Para evitar dicho problema, será el Sistema Virtual quién aparte de leer el valor de bandejas estimado por el Stacker PLC, detectará si dicho pistón se encuentra elevado o no. En tal caso, se muestra el siguiente mensaje : Figura 44 Valor devuelto por el Sist.Virtual. Así, en la imagen 44 se pueden observar las dos posibilidades que devuelve el Sistema Virtual cuando le sea pedido que realice la estimación del número de bandejas. El primer caso es cuando el pistón se encuentra elevado; y el segundo caso es cuando se encuentra en la posición inferior. 80 Capítulo . Bibliografía 2 Parámetros de comunicación Código 13 Parámetros de comunicación. 1public class CommunicationParametersModBus :MonoBehaviour 2{ 3//Parameters for the communication with the plant 4public const string ServerURL ="192.168.0.12";//IP direction of the main PLC 5public const string StackerURL ="192.168.0.15";//IP direction of the Stacker PLC 6public const int Port = 502; //Port of the TCP Connection 7 8public const byte slave_Address = 12; //Direction of the slave for the main PLC 9public const byte slaveStacker_Address = 15; //Direction of the slave for the Stacker PLC 10 11 //Internal parameters for the communication 12 public const bool CommunicationFromPLCToDT =false;//IF TRUE -> Communication from the PLC to the DT 13 //IF FALSE -> Communication from the DT to the PLC 14 public const Int32 max_time = 500; //Timeout, in ms --> In the case a non-blocking wait it’ s no t wanted, use -1 15 // If this value is lower than 500 ms, will be set directly to 500 ms 16 17 18 19 //Parameters of the main PLC 20 public const int NumberOfSensors = 19; //Number of sensor in the plant 21 public const int StartingDirectionOfTheSensors = 0; //Offset direction of the sensor values in the Main PLC’s memory 22 public const int NumberOfActuators =8;//Number of actuators in the plant 23 public const int StartingDirectionOfTheActuators = 20; //Offset direction of the actuator values in the Main PLC’s memory 24 public const int StartingDirectionOfTheVirtualActuators = 150; //Offset direction to write the actuator values from the DT on the Main PLC 25 public const int NumberOfConveyors =4;//Number of conveyors in the plant 26 public const int StartingDirectionOfTheConveyors = 33; //Offset direction of the conveyor values in the Main PLC’s memory 27 public const int StartingDirectionOfTimersWordValuesMainPLC =0;//Offset direction of the word values where is stored the time values from the sensors 28 public const int NumberOfTimersWordsMainPLC = 24; //Number of words to read of the time values from the sensors 29 30 //Parameters of the Stacker PLC 31 public const int NumberOfActuatorsStacker =6;//Number of actuators in the Stacker 32 public const int NumberOfSensorsStacker = 11; //Number of sensors in the Stacker 33 public const int StartingDirectionOfTheActuatorsStacker = 20; //Offset direction of the actuator’s values in the Stacker PLC’s memory 34 public const int StartingDirectionOfTheSensorsStacker = 1; //Offset direction of the sensor ’s values in the Stacker PLC’s memory 35 public const int NumberOfWordsValueStackerPLC = 1; //Number of words values in the Stacker PLC’s memory 36 public const int StartingDirectionOfTheVirtualActuatorsStacker = 150; //Offset direction to write the actuator values from the DT on the Stacker PLC 37 38 //Parameters of the counter sensor in the Stacker PLC 39 static public UInt16[] BoxesAverageValues =new UInt16[] {3,253,981,1345,1793,2483,3101,3657,3949}; //Average values returned by the sensor to count the numbr of boxes stored in the Stacker jail 40 static public UInt16[] BoxesDesvValues =new UInt16[] {1,5,15,14,16,13,21,13,18}; // Standard desviation for each one of the values in the previous array 41 public const int DirectionOfTheCounterSensor = 5; //Direction where the sensor value is stored in the PLC 42 public const int NumberOfValuesToReadInTheSensor = 5; //Number of values to apply the filter 43 44 //Parameters to measeure the delay in the communications 45 static public bool CheckDelay =true; 2 Parámetros de comunicación 81 46 static public bool CheckDelayCoilsMainPLC =true;//Boolean to indicate if measure the delay in the operation with the coils in the Main PLC 47 static public bool CheckDelayWordsMainPLC =true;//Boolean to indicate if measure the delay in the operation with the words in the Main PLC 48 static public bool CheckDelayCoilsStackerPLC =true;//Boolean to indicate if measure the delay in the operation with the coils in the Stacker PLC 49 static public bool CheckDelayWordsStackerPLC =true;//Boolean to indicate if measure the delay in the operation with the words in the Stacker PLC 50 public const string DelayCoilsMainPLCPath =@".\Assets\Scripts\Communication\JSONFiles\ DelayCoilsMainPLC.json";//Path where the delay values in the simulation is stored 51 public const string DelayWordsMainPLCPath =@".\Assets\Scripts\Communication\JSONFiles\ DelayWordsMainPLC.json";//Path where the delay values in the simulation is stored 52 public const string DelayCoilsStackerPLCPath =@".\Assets\Scripts\Communication\JSONFiles\ DelayCoilsStackerPLC.json";//Path where the delay values in the simulation is stored 53 public const string DelayWordsStackerPLCPath =@".\Assets\Scripts\Communication\JSONFiles\ DelayWordsStackerPLC.json";//Path where the delay values in the simulation is stored 54 public const int NumberOfCommunicationCyclesToDo = 30; //Number of communication interactions to check the delay 55 56 57 } 82 Capítulo . Bibliografía 3 Librería de comunicación Código 14 Librería de comunicación. 1using System; 2using System.Net; 3using System.Net.Sockets; 4using UnityEngine; 5 6 7namespace TCPSokets //With the scope to create our own library 8{ 9public struct DiscreteCoilsValues //Structure with the values of the discrete coils that have been read 10 { 11 public int startingDirection;//PLC’s direction of the first logic value in the following array 12 public bool[] arrayCoilsValues;//Boolean array with the values of the registers 13 public DiscreteCoilsValues(int startingDirectionValue,bool[] arrayCoilsValuesValue) 14 { 15 //Constructor of the structure 16 startingDirection =startingDirectionValue; 17 arrayCoilsValues =arrayCoilsValuesValue; 18 } 19 } 20 21 public struct WordCoilsValues //Structure with the values of the holding registers that have been read 22 { 23 public int startingDirection;//PLC’s direction of the first word value in the following array 24 public UInt16[] arrayWordsValues;//UInt16 array with the values of the registers 25 public WordCoilsValues(int startingDirectionValue,UInt16[] arrayWordsValuesValue) 26 { 27 //Constructor of the structure 28 startingDirection =startingDirectionValue; 29 arrayWordsValues =arrayWordsValuesValue; 30 } 31 } 32 public class ClientTCPClass //Main class 33 { 34 private string PLCDirection;//IP Address of the PLC, as string 35 private int Port;//Communication Port 36 private IPAddress PLCIP;//IP Address of the PLC, as IPAddress object 37 private IPEndPoint EndPoint;//IP Endpoint 38 private Int32 TimeOut;//Timeout to send and receive 39 private Socket s;//Socket fot the communication 40 41 42 public ClientTCPClass(string direction_in_string,int PortValue,int TimeOutValue) 43 { 44 //Constructor of the class 45 this.PLCDirection =direction_in_string; 46 this.Port =PortValue; 47 this.PLCIP =IPAddress.Parse(this.PLCDirection); 48 this.EndPoint =new IPEndPoint(this.PLCIP,this.Port); 49 this.TimeOut =TimeOutValue; 50 } 51 52 public bool SocketCreation() 53 { 54 //Function to create the socket 55 Socket tempSocket =new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); //Auxiliar socket to establish the communication 56 //AddressFamily.InterNetwork --> For IPv4 addressing 57 //SocketType.Stream --> Type of the socket. Enables communication in both directions . Needed for the communication in TCP 58 //ProtocolType.TCP --> TCP protocol use 59 3 Librería de comunicación 83 60 try 61 { 62 tempSocket.Connect(this.EndPoint); //Connection to the endpoint 63 64 } 65 catch(SocketException e) 66 { 67 //In case of error trying to connect 68 Debug.LogError(e.Message +" Error code: " +e.ErrorCode); 69 } 70 if (tempSocket.Connected) 71 { 72 //In the case the connection was done 73 s=tempSocket; 74 s.ReceiveBufferSize = 256; //256 as RX buffer size, which is the max.length of a TCP message 75 s.SendBufferSize = 256; //256 as TX buffer size, which is the max.length of a TCP message 76 s.ReceiveTimeout =this.TimeOut; 77 s.SendTimeout =this.TimeOut; 78 return true; 79 } 80 else 81 { 82 Debug.LogError("Error during the TCP connection"); 83 return false; 84 } 85 } 86 87 public void SocketSendDiscreteCoils(byte slave_Address,UInt16 offSetDirection,UInt16 numberOfRegisters) 88 { 89 //Function to send the needed data to read from the discrete coils 90 //--------------------------------------------------// 91 //Construction of the message 92 byte[] startingDirection =BitConverter.GetBytes(offSetDirection); 93 byte[] numRegister =BitConverter.GetBytes(numberOfRegisters); 94 byte[] message =new byte[12]; 95 message[0] = 0; //Higher byte for the transaction ID 96 message[1] = 0; //Lower byte for the transaction ID 97 message[2] = 0; //Higher byte for the protocol ID 98 message[3] = 0; //Lower byte for the protocol ID --> Always 0 fot the TCP Modbus Ethernet 99 message[4] = 0; //Higher byte for the remaining length of the message, in bytes 100 message[5] = 6; //Lower byte for the remaining length of the message, in bytes 101 message[6] = slave_Address;//Byte to indicate the slave’s address 102 message[7] = 1; //Byte to indicate the function -> Read from discrete coils 103 message[8] = startingDirection[1]; //Higher byte for the offset direction of the coils that want to be read 104 message[9] = startingDirection[0]; //Lower byte for the offset direction of the coils that want to be read 105 message[10] = numRegister[1]; //Higher byte to indicate the number of registers we want to read 106 message[11] = numRegister[0]; //Lower byte to indicate the number of registers we want to read 107 //--------------------------------------------------// 108 //Send the message 109 try 110 { 111 this.s.Send(message,message.Length,SocketFlags.None); 112 } 113 catch (SocketException e) 114 { 115 //In case of overtiming 116 Debug.LogError(e.Message +" Error code: " +e.ErrorCode); 117 } 118 } 119 120 public void SocketSendWordCoils(byte slave_Address,UInt16 offSetDirection,UInt16 numberOfRegisters) 121 { 84 Capítulo . Bibliografía 122 //Function to send the needed data to read from the word coils 123 //--------------------------------------------------// 124 //Construction of the message 125 byte[] startingDirection =BitConverter.GetBytes(offSetDirection); 126 byte[] numRegister =BitConverter.GetBytes(numberOfRegisters); 127 byte[] message =new byte[12]; 128 message[0] = 0; //Higher byte for the transaction ID 129 message[1] = 0; //Lower byte for the transaction ID 130 message[2] = 0; //Higher byte for the protocol ID 131 message[3] = 0; //Lower byte for the protocol ID --> Always 0 fot the TCP Modbus Ethernet 132 message[4] = 0; //Higher byte for the remaining length of the message, in bytes 133 message[5] = 6; //Lower byte for the remaining length of the message, in bytes 134 message[6] = slave_Address;//Byte to indicate the slave’s address 135 message[7] = 3; //Byte to indicate the function -> Read from discrete coils 136 message[8] = startingDirection[1]; //Higher byte for the offset direction of the coils that want to be read 137 message[9] = startingDirection[0]; //Lower byte for the offset direction of the coils that want to be read 138 message[10] = numRegister[1]; //Higher byte to indicate the number of registers we want to read 139 message[11] = numRegister[0]; //Lower byte to indicate the number of registers we want to read 140 //--------------------------------------------------// 141 //Send the message 142 try 143 { 144 this.s.Send(message,message.Length,SocketFlags.None); 145 } 146 catch (SocketException e) 147 { 148 //In case of overtiming 149 Debug.LogError(e.Message+" Error code: " +e.ErrorCode); 150 } 151 152 } 153 154 public bool[] ReadRxBufferDiscreteCoils() 155 { 156 //Function to read from the RX buffer, intended for the discrete coils 157 byte[] RXXData =new byte[255]; //Data to be stored 158 try 159 { 160 this.s.Receive(RXXData,RXXData.Length,SocketFlags.None); 161 } 162 catch (SocketException e) 163 { 164 //In case of overtiming 165 Debug.LogError(e.Message +" Error code: " +e.ErrorCode); 166 } 167 168 if(RXXData[7] == Convert.ToByte(0x01+0x80)) 169 { 170 //In the case of error response 171 Debug.LogError("Error in the response, executing function 1"); 172 switch(RXXData[8]) 173 { 174 case 0x01: 175 Debug.LogError("Function code not supported"); 176 break; 177 case 0x02: 178 Debug.LogError("Starting address not supported or starting address+ quantity of registers not supported"); 179 break; 180 case 0x03: 181 Debug.LogError("Quantity of outputs not supported"); 182 break; 183 case 0x04: 184 Debug.LogError("Cannot read the values"); 185 break; 186 default: 4 Algoritmo de comunicación en el entorno del Gemelo Digital 91 31 TranslationStackerPLCToDT translatorPLCStackerToDT;// Class for the application from the PLC Stacker value’s to the DT 32 TranslationDTToStackerPLC translatorDTToPLCStacker;// Class for the application from the DT’s values to the PLC Stacker 33 DiscreteCoilsValues SensorsValues;//Structure to storage the value of the sensors 34 DiscreteCoilsValues ActuatorsValues;//Structure to store the value of the actuators 35 DiscreteCoilsValues CornerPistonsValues;//Structure to store the value of the corner actuators 36 DiscreteCoilsValues BeltsValues;//Structure to store the value of the belts 37 DiscreteCoilsValues StackerActuatorsValues;//Structure to store the value of the Stacker actuators 38 DiscreteCoilsValues StackerSensorsValues;//Structure to store the value of the Stacker sensors 39 DiscreteCoilsValues MainPLCButtomEmergency;//Structure to store the value of the buttom emergency in the Main PLC 40 DiscreteCoilsValues StackerPLCButtomEmergency;//Structure to store the value of the buttom emergency in the Stacker PLC 41 42 public WordCoilsValues TimeValues;//Value of the %MW words where the time Values are saved 43 44 //Variable for the sensor used to count the number of boxes stored inside the Stacker jail 45 WordCoilsValues CounterValue;//Value of the %MW word where is stored the sensor value 46 UInt16 UpperLimit;//Auxiliar variable to establish the upper limit to be evaluated 47 UInt16 LowerLimit;//Auxiliar variable to establish the lower limit to be evaluated 48 int NumberOfBoxes =0;//Number of boxes stored in the Stacker jail 49 UInt16[] ArrayOfSensorsValue =new UInt16[CommunicationParametersModBus. NumberOfValuesToReadInTheSensor]; //Array to store the values returned by the sensor 50 UInt16 AverageValueSensor;//Average value of the previous array components 51 UInt16 DistanceToTheClosestAverageValue;//In the case the AverageValueSensor it’s not inside of any admitted range 52 //It’s used this variable to estimate the closest admitted region 53 //Variables to get the time difference when it’s necessary 54 TimeSpan DateTime1;//Time structure to store the time before the message is sent 55 TimeSpan DateTime2;//Time structure to store the time after the message is sent 56 //Arrays of values for the delay estimated 57 double[] TimeDelayCoilsMainPLC =new double[CommunicationParametersModBus. NumberOfCommunicationCyclesToDo]; 58 double[] TimeDelayWordsMainPLC =new double[CommunicationParametersModBus. NumberOfCommunicationCyclesToDo]; 59 double[] TimeDelayCoilsStackerPLC =new double[CommunicationParametersModBus. NumberOfCommunicationCyclesToDo]; 60 double[] TimeDelayWordsStackerPLC =new double[CommunicationParametersModBus. NumberOfCommunicationCyclesToDo]; 61 int CountCommunicationLoops =0;//Counter to store the number of loop done in the process to estimate the delay in the communications 62 DelayValue CoilsMainPLCObject ;//Object to store the delay information for the coils in the Main PLC 63 DelayValue WordsMainPLCObject ;//Object to store the delay information for the words in the Main PLC 64 DelayValue CoilsStackerPLCObject ;//Object to store the delay information for the coils in the Stacker PLC 65 DelayValue WordsStackerPLCObject ;//Object to store the delay information for the words in the Stacker PLC 66 List<DelayValue>CoilsMainPLCObjectList =new List<DelayValue>{}; //List to write on the JSON file the coils delay information in the Main PLC 67 List<DelayValue>WordsMainPLCObjectList =new List<DelayValue>{}; //List to write on the JSON file the coils delay information in the Main PLC 68 List<DelayValue>CoilsStackerPLCObjectList =new List<DelayValue>{}; //List to write on the JSON file the coils delay information in the Main PLC 69 List<DelayValue>WordsStackerPLCObjectList =new List<DelayValue>{}; //List to write on the JSON file the coils delay information in the Main PLC 70 void Awake() 71 { 72 73 try 74 { 75 //Start the class for the main PLC communication 76 client =new ClientTCPClass(CommunicationParametersModBus.ServerURL, CommunicationParametersModBus.Port,CommunicationParametersModBus.max_time); 77 92 Capítulo . Bibliografía 78 if(client.SocketCreation()) 79 { 80 Debug.Log("Connection established successfully with the main PLC"); 81 if(CommunicationParametersModBus.CommunicationFromPLCToDT) 82 { 83 translatorPLCToDT =new TranslationPLCToDT(); //Initialization the class to translate the PLC’s values to the DT 84 } 85 else 86 { 87 translatorDTToPLC =new TranslationDTToPLC(); //Initialization the class to translate the DT’s values to the PLC 88 } 89 } 90 else 91 { 92 Debug.LogError("The connection had not been established"); 93 } 94 } 95 catch(Exception ex) 96 { 97 Debug.LogError("Connection error in the TCP initialization : " +ex.ToString()); 98 } 99 100 try 101 { 102 //Start the class for the Stacker PLC communication 103 client_Stacker =new ClientTCPClass(CommunicationParametersModBus.StackerURL, CommunicationParametersModBus.Port,CommunicationParametersModBus.max_time); 104 105 if(client_Stacker.SocketCreation()) 106 { 107 Debug.Log("Connection established successfully with the Stacker PLC"); 108 if(CommunicationParametersModBus.CommunicationFromPLCToDT) 109 { 110 translatorPLCStackerToDT =new TranslationStackerPLCToDT(); //Initialization the class to translate the PLC’s Stacker values to the DT 111 } 112 else 113 { 114 translatorDTToPLCStacker =new TranslationDTToStackerPLC(); //Initialization the class to translate the DT’s values to the PLC Stacker 115 } 116 } 117 } 118 catch(Exception ex) 119 { 120 Debug.LogError("Connection error in the TCP initialization : " +ex.ToString()); 121 } 122 123 124 125 TrainingParameters.resetTimeValues =false;//Reset that coil on the PLC 126 127 128 } 129 130 void Start() 131 { 132 if(CommunicationParametersModBus.CheckDelay) 133 { 134 //Init the delay in the communication files 135 string CommunicationType;//Direction of the communication 136 if(CommunicationParametersModBus.CommunicationFromPLCToDT) 137 { 138 CommunicationType ="PLCToDT"; 139 } 140 else 141 { 142 CommunicationType ="DTToPLC"; 143 } 4 Algoritmo de comunicación en el entorno del Gemelo Digital 93 144 145 if(CommunicationParametersModBus.CheckDelayCoilsMainPLC) 146 { 147 //If the check with the deay with the coils in the Main PLC is activated 148 //Init the JSON file 149 StoreDelay.InitDelayFile(CommunicationParametersModBus.DelayCoilsMainPLCPath," Coils",CommunicationParametersModBus.NumberOfActuators, CommunicationParametersModBus.NumberOfCommunicationCyclesToDo, CommunicationType); 150 //Init the structure 151 CoilsMainPLCObject =new DelayValue("Coils",CommunicationParametersModBus. NumberOfActuators,CommunicationParametersModBus. NumberOfCommunicationCyclesToDo,CommunicationType); 152 } 153 154 if(CommunicationParametersModBus.CheckDelayWordsMainPLC && TrainingParameters. enableTraining) 155 { 156 //If the check with the deay with the words in the Main PLC is activated and the training to read the word values is activated 157 //Init the JSON file 158 StoreDelay.InitDelayFile(CommunicationParametersModBus.DelayWordsMainPLCPath," Words",CommunicationParametersModBus.NumberOfTimersWordsMainPLC, CommunicationParametersModBus.NumberOfCommunicationCyclesToDo, CommunicationType); 159 //Init the structure 160 WordsMainPLCObject =new DelayValue("Words",CommunicationParametersModBus. NumberOfTimersWordsMainPLC,CommunicationParametersModBus. NumberOfCommunicationCyclesToDo,CommunicationType); 161 } 162 163 if(CommunicationParametersModBus.CheckDelayCoilsStackerPLC) 164 { 165 //If the check with the deay with the coils in the Stacker PLC is activated 166 //Init the JSON file 167 StoreDelay.InitDelayFile(CommunicationParametersModBus.DelayCoilsStackerPLCPath, "Coils",CommunicationParametersModBus.NumberOfActuatorsStacker, CommunicationParametersModBus.NumberOfCommunicationCyclesToDo, CommunicationType); 168 //Init the structure 169 CoilsStackerPLCObject =new DelayValue("Coils",CommunicationParametersModBus. NumberOfActuatorsStacker,CommunicationParametersModBus. NumberOfCommunicationCyclesToDo,CommunicationType); 170 } 171 172 if(CommunicationParametersModBus.CheckDelayWordsStackerPLC) 173 { 174 //If the check with the deay with the words in the Stacker PLC is activated 175 //Init the JSON file 176 StoreDelay.InitDelayFile(CommunicationParametersModBus.DelayWordsStackerPLCPath, "Words",CommunicationParametersModBus.NumberOfWordsValueStackerPLC, CommunicationParametersModBus.NumberOfCommunicationCyclesToDo, CommunicationType); 177 //Init the structure 178 WordsStackerPLCObject =new DelayValue("Words",CommunicationParametersModBus. NumberOfWordsValueStackerPLC,CommunicationParametersModBus. NumberOfCommunicationCyclesToDo,CommunicationType); 179 } 180 } 181 182 183 } 184 185 186 void Update() 187 { 188 //Read the sensors values 189 if(!first_loop_done) 190 { 191 first_loop_done =true;//For the next loop 192 //In the case it’s the first loop 94 Capítulo . Bibliografía 193 if(CommunicationParametersModBus.CommunicationFromPLCToDT) 194 { 195 //In the case the communication is from the PLC to the DT 196 197 //For the main PLC 198 UInt16 start_direction = 500; //Direction coil to indicate to the PLC’s to start their external automation 199 UInt16 air_direction = 19; 200 UInt16 simulation_direction = 501; 201 bool[] array =new bool[1]; 202 array[0] = true; 203 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, start_direction,array); //Write true on the start coil 204 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, air_direction,array); //Write true in the air coil 205 array[0] = false; 206 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, simulation_direction,array); //Write false on the simulation coil 207 208 //For the Stacker PLC 209 client_Stacker.WriteDiscreteCoils(CommunicationParametersModBus. slaveStacker_Address,simulation_direction,array); //Write false on the simulation coil 210 211 } 212 else 213 { 214 //In the case the communication is from the DT to the PLC 215 216 //For the main PLC 217 UInt16 start_direction = 500; 218 UInt16 air_direction = 19; 219 UInt16 simulation_direction = 501; 220 UInt16 rearm_direction = 156; 221 bool[] array =new bool[1]; 222 array[0] = true; 223 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, start_direction,array); //Write true on the start coil 224 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, air_direction,array); //Write true on the air coil 225 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address, simulation_direction,array); //Write true on the simulation 226 227 //For the Stacker PLC 228 client_Stacker.WriteDiscreteCoils(CommunicationParametersModBus. slaveStacker_Address,simulation_direction,array); //Write true on the simulation coil 229 client_Stacker.WriteDiscreteCoils(CommunicationParametersModBus. slaveStacker_Address,rearm_direction,array); //Write true on the rearm coil 230 231 } 232 233 234 } 235 else 236 { 237 //In the case the PLC is already activated 238 239 if(CommunicationParametersModBus.CommunicationFromPLCToDT) 240 { 241 //In the case the communication is from the PLC to the DT 242 243 //For the main PLC 244 //Read the sensors values 245 SensorsValues =client.ReadValuesDiscreteCoils(CommunicationParametersModBus. slave_Address,Convert.ToUInt16(CommunicationParametersModBus. StartingDirectionOfTheSensors), Convert.ToUInt16( CommunicationParametersModBus.NumberOfSensors)); 246 4 Algoritmo de comunicación en el entorno del Gemelo Digital 95 247 //In the case the algorith to measure the delay in the communications has been activated 248 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsMainPLC) 249 { 250 DateTime1 =DateTime.Now.TimeOfDay; 251 } 252 //Read the value of the pistons 253 ActuatorsValues =client.ReadValuesDiscreteCoils(CommunicationParametersModBus. slave_Address,Convert.ToUInt16(CommunicationParametersModBus. StartingDirectionOfTheActuators), Convert.ToUInt16( CommunicationParametersModBus.NumberOfActuators)); //Extract the actuators values 254 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsMainPLC) 255 { 256 DateTime2 =DateTime.Now.TimeOfDay; 257 TimeDelayCoilsMainPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 258 } 259 260 //Read the value of the belts 261 BeltsValues =client.ReadValuesDiscreteCoils(CommunicationParametersModBus. slave_Address,Convert.ToUInt16(CommunicationParametersModBus. StartingDirectionOfTheConveyors), Convert.ToUInt16( CommunicationParametersModBus.NumberOfConveyors)); //Extract the belts values 262 //Apply the vavlue of the actuators on the DT 263 translatorPLCToDT.ApplyBeltsFromPLCToDT(BeltsValues); 264 translatorPLCToDT.ApplyPistonsFromPLCtoDT(ActuatorsValues); 265 translatorPLCToDT.ApplyCornerBeltsFromPLCToDT(ActuatorsValues); 266 267 if(TrainingParameters.enableTraining) 268 { 269 //If the training has been enabled 270 271 //Extract the data from the PLC 272 //In the case the algorith to measure the delay in the communications has been activated 273 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsMainPLC) 274 { 275 DateTime1 =DateTime.Now.TimeOfDay; 276 } 277 //Read the value of the timers in the Main PLC 278 TimeValues =client.ReadValuesWordCoils(CommunicationParametersModBus. slave_Address,Convert.ToByte(CommunicationParametersModBus. StartingDirectionOfTimersWordValuesMainPLC), Convert.ToByte( CommunicationParametersModBus.NumberOfTimersWordsMainPLC)); 279 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsMainPLC) 280 { 281 DateTime2 =DateTime.Now.TimeOfDay; 282 TimeDelayWordsMainPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 283 } 284 //Write it on the Belt Values, to be read after by the Training File 285 int cont = 0; 286 for(int i=0;i<4;i++) 287 { 288 Automation.Belts[i].previousSensorTime.MiliValue =TimeValues. arrayWordsValues[cont]; 289 cont++; 290 Automation.Belts[i].previousSensorTime.Sec16LeastSig =TimeValues. arrayWordsValues[cont]; 291 cont++; 292 Automation.Belts[i].previousSensorTime.Sec16MostSig =TimeValues. arrayWordsValues[cont]; 293 cont++; 294 96 Capítulo . Bibliografía 295 Automation.Belts[i].rearSensorTime.MiliValue =TimeValues. arrayWordsValues[cont]; 296 cont++; 297 Automation.Belts[i].rearSensorTime.Sec16LeastSig =TimeValues. arrayWordsValues[cont]; 298 cont++; 299 Automation.Belts[i].rearSensorTime.Sec16MostSig =TimeValues. arrayWordsValues[cont]; 300 cont++; 301 } 302 } 303 304 //For the Stacker PLC 305 //In the case the algorith to measure the delay in the communications has been activated 306 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsStackerPLC) 307 { 308 DateTime1 =DateTime.Now.TimeOfDay; 309 } 310 //Read the value of the actuators 311 StackerActuatorsValues =client_Stacker.ReadValuesDiscreteCoils( CommunicationParametersModBus.slaveStacker_Address,Convert.ToUInt16( CommunicationParametersModBus.StartingDirectionOfTheActuatorsStacker), Convert.ToUInt16(CommunicationParametersModBus.NumberOfActuatorsStacker)); 312 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsStackerPLC) 313 { 314 DateTime2 =DateTime.Now.TimeOfDay; 315 TimeDelayCoilsStackerPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 316 } 317 //Read the value of the sensors 318 StackerSensorsValues =client_Stacker.ReadValuesDiscreteCoils( CommunicationParametersModBus.slaveStacker_Address,Convert.ToUInt16( CommunicationParametersModBus.StartingDirectionOfTheSensorsStacker), Convert.ToUInt16(CommunicationParametersModBus.NumberOfSensorsStacker)); 319 translatorPLCStackerToDT.ApplyActuartorsFromPLCStackerToDT( StackerActuatorsValues); 320 321 322 323 //Extract the value of the counter in the Stacker jail to get number of boxes stored 324 //In the case the process to estimete the delay has been selected 325 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsStackerPLC) 326 { 327 DateTime1 =DateTime.Now.TimeOfDay; 328 } 329 //Execute the communication, but the valye it’s not stored at any significant direction in the Stacker PLC 330 client_Stacker.ReadValuesWordCoils(CommunicationParametersModBus. slaveStacker_Address,Convert.ToUInt16(CommunicationParametersModBus. DirectionOfTheCounterSensor), Convert.ToUInt16(1)); 331 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsStackerPLC) 332 { 333 DateTime2 =DateTime.Now.TimeOfDay; 334 TimeDelayWordsStackerPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 335 } 336 337 if(Input.GetKeyUp("c")) 338 { 339 //In the case is activated by the user 340 if(StackerSensorsValues.arrayCoilsValues[7] == true && StackerSensorsValues. arrayCoilsValues[6] == false) 341 { 342 //In the case the key "c" is pressed and released, and the main piston is down 4 Algoritmo de comunicación en el entorno del Gemelo Digital 97 343 Debug.Log("Starting the measure of the number of boxes stored in the Stacker"); 344 for(int i=0;i<CommunicationParametersModBus. NumberOfValuesToReadInTheSensor;i++) 345 { 346 //Extract the value from the Stacker PLC 347 CounterValue =client_Stacker.ReadValuesWordCoils( CommunicationParametersModBus.slaveStacker_Address,Convert. ToUInt16(CommunicationParametersModBus. DirectionOfTheCounterSensor), Convert.ToUInt16(1)); 348 //Store it in the array 349 ArrayOfSensorsValue[i] = CounterValue.arrayWordsValues[0]; 350 } 351 352 //Once the array has been fullfilled 353 AverageValueSensor = 0; //Reset the average value 354 for(int i=0;i<CommunicationParametersModBus. NumberOfValuesToReadInTheSensor;i++) 355 { 356 AverageValueSensor += ArrayOfSensorsValue[i]; 357 } 358 AverageValueSensor =Convert.ToUInt16(AverageValueSensor/ ArrayOfSensorsValue.GetLength(0)); //Extract the average value 359 360 //Once this value it’s calculated, it’s time to see in which zone it’s, and therefore, which is the number of boxes estimated 361 NumberOfBoxes = -1; //It’s supposed there’s an error if the value is equal to -1 362 for(int i=0;i<CommunicationParametersModBus.BoxesAverageValues.GetLength (0);i++) 363 { 364 UpperLimit =Convert.ToUInt16(CommunicationParametersModBus. BoxesAverageValues[i]+(2*CommunicationParametersModBus. BoxesDesvValues[i])); 365 LowerLimit =Convert.ToUInt16(CommunicationParametersModBus. BoxesAverageValues[i]-(2*CommunicationParametersModBus. BoxesDesvValues[i])); 366 if(AverageValueSensor >= LowerLimit && AverageValueSensor <= UpperLimit) 367 { 368 NumberOfBoxes =i; 369 break;//To stop the for loop once the average value it’s admitted 370 } 371 } 372 373 if(NumberOfBoxes == -1) 374 { 375 //In the case the value wasn’t admitted for any range 376 Debug.LogError("The sensor average value has not been admitted."); 377 //In the case the value it’s not inside of any range, it’s assumed that the value of boxes stored is the closest average value 378 for(int i=0;i<CommunicationParametersModBus.BoxesAverageValues. GetLength(0);i++) 379 { 380 if(i==0) 381 { 382 DistanceToTheClosestAverageValue =Convert.ToUInt16(Math.Abs( AverageValueSensor -CommunicationParametersModBus. BoxesAverageValues[i])); 383 NumberOfBoxes =i; 384 } 385 else 386 { 387 if(DistanceToTheClosestAverageValue >Convert.ToUInt16(Math. Abs(AverageValueSensor -CommunicationParametersModBus. BoxesAverageValues[i]))) 388 { 389 //If the "i" index is closest to the average value 390 //Update the distance to the closest element 98 Capítulo . Bibliografía 391 DistanceToTheClosestAverageValue =Convert.ToUInt16(Math. Abs(AverageValueSensor -CommunicationParametersModBus. BoxesAverageValues[i])); 392 //Update the closest index, therefore, the closest value to the number of boxes stored 393 NumberOfBoxes =i; 394 } 395 } 396 } 397 //Once has been selected the closest value 398 Debug.Log("The closest value admitted is : "+NumberOfBoxes+" boxes."); 399 } 400 else 401 { 402 //In the case the value was admitted inside one range 403 Debug.Log("The number of boxes stored is : "+NumberOfBoxes); 404 } 405 } 406 else 407 { 408 //In the case the main piston is still up 409 Debug.LogError("It’s not possible to estimate the number os boxes now, the main piston is currently up"); 410 } 411 412 } 413 414 } 415 else 416 { 417 //In the case the communication is from the DT to the PLC 418 419 //In the case there’s an emergency, notify it in the DT with a message. The actuators will be stopped in the reality, so their value will be desactivated in the simulation 420 MainPLCButtomEmergency =client.ReadValuesDiscreteCoils( CommunicationParametersModBus.slave_Address,Convert.ToUInt16( CommunicationParametersModBus.StartingDirectionOfTheSensors), Convert. ToUInt16(1)); 421 if(MainPLCButtomEmergency.arrayCoilsValues[0] == false) 422 { 423 //The emergency buttom has been pushed 424 Debug.LogWarning("The emergency buttom in the Main PLC has been pushed"); 425 translatorDTToPLC.ResetValuesInEmergency(); //Reset all the values in the plant in the DT 426 } 427 428 //For the main PLC 429 430 DiscreteCoilsValues beltsValuesOnDT =translatorDTToPLC.ApplyBeltsFromDTToPLC(); //Extract the belt values from the DT 431 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(CommunicationParametersModBus.StartingDirectionOfTheConveyors), beltsValuesOnDT.arrayCoilsValues); 432 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(158), beltsValuesOnDT.arrayCoilsValues); 433 434 DiscreteCoilsValues actuatorValuesOnDT =translatorDTToPLC. ApplyPistonsFromDTToPLC(); 435 436 //Write on the direction where the virtual actuators values are stored, in the main PLC 437 //If the algorithm to check the dealy in the communications has been activated 438 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsMainPLC) 439 { 440 DateTime1 =DateTime.Now.TimeOfDay; 441 } 442 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(CommunicationParametersModBus. 4 Algoritmo de comunicación en el entorno del Gemelo Digital 99 StartingDirectionOfTheVirtualActuators), actuatorValuesOnDT. arrayCoilsValues); 443 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsMainPLC) 444 { 445 DateTime2 =DateTime.Now.TimeOfDay; 446 TimeDelayCoilsMainPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 447 } 448 449 //If the algorithm to check the dealy in the communications has been activated 450 UInt16[] prueba =new UInt16[] {0}; 451 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsMainPLC) 452 { 453 DateTime1 =DateTime.Now.TimeOfDay; 454 } 455 //If currently has not been introduced any word to be written on the Stacker PLC, a random value is written in a no important random direction 456 client.WriteWordCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(200), prueba); 457 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsMainPLC) 458 { 459 DateTime2 =DateTime.Now.TimeOfDay; 460 TimeDelayWordsMainPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 461 } 462 463 //In the case the emergency is activated in the Stacker 464 StackerPLCButtomEmergency =client_Stacker.ReadValuesDiscreteCoils( CommunicationParametersModBus.slaveStacker_Address,Convert.ToUInt16(14), Convert.ToUInt16(1)); 465 if(StackerPLCButtomEmergency.arrayCoilsValues[0] == true) 466 { 467 Debug.LogWarning("The emergency buttom in the Stacker PLC has been pushed"); 468 translatorDTToPLCStacker.ResetValuesInEmergency(); //Reset all the values in the Stacker in the DT 469 } 470 471 //For the Stacker PLC 472 DiscreteCoilsValues StackerActuatorsValuesOnDT =translatorDTToPLCStacker. ApplyActuartorsFromDTToPLCStacker(); //Extract the Stacker actuators from the DT 473 474 475 //If the algorithm to check the dealy in the communications has been activated 476 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsStackerPLC) 477 { 478 DateTime1 =DateTime.Now.TimeOfDay; 479 } 480 //Write the value of the actuators on the Stacker PLC 481 client_Stacker.WriteDiscreteCoils(CommunicationParametersModBus. slaveStacker_Address,Convert.ToUInt16(CommunicationParametersModBus. StartingDirectionOfTheVirtualActuatorsStacker), StackerActuatorsValuesOnDT. arrayCoilsValues); 482 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayCoilsStackerPLC) 483 { 484 DateTime2 =DateTime.Now.TimeOfDay; 485 TimeDelayCoilsStackerPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 486 } 487 488 //If the algorithm to check the dealy in the communications has been activated 489 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsStackerPLC) 490 { 491 DateTime1 =DateTime.Now.TimeOfDay; 492 } 100 Capítulo . Bibliografía 493 //If currently has not been introduced any word to be written on the Stacker PLC, a random value is written in a no important random direction 494 client_Stacker.WriteWordCoils(CommunicationParametersModBus.slaveStacker_Address, Convert.ToUInt16(200), prueba); 495 if(CommunicationParametersModBus.CheckDelay && CommunicationParametersModBus. CheckDelayWordsStackerPLC) 496 { 497 DateTime2 =DateTime.Now.TimeOfDay; 498 TimeDelayWordsStackerPLC[CountCommunicationLoops]=(DateTime2-DateTime1). TotalMilliseconds; 499 } 500 501 } 502 503 504 } 505 506 507 //Check which value should be written in the reset coil for the timers on the Main PLC 508 if(TrainingParameters.resetTimeValues == false) 509 { 510 bool[] false_array =new bool[1]; 511 false_array[0] = false; 512 //Write the value of the flag to reset or not the word registers with the time values 513 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(505), false_array); 514 } 515 else 516 { 517 Debug.Log("Writting reset coil for training"); 518 bool[] true_array =new bool[1]; 519 true_array[0] = true; 520 //Write the value of the flag to reset or not the word registers with the time values 521 client.WriteDiscreteCoils(CommunicationParametersModBus.slave_Address,Convert. ToUInt16(505), true_array); 522 TrainingParameters.resetTimeValues =false;//To be able to reset again on the PLC 523 } 524 525 if(CommunicationParametersModBus.CheckDelay) 526 { 527 //If the algorithm to check the dealy in the communcations has been activated 528 CountCommunicationLoops += 1; //Increase the value of the counter to measure the delay in the communications 529 if(CountCommunicationLoops >= CommunicationParametersModBus. NumberOfCommunicationCyclesToDo) 530 { 531 double sum = 0; 532 double average = 0; 533 double std_desv = 0; 534 if(CommunicationParametersModBus.CheckDelayCoilsMainPLC) 535 { 536 sum = 0; 537 average = 0; 538 std_desv = 0; 539 //Get the values and write the JSON file for the Main PLC coils 540 foreach(double vin this.TimeDelayCoilsMainPLC) 541 { 542 sum += v; 543 } 544 average =sum/this.TimeDelayCoilsMainPLC.Length; 545 foreach(double vin this.TimeDelayCoilsMainPLC) 546 { 547 std_desv =std_desv +Math.Pow((v-average),2); 548 } 549 std_desv =Math.Sqrt(std_desv/this.TimeDelayCoilsMainPLC.Length); 550 //Assign the values to the object to be written in the 551 CoilsMainPLCObject.averageDelay =average; 552 CoilsMainPLCObject.standardDesv =std_desv; 553 CoilsMainPLCObjectList.Add(CoilsMainPLCObject); 6 Parámetros de entrenamiento 107 276 { 277 //Constructor of the class 278 //ID assignment 279 this.actuatorsValue[0,0] = 13; //Main cylinder down 280 this.actuatorsValue[1,0] = 13; //Main cylinder up 281 this.actuatorsValue[2,0] = 14; //Rotate main cylinder to right 282 this.actuatorsValue[3,0] = 14; //Rotate main cylinder to left 283 this.actuatorsValue[4,0] = 15; //Previous piston 284 this.actuatorsValue[5,0] = 16; //Rear piston 285 } 286 287 public void ApplyActuartorsFromPLCStackerToDT(DiscreteCoilsValues actuatorValues) 288 { 289 //Function to apply the data read from the Stacker coils in the PLC on the DT 290 for(int i=0;i<actuatorValues.arrayCoilsValues.Length;i++) 291 { 292 //Extract the data 293 this.actuatorsValue[i,1] = Convert.ToInt32(actuatorValues.arrayCoilsValues[i]); //Conversion of the bool values in the input array to int 294 } 295 for(int i=0;i<actuatorValues.arrayCoilsValues.Length;i++) 296 { 297 //Apply it on the DT 298 if(i==0 || i==3) 299 { 300 //In the case it’s the second actuator value of the DynamicStackerPiston or the DynamicRotor 301 AutomationFunctions.ReturnActuatorComponentByNumber(Automation.Memory,this. actuatorsValue[i,0]).flag[1] = Convert.ToBoolean(this.actuatorsValue[i ,1]); 302 } 303 else 304 { 305 //In other case 306 AutomationFunctions.ReturnActuatorComponentByNumber(Automation.Memory,this. actuatorsValue[i,0]).flag[0] = Convert.ToBoolean(this.actuatorsValue[i ,1]); 307 } 308 } 309 } 310 } 311 312 } 6 Parámetros de entrenamiento Código 17 Parámetros para el entrenamiento. 1using System.Net.Sockets; 2using System.Threading; 3using UnityEngine; 4using TrainingLibrary; 5using System; 6 7public class TrainingParameters :MonoBehaviour 8{ 9//Training parameters 10 static public float[] defaultVelocityOnBelts = {0.5f,1f,0.5f,1f}; //Default values of velocity established on the DT at the beginning 11 static public float[] defaultVelocityOnBeltsInReality = {0f,0f,0f,0f}; //Default values of velocity established as real velocity of the belts at the beginning 12 public const string FileVelPath =@".\Assets\Scripts\Training\JSONFiles\VelValues.json";// Path where the velocity value in the simulation is stored 13 public const string FileVelRealityPath =@".\Assets\Scripts\Training\JSONFiles\ VelValuesReality.json";//Path where the velocity value in the reality is stored 14 108 Capítulo . Bibliografía 15 //Modificable parameters 16 static public bool enableTraining =true;//Boolean variable to activate or desactivate the algorythm to estimate the velocity from the belts in the reality and apply it on the DT 17 static public bool resetTimeValues;//To indicate if the time values on the PLC should be reset 18 static public int howManyLoops =4;//How many loop must the tray do to estimate the velocity of the belts 19 20 //Distance values in the reality 21 static public float top_distance = 0.30f;//Distance for the top conveyor,between S1 and S2 22 static public float left_distance = 1.42f;//Distance for the left conveyor, between S3 and S4 23 static public float bottom_distance = 0.32f;//Distance for the left conveyor, between S6 and S7 24 static public float right_distance = 1.72f;//Distance for the right conveyor, between S8 and S9 25 26 //Distance in the DT, from the proper objects in the digital scene 27 static public float top_distance_DT =Math.Abs((GameObject.Find("LongBeltConveyor0/ StopActuatorModule/InductiveSensor0").transform.position.x)-GameObject.Find(" LongBeltConveyor0/InductiveSensorModule/InductiveSensor2").transform.position.x); 28 static public float left_distance_DT =Math.Abs((GameObject.Find("ShortBeltConveyor1/ InductiveSensorModule/InductiveSensor1").transform.position.z)-GameObject.Find(" ShortBeltConveyor1/StopActuatorModule/InductiveSensor0").transform.position.z); 29 static public float bottom_distance_DT =Math.Abs((GameObject.Find("LongBeltConveyor1/ StopActuatorModule/InductiveSensor0").transform.position.x)-GameObject.Find(" LongBeltConveyor1/InductiveSensorModule/InductiveSensor2").transform.position.x); 30 static public float right_distance_DT =Math.Abs((GameObject.Find("ShortBeltConveyor0/ InductiveSensorModule/InductiveSensor1").transform.position.z)-GameObject.Find(" ShortBeltConveyor0/StopActuatorModule/InductiveSensor0").transform.position.z); 31 32 //Structure to be used by the training functions 33 public struct BeltStruct 34 { 35 //Struct to save the needed data for the training 36 public string BeltName;//Name of the belt 37 public int numberPreviousSensor;//Number of the previous sensor 38 public int numberRearSensor;//Number of the rear sensor 39 40 public SensorsTimeValue previousSensorTime;//To save the time value of the previous sensor 41 public SensorsTimeValue rearSensorTime;//To save the time value of the rear sensor 42 43 public BeltStruct(string Name,int numberFirstSensor,int numberSecondSensor) 44 { 45 //Constructor of the structure 46 BeltName =Name; 47 numberPreviousSensor =numberFirstSensor; 48 numberRearSensor =numberSecondSensor; 49 previousSensorTime =new SensorsTimeValue(numberFirstSensor); 50 rearSensorTime =new SensorsTimeValue(numberSecondSensor); 51 } 52 } 53 } 7 Librería de entrenamiento 109 7 Librería de entrenamiento Código 18 Librería desarrollada para el entrenamiento. 1using System; 2using System.Collections.Generic; 3using System.IO; 4using Newtonsoft.Json; 5using UnityEngine; 6 7 8namespace TrainingLibrary //With the scope to create our own library 9{ 10 public struct SensorsTimeValue 11 { 12 //Structure for saving the time’s values of the different sensors. Create one structure for each sensor 13 public int sensorNumber; 14 public UInt16 MiliValue;//Value of milliseconds 15 public UInt16 Sec16LeastSig;//Value of the 16 least significant bits for the seconds value 16 public UInt16 Sec16MostSig;//Value of the 16 most significant bits for the seconds value 17 18 public SensorsTimeValue(int numberOfTheSensor) 19 { 20 //Constructor of the structure 21 sensorNumber =numberOfTheSensor; 22 MiliValue = 0; 23 Sec16LeastSig = 0; 24 Sec16MostSig = 0; 25 } 26 } 27 28 public struct VelValue 29 { 30 //Class to handle the vel.data in the JSON files 31 //Both of the following arrays contains the data. It’shape is : 32 /* 33 Element 0 --> Top belt 34 Element 1 --> Left blet 35 Element 2 --> Bottom belt 36 Element 3 --> Right belt 37 */ 38 public float[] velocity;//Value of the average values of velocity in each belt. 39 public float[] desv;//Standard desviation of each one of the measures 40 public int num_loops;//Number of loop required to obtain the previous data 41 public VelValue(int num_elements)//The number of elements must be 4 in the case of the plant, due to there’s 4 belts 42 { 43 //Constructor of the structure 44 velocity =new float[num_elements]; 45 desv =new float[num_elements]; 46 num_loops = 0; 47 } 48 } 49 50 public class StoreVel 51 { 52 //Class to manage the velocity JSON files 53 54 public static bool InitVelFile(string _path,float[] Values) 55 { 56 //Function to initialize the velocity JSON file 57 if(!File.Exists(_path)) 58 { 59 //In the case the specified file does not exist 60 List<VelValue>ObjectList =new List<VelValue>{}; //Init a list of "VelValue" classes 110 Capítulo . Bibliografía 61 VelValue Object =new VelValue(4); //Init a class type "VelValue", with an array of 4 float 62 for(int i=0;i<Object.velocity.Length;i++) 63 { 64 Object.velocity[i] = Values[i]; //Give the value 65 Object.desv[i]=0f;//It’s assumed that the std.desv at the beginning it’s zero 66 } 67 Object.num_loops = 0; 68 69 ObjectList.Add(Object); //Add the object "Object" to the list 70 writeVelJSON(ObjectList,_path); //Write on the file the initial structure of values 71 return true; 72 } 73 else 74 { 75 //In the case the file exists 76 return false; 77 } 78 } 79 80 public static void writeVelJSON(List<VelValue>VelValueList,string _path) 81 { 82 //Function to write on the JSON files for the velocity values 83 string ValueListArray =Newtonsoft.Json.JsonConvert.SerializeObject(VelValueList. ToArray(), Formatting.Indented); 84 File.WriteAllText(_path,ValueListArray); 85 } 86 87 public static List<VelValue>readVelJSON(string _path) 88 { 89 //Funtion to read from the JSON files the velocity values 90 string VelocityListArray; 91 using (var reader =new StreamReader(_path)) 92 { 93 VelocityListArray =reader.ReadToEnd(); 94 } 95 96 List<VelValue>VelocityList =Newtonsoft.Json.JsonConvert.DeserializeObject<List< VelValue>>(VelocityListArray); 97 98 return VelocityList; 99 } 100 } 101 public class VelClass 102 { 103 //Class for the belt’s velocity estimation 104 private bool times_top_calculated;//Top velocity already calculated 105 private bool times_left_calculated;//Left velocity already calculated 106 private bool times_bottom_calculated;//Bottom velocity already calculated 107 private bool times_right_calculated;//Right velocity already calculated 108 109 110 111 public VelClass() 112 { 113 //Constructor of the class 114 this.times_top_calculated =false; 115 this.times_left_calculated =false; 116 this.times_bottom_calculated =false; 117 this.times_right_calculated =false; 118 } 119 120 public float EstimateVelocityInDT(SensorsTimeValue previousSensor,SensorsTimeValue rearSensor,float distanceInReality,float distanceInTheDT) 121 { 122 //Function to estimate the velocity in the DT 123 float velocity; 124 125 //Get the second’s value for the previous sensor 7 Librería de entrenamiento 111 126 UInt64 previousSensorSeconds =Convert.ToUInt64((Convert.ToUInt64(previousSensor. Sec16MostSig) * Convert.ToUInt64(Math.Pow(2, 16)))) + Convert.ToUInt64( previousSensor.Sec16LeastSig); 127 //Get the second’s value for the rear sensor 128 UInt64 rearSensorSeconds =Convert.ToUInt64((Convert.ToUInt64(rearSensor. Sec16MostSig) * Convert.ToUInt32(Math.Pow(2, 16)))) + Convert.ToUInt64( rearSensor.Sec16LeastSig); 129 130 //Get the milisecond’s value for the previous sensor 131 UInt64 previousSensorMili =Convert.ToUInt64(previousSensor.MiliValue); 132 //Get the milisecond’s value for the rear sensor 133 UInt64 rearSensorMili =Convert.ToUInt64(rearSensor.MiliValue); 134 //Get the time that has passed in the reality 135 double realTime = (Convert.ToDouble(rearSensorSeconds) - Convert.ToDouble( previousSensorSeconds)) + ((Convert.ToDouble(rearSensorMili) - Convert.ToDouble (previousSensorMili)) / 1000); 136 //Get the velocity in the reality 137 float realVelocity; 138 if(realTime != 0 && rearSensorSeconds != 0 && rearSensorMili != 0 && previousSensorSeconds !=0&&previousSensorMili != 0) //The velocity will be caLculated once the tray have passed through both sensors 139 { 140 realVelocity =distanceInReality / (float)realTime; 141 } 142 else //In other case, the value of the velocity estimated will be equal to 0 143 { 144 realVelocity =0;//If the time read it’s equal to 0, we estimate the velocity as 0, in order to avoid a new estimation in the training file 145 } 146 147 //Conversion to the velocity that must be returned, with the objective to need the same time to travel the distance 148 //We apply that conversion factor due to the distance in the DT and the reality are not the same 149 velocity =realVelocity * (distanceInTheDT /distanceInReality); 150 151 return velocity; 152 } 153 154 public float EstimateVelocityInReality(SensorsTimeValue previousSensor, SensorsTimeValue rearSensor,float distanceInReality) 155 { 156 //Function to estimate the velocity in the reality 157 158 //Get the second’s value for the previous sensor 159 UInt64 previousSensorSeconds =Convert.ToUInt64((Convert.ToUInt64(previousSensor. Sec16MostSig) * Convert.ToUInt64(Math.Pow(2, 16)))) + Convert.ToUInt64( previousSensor.Sec16LeastSig); 160 //Get the second’s value for the rear sensor 161 UInt64 rearSensorSeconds =Convert.ToUInt64((Convert.ToUInt64(rearSensor. Sec16MostSig) * Convert.ToUInt32(Math.Pow(2, 16)))) + Convert.ToUInt64( rearSensor.Sec16LeastSig); 162 163 //Get the milisecond’s value for the previous sensor 164 UInt64 previousSensorMili =Convert.ToUInt64(previousSensor.MiliValue); 165 //Get the milisecond’s value for the rear sensor 166 UInt64 rearSensorMili =Convert.ToUInt64(rearSensor.MiliValue); 167 //Get the time that has passed in the reality 168 double realTime = (Convert.ToDouble(rearSensorSeconds) - Convert.ToDouble( previousSensorSeconds)) + ((Convert.ToDouble(rearSensorMili) - Convert.ToDouble (previousSensorMili)) / 1000); 169 //Get the velocity in the reality 170 float realVelocity; 171 if(realTime != 0 && rearSensorSeconds != 0 && rearSensorMili != 0 && previousSensorSeconds !=0&&previousSensorMili != 0) //The velocity will be caLculated once the tray have passed through both sensors 172 { 173 realVelocity =distanceInReality / (float)realTime; 174 } 175 else //In other case, the value of the velocity estimated will be equal to 0 176 { 112 Capítulo . Bibliografía 177 realVelocity = 0; //If the time read it’s equal to 0, we estimate the velocity as 0, in order to avoid a new estimation in the training file 178 } 179 180 return realVelocity; 181 182 } 183 } 184 } 8 Algoritmo de entrenamiento en el entorno del Gemelo Digital Código 19 Algoritmo de entrenamiento en el entorno del DT. 1using System.Collections.Generic; 2using UnityEngine; 3using TrainingLibrary; 4using System; 5 6public class Training :MonoBehaviour 7{ 8VelClass velocityClass;//Class to estimate the velocity of each belt 9 10 //Values to be stored in the JSON files 11 //For the DT 12 private float top_vel_JSON; 13 private float top_vel_JSON_desv; 14 private float left_vel_JSON; 15 private float left_vel_JSON_desv; 16 private float bottom_vel_JSON; 17 private float bottom_vel_JSON_desv; 18 private float right_vel_JSON; 19 private float right_vel_JSON_desv; 20 //For the reality 21 private float top_vel_reality_JSON; 22 private float top_vel_reality_JSON_desv; 23 private float left_vel_reality_JSON; 24 private float left_vel_reality_JSON_desv; 25 private float bottom_vel_reality_JSON; 26 private float bottom_vel_reality_JSON_desv; 27 private float right_vel_reality_JSON; 28 private float right_vel_reality_JSON_desv; 29 30 private float top_vel;//Velocity of the top belt in the DT 31 private float left_vel;//Velocity of the left belt in the DT 32 private float bottom_vel;//Velocity of the bottom belt in the DT 33 private float right_vel;//Velocity of the right belt in the DT 34 35 private float top_vel_reality;//Velocity of the top belt in the reality 36 private float left_vel_reality;//Velocity of the left belt in the reality 37 private float bottom_vel_reality;//Velocity of the bottom belt in the reality 38 private float right_vel_reality;//Velocity of the right belt in the reality 39 40 //Arrays of values for the velocity estimated in each belt for the DT 41 private float[] top_vel_array =new float[TrainingParameters.howManyLoops]; 42 private float[] left_vel_array =new float[TrainingParameters.howManyLoops]; 43 private float[] bottom_vel_array =new float[TrainingParameters.howManyLoops]; 44 private float[] right_vel_array =new float[TrainingParameters.howManyLoops]; 45 //Arrays of values for the velocity estimated in each belt in the reality 46 private float[] top_vel_array_reality =new float[TrainingParameters.howManyLoops]; 47 private float[] left_vel_array_reality =new float[TrainingParameters.howManyLoops]; 48 private float[] bottom_vel_array_reality =new float[TrainingParameters.howManyLoops]; 49 private float[] right_vel_array_reality =new float[TrainingParameters.howManyLoops]; 50 51 52 8 Algoritmo de entrenamiento en el entorno del Gemelo Digital 113 53 private int num_loop;//Number of the loop 54 55 private float[] vel_values =new float[4]; //Array of float values to write/read on/from the JSON files for the DT 56 private float[] vel_values_desv =new float[4]; //Array of float values to write/read on/ from the JSON files for the DT desviation 57 private float[] vel_values_reality =new float[4]; //Array of float values to write/read on /from the JSON files for the reality 58 private float[] vel_values_reality_desv =new float[4]; //Array of float values to write/ read from the JSON files for the reality desviation 59 //Top belt 60 //Left belt 61 //Bottom belt 62 //Right belt 63 64 List<VelValue>ObjectList =new List<VelValue>{}; //List to write on the JSON file for the DT 65 List<VelValue>ObjectListReality =new List<VelValue>{}; //List to write on the JSON file for the reality 66 VelValue Object =new VelValue(4); //Object to store the data to be written for the DT 67 VelValue ObjectReality =new VelValue(4); //Object to store the data to be written for the reality 68 69 70 void Start() 71 { 72 73 if (TrainingParameters.enableTraining) 74 { 75 76 this.num_loop = 0; //Init the number of loops to estimate the velocity 77 //If the training has been enabled 78 velocityClass =new VelClass(); //Init the vel class 79 80 for (int i=0;i<4;i++) //Loop to initialize the structures created to store the belt velocity values 81 { 82 switch (i) 83 { 84 case 0: 85 Automation.Belts[i] = new TrainingParameters.BeltStruct("Top Belt", 1, 2) ; 86 break; 87 case 1: 88 Automation.Belts[i] = new TrainingParameters.BeltStruct("Left Belt", 3, 4); 89 break; 90 case 2: 91 Automation.Belts[i] = new TrainingParameters.BeltStruct("Bottom Belt", 6, 7); 92 break; 93 case 3: 94 Automation.Belts[i] = new TrainingParameters.BeltStruct("Right Belt", 8, 9); 95 break; 96 default: 97 break; 98 } 99 } 100 101 } 102 } 103 104 void LateUpdate() 105 { 106 if (TrainingParameters.enableTraining) 107 { 108 //Extract the data written by the communication 109 //Velocity for the DT 114 Capítulo . Bibliografía 110 this.top_vel =velocityClass.EstimateVelocityInDT(Automation.Belts[0]. previousSensorTime,Automation.Belts[0].rearSensorTime,TrainingParameters. top_distance,TrainingParameters.top_distance_DT); 111 this.left_vel =velocityClass.EstimateVelocityInDT(Automation.Belts[1]. previousSensorTime,Automation.Belts[1].rearSensorTime,TrainingParameters. left_distance,TrainingParameters.left_distance_DT); 112 this.bottom_vel =velocityClass.EstimateVelocityInDT(Automation.Belts[2]. previousSensorTime,Automation.Belts[2].rearSensorTime,TrainingParameters. bottom_distance,TrainingParameters.bottom_distance_DT); 113 this.right_vel =velocityClass.EstimateVelocityInDT(Automation.Belts[3]. previousSensorTime,Automation.Belts[3].rearSensorTime,TrainingParameters. right_distance,TrainingParameters.right_distance_DT); 114 //Velocity for the reality 115 this.top_vel_reality =velocityClass.EstimateVelocityInReality(Automation.Belts[0]. previousSensorTime,Automation.Belts[0].rearSensorTime,TrainingParameters. top_distance); 116 this.left_vel_reality =velocityClass.EstimateVelocityInReality(Automation.Belts[1]. previousSensorTime,Automation.Belts[1].rearSensorTime,TrainingParameters. left_distance); 117 this.bottom_vel_reality =velocityClass.EstimateVelocityInReality(Automation.Belts [2].previousSensorTime,Automation.Belts[2].rearSensorTime,TrainingParameters. bottom_distance); 118 this.right_vel_reality =velocityClass.EstimateVelocityInReality(Automation.Belts [3].previousSensorTime,Automation.Belts[3].rearSensorTime,TrainingParameters. right_distance); 119 120 121 //In the case all the velocities have been estimated 122 if(this.top_vel != 0 && this.left_vel != 0 && this.bottom_vel != 0 && this.right_vel != 0) 123 { 124 //Apply it to the data arrays 125 //For the DT’s arrays 126 this.top_vel_array[this.num_loop]=this.top_vel; 127 this.left_vel_array[this.num_loop]=this.left_vel; 128 this.bottom_vel_array[this.num_loop]=this.bottom_vel; 129 this.right_vel_array[this.num_loop]=this.right_vel; 130 //For the reality’s arrays 131 this.top_vel_array_reality[this.num_loop]=this.top_vel_reality; 132 this.left_vel_array_reality[this.num_loop]=this.left_vel_reality; 133 this.bottom_vel_array_reality[this.num_loop]=this.bottom_vel_reality; 134 this.right_vel_array_reality[this.num_loop]=this.right_vel_reality; 135 this.num_loop++; //Increase the number of loop to calculate the average value for the velocity of the belts 136 TrainingParameters.resetTimeValues =true;//To reset the time values on the PLC 137 138 Debug.Log("Velocity values in the reality"); 139 Debug.Log("Top : "+this.top_vel_reality); 140 Debug.Log("Left : "+this.left_vel_reality); 141 Debug.Log("Bottom : "+this.bottom_vel_reality); 142 Debug.Log("Right : "+this.right_vel_reality); 143 144 145 Debug.Log("Number loop : "+this.num_loop); 146 147 //Reset all the values 148 //For the DT 149 this.top_vel = 0; 150 this.left_vel = 0; 151 this.bottom_vel = 0; 152 this.right_vel = 0; 153 //For the reality 154 this.top_vel_reality = 0; 155 this.left_vel_reality = 0; 156 this.bottom_vel_reality = 0; 157 this.right_vel_reality = 0; 158 159 //To establish the true value on the reset coil in the PLC, so in the next loop the value of the times will be 0 160 bool[] true_array =new bool[1]; 161 true_array[0] = true; 8 Algoritmo de entrenamiento en el entorno del Gemelo Digital 115 162 Communication.client.WriteDiscreteCoils(CommunicationParametersModBus. slave_Address,Convert.ToUInt16(505), true_array); 163 } 164 165 //In the case we have done the specified number of loops 166 if(this.num_loop >= TrainingParameters.howManyLoops) 167 { 168 //For the DT 169 float sum = 0; 170 foreach (float vin this.top_vel_array)sum += v; 171 this.top_vel_JSON =sum/this.top_vel_array.Length;//Extract the average value for the top belt in the DT 172 foreach(float vin this.top_vel_array) 173 { 174 this.top_vel_JSON_desv =this.top_vel_JSON_desv + (float)Math.Pow((v-this. top_vel_JSON),2); 175 } 176 this.top_vel_JSON_desv = (float)Math.Sqrt((this.top_vel_JSON_desv/this. top_vel_array.Length)); 177 178 sum = 0; 179 foreach(float vin this.left_vel_array)sum += v; 180 this.left_vel_JSON =sum/this.left_vel_array.Length;//Extract the average value for the left belt in the DT 181 foreach(float vin this.left_vel_array) 182 { 183 this.left_vel_JSON_desv =this.left_vel_JSON_desv + (float)Math.Pow((v-this. left_vel_JSON),2); 184 } 185 this.left_vel_JSON_desv = (float)Math.Sqrt((this.left_vel_JSON_desv/this. left_vel_array.Length)); 186 187 sum = 0; 188 foreach(float vin this.bottom_vel_array)sum +=v; 189 this.bottom_vel_JSON =sum/this.bottom_vel_array.Length;//Extract the average value for the bottom belt in the DT 190 foreach(float vin this.bottom_vel_array) 191 { 192 this.bottom_vel_JSON_desv =this.bottom_vel_JSON_desv + (float)Math.Pow((vthis.bottom_vel_JSON),2); 193 } 194 this.bottom_vel_JSON_desv = (float)Math.Sqrt((this.bottom_vel_JSON_desv/this. bottom_vel_array.Length)); 195 196 sum = 0; 197 foreach(float vin this.right_vel_array)sum += v; 198 this.right_vel_JSON =sum/this.right_vel_array.Length;//Extract the average value for the right belt in the DT 199 foreach(float vin this.right_vel_array) 200 { 201 this.right_vel_JSON_desv =this.right_vel_JSON_desv + (float)Math.Pow((v-this .right_vel_JSON),2); 202 } 203 this.right_vel_JSON_desv = (float)Math.Sqrt((this.right_vel_JSON_desv/this. right_vel_array.Length)); 204 205 206 //For the reality 207 //For the top belt 208 sum = 0; 209 foreach(float vin this.top_vel_array_reality)sum += v; 210 this.top_vel_reality_JSON =sum/this.top_vel_array_reality.Length;//Averaga value 211 foreach(float vin this.top_vel_array_reality) 212 { 213 this.top_vel_reality_JSON_desv =this.top_vel_reality_JSON_desv + (float)Math .Pow((v-this.top_vel_reality_JSON),2); 214 } 215 //Std.desv 216 this.top_vel_reality_JSON_desv = (float)Math.Sqrt((this. top_vel_reality_JSON_desv/this.top_vel_array_reality.Length)); 116 Capítulo . Bibliografía 217 218 //For the left belt 219 sum = 0; 220 foreach(float vin this.left_vel_array_reality)sum += v; 221 this.left_vel_reality_JSON =sum/this.left_vel_array_reality.Length;//Average value 222 foreach(float vin this.left_vel_array_reality) 223 { 224 this.left_vel_reality_JSON_desv =this.left_vel_reality_JSON_desv + (float) Math.Pow((v-this.left_vel_reality_JSON),2); 225 } 226 //Std.desv 227 this.left_vel_reality_JSON_desv = (float)Math.Sqrt((this. left_vel_reality_JSON_desv/this.left_vel_array_reality.Length)); 228 229 //For the bottom belt 230 sum = 0; 231 foreach(float vin this.bottom_vel_array_reality)sum += v; 232 this.bottom_vel_reality_JSON =sum/this.bottom_vel_array_reality.Length;// Average value 233 foreach(float vin this.bottom_vel_array_reality) 234 { 235 this.bottom_vel_reality_JSON_desv =this.bottom_vel_reality_JSON_desv + ( float)Math.Pow((v-this.bottom_vel_reality_JSON),2); 236 } 237 //Std.desv 238 this.bottom_vel_reality_JSON_desv = (float)Math.Sqrt((this. bottom_vel_reality_JSON_desv/this.bottom_vel_array_reality.Length)); 239 240 //For the right belt 241 sum = 0; 242 foreach(float vin this.right_vel_array_reality)sum += v; 243 this.right_vel_reality_JSON =sum/this.right_vel_array_reality.Length; 244 foreach(float vin this.right_vel_array_reality) 245 { 246 this.right_vel_reality_JSON_desv =this.right_vel_reality_JSON_desv + (float) Math.Pow((v-this.right_vel_reality_JSON),2); 247 } 248 this.right_vel_reality_JSON_desv = (float)Math.Sqrt((this. right_vel_reality_JSON_desv/this.right_vel_array_reality.Length)); 249 250 //Refresh the vel values 251 252 //Assign the values to the JSON file 253 //For the DT 254 //Average 255 this.vel_values[0] = this.top_vel_JSON; 256 this.vel_values[1] = this.left_vel_JSON; 257 this.vel_values[2] = this.bottom_vel_JSON; 258 this.vel_values[3] = this.right_vel_JSON; 259 //Desviation 260 this.vel_values_desv[0] = this.top_vel_JSON_desv; 261 this.vel_values_desv[1] = this.left_vel_JSON_desv; 262 this.vel_values_desv[2] = this.bottom_vel_JSON_desv; 263 this.vel_values_desv[3] = this.right_vel_JSON_desv; 264 //For the reality 265 //Average 266 this.vel_values_reality[0] = this.top_vel_reality_JSON; 267 this.vel_values_reality[1] = this.left_vel_reality_JSON; 268 this.vel_values_reality[2] = this.bottom_vel_reality_JSON; 269 this.vel_values_reality[3] = this.right_vel_reality_JSON; 270 //Desviation 271 this.vel_values_reality_desv[0] = this.top_vel_reality_JSON_desv; 272 this.vel_values_reality_desv[1] = this.left_vel_reality_JSON_desv; 273 this.vel_values_reality_desv[2] = this.bottom_vel_reality_JSON_desv; 274 this.vel_values_reality_desv[3] = this.right_vel_reality_JSON_desv; 275 276 //For the DT 277 for(int i=0;i<Object.velocity.Length;i++) 278 { 279 Object.velocity[i] = this.vel_values[i]; 9 Mejora de experiencia de usuario en el entorno del Gemelo Digital 123 272 } 273 } 274 275 if(aux1 != aux2) 276 { 277 //If the state has changed within the last loop 278 soundManager.PlaySound(1,50); 279 } 280 281 transform.GetComponentInChildren<TextMeshProUGUI>().SetText(aux2); 282 } 283 284 } 285 catch(NullReferenceException ex) 286 { 287 //In the case the camera selected is still being initialized 288 target =PreviousCamera.transform;//Look at the last position saved to look at 289 } 290 291 transform.LookAt(target); //To orientate the display to the camera 292 transform.position =initPosition; 293 } 294 } En el segundo caso, se corresponde con la clase desarrollada para con los métodos necesarios para la ejecución de los sonidos asociados sobre la clase : Código 21 Librería para la reproducción de sonidos por parte de los actuadores. 311 using System.Collections; 312 using System.Collections.Generic; 313 using UnityEngine; 314 315 public class SoundManager :MonoBehaviour 316 { 317 [SerializeField]private AudioClip[] audioClips;//Where the audio clip is going to be stored 318 private AudioSource controlAudio;//Object to control the audio 319 320 void Awake() 321 { 322 controlAudio =GetComponent<AudioSource>(); //Extract the component 323 } 324 325 326 public void PlaySound(int index,float volume) 327 { 328 //Function to play an audioclip with a specific volumen 329 controlAudio.PlayOneShot(audioClips[index],volume); //Play one time that sound 330 } 331 }