Proyecto

“Simulación de un

Sistema domótico en Python”

Integrantes:              → Tomas Ibaceta                Rol: 201721039-7

                                    → Patricio Carrasco           Rol: 201721064-8

                                    → Gustavo Silva                 Rol: 201721012-5

Ramo:                                    → [ELO330] Programación de Sistemas

Fecha:                       → 14 – 12 – 2021

Contexto:                  → Se ha observado la necesidad de registrar datos en la nube es una necesidad común, debido al acceso remoto que logra. Dentro de este tópico es que alumnos del ELO330 de la UTFSM han desarrollado previamente una solución simplificada a este problema (Tarea 2 – ELO330 – 2021S2), es debido a ello que; se han planteado replicar el sistema medición en un nuevo lenguaje, este corresponde a Python. Este cambio del lenguaje es debido a que este ha estado en auge en el ultimo tiempo, debido a su nivel de abstracción de alto nivel que ofrece a sus usuarios y programadores.

                                    De ello se establece la siguiente problemática, ¿Como puedo crear una arquitectura cliente/servidor en lenguaje de Python que sea concurrente?

                                    De esta manera se plantea la siguiente estructura de programa que se comunicara mediante TCP/UDP con programas del mismo lenguaje (python); todos evidenciable en el siguiente diagrama.

Figura 1: Diagrama de arquitectura de solución

En la figura 1 se puede observar de la grafica que existe un servidor OPC, este es el encargado de emular el servidor en la nube, administrando conexiones TCP con los distintos clientes y UDP con el único sensor que estamos observando. Es importante recalcar que hay distintas formas de realizar un servidor concurrente, uno de ellos corresponde a simularlo a través de la estructura select.

Por otra parte, de la misma grafica se observa el programa Medicion.py, este es el encargado de crear una rampa de periodo 1 amplitud 1, enviando los valores de esa señal cada delta_t [s] a opc.py mediante una conexión UDP.

Finalmente observamos un cliente, que corresponde al programa lector escrito en Python este se comunica mediante TCP con opc para leer el valor de un único sensor; logrando graficar a través de utilizar una librería llamada matplotlib junto con un buffer circular.

Estrategia y Solución para resolver Problema:

Medidor.py:

La función de este programa es crear un socket UDP y mandar datos periódicamente de una función determinada, para este caso son dientes de sierra de 50% de ciclo de trabajo.

La estrategia que se toma es pasar el nombre de hostname , puerto a usar en aragorn y el valor de delta t por argumentos al correr el ejecutable del programa; guardando cada una de las variables en el formato que corresponde. Una vez teniendo las variables se procede a crear el socket para UDP :

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

y luego guardando las direcciones del server en la estructura sockaddr_in como son el puerto en el que trabaja y su dirección de red (variables que se pasan al ejecutar el programa) .

Luego en un while(1) se generan constantemente valores de números flotantes de la función de diente de sierra y estos son mandados a través de la función sendto con dirección al server guardado en la estructura sockaddr_in :

sendData = str.encode(str(num))

sent = sock.sendto(sendData, server_address)

lector.py:

De igual forma que medidor.py la información del servidor opc.c se pasa por argumentos cuando se ejecuta el programa lector.py ( hostname , numero de puerto aragorn y delta ) . Se almacena el hostname en una tupla junto con el puerto a usar, luego se crea el buffer para almacenar los 20 datos que corresponde con un arreglo de flotantes.

Finalmente, para comunicarse con el servidor opc dentro de un while infinito hasta que se usa la señal SIGINT; se crea el socket con el número del Puerto del servidor y se crean objetos para la comunicación entre servidor y cliente gracias a las funciones que posee el socket en python, como lo son los métodos recv que permite leer datos que le manda el servidor y el otro método es  send que permite mandar datos al servidor. Luego, se vuelve a realizar la conexión del socket con el método connect; para así mandar el carácter ´n´ para advertir al servidor que envíe datos a este cliente y este mismo almacena de a 20 datos en un buffer circular el cual dará paso al gráfico.

Para plotear ocupa la siguiente estructura:

        fig = plt.figure()

       

        fig.clear()

        ax = plt.subplot()

        ax.plot(np.asarray(xValues), np.asarray(yValues), linewidth=2.0)

        plt.pause(0.02)

        plt.show()

Lector ocupan un buffer circular para ordenar los datos que se caracteriza por ser FIFO, es decir; cada vez que llega una nueva medición se agrega al final del buffer de 20 datos, pero para ello deben eliminar el primer dato del buffer a través del método pop y agregar al final del buffer con el método append

opc.py:

Para el servidor solo se necesita como argumento el número de puerto en el que se desea trabajar, gracias a las características que posee un socket se puede trabajar con UDP y TCP en el mismo socket sin ningún tipo de problema, por lo que se crean los 2 socket :

socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

socket_UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

y haciendo el bind correspondiente a cada uno, con la dirección del servidor almacenada en la estructura de tipo sockaddr_in. En el loop infinito realizado con while(isfinish == False) (con condiciones de excepción para finalizar con ctrl +C) se utiliza la función select() con un time out de 1 segundo para emular un servidor concurrente, de esta manera solo revisara las acciones de TCP o UDP si es que hubo movimiento en alguno de ellos. Si es que esto último sucede primero se analiza si el movimiento fue en una conexión TCP y si tiene que mandar los datos a un cliente en específico; y luego analiza si el movimiento fue en alguna conexión UDP para almacenar datos que llegan desde el medidor (que en algún momento puede ser enviado por la conexión TCP del socket al cliente que lo solicitó).

Es importante recalcar que el select funciona muy similar a como funciona en c, en este caso ya retorna 3 listas:

read_lists, write_list, exception_list = select.select(socket_lists_rd, socket_lists_rd, socket_lists_rd, timeout)

donde en cada lista, ya se encuentran los descriptores de socket en que solamente hubo movimientos.

Situaciones problemáticas del programa:

Una de las situaciones problemáticas que ocurren el programa es que el medidor siempre genera una función de periodo 1 segundo, ya que nunca se indica que este cambie por lo que si se elige un delta muy grande lo más probable es que la gráfica realizada por el cliente lector no se distinga como corresponde además de que esta lee cada un segundo lo cual ocasiona que no se forme la señal como uno desee.

Conclusiones:

Es importante recalcar que este código se basa en una solución ya propuesta con anterioridad en una evaluación del curso ya mencionado. Esto hace que sea importante hacer notar una característica de este proyecto: “este proyecto se basa en una traducción de código”; lo cual no desmerece su desarrollo, esto dice que un lenguaje de alto nivel como python, puede reducir mucho lo que es código, pero el nivel de abstracción produce que se puedan perder detalles y eficiencia si es que no se entiende lo que se esta realizando por debajo; lo cual se consigue entendiendo como realizar el mismo código en lenguajes como C.

Además, este proyecto tiene muchas posibles mejoras, una de ellas es considerar y evaluar el comportamiento con versiones de estos programas en lenguajes diferentes a python. Por otra parte se puede reestructurar la solución para que se realice con multi-hebra.