lunes, 18 de febrero de 2013

[Lab RT] Actividad 3: Escáner WIFI

La actividad consistió en escribir un código para escanear las redes cercanas a un lugar y posteriormente realizar el mapeo de las mismas utilizando Google Maps.

Básicamente lo que se buscara es el nombre de la red y la potencia de la señal en determinado lugar.

El escáner

Lo primero que programé fue el escáner wifi utilizando python. Utilizando subprocess llamo al comando iwlist para que me muestre la información disponible de las redes a mi alrededor. Sin embargo, el comando iwlist por sí solo nos mostrará solo la información del router en el que estamos conectados, hay que llamar el comando con sudo, también le damos la interfaz que usaremos para escanear, en mi caso wlan0 que corresponde a la inalámbrica, el comando queda de la siguiente forma:


sudo iwlist wlan0 scan


Lo que nos produce la siguiente salida.


Nos regresa mucha información para solo 3 datos que necesitamos, entonces combinamos iwlist con el comando egrep  para extraer solo la información que necesitamos, en mi caso los campos ESSID, Frequency, Quality y Signal level, el comando queda de la siguiente forma:


sudo iwlist wlan0 scan | egrep -i 'ESSID|quality|Frequency'


Lo que nos produce la siguiente salida.



Mucho mejor, con ello nuestro escaner esta en un 50%, ahora debemos automatizar la tarea, para ello utilicé python y escribí el siguiente script:



Como las expresiones son iguales en cada corrida entonces lo que hago es eliminar las partes que no me sirven con la función de python replace aplicada a cada cadena

El escáner lanzará la información formateada de la siguiente forma:

Como imagen de ejemplo solo se muestran 4 redes pero al realizar la práctica se hallarón las 5 redes.

De la información que obtuvimos nos importan 2 cosas, la calidad o quality, que de mide entre 0 y 70, y el nivel de señal o signal level que se mide en dbm. El nivel máximo de calidad es 70 y el mínimo es 10 antes de que nuestra antena decida ignorar un router, lo mismo pasa con la señal, el máximo es -35dbm y el mínimo -95dbm, si utilizamos el nivel de señal entonces mediremos de manera lineal, es decir, si -35dbm es el 100% y -95dbm es 0% entonces -60dbm es el 50%, por citar un ejemplo.

Lo siguiente fue "pedir permiso" a mis vecinos para accesar a su router y poder ubicarlo, hay varios tutoriales en internet de cómo hacerlo, pero recuerden que no es algo muy ético o legal. Hay otras formas, pero fue la más sencilla que se me ocurrió por ahora.

Así pude aproximar la ubicación de cada router utilizando WIFI y Google Maps. Con ello obtuve el siguiente mapita basado en las coordenadas que me dio Maps:



Después utilicé mi celular y camine por ahí con el monitor en la computadora alrededor de mi casa y en la calle. La caminata fue para buscar el lugar en donde la señal dejaba de ser detectada por el monitor en python. Después de varias mediciones ubiqué el límite de todas las redes, en dónde dejaban de ser detectadas, entonces supuse que en esé lugar estaba la frontera de señal de cierto router. Con mi celular simplemente recuperaba las coordenadas donde se perdía la señal y ahora tenía 2 puntos:
  • El centro del router
  • El punto donde se pierde la señal

Por ultimo utilizando Google Maps, en el panel izquierdo, hasta abajo seleccionamos Maps Labs, ahí simplemente activamos la Herramienta de medición de distancias, la distancia entre el punto que indica el centro del router y el punto que indica la frontera del router es la distancia que representa el radio del circulo que dibujaremos para mapear el alcance la red.



Tomé nota de las distancias que me arrojó la herramienta (en metros) y esa distancia la utilicé como radio para dibujar los círculos que indican el alcance de la red.

Finalmente dibujamos los círculos  centrados en la ubicación aproximada de cada router y el radio como el alcance del router.



Como podemos ver, algunos varían en tamaño, también las ubicaciones de cada router parecen ser no las más acertadas, recordemos que es una aproximación pero resulta útil por ahora para saber que tanto puedes alejarte de tu casa antes de perder la señal de internet.


Referencias


[VC] Tarea 2: Detección de formas

Para esta entrada el objetivo fue programar las rutinas necesarias para la detección de formas en una imagen.
Con lo que hicimos en la tarea 1, detección de bordes utilizando máscaras de Prewitt y aplicación de umbrales para un mejor resultado:


ahora vamos a detectar los objetos que quedan encerrados dentro de los contornos resaltados en color blanco, para ello probaremos con las siguientes imágenes:


Imágenes originales

Para comenzar necesitamos binarizar las imágenes con la técnica de convolución que ya vimos.
Después de aplicar máscaras y umbrales tendremos algo como ésto:

Imágenes binarizadas

Ahora a detectar los objetos, es un procedimiento sencillo y complejo a la vez:

  • Creamos una matriz de pixeles visitados del mismo tamaño que la imagen (alto x ancho)
  • Creamos una lista para guardar la información de los objetos que encontremos y un contador que servirá para poner un ID a cada objeto.
  • Ahora comenzamos a recorrer todos los pixeles de la imagen.
  • Verificamos cada pixel en el que nos posicionamos en la matriz de pixeles visitados, si no esta marcado como visitado (visitado[x][y] = False) y el color del pixel es negro entonces marcamos ese pixel como el origen de nuestra búsqueda (los pixeles negros pertenecen a objetos, los blancos a bordes).
  • Generamos un color aleatorio
  • Aplicamos búsqueda en anchura BFS en el pixel que marcamos como origen para pintar con el color generado los pixeles que nos lance la búsqueda en anchura.
  • BFS nos regresará la lista de pixeles que fueron visitados, dichos pixeles nos sirven para calcular también la masa del objeto y buscar su centro. Además nos regresa la lista de pixeles pintados de la imágen.
  • Calculamos sus propiedades: 
    • tamaño del objeto a partir del largo de la lista de pixeles visitados
    • su porcentaje con una regla de tres simple utilizando el tamaño del objeto y a cantidad total de pixeles en la imágen.
  • Tomamos las coordenadas de la lista de pixeles visitados y realizamos 2 sumas, en una sumamos los valores de las coordenadas x y en la otra suma los valores de las coordenadas y, después dividimos la sumatoria entre la cantidad de valores sumados. Este par de resultados son las coordenadas del centro de masa del objeto.
  • Tomamos las propiedades y armamos un objeto en forma de diccionario, ahí empaquetamos toda la información.
  • Por cada objeto creado incrementamos el contador de los IDs
  • Repetimos hasta que todos los pixeles hayan sido visitados.
  • Después, de la lista de objetos tomamos aquel de mayor tamaño, supondremos que el objeto de mayor tamaño es el fondo de la imágen por ahora. Pintamos los pixeles que pertenecen a ese objeto de color gris.
  • Ahora solo nos queda recorrer la lista de objetos obteniendo las propiedades de cada uno, mas precisamente, las coordenadas de su centro. En esas coordenadas dibujamos un punto negro y colocamos una etiqueta con el ID del objeto al que pertenece.
  • Mientras en terminal imprimimos sus propiedades para que podamos relacionarlas.


La rutina BFS esta basada en el algoritmo visto en clase.

Los objetos identificados se colorean y se ven así:

Objetos identificados con centro de masa

Podemos ver el centro de masa marcado con un punto negro.


Por último los etiquetamos.

Objetos identificados con centro de masa

Mientras en la terminal vemos la información recolectada de cada objeto, su tamaño en pixeles y porcentaje.



Y así termina la entrada, en el repositorio encuentran la implementación completa en código, la carpeta marcada como Tarea 2.

Código:

Clasificación de objetos parte 1, que hace el trabajo en la interfaz gráfica

Clasificación de objetos parte 2, encargada de la parte lógica

Interfaz


La interfaz permanece sin cambios, al menú Machine vision se agregaron las opciones:

  • Convex hull: Envoltura de objetos, aún sin funcionalidad
  • Object Detection: Detección de un objeto individual
  • Object Classification: Detección de todos los objetos en una imágen

Como agregado hice una rutina llamada objectDetection cuyo objetivo es detectar una sola figura dada las coordenadas de origen.
Coloque un listener en el canvas para guardar las coordenadas donde se da clic, después la rutina toma esas coordenadas como inicio para detectar ese objeto utilizando BFS. No etiqueta ni marca centros de masa, pero es una buena práctica para simular el rellenado de figuras que vemos en programas de dibujo. Las coordenadas seleccionadas se muestran en terminal, la figura se rellena de color azul sólido



Citar el blog de Emmanuel García donde me apoye para optimizar el algoritmo de búsqueda de centros de masa.


domingo, 17 de febrero de 2013

[Lab CU] Actividad 3. Técnicas de diseño conceptual

Introducción


El diseño conceptual es una fase importante en la planeación y construcción de un producto o servicio.
El diseño de un producto implica conocer a los usuarios, sus características y necesidades, por lo que durante esta fase es necesario aplicar diversas metodologías que ayuden a conocer a los usuarios, su entorno y la manera en que trabajan, y de esta manera centrar nuestros esfuerzos para ofrecer algo que satisfaga a los usuarios.

En la fase de diseño se deben considerar:
  • Necesidades, expectativas e intereses de los usuarios
  • Indagación previa del software a lanzar por medio de encuestas o tests, entrevistas, observaciones y análisis de las reacciones de los usuarios
  • En cuanto al diseño, se hacen pruebas y se corrigen problemas
  • La usabilidad, capacidad y viabilidad del proyecto
  • Consultar al usuario como base para conocer sus opiniones, necesidades y aceptar sus sugerencias con el propósito de mejorar el producto y de ahí tomar decisiones viables.

Los autores H. Beyer y K. Holtzblatt  mencionan que el diseño conceptual se basa en siete partes centradas en el usuario:

  • Preguntas en el campo o indagación contextual la cual considera las necesidades reales de los usuarios
  • Modelado del trabajo
  • Consolidación
  • Rediseño del trabajo
  • Diseño del “plano” del nuevo sistema
  • Prototipo y pruebas de usuario (prototipo puede ser en papel)
  • Implementación final

Pasos o fases

El diseño conceptual es un método iterativo que comienza con una pequeña descripción que expresa de forma muy completa y general la idea del sistema que se va a desarrollar, cómo debe comportarse y hasta cómo puede verse. Se debe tener la precaución de explicar esta idea de una forma que todos los involucrados la entiendan de manera correcta.


Después se procede a una sesión de discusiones cuyo objetivo es provocar una lluvia de ideas, donde todos participen y aporten ideas, desde la más simple hasta la más completa.
Todas las ideas recabadas se comparan con la idea principal, de ellas se obtienen todas las ideas secundarias que concuerden con la esencia principal para así lograr una integración que aporte un verdadero valor agregado al sistema.
Para tener una buena sesión de lluvia de ideas, se debe ver el sistema desde diferentes perspectivas, 3 perspectivas son las más importantes:
  • Interacción: Se refiere a la manera en la que el usuario realizará una acción al interactuar con e sistema. Abarca 4 puntos importantes:
    • Instrucción:  Cómo se le presentan al usuario las actividades, por ejemplo: iconos.
    • Conversación: Cómo el sistema transmite sus estados al cliente, por ejemplo: hints o mensajes.
    • Manipulación: Cómo el usuario interactúa con el sistema, por ejemplo: touch, teclado, mouse.
    • Exploración: Se refiere a la forma en que el usuario se moverá por el sistema, por ejemplo: menús, sistema de archivos.
  • Interfaz: Como su nombre lo dice, abarca aspectos de la interfaz, pero más de estructura y representación de datos, qué tan clara será y qué tan extensible será.
  • Paradigma: Se discute la orientación del sistema, para explicarlo con ejemplos, se refiere si el sistema será de escritorio o será algún tipo se sistema ubicuo, persasivo. etcétera.

Cuando se tiene la idea principal y las ideas secundarias se procede a una sesión de "sketching".
Sketching es la una forma especial de plasmar nuestras ideas de forma física mediante dibujos y bosquejos.
Cualquier miembro puede dibujar, desde lo mas simple a lo mas complejo, el punto es que todos compartan sus bosquejos y al final se junten para representar la idea principal.
Existen varias técnicas para el desarrollo de sketches, todas ellas son prototipos de baja fidelidad:

  • Bocetos: Son simples dibujos, posiblemente garabatos que muestran las ideas que se tienen sobre el sistema, incluyen dibujos de la interfaz con diversas notas sobre cómo ha de verse y que opciones estarán en qué lugar.
  • Storyboard: Son la representación más simple de casos de uso, utilizando caricaturas que muestran las situaciones en las que se puede encontrar el usuario y la solución planteada para resolverla.
  • Maquetas: Son prototipos hechos con papel, cartón o cualquier otro material simple. Son pequeños modelos elaborados, a veces con partes móviles que muestran el funcionamiento esperado de algún producto.
  • Videos: Muy famosos y utilizados, sobre todo en el área de marketing, parecen promocionales de televisón que ilustran de una forma bastante gráfica las caracteristicas sobre un sistema o producto.
  • Diagramas: Uno de los recursos más utilizados en el campo del software, muestran el flujo de proceso esperado en un sistema para expresar más los aspectos técnicos y de ingeniería que los aspectos de diseño visual.


La siguiente etapa es la de evaluación y presentación. Primero de presentación, la idea de presenta a los usuarios, ellos emitirán todo tipo de criticas las cuales servirán de mucho en la etapa de evaluación. Aquí es muy importante ubicar a los usuarios correctos teniendo de antemano el lugar donde se llevará acabo la presentación y la muestra poblacional de los usuarios.

En la etapa de evaluación se analiza toda la retroalimentación obtenida de la etapa de presentación de sketches. Aquí se deben responder las siguientes preguntas, basadas en las opiniones de los usuarios:
  • ¿Qué funciones debe tener el producto?
  • ¿Que relación existe entre una y otras funciones?
  • ¿Qué funcionalidad se considera valiosa?
Entre muchas otras.

Finalmente el proceso se repite hasta que se tiene una idea solida y unificada del producto que se ha de desarrollar


Otras técnicas


¿Qué técnicas aplicar durante el desarrollo conceptual? Además de las que están implícitas en el proceso, como las que acabo de describir, existen otras que pueden servir para profundizar más en el entorno del cliente y conocer mejor sus necesidades:

  • Análisis etnográfico: Sirve para describir el contexto del usuario, su lugar de trabajo y como se desempeña en él y así saber como realiza sus tareas. También detalla la manera en la que se relaciona con los demás.
  • Análisis de perfil de usuario: Sirve para conocer mejor a los usuarios, sus características y objetivos. Suelen usarse tipos especiales de cuestionarios.
  • Entrevista contextual: Es un método menos invasivo, pero consume más tiempo ya que se cita en persona a los usuarios para realizarles distintos tipos de preguntas.
  • Escenarios: Son una descripción verbal o gráfica sobre las situaciones que experimentará el usuario al utilizar el producto o servicio durante sus diferentes etapas.
  • Observación: Se realizan visitas a los usuarios para observar la forma en la que se desenvuelven en su entorno y tomar notas de ello. Puede consumir mucho tiempo realizarla.
  • Cuestionarios y entrevistas: Son un conjunto de preguntas hechas y ordenadas estratégicamente para obtener información precisa sobre lo que piensa, siente o recomienda un usuario.


Recomendaciones


Ésto es un pequeño resumen sobre el diseño conceptual, si se desea saber un poco más de pueden consultar las siguientes referencias:

Es el sitio del doctot Donald A. Norman, experto en el campo de la ciencia cognitiva en el dominio de la ingeniería de la usabilidad.
En él se encuentran una gran cantidad de ensayos sobre diseño de sistemas, usabilidad y diseño centrado en el usuario.
También contiene elementos multimedia como videos y entrevistas sobre conferencias así como recomendaciones a publicaciones y libros.



Es un artículo de una revista que explica la importancia del diseño conceptual y contextual de una manera sencilla, así como su evolución a través del tiempo.



3. Video "Conceptual Design in 2 minutes"

Un video muy ilustrativo que explica de una forma simple las fases importantes del desarrollo conceptual, algunas ya descritas aquí en esta entrada. Muy bueno si se necesita algo gráfico para entender mejor el diseño conceptual.







Es un sitio dedicado al diseño centrado en el usuario o cliente. Se trata de una firma que ofrece servicios de asesoría en el campo, cursos, talleres, etcétera.
Incluye también un blog donde se publican noticias de actualidad relacionadas con el diseño y la experiencia de usuario.


Referencias (otras):
ehow.com | Conceptual Design
Modelo de Proceso de la Ingeniería de la usabilidad y de la accesibilidad. MPIu+a
School of Information and Computer Science | Conceptual Design Slides [ppt]

jueves, 14 de febrero de 2013

[Lab VC] Actividad 1: Ruido y diferencia

Para la actividad 2 de laboratorio se programaron las rutinas necesarias para:

  • Generar ruido "sal y pimienta" (pixeles blancos y negros sobre la imagen)
    • Controlar la cantidad de ruido
    • Controlar la intensidad del ruido (que tan blancos o negros son los pixeles)
  • Limpiar, reducir el nivel de ruido

Como partes opcionales, programar las rutinas necesarias para:
  • Calcular la diferencia entre 2 imágenes
    • Normalizar y binarizar la imagen resultante

El ruido simplemente se genera mediante una rutina que recorre la imagen,  la cantidad de ruido es un valor entre cero y uno que representa una probabilidad, la probabilidad de que un pixel sea sustituido por ruido. Se utiliza la función random.random() para jugar las probabilidades de cambio.
Después se vuelve a jugar la probabilidad pero con un valor de 0.5,  donde esta probabilidad indica si un pixel de ruido será blanco o negro, es decir, pixeles blancos y negros tienen la misma probabilidad de salir.
Por último, una comparación final indica si el pixel se volverá completamente blanco, negro o a algún nivel de gris dependiendo de la intensidad seleccionada. La intensidad es un valor entre 1 y 10 que se multiplica por 13, así si se elige intensidad 10, el color de los pixeles negros será 130 (0+(10*13)) y los blancos de bajarán a 125 (255-(10*13)), lo que los deja en una escala de gris casi idéntica.
Tanto la cantidad y el ruido se controlan mediante un slider en la interfaz de Python.

Eliminar ruido es una rutina donde se recorre cada pixel de la imagen, por cada pixel se toman los 8 vecinos que lo rodean, se obtiene un promedio ya sea RGB o en escala de grises, da igual, después se toma el pixel actual y se compara qué tanta diferencia hay entre sus 3 colores, dicha diferencia es una variable llamada agresividad, que va de 1 a 10 multiplicado por 10, entonces buscara si la diferencia es mayor a 10, 20 o 30 dependiendo del nivel de agresividad deseado. Sin embargo, entre mayor sea el nivel, la agresividad es menor ya que en realidad representa el umbral de diferencia en la comparación, el cual crece. Se dejó parametrizado para poder buscar manualmente el nivel óptimo de limpieza.


Los pasos para calcular la diferencia entre 2 imágenes son:
  • Tomar la imagen original y aplicar filtro de escala de grises.
  • Tomar una copia de la imagen en escala de grises y aplicar filtro de difuminado.
  • Recorrer ambas imágenes (original en grises y original en grises + difuminado) y restar cada par de pixeles en las coordenadas correspondientes.
  • Aplicar el filtro de umbrales para normalizar y binarizar la imagen.
También se programó la rutina para aplicar máscaras mediante convolución discreta.


Resultados

Aplicación y limpieza de ruido

Nivel de ruido
-
1
3
5
Original
Agr. 1
Agr. 5
Agr. 10

Como se puede apreciar en la tabla de comparación; fila 1 muestra imágenes originales, filas siguientes  la aplicación de limpieza a distintos niveles y cada columna representa distintos niveles de ruido.
Como es de esperar, un filtro medio proporciona los mejores resultados, mientras que un valor bajo de diferencia ataca mucho mas la imagen, un nivel de agresividad alto aumenta el umbral de diferencia y muchos pixeles ruidosos no son removidos.
Aplicando varias veces la limpieza de ruido a un nivel medio reduce mucho más y mejor el ruido, sin embargo, comenzarán a notarse partes borrosas en la imágen.

Remover ruido con agresividad a nivel 5 aplicado 5 veces a una imagen con cantidad de ruido 3
Comienzan a notarse bordes irregulares.


Filtro de diferencia

Con un gif se pueden ver las diferentes fases del proceso:

Fases de aplicación del filtro de diferencia


La aplicación del filtro de diferencia es una forma bastante básica de hallar bordes en imágenes.


 

Detalle de las fases relevantes, a la derecha diferencia, a la izquierda normalización y binarización

Máscaras


También se programo la rutina para aplicar máscaras utilizando convolución discreta, les dejo la liga a la entrada de este mismo blog donde explico el método paso a paso con código incluído.



Interfaz



Se realizarón algunos cambios a la interfaz, ahora se incluye una ventana flotante para configurar los diferentes parametros:
  • Umbral
    • Mínimo y máximo
  • Ruido
    • Cantidad e intensidad
  • Remover ruido
    • Agresividad

Código



Ahí pueden encontrar información sobre la aplicación de filtros básicos, máscaras y convolución discreta, ahora incluyo sólo las rutinas de agregar y eliminar ruido, y filtro de diferencia






En la próxima entrega cambiaré de imágenes para no aburrirlos :)

martes, 12 de febrero de 2013

[IT] Traducción

Contribuí en la traducción del documento:


del autor C. E. Shannon.

Se me asignaron las páginas 36 a 40.

El archivo con la traducción es juan.tex, ubicado en el repositorio

[RT] Tarea 2: Implementación de un protocolo

Protocolo para el envió de mensajes a un buzón anónimo.

Propósito

El propósito del protocolo que diseñé es el envío de mensajes cortos vía Internet a un servidor que los almacena en el buzón correspondiente a cada usuario.

La aplicación

Se cuenta con un servidor programado en Python utilizando comunicación UDP, para ello utilicé la librería SocketServer. Para aplicar el protocolo UDP se declara una clase llamada UDPHandler que hereda el módulo SocketServer.BaseRequestHandler, que es un módulo con funciones base para procesar las peticiones al servidor pero en este caso especifico que lo quiero UDP.
Dentro tiene un método llamado handler donde se colocan los pasos que seguirá el servidor para procesar cada petición.
Ahora necesito el servidor UDP funcionando y, como lo quiero multiusuario, necesitamos importar la librería Threading, para ello es necesario implementar correctamente la siguiente clase, llamada ThreadingUDPServer que hereda a las clases SocketServer.ThreadingMixIn y SocketServer.ForkingUDPServer. La clase se queda vacía (pass). El threading server correra un hilo diferente cada vez que se conecte un cliente.

Ésta es la forma más fácil, simple y correcta de implementar un servidor UDP multiusuario.

Lo que acabo de explicar quedaría mas o  menos así en sintaxis:

import SocketServer import threading class MyUDPHandler(SocketServer.BaseRequestHandler): def handle(self): # Como se procesa la peticion # Pasos definidos por el usuario class ThreadingUDPServer(SocketServer.ThreadingMixIn, SocketServer.ForkingUDPServer): pass
El cliente realiza 3 peticiones básicas que son servidas por el servidor:
  • Descargar mensajes: El cliente especifica el ID del buzón para ver los mensajes guardados en el. El servidor lee la base de datos y envía los mensajes uno a uno.
  • Enviar mensajes: El cliente especifica el ID del buzón donde se dejará el mensaje. El servidor guarda el mensaje en el buzón indicado.
  • Obtener buzón: El cliente pide un ID para un nuevo buzón. El servidor genera un ID dinámico ente 1 y 999999.
  • Una opción salir cierra la conexión entre el cliente y el servidor, el servidor continúa corriendo.
Para simular los buzones se utilizan archivos donde el nombre corresponde al ID que se le proporciono al usuario al momento que lo solicito con la opción 3.


Sintaxis del protocolo


Básicamente, el protocolo utiliza 4 datos sencillos dentro de cada "paquete", los datos los nombre de la siguiente forma:
  • Acción: Indica el tipo de petición a servir por el servidor. El dato es de tipo entero.
  • ID1: Reservado para el ID del remitente que envía el mensaje. El dato es de tipo entero.
  • ID2: Contiene el ID del destinatario en el caso del envío de mensajes. En el caso de descarga contiene el ID del buzón a leer. El dato es de tipo entero.
  • Mensaje: Contiene el mensaje a enviar en el caso 2. Contiene el mensaje a descargar en el caso 1. Contiene el ID generado dinamicamente en el caso 3.
El "paquete" del protocolo se representa mediante una tupla de 4 elementos tuple(int, int, int, string), cada  entero ocupa 4 bytes y el mensaje esta limitado a 128 caracteres o 128 bytes, entonces el largo del paquete esta limitado a 140 bytes.
En el caso del empaquetado, si el mensaje es mayor a 128 caracteres entonces es recortado hasta el límite. Si el mensaje es menor a 128 se agrega un caracter de separación ( | ) y el resto se rellena con basura ( . ) porque el tamaño del mensaje es fijo siempre, el caracter de separación le permite al desempaquetador recuperar el mensaje y desechar la basura.

Para codificar los datos se utiliza la librería Struct de python que permite empaquetar una tupla de datos en una representación de bytes utilizando estructuras parecidas a las de C. Se necesita especificar el formato de codificación el cual es "i i i 128s" (int, int int 128string). Después utilizando la función Struct.pack(*datos) los datos se convierten en su presentación en bytes.

Después de empaquetar los datos se encaminan al servidor utilizando un canal UDP y especificando el host y puerto del servidor (localhost, 8080)

El desempaquetado realiza el proceso inverso, con el mismo formato de codificación pero en éste caso es para recuperar la información del paquete. Para ayudar a darle forma a los datos de nuevo se utiliza un diccionario donde se leen los elementos de la tupla recuperada, aquí es donde el caracter de separación agregado en el empaquetado ayuda a recuperar el mensaje y desechar los caracteres de relleno. Los campos del diccionario son: datos["accion"], datos["ID1"], datos["ID2"], datos["mensaje"].


Semántica del protocolo


Como ya expliqué, existen 4 comandos dentro de la aplicación, el servidor solo responde a 3 comandos.

El comando Descargar es especial, es la única opción que permite enviar varios paquetes a la vez alternadamente. Es decir, del lado del servidor, si se desea leer un buzón y el buzón contiene más de un mensaje entonces se leerá el buzón hasta el final y los mensajes se almacenarán en un buffer temporal (list), después por cada mensaje se armará un paquete y se enviarán uno a uno, al final se añadirá un paquete adicional con el mensaje EOT (end-of-transmision).
Del lado del cliente, la aplicación pedirá el ID del buzón que se desea leer y se guardará en el campo ID2 y el campo acción se rellenará con el número 1, los campos ID1 y mensaje permanecen vacíos  La aplicación enviará la petición, creará un buffer temporal (list), y esperará el stream de información. Una vez comenzado el stream desempaquetará todos los mensajes y buscará por la expresión EOT, cuando sea recibida comenzará a vaciar el buffer mostrando al usuario uno a uno los mensajes en la pantalla.

El comando Enviar pide al usuario el ID del buzón destinatario y se guardará en el campo ID2, se pedirá también el mensaje que será empaquetado y se guardará en el campo mensaje. En campo acción se guardará un 2 y el campo ID1 permanecerá vacío.
Del lado del servidor, desempaqueta y recupera la información, buscará por el buzón indicado en el campo ID2 y si existe se abrirá para guardar el mensaje

El comando Obtener no pedirá ningún dato, enviará un paquete donde solo el campo acción contendrá un número 3, los demás campos estarán vacíos.
Del lado del servidor se desempaquetan y recuperan los datos, se generará un ID dinamicamente (random.randint). El servidor responde con paquete igual, solo que ahora en el campo mensaje irá el ID correspondiente al buzón creado y asignado. El usuario puede compartir éste ID para que le envíen mensajes o para recuperar los mensaje de su buzón.

Salir cierra la conexión entre el cliente y el servidor.


Uso de mensajes


Existe un manejo básico de errores y excepciones.

En Descargar el cliente no cuenta con soporte. Del lado del servidor, si  no existe el buzón, se enviarán los mismos paquetes de la misma forma, un stream de 2 mensajes, el primer mensaje contiene la expresión "[X] Error, se intentaron decargar los mensajes de un buzon inexistente (buzon=ID2)", el segundo es la expresión "EOT". El cliente recibirá los datos como si se tratará de una descarga normal pero recibirá el mensaje de error.

En Enviar el cliente no cuenta con soporte. Del lado del servidor, si no existe el buzón se regresará un paquete con el mensaje "[X] Error, se intento dejar un mensaje en un buzon inexistente (buzon=ID2)". El cliente desempaqueta y recupera la información y muestra el mensaje de error en pantalla.

En Obtener no se cuenta con soporte de ambos lados. El servidor se encargará de crear siempre un ID y enviarlo.

De forma general, si hay un error de conexión en ambas partes se genera el mensaje "[X] Error al enviar la peticion (error=ErrorCode)", donde el error code corresponde al código de la excepción capturada por Python en los bloques try...except...

El servidor almacena un LOG de actividades, con los siguientes mensajes.
  • Operación exitosa:
    • [O] Se decargaron los mensajes del buzon [ID2]
    • [O] Se dejo un mensaje en el buzon [ID2]"
    • [O] Nuevo buzon creado [id=ID2]
  • Operación fallida (el servidor los envía como cualquier paquete):
    • [X] Error, se intentaron decargar los mensajes de un buzon inexistente [buzon=ID2]
    • [X] Error, se intento dejar un mensaje en un buzon inexistente [buzon=ID2]
    • [X] Error al enviar la peticion [error=ID2]
El cliente muestra los siguientes mensajes, pero no los almacena en ningú lado:
  • Operación exitosa:
    • [O] Se decargaron [NUM] mensajes
    • [O] Se dejo un mensaje en el buzon [ID2]"
    • [O] Su nuevo buzon es [%s]
  • Operación fallida (el cliente los recibe como respuesta normal del servidor):
    • [X] Error, se intentaron decargar los mensajes de un buzon inexistente [buzon=ID2]
    • [X] Error, se intento dejar un mensaje en un buzon inexistente [buzon=ID2]
    • [X] Error al enviar la peticion [error=ID2]

 

Diagramas







Ejecución





Código

protocolo.py


cliente.py



servidor.py


Referencias:

[Lab RT] Actividad 2: Infografía de los protocolos de redes alámbricas

Infografía:






Referencias: