Detector de Posición mediante Wiimote TITULACIÓN: Ingeniería Técnica Industrial en Electrónica Industrial Autor: Josué Sánchez Barrero Director: Jose Luis Ramírez Falo Fecha: Septiembre 2012 Índice 1 - Índice 2 - Introducción 4 3 - Estado del Arte 5 3.1 - Wiimote 5 3.2 - Proyectos Basados en el Wiimote 6 3.3 - Librerías 8 3.4 - Sistemas de Localización 11 4 - Desarrollo del Software 13 4.1 - Desarrollo de Aplicación Base 13 4.2 - Desarrollo de Aplicación Final 14 4.2.1 - Interfaz de Usuario 14 4.2.1.1 - Lienzo 15 4.2.1.2 - Controles 23 4.2.1.2.1 - Controles para Lienzo 23 4.2.1.2.2 - Controles para Edición de Características de Wiimote 28 4.2.1.2.3 - Controles de Pestaña para Datos de Wiimote32 4.2.1.2.4 - Controles para Seguimiento 36 4.2.1.2.5 - Controles para Área de Restricción 37 4.2.1.2.6 - Controles de Menú 38 4.2.2 - Calibración (Cambio de Coordenadas) 39 4.2.3 - Generación de Coordenadas 47 4.2.4 - Sistema de Notificaciones 48 1 de 199 Índice 5 - Montaje del Tag Infrarrojo 57 6 - Resultados 62 6.1 - Conjunto de Pruebas 62 6.2 - Sala de Pruebas y Montaje 62 6.3 - Mediciones 66 6.3.1 - Medición de Coordenadas en la Matriz 66 6.3.2 - Medición de la Resolución 72 6.3.3 - Medición de una misma Coordenada a lo Largo del Tiempo 74 6.4 - Estudio de los Resultados 75 6.4.1 - Medición de Coordenadas en la Matriz 75 6.4.2 - Medición de la Resolución 81 6.4.3 - Medición de una misma Coordenada a lo Largo del Tiempo 81 7 - Conclusiones 83 7.1 - Funcionalidad del Programa 83 7.2 - Desarrollo del Proyecto 84 8 - Posibles Mejoras 85 9 - Presupuesto 87 Referencias de presupuestos Anexos 89 90 1 - Cálculo Área de Visión de un Wiimote 90 2 - Manual de Instalación y Uso 96 2.1 - Instalación del Software WIPS 96 2.2 - Manual de Uso de WIPS 96 2.2.1 -Requisitos 96 2.2.1 -Conexionado de los Wiimotes 97 2.2.1.1 - Emparejamiento Inicial 2 de 199 97 Índice 2.2.1.2 - Uso continuado 97 2.2.2 - Funcionamiento del Programa 97 2.2.2.1 - Pantalla Principal del Programa 99 2.2.2.2 - Configuración del Mapeado de Wiimotes 100 2.2.2.3 - Configuración de los Mandos 103 2.2.2.4 - Calibrado de Mandos 104 2.2.2.6 - Activación de Seguimiento 108 2.2.2.6 - Sistema de Notificaciones 110 2.2.2.7 - Archivos de Configuración 116 3 - Código 117 3.1 - MultipleWiimoteForm.cs 117 3.2 - UserConf.cs 136 3.3 - ZoneData.cs 170 3.4 - Calibration.cs 173 3.5 - WiimoteMapDraw.cs 175 3.6 - WiimoteInfo.cs 177 3.7 - WiimoteInfoLite.cs 184 3.8 - AllWiimoteInfo.cs 185 3.9 - Preferences.cs 187 3.10 - AutenticateTwitter.cs 192 3.11 - AutenticateEmail.cs 195 Referencias 198 3 de 199 Introducción 2 - Introducción Este proyecto, titulado “Detector de posición mediante wiimote”, tiene como objetivo la realización de un sistema de bajo coste que permita detectar la posición de un objeto contenido dentro de un área determinada. El sistema de detección se basa en el uso de uno o varios Wiimotes (controladores de la videoconsola Wii, de Nintendo) y de un led infrarrojo. Aprovecharé las capacidades de detección de puntos de luz infrarroja y la comunicación vía Bluetooth que tienen los Wiimotes para colocarlos de forma distribuida alrededor de un área determinada. Emplearé varios leds infrarrojos, colocados en el objeto móvil que queremos seguir, de forma y manera que la emisión de su luz infrarroja sea captada por los Wiimotes repartidos por el área de movimiento del objeto. De esta forma, cuando uno o varios Wiimotes vean la luz infrarroja emitida por el led del objeto móvil, transmitirán las coordenadas del mismo al ordenador. Este último calculará la localización del objeto móvil en el área en el que se encuentra contenido y entregará dicha información por pantalla. Por lo tanto, deberé diseñar un programa informático que sea capaz de recoger la información que están transmitiendo todos los Wiimotes, utilizar dicha información para calcular la localización aproximada del objeto móvil, y entregar dicha información por pantalla. Todo ello dentro de un entorno de uso amigable y claro para el usuario. Posteriormente, realizaré un conjunto de pruebas, en una o varias localizaciones, de distinto tamaño, con la intención de comprobar las capacidades del sistema de localización. 4 de 199 Estado del Arte 3 - Estado del Arte Una vez establecidos los objetivos básicos del proyecto, comencé viendo cual era el estado del arte de los aspectos clave a tener en cuenta antes de iniciar este proyecto, tales como: • Funcionamiento del wiimote. • Existencia de proyectos similares, o no, basados en el uso del wiimote. • Librerías usadas para comunicar con el wiimote con un ordenador. Comencé a investigar en el mismo orden expuesto arriba. Busqué información del wiimote y de su funcionamiento para familiarizarme más con el. Además investigué la existencia de otros proyectos en los que se haya hecho uso del wiimote e incluso de si existía alguno de ellos que hubiera perseguido los mismos objetivos que este proyecto. Después me fijé en las librerías que se usaron en los distintos proyectos que encontrara, además de buscar otras distintas que me permitieran escoger la más adecuada para mi proyecto. 3.1 - Wiimote El controlador de la videoconsola Wii, Wii Remote o Wiimote[1], es un controlador inalámbrico que utiliza la tecnología Bluetooth para comunicarse con la videoconsola Wii. Este controlador está dotado de los típicos botones de mando de videoconolas, pero además incluye un acelerómetro y una cámara de 128x96 píxeles de resolución, que unida a un filtro infrarrojo le hace posible observar puntos de luz infrarroja. Aunque lo más impresionante del conjunto es el procesador de imagen de la cámara de alta resolución. Debido a la esperada alta distribución en el mercado de este controlador, a Nintendo no se le hizo complicado invertir en un procesador potente pero que tuviera un coste muy bajo. Este procesador de imagen es el que hace posible que el Wiimote sea capaz de detectar simultáneamente hasta cuatro puntos de luz infrarroja y además enviarnos sus coordenadas X e Y (respecto del Wiimote) y el “grosor” del objeto visto, dándonos información acerca de la distancia a la que se pudiera encontrar. Y todo esto con un tiempo de muestreo de 100 ms. Este mismo chip multiplica la resolución de la cámara en 8x, haciendo que parezca ser una cámara de 1024x768 píxeles de resolución[2]. Con estos datos uno puede ver que este mando es de una gran utilidad en aplicaciones en las que nos interese conocer las coordenadas de un objeto a un coste muy bajo, mucho más bajo que otras cámaras infrarrojas existentes en el mercado para tal fin. La comunidad de entusiastas, programadores y hackers que surgió alrededor de este controlador, hizo posible que, en un tiempo razonablemente corto, pudiera diseccionarse el funcionamiento del controlador. Esto propició que se crearan librerías que fueran capaces 5 de 199 Estado del Arte de comunicar un ordenador con un Wiimote y poder aprovecharse de las grandes posibilidades que ofrece este controlador de bajo coste. Toda la información adicional referente al funcionamiento y las entrañas de este controlador puede ser interesante desde un punto de vista técnico, pero es información poco relevante para los objetivos de este proyecto, con lo cual no se va a incluir más información al respecto. Para mayor detalle del funcionamiento y la construcción del Wiimote se recomienda visitar esta página web: http://wiibrew.org/wiki/Wiimote. 3.2 - Proyectos Basados en el Wiimote En el apartado anterior he hablado acerca de la comunidad de entusiastas, programadores y hackers que surgió alrededor de este controlador. Con la publicación de librerías para obtener los datos de los Wiimotes (de las que se hablan en el punto siguiente), muchas personas comenzaron a pensar en aplicaciones en las que pudiera utilizarse este controlador. Se puede decir que Johnny Lee[3], graduado en Interacción entre Computadores y Humanos por la Universidad de Carnegie Mellon, fue el que mostró a la comunidad qué se podía conseguir con el Wiimote. En 2008 desarrolló la aplicación “Wiimote Whiteboard” que permitía utilizar cualquier superficie enfocada por un proyector como una pizarra interactiva, tan solo con un wiimote, un led infrarrojo, y obviamente un ordenador. De esta forma desarrolló un sistema de pizarra interactiva que no pasaba la barrera de los 50!, y que funcionaba casi tan bien como los sistemas de pizarra interactiva de 1000$ o más que existían en el mercado. Además desarrolló varias aplicaciones interesantes. Una de ellas se valían de dos puntos infrarrojos para ampliar, reducir o mover una imagen por la pantalla, tal y como hoy en día es habitual ver en los móviles de última generación que usan pantallas táctiles capacitivas con el famoso gesto “pinch”. Otra aplicación se basaba en el hecho de invertir el uso habitual del Wiimote, haciendo que este estuviera en movimiento y que el led o leds infrarrojos fueran los elementos que se mantuvieran estáticos. De esta forma desarrolló una aplicación en la que , mediante este sistema, se podía determinar la posición de la cabeza con respecto a una pantalla. Mediante esta información, el programa era capaz de variar la forma en la que una imagen se mostraba mediante la pantalla, consiguiendo así que dicha imagen “reaccionara” a los movimientos de la persona, y renderizara la imagen creando la ilusión de profundidad, como si de una ventana real se tratara[4]. Estos ejemplos fueron los que propiciaron que más personas se interesaran por este uso del wiimote, pero no solo por el hecho de existir y servir como inspiración para los demás, sino por otro hecho de mayor importancia: Johnny Lee distribuyó todos sus ejemplos junto con el código fuente, Así pues, todo el mundo podía observar el funcionamiento de estas aplicaciones y comprender como llevar a cabo sus propias ideas, o llevar los ejemplos de Johnny Lee a un nivel superior, ampliando sus posibilidades e incluso creando productos comerciales. 6 de 199 Estado del Arte Estas son algunas de las aplicaciones que he buscado para poder comprender el funcionamiento del Wiimote, ver de qué librerías se servían y entender como se han integrado en una aplicación. • Whiteboards o pizarras interactivas: He hablado de la WhiteBoard de Johnny Lee, la primera que se dio a conocer, pero sin duda no ha sido la más avanzada de las que han salido puesto que Johnny Lee dejó como encargada del avance de este tipo de aplicaciones a la comunidad de entusiastas. Esta whiteboard soportaba 1 mando, con lo cual en según que ambientes o situaciones, podía verse muy limitada en cuanto a funcionalidades. Otro ejemplo es la WiimoteWhiteboard de Uwe Schmidt[5]. Esta se basa en una librería Java, con lo cual la aplicación es multiplataforma, compatible con entornos Linux y Mac OS, además de Windows. Soporta 2 wiimotes, autoconexión de mandos, varios idiomas y una interfaz agradable y sencilla para el usuario. Además tiene bastante documentación, con lo cual es fácil comenzar a usar dicha aplicación de una forma rápida . Por último destacar la WhiteBoard de Ricardo Bonache. Esta aplicación fue desarrollada en el año 2009 en la URV a modo de PFC. Era una evolución de la whiteboard de Johnny Lee, pero añadiendo soporte para dos wiimotes, una interfaz de usuario más cuidada, y la posibilidad de lanzar desde el mismo programa aplicaciones específicas para whiteboards como Classroom presenter y Click-N-Type. Por haberse desarrollado en la misma universidad y bajo la dirección del mismo profesor, ha sido de gran ayuda su documentación y código fuente, gentilmente prestados por Ricardo para mi análisis[6]. • Aplicaciones basadas en el acelerómetro Existe una gran multitud de aplicaciones desarrolladas para emplear las capacidades de detección de movimiento del Wiimote. Desde el movimiento de pequeños robots teledirigidos, al movimiento de brazos robóticos. Este tipo de aplicaciones han sido las que menos se han tenido en cuenta debido a que el código existente apenas es de utilidad en una aplicación que pretende utilizar las capacidades de detección infrarrojas del Wiimote • Seguimiento 2D Este caso es el que pretendo conseguir con este proyecto. El seguimiento mediante varios Wiimotes de un objeto que se mueve en un plano XY. Encontré un grupo de investigadores japoneses que realizaron un proyecto parecido para realizar el seguimiento de personas con discapacidades mentales en salas de clínicas[7]. Este ejemplo sirvió para comprobar que la consecución del objetivo de este proyecto era posible. Pese a todo, la información aportada en el informe era escasa, redactada en un inglés muy particular, y con 7 de 199 Estado del Arte el código fuente no disponible. Aún así mantuve contacto con alguno de dichos investigadores para conocer más a fondo el funcionamiento de algunos aspectos relacionados con el programa. Aún existe otro proyecto que también buscaba desarrollar un sistema de seguimiento mediante Wiimote, pese a que en este caso, el objeto que se movía y que se pretendía seguir, no sostenía una fuente de luz infrarroja sino un wiimote. Después, llenaban el techo de leds infrarrojos, fijos y colocados en una disposición concreta[8]. • Seguimiento 3D Sabiendo las características en cuanto a visualización de objetos de luz infrarroja que tiene el Wiimote, la unión de varios de ellos, colocados en posiciones estratégicas, podría permitir crear un sistema que nos permitan conocer la posición y la orientación de un objeto en un espacio definido. 3.3 - Librerías Por último, me lancé a la búsqueda de distintas librerías que me permitieran obtener los datos que los wiimotes envían constantemente. No todas las librerías siguientes fueron buscadas al inicio del proyecto; alguna fue buscada posteriormente. Estas son algunas de las distintas librerías que existen: • WiimoteLib Creador: Brian Peek Última versión estable: 1.7 (19-1-2009) Lenguaje de programación: C# y Visual Basic Página web: http://codeplex.com/WiimoteLib (Visitada el 10 de Febrero de 2011) Entorno de desarrollo: Visual Studio (C# o VB) 2005 o superior Esta fue una de las primeras librerías en ser liberada al gran público, razón por la cual es también la librería que mayor distribución ha tenido. Existía gran documentación de esta librería puesto que existía un foro en la página del creador con gran cantidad de mensajes y resolución de problemas, aunque a día de hoy esa sección ya no está disponible. De todas formas existe una gran cantidad de proyectos y ejemplos cuyo código fuente está disponible para descargar en la red. 8 de 199 Estado del Arte • Wiiuse Creador: Michael Laforest Última versión estable: 0.12 (2-4-2008) Lenguaje de programación: C Página web: http://sourceforge.net/projects/wiiuse (Visitada el 19 de Enero de 2011) Entorno de desarrollo: Cualquier IDE C Esta librería permite conectar varios wiimotes. Soporta todos los tipos de transmisión de datos que envía el wiimote. Tiene poca documentación, de hecho la página oficial ya no funciona, pero como se ve abajo, otros desarrolladores se basaron en ella para mejorarla, añadir más funciones o incluso la posibilidad de utilizar lenguaje C++. • WiiC/C++ Creador: RoCoCo Laboratory Última versión estable: 1.1 (18-4-2011) Lenguaje de programación: C y C++ Página web: http://wiic.sourceforge.net (Visitada el 10 de Marzo de 2011) Entorno de desarrollo: Cualquier IDE C o C++ Está basada en la librería Wiiuse, aunque esta extiende la compatibilidad de la misma hasta distribuciones Linux y Mac OS. Además tiene algunas funciones adaptadas para el mundo de la robótica, razón por la cual está soportada por OpenRDK, framework diseñada para una programación ágil de aplicaciones robóticas. • WiiRemoteJ Creador: Michael Diamond Última versión estable: 1.6 (Fecha desconocida) Lenguaje de programación: Java, Processing, Arduino Página web: http://code.google.com/p/bochovj/wiki/WiiRemoteJ (Visitada el 1 de Abril de 2011) Entorno de desarrollo: Eclipse, Processing, Arduino Esta librería tiene la particularidad de estar creada bajo el lenguaje Java, con lo cual hace mucho más sencilla la programación multiplataforma. Soporta todas las 9 de 199 Estado del Arte funcionalidades hardware del wiimote. El único inconveniente es que, según la web antecitada, hay algunos problemas con los stacks bluetooth para algunas versiones de Windows. Para solucionarlos hay una serie de drivers que recomiendan instalar. • WiiYourself! Creador: Desconocido Última versión estable:1.15 (31-3-2010) Lenguaje de programación: C++ Página web: http://wiiyourself.gl.tter.org (Visitada el 10 de Febrero de 2011) Entorno de desarrollo: Cualquier IDE C++ Esta librería está basada en la WiimoteLib, de Brian Peek, pero va un poco más allá en algunas de sus funciones, además de ser completamente en C++. Añade soporte experimental para el altavoz, además de estar optimizada para multitarea. Carece de documentación puesto que, pese a ser una reescritura de la de Brian Peek, remite a su documentación. • Bobcat Creador: Desconocido Última versión estable:1.0 (26-3-2011) Lenguaje de programación: C# Página web: http://code.google.com/p/bobcat-wii-lib Entorno de desarrollo: Visual Studio C# Esta librería también está basada en la WiimoteLib, de Brian Peek, pero añade varias capas encima de la misma para hacerla más sencilla de cara al programador. Tan solo contiene lo necesario para poder acceder a la cámara del wiimote, además está preparada para poder hacer aplicaciones multitáctiles. Tiene mucha documentación y contiene varios ejemplos de lo que esta librería es capaz. Esta librería en particular fue encontrada tiempo después de haber comenzado el desarrollo de la aplicación, y fue de gran utilidad, porque para la calibración del mando se vale de una librería externa, que es la que decidí utilizar en un momento dado para la calibración del mando. 10 de 199 Estado del Arte 3.4 - Sistemas de Localización De cara al diseño de un sistema de localización, es interesante conocer un poco las tipologías de dichos sistemas para comprender qué tipo de sistema es el que vamos a diseñar. Los sistemas de localización, tanto exteriores como interiores, pueden clasificarse en dos tipos: • Infraestructuras dedicadas Se basan en el uso de infraestructuras dedicadas única y exclusivamente para el posicionamiento o localización de objetos. Dentro de esta categoría entrarían los sistemas de localización que se sirven de satélites, tales como el sistema GPS, Galileo o GLONASS. También entrarían dentro de esta categoría sistemas infrarrojos o de ultrasonidos. • Infraestructuras integradas Se basan en el aprovechamiento de una infraestructura de comunicaciones previamente existente. Dentro de esta categoría pudieran entrar sistemas de localización que se valen de redes de internet inalámbricas WLAN, sistemas Bluetooth o la red de telecomunicaciones móvil. Hablando de los sistemas de localización, surgen dos conceptos: Posicionamiento y seguimiento. A primera vista pudiera parecer que ambos términos son lo mismo, pero, en realidad, son distintos: • Posicionamiento Hablamos de posicionamiento cuando el sistema de localización se basa en el cliente u objeto, es decir, que el objeto a seguir determina o calcula de forma activa su propia posición. Esto hace esencial que el objeto tenga a su alcance la información acerca de su propia posición y que además posea del poder computacional adecuado para calcular dicha posición. • Seguimiento Hablamos de seguimiento cuando el sistema de localización se basa en una infraestructura. El objeto a seguir se encarga de enviar señales, con alguna tecnología capaz de ello, y la infraestructura es la encargada de calcular su posición sin que el objeto tenga conocimiento de la misma, algo que podemos considerar una de las posibles desventajas de dichos sistemas. Pese a ello, estos sistemas suelen ser mucho más simples debido a que no hay necesidad de que el objeto a seguir posea hardware complejo ni poder computacional de ningún tipo. El sistema de localización que persigo con este proyecto es claramente un sistema basado en una infraestructura dedicada puesto que tendré que desplegar un número determinado de wiimotes a lo largo de un área determinada con el único fin de detectar un 11 de 199 Estado del Arte objeto que emita luz infrarroja. Además será un sistema basado en el seguimiento, puesto que el objeto a localizar emitirá una señal infrarroja (al objeto emisor lo denominaré tag infrarrojo), señal captada por los wiimotes, que enviarán dicha información a un ordenador para que procese la posición del objeto, sin que este la conozca. Cabe destacar que el sistema de seguimiento que voy a desarrollar, pretende el seguimiento de un robot desarrollado por MBOT Industries, robot que, de hecho, posee gran capacidad computacional, con lo cual cabría la posibilidad de que los wiimotes enviaran la información al mismo robot y que este obtuviera las coordenadas de su localización, y que esto se pudiera observar en una monitor o pantalla externa, enviando esta información de alguna forma desde el ordenador integrado en el robot al monitor. Aún en este caso, hablaríamos de un sistema de seguimiento y no de posicionamiento[9]. 12 de 199 Desarrollo del Software 4 - Desarrollo del Software 4.1 - Desarrollo de Aplicación Base Una vez terminada la búsqueda y recopilación de información necesaria, comencé con el desarrollo de la aplicación. Primero decidí que librería iba a emplear para comunicar el ordenador con los wiimotes. Después de valorar todas las librerías que fui encontrando, decidí utilizar la librería WiimoteLib. Esta librería es la más extendida y existen muchas aplicaciones con sus respectivos códigos fuente (de ser público) para poder ser investigados en caso de duda durante el desarrollo del software. Además se basa en el lenguaje C#, un lenguaje de alto nivel, moderno, que se programa con la IDE Visual Studio, la cual me resultaba familiar a la IDE Delphi, que años antes había empleado. No en vano, el desarrollador del lenguaje C# fue el mismo que desarrolló Delphi. Además, bajo Visual Studio era muy sencillo el desarrollo de una aplicación para Windows, que era el sistema operativo para el cual iba a diseñar la aplicación. Por lo tanto otras librerías multiplataforma tampoco ofrecían ningún beneficio adicional. Por esta razón utilizaré Visual Studio C# 2010 para diseñar y desarrollar esta aplicación. Una vez decidido tanto la librería como el lenguaje de programación, recopilé los códigos fuente de otras aplicaciones o ejemplos desarrollados con la librería WiimoteLib, a la vez que recopilé varios libros que me recomendaron para comenzar a comprender el lenguaje C#. Comencé a desarrollar pequeños ejemplos en Visual Studio 2008 con el libro Teach Yourself Visual c# 2008 in 24 hours[10]. Después de seguir algunas de las lecciones de este libro, decidí dejar atrás los ejemplos y comencé a desarrollar una aplicación básica. La idea era desarrollar un pequeño programa que mostrara en una ventana un punto rojo. Las coordenadas de dicho punto serían enviadas al ordenador por un wiimote que estaría viendo un led infrarrojo. Con esto comenzaría a desarrollar una aplicación completa con el entorno Visual Studio y también me familiarizaría con la librería WiimoteLib y las funciones que esta contiene. Después de varios días de intentos y pruebas, conseguí programar la aplicación base. Costó un poco porque al principio no comprendía muy bien el funcionamiento de la librería, ni los pasos que había que seguir para conectar los wiimotes, ni tampoco algunas de las funciones que se utilizaban para adquirir los datos del Wiimote. Completar esta aplicación me sirvió para disipar estas y otras dudas, puesto que pude hacer pruebas con un wiimote y un led, viendo que pasaba al variar el ángulo del wiimote, al acercar o alejar el led infrarrojo, o al llevarlo a los extremos del área de visión del wiimote. 13 de 199 Desarrollo del Software Figura 01. Aspecto de la aplicación base 4.2 - Desarrollo de Aplicación Final Una vez desarrollado el programa base, me lancé al desarrollo de lo que sería la aplicación final del proyecto. Partiendo del programa base, comencé a ampliarlo, añadiendo las ideas que, hasta el momento, tenía en mente acerca de cómo debía ser dicha aplicación. Inicialmente, y basándome en la interfaz gráfica del ejemplo incluido en la librería WiimoteLib, pensé que el programa debería tener una sola ventana. Dentro de ella integraría varias pestañas en la parte superior, emparejando cada una de las pestañas con un wiimote. Así podría tener vinculada con una pestaña toda la información que envíe cada wiimote, tal y como ya había hecho con mi aplicación base. 4.2.1 - Interfaz de Usuario Teniendo en cuenta que para el funcionamiento de este programa será preciso colocar los wiimotes en la habitación en una localización y posición concreta, deberemos proveer al usuario un método cómodo para ir añadiendo los datos de cada wiimote. Estos datos son los referentes a la posición XYZ del mando dentro del área donde se realizaría el seguimiento. Pensé que crearía una pestaña adicional en la que existiría una zona de 14 de 199 Desarrollo del Software dibujado o lienzo. Esta zona sería semejante al área en la que se pretende hacer el seguimiento del objeto. De esta forma tendremos un lienzo a escala en el cual podremos posicionar los mandos que tenemos activos y repartidos por la sala. Después de comentar esto con los integrantes de MBOT, me sugirieron otras opciones que sería interesante añadir. Entre ellas la de que el usuario pudiera indicar el ángulo que cada wiimote forma con la vertical y la horizontal, ya que esto influye en el área de visionado del mismo, y es un parámetro que el usuario bien pudiera querer modificar. Y ya que esos ángulos hacen variar un área, me sugirieron que dicha área también fuera visible en el lienzo de que disponemos, y así tener información visual del área de visionado de cada wiimote. Tomé nota y comencé a pensar en como implementar todas estas características. A simple vista no parecía entrañar gran dificultad, pero, a la postre, esta tarea la fui desarrollando a lo largo de todo el periodo de desarrollo de la aplicación. En etapas finales del desarrollo decidí añadir alguna funcionalidad adicional al sistema. Junto con mi director de PFC, vimos buena idea añadir un tipo de área de restricción o zona prohibida. Si el robot entrara en la zona delimitada por el usuario, el programa lanzaría un aviso al usuario. 4.2.1.1 - Lienzo Lo que yo llamo lienzo, el entorno Visual Studio lo llama PictureBox. Es un control que se suele utilizar para mostrar gráficos de un archivo de imagen, pero que también puede usarse para dibujar sobre él figuras. Esto se consigue gracias a las funciones gráficas que ofrece la clase Graphics. De esta forma se pueden dibujar círculos, rectángulos u otros polígonos, “creando” un archivo imagen el cual referenciaremos a nuestro PictureBox para que lo muestre por pantalla. Este lienzo deberá ser semejante en dimensiones a la habitación real en la que se esté haciendo el seguimiento. Por lo tanto, previamente, debemos haber introducido los datos de ancho y largo de la sala, para aplicar un factor de proporcionalidad adecuado a lienzo. Este factor de proporcionalidad, estará relacionado con unas medidas máximas de largo y ancho, en píxeles. Esto es debido a que tengo un lugar definido en mi aplicación donde irá colocado este lienzo y de sobrepasarse estas medidas, podría superponerse a otros controles o incluso ser más grande que la ventana de la aplicación. 15 de 199 Desarrollo del Software Figura 02. Dimensiones máximas que podrá tener el lienzo, en píxeles. Diseñé el tamaño máximo de la ventana principal, y a partir de ahí decidí que el lienzo tendría unas dimensiones máximas de 500 píxeles de ancho y 339 píxeles de alto, para dejar sitio a los controles de la parte derecha. Destacar que los controles vistos en la Figura 2 se corresponden con una beta muy temprana de la aplicación. Cualquier control que se puede añadir en el entorno Visual Studio posee un método denominado OnPaint que es llamado cada vez que se vuelve a redibujar el control. Dentro de este método es donde colocaré el código encargado de dibujar todos los elementos que se verán en el PictureBox. Decidí separar estos elementos en capas, de modo que se fueran dibujando secuencialmente. Estos son los elementos a dibujar y el orden de dibujado: 16 de 199 Desarrollo del Software - Dibujo de coordenadas Porción de código que dibuja el punto (0,0) en el lienzo para que el usuario sepa cual será el punto de origen respecto del cual medir las distancias que deberá introducir. Figura 03. Coordenadas dibujadas. - Dibujo de los wiimotes Porción de código que dibuja un círculo de color blanco de 20 píxeles de diámetro en las coordenadas XY que el usuario le indique. Cada uno de estos círculos representa un wiimote. Figura 04. Wiimote dibujado. 17 de 199 Desarrollo del Software - Dibujo del índice de los wiimotes Porción de código que escribe un número encima de los círculos blancos que representan los wiimotes. Este número indicará el orden en el que fue conectado dicho wiimote. Figura 05. Numeración de los wiimotes añadida. - Dibujo del área de visión Porción de código que dibuja un cuadrilátero que representa el área de visión de cada wiimote bajo la altura y ángulos seleccionados por el usuario. Figura 06. Area de wiimotes añadida. 18 de 199 Desarrollo del Software - Dibujo del objeto en seguimiento Porción de código que dibuja un círculo rojo de 10 píxeles de diámetro en las coordenadas XY calculadas por el algoritmo de posicionamiento. Figura 07. Objeto en seguimiento dibujado. Adicionalmente, tenemos otras dos funciones que no tienen que ver con este sistema de capas, pero si pertenecen a lo que podríamos llamar sistema de dibujado: GenerateWiimoteAreaPoints(); Función que genera las coordenadas de los cuatro puntos que marcan los extremos del cuadrilátero que representa el área de visión de cada wiimote. Sabemos que la cámara del wiimote tiene, según especificaciones, 33º de visión horizontal y 23º de visión vertical, sin embargo, estos son cálculos teóricos. Según pruebas con datos reales, podemos obtener unos 41º de visión horizontal y unos 31º de visión vertical[6]. Para este proyecto he decidido tomar éstos últimos ángulos como ángulos de visión del wiimote. Conociendo estos ángulos y el ángulo que forma el wiimote con la vertical podemos calcular los cuatro puntos que definirán el área de visión del wiimote. En la Figura 8 podemos observar una vista de perfil de un caso hipotético en el que tenemos un wiimote colocado sobre una pared a una altura h con respecto al led infrarrojo del objeto a seguir e inclinado bº con respecto a la vertical. 19 de 199 Desarrollo del Software Figura 08. Cálculo teórico del área de visión de un wiimote Teniendo en cuenta el ángulo de visión vertical mencionado anteriormente, 31º, se pueden encontrar los límites de visión vertical del wiimote. Observamos que en la Figura 9 obtenemos dos distancias, x1 y x2, que sumadas nos dan el alto del cuadrilátero que delimita el área de visión del wiimote. Además también obtenemos x0, la distancia que marca la base de un triángulo rectángulo que delimita el espacio “ciego” por debajo del wiimote, en el cual no podremos ver absolutamente nada con dicho wiimote. Análogamente, sumando las tres distancias, podemos obtener la distancia a la que volvemos a entrar en el espacio en el que el wiimote no verá nada. Un desglose completo de los cálculos necesarios para hallar todas estas distancias se encuentra en el Anexo 1. Nos falta ahora conocer el ancho del cuadrilátero que delimita el área de visión del wiimote. Para ello hay que tener en cuenta el ángulo de visión horizontal mencionado anteriormente, 41º, y las dimensiones de las rectas A y B, marcadas en la Figura 8. En realidad, como marca la Figura 9, tenemos dos distancias distintas para delimitar el ancho 20 de 199 Desarrollo del Software de este cuadrilátero, puesto que para todos los casos en los que el ángulo " sea mayor que 0, obtendremos un trapecio en lugar de una figura regular. Figura 09. Vista en planta del área de visión de un wiimote en el supuesto anterior. Según lo expuesto, si el ángulo " siguiera creciendo, llegaría un punto en el que, de forma teórica, el área de visión se estiraría y se alejaría cada vez más del wiimote. Pero esto no sucede en la realidad, ya que cuando el objeto infrarrojo está alejado perpendicularmente entre 5 y 7 metros de la cámara del wiimote, éste deja de ver el objeto. Con lo cual, en este código se ha incluido una limitación, de forma que cuando la longitud de la recta A, vista el la Figura 8, sea superior a un valor de corte, entre 5 y 7 metros, los valores de las rectas x1 y x2 varíen, para reflejar las limitaciones de la cámara en el lienzo. 21 de 199 Desarrollo del Software RotateWiimoteAreaPoints(); Función que rota beta grados el área de visión del wiimote con respecto al punto de colocación del mismo. A la hora de posicionar el mando, tenemos en cuenta dos ángulos. Hasta ahora se ha hablado del ángulo ", el que forma el wiimote con la vertical, y que sin duda es el de mayor complejidad, a nivel de programación, debido a que dicho ángulo forma una parte muy importante en el dibujado del área de visión de los wiimotes en el lienzo. Sin embargo, el otro ángulo también es muy importante, ya que el ángulo #, el que forma el wiimote con la horizontal, es el que determina hacia qué dirección está apuntado el wiimote, ofreciendo gran versatilidad a la hora de intentar cubrir un área. Figura 10. Posibles valores y márgenes de !. Lo que hace el código de rotación es efectuar un cambio de coordenadas de los cuatro pares de coordenadas, girándolos #º con respecto al punto XY, el punto donde está colocado el wiimote en nuestro lienzo. Este código es una adaptación de una función ya existente[11]. 22 de 199 Desarrollo del Software 4.2.1.2 - Controles Hasta ahora he hablado un poco acerca de lo que espero visualizar a través del lienzo, sin embargo no he provisto ningún tipo de control que permita al usuario: • Modificar el aspecto del lienzo • Editar las características de los wiimotes • Visualización de información de wiimotes • Gestionar el seguimiento del objeto. Veamos que pensé para solucionar este aspecto en cada uno de los apartados mencionados. 4.2.1.2.1 - Controles para Lienzo A la hora de colocar los wiimotes en el lienzo, se deben proveer unas coordenadas en las que se dibujarán las circunferencias blancas, que son los símbolos que representan a los wiimotes. Estas coordenadas se pueden proveer mediante dos cajas de texto y un botón de validación: introducimos la coordenada X, la Y y pulsamos el botón para que se dibuje el wiimote recién introducido. Sin embargo pensé en un método más intuitivo. El usuario podría mover el ratón por encima del lienzo y una etiqueta de texto (ToolTip) en la esquina inferior derecha del ratón mostraría las coordenadas, en metros, correspondientes a la posición que el wiimote ocuparía en la sala. De esta forma el usuario puede mover el ratón hasta llegar a la posición XY en la que tiene colocado el wiimote en la sala. Acto seguido el usuario haría clic con el botón izquierdo del ratón y es en ese par de coordenadas XY donde el programa marcaría la posición de nuestro wiimote. Si quisiéramos borrarlo, tan solo tendríamos que pulsar encima del circulo haciendo clic con el botón derecho del ratón. También había que proveer un lugar en el programa que informara de los parámetros o características del wiimote. Pensé que la mejor manera de hacer esto sería también usando el ratón. El usuario haría clic con el botón izquierdo del ratón sobre el wiimote a inspeccionar y el programa mostraría las características de dicho wiimote. Además el usuario podría modificarlas desde ahí mismo. Por lo tanto decidí que crearía un botón que permitiría al usuario entrar en el estado de “Edición”, para añadir o borrar wiimotes en el lienzo, o por lo el contrario, entrar en el estado normal o de “Visualización”, que permitiría al usuario pulsar sobre los wiimotes del lienzo y editar las características de cada uno de ellos. Dentro del estado de “Visualización” el usuario podrá además elegir que capas estarán activas. Según el número de wiimotes utilizados, la carga gráfica puede ser elevada. Además en el lienzo habrá muchos elementos que serán superfluos cuando tan solo queramos ver el objeto en seguimiento. Para tal fin integré unas cajas de selección a 23 de 199 Desarrollo del Software la derecha del botón de cambio de estado. Activando cada una de las cajas de selección activaremos la capa indicada. Figura 11. Vista del botón editar y la selección de capas. En la Figura 11 se puede ver el botón que hace el cambio de modo y el panel de selección de capas activas. Cuando pulsamos sobre él entramos en el modo de edición de lienzo, tal y como se ve en la Figura 12. Figura 12. Entrada en el modo de edición de lienzo. 24 de 199 Desarrollo del Software Una vez dentro de este modo el aspecto visual de la aplicación cambia levemente. Donde antes estaba el panel de selección de capas, ahora aparecen dos botones y un área informativa. El primer botón, el que tiene un wiimote dibujado, indica que estamos añadiendo un wiimote. Si ahora clicamos con el botón izquierdo sobre el lienzo se dibujará un círculo numerado en la coordenada XY donde se realizó el clic. Si se desea borrar el círculo se ha de hacer clic encima del mismo con el botón derecho del ratón. También tenemos la posibilidad de mover un wiimote ya colocado. Si hacemos clic izquierdo en un wiimote, y sin soltar arrastramos el ratón, el circulo se irá moviendo a la vez que el puntero. Cuando hayamos colocado el mando en la nueva posición deseada, soltamos el botón izquierdo del ratón. Cuando terminamos de añadir los wiimotes, podemos pulsar el botón de edición de lienzo, que esta vez mostrará el texto “Detener edición”, para salir del modo edición. Figura 13. Wiimote añadido. También añadí un segundo botón, con una señal de alerta dibujada. Este botón es el que permite dibujar un área de restricción sobre el lienzo. Como he comentado anteriormente, el director de mi PFC y yo decidimos que sería buena idea incluir la posibilidad de que le usuario pudiera crear un área restringida. Esta es la función de este botón. Cuando el usuario pulsa este botón pasa a un segundo estado dentro del modo “Edición”, dentro del cual se puede dibujar un cuadrilátero dentro del lienzo. Para hacerlo, hacemos clic sin soltar con el botón izquierdo del ratón, arrastramos y soltamos el botón cuando lo deseemos. 25 de 199 Desarrollo del Software Figura 14. Área de restricción añadida. Ahora nos encontraremos en el caso de haber añadido un wiimote al lienzo y haber vuelto al modo de “Visualización”. Podemos hacer clic con el botón izquierdo del ratón sobre el círculo del lienzo para que en la derecha de la pantalla, en la parte de los controles de wiimote, de los cuales se hablan en el siguiente apartado, aparezcan las características de dicho wiimote y el usuario pueda editarlas: Figura 15. Modo visualización de wiimote junto con los controles asociados. He de decir que este sistema tiene alguna limitación. Cuando estaba desarrollando el sistema de visualización tuve que decidir como almacenaría la información de cada wiimote. Investigando como la librería WiimoteLib gestiona el almacenamiento de los wiimotes encontrados, observé que existe una clase denominada WiimoteCollection, que 26 de 199 Desarrollo del Software almacena todos los wiimotes en un conjunto ordenado de elementos, denominado colección. Este tipo de estructura permite hacer referencia a un grupo de elementos relacionados como si de un único objeto se tratara[12]. Una vez agrupados, se podía acceder a cada uno de los elementos, o wiimotes, para conectarlos y posibilitar el envío de datos por parte del wiimote. Cabe destacar que esto se hace en el momento de inicialización del programa, detectando cuántos wiimotes están conectados al ordenador vía bluetooth y añadiéndolos a una instancia de la clase WiimoteCollection creada por mi. Teniendo esto en cuenta, no me pareció algo razonable pensar que el usuario conectara, por ejemplo 5 wiimotes al ordenador, iniciara el programa y después quisiera conectar o desconectar alguno más. Interpreté que esto sería una configuración distinta y que podría hacerse perfectamente antes de arrancar la aplicación, o volviéndola a ejecutar introduciendo los nuevos parámetros. Posiblemente pudiera llegarse a hacer un sistema que permitiera al usuario eliminar de la colección de wiimotes conectados alguno de ellos, pero realmente el tiempo y la complejidad de programación hubieran sido más elevados, y teniendo en cuenta que no era una parte determinante del objetivo del proyecto, decidí seguir adelante con el sistema de visualización ideado. Uno de los defectos de este sistema era que tampoco permitía al usuario mover la coordenada XY de un wiimote si se había colocado un wiimote después del que se quiere editar. Por ejemplo, si el usuario ha colocado 5 wiimotes, y desea mover la coordenada XY del 2º wiimote que colocó, el usuario tenía que borrar los 4 anteriores (incluido el que se quiere editar), perdiendo los datos que el usuario haya modificado de estos wiimotes y obligándole a volver a introducir los datos y características de los cuatro wiimotes. Al principio del desarrollo esto era aceptable, pero llegado un momento vi que este era un inconveniente demasiado engorroso como para no intentar modificarlo. Por lo tanto hice unos cambios en el programa que me permitieron eliminar esta desventaja. Ahora el usuario puede mover las coordenadas de un wiimote cualquiera siempre que se encuentre dentro del modo de edición. 27 de 199 Desarrollo del Software 4.2.1.2.2 - Controles para Edición de Características de Wiimote Hasta ahora se ha visto que para generar el área de visión de un wiimote hace falta conocer muchas variables: la posición del wiimote, la altura del wiimote y los ángulos que forma el mismo con la vertical y la horizontal. Ante tal cantidad de parámetros a mostrar y modificar, es esencial facilitar al usuario la entrada de datos mediante una interfaz sencilla y amigable. En un primer momento opté por el diseño visto en la figura siguiente: Altura led IR Actualización de cambios Ángulos wiimote Figura 16. Controles de características de wiimote (Versión inicial). Como se observa en la figura, diseñé los controles para las características principales. Existen dos cajas de texto para introducir el ángulo de los ángulos # y " y una barra deslizante para indicar la altura del led infrarrojo. Una vez modificados estos datos se pulsa el botón “Guardar cambios” para actualizar el lienzo y mostrar la nueva área calculada. Sin embargo este diseño tenía algunos inconvenientes. El principal inconveniente no era tanto de diseño de controles como de concepto: hasta aquí había supuesto que todos los wiimotes se situarían a la misma altura, pero esto no tenía porqué ser así. Podríamos colocar un wiimote a 2 metros y otro a 2,5 metros, quizás intentando sortear algún obstáculo presente en la habitación. Con lo cual era preciso añadir otra variable más, la altura del wiimote. Tampoco me convencía la forma de introducción de los ángulos y pensé en como añadir un control algo más adecuado a la variable que se quería controlar. Por ello pensé que quizás un controlador tipo “knob” o de rueda sería adecuado ya que el usuario podría ir girando la rueda, modificar los grados de los ángulos e ir observando como afectaría esto al área de visión y así poder encontrar una configuración óptima, aunque dejara también la posibilidad de poder introducir los datos mediante la caja de texto. Este tipo de controles no están incluidos de serie en las librerías de Visual Studio, por lo tanto tendría que crearlo yo mismo, o algo mucho más factible, que alguien ya lo hubiera hecho. Pronto deseché la posibilidad de intentar diseñar el control por mi mismo ya que no solo no sabía como hacerlo, sino que una parte tan pequeña del programa iba a consumir una cantidad de 28 de 199 Desarrollo del Software tiempo de programación demasiado elevada. Así que me tomé un día para investigar sobre el asunto y ver si existía en la red algo parecido a lo que yo tenía en mente. Fue así como llegué a la página llamada VCSKicks[13], una página con recursos para el lenguaje C# y el entorno Visual Studio. Uno de estos recursos era precisamente una librería para Visual Studio que contenía un controlador tipo “knob” con el aspecto mostrado en la Figura 17. Figura 17. Aspecto de los controladores de la librería AngleAltitudeControls. Se puede observar en la figura anterior dos tipos de controladores. En el primero, el ángulo se modificaba mediante mover con el puntero del ratón el radio de la circunferencia, mientras que el segundo consistía en mover por la circunferencia la cruz, para variar tanto el ángulo como la altura, en un intento de copiar un tipo de controlador existente en el software de tratamiento de imagen Adobe Photoshop[14]. Para mi aplicación, el segundo tipo de controlador no era útil, ya que tan solo tenía que modificar una magnitud a la vez. Pero el primer tipo de controlador era exactamente lo que yo tenía en mente y además el autor incluía el código fuente no solo del ejemplo mostrado arriba, sino de las librerías. De esta forma podría modificar dicho código fuente y compilar una librería adecuada a mis necesidades. Además la licencia de uso también era apropiada para el proyecto, con lo cual decidí seguir adelante con la idea. Una vez obtenido el código fuente, modifiqué algunos parámetros de la librería para modificar los márgenes de los posibles valores de los ángulos ya que, por ejemplo, el ángulo " no va de 0º a 360º, sino de 0º a 90º. Una vez modificado esto, compilé la nueva librería y procedí a incluirla en el proyecto con el fin de poder usar estos nuevos controladores. Los controladores tienen un nuevo evento incluido, llamado AngleChanged(), que nos permite lanzar una secuencia de código cuando movemos el controlador. Por lo tanto sería preciso crear algunas funciones nuevas para encargarse del tratamiento de los valores introducidos mediante los controladores. Aunque hasta el momento, estos cambios tampoco mejoraban la aplicación ya que el usuario aún tenía que pulsar un botón para que 29 de 199 Desarrollo del Software el dibujo del lienzo se actualizara. Fue en este momento cuando decidí que el botón de guardado de datos debía desaparecer para dar paso a una actualización “al vuelo” del dibujo del lienzo. Cada vez que el usuario cambiara la altura del led infrarrojo, la del wiimote, o la de alguno de los ángulos # o ", se debería forzar el redibujado del lienzo. Pero había que prestar atención a la entrada de datos, ya que optando por una actualización constante del dibujo, si el usuario introdujera mal los datos, podría provocarse cuelgues en el programa. Decidí diseñar estos controles siguiendo estas directrices: • Ángulo # ✓Su valor irá de 0º a 180º y de 0º a -180º ✓En la caja de texto no se podrán introducir letras ✓Si se introduce un número, deberá estar contenido en los límites ✓Si en la caja de texto se introduce un guión: ‣ Si había un número, se cambia el signo del mismo ‣ Si no había número, se introduce un guión, como paso previo a la introducción de un número. ‣ Después de introducir el valor definitivo, habrá que pulsar la tecla intro para validar • Ángulo ": ✓Su valor irá de 0º a 90º ✓El resto igual que las directrices para el ángulo ". • Alturas wiimote y led: ✓La altura mínima que se podrá asignar al wiimote será la altura a la que se encuentre el led ‣ Así pues cuando la altura del led varíe, deberán variar las alturas de wiimote que fueran precisas ✓La altura máxima que se podrá asignar al wiimote será la altura de la sala ✓Para variar las alturas habrá dos barras deslizantes que variarán el aspecto del lienzo en el momento en el que el usuario las mueva. ✓Se podrá introducir la altura del wiimote mediante una caja de texto, que solo aceptará números. Se deberá pulsar la tecla intro para validar el valor introducido. Teniendo en cuenta todas estas directrices, seguí adelante con el desarrollo de los controles. Así acabaron siendo los controles de los wiimotes: 30 de 199 Desarrollo del Software Altura led Ángulos IR wiimote Datos adicionales Actualización de cambios Figura 18. Controles características Wiimote (Versión final). En la Figura 18 se puede observar el aspecto definitivo de los controles que se encargan de facilitar al usuario la modificación de las características de los wiimotes. Como se puede observar en la misma figura, también se nos informa cuál es el wiimote que se está modificando. Además, existen tres líneas informativas que muestran la posición X e Y del wiimote en la realidad y nos muestran el área de visión que abarcará el wiimote con dichos parámetros. Destacar también la limitación existente entre las barras de desplazamiento. Se ha de tener en cuenta que el led infrarrojo no puede estar colocado por encima del wiimote, puesto que el mando nunca verá el led. Así pues cuando desplazamos la barra que controla la altura del led, verificamos que la altura sea menor que la de los wiimotes colocados. Si no fuera así, por código, subirá la altura de los wiimotes que no cumplan con la condición y se evitará que el usuario pueda configurar un wiimote a una altura menor que la del led infrarrojo. 31 de 199 Desarrollo del Software 4.2.1.2.3 - Controles de Pestaña para Datos de Wiimote Previamente dije que mi idea para la aplicación era que estuviera compuesta de una ventana principal y que dentro de ella hubiera distintas pestañas. Figura 19. Muestra de las pestañas. En etapas tempranas del desarrollo creí conveniente pensar en la cantidad de pestañas a tener en la aplicación. Tenía claro que cada wiimote debería tener una pestaña independiente, pero no tenía tan claro cuantas pestañas aparte de esas debería crear. Pensé en crear dos pestañas, una llamada “Configuración” y otra denominada “Visión general”. El contenido de estas dos pestañas fue cambiando a lo largo del desarrollo. Por ejemplo, en un primer momento iba a haber un lienzo en la pestaña de “Visión general” cuyo contenido sería la coordenada XY absoluta del objeto visto por todos los wiimotes. Mientras que en la pestaña de “Configuración” iba a existir otro lienzo en el que iría colocando los wiimotes y configurando sus características. Conforme desarrollé esta parte de los controles vi que, en parte, la idea no era buena puesto que estaba dividiendo la funcionalidad del lienzo en dos lugares distintos; en uno configuraba y en el otro visualizaba, y el usuario debería ir alternando entre dos pestañas distintas. Todo debía estar en un mismo lugar, que un mismo lienzo fuera el que permitiera la configuración de los wiimotes de la sala y la visualización del objeto infrarrojo. Con lo cual hubo un periodo en el desarrollo del programa que la pestaña de “Visión general” perdió su utilidad, aunque creí conveniente dejarla por si se me ocurría alguna buena idea con ella. Hablaré antes de las pestañas de wiimote. Cada una de estas pestañas va ligada a uno de los wiimotes que hayan sido conectados al ordenador y se crean justo después de la conexión de los mismos por parte de la librería WiimoteLib. Cada una de las pestañas de wiimote alberga una instancia de un control de usuario creado por mi en Visual Studio. Esto quiere decir que se creó un control de usuario base tal y como se muestra en la Figura 21, pero que no está vinculado a ningún wiimote en concreto. Es cuando se crea la instancia del mismo que se vincula al wiimote 1, 2 o n, haciendo que el código para la generación de estas pestañas sea más sencillo y eficaz. Este control lo llamé WiimoteInfo. Este control de usuario, puesto que estará vinculado a un wiimote, debería contener parte de los datos que obtenemos del wiimote, tales como la batería restante del mismo, la posición XY del objeto infrarrojo (si es que viera un objeto infrarrojo) así como también los controles necesarios para la calibración del wiimote (de los cuales se habla en el apartado 4.2.2). 32 de 199 Desarrollo del Software Puesto que los datos que se van a ver correspondientes al objeto infrarrojo a seguir serán un par de coordenadas, parecía lógico incluir no solo un par de números mostrando las coordenadas XY, sino también un lienzo en el que dibujaremos un circulo rojo en la coordenada adecuada, para que el usuario pueda, en un vistazo, ver lo que el wiimote está viendo por su cámara. Cabe destacar que si bien la resolución de la cámara del wiimote es de 1024 x 768 píxeles, el lienzo tiene un tamaño de 512 x 384 píxeles debido a que incluir un lienzo del mismo tamaño que la resolución del wiimote, hubiera ocupado un espacio demasiado grande, y hubiera hecho que las dimensiones de la aplicación fuera incómodas para la mayoría de ordenadores. Figura 20. Aspecto de la pestaña de wiimote base (Versión inicial). En la Figura 20 se puede observar el aspecto que tenía en las versiones iniciales una pestaña de wiimote. En ella podemos encontrar todos los datos mencionados anteriormente. Pasado el tiempo de desarrollo, el aspecto de esta pestaña se fue perfilando y adaptando a las nuevas necesidades que iban surgiendo. Por ejemplo el sistema de calibrado cambió bastante, y la parte derecha de la pestaña acabó más optimizada en temas de espacio. 33 de 199 Desarrollo del Software Figura 21. Aspecto de la pestaña de wiimote base (Versión final). En la Figura 21 se pueden observar los cambios descritos anteriormente, aunque no los voy a comentar puesto que la mayoría de ellos tienen que ver con la calibración y de ellos hablo en un apartado posterior. Pasado el tiempo se me ocurrió algo para que la pestaña “Visión general” tuviera un propósito. Pensé que podría usar esta pestaña para ofrecer una visión general de todos los wiimotes conectados al programa. Está claro que cada wiimote tiene una pestaña para ver los datos y editar la calibración de cada uno de ellos, pero creí que poder observar a la vez todos los datos que están enviando los wiimotes conectados podía ser algo atractivo para el usuario. Entonces pensé en crear un nuevo control de usuario reducido del control usado para las pestañas de wiimote. Cogí el control WiimoteInfo y lo reduje a su mínima expresión para que en una misma pestaña pudiera incorporar la información del peor caso, esto es un sistema con 7 wiimotes conectados. Llamé a este nuevo control de usuario WiimoteInfoLite y en la Figura 22 se puede observar su aspecto. Figura 22. Aspecto del control de usuario WiimoteInfoLite. 34 de 199 Desarrollo del Software Como se puede observar en la Figura XX, lo que más espacio ocupaba en el control de usuario WiimoteInfo era el lienzo de visualización de coordenadas. Por lo tanto, reduciendo al máximo este lienzo se podría reducir drásticamente el tamaño del control. Por esta razón se vio que un tamaño de 128 x 96 píxeles era suficiente. Después se agruparon los datos de wiimote a la derecha del lienzo, quedando un diseño lo más compacto posible. Acto seguido, cree otro control de usuario que denominé AllWiimoteInfo que es el que se colocaría dentro de la pestaña “Visión general” y que se encargaría de decidir cuántos controles WiimoteInfoLite serían necesarios, de colocarlos en el control de usuario y de vincularlos con cada uno de los wiimotes contenidos en la colección generada al iniciar el programa. Dividí el tamaño del control AllWiimoteInfo en 8 partes iguales, para poder introducir los 7 controles máximos posibles dentro de una sola ventana, tal y como se puede observar en la Figura 23. Figura 23. Aspecto de la pestaña “Visión general”. De esta forma conseguí que tan solo se crearan las instancias de control de usuario necesarias y que si tan solo tenemos, por ejemplo, 2 wiimotes conectados, tan solo veamos los controles WiimoteInfoLite necesarios. Mencionar que los datos de estos controles se actualizarán cada vez que un wiimote provoque una interrupción. Entonces se llamará a la función UpdateLiteTab() que será la encargada de leer en ese momento los datos de todos los wiimotes conectados y actualizar los que se mostraban previamente. 35 de 199 Desarrollo del Software 4.2.1.2.4 - Controles para Seguimiento Se da la situación que mientras estamos visualizando el objeto, el usuario no debería poder editar el lienzo, ni tampoco las características del wiimote, ya que los cálculos de seguimiento se realizan teniendo en cuenta estas características, por lo tanto hay que desarrollar un control que inicie o detenga el seguimiento, y algunas lineas de código para activar o desactivar los controles de lienzo y de wiimote. Esta parte del desarrollo fue de las últimas, con lo cual apenas ha habido cambios entre la idea inicial y el resultado final. Decidí primeramente crear un botón que activara y desactivara el seguimiento que fuera lo suficientemente claro para el usuario. El usuario debía saber rápidamente si estaba el seguimiento activado o no. Figura 24. Aspecto de los dos estados del botón de seguimiento. Como se ve en la figura superior, diseñé un botón con colores, rojo y verde, con distinto mensaje de texto, que permiten al usuario saber en qué estado se encuentra el seguimiento. Cuando el botón está verde el seguimiento no está iniciado, y por lo tanto los controles de edición de lienzo y de características de wiimote están activos. Cuando el botón está rojo el programa está en modo de seguimiento y por lo tanto los controles de edición y configuración de wiimote han quedado deshabilitados. Como parte de estos controles de seguimiento se creó una nueva ayuda informativa para el usuario. Se rediseñó la parte inferior derecha de la aplicación para que, cuando se iniciara el seguimiento, mostrara un panel con información de seguimiento útil para el usuario. En dicho panel decidí informar de las coordenadas en píxeles y en metros del objeto en seguimiento. También añadí un lienzo de visualización que mostrar al usuario qué mandos ven el objeto infrarrojo cuando el seguimiento está activado. De esta forma el usuario puede conocer con los datos de qué mandos se está realizando el cálculo de coordenadas final del objeto en seguimiento. Figura 25. Captura del lienzo de visualización de la información de seguimiento. 36 de 199 Desarrollo del Software 4.2.1.2.5 - Controles para Área de Restricción ! Resulta muy útil para un usuario que pretende el seguimiento de un objeto, disponer de un programa de seguimiento que sea capaz de avisarle cuando este haya entrado en alguna zona no deseada. Parte de estos controles están comentados en el apartado 4.2.1.2.1 - Controles para lienzo, aunque tan solo se mostraba como añadir una zona de restricción. Comentaré en este apartado más funcionalidades de esta característica. Figura 26. Controles de área de restricción. En la figura superior se pueden observar los controles que permiten al usuario usar esta función. Tenemos dos controles circulares denominados RadioButton. Con estos controles escogemos si queremos que el área restringida sea el interior del cuadrilátero que hemos dibujado, o el exterior. Además tenemos una caja de selección que nos permite activar o desactivar el sistema de restricción. El procedimiento es muy sencillo: según hayamos seleccionado una restricción interior o exterior, el programa comprobará que el objeto no entre en dicha zona. Si el objeto entrara en esta zona, el programa lanzaría un aviso al usuario (más adelante ampliaré esta información). He creído conveniente añadir también una temporización a esta función. Pongamos por caso que el objeto se encuentra moviéndose entre la zona prohibida y la zona válida. El programa estaría lanzando avisos constantes al usuario. Con la temporización que he añadido, si el objeto entra en la zona prohibida el programa lanza un aviso. Si el objeto sale y vuelve a entrar en dicha zona en menos tiempo del que se ha seleccionado, se considerará una falsa alarma. Hasta que no haya pasado ese tiempo no volverán a estar activos los avisos al usuario. Esta función de temporización también podrá ser deshabilitada por el usuario si así lo deseara. 37 de 199 Desarrollo del Software 4.2.1.2.6 - Controles de Menú ! En la mayoría de aplicaciones de Windows tenemos una barra de menús en la que podemos efectuar distintas acciones. Vi útil no sobrecargar esta barra con demasiadas opciones, tan solo añadir las que fueran a utilizarse. Figura 27. Aspecto de la barra de menús del programa. - Submenú “Archivo” Mediante este submenú el usuario puede acceder al panel de preferencias, en el que se pueden modificar varios parámetros del programa. Además existe la posibilidad de cargar el último mapeado de wiimotes que se utilizó en el programa, para ahorrar faena al usuario. Por último, también nos permite cerrar el programa Figura 28. Aspecto del submenú “Archivo”. - Submenú “Ver” Mediante este submenú el usuario puede acceder a la carpeta donde están alojados los archivos en los que se guardan los datos de calibración. Ya que dichos archivos son fácilmente editables, es posible que el usuario quiera acceder a ellos para modificarlos. O quizás el usuario quiera coger estos archivos para cargarlos en otro ordenador. Figura 29. Aspecto del submenú “Ver”. 38 de 199 Desarrollo del Software - Submenú “Ayuda” Mediante este submenú el usuario puede acceder al “Manual de usuario”. Este manual es el mismo que se puede consultar en el Anexo 2 de esta memoria. Además el usuario podrá acceder a una ventana en la que se da información de este programa. Figura 30. Aspecto del submenú “Ayuda”. 4.2.2 - Calibración (Cambio de Coordenadas) Hasta el momento he mencionado en varias ocasiones que el wiimote nos envía mediante Bluetooth las coordenadas XY de hasta cuatro puntos infrarrojos, aunque en este proyecto esta cantidad está limitada a uno. Con lo cual, cuando el wiimote ve un objeto infrarrojo, nos envía un par de coordenadas. Este par de coordenadas estará comprendido entre 0 y 1024 píxeles para el eje X, y 0 y 768 píxeles para el eje Y. Pero se puede ver que este par de coordenadas serán relativas al origen de coordenadas de la cámara del wiimote, tal y como se muestra en la Figura 31. Por lo tanto estas coordenadas no eran útiles tal y como las entrega el wiimote, sin embargo, realizando un cambio de coordenadas relativas a absolutas si que podría aprovecharlas. Figura 31. Coordenadas relativas a la posición del wiimote. 39 de 199 Desarrollo del Software Figura 32. Plano absoluto y plano relativo. Como se puede observar en la Figura 32, el wiimote no ve un rectángulo en el plano XY de la sala, sino un trapecio, debido a que, por las leyes de la perspectiva, la cámara del wiimote ve los objetos distorsionados y la superficie cuadrada de su área de visión es ahora trapezoidal, tal y como también se puede observar en el apartado 4.2.1.1 donde se explica la función GenerateWiimoteAreaPoints. Para tratar estas distorsiones son necesarias unas transformación de proyección, en este caso de perspectiva, que determine una correspondencia entre las coordenadas XY relativa y absoluta, también llamada homografía[X]. Figura 33. Matriz de homografía entre plano del objetivo y plano real [15]. 40 de 199 Desarrollo del Software Este tipo de transformación se realiza mediante la multiplicación de una matriz, del mismo nombre que la transformación, de homografía [Figura 33]. Esta matriz será la encargada de transformar las coordenadas relativas que envían los wiimotes a coordenadas absolutas. Obviamente esta tarea tiene una gran carga matemática, pero este tipo de transformaciones geométricas ya existen dentro de librerías gráficas de distintos lenguajes. Además hay programadores, como Johnny Lee, que ya crearon una matriz de homografía para que su pizarra interactiva fuera funcional. Incluso algunos han compilado librerías que simplifican al máximo esta tarea puesto que contienen funciones que entregan las coordenadas transformadas. El usuario tan solo tiene que haber almacenado los datos necesarios para formar la matriz de homografía, siendo los cálculos invisibles tanto para el usuario como para el programador. Debo mencionar que la calibración de los wiimotes fue la parte del proyecto que más problemas me creó, más que nada por un error de entendimiento del concepto. Si atendemos a la definición pura y dura del concepto, observamos que la calibración es simplemente el procedimiento de comparación entre lo que indica un instrumento y lo que “debiera indicar”. Cuando se calibra un instrumento, se persigue que los valores de la magnitud que se esté midiendo con el mismo sean correctos y estén dentro de unos márgenes de error previamente establecidos. Observando esta definición, da la impresión de que esta “calibración” persigue un ajuste hardware o software del wiimote, como si intentáramos corregir un error. Pero no se refiere a esto. Como hemos visto, se adoptó el termino de calibración para definir la necesidad de crear una matriz de homografía para realizar la transformación de perspectiva necesaria para obtener las coordenadas adecuadas. En un primer momento, mientras desarrollaba el programa y llegaba a la fase de desarrollo del tema del cambio de coordenadas decidí usar el código de transformación de perspectiva que incluía Johnny Lee en su proyecto de pizarra interactiva, y que es el que se usa en mayor o menor medida en este tipo de aplicaciones. En una aplicación de pizarra interactiva se introducía un apartado de cambio de coordenadas en el que se precisaba que el usuario marcara con un flash de un led infrarrojo (una pulsación) cuatro puntos en la pantalla o superficie que se fuera a utilizar como pizarra, formando las esquinas de un rectángulo. Las coordenadas de estos cuatro puntos en la pantalla eran conocidas, puesto que era el programa el que los dibujaba en la misma, y además se obtenían mediante unos cálculos sencillos en los que intervenía la resolución del monitor. 41 de 199 Desarrollo del Software Figura 34 Aspecto de uno de los cuatro puntos de calibración. Por lo tanto, cuando el usuario marcaba uno de los cuatro pares de coordenadas, el programa almacenaba dos pares de coordenadas. Uno era el par de coordenadas donde se había dibujado ese punto en la pantalla, y otro era el par de coordenadas que enviaba el wiimote al ver el flash que producía el led infrarrojo en ese punto. Estos eran los datos que se introducían pues en la matriz de homografía, la cual calculaba cualquier punto visto por el wiimote en un punto sobre la pantalla o superficie de la pizarra interactiva. Aquí estribaba una diferencia importante entre mi proyecto y los que tenían que ver con pizarras interactivas, y que fue la razón de mi confusión: en esos proyectos se conocían las coordenadas del plano donde se quería posicionar el objeto, en este caso el lápiz infrarrojo, ya que esas coordenadas las generaba el programa para poder dibujar las cruces que indicaban al usuario donde debía hacer un flash con el led infrarrojo. Además las superficies que hacían de pizarra interactiva “cabían” dentro del área vista por el wiimote. Sin embargo en mi caso el área total era mucho más grande que el área que podía ver un wiimote y además no tenía claro como podía hacer yo todo el sistema de los cuatro puntos ya que, ¿como de grande sería el rectángulo? ¿cómo iba a saber que coordenadas tendría? No era capaz de conseguir que este apartado de la calibración funcionará, y esta fue la razón por la que, tal y como comenté en el apartado 3.3, decidí incluí la librería de calibración “FourPointCalibration”, la cual estaba agrupada dentro de la librería Bobcat. Decidí intentar cambiar la parte de programa que generaba las coordenadas y por lo tanto sustituí el código de Johnny Lee pensando que había algún tipo de problema con este y que esta librería sería la solución. Pero era una solución para un problema que no existía, puesto que lo que era erróneo era mi entendimiento del problema, no el código. Así que después de un par de semanas de dar muchas vueltas, hice una visita a mi director de proyecto para intentar ver este tema desde otro punto de vista. Durante esa conversación pudimos aclarar el concepto de la calibración e intentamos ver como podíamos abordarlo. La cosa fue bien puesto que al término de la reunión tenía muy claro qué tenía que hacer y también cómo hacerlo, con lo cual había eliminado un problema 42 de 199 Desarrollo del Software importante. Tan solo había que cambiar la forma de pensar. Esto no era una pantalla de ordenador, no podía calcular las coordenadas de los puntos en donde se dibujaban las marcas de calibración, y yo lo que estaba haciendo era dar unas coordenadas relacionadas con las dimensiones entre las marcas de calibración. Es decir, puesto que no tenía pantalla de ordenador en la que posar un led infrarrojo para emitir una pulsación de luz, fabriqué una plantilla de calibración. Esta plantilla tenía cuatro marcas de calibración, con una distancia determinada entre las mismas. Entonces lo que hacía era calcular las coordenadas que tendrían esos puntos como si las hubiera dibujado en la pantalla del ordenador. Un análisis de la solución anteriormente descrita me llevó a ver cuales eran los errores en los que había incurrido. Estaba haciendo varios cambios de coordenadas: de las relativas al wiimote a las coordenadas que yo le estaba dando, o sea como si estuviera marcando esos puntos en una pantalla de ordenador y no en el suelo como en realidad estaba haciendo. Además, después todavía realizaba otro cambio entre esas y las absolutas de la sala. Estaba haciendo varios cambios de coordenadas, todos ellos erróneos en el concepto, no en el cálculo. Para la solución correcta tan solo era necesario un único cambio de coordenadas. Para ello seguiría utilizando la misma plantilla de papel con las cuatro marcas de calibración que había hecho, pero esta vez introduciría en el programa las coordenadas absolutas, en metros, de las cuatro marcas de calibración. Con lo cual la matriz de homografía esta vez estaría bien generada, puesto que estaríamos introduciendo los pares de coordenadas relativas de las cuatro marcas de calibración, y los pares de coordenadas absolutas de esas mismas marcas. Pero todo esto era teoría, así que en cuanto llegué a casa me puse a pensar en como podría integrar estos cambios en mi programa. Lo que estaba claro es que habría que cambiar varias cosas, no tanto a nivel de generación de la matriz de homografía, puesto que eso es algo automático gracias a la librería mencionada anteriormente, sino por el hecho de que hasta ahora el usuario solo tenía que pulsar un botón para iniciar la calibración. Esto iba a dejar de ser así puesto que aunque el usuario seguiría teniendo que pulsar un botón para iniciar la calibración, ahora debería introducir los cuatro pares de coordenadas absolutas de las marcas de la plantilla. Lo primero es tener una idea clara de los pares de coordenadas que son necesarios para que se cree la matriz de homografía. 43 de 199 Desarrollo del Software Figura 35. Pares de coordenadas necesarios para la homografía. En la figura superior se pueden observar 8 pares de coordenadas. Cuatro de ellos comienzan con el prefijo Mscr; estos son los pares de coordenadas que envía el wiimote. Los otros cuatro pares de coordenadas restantes comienzan con el prefijo Mdst; estos son los pares de coordenadas absolutas de los cuatro puntos de calibración. Estos datos los almacenaré de la siguiente forma: Existirán cuatro arrays de arrays para almacenar tanto la componente X como la componente Y de las coordenadas de los puntos. Al iniciar el programa, se crearán ambos arrays, y cada uno de ellos tendrán una longitud igual al número de wiimotes conectados en ese momento. Así pues tendré una estructura como muestra la figura inferior: Mdst* y Mscr* Puntos de calibrac ión Nº de wiimotes conectados 1 2 ... n 0 P1X P1X ... P1X 1 P2X P2X ... P2X 2 P3X P3X ... P3X 3 P4X P4X ... P4X Tabla 01. Aspecto de los arrays de arrays MdstX, MdstY, MscrX y MscrY. Como se puede observar, para cada wiimote conectado, se creará un array de 4 valores float, que corresponderán a las componentes X o Y, de las coordenadas Mdst o Mscr de los 4 puntos de calibración existentes. 44 de 199 Desarrollo del Software Centrándonos en los valores que albergarán los arrays MdstX y MdstY, ¿cómo recoger estos pares de coordenadas? Deben ser introducidos por el usuario. El usuario deberá medir, en metros, cuanta distancia hay desde cada una de las cuatro marcas de calibración hasta el origen del eje X y del eje Y, y así obtener cuatro pares de coordenadas que corresponderán con la posición que tienen esos cuatro puntos de calibración dentro de la sala. Una vez obtenidas estas coordenadas, el usuario deberá introducirlas en el programa. Para poder efectuar estos cambios, modifiqué la pestaña básica de wiimote mostrada en la Figura 20 para poder añadir los controles necesarios. Figura 36. Panel de introducción de datos del cambio de coordenadas. Como se observa en la figura superior, añadí 8 cajas de texto, dos para cada marca de calibración, en los que el usuario deberá introducir las coordenadas absolutas de dicha marca, en metros. Existe un control de errores integrado, el mismo que en las anteriores cajas de texto diseñadas, por lo tanto el usuario no podrá introducir ni letras ni símbolos, tan solo números decimales o enteros. El programa observará si el usuario ha introducido algún número en cada una de las ocho cajas de texto. Una vez que las ocho cajas de texto hayan sido rellenadas, aparecerá en el centro del dibujo superior, el de fondo blanco con las cuatro marcas de calibración, un botón llamado “Calibrar”. Será entonces cuando el usuario podrá calibrar dicho wiimote y no antes. Una vez que el usuario pulse el botón, aparecerá una ventana, con fondo negro, en la que estará dibujada la marca de calibración superior izquierda, la primera. El usuario deberá entonces colocar el objeto infrarrojo encima de la primera marca de calibración, la 45 de 199 Desarrollo del Software misma que señala la ventana emergente, pero en la realidad, en la marca de calibración que estará colocada en la sala donde se producirá el seguimiento. Una vez haya colocado el objeto infrarrojo encima de dicha marca, deberá hacer un flash con el mismo, es decir, una pulsación de luz infrarroja. El programa esperará a que el usuario realice todas estas acciones, y cuando reciba la señal del wiimote que está siendo calibrado de que ha observado una pulsación infrarroja, entrarán en juego las coordenadas que en la Figura 35 comenzaban con el prefijo scr. Cuando el programa, que estaba en estado de espera, reciba la primera pulsación infrarroja, almacenará las coordenadas que haya enviado el wiimote, valiéndose para ello de los array de arrays MscrX y MscrY. Estos arrays de arrays tienen la misma estructura expuesta en la Tabla 1, por lo tanto, para cada wiimote se almacenarán cuatro pares de coordenadas, depositando sus componentes X e Y en distintos lugares. Este proceso se repite para las tres marcas de calibración restantes. Una vez almacenados los datos de la última marca de calibración, el lienzo situado en la parte inferior del panel de calibración, visto en la Figura 36, mostrará el trapecio formado por los cuatro puntos que acabamos de almacenar, y la caja de selección situada a su derecha se activará, informando al usuario de que dicho wiimote se ha calibrado correctamente. También es posible que el usuario detenga la calibración antes de finalizarla correctamente. Para ello tan solo deberá pulsar la tecla ESC en algún momento desde que aparezca la ventana con el lienzo negro junto con la primera marca de calibración, y la aparición de la última marca de calibración en dicho lienzo. Dado que cuando alguien utilice este programa para montar un sistema de seguimiento muy posiblemente pretenderá usar la misma configuración varias veces, se ha añadido un sistema de guardado y cargado de datos de calibración, de tal forma que el usuario, una vez que haya calibrado un wiimote, pueda guardar los datos de calibración para cargarlos la próxima vez que vaya a iniciar el sistema de seguimiento. Cabe destacar que los valores de calibración se guardarán en un archivo de extensión .dat, modificables en cualquier editor de textos, y que siguen la siguiente nomenclatura: "calibration_data_" + (Número de wiimote) + ".dat" Una vez creado el archivo de guardado, se almacenarán en una misma columna los valores de los cuatro arrays de arrays que contienen todas las coordenadas obtenidas durante el proceso de calibración. De esta forma, cuando en una próxima inicialización del programa se quieran recuperar estos datos de calibración guardados, el programa buscará un archivo que pertenezca al mismo wiimote que se quiere calibrar. Es decir, si se pretende calibrar el wiimote número 3, el programa observará si se han guardado previamente datos de calibración de dicho wiimote y los cargará de ser así. Sino entregará un mensaje de error informando de que no hay datos que cargar para dicho wiimote. 46 de 199 Desarrollo del Software 4.2.3 - Generación de Coordenadas Una vez tenemos los wiimotes calibrados y estos nos están enviando coordenadas absolutas del objeto en seguimiento, es cuando tenemos que coger todas estas coordenadas y reducirlas a un simple par de coordenadas. Este es pues el apartado clave del proyecto, el que consigue mostrarnos por pantalla la situación del objeto en seguimiento. Voy a explicar como abordé esta tarea. Cuando tenemos un solo mando la tarea es realmente sencilla: Calibramos el wiimote y desde ese momento ya podemos calcular un par de coordenadas absolutas. Coordenadas que serán las únicas puesto que solo existiría un mando. No hay mucho trabajo que hacer en este caso, esas coordenadas son las que utilizaría para dibujar un punto en pantalla que representaría el objeto en seguimiento. Sin embargo, cuando el número de wiimotes es mayor que uno hay que hacer algún tipo de comparación o cálculo con las coordenadas recibidas. Los wiimotes estarán colocados en lugares distintos y aunque convirtamos sus coordenadas relativas en absolutas, difícilmente vamos a obtener dos pares de coordenadas exactamente iguales. Cada mando tendrá una carga de batería distinta, una localización distinta, una exposición a posibles fuentes de luz externa distinta, y otros factores que pueden hacer que haya pequeñas o grandes diferencias entre las coordenadas transformadas. Después de que el programa confirme que el usuario desea iniciar el seguimiento, identifico cuales son los wiimotes que están viendo el objeto y los almaceno en una lista. Acto seguido, de los wiimotes contenidos en esta lista, comparo los valores de la batería de todos los wiimotes para hallar cual tiene una carga mayor. Este wiimote será el que use como wiimote de referencia, interpretando que un wiimote que tiene mayor batería que otro me entregará unos valores de coordenadas más fiables. Esto no es correcto al 100% puesto que pudiera ser que el wiimote que más batería tiene sea el que vea el objeto más cerca de los límites de su área de visión. Sin embargo, si discriminaba un wiimote por el hecho de que estuviera viendo el objeto cerca de los bordes de su área de visión, estaría eliminando gran parte del atractivo de este proyecto. La sala en la que se vaya a utilizar este proyecto, en circunstancias normales, no será excesivamente pequeña. Por lo tanto los mandos estarán alejados entre sí para poder abarcar la mayor área posible. En estas condiciones es muy fácil que el objeto infrarrojo se encuentre entre las áreas de visión de dos wiimotes distintos, y además lo haga en zonas extremas de dichas áreas. Por lo tanto si eligiera no usar estos wiimotes por ver el wiimote en esas zonas, estaría eliminando área útil de seguimiento. Por lo tanto decidí seguir con el planteamiento inicial de escoger el wiimote de referencia según su nivel de batería. Una vez escogido el wiimote de referencia, asigno a unas variables temporales la componente X e Y de las coordenadas entregadas por dicho wiimote. Después hago un recorrido por los wiimotes restantes de la lista comparando las coordenadas de los wiimotes con las del wiimote de referencia. Si tanto el valor X e Y de las coordenadas del wiimote de referencia y el próximo wiimote de la lista están entre sí a una distancia menor de 10 puntos, añado el valor de estas coordenadas a la variable temporal creada 47 de 199 Desarrollo del Software anteriormente, como si fuera un sumatorio. Hago lo mismo con el resto de wiimotes de la lista hasta que se acaban. A la vez que hago un sumatorio de las coordenadas, aumento una variable que me indica el número de wiimotes que se han tenido en cuenta en ese sumatorio. De esta forma, cuando termino el recorrido por la lista de wiimotes, tengo un sumatorio de las componentes de las coordenadas de un número n de wiimotes. Ahora tan solo tengo que dividir el sumatorio de las componentes de las coordenadas por n wiimotes, obteniendo un par de coordenadas como resultado. Estas coordenadas serán las coordenadas medias de los datos recibidos por n wiimotes. Justo después obligamos a que el lienzo se redibuje, y como el modo de seguimiento estará activado, se dibujará en pantalla un punto rojo, justo en las coordenadas medias calculadas según el procedimiento anterior. Además transformo estas coordenadas y las paso a metros, teniendo en cuenta los factores de proporcionalidad que calculo al dimensionar el lienzo al inicio del programa. Después muestro esos datos por pantalla para que el usuario sepa exactamente en qué posición, en metros, se encuentra el objeto. 4.2.4 - Sistema de Notificaciones Anteriormente he hablado acerca del sistema de creación de zona prohibida en el lienzo del programa. Este sistema lanza avisos al usuario cuando el objeto entre dentro de una área prohibida delimitada por el usuario. Estos avisos pueden mostrarse en la pantalla principal del programa, pero pensé que sería muy interesante poder disponer de avisos a distancia. De esta forma, el usuario podría tener conectado el sistema de seguimiento en un lugar en concreto y poder recibir avisos o notificaciones a distancia. Lo primero era decidir qué servicios podría utilizar para enviar las notificaciones al usuario. Creí conveniente poder enviar notificaciones vía correo electrónico. Todo el mundo tiene una o varias cuentas de correo electrónico así que sería muy fácil hacer llegar al usuario un correo indicándole el aviso correspondiente. Quizás podría enviar un SMS al teléfono móvil del usuario alertándole de los movimientos del robot. También Twitter sería una buena forma de comunicarme con el usuario. Podría enviar un mensaje privado vía Twitter al usuario o incluso escribir en su muro, avisándole de la incidencia. Facebook me pareció un buen servicio para enviar notificaciones al usuario. - Notificaciones vía SMS Después de establecer qué servicios podría utilizar, investigué cómo los podía implementar en mi aplicación. Primeramente me informé acerca del envío de SMS a móviles vía web. Pensé que, a estas alturas de la revolución digital, sería algo sencillo de hacer. Pero pronto descarté la opción de avisar al usuario vía SMS. En España existía alguna operadora que permitía enviar SMS vía informática, pero en el momento de hacer las pruebas, las operadoras que permitían estas operaciones han dejado de hacerlo. Busqué servicios on-line que me sirvieran de enlace entre mi aplicación en C# y un teléfono móvil, 48 de 199 Desarrollo del Software pero los que encontré o eran muy caros, enfocados a empresas, o se veían poco fiables e inseguros. Así que decidí no implementar esta posibilidad. - Notificaciones vía Twitter Aunque la posibilidad de enviar SMS la había descartado, debido a la proliferación de teléfonos inteligentes y de las redes de datos móviles durante los últimos 4 o 5 años, es muy sencillo poder lanzar notificaciones a un teléfono móvil mediante los otros servicios que pensé en utilizar. Por ello decidí concentrarme en implementar notificaciones vía Twitter y de esta forma, no solo llegar al escritorio del ordenador del usuario, sino también a su teléfono inteligente. Busqué varias librerías para Visual Studio C# que me permitieran conseguir esto. Entre las que busqué, una de las más populares es Twitterizer[16]. Esta es una librería documentada que facilita mucho la tarea de implementar en aplicaciones de escritorio y web la comunicación con los servicios de Twitter. Descargué la librería y la documentación y ejemplos existentes para investigar un poco el asunto. Lo primero que tenía que hacer para que mi aplicación pudiera comunicarse con algún usuario vía Twitter era registrarme como desarrollador en la página que Twitter dispone para tal fin: www.dev.twitter.com. En esta página seguí los pasos indicados para poder registrar mi aplicación y poder obtener las claves necesarias que tenía que utilizar para poder autentificar mi aplicación. Figura 37. Códigos secretos de mi aplicación. Todo esto es para que Twitter pueda ejercer un control sobre las personas y programas que pueden enviar mensajes o escribir sobre las páginas de Twitter de los demás 49 de 199 Desarrollo del Software usuarios. Eso quiere decir que el usuario deberá dar permiso a mi programa para que pueda enviarle notificaciones. Es aquí donde entran en juego estos códigos. Se producen unos intercambios de códigos o tokens entre el programa y los servidores de Twitter que permiten al programa vincular la cuenta del usuario y poder comunicarse con el. Después hablaré un poco más acerca de como lo acabé implementando en el programa. - Notificaciones vía Facebook Después de Twitter, me puse con Facebook. También utiliza el mismo sistema de autenticación que Twitter, así que también había que registrar la aplicación como desarrollador en Facebook. Sin embargo, no pude hacerlo. Facebook ha cambiado algunas políticas de cara a los desarrolladores y ahora hay que compartir con Facebook el número de teléfono o el de cuenta bancaria para poder registrar una aplicación. No estaba dispuesto a compartir mi número de teléfono con Facebook, mucho menos el número de mi cuenta bancaria, así que desestimé el uso de Facebook como servicio de notificaciones. - Notificaciones vía Correo Electrónico Esta fue el servicio más sencillo de implementar puesto que usa librerías desarrolladas por Microsoft y que están incluidas en la suite Visual Studio[17]. Tan solo fue necesario un par de búsquedas por internet para llegar a varios ejemplos que me mostraban como poder enviar correos electrónicos mediante código dentro de mi aplicación[18]. Fue rápido y sencillo de implementar. Después de decidir que servicios iba a emplear, decidí aglutinar la configuración de cuentas de los mismos en un mismo lugar. Emplee el elemento “Preferencias” del submenú “Archivo”. Cuando el usuario hace clic sobre este elemento se abre un nuevo formulario, el formulario de preferencias. 50 de 199 Desarrollo del Software Figura 38. Formulario “Preferencias” abierto después de clicar sobre el elemento de mismo nombre bajo el submenú “Archivo”. Pese a que he dicho que solo voy a utilizar dos servicios de notificación, Twitter y correo electrónico, en el panel he añadido Facebook porque esperaba encontrar una forma de vincular el programa con dicho servicio. En la versión final quedará descartada, dejando la puerta abierta para poder añadir este servicio en una futura revisión del programa. Comentando acerca de lo que se ve en este nuevo formulario, el usuario tiene 5 botones, dos de ellos que controlan el formulario y tres que sirven para activar los distintos servicios de notificaciones. A cada lado de estos tres botones existen tres paneles que muestran un aspa si el servicio no está vinculado, y un tick si por el contrario hemos vinculado una cuenta de dicho servicio. Haciendo clic sobre estos paneles, el programa da la posibilidad al usuario de borrar la vinculación del servicio correspondiente. Por último añadí una casilla de selección para que el programa pueda guardar los datos de autenticación de las cuentas en un archivo para que, durante un próximo inicio del programa, este pueda cargar estos datos y evitar que el usuario tenga que volver a vincular los servicios de notificación. Veamos que sucede cuando el usuario pulsa uno de los dos 51 de 199 Desarrollo del Software botones correspondientes a los dos servicios de notificación disponibles: Twitter y correo electrónico. - Twitter Cuando el usuario clica el botón llamado “Vincular cuenta de Twitter” lanzamos el proceso de autorización de nuestra aplicación por parte de la cuenta de Twitter del usuario. Se abrirá el navegador predeterminado del sistema y automáticamente se abrirá la página web de Twitter en la que el usuario deberá introducir su nombre y su contraseña. A la par se habrá abierto en el programa el formulario “Vinculando cuenta Twitter” que se muestra en la Figura 39. Cuando el usuario se haya autenticado en la página web, aparecerá un código de 7 dígitos que es el que debemos introducir en el nuevo formulario abierto por el programa. Figura 39. Formulario para vincular una cuenta de Twitter. Si nos equivocamos e introducimos un código de menos de 7 dígitos, el programa pide al usuario que revise el código puesto que falta algún número. Si el usuario introduce 7 dígitos pero no son los correctos, el programa avisará de tal suceso al usuario y cerrará el formulario de vinculación de cuenta de Twitter. Si quiere volver a intentar la vinculación, deberá volver a clicar en el botón “Vincular cuenta Twitter” y volver a repetir el proceso descrito en el párrafo anterior. 52 de 199 Desarrollo del Software Figura 40. Dos posibles errores durante la vinculación de la cuenta de Twitter. Por el contrario, si hemos introducido bien el código, obtendremos la notificación de que esto ha sido así cuando volvamos al formulario “Preferencias”, puesto que el aspa roja habrá sido sustituida por un tick verde, tal y como se ve en la Figura inferior. Figura 41. Éxito en la vinculación de una cuenta de Twitter. 53 de 199 Desarrollo del Software Ahora, si queremos desvincular la cuenta, tan solo hay que hacer doble clic en el tick verde. Acto seguido nos aparecerá una ventana preguntándonos si queremos desvincular nuestra cuenta de Twitter, a lo que el usuario puede escoger qué hacer. Figura 42. Desvinculando la cuenta de Twitter del usuario. - Correo Electrónico El proceso para vincular nuestro correo electrónico al servicio de notificaciones es mucho más sencillo que el descrito anteriormente para Twitter. Cuando el usuario clica el botón de “Vincular e-mail”, lanzamos un formulario llamado “Vinculando email” que pide que el usuario escriba una cuenta de correo válida. Llegados a este punto, debo comentar que para enviar una notificación, ya sea por Twitter o por correo electrónico, necesita existir una cuenta de cada servicio que se identifique como remitente del aviso enviado. Es por ello que decidí crear una cuenta de Twitter para el programa, con el nombre de usuario @WIPS_Alerts, y además cree la siguiente cuenta de correo para el programa: [email protected]. Desde estas dos cuentas gestiono los envíos de notificaciones y las posibles recepciones de errores o consultas por parte de posibles usuarios. En Twitter se recibe un mensaje del usuario @WIPS_Alerts, y al correo electrónico llega un mensaje de [email protected]. Es esencial que el usuario de Twitter introducido esté siguiendo al usuario @WIPS_Alerts para poder recibir las notificaciones. 54 de 199 Desarrollo del Software Figura 43. Ventana “Vinculando email”. Volviendo a la suscripción por correo electrónico, cuando el usuario ha escrito algo en la caja de texto, el programa verifica que sea una cuenta de correo válida. Esto lo hago mediante un código de ejemplo que encontré en la página de soporte de microsoft para el lenguaje C# [19]. Si el correo electrónico no es válido el programa lanza el siguiente aviso: Figura 44. Error de validación del correo electrónico introducido. De esta forma el formulario no dará por válido cualquier texto que el usuario ponga en la caja de texto. El usuario podrá cancelar en cualquier momento la vinculación de la cuenta de correo haciendo clic en el botón Cancelar. 55 de 199 Desarrollo del Software Una vez que hayamos introducido un mail de forma correcta, el programa envía un mensaje de confirmación a la cuenta vinculada por el usuario, informándole de que se acaba de suscribir a los avisos del programa. Además el programa nos informará de la correcta vinculación de la cuenta de correo mostrando un tick verde en lugar del aspa roja. De igual manera el usuario puede desvincular del servicio de notificaciones la cuenta de correo haciendo clic sobre dicho tick verde. Se le muestra al usuario una ventana semejante a la que se muestra cuando se quiere desvincular una cuenta de Twitter y se le da la posibilidad de que el usuario elija. En las figuras inferiores he añadido unas capturas de esto. Figura 45. Éxito en la vinculación de una cuenta de correo electrónico. Figura 46. Desvinculando la cuenta de correo electrónico del usuario. 56 de 199 Montaje del Tag Infrarrojo 5 - Montaje del Tag Infrarrojo Como se ha comentado al inicio, el seguimiento del objeto sería posible gracias a que este portaría un objeto o tag, que emitirá una señal determinada que la infraestructura sea capaz de recibir para su posterior procesado. Teniendo en cuenta que los wiimotes reciben las señales de luz infrarrojas, es lógico concluir que nuestro tag deberá ser capaz de enviar señales infrarrojas. En los inicios del proyecto utilicé un simple led infrarrojo para poder efectuar las primeras pruebas, no obstante, enseguida quedó patente que con un solo led infrarrojo sería insuficiente para conseguir el objetivo de este proyecto. Obviamente el wiimote era capaz de ver el led infrarrojo a una distancia razonable (aproximadamente entre 5 y 6 metros de distancia), pero cuando dicho led se mueve a lo largo y ancho del plano XY, empiezan a surgir problemas de visión. Estos problemas son debidos a que el cono de emisión de un led infrarrojo es suficiente cuando el wiimote está enfocado de forma perpendicular al mismo pero cuando deja de existir dicha perpendicularidad el wiimote es incapaz de ver el led infrarrojo aunque todavía se encuentre dentro del área de visión del mismo. Lo mencionado en el párrafo anterior era un problema muy importante ya que, debido a esto, las áreas de visión de los wiimotes se reducían drásticamente, siendo necesario aumentar la cantidad de mandos situados alrededor de la sala. Usar un solo led infrarrojo era poco eficaz para el sistema, por lo que era necesario encontrar una alternativa. Pensé en diseñar una matriz de leds infrarrojos para ampliar la zona de emisión infrarroja. Pero esto no era suficiente de por sí, ya que si en dicha matriz, todos los leds estaban colocados de la misma forma, tan solo estaríamos ampliando la visibilidad en unos centímetros. Para que sea eficaz, los leds que forman la matriz deben estar colocados a distintos ángulos, formando una esfera, de forma y manera que siempre exista un leds infrarrojo de dicha matriz enfocando lo más perpendicularmente posible al wiimote más lejano para evitar recortes en la zona de visión del mismo. En el momento de diseñar el tag, había que tener en cuenta varias consideraciones: • Cantidad de leds a emplear • Fuente de alimentación del tag • Que pueda ser usado para la calibración de los wiimotes A primera vista se puede ver que las dos primeras consideraciones mencionadas están relacionadas entre sí. Dependiendo de la cantidad de leds empleados, y la disposición de los mismos, se necesitará un voltaje determinado. Por lo tanto tuve en consideración ambos aspectos a la vez. 57 de 199 Montaje del Tag Infrarrojo Lo primero es conocer el tipo de led infrarrojo a utilizar. Para elegir el modelo de led me basé en la decisión tomada al respecto por Ricardo Bonache en su proyecto[6], que también coincidía con la opinión de la mayoría de personas que habían realizado proyectos relacionados con el wiimote. Según ellos el modelo optimo es el Vishay TSAL 6400. Este led es el más adecuado para utilizar junto con el wiimote, pues la longitud de onda de las señales infrarrojas que deja pasar la ventana infrarroja situada delante de la cámara del wiimote es de 940 nm, igual que el valor de emisión del led antecitado. El ángulo de visión es de unos 25º, aunque esta característica en concreto no es tan esencial en este proyecto, debido a la disposición que se pretende dar a los leds. Un aspecto que sí es importante es el valor máximo de tensión que soporta dicho modelo. Este led aguanta 1.7 V, lo que nos permite alimentarlo sin riesgos a 1.5 V y conectar varios led en serie y en paralelo, de forma y manera que puedan ser alimentados con una pila de 9 V y obtener un mayor número de horas de duración que si usáramos otra configuración y otra pila de menor tamaño y voltaje. La cantidad de leds a emplear debía ser la justa. Es más deseable una disposición de los leds homogénea a que el número de leds sea elevado, además así controlamos el consumo. Y si se usan menos leds sucede que hay mucho espacio entre los mismos, lo que ocasiona que el wiimote, en lugar de ver la matriz de leds infrarrojos como un todo, como un solo punto, la vea como uno o varios, ocasionando errores en la adquisición de coordenadas. Para diseñar la matriz de leds dispuse varios leds en un trozo de placa de topos, formando tres alturas distintas y colocando cada altura en una orientación distinta a la anterior, creando una especie de “flor” de leds. Los coloqué de forma provisional para ir viendo como quedaría cada capa después de que soldara los leds y para saber si dentro de dicha capa cabría el resto de leds que tenía pensado ir colocando. Es mucho más sencillo de comprender con una imagen; la Figura 47 muestra el resultado final de la matriz de leds infrarrojos. Figura 47. Diseño de la matriz de leds infrarrojos. 58 de 199 Montaje del Tag Infrarrojo Con esta disposición se consigue la homogeneidad citada anteriormente a la vez que consigo utilizar un número de leds que me permite utilizar la pila de 9 V. El esquema de la distribución de los leds es el siguiente: Figura 48. Esquema de la matriz de leds infrarrojos diseñada. Como se puede observar tenemos dos filas de leds en serie, consiguiendo una caída de voltaje cercana a los 1.5 V por led. Tuve que añadir un led más, el que ocupa la posición central en la Figura 47, con lo cual fue necesario conectar una resistencia en serie para que el voltaje y la intensidad del led fueran las necesarias para su correcto funcionamiento. El valor teórico de la resistencia se obtiene de la siguiente forma: (1) Cuando quise plasmar en la práctica dicho cálculo, tuve que utilizar una resistencia normalizada de 82 $. Aún así, una vez montado y conectado el circuito, vi que el led que se alimentaba a través de esta resistencia, tenía una intensidad luminosa superior a la del resto de leds, con lo que vi conveniente elevar el valor de la resistencia para limitar el paso de corriente a través del led e intentar que la luminosidad de la matriz fuera lo más homogénea posible. Por ello, y tras probar distintos valores, decidí utilizar una resistencia de 270 $. Una vez montada la matriz, pasé a determinar la distancia mínima a partir de la cual un wiimote empezaba a ver dicha matriz como dos o más puntos infrarrojos en lugar de uno solo. Para ello, conecté el wiimote al ordenador y lancé la aplicación de prueba que viene con la librería WiimoteLib, la cual permite conocer cuántos puntos infrarrojos está observando un wiimote en un momento determinado. Conecté la matriz de leds a una fuente de alimentación y fue acercando el wiimote conectado al ordenador. 59 de 199 Montaje del Tag Infrarrojo Pude notar que desde la distancia máxima de visión (5 o 5,5 metros) hasta el medio metro, el wiimote observaba la matriz de leds y tan solo marcaba un punto infrarrojo visto. A partir de los 0,5 metros en adelante, el wiimote comenzaba a ver dos, tres y hasta cuatro puntos infrarrojos distintos, durante cantidades de tiempo que podían oscilar entre unos milisegundos o la permanencia total de dicha situación. Analizando los resultados de las pruebas, pude determinar que la matriz construida no estaba preparada para una situación en la que el wiimote esté a menos de 0,5 metros de distancia de la misma. Pero se puede ver que esta no es una limitación importante en mi caso, puesto que en este proyecto, en la mayoría de las situaciones, el wiimote estará situado a una distancia mucho mayor que esta distancia mínima, puesto que lo que se pretende es abarcar el mayor área posible con cada wiimote. Una vez determinado el número de leds y la disposición de los mismos,me detuve a pensar qué era necesario añadir a esa matriz de leds para que fuera utilizable de cara al usuario. Sería de gran utilidad disponer de un interruptor que permitiera conectar o desconectar el tag infrarrojo cuando fuera necesario, y así poder alargar la duración de la pila. Además, cuando realizamos la calibración de un mando, es necesario que el tag infrarrojo sea capaz de emitir un destello durante unos instantes y así poder marcar los cuatro puntos de calibración de la plantilla. Esto podía hacerse encendiendo y apagando el interruptor, pero debido a que iba a utilizar un interruptor de tipo palanca, vi conveniente introducir un pulsador suplementario. Dicho pulsador activará la matriz de leds cuando el pulsador principal esté desactivado, algo que me llevó a modificar levemente el circuito que había ido construyendo en mi cabeza conforme iba pensando en estos añadidos. Figura 49. Circuito final del tag infrarrojo. 60 de 199 Montaje del Tag Infrarrojo Como se puede observar en la figura superior, para poder integrar el pulsador de destello, tuve que sustituir el interruptor por un doble conmutador. Mediante este elemento, cuando el doble conmutador se encuentre en la posición que muestra la figura, el circuito estará preparado para presionar y soltar el pulsador y que se produzca el destello necesario para la calibración de los wiimotes. Una vez que se ha terminado la calibración, se cambia la posición del doble conmutador. Entonces entraremos en el estado de “siempre encendido”, que se utilizará cuando se quiere usar el tag para el seguimiento. Ahora tan solo era necesario encontrar algo que pudiera servir para contener todos estos elementos. Tras buscar cajas metálicas o de plástico de distintos fabricantes, no pude encontrar ninguna que fuera lo suficientemente pequeña como para que el tag no tuviera un tamaño desproporcionado. Entonces observé que tenía una caja pequeña de plástico, proveniente de un mazo de naipes. Observé que se podía contener perfectamente todos los elementos del circuito y además la altura interna de la caja era exactamente la del ancho de la pila de 9V. Por lo tanto, decidí utilizar dicha caja para el diseño final del tag infrarrojo. Figura 50. Aspecto final del tag infrarrojo. Como se observa en la figura superior, el tag infrarrojo quedó muy compacto. Además la caja se puede abrir fácilmente, permitiendo cambiar la pila de forma sencilla y rápida. Añadí etiquetas en el pulsador y a ambos lados del doble conmutador a modo informativo para que no hubiera confusión alguna en cuanto a los distintos estados de ambos elementos. 61 de 199 Resultados 6 - Resultados 6.1 - Conjunto de Pruebas Una vez terminado tanto el diseño del programa como el montaje del tag infrarrojo comencé a planificar el conjunto de pruebas que iba a realizar. Estas son las pruebas en las que pensé: • Crear una matriz de puntos repartidos de forma uniforme a lo largo de la sala. Posteriormente colocar el tag infrarrojo en cada uno de esos puntos, comparando las coordenadas XY reales y las calculadas por el programa. • Medir la resolución de los ejes X e Y en cada uno de los puntos de medición anteriores. • Colocar el tag infrarrojo en una coordenada determinada y medir las posibles variaciones de las coordenadas calculadas a través del tiempo. 6.2 - Sala de Pruebas y Montaje No era fácil tener acceso a una sala de dimensiones demasiado elevadas, tan solo tuve acceso a una porción de 15 m2. El área de seguimiento tenía 4,40 m de largo y 3,50 m de ancho. Después de simular con el programa como posicionar los mandos en la sala, encontré una configuración que me permitía abarcar toda la sala con los cuatro wiimotes que pretendía usar. Figura 51. Simulación de la configuración de los mandos 62 de 199 Resultados Con la ayuda de mi padre, diseñamos un soporte para los wiimotes. Consistía en una escuadra anclada a una pinza de sujeción de micrófonos. En la parte trasera del soporte hicimos un agujero con una broca de métrica 4, pasamos un macho de métrica 5 para hacerle rosca al agujero. De esta forma, con un tornillo de cabeza plana de 5x16 unimos la escuadra metálica con la pinza para micrófonos. Después solo hacia falta colocar a nivel la escuadra en la pared. El resultado se puede observar en la Figuras 52 y 53. Figura 52. Fabricación de los soportes de los wiimotes Figura 53. Soporte de wiimote anclado a la pared 63 de 199 Resultados Después de fabricar los cuatro soportes los colocamos en la pared a la misma altura. La configuración de los wiimotes era la que yo había simulado anteriormente, siendo estas las características de los mismos: Wiimote 1 Wiimote 2 Wiimote 3 Wiimote 4 2,28 m 2,28 m 2,28 m 2,28 m Ángulo # -90º 90º -90º 90º Ángulo " 41º 41º 41º 41º Altura Tabla 02. Configuración de los wiimotes durante las mediciones Una vez los mandos estaban colocados en las paredes, era momento de marcar la matriz de puntos de medición. Cogí las medidas de la sala e hice una serie de divisiones para poder tener una matriz de suficientes puntos como para que fuera representativa de toda la sala. Hice una matriz de 45 puntos, con unas distancias entre ellos de 44 cm en el eje X y 58 cm en el eje Y. 44 cm 1,1 1,2 1,3 1,4 1,5 1,6 1,7 1,8 1,9 2,1 2,2 2,3 2,4 2,5 2,6 2,7 2,8 2,9 3,1 3,2 3,3 3,4 3,5 3,6 3,7 3,8 3,9 4,1 4,2 4,3 4,4 4,5 4,6 4,7 4,8 4,9 5,1 5,2 5,3 5,4 5,5 5,6 5,7 5,8 5,9 58 cm Figura 54. Matriz de coordenadas de medición. 64 de 199 Resultados Una vez diseñé la matriz, compré unos adhesivos de colores, y los pegué en el suelo en cada una de las coordenadas de medición. De esta forma conseguí pasar la matriz del papel a la realidad. Anoté las distancias de las coordenadas de la matriz de mediciones en una tabla para posteriores comparaciones: Figura 55. Matriz de coordenadas de medición en la sala de mediciones. m 1 X 2 Y X 3 Y X 4 Y X 5 Y X 6 Y X 7 Y X 8 Y X 9 Y X Y 1 0,45 0,59 0,89 0,58 1,33 0,58 1,77 0,58 2,21 0,58 2,65 0,58 3,09 0,58 3,53 0,58 3,97 0,58 2 0,44 1,17 0,88 1,16 1,32 1,16 1,76 1,16 2,2 1,16 2,64 1,16 3,08 1,16 3,52 1,16 3,96 1,16 3 0,44 1,75 0,88 1,74 1,32 1,74 1,76 1,74 2,2 1,74 2,64 1,74 3,08 1,74 3,52 1,74 3,96 1,74 4 0,44 2,33 0,88 2,32 1,32 2,32 1,76 2,32 2,2 2,32 2,64 2,32 3,08 2,32 3,52 2,32 3,96 2,32 5 0,44 2,91 0,88 2,91 1,32 2,91 1,76 2,91 2,2 2,91 2,64 2,91 3,08 2,9 3,52 2,9 3,96 2,9 Tabla 03. Coordenadas reales de la matriz de medición, en metros. Una vez marqué las coordenadas de la matriz de medición, era momento de conectar los wiimotes al ordenador y comenzar el proceso de calibrado de los mismos. Para ello utilicé la plantilla de calibración que hice para realizar las pruebas en mi casa conforme iba haciendo el programa. Después de calibrar los mandos, guarde los datos de calibración para posteriores mediciones y comencé las pruebas. 65 de 199 Resultados 6.3 - Mediciones 6.3.1 - Medición de Coordenadas en la Matriz El procedimiento en esta prueba era colocar el tag infrarrojo en cada una de las coordenadas de medición de la matriz e ir anotando las coordenadas que el programa calculaba. Realicé tres tandas de mediciones, realizadas entre dos días distintos, y con un rango de carga de batería de los wiimotes para cada tanda de medición. Estos fueron los resultados: -1ª medición, realizada el 3 de Septiembre de 2012. Batería de los wiimotes entre el 97% y el 84%: m 1 2 3 4 5 X Y X 1 0,44 0,43 0,43 0,42 0,41 Y X 2 0,61 1,17 1,74 2,33 2,95 0,87 0,87 0,87 0,88 0,87 Y X 3 0,61 1,18 1,74 2,34 2,93 1,29 1,29 1,3 1,32 1,32 Y X 4 0,61 1,17 1,74 2,33 2,92 1,75 1,72 1,75 1,75 1,75 Y X 5 0,63 1,17 1,74 2,32 2,91 2,17 2,18 2,17 2,18 2,21 Y X 6 0,62 1,18 1,73 2,32 2,93 2,65 2,65 2,64 2,64 2,68 Y X 7 0,61 1,18 1,73 2,31 2,9 3,08 3,08 3,08 3,09 3,13 Y X 8 0,61 1,17 1,72 2,3 2,87 3,52 3,52 3,51 3,53 3,57 Y 9 0,59 1,16 1,72 2,29 2,84 3,96 3,96 3,94 3,96 3,97 0,58 1,15 1,7 2,27 2,83 Tabla 04. Resultados de la primera medición, en metros. -2ª medición, realizada el 3 de Septiembre de 2012. Batería de los wiimotes entre el 84% y el 69%: m X Y 1 X Y 2 X Y 3 X Y 4 X Y 5 X Y 6 X Y 7 X Y 8 X Y 9 1 0,45 0,61 0,87 0,62 1,29 0,61 1,75 0,63 2,18 0,62 2,65 0,62 3,08 0,61 3,52 0,59 3,96 0,58 2 0,44 1,18 0,87 1,17 1,3 1,17 1,73 1,17 2,19 1,18 2,65 1,19 3,08 1,17 3,52 1,17 3,96 1,15 3 0,43 1,73 0,89 1,74 1,3 1,74 1,75 1,74 2,19 1,74 2,64 1,74 3,08 1,73 3,51 1,72 3,95 1,72 4 0,42 2,33 0,87 2,33 1,33 2,33 1,75 2,32 2,2 2,33 2,66 2,31 3,1 2,29 3,53 2,27 3,95 2,26 5 0,41 2,94 0,87 2,94 1,33 2,93 1,75 2,91 2,21 2,93 2,69 2,91 3,13 2,87 3,57 2,84 3,98 2,83 Tabla 05. Resultados de la segunda medición, en metros. 66 de 199 Resultados -3ª medición, realizada el 3 de Septiembre de 2012. Batería de los wiimotes entre el 69% y el 58%: m X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y X 6 Y X 7 Y X 8 Y 9 1 0,45 0,61 0,87 0,62 1,3 0,62 1,75 0,64 2,18 0,62 2,65 0,62 3,08 0,61 3,52 0,59 3,96 0,58 2 0,44 1,18 0,87 1,18 1,3 1,17 1,73 1,17 2,21 1,2 2,63 1,17 3,08 1,17 3,52 1,16 3,96 1,15 3 0,43 1,74 0,88 1,74 1,3 1,74 1,75 1,74 2,2 1,74 2,64 1,74 3,08 1,73 3,51 1,72 3,95 1,71 4 0,42 2,33 0,87 2,33 1,33 2,33 1,75 2,33 2,2 2,33 2,65 2,31 3,1 2,3 2,53 2,27 3,95 2,26 5 0,41 2,94 0,87 2,94 1,33 2,93 1,76 2,93 2,22 2,93 2,69 2,91 3,13 2,88 3,58 2,84 3,98 2,82 Tabla 06. Resultados de la tercera medición, en metros. Una vez realizadas las tres mediciones, calculé los errores absolutos entre cada una de las mediciones tomadas mediante el programa y las reales, mostradas en la Tabla 3: - Error del 1er conjunto de pruebas con las medidas reales: m X Y X 1 Y X 2 Y X 3 Y X 4 Y 5 0 0 0,02 0,02 5 0,03 0,04 0,01 0,02 0 0 0 X 0 Y X 7 Y X 8 0 Y 9 0,03 0,01 0,03 0,01 0,01 0,01 0,01 0,02 0,03 0,01 0,04 0,01 0,02 0,02 0,01 0,02 3 0,01 0,01 0,01 4 0,02 Y 6 1 0,01 0,02 0,02 0,03 0,04 0,03 0,02 0,05 0,04 0,04 2 0,01 X 0 0 0 0 0,01 0,01 0 0,02 0,01 0,02 0,02 0,04 0,01 0 0,03 0,01 0 0,01 0 0,01 0,01 0 0,02 0 0,01 0,01 0,02 0,01 0,03 0 0,01 0,01 0 0,01 0,02 0,04 0,01 0,05 0,03 0,05 0,06 0,01 0,07 0 Tabla 07. Error entre la 1ª medición del programa y las medidas reales, en metros. 67 de 199 0 0,05 Resultados Figura 56. Gráfica del error en la componente X de las coordenadas durante la 1ª medición Figura 57. Gráfica del error de la componente Y de las coordenadas durante la 1ª medición 68 de 199 Resultados - Error del 2º conjunto de pruebas con las medidas reales: m X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y 6 0 0,02 0,02 0,04 0,04 0,03 0,02 0,05 0,03 0,04 2 0 0,01 0,01 0,01 0,02 0,01 0,03 0,01 0,01 0,02 0,01 0,03 0,01 0 0,01 0,01 0,01 0,01 0,01 0,01 0 0 5 0,03 0,03 0,01 0,03 0,01 0,02 0,01 0 4 0,02 0 0 0,02 0 0 0 0 Y X 7 1 3 0,01 0,02 0,01 X Y X 8 Y 9 0,04 0,01 0,03 0,01 0,01 0,01 0 0 0,01 0 0 0 0,01 0,01 0 0,01 0,01 0,02 0,01 0,02 0,01 0,02 0,01 0,02 0,03 0,01 0,05 0,01 0,06 0,01 0,02 0,05 0 0,05 0,03 0,05 0,06 0,02 0,07 Tabla 08. Error entre la 2ª medición del programa y las medidas reales, en metros. Figura 58. Gráfica del error de la componente X de las coordenadas durante la 2ª medición. 69 de 199 Resultados Figura 59. Gráfica del error de la componente Y de las coordenadas durante la 2ª medición. - Error del 3er conjunto de pruebas con las medidas reales: m X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y 6 0 0,02 0,02 0,04 0,03 0,04 0,02 0,06 0,03 0,04 2 0 0,01 0,01 0,02 0,02 0,01 0,03 0,01 0,01 0,04 0,01 0,01 4 0,02 0 0 0 0,02 0 0,01 0 0,01 0,01 0,01 0,01 0,01 0,01 5 0,03 0,03 0,01 0,03 0,01 0,02 0 0 0 0 0 0 Y X 7 1 3 0,01 0,01 X Y X 8 9 0,04 0,01 0,03 0,01 0,01 0,01 0 0 Y 0 0 0 0 0,01 0,01 0 0,01 0,01 0,02 0,01 0,03 0,01 0,01 0,01 0,02 0,02 0,01 0,05 0,01 0,06 0,02 0,02 0,02 0,05 0 0,05 0,02 0,06 0,06 0,02 0,08 Tabla 09. Error entre la 3ª medición del programa y las medidas reales, en metros. 70 de 199 Resultados Figura 60. Gráfica del error de la componente X de las coordenadas durante la 3ª medición. Figura 61. Gráfica del error de la componente Y de las coordenadas durante la 3ª medición. 71 de 199 Resultados Con las diferencias que hemos calculado podemos observar la repetibilidad del sistema, es decir, si este sistema es bueno para obtener los mismos resultados cuando efectuemos una misma medición repetidas veces. Para ello calculamos la desviación estándar de las tres mediciones de cada coordenada: X m Y X 1 1 0,58 Y X 2 0 2 0,58 0,58 3 0 4 0 0 5 0 0,58 Y 3 0,58 0,58 0,58 0 0,58 0,58 0 0 0,58 0,58 0,58 0 Y X 4 0 0,58 0,58 X 0 Y X 5 0,58 0,58 Y X 6 0 Y X 7 Y X 8 Y 9 0 0,58 0 0 0 0 0 0 0 0,58 0 0,58 1,15 0 1 0 0 0 0,58 0 0 0 0 0 1,53 0,58 0 0,58 0 0,58 0 0 0,58 1 0 0 0,58 1,15 0,58 1 0 0,58 0,58 0 0,58 0,58 0,58 0,58 1,15 0,58 0 0,58 0,58 0 1,15 0,58 0,58 0,58 0,58 0 0,58 0,58 Tabla 10. Desviación estándar de los datos obtenidos durante las tres mediciones, en centímetros. 6.3.2 - Medición de la Resolución Una vez terminados las tres mediciones, pasé a comprobar la resolución del sistema en cada una de las coordenadas de la matriz de mediciones. Para ello, coloqué el tag infrarrojo en cada punto y lo desplazaba a través del eje X primero, y del eje Y después. A la vez que lo movía observaba las coordenadas calculadas que entregaba el programa, y en cuanto el eje que estaba midiendo cambiaba de coordenadas, observaba la distancia entre el tag infrarrojo y el punto de medición de la matriz. cm X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y X 6 Y X 7 Y X 8 Y 9 1 1 0,5 1 1 0,8 0,9 0,9 0,8 0,9 0,4 0,8 0,5 0,9 0,3 1 0,4 0,9 1,5 2 1 0,9 0,8 0,7 0,8 1,1 0,6 0,7 0,8 0,8 1,5 0,6 1 0,4 0,9 0,4 1,3 1,4 3 1,1 0,5 1 0,9 0,8 0,6 0,7 0,9 1,1 0,5 0,6 0,7 1 1,1 1,2 0,9 1,2 0,9 4 0,9 0,6 0,6 0,5 1 0,7 1,1 0,9 0,8 0,5 0,7 0,5 0,8 0,9 1 1,2 1 1,5 5 1 0,7 0,7 0,8 0,7 0,9 0,9 0,8 1,1 0,5 0,8 0,4 0,9 0,8 1 0,8 0,8 1,5 Tabla 11. Resolución del sistema en cada una de las coordenadas de medición, en centímetros. 72 de 199 Resultados Figura 62. Gráfica de la resolución del eje X. Figura 63. Gráfica de la resolución del eje Y. 73 de 199 Resultados 6.3.3 - Medición de una misma Coordenada a lo Largo del Tiempo Para esta prueba coloqué el tag infrarrojo en una coordenada determinada y medí las variaciones producidas en el cálculo del programa durante un tiempo determinado. En concreto puse el tag en la coordenada (3,5) de la matriz de medición. Escogí esta coordenada puesto que el tag era visto por 3 wiimotes diferentes, además de que la distancia entre los wiimotes y el tag infrarrojo era aproximadamente la misma. Cree una función en el programa que recogiera datos cada 2 segundos y los guardara formateados en un archivo de texto plano. Recogí las coordenadas transformadas de cada uno de los cuatro wiimotes, tanto en píxeles como en metros, además del porcentaje de batería de los mismos. También recogí las coordenadas finales calculadas por el programa, tanto en píxeles como en metros. La duración de la muestra fue de 3 horas. La cantidad de datos recogidos durante la duración de la prueba fue enorme, por esa razón no incluiré la tabla con los mismos. Más bien mostraré la desviación estándar de cada grupo de datos recogidos para comprobar la variación a través de las 3 horas de pruebas. W1 X (pixeles) W1 Y (pixeles) W1 X (metros) W1 Y (metros) 0 0,4402 0 0,0046 Tabla 12. Desviación estándar de los datos recogidos del wiimote 1. W2 X (pixeles) W2 Y (pixeles) W2 X (metros) W2 Y (metros) 0 0 0 0 Tabla 13. Desviación estándar de los datos recogidos del wiimote 2. W3 X (pixeles) W3 Y (pixeles) W3 X (metros) W3 Y (metros) 0,4767 0 0,0049 0 Tabla 14. Desviación estándar de los datos recogidos del wiimote 3. W4 X (pixeles) W4 Y (pixeles) W4 X (metros) W4 Y (metros) 0 0 0 0 Tabla 15. Desviación estándar de los datos recogidos del wiimote 4. X calculada (pixeles) Y calculada (pixeles) X calculada (metros) Y calculada (metros) 0,4767 0 0,0049 0 Tabla 16. Desviación estándar de los datos calculados por el programa. 74 de 199 Resultados 6.4 - Estudio de los Resultados 6.4.1 - Medición de Coordenadas en la Matriz Observando los errores a lo largo de las distintas mediciones, se pueden ver varios detalles: 1 - Los errores tanto en las componentes X como Y suelen ser muy similares en cada una de las coordenadas de medición. Aún así, se pueden observar zonas, cerca de la coordenada (5,9), en la que el error es mucho más elevado que en el resto de coordenadas, y esto se repite de forma sistemática para esta coordenada además de alguna otra. La causa pudiera ser la batería de los mandos. Pero el mando del cual se cogían las coordenadas tenía una carga similar a las de los demás mandos y, de hecho, este era el que mayor batería tenía de todos. Además ya en la 1ª medición, cuando los mandos tenían las baterías nuevas, ya se producían errores mucho mayores en las mismas zonas. Por todo esto se puede determinar que el causante de este efecto no eran las baterías de los wiimotes. La distancia tampoco sería un factor determinante puesto que, si bien en otras coordenadas en las que el wiimote que veía el tag estaba a una distancia similar hay una tendencia a errores más elevados, los valores de los mismos no eran tan altos. Además la distancia perpendicular entre el wiimote y el tag infrarrojo era menor de 5,5 metros. A esa distancia el wiimote no debería tener problemas para ver el tag infrarrojo, teniendo en cuenta el diseño del tag. Lo explicado en los anteriores párrafos me hace pensar que los errores producidos en esta zona, y quizá en otras, se debe a que es necesaria una mayor precisión en el momento de la calibración del mando. En el momento de realizar la calibración sucede que algunas de las marcas de calibración quedan a una distancia en la que se hace necesario introducir 3 decimales. Pero el programa tan solo acepta 2. Esto, unido a los errores de medición que puedan haber al medir la posición absoluta de alguna de las cuatro marcas de calibración, pudiera hacer que en determinadas posiciones los errores en las mediciones fueran más abultados conforme llegáramos a zonas extremas del área de visión del mando. 2 - La carga de la batería afecta a la calidad de las mediciones. Antes he dicho que la carga de la batería no causaba que en según que zonas los errores fueran mucho mayores que en otras. Pero a lo largo de las tres mediciones, la carga de los mandos ha ido descendiendo y faltaba ver si los errores también fueron aumentando. Para ello decidí observar los errores almacenados en las Tablas 7, 8 y 9, y hacer unas gráficas en las que pudiera ver el porcentaje de veces que se repetían los errores durante las tres mediciones del apartado 6.3.1: 75 de 199 Resultados 1ª Medición 1% 4% 1% 8% 23% 10% 19% 33% 0 1 2 3 4 5 6 7 Figura 64. Gráfica con el porcentaje de repetición de los errores en la 1ª medición 0 cm 1 cm 2 cm 3 cm 4 cm 5 cm 6 cm 7 cm 21 30 17 9 7 4 1 1 2ª Medición 2% 6% 1% 4% 21% 11% 17% 38% 0 1 2 3 4 5 6 7 Figura 65. Gráfica con el porcentaje de repetición de los errores en la 2ª medición 0 cm 1 cm 2 cm 3 cm 4 cm 5 cm 6 cm 7 cm 19 34 15 10 4 5 2 1 76 de 199 Resultados 3ª Medición 1% 4% 3% 6% 23% 9% 18% 36% 0 1 2 3 4 5 6 7 8 Figura 66. Gráfica con el porcentaje de repetición de los errores en la 3ª medición 0 cm 1 cm 2 cm 3 cm 4 cm 5 cm 6 cm 7 cm 8 cm 21 32 16 8 5 3 4 0 1 En las tres anteriores gráficas se puede observar que a lo largo de las mediciones se ha producido un empeoramiento de los errores obtenidos. Existen 45 puntos de medición, con dos componentes a medir, X e Y, en cada punto, lo que nos da un total de 90 valores. De estos 90 valores, 77, 78 y 77 han estado comprendidos entre 0 y 3 cm para la 1ª, 2ª y 3ª medición respectivamente. Los valores restantes de cada medición han estado comprendidos entre 4 y 8 cm, y la cantidad de errores en este intervalo también se ha mantenido estable. Sin embargo, se puede observar como, a lo largo de las tres mediciones, ha habido un desplazamiento en la número de errores de 4 cm hacia valores más altos. Este efecto pudiera deberse a la batería de los wiimotes, pero no puede determinarse tan solo con estas mediciones. Por ello decidí realizar dos mediciones adicionales. En esta ocasión cambiaría las pilas de los mandos por unas nuevas. Para realizar el cambio de baterías, decidí abrir levemente las pinzas de los micrófonos de sujetaban los mandos. Deslicé los mandos a través de las ranuras y cambié las pilas. Luego, volví a abrir levemente las pinzas de los micrófonos e introduje los wiimotes dentro de las ranuras. Hice unas marcas antes de sacarlos para saber en que posición debían estar. No volví a calibrar el sistema. 77 de 199 Resultados Seguí adelante con el primer conjunto de mediciones, pero observé que en algunas de las coordenadas de la matriz el sistema no era capaz de ver el tag infrarrojo. En este momento es cuando me di cuenta de que el tag infrarrojo mismo también tiene una batería, y esta podría ser la causante de este efecto. De todas formas quise seguir adelante con la medición que ya había comenzado y así podría conseguir dos cosas: 1. La primera sería que podría comparar dos grupos de mediciones distintos realizados bajo los mismos datos de calibrado, pero cuyos wiimotes habían sido sacados y colocados por el usuario en la misma posición. Con los datos se podrá ver si hay cambios significativos en los errores. 2. La segunda sería que podría comprobar qué efecto tiene la carga de la pila del tag infrarrojo en las mediciones, para una carga de batería de los mandos dentro del un rango aceptable. Para el primer punto pensé en calcular la desviación entre los errores de las tres mediciones del apartado 6.3.1 y la medición que hice con las pilas nuevas tanto en el wiimote como en el tag: cm X Y 1 X Y 2 1 0,58 0 0,5 0,58 2 0,58 0,58 0,5 0,58 3 0,5 0,5 0,58 0 4 0 0,5 0,58 0,5 5 0 0,58 0,5 0,82 X Y 3 0,96 0,5 0,5 0 1 0 0,5 0 0,5 0,5 X Y 4 X Y 5 X Y 6 X Y X 7 0 0,82 0,5 0 0,5 0,5 0,5 0,5 0,5 0,5 0 0,5 1 0 0,96 0,5 0 0,5 0,5 0 1,5 0,58 0 0,58 0 0,82 0,5 0,5 0,5 1 1,26 0,96 1 0,58 1,26 1 0,5 0,96 0,58 2 0,5 2,87 1 0,5 0,58 Y 8 X Y 9 0,5 0,96 0,5 1,91 3 0,5 0 0 0 0,82 1,29 0,5 1,89 1,73 2,71 Tabla 17. Desviación entre los errores de las mediciones del apartado 6.3.1 y la nueva medición con pilas nuevas en todos los elementos, en centímetros. No se pueden comparar los resultados de ambas pruebas debido a que el experimento cambio cuando yo toqué los wiimotes. Sin embargo, si comparo los resultados de la Tabla 17 con los registrados en la Tabla 10, veo claramente el efecto que produce en el sistema utilizar unos datos de calibración que no se ajusten exactamente a la posición de los wiimotes. Si bien algunos valores de la desviación son similares a los de la Tabla 10, en la mayoría de valores se ha producido un empeoramiento del error. Además, se producen mayores desviaciones en zonas donde el error ya era elevado, lo que refuerza mi opinión de que la causa de estas zonas con errores más elevados que el resto sea una calibración poco precisa. 78 de 199 Resultados Para el segundo punto pensé en contrastar los resultados obtenidos durante las dos nuevas mediciones. Esto se muestra en las siguientes dos tablas: -1ª medición, realizada el 6 de Septiembre de 2012. Batería de los wiimotes entre el 100% y el 89%: m 1 2 3 4 5 X Y X 1 0,45 0,43 0,42 0,42 Y X 2 1,18 1,74 2,33 2,95 0,88 0,88 0,88 0,88 Y X 3 1,18 1,74 2,33 2,94 1,32 1,33 1,33 1,33 Y X 4 1,18 1,75 2,33 2,93 1,76 1,74 1,75 1,75 1,77 Y X 5 0,63 1,17 1,74 2,33 2,92 2,19 2,18 2,18 2,18 - Y X 6 0,62 1,18 1,75 2,4 - 2,64 2,63 2,64 2,63 - Y X 7 0,63 1,18 1,75 2,33 - 3,09 3,07 3,08 3,11 - Y X 8 0,62 1,18 1,75 2,33 - 3,52 3,52 3,51 3,55 - Y 9 0,6 1,17 1,74 2,32 - 3,95 3,96 3,96 3,96 - 0,59 1,16 1,73 2,3 - Tabla 18. Resultados de la primera de las nuevas mediciones, en metros. -2ª medición, realizada el 6 de Septiembre de 2012. Batería de los wiimotes entre el 89% y el 65%: m X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y X 6 Y X 7 Y X 8 Y 9 1 0,46 0,61 0,88 0,61 1,31 0,61 1,75 0,62 2,18 0,62 2,64 0,62 3,07 0,6 3,51 0,6 3,95 0,58 2 0,45 1,17 0,88 1,17 1,3 1,17 1,73 1,17 2,19 1,18 2,63 1,17 3,07 1,17 3,51 1,18 3,96 1,15 3 0,44 1,74 0,88 1,74 1,32 1,74 1,76 1,74 2,17 1,75 2,64 1,75 3,08 1,74 3,52 1,73 3,96 1,73 4 0,42 2,34 0,88 2,33 1,33 2,33 1,76 2,32 2,2 2,35 2,66 2,35 3,09 2,32 3,55 2,31 3,97 2,3 5 0,41 2,95 0,88 2,95 1,33 2,93 1,77 2,92 2,22 2,97 2,69 2,97 3,15 2,93 3,58 2,9 4,01 2,88 Tabla 19. Resultados de la segunda de las nuevas mediciones, en metros. Ahora las dos tablas con los errores de las mediciones: m 1 2 3 4 5 X Y X 1 Y 2 0,01 0,01 0,01 0,01 0,02 0 0,02 0,04 0 0 0 0 X Y X 3 - 0,01 0,02 0 0,02 0,02 0 0,01 0,01 0,01 0,01 0,01 0,01 0,01 0,03 0,01 0,02 0,01 Y X 4 Y 5 0,05 0,01 0 0,01 0,01 0,02 0,02 0,02 0,02 - X Y X 6 0,04 0,01 0,05 0 0,02 0,01 0,02 0,01 0,01 0 0,01 0 0,08 0,01 0,01 0,03 - Y 7 X Y 8 Y 9 0,04 0,01 0,02 0,02 0,01 0,02 0 0,01 0 0 0,01 0,01 0 0 0,01 0,01 0,03 0 0 0,02 - Tabla 20. Error entre la 1ª medición del programa y las medidas reales, en metros. 79 de 199 X Resultados -2ª medición, realizada el 6 de Septiembre de 2012. Batería de los wiimotes entre el 89% y el 65%: m X Y X 1 Y X 2 Y X 3 Y X 4 Y X 5 Y X 6 Y X 7 Y X 8 9 1 0,01 0,02 0,01 0,03 0,02 0,03 0,02 0,04 0,03 0,04 0,01 0,04 0,02 0,02 0,02 0,02 0,02 2 0,01 0 0 0,01 0 4 0,02 0,01 0 0,01 0,01 0,01 5 0,03 0,04 0 0,04 0,01 0,02 0,01 0,01 0,02 0,06 0,05 0,06 0,07 0,03 0,06 3 0 0,01 0,02 0,01 0,03 0,01 0,01 0,02 0,01 0,01 0,01 0,01 0,01 0,02 0 0 0 0 0 0 0 0,03 0,01 0 0 0,01 0 0,03 0,02 0,03 0,01 0 0 0 Y 0,01 0 0 0,01 0 0,01 0,03 0,01 0,01 0,02 0 0,05 0,02 Tabla 21. Error entre la 2ª medición del programa y las medidas reales, en metros. Observando las tablas 20 y 21 se ve claramente el efecto negativo que ejerce la carga de la pila del tag infrarrojo. Hasta 16 valores no se pudieron registrar por el programa. Además, con la pila vieja en el tag, se obtuvieron un 20% de valores con un error del 0%, mientras que en las mediciones con la pila nueva fueron un 26% los valores con un error del 0%. Recuperando la razón por la que he realizado estas nuevas mediciones, y viendo los resultados obtenidos, no puedo concluir que factor influyó en las primeras mediciones. Aunque se ha visto el efecto negativo que tuvo la poca batería del tag infrarrojo cuando las pilas de los wiimotes son nuevas, durante la primera tanda de mediciones, tanto las baterías de los wiimotes como la del tag fueron disminuyendo a la vez. Por ello, en el caso de dichas mediciones, no puedo precisar la causa de que se produjera un empeoramiento de los errores que eran peores que el resto. No obstante, tanto si la batería del tag infrarrojo como la de los wiimotes ejercen un efecto negativo en las medidas del sistema no es algo determinante para el proyecto. Las baterías de ambos elementos disminuyen de forma muy rápida y en una situación en la que se quisiera emplear el sistema durante un periodo de tiempo suficientemente largo, sería imposible hacerlo utilizando pilas en ambos elementos. Por ello se recomienda, y casi se obliga, a que el usuario provea de alimentación constante tanto a los wiimotes como al tag infrarrojo si piensa emplear el sistema durante largos periodos. 3 - El sistema es consistente en las mediciones. Observando la Tabla 10 pude darme cuenta que el sistema ofrece una gran repetibilidad en los resultados obtenidos. Las diferencias entre las distintas muestras son nulas o muy pequeñas para un porcentaje muy elevado de las posiciones probadas. Hasta en las coordenadas de prueba en las que el error era mayor se seguían obteniendo errores elevados en las distintas mediciones. Si bien hay coordenadas en las que se pueden observar algunas diferencias en las distintas mediciones, en la gran mayoría de casos el sistema demuestra ser fiable en este aspecto. 80 de 199 Resultados 6.4.2 - Medición de la Resolución La resolución del sistema es consistente a lo largo de toda la matriz de coordenadas. En cualquiera de los puntos testeados se registraron resoluciones similares tanto para la componente X como para la componente Y de la coordenada. La peor resolución que se encontró fue de 1,5 cm y la mejor de 0,4. Comparando las gráficas de la resolución con las de los errores obtenidos durante las mediciones del apartado 6.3.1, se puede ver que las peores resoluciones obtenidas tienden a encontrarse en las mismas zonas en las que los errores eran mayores. Esto me lleva a pensar que este efecto sea causado, de nuevo, por una calibración menos precisa de lo deseable. Sin embargo, una resolución de 1,5 cm en el peor de los casos es perfectamente asumible para el objetivo del programa, que es el de realizar el seguimiento de un robot de un diámetro de unos 30 a 50 cm. 6.4.3 - Medición de una misma Coordenada a lo Largo del Tiempo Los datos obtenidos demuestran que el sistema es consistente durante un largo periodo de tiempo. Si bien hay algunas cosas que comentar acerca de esta prueba y los datos obtenidos: • El Wiimote 2 no vio el tag infrarrojo en ningún momento durante estas pruebas puesto que desde su posición no alcanzaba a verlo. Por lo tanto este mando no intervino en el cálculo de la posición final del tag, pese a que se muestran sus datos en la Tabla 13. • El Wiimote 1 dejó de ver el tag infrarrojo media hora antes de terminar las pruebas. Para el cálculo de la desviación estándar de los valores tan solo tuve en cuenta los datos recogidos hasta que el Wiimote 1 dejo de ver el tag infrarrojo. Como no he podido determinar si el empeoramiento de las mediciones tuvo que ver con las baterías de los wiimotes o con la del tag infrarrojo, tampoco aquí puedo concluir que la causa de que el wiimote dejara de ver el tag fuera la disminución de su batería. Bien pudiera ser que la pila del tag no pudiera proveer de la suficiente intensidad a los leds como para que, desde la posición del wiimote 1 pudiera verse. • Leyendo los datos recogidos, no apreciaba una disminución de la batería de los wiimotes. Esto me hizo investigar posteriormente la función que yo empleaba para guardar los datos. Descubrí que los valores que reflejaban la carga de las baterías de los wiimotes no se estaban actualizando. Las coordenadas enviadas por los mandos si se guardaron correctamente puesto que variaron levemente con el tiempo. Por ello no puedo precisar cuanto descendieron las cargas de las baterías de los wiimotes durante esta prueba. • Observando las Tablas 11 a 15, se puede ver que los datos enviados por los wiimotes se mantuvieron constantes durante las tres horas que duró la prueba y apenas hubo variaciones. La coordenada calculada por el programa al inicio de la 81 de 199 Resultados prueba fue (2,1968, 1,7409) y la que se estaba mostrando al finalizar las tres horas fue (2,1865, 1,7409), siendo la coordenada real (2,2, 1,74). Los resultados han sido satisfactorios puesto que se ha comprobado que el cálculo de una coordenada fija se mantiene estable durante un largo periodo de tiempo. 82 de 199 Conclusiones 7 - Conclusiones A continuación resumo las conclusiones más importantes extraídas del desarrollo de este proyecto, y las divido en dos partes: 7.1 - Funcionalidad del Programa • Observando las mediciones y las gráficas realizadas en el apartado 6 de esta memoria, me di cuenta que, en general, se obtuvieron mediciones con un error menor a lo largo de la zona central de la sala de seguimiento. Esta zona coincide con la zona en la que uno o varios wiimotes contribuyen al cálculo de las coordenadas finales. Por ello se puede decir que se obtienen resultados más ajustados a la realidad si se intenta que las áreas de visionado de los wiimotes se superpongan entre si, pese a que de esta forma el área total de seguimiento del sistema se vea reducida. • En cuanto al montaje del sistema, he podido observar que el factor de mayor importancia es la calibración. Si los wiimotes se colocan a una altura mayor o menor, o con unos ángulos alfa o beta mayores o menores es importante pero no determinante. Si en el proceso de calibración alguna de las marcas de calibración no ha podido calcularse de la forma más precisa posible produce un efecto negativo sobre los resultados. • La carga de la batería de los wiimotes y la del tag infrarrojo desciende de forma muy rápida durante el uso normal del programa. Esto tiene un efecto negativo en varias zonas de medición. Por lo tanto y debido a que el uso del sistema se alargará durante largos periodos de tiempo, se hace obligatorio el uso de alimentación constante tanto para los wiimotes como para el tag infrarrojo. • El sistema puede posicionar perfectamente un robot de unos 30 o 50 cm de diámetro, dentro de una sala de entre 15 y 20 metros cuadrados usando para el seguimiento un mínimo de 4 wiimotes. Para un robot de ese tamaño, o incluso un poco menor, y gracias a las pruebas que he realizado, puedo garantizar que el robot estaría en la posición marcada por el programa con un margen de error muy pequeño, de entre 1 y 4 cm. Un error perfectamente asumible para un robot del tamaño especificado anteriormente. • Se ha detener en cuenta que la velocidad de actualización del programa viene determinada por las características técnicas del ordenador que se esté utilizando para hacer funcionar el programa. El ordenador que he utilizado para las pruebas ha sido un Macbook con un procesador Intel Core 2 Duo a 2,2 GHz de frecuencia. Sin embargo debo decir que debido a que para las pruebas no iba a medir la velocidad de reacción del programa, utilicé un programa de virtualización para utilizar el programa desde mi Macbook. A mayor velocidad de procesado se podrán seguir robots u objetos que se desplacen a una velocidad mayor. • El sistema de notificaciones integrado en el programa es la funcionalidad que más juego da al programa. El seguimiento del objeto está bien y es el objetivo central del 83 de 199 Conclusiones proyecto, pero obliga a que el usuario esté presente delante del ordenador. Sin embargo, poder recibir notificaciones de la posición del objeto cuando no estamos delante del programa es de una gran utilidad. Es por ello que durante las pruebas que realicé también estuve probando el sistema de notificaciones que tiene integrado el programa. La función de zona prohibida funciona perfectamente. El programa envía un mensaje directo por Twitter al usuario en el mismo instante en el que el objeto entra en la zona no deseada. Las notificaciones vía Twitter son muy rápidas y llegan tanto al móvil como al ordenador en cuanto se lanza el aviso. Sin embargo el servicio de notificaciones por e-mail dependerá del tipo de correo que el usuario utilice. En mi caso utilicé una cuenta de Gmail y llegaban al instante puesto que Gmail envía los mensajes de correo a dispositivos móviles al instante. 7.2 - Desarrollo del Proyecto • En cuanto a la programación del sistema puedo decir que la preparación académica en lenguajes de programación es muy útil. En general, estudiar un poco de varios lenguajes de programación facilita la comprensión de la mayoría de lenguajes restantes. Esto, unido a la gran cantidad de recursos de ayuda vía web que existen, tanto en las páginas de Microsoft, como en las de usuarios y páginas de recopilación de aplicaciones de ejemplo del lenguaje C# (.NET) facilitó mucho la programación de la aplicación e hizo innecesario el uso de bibliografía adicional. • Ponerse a programar un sistema que dependiera de librerías cuya última actualización fue hace 3 o 4 años ha sido costoso. La comunidad de usuarios fue muy activa durante los años en los que surgieron tanto el wiimote como las librerías que se crearon para conectarlos al PC. Sin embargo, pasando los años, muchos de los foros y comunidades creados alrededor de ellos fueron borrados o cayeron en el olvido, aumentando la dificultad de la búsqueda de ayuda en este aspecto. • Me ha resultado particularmente difícil hacer una separación entre la adición de nuevas características al programa y la mejora de las ya existentes. La cantidad de nuevas ideas para el programa ha sido constante a lo largo del desarrollo de todo el proyecto. Sin embargo a veces he perdido tiempo añadiendo según que funcionalidades al programa. Las nuevas funcionalidades son útiles, pero quizás es bueno aprender cuando hay que cortar el flujo de nuevas ideas y centrarse en terminar las ya existentes. En general estoy satisfecho con los resultados obtenidos. El programa se mostró fiable y preciso durante las pruebas, más de lo que hubiera podido esperar en un principio. El objetivo del proyecto se vería cumplido puesto que he podido desarrollar un sistema que es capaz de realizar el seguimiento de un objeto con errores asumibles y que podrían reducirse si la parte de la instalación física del sistema aumentara en calidad. 84 de 199 Posibles Mejoras 8 - Posibles Mejoras Durante las distintas fases de este proyecto he seguido implementando mejoras al mismo. Pero como en cualquier otro proyecto, llega un momento en el que detuve el flujo de nuevas ideas o mejoras puesto que era momento de cerrar el proyecto definitivamente. Aún después de tomar esta decisión, mejoré algunos aspectos de la interfaz de usuario e implementé alguna mejora que vi oportunas, tales como el sistema de notificaciones. De todas formas, muchas otras ideas o mejoras se quedaron en el tintero por falta de tiempo. Si tuviera que continuar con este proyecto, mejorándolo y ampliándolo, estas que voy a describir son algunas de las posibles mejoras que darían una mayor utilidad a este proyecto. - Interfaz de Usuario • Mejorar los menús, añadiendo más opciones para el usuario. • Implementar la carga de la última disposición de los wiimotes. Cuando el usuario ha colocados y configurado los wiimotes existentes en su instalación, debe volver a repetir este paso cada vez que reinicie el programa. Creación de funciones que se encarguen de guardar toda esta información en archivos para que el usuario pueda cargar distintas configuraciones según el lugar donde se esté realizando el seguimiento. • Implementar la posibilidad de incluir algún tipo de grabación de datos. El usuario puede desear grabar los movimientos del objeto durante un tiempo determinado. Crear el código y la interfaz necesaria para que esto pueda ser posible, tal vez guardando las coordenadas del objeto cada x segundos. Cuando el usuario quiera visionar esta grabación, ir dibujando esas coordenadas en el lienzo de forma secuencial, para que parezca estar viendo una grabación de los movimientos del objeto. • Permitir dibujar más de una zona prohibida. Poder dibujar distintas formas, no solo cuadriláteros. • Implementar la posibilidad de seleccionar salas de distintas formas. Quizás salas en forma de L, con columnas en el centro, o con formas irregulares. - Sistema y Código de Seguimiento • Probar distintos métodos de seguimiento. No escoger como wiimote base el wiimote con mayor batería, sino el que tenga una visión más centrada y más cercada del objeto. Esto se puede observar por las coordenadas que recibe el wiimote y por el tamaño del punto infrarrojo observado. 85 de 199 Posibles Mejoras • Reducir el área de visión de los wiimotes. Acotar 20 o 30 centímetros en los extremos de las áreas de visión para que estas no se tomen en cuenta en los cálculos de la posición del objeto. De esta forma se evita usar coordenadas de mandos que ven el objeto en sus extremos y pueden tener una peor precisión. - Sistema de Notificaciones • Investigar otros métodos o servicios de notificación. • Volver a intentar la implementación de servicios como el SMS o Facebook. • Cuando se avisa al usuario de que el objeto ha invadido una zona prohibida, adjuntar una captura del lienzo para que este conozca exactamente donde se ha producido la invasión. - Código General • El dibujado del lienzo tiene una gran carga en el rendimiento del ordenador. Optimizar el código que se encarga del dibujado de capas para reducir el consumo de memoria y microprocesador del programa. • Modificar código de seguimiento para que consuma menos recursos. • El usuario puede mover los wiimotes una vez colocados en el lienzo. Mejorar la eficacia del código que permite estas operaciones ya que a veces funciona a golpes. Reducir los recursos que emplea el código para que sea más fluido. • Desechar el uso de archivos de texto plano para el guardado de datos de configuración y de calibración de los wiimotes y la futura implementación del guardado de la disposición de los wiimotes. Sustituir este tipo de archivos por archivos en lenguaje XML que permiten una integración mejor con otros sistemas. • Existe un archivo que guarda datos clave del usuario. Estos están en texto plano y son perfectamente visibles por cualquier persona. Establecer algún tipo de cifrado para estos datos sensibles e importantes. - Implementación Física del Sistema • Uso de baterías recargables con conexión usb para poder tener un seguimiento más preciso y de una duración mayor. • Modificación de los mandos. Soldar un pequeño condensador en los contactos del botón de sincronización situado dentro de la tapa de las pilas del wiimote. De esta forma, cuando se enchufen los wiimotes, entrarán en modo de búsqueda y se podrán emparejar desde el ordenador sin tener que pulsar los botones 1 y 2 de cada wiimote. 86 de 199 Presupuestos 9 - Presupuesto El presupuesto del prototipo se puede examinar en la tabla inferior. La compra de dichos artículos es variada: eBay, páginas web especializadas en pizarras interactivas y tiendas de electrónica. Descripción Unidades Precio unitario Precio total Led IR Vishay TSAL 6400 37 ! 0,49 ! 18,13 Pinza micrófono YH-4 4 ! 4,62 ! 18,48 Resistencia 270$ 1/4W 1 ! 0,04 ! 0,04 Pulsador membrana 12x12 1 ! 0,42 ! 0,42 Interruptor ON-ON 6A 1 ! 2,42 ! 2,42 Portapilas 9V 1 ! 0,46 ! 0,46 Cable rojo 22AWG 0,30 ! 1,46 ! 0,44 Caja HAMOND 105x60x22 1 ! 3,03 ! 3,03 Placa topos prototipado 0,21 ! 4,01 ! 0,84 Baterías recargables para wiimote 2 ! 2,94 ! 5,88 Pilas 9 V 3 ! 2,09 ! 6,27 Horas de montaje tag infrarrojo 5 ! 25,00 ! 125,00 Horas de desarrollo software 540 ! 25,00 ! 13.500,00 TOTAL ! 13.681,41 Tabla 16. Presupuesto correspondiente al diseño del prototipo 87 de 199 Presupuestos Para conocer el coste que tendría una hipotética producción de este proyecto, supondré una tirada de 1000 unidades. En este presupuesto no se tendrán en cuenta algunos artículos que se supone debe proveer el comprador, puesto que dependen de la configuración que el quiera utilizar. Los precios unitarios han sido tomados a día 18 de Agosto de 2012. Descripción Uds. Led IR Vishay TSAL 6400 P.U. Farnell P.T. Farnell ! 0,171 ! 0,00 Resistencia 270$ 1/4W 1000 ! 0,008 ! 8,00 Pulsador membrana 12x12 1000 ! 0,270 ! 270,00 Interruptor ON-ON 6A 1000 ! 1,44 ! 1.440,00 Portapilas 9V 1000 ! 0,24 ! 240,00 Cable rojo 22AWG 300 ! 51,24 ! 51,24 Caja HAMOND 105x60x22 1000 ! 2,68 ! 2.680,00 Placa topos prototipado 48 ! 3,44 ! 165,12 Horas de montaje tag infrarrojo 2500 ! 20,00 ! 50.000,00 TOTAL ! 54.854,36 IVA 21,00% ! 11.519,42 Precio artículos y horas de montaje incluyendo IVA ! 66.373,78 Amortización de desarrollo y prototipo ! 13.681,41 Precio total incluyendo amortización ! 80.055,19 Precio unitario sin beneficio ! 80,06 Tabla 17. Presupuesto de producción de una tirada de 1000 unidades En estos cálculos he supuesto unas 2500 horas dedicadas al montaje del tag infrarrojo. Si bien un tag infrarrojo se monta en unas 5 horas, he supuesto que para 1000 tags infrarrojos podrían automatizarse ciertas tareas o fabricar en cadena ciertos montajes, reduciendo el tiempo de fabricación de un tag infrarrojo a la mitad. 88 de 199 Presupuestos Referencias de presupuestos Componente Led IR Vishay TSAL 6400 Resistencia 270$ 1/4W Pulsador membrana 12x12 Interruptor ON-ON 6A Portapilas 9V Cable rojo 22AWG Caja HAMOND 105x60x22 Placa topos prototipado Dirección web http://es.farnell.com/vishay/tsal6400/iremitter-5mm-940nm/dp/3152868 http://es.farnell.com/multicomp/mcf-0-25w-270r/ resistor-0-25w-5-270r/dp/9339353 http://es.farnell.com/te-connectivity-alcoswitch/fsm100/ switch-tactile-spst-50ma-through/dp/1570388 http://es.farnell.com/apem/5636a/switch-spdt-6a-250vsolder/dp/1082299 http://es.farnell.com/keystone/233/battery-strap-9v-wirelead/dp/4518238 http://es.farnell.com/alpha-wire/3251-rd001/wireul1061-22awg-red-305m/dp/1199017 http://es.farnell.com/hammond/001115/case-absgrey-105x60x22mm/dp/1244225 http://es.farnell.com/roth-elektronik/re200-hp/pcbeurocard-fr2-2-54mm/dp/1172146 Tabla 18. Referencias de presupuestos 89 de 199 Anexos Anexos 1 - Cálculo Área de Visión de un Wiimote Para visualizar las áreas de visión de los wiimotes en el lienzo dispuesto para ello, debemos conocer los cuatro pares de coordenadas que delimitarán el cuadrilátero que contiene este área. Sabemos que este cuadrilátero cambiará de dimensiones si varían tanto el ángulo " como la altura h. Para poder hallar pues estos cuatro pares de coordenadas, deberemos realizar una serie de cálculos trigonométricos ayudándonos de los ángulos de visión del wiimote. Figura 1. Vista de perfil de un wiimote colocado en una pared En este caso nos centraremos en la vista de perfil de la Figura 1. Sabiendo que el ángulo vertical de visión del wiimote es de 31º, podemos comenzar a hallar varias distancias. Para ello, definiremos el ángulo " como 45º y la altura h como 80 cm y así poder obtener los datos de un caso real. Primeramente nos fijaremos en el triángulo rectángulo izquierdo para poder obtener las distancias x0 y B: 90 de 199 Anexos (2) (3) Nos interesa ahora conocer la distancia x1: (4) (5) Calculemos ahora las distancias x2 y A: (6) (7) (8) 91 de 199 Anexos Una vez calculadas las distancias verticales del cuadrilátero de visión, podemos pasar a calcular las distancias horizontales del mismo. Para ello nos es útil tener una visión en planta del wiimote anterior: Figura 02. Visión en planta del área de visión del wiimote Viendo la Figura 2 podemos observar que para hallar las medidas y0 y z0 debemos realizar unos cálculos trigonométricos con los dos triángulos coloreados. Tenemos dos ángulos marcados en la figura que corresponden con la mitad del ángulo de visión horizontal del wiimote, que es 41º, y que son exactamente iguales ambos, aunque la perspectiva haga pensar que son de distinto tamaño. Calculemos primero z0: (9) (10) (11) 92 de 199 Anexos (12) (13) (14) Una vez calculadas las dimensiones del cuadrilátero, podemos pasar a calcular los cuatro pares de coordenadas que nos indicarán donde dibujar dicho cuadrilátero en nuestro lienzo. Figura 03. Cuadrilátero con la posición del wiimote 93 de 199 Anexos (15) (16) (17) (18) (19) (20) (21) (22) 94 de 199 Anexos Aclarar que X e Y son las coordenadas, en píxeles, a las que hemos colocado el wiimote en nuestro lienzo y que widthFactor y heightFactor son los factores de conversión de metros a píxeles, para ambas dimensiones, calculados en el momento de la creación del lienzo, tal y como se explica en el apartado 4.2.1.1. 95 de 199 Manual de Instalación y Uso 2 - Manual de Instalación y Uso El programa WIPS, acrónimo de Wiimote Indoor Positioning System, forma parte de un sistema de seguimiento de objetos infrarrojos en entornos cerrados. Este programa se basa en la utilización de varios controladores de la videoconsola Wii en conjunto con un ordenador y un tag infrarrojo, incluido este último con la compra de este sistema. 2.1 - Instalación del Software WIPS Para utilizar este programa no será necesaria su instalación. Se ofrecerá como un archivo ejecutable dentro de una carpeta que podrá ser colocada en cualquier lugar del sistema. 2.2 - Manual de Uso de WIPS Antes de poder iniciar el seguimiento de un objeto con el tag infrarrojo, deberemos conectar, iniciar y configurar todo el sistema. 2.2.1 -Requisitos - Hardware • Ordenador con un adaptador Bluetooth. La mayoría de portátiles lo tienen integrado, sino deberás adquirir uno externo. Aquí tienes una lista de adaptadores USB compatibles con el conexionado de wiimotes: http:// wiibrew.org/wiki/List_of_Working_Bluetooth_Devices. • Tag infrarrojo incluido con la compra de WIPS (pila de 9V no incluida). • Un wiimote mínimo. Siete como máximo. • Método de sujeción de los wiimotes a la forma física de la sala donde se vaya a realizar el uso de este programa - Software • Sistema operativo Windows XP. WIPS ha sido probado tan solo bajo Windows XP versión 32 bits. No se asegura el funcionamiento en otros sistemas operativos aunque es muy probable que esto sea así. • Microsoft Bluetooth Stack. Software de conexión Bluetooth incorporado en Windows XP y recomendado para conectar los wiimotes al ordenador. En el siguiente enlace encontrarás varios programas como el anterior: http:// 96 de 199 Manual de Instalación y Uso www.wiimoteproject.com/bluetooth-and-connectivity-knowledge-center/asummary-of-windows-bluetooth-stacks-and-their-connection • .NET Framework 4. WIPS no funcionará si no está instalado este paquete de Microsoft. Para descargarlo ve a la siguiente dirección: http:// www.microsoft.com/es-es/download/details.aspx?id=17851 2.2.1 -Conexionado de los Wiimotes 2.2.1.1 - Emparejamiento Inicial Para poder utilizar la aplicación, deberá emparejar con el ordenador los wiimotes que vaya a utilizar para realizar el seguimiento del tag infrarrojo. En este momento, deberá tener conectado al ordenador el dispositivo Bluetooth. Para realizar el emparejamiento, deberá pulsar simultáneamente los botones 1 y 2 del wiimote a emparejar. Después, en la ventana de selección de dispositivos Bluetooth de su ordenador, deberá comenzar la búsqueda de dispositivos en los alrededores. Aparecerá en su ventana un dispositivo llamado “Nintendo RVL-CNT-01”. Haga doble clic encima del mismo para emparejarlo. Deberá emparejar los wiimotes en un orden establecido puesto que después, una vez haya abierto la aplicación WIPS, tendrá que introducir los wiimotes según este mismo orden. 2.2.1.2 - Uso continuado Una vez haya emparejado los wiimotes con el ordenador, estos permanecerán en la carpeta de dispositivos emparejados. El siguiente paso es conectar dichos wiimotes. Vaya a la carpeta de dispositivos emparejados. Acto seguido pulse los botones 1 y 2 del wiimote que emparejo en primer lugar, y haga doble clic en el icono de la carpeta de dispositivos emparejados que representa a dicho wiimote. Los leds situados en la parte inferior de los mandos estarán parpadeando durante todo el proceso, y solo dejarán de hacerlo cuando usted inicie la aplicación WIPS. 2.2.2 - Funcionamiento del Programa Para comenzar a utilizar el sistema WIPS deberá hacer doble clic en el icono del programa recién instalado. Aparecerá una pequeña ventana en la que deberá introducir las dimensiones físicas de la sala donde va a utilizar la aplicación [Figura 1]. 97 de 199 Manual de Instalación y Uso Figura 01. Introducción de las dimensiones físicas de la sala. Puede utilizar números decimales usando indistintamente las teclas “,” o “.” Asegúrese de rellenar todas las cajas de texto, de lo contrario el programa le indicará que hay un error con los datos introducidos. Figura 02. Debe rellenar todas las cajas de texto. Previamente deberá haber conectados los wiimotes al ordenador, siguiendo los pasos indicados en el apartado 2.2.1 de este manual. De no haber seguido estos pasos, el programa lanzará el siguiente aviso: Figura 03. No hay wiimotes conectados al ordenador. 98 de 199 Manual de Instalación y Uso Haya conectado o no algún wiimote al ordenador, el programa igualmente se abrirá. Pero tenga en cuenta que si no tiene, al menos, un wiimote conectado, no podrá realizar el seguimiento del tag infrarrojo. 2.2.2.1 - Pantalla Principal del Programa Después de que haya introducido las características de la sala, el programa abrirá la pantalla principal del programa. En la Figura 4 tiene una descripción de todos los elementos que se pueden observar en esta pantalla. (1) (2) (3) (4) (5) (7) (6) (8) (9) (10) Figura 04. Pantalla principal del programa. (1)Barra de menú. Desde esta barra podrá acceder al menú de preferencias, localizar los archivos de datos de los que hace uso el programa, y acceder al manual de usuario. (2)Barra de pestañas. Podrá acceder a las distintas pestañas que el programa va creando. Como mínimo habrá dos pestañas: - Pestaña de Configuración. En esta pestaña usted podrá indicar la distribución de sus wiimotes y también podrá editar su configuración. Además, desde esta pestaña es desde donde realizará el seguimiento del tag infrarrojo. - Pestaña de Visión general. Desde esta pestaña podrá recibir, en una misma localización, todos los datos que los wiimotes que tenga conectados le estén enviando. - Pestañas de Wiimote. Por cada wiimote que tenga conectado el programa añadirá una pestaña más. Desde dichas pestañas usted podrá calibrar los wiimotes, 99 de 199 Manual de Instalación y Uso conocer el estado de la carga de la batería y obtendrá una representación visual de lo que dicho wiimote está visualizando dentro de su rango de visión. (3)Botón “Editar distribución”. Este botón permite al usuario entrar en el modo de edición, pudiendo añadir wiimotes al sistema, moverlos de posición o eliminarlos. Además también podrá marcar una zona de restricción para poder ser usada durante el seguimiento. (4)Selección de capas. Mediante estas casillas de selección, el usuario podrá elegir qué capas se visualizaran en la ventana de seguimiento. (5)Ventana de seguimiento. Esta ventana de color negro será semejante en dimensiones a la sala en la que se esté realizando el seguimiento. En esta ventana es donde el usuario deberá introducir los wiimotes y donde marcará la zona de restricción, si así lo deseara. Además, tal y como indica su nombre, en esta ventana será donde se podrá observar el movimiento del objeto en seguimiento. (6)Panel de restricción. El usuario podrá escoger si activa la zona restringida previamente dibujada. Además el usuario puede escoger que la zona restringida sea el área externa o la interna de dicha zona restringida. (7)Botón de seguimiento. El usuario podrá iniciar el seguimiento haciendo clic sobre este botón. Se deberán cumplir algunas condiciones para que esto sea así. (8)Panel de coordenadas del mouse. El usuario recibirá información acerca de la posición del ratón cuando este esté moviéndose encima de la ventana de seguimiento. El usuario recibirá esta posición en píxeles y en metros. (9)Panel de características de sala. En este panel el usuario tendrá acceso a las características de la sala en la que se esté realizando el seguimiento. Serán las que el usuario haya introducido previamente. (10)Barra altura de tag. Esta barra deslizante controla la altura a la que el usuario va a colocar su tag infrarrojo. Distancia en metros. 2.2.2.2 - Configuración del Mapeado de Wiimotes Para comenzar a añadir wiimotes a la ventana de seguimiento, se deberá clicar el botón “Editar distribución”. Cuando se haya pulsado este botón, se entrará en el modo de Edición. En este modo, el panel de selección de capas es sustituido por el panel de distribución, tal y como se ve en la Figura 5. 100 de 199 Manual de Instalación y Uso Figura 05. Paso del modo visualización al modo edición. En este panel podemos observar dos botones a la izquierda, y una zona de información a la derecha. Estos botones tienen estas funciones: - Añadir wiimote. Cuando este botón esté pulsado, el usuario podrá añadir, mover o quitar wiimotes de la ventana de seguimiento. Para hacer esto el usuario deberá hacer clic con el botón izquierdo del ratón encima de la ventana de seguimiento. Cada vez que se haga esto se añadirá un nuevo wiimote. Así hasta llegar a la cantidad de mandos que se hayan conectado al ordenador. Figura 06. Añadiendo un wiimote. Como máximo solo se podrán conectar 7 wiimotes al sistema debido a las limitaciones de la tecnología Bluetooth. 101 de 199 Manual de Instalación y Uso Para mover un wiimote ya colocado bastará con hacer clic, sin soltar, con el botón izquierdo del ratón sobre el wiimote que desee mover. Ahora mueva el ratón a lo largo de la ventana de seguimiento. Cuando haya llegado a la nueva posición en la que quiere colocar el wiimote, suelte el botón izquierdo del ratón. Para eliminar un wiimote deberá hacer clic encima de dicho wiimote con el botón derecho del ratón. Debe tener en cuenta que hay que eliminar los wiimotes de forma regresiva, es decir, en el orden contrario al que fueron colocados. Es decir, un hipotético wiimote nº2 no podrá ser eliminado antes que un wiimote nº3. Para facilitar esta tarea los wiimotes estarán numerados. - Añadir restricción. Cuando este botón esté pulsado, el usuario podrá añadir una zona de restricción en la ventana de seguimiento. Para ello deberá hacer clic sin soltar con el botón izquierdo del ratón, mover el ratón hasta el punto deseado, y entonces soltar el botón. Figura 07. Añadiendo una zona de restricción. Tan solo es posible añadir una sola zona de restricción. Una vez que se hayan añadido los wiimotes deseados y la zona de restricción se deberá volver a pulsar el botón “Editar distribución” que ahora mostrará el mensaje “Detener distribución”. 102 de 199 Manual de Instalación y Uso 2.2.2.3 - Configuración de los Mandos Después de haber añadido los wiimotes deseados a la ventana de seguimiento, habrá que configurar cada wiimote. Para configurar un wiimote bastará con hacer clic con el botón izquierdo del ratón sobre uno de los wiimotes colocados en la ventana de seguimiento. Acto seguido, aparecerá un nuevo panel en la zona inferior derecha de la pantalla principal que nos servirá para configurar el wiimote seleccionado. (3) (1) (2) (4) (5) (6) (7) Figura 08. Panel de edición de las características de cada wiimote. (1)Número de wiimote. Indica cuál es el wiimote seleccionado cuya configuración se va a cambiar. (2)Altura de wiimote. Mediante una caja de texto y una barra de desplazamiento, el usuario podrá indicar a qué altura está colocado el wiimote. La entrada de texto está controlada para que no puedan introducirse letras ni símbolos extraños. La máxima altura posible será la altura de la sala donde se realizará el seguimiento, y la mínima vendrá determinada por la altura del tag infrarrojo. (3)Ángulo alfa. Mediante una caja de texto y un control tipo knob, el usuario podrá indicar el ángulo que el wiimote forma con la horizontal. Se podrán seleccionar valores comprendidos entre 0 y 180, y entre 0 y -180. Cuando se quieran introducir valores negativos en la caja de texto, el usuario podrá hacerlo de dos formas: pulsando la tecla guión, y acto seguido introducir el valor, o introducir el valor y posteriormente pulsar la tecla guión. Ambas acciones comportarán la introducción de un ángulo negativo. (4)Ángulo beta. Controles similares a los del ángulo alfa. Todo es igual exceptuando los rangos de valores; en este caso solo se podrán seleccionar valores comprendidos entre 0 y 90. Para un mejor entendimiento del ángulo alfa y beta, observe la Figura 9. 103 de 199 Manual de Instalación y Uso (5)Posición X. Texto que indicará al usuario la posición X, en metros, del wiimote seleccionado. (6)Posición Y. Texto que indicará al usuario la posición Y, en metros, del wiimote seleccionado. (7)Área de visión. Texto que indicará al usuario la cantidad de m2 que abarca el área de visión del mando seleccionado, atendiendo a la configuración que tenga en ese momento. Figura 09. Representación de los ángulos alfa y beta. Las modificaciones en todos estos controles se verán reflejadas en la ventana de seguimiento de forma instantánea. 2.2.2.4 - Calibrado de Mandos Una vez hay configurado las características de cada wiimote colocado, habrá que calibrarlos. Para la calibración serán necesarios el tag infrarrojo y una plantilla de calibración. Deberá parecerse a la imagen mostrada en la Figura 10. El tamaño de la plantilla debería tener las dimensiones mostradas en la misma Figura. 70 cm 40 cm Figura 10. Plantilla de calibración. Una vez que tenga diseñada la plantilla, deberá colocarla dentro de la zona de visión del wiimote que vaya a calibrar. Las cuatro marcas rojas deben quedar dentro de dicha área. Para asegurarse de ello, puede valerse de la pestaña del mismo wiimote y del tag infrarrojo. Marque las dimensiones de la plantilla con el tag para asegurarse de que quedan dentro del área de visión. 104 de 199 Manual de Instalación y Uso Cuando tenga la plantilla en la posición idónea, debe medir la coordenada, en metros, de cada una de las marcas rojas. Para ello deberá medir la distancia entre cada uno de las marcas y los ejes X e Y, obteniendo un par de coordenadas por cada marca de calibración, tal y como se muestra en la Figura 11. (0,0) P1Y P1X Figura 11. Medición de coordenadas de la plantilla de calibración. Una vez obtenidos los cuatro pares de coordenadas diríjase a la pestaña del wiimote en cuestión. Dentro de ella, en la zona superior derecha, observará un panel denominado “Coordenadas plantilla”. Dentro de dicho panel hay cuatro pares de cajas de texto en las que deberá introducir los pares de coordenadas que usted acaba de obtener. Figura 12. Panel “Coordenadas plantilla”. 105 de 199 Manual de Instalación y Uso Una vez que haya introducido los cuatro pares de coordenadas, en medio de la plantilla de calibración dibujada dentro del panel “Coordenadas plantilla”, aparecerá un botón llamado “Calibrar” (Figura 13). Para lanzar la calibración bastará con hacer clic en dicho botón. Figura 13. Aparición del botón “Calibrar”. Después de hacer clic en el botón “Calibrar”, aparecerá una ventana con el fondo de color negro. En ella aparecerá dibujada la 1ª marca de calibración, situada en la parte superior izquierda. Deberá seguir estos pasos: 1. El programa esperará a que coloque el tag infrarrojo en la 1ª marca de calibración, la que está situada en la zona superior izquierda de su plantilla. Deberá tener el interruptor del tag infrarrojo en la posición “OFF”. 2. Una vez que haya posado el tag infrarrojo encima de la 1ª marca de calibración, deberá pulsar una vez el botón denominado “Calibration”. 3. La marca de calibración anterior habrá desaparecido de la ventana, y ahora se mostrará la 2ª marca, situada en la zona superior derecha. 4. Repita los pasos 1 y 2 con las 3 marcas de calibración restantes. La 1ª marca de calibración está situada en la zona superior izquierda de la plantilla. Recuerde que el orden de las marcas es en sentido horario a partir de la 1ª marca de calibración. 106 de 199 Manual de Instalación y Uso Si quiere cancelar el proceso de calibración, pulse la tecla ESC en cualquier momento. Una vez haya finalizado con la última marca, la ventana de calibración desaparecerá y el panel “Coordenadas plantilla” mostrará el siguiente aspecto: Figura 14. Aspecto del panel “Coordenadas plantilla” una vez finalizada la calibración. Como puede observar en la Figura superior, en la ventana negra, situada bajo el texto “Puntos de calibración”, se dibujará un cuadrilátero, formado por los cuatro puntos que usted acaba de marcar. Dicho cuadrilátero está deformado debido a las leyes de la perspectiva; es debido a esta razón que es necesaria la calibración. Observará también que la casilla “Mando calibrado” está activada, informando al usuario que el proceso de calibración ha sido satisfactorio. Se habrá dado cuenta que en el panel “Coordenadas plantilla” existen dos botones. Para facilitar la tarea del calibrado si usted va a emplear la misma configuración de wiimotes de manera sistemática, existe la posibilidad de que pueda guardar y cargar datos de calibrado de los wiimotes. 107 de 199 Manual de Instalación y Uso - Guardar Datos de Calibración. Cuando haya calibrado un mando y quiera guardar los datos de calibración, haga clic en el botón “Guardar datos”. Se creará un archivo con extensión .dat llamado “calibration_data_X”, donde la X corresponde con el número de wiimote calibrado. Si no se ha ejecutado el proceso de calibración y se hace clic en el botón “Guardar datos” el programa lanzará el siguiente mensaje de error: Figura 15. Mensaje de error en el guardado de los datos de calibración. - Cargar Datos de Calibración. Cuando quiera cargar los datos de calibración de un wiimote guardados anteriormente, haga clic en el botón “Cargar datos”. Si existe el archivo “calibration_data_X”, donde X es el número de wiimote que se quiere calibrar, el programa cargará los datos. Si no existiera dicho archivo, el programa le avisaría de que los datos no se han podido cargar. 2.2.2.6 - Activación de Seguimiento Después de haber configurado todo los aspectos del programa, estará listo para comenzar con el seguimiento. Para ello deberá pulsar el botón de color verde situado en la esquina superior derecha llamado “Activar seguimiento”. Figura 16. Activando el seguimiento. Una vez pulsado el botón, si el tag infrarrojo está situado dentro del área de visión de alguno de los wiimotes que usted ha colocado a lo largo de la sala de seguimiento, deberá aparecer un círculo de color rojo dentro de la ventana de seguimiento. Dicho círculo rojo corresponde con la posición en la sala del tag infrarrojo. 108 de 199 Manual de Instalación y Uso Figura 17. Tag infrarrojo visto por algún wiimote. En la zona inferior derecha, donde se encontraba situado el panel de información de wiimote, se mostrará ahora el panel de información de seguimiento. En dicho panel se le informará acerca de la posición del tag infrarrojo. Tendrá las coordenadas de la posición del tag infrarrojo tanto en metros como en píxeles. Además, hay una zona en la que se muestra en tiempo real qué wiimotes se tienen en consideración en el momento de calcular las coordenadas finales de la posición del tag infrarrojo. Cada esfera representa un mando, y los mandos que están visualizando el tag infrarrojo estarán coloreados de forma más intensa que los que no estén observando el tag infrarrojo. Figura 18. Panel de información de seguimiento del tag infrarrojo. 109 de 199 Manual de Instalación y Uso Una vez que desee detener el seguimiento, deberá volver a hacer clic en el botón que usó para iniciar el seguimiento, pero que ahora será de color rojo y tendrá escrito “Detener seguimiento”. Figura 19. Haciendo clic de nuevo en el botón se detiene el seguimiento. 2.2.2.6 - Sistema de Notificaciones El programa tiene integrado un sistema de notificaciones para que usted pueda saber cuando el objeto que porta el tag infrarrojo ha invadido una zona restringida. La versión actual del programa WIPS ofrece soporte para dos servicios de notificaciones: Twitter e E-mail. Para acceder a la configuración y/o activación de ambos servicios, deberá seguir los siguientes pasos: - Para ambos Servicios: 1º - Haga clic en el menú “Archivo” para, seguidamente, hacer clic en el apartado llamado “Preferencias”. Figura 20. Acceso al cuadro de Preferencias. 110 de 199 Manual de Instalación y Uso - Para Configurar Notificaciones por Twitter: 2º - Dentro del cuadro de Preferencias, haga clic en el botón “Vincular cuenta de Twitter para lanzar el proceso de autorización de cuenta Figura 21. Aspecto del cuadro de Preferencias. 3º - Su navegador predeterminado se abrirá con una página en la que Twitter le pedirá introducir su nombre de usuario y clave. Después de eso se le pasará a otra página en la que se le mostrará un código de 7 cifras. 4º- Simultáneamente, en el programa se le habrá abierto una nueva ventana llamada Vinculando cuenta de Twitter (Figura 22). Tal y como le indicará dicha ventana, deberá introducir el código de 7 cifras dentro de la caja de texto prevista para ello. Asegúrese de introducir correctamente el código puesto que sino deberá salir de esta ventana y volver a comenzar el proceso de autorización (Figura 23). Figura 22. Formulario para vincular una cuenta de Twitter. 111 de 199 Manual de Instalación y Uso Figura 23. Código de 7 cifras erróneo. Si introduce un código de menos de 7 cifras, podrá volver a introducir el código de nuevo sin tener que lanzar todo el proceso de autorización. De ello le informará el programa, como lo muestra la Figura 24. Figura 24. Código erróneo. Debe tener 7 cifras. 5º - Si ha introducido bien el código, el programa volverá al cuadro de Preferencias y nos informará del éxito del proceso, cambiando el aspa roja situada a la derecha del botón de vinculación de cuenta de Twitter por un tick verde (Figura 25). 112 de 199 Manual de Instalación y Uso 6º - Si desea que los datos de vinculación se guarden para la próxima vez que inicie la aplicación marque la casilla “Guardar los datos para futuros usos”. Figura 25. Éxito en la vinculación de una cuenta de Twitter. 7º - Una vez realizados todos estos pasos, deberá abrir su cuenta de Twitter en el navegador, y seguir al usuario @WIPS_Alerts. De este usuario es de quien recibirá las notificaciones a su cuenta de Twitter mediante mensajes directos. - Para Configurar Notificaciones por Correo Electrónico 2º - El proceso es similar a la vinculación de una cuenta de Twitter. Esta vez haga clic en el botón “Vincular e-mail”. Figura 26. Cuadro “Vinculando E-mail”. 113 de 199 Manual de Instalación y Uso 3º - Ahora se le abrirá un cuadro, llamado “Vinculando E-mail”, en el que deberá introducir la dirección de correo electrónica en la que desee recibir las notificaciones. Asegúrese que escribe una dirección de correo válida. Si no lo hiciera así, el programa le avisaría de ello, como lo muestra la Figura 27. Figura 27. Error de validación del correo electrónico introducido. 4º - Cuando haya introducido una cuenta de correo válida, el programa volverá al cuadro de Preferencias, no sin antes enviar un mensaje de confirmación a la cuenta de correo electrónica introducida. También en este caso el programa nos informará de la correcta vinculación de la cuenta de correo introducida mostrando un tick verde en lugar del aspa roja situada a la derecha del botón “Vincular E-mail”. 5º - Si desea que los datos de vinculación se guarden para la próxima vez que inicie la aplicación marque la casilla “Guardar los datos para futuros usos”. Figura 28. Éxito en la vinculación de una cuenta de correo electrónico. 114 de 199 Manual de Instalación y Uso - Para Desvincular algún Servicio de Notificación Si usted desea desvincular alguno de los dos servicios de notificación podrá hacerlo haciendo clic en el tick verde situado a la derecha del botón del servicio que desee desvincular (Figura 29). Figura 29. Desvinculando un servicio de notificación. Acto seguido le aparecerá una ventana de confirmación, asegurándose de que realmente desea desvincular el servicio escogido, tal y como puede ver en la Figura 30. Figura 31. Desvinculando la cuenta de Twitter del usuario. Figura 32. Desvinculando la cuenta de correo electrónico del usuario Después de desvincular un servicio, podrá comprobar como el tick verde de la derecha del botón de dicho servicio vuelve a cambiarse por un aspa roja. 115 de 199 Manual de Instalación y Uso 2.2.2.7 - Archivos de Configuración Existe un archivo de configuración llamado “AutenticateConfiguration.cfg”. Dentro de este archivo se guardan todos los datos que sirven para autenticar la cuenta de twitter y el e-mail vinculados. Es un archivo con datos sensibles que no están cifrados. Usted se hace responsable del uso de dicho archivo. Si lo prefiere, desactive la casilla que habilita la creación de este archivo, aunque con ello deberá vincular todas las cuentas cada vez que abra el programa. 116 de 199 Código 3 - Código 3.1 - MultipleWiimoteForm.cs Este archivo contiene la rutina de atención a las interrupciones que provocan los mandos. Durante la ejecución de las interrupciones se evalúan las coordenadas enviadas por todos los wiimotes y se realiza el cálculo de las coordenadas finales. También contiene funciones necesarias para la calibración de los mandos, además del guardado y cargado de datos de calibración. using using using using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Drawing.Imaging; System.IO; System.Linq; System.Text; System.Threading; System.Windows.Forms; WiimoteLib; namespace WIPS { public partial class MultipleWiimoteForm : Form { //Colecciones que contienen datos de los wiimotes y de su posición en la ventana de seguimiento public static Dictionary<Guid, WiimoteInfo> mWiimoteMap = new Dictionary<Guid, WiimoteInfo>(); public static Dictionary<int, UserConf> mWiimoteMapConf = new Dictionary<int, UserConf>(); public static List<int> howManySee = new List<int>(); //Array que contiene los guidID de cada wiimote public static Guid[] guidID; Guid nullGuid = new Guid("00000000-0000-0000-0000-000000000000"); //Variables de dibujado static Bitmap bNoCalPoints = new Bitmap(128, 96, PixelFormat.Format24bppRgb); static Graphics gNoCalPoints; SolidBrush redBrush = new SolidBrush(Color.Red); public static UserConf Conf = null; public static AllWiimoteInfo awi; //Estructura que contiene los datos recibidos por los wiimotes private WiimoteCollection mWiimoteCollection; //Variable que indica el número de wiimotes que se han detectado public static int numwiimotes; //Variable que indica qué wiimote quiere ser calibrado public static int WhoWantsCalibration; //Variable que indica qué número de pestaña está activa public static int actualTab; 117 de 199 Código //Variables usadas durante el dibujado de la ventana de calibración public static int TemplateWidth = 1024; public static int TemplateHeight = 768; public static float calibrationMargin = 0.1F; public static int calibrationState = 0; public static Calibration cf = null; public static bool calibra; public static bool firstPositionDataWrite = false; //se define una nueva coordenadas recibidas durante static float[] dstX = static float[] dstY = static float[] srcX = static float[] srcY = matriz de valores en las que pasaremos las la calibración new float[4]; new float[4]; new float[4]; new float[4]; //Instancias clase homografía static four_point_callibration.four_point_calibration[] calWiimoteData; //Arrays de arrays que contienen las coordenadas absolutas de las cuatro marcas de calibración public static float[][] MdstX; public static float[][] MdstY; //Arrays de arrays que contienen las coodenadas relativas al wiimote de las cautro marcas de calibración static float[][] MsrcX; static float[][] MsrcY; public static bool[] vis; public static bool[] calibrated; //Flag que indica que al menos un mando ha sido calibrado public static bool almostOneCalibrated = false; //Flag que indica que al menos un mando ve el objeto infrarrojo public static bool almostOneSees = false; static bool[] remote; //Array que almacena las coordenadas enviadas directamente por cada wiimote. static int[][] posIR; static float[][] calData; //Variables creadas para facilitar la carga y guardado de los datos de calibración en archivos .dat static double[,] wiiPoints = new double[4, 2]; static double[,] realityPoints = new double[4, 2]; //Array que contiene las coordenadas absolutas que entregan todos los wiimotes public static System.Drawing.Point[] calPointCoords; //Array que contiene las coordenadas de las marcas de calibración vistas por el wiimote public static System.Drawing.Point[][] WiimoteSeenCalPoints; 118 de 199 Código //Array que contiene las coordenadas convertidas por la función de homografía public static System.Drawing.Point[][] ConvertedCalPoints; //Variable en la que se almacenan las coordenadas finales calculadas por el programa public static System.Drawing.Point RoomBaseCoords; public static System.Drawing.Point Interim; //Variables temporales en las que se almacenan las coordenadas de los cuatro puntos de calibración static System.Drawing.Point p0; static System.Drawing.Point p1; static System.Drawing.Point p2; static System.Drawing.Point p3; public static TextWriter PositionText = new StreamWriter ("position_data_" + DateTime.Now.Day.ToString() + "_" + DateTime.Now.Month.ToString() + "_" + DateTime.Now.Year.ToString() + ".txt"); static Mutex irqs = new Mutex(); //Inicialización del formulario public MultipleWiimoteForm() { InitializeComponent(); gNoCalPoints = Graphics.FromImage(bNoCalPoints); } //Cuando cargamos el formulario public void MultipleWiimoteForm_Load(object sender, EventArgs e) { /*Pintamos de negro los graficos que se mostrarán en las picture box * de 128 x 96 que nos muestran los puntos de calibración según el wiimote * antes y después de la calibración*/ gNoCalPoints.Clear(Color.Black); // Creamos una instancia de la clase que agrupará los Wiimotes conectados mWiimoteCollection = new WiimoteCollection(); //Buscamos los wiimotes conectados y, en caso de error, se entrega una excepción try { mWiimoteCollection.FindAllWiimotes(); } catch (WiimoteNotFoundException ex) { 119 de 199 Código MessageBox.Show(ex.Message, "No se han encontrado Wiimotes para conectar", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (WiimoteException ex) { MessageBox.Show(ex.Message, "Ha habido algún error de comunicación con alguno de los Wiimotes", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error de conexión desconocido", MessageBoxButtons.OK, MessageBoxIcon.Error); } //Creamos la pestaña de configuración TabPage tabConf = new TabPage("Configuración"); tabWiimotes.TabPages.Add(tabConf); //Vinculamos una instancia del formulario UserConf UserConf wo = new UserConf(); numwiimotes = mWiimoteCollection.Count; tabConf.Controls.Add(wo); mWiimoteMapConf[0] = wo; //Creamos la pestaña de Visión general TabPage tabTotal = new TabPage("Vision general"); tabWiimotes.TabPages.Add(tabTotal); AllWiimoteInfo awi = new AllWiimoteInfo(); tabTotal.Controls.Add(awi); /*Creación de variables de la matriz de transformacion * dependientes del número de Wiimotes encontrados*/ MdstX = new float[mWiimoteCollection.Count][]; MdstY = new float[mWiimoteCollection.Count][]; MsrcX = new float[mWiimoteCollection.Count][]; MsrcY = new float[mWiimoteCollection.Count][]; los puntos misma */ /*Creación de los arrays que contendrán las coordenadas de * de calibración vistos por el wiimote antes y después de la WiimoteSeenCalPoints = new System.Drawing.Point [mWiimoteCollection.Count][]; ConvertedCalPoints = new System.Drawing.Point [mWiimoteCollection.Count][]; /*Construcción de matriz de datos * según el número de Wiimotes encontrados*/ for (int i = 0; i < MdstX.Length; i++) { MdstX[i] = new float[4]; MdstY[i] = new float[4]; MsrcX[i] = new float[4]; MsrcY[i] = new float[4]; WiimoteSeenCalPoints[i] = new System.Drawing.Point[4]; ConvertedCalPoints[i] = new System.Drawing.Point[4]; for (int j = 0; j < MdstX[i].Length; j++) { MdstX[i][j] = 0.0F; MdstY[i][j] = 0.0F; MsrcX[i][j] = 0.0F; MsrcY[i][j] = 0.0F; } 120 de 199 Código } /*Creación de instancias de la clase que se encarga de la transformación de las coordenadas de los puntos vistos por cada wiimote*/ calWiimoteData = new four_point_callibration.four_point_calibration[mWiimoteCollection.Count]; convertidas /*Creación del array que contendrá las coordenadas * del punto que esté viendo cada wiimote*/ calPointCoords = new System.Drawing.Point [mWiimoteCollection.Count]; /*Construcción de los arrays que contienen tanto las instancias de la clase encargada de las transformaciones de las coordenadas como del que contiene las coordenadas convertidas del punto que ve el wiimote*/ for (int i = 0; i < calWiimoteData.Length; i++) { calWiimoteData[i] = new four_point_callibration.four_point_calibration(); calPointCoords[i] = new System.Drawing.Point(); } /*Creación de variables usadas durante la scalibración egún el número de wiimotes encontrados*/ remote = new bool[mWiimoteCollection.Count]; vis = new bool[mWiimoteCollection.Count]; calibrated = new bool[mWiimoteCollection.Count]; posIR = new int[mWiimoteCollection.Count][]; calData = new float[mWiimoteCollection.Count][]; //howManySee = new int[mWiimoteCollection.Count]; /*Construcción de variables usadas durante la calibracion según el número de Wiimotes encontrados*/ for (int i = 0; i < mWiimoteCollection.Count; i++) calibrated[i] = false; //Coordenadas del punto visto por el wiimote for (int i = 0; i < posIR.Length; i++) { posIR[i] = new int[2]; for (int j = 0; j < posIR[i].Length; j++) posIR[i][j] = 0; } for (int i = 0; i < calData.Length; i++) { calData[i] = new float[2]; for (int j = 0; j < calData[i].Length; j++) calData[i][j] = 0.0F; } cada wiimote //Array que contendrá una copia del guid correspondiente a guidID = new Guid[mWiimoteCollection.Count]; //Para cada Wiimote encontrado y guardado en la colección... 121 de 199 Código int index = 1; foreach (Wiimote wm in mWiimoteCollection) { //Creamos nueva pestaña TabPage tp = new TabPage("Wiimote " + (index)); tabWiimotes.TabPages.Add(tp); //Insertamos un control de usuario en dicha pestaña WiimoteInfo wi = new WiimoteInfo(wm); tp.Controls.Add(wi); //Vinculamos un Wiimote a dicho control de usuario mWiimoteMap[wm.ID] = wi; /*Vinculamos las PictureBox de la parte superior derecha * de la ventana asociada al wiimote para que podamos * visualizar los puntos calibrados y no calibrados del * proceso de calibración */ mWiimoteMap[wm.ID].pbNoCalPoints.Image = bNoCalPoints; /*Guardamos el número de identificacion propio de cada Wiimote poder acceder * en una variable que utilizaremos más adelante para * a los PictureBox anteriormente citados */ guidID[index - 1] = wm.ID; //Lo conectamos wm.WiimoteChanged += wm_WiimoteChanged; wm.Connect(); //Especificamos el tipo de datos que queremos recibir del wiimote wm.SetReportType(InputReport.IRAccel, IRSensitivity.Maximum, true); //Encendemos los led's correspondientes al número de wiimote } wm.SetLEDs(index++); } //Cuando cerramos el formulario private void MultipleWiimoteForm_FormClosing(object sender, FormClosingEventArgs e) { foreach (Wiimote wm in mWiimoteCollection) wm.Disconnect(); PositionText.Close(); if (!Preferencias.saveConfig) { File.Delete("AutenticateConfig.cfg"); } Application.Exit(); } //Cuando cambia el estado de alguno de los wiimotes void wm_WiimoteChanged(object sender, WiimoteChangedEventArgs e) { //Bloqueamos la llegada de interrupciones irqs.WaitOne(); 122 de 199 Código punto general //Actualizamos los datos de la ventana del wiimote que vea un WiimoteInfo wi = mWiimoteMap[((Wiimote)sender).ID]; //Actualizamos la información de la pestaña de visualización UpdateLiteTab(); wi.UpdateState(e); //Si pulsamos el boton Calibrar de la ventana asociada a uno de los wiimotes if (wi.WantCalibration) { //Preparamos el primer paso de la calibracion calibrationState = 1; //Obtenemos el wiimote que ha pedido la calibracion WhoWantsCalibration = tabWiimotes.SelectedIndex - 2; //Llamamos a la función doCalibration() para lanzar el proceso de calibracion doCalibration(); /*Preparamos la variable WantCalibration para la próxima vez que se pulse el boton calibrar*/ wi.WantCalibration = false; } calibración // Se identifica cuál es el mando que ha lanzado la for (int i = 0; i < mWiimoteCollection.Count; i++) { if (((((Wiimote)sender).ID).ToString()) == mWiimoteCollection[i].ID.ToString()) remote[i] = true; else remote[i] = false; } for (int i = 0; i < mWiimoteCollection.Count; i++) { if (remote[i] && mWiimoteCollection [i].WiimoteState.IRState.IRSensors[0].Found) { //Rellenamos el array vis[] según si el wiimote i ve un punto o no vis[i] = mWiimoteCollection [i].WiimoteState.IRState.IRSensors[0].Found; por el wiimote i //Guardamos las componentes X e Y del punto observado posIR[i][0] = mWiimoteCollection [i].WiimoteState.IRState.IRSensors[0].RawPosition.X; posIR[i][1] = mWiimoteCollection [i].WiimoteState.IRState.IRSensors[0].RawPosition.Y; if (calWiimoteData[i] != null) { 123 de 199 Código /*Coordenadas X e Y del punto infrarrojo una vez transformadas correspondiente*/ * con la matriz de transformación calPointCoords[i].X = (int)Math.Round (calWiimoteData[i].get_x(posIR[i][0], posIR[i][1])); calPointCoords[i].Y = (int)Math.Round (calWiimoteData[i].get_y(posIR[i][0], posIR[i][1])); } } else { vis[i] = mWiimoteCollection [i].WiimoteState.IRState.IRSensors[0].Found; } } //Si hemos activado el seguimiento del objeto if (mWiimoteMapConf[0].trackingButton == true) { //Identificamos que mandos ven el objeto for (int i = 0; i < UserConf.numCircles; i++) if (vis[i] == true) { howManySee.Add(i); } int mayor = 0; int average = 0; mayor batería //De los que ven el objeto, identificamos cuál tiene for (int i = 0; i < howManySee.Count; i++) { if (mWiimoteMap[guidID[mayor]].Battery < mWiimoteMap [guidID[i]].Battery) mayor = i; } //Si hay algun mando que ve el objeto if (howManySee.Count > 0) { //Flag que identifica que hay un mando que ve almostOneSees = true; tiene mas batería mayor batería... //Las coordenadas madre serán las del mando que ve y Interim.X = (calPointCoords[howManySee[mayor]].X); Interim.Y = (calPointCoords[howManySee[mayor]].Y); average = 1; //De todos los mandos que ven, menos el que tiene for (int i = 0; i < howManySee.Count; i++) { if (i != mayor) { /*Observamos si las coordenadas de vision de los demas mandos no tiene una gran desviacion * con respecto al que tiene una batería mayor */ if ((Math.Abs(calPointCoords[howManySee [mayor]].X - calPointCoords[howManySee[i]].X)) < 10 && 124 de 199 Código (Math.Abs(calPointCoords[howManySee [mayor]].Y - calPointCoords[howManySee[i]].Y)) < 10) { //Preparamos el terreno para obtener unas coordenadas medias Interim.X += (calPointCoords[howManySee [i]].X); Interim.Y += (calPointCoords[howManySee [i]].Y); average++; } } } //Observamos cuantos mandos entran en la media //average++; //Obtenemos las coordenadas finales de la media de todas las coordenadas de los demas Wiimotes if (average != 0) { RoomBaseCoords.X = Interim.X / average; RoomBaseCoords.Y = Interim.Y / average; } } else { //Se resetean las variables RoomBaseCoords.X = 0; RoomBaseCoords.Y = 0; almostOneSees = false; } //Reseteamos más variables howManySee.RemoveRange(0, howManySee.Count); Interim.X = 0; Interim.Y = 0; } if (mWiimoteMapConf[0].trackingButton == true) { //Lanzamos el redibujado teniendo en cuenta las coordenadas del objeto mWiimoteMapConf[0].pbWiimotes.Invalidate(); mWiimoteMapConf[0].pbActiveWiimoteTracking.Invalidate(); } //Si el mando que tenemos seleccionado ve el punto infrarrojo if ( remote[WhoWantsCalibration] && mWiimoteCollection [WhoWantsCalibration].WiimoteState.IRState.IRSensors[0].Found) //Iniciamos el proceso de calibración de dicho wiimote switch (calibrationState) { case 1: /*Guardamos las coordenadas vistas por el wiimote del punto de calibracion * superior izquierdo y las guardamos en las variables adecuadas*/ MsrcX[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][0]; 125 de 199 Código MsrcY[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][1]; //Generamos un punto con dichas coordenadas p0 = new System.Drawing.Point((int)posIR [WhoWantsCalibration][0], (int)posIR [WhoWantsCalibration][1]); //Preparamos el siguiente paso de la calibración calibrationState = 2; //Llamamos la función doCalibration doCalibration(); break; case 2: /*Guardamos las coordenadas vistas por el wiimote del punto de calibracion * superior derecho y las guardamos en las variables adecuadas*/ MsrcX[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][0]; MsrcY[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][1]; //Generamos un punto con dichas coordenadas p1 = new System.Drawing.Point((int)posIR [WhoWantsCalibration][0], (int)posIR [WhoWantsCalibration][1]); //Preparamos el siguiente paso de la calibración calibrationState = 3; //Llamamos la función doCalibration doCalibration(); break; case 3: /*Guardamos las coordenadas vistas por el wiimote del punto de calibracion * inferior derecho y las guardamos en las variables adecuadas*/ MsrcX[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][0]; MsrcY[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][1]; //Generamos un punto con dichas coordenadas p2 = new System.Drawing.Point((int)posIR [WhoWantsCalibration][0], (int)posIR [WhoWantsCalibration][1]); //Preparamos el siguiente paso de la calibración calibrationState = 4; //Llamamos la función doCalibration doCalibration(); break; case 4: 126 de 199 Código punto de calibracion /*Guardamos las coordenadas vistas por el wiimote del * inferior izquierdo y las guardamos en las variables adecuadas*/ MsrcX[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][0]; MsrcY[WhoWantsCalibration][calibrationState - 1] = posIR[WhoWantsCalibration][1]; //Generamos un punto con dichas coordenadas p3 = new System.Drawing.Point((int)posIR [WhoWantsCalibration][0], (int)posIR [WhoWantsCalibration][1]); //Preparamos el siguiente paso de la calibración calibrationState = 5; //Llamamos la función doCalibration doCalibration(); break; default: break; } } //Permitimos las interrupciones irqs.ReleaseMutex(); #region Funciones referentes a la calibración /*Función doCalibration(). Función que es llamada cada vez que queremos crear un punto * de calibración nuevo. También se guardan las coordenadas de dichos puntos, que serviran * para crear la matriz de homografía con la cual transformar las coordenadas recibidas * por cada mando de wii que tengamos*/ public static void doCalibration() { if (cf == null) return; // se actualiza el punto a mostrar por pantalla dond el usuario tiene que colocar el puntero en calibración int x = 0; int y = 0; int size = 10; Pen p = new Pen(Color.Red); //segun el valor de calibrationstate, se dibuja la cruz en un lugar o otro y se guardan las coordenadas de la pantalla del PC switch (calibrationState) { case 1: /*Coordenadas para el dibujado de la cruz situada en la parte superior izquierda de la ventana de calibracion*/ 127 de 199 Código x = (int)(cf.pbCalibrate.Width * calibrationMargin); y = (int)(cf.pbCalibrate.Height * calibrationMargin); /*Enviamos las coodenadas a la función de dibujado*/ cf.DrawCalibrationMarks(x, y, size, p); superior la parte superior /*Coordenadas correspondientes a la cruz de la parte izquierda de la plantilla*/ //x = (int)(TemplateWidth * calibrationMargin); //y = (int)(TemplateHeight * calibrationMargin); /*Guardamos el valor x e y de la cruceta situada en izquierda en las variables adecuadas*/ MdstX[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P1X * UserConf.widthFactor; MdstY[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P1Y * UserConf.heightFactor; podamos apagar el calibración*/ /*Detenemos durante un segundo el programa para que led y poderlo llevar al segundo punto de Thread.Sleep(1000); break; case 2: /*Coordenadas para el dibujado de la cruz situada en la parte superior derecha de la ventana de calibracion*/ x = cf.pbCalibrate.Width - (int)(cf.pbCalibrate.Width * calibrationMargin); y = (int)(cf.pbCalibrate.Height * calibrationMargin); /*Enviamos las coodenadas a la función de dibujado*/ cf.DrawCalibrationMarks(x, y, size, p); superior calibrationMargin); la parte superior /*Coordenadas correspondientes a la cruz de la parte derecha de la plantilla*/ //x = TemplateWidth - (int)(TemplateWidth * //y = (int)(TemplateHeight * calibrationMargin); /*Guardamos el valor x e y de la cruceta situada en derecha en las variables adecuadas*/ MdstX[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P2X * UserConf.widthFactor; MdstY[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P2Y * UserConf.heightFactor; podamos apagar el calibración*/ /*Detenemos durante un segundo el programa para que led y poderlo llevar al tercer punto de Thread.Sleep(1000); break; case 3: /*Coordenadas para el dibujado de la cruz situada en la parte inferior derecha de la ventana de calibracion*/ 128 de 199 Código x = cf.pbCalibrate.Width - (int)(cf.pbCalibrate.Width * calibrationMargin); y = cf.pbCalibrate.Height - (int) (cf.pbCalibrate.Height * calibrationMargin); /*Enviamos las coodenadas a la función de dibujado*/ cf.DrawCalibrationMarks(x, y, size, p); inferior calibrationMargin); calibrationMargin); la parte inferior /*Coordenadas correspondientes a la cruz de la parte derecha de la plantilla*/ //x = TemplateWidth - (int)(TemplateWidth * //y = TemplateHeight - (int)(TemplateHeight * /*Guardamos el valor x e y de la cruceta situada en derecha en las variables adecuadas*/ MdstX[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P3X * UserConf.widthFactor; MdstY[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P3Y * UserConf.heightFactor; podamos apagar el calibración*/ /*Detenemos durante un segundo el programa para que led y poderlo llevar al tercer punto de Thread.Sleep(1000); break; case 4: /*Coordenadas para el dibujado de la cruz situada en la parte inferior izquierda de la ventana de calibracion*/ x = (int)(cf.pbCalibrate.Width * calibrationMargin); y = cf.pbCalibrate.Height - (int) (cf.pbCalibrate.Height * calibrationMargin); /*Enviamos las coodenadas a la función de dibujado*/ cf.DrawCalibrationMarks(x, y, size, p); inferior calibrationMargin); la parte inferior /*Coordenadas correspondientes a la cruz de la parte izquierda de la plantilla*/ //x = (int)(TemplateWidth * calibrationMargin); //y = TemplateHeight - (int)(TemplateHeight * /*Guardamos el valor x e y de la cruceta situada en izquierda en las variables adecuadas*/ MdstX[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P4X * UserConf.widthFactor; MdstY[WhoWantsCalibration][calibrationState - 1] = mWiimoteMap[guidID[WhoWantsCalibration]].P4Y * UserConf.heightFactor; podamos apagar el calibración*/ /*Detenemos durante un segundo el programa para que led y poderlo llevar al ultimo punto de Thread.Sleep(1000); break; 129 de 199 Código case 5: /*Finalizamos el guardado de los cuatro puntos y pasamos a pasar los valores de * las coordenadas de la plantilla y las de esos puntos de la plantilla vistos * por el wiimote, a unas variables que sean aceptadas por las funciones de la * librería de calibración */ for (int i = 0; i < 4; i++) { wiiPoints[i, 0] = MsrcX[WhoWantsCalibration][i]; wiiPoints[i, 1] = MsrcY[WhoWantsCalibration][i]; realityPoints[i, 0] = MdstX[WhoWantsCalibration] [i]; realityPoints[i, 1] = MdstY[WhoWantsCalibration] [i]; } /*Enviamos todos los datos a la función de la librería que nos creará la matriz * de homografía para transformar las coordenadas vistas por el wiimote*/ calWiimoteData[WhoWantsCalibration].do_calibration (wiiPoints, realityPoints); drawNoCalPoints(WhoWantsCalibration, p0, p1, p2, p3); drawCalPoints(WhoWantsCalibration); cf.Close(); cf = null; cuando lo precisemos //Indicamos que podemos iniciar otra calibración calibrationState = 0; //Indicamos que el mando actual está calibrado calibrated[WhoWantsCalibration] = true; almostOneCalibrated = true; //Hacemos visible la PictureBox que nos indica que la calibración ha sido correcta mWiimoteMap[guidID [WhoWantsCalibration]].ShowCalibrationOK(); } break; default: break; } //Guardado de los datos de calibración de cada wiimote, si existieran public static void saveCalibrationData(int Wiimote) { TextWriter tw = new StreamWriter("calibration_data_" + Wiimote.ToString() + ".dat"); los /*Guardamos los valores correspondientes a las coordenadas de * cuatro puntos de calibración vistos por cada wiimote*/ 130 de 199 Código for (int j = 0; j < 4; j++) { tw.WriteLine(MsrcX[Wiimote - 1][j]); tw.WriteLine(MsrcY[Wiimote - 1][j]); } for (int i = 0; i < 4; i++) { tw.WriteLine(MdstX[Wiimote - 1][i]); tw.WriteLine(MdstY[Wiimote - 1][i]); } tw.Close(); } //Carga de los datos de calibración de cada wiimote, si existieran public static int loadCalibrationData(int Wiimote) { try { TextReader tr = new StreamReader("calibration_data_" + Wiimote.ToString() + ".dat"); for (int j = 0; j < 4; j++) { MsrcX[Wiimote - 1][j] = float.Parse(tr.ReadLine()); MsrcY[Wiimote - 1][j] = float.Parse(tr.ReadLine()); } for (int i = 0; i < 4; i++) { MdstX[Wiimote - 1][i] = float.Parse(tr.ReadLine()); MdstY[Wiimote - 1][i] = float.Parse(tr.ReadLine()); } } tr.Close(); catch (Exception x) { return 0; } for (int j = 0; j < 4; j++) { wiiPoints[j, 0] = MsrcX[Wiimote - 1][j]; wiiPoints[j, 1] = MsrcY[Wiimote - 1][j]; } for (int k = 0; k < 4; k++) { realityPoints[k, 0] = MdstX[Wiimote - 1][k]; realityPoints[k, 1] = MdstY[Wiimote - 1][k]; } //Cargamos los valores de calibración en la instancia correspondiente al wiimote calWiimoteData[Wiimote - 1].do_calibration(wiiPoints, realityPoints); 131 de 199 Código //Creamos cuatro coordenadas que corresponden con los cuatro puntos que ve el wiimote p0 = new System.Drawing.Point((int)MsrcX[Wiimote - 1][0], (int)MsrcY[Wiimote - 1][0]); p1 = new System.Drawing.Point((int)MsrcX[Wiimote - 1][1], (int)MsrcY[Wiimote - 1][1]); p2 = new System.Drawing.Point((int)MsrcX[Wiimote - 1][2], (int)MsrcY[Wiimote - 1][2]); p3 = new System.Drawing.Point((int)MsrcX[Wiimote - 1][3], (int)MsrcY[Wiimote - 1][3]); //Se envían a la funcion para que los dibuje en la PictureBox correspondiente drawNoCalPoints(Wiimote - 1, p0, p1, p2, p3); //Se envian a la función para cambiar de base los puntos que ve el wiimote drawCalPoints(Wiimote - 1); //Indicamos que el mando se ha calibrado correctamente calibrated[Wiimote - 1] = true; almostOneCalibrated = true; return 1; } //Funcion que se encarga de dibujar un paralelogramo en la pequeña PictureBox existente en cada instancia del formulario WiimoteInfo public static void drawNoCalPoints(int Who, System.Drawing.Point a, System.Drawing.Point b, System.Drawing.Point c, System.Drawing.Point d) { /*Guardamos los cuatro puntos de calibración con las coordenadas según * las ve el wiimote que estamos calibrando*/ a.X a.Y b.X b.Y c.X c.Y d.X d.Y /= /= /= /= /= /= /= /= 8; 8; 8; 8; 8; 8; 8; 8; System.Drawing.Point[] NoCalPoints = { a, b, c, d }; WiimoteSeenCalPoints[Who][0] WiimoteSeenCalPoints[Who][1] WiimoteSeenCalPoints[Who][2] WiimoteSeenCalPoints[Who][3] = = = = a; b; c; d; /*Dibujamos dichos puntos en la primera de las dos PictureBox en la parte superior * derecha de la pestaña asociada al wiimote que estamos calibrando*/ gNoCalPoints.Clear(Color.Black); gNoCalPoints.DrawPolygon(new Pen(Color.Green), WiimoteSeenCalPoints[Who]); mWiimoteMap[guidID[Who]].pbNoCalPoints.Image = bNoCalPoints; 132 de 199 Código } mWiimoteMap[guidID[Who]].pbNoCalPoints.Refresh(); public static void drawCalPoints(int Who) { /*Pasamos ahora a transformar las coordenadas obtenidas por el wiimote de cada uno * de los cuatro puntos de calibración*/ [Who][0]); [Who][0]); //Primer punto - Superior izquierdo double p0x = calWiimoteData[Who].get_x(MsrcX[Who][0], MsrcY double p0y = calWiimoteData[Who].get_y(MsrcX[Who][0], MsrcY p0 = new System.Drawing.Point((int)(p0x / 8 + 0.5), (int)(p0y / 8 + 0.5)); [Who][1]); [Who][1]); //Segundo punto - Superior derecho double p1x = calWiimoteData[Who].get_x(MsrcX[Who][1], MsrcY double p1y = calWiimoteData[Who].get_y(MsrcX[Who][1], MsrcY p1 = new System.Drawing.Point((int)(p1x / 8 + 0.5), (int)(p1y / 8 + 0.5)); [Who][2]); [Who][2]); //Tercer punto - Inferior derecho double p2x = calWiimoteData[Who].get_x(MsrcX[Who][2], MsrcY double p2y = calWiimoteData[Who].get_y(MsrcX[Who][2], MsrcY p2 = new System.Drawing.Point((int)(p2x / 8 + 0.5), (int)(p2y / 8 + 0.5)); [Who][3]); [Who][3]); //Cuarto punto - Inferior izquierdo double p3x = calWiimoteData[Who].get_x(MsrcX[Who][3], MsrcY double p3y = calWiimoteData[Who].get_y(MsrcX[Who][3], MsrcY p3 = new System.Drawing.Point((int)(p3x / 8 + 0.5), (int)(p3y / 8 + 0.5)); /*Guardamos los cuatro puntos de calibración con las coordenadas según * nos las devuelven las funciones de transformación*/ System.Drawing.Point[] CalPoints = { p0, p1, p2, p3 }; } ConvertedCalPoints[Who][0] ConvertedCalPoints[Who][1] ConvertedCalPoints[Who][2] ConvertedCalPoints[Who][3] = = = = #endregion 133 de 199 p0; p1; p2; p3; Código e) //Cuando clicamos en alguna de las pestañas private void tabWiimotes_MouseDown(object sender, MouseEventArgs { los cuatro wiimote*/ //Obtenemos la pestaña activa actualTab = tabWiimotes.SelectedIndex - 2; //Si la pestaña activa corresponde a uno de los wiimotes if (actualTab > -1) { /*Actualizamos el dibujo de la ventanita que nos indica * puntos de calibración correspondientes a dicho p0 = new System.Drawing.Point((int)MsrcX[actualTab][0], (int)MsrcY[actualTab][0]); p1 = new System.Drawing.Point((int)MsrcX[actualTab][1], (int)MsrcY[actualTab][1]); p2 = new System.Drawing.Point((int)MsrcX[actualTab][2], (int)MsrcY[actualTab][2]); p3 = new System.Drawing.Point((int)MsrcX[actualTab][3], (int)MsrcY[actualTab][3]); drawNoCalPoints(actualTab, p0, p1, p2, p3); } } //Cuando pulsamos el botón Salir en el menu superior private void salirToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } //Función que actualiza la información de la pestaña de visualización general private void UpdateLiteTab() { //Pasamos por todos los wiimotes activos for (int m = 0; m < mWiimoteCollection.Count; m++) { //Si no hay ningun guid que sea nulo if (guidID[m] != nullGuid) { /*Pintamos de negro la PictureBox de visualización y si el mando actual * ve un punto infrarrojo, lo dibujamos en la misma*/ AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].gLite.Clear (Color.Black); if (vis[m] == true) AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].gLite.FillEllipse(redBrush, (int)(posIR[m][0] / 8), (int)(posIR[m][1] / 8), 6, 6); /*Actualizamos la información de la batería, de la posición del objeto infrarrojo * y del número de wiimote actual*/ 134 de 199 Código AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].pbIRPosLite.Image = AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].bLite; AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].pbBatteryLite.Value = mWiimoteMap[guidID[m]].pbBattery.Value; AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].lblBatteryLite.Text = mWiimoteMap[guidID[m]].lblBattery.Text; AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].lblXLite.Text = mWiimoteMap[guidID[m]].lblX.Text; AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].lblYLite.Text = mWiimoteMap[guidID[m]].lblY.Text; AllWiimoteInfo.mWiimoteInfoLiteMap[m + 1].lblSelectedWiimLite.Text = "Wiimote nº\n" + (m + 1).ToString(); } } } //Cuando hacemos clic en el menu de preferencias private void PreferenciasToolStripMenuItem_Click(object sender, EventArgs e) { new Preferencias().Show(); } //Cuando hacemos clic aparece la ventana "Acerca de..." private void acercaDeWIPSToolStripMenuItem_Click(object sender, EventArgs e) { new AboutBox().Show(); } } } Código 1. MultipleWiimoteForm.cs 135 de 199 Código 3.2 - UserConf.cs Este archivo contiene las funciones que se encargan del dibujado de todos los elementos que se muestran en la ventana de seguimiento. Además controla la entrada de datos del usuario. Dentro de este código también se encuentran las funciones que envían las notificaciones al usuario cuando el objeto ha entrado en la zona prohibida.la rutina de atención a las interrupciones que provocan los mandos. using using using using using using using using using using using using using using using using using System; System.Collections.Generic; System.Collections.ObjectModel; System.ComponentModel; System.Diagnostics; System.Drawing; System.Drawing.Imaging; System.Drawing.Drawing2D; System.Data; System.IO; System.Linq; System.Text; System.Windows.Forms; System.Media; WiimoteLib; Twitterizer; Newtonsoft.Json; namespace WIPS { public partial class UserConf : UserControl { Dictionary<int, WiimoteMapDraw> MappedWiimotes = new Dictionary<int, WiimoteMapDraw>(); private delegate void UpdateWiimoteStateDelegate (WiimoteChangedEventArgs args); //Variables encargadas de los gráficos de los PictureBox private Bitmap b; private Graphics g; private Bitmap bActiveWiimoteTracking; private Graphics gActiveWiimoteTracking; //Variables para área restringida private System.Drawing.Point initialMousePos; private System.Drawing.Point currentMousePos; private Rectangle areaRestricted; private bool drawing = false; //Pinceles y plumas de colores SolidBrush whiteBrush = new SolidBrush(Color.White); SolidBrush blackBrush = new SolidBrush(Color.Black); SolidBrush controlBrush = new SolidBrush(SystemColors.Control); SolidBrush redBrush = new SolidBrush(Color.Red); SolidBrush translucidWhiteBrush = new SolidBrush(Color.FromArgb (60, Color.White)); SolidBrush translucidBlackBrush = new SolidBrush(Color.FromArgb (60, Color.Black)); SolidBrush[] Brushes = new SolidBrush[7]; SolidBrush[] translucidBrushes = new SolidBrush[7]; 136 de 199 Código Pen whitePen = new Pen(Color.White, 2); Pen blackPen = new Pen(Color.Black, 3); Pen greenPen = new Pen(Color.ForestGreen, 2); Pen[] Pens = new Pen[7]; Pen[] translucidPens = new Pen[7]; Color[] Colors = new Color[]{Color.Blue, Color.Crimson, Color.LimeGreen, Color.Orange, Color.Gold, Color.Tomato, Color.Turquoise}; FillMode newFillMode = FillMode.Winding; private System.Drawing.Point[] circlesCoordinates = new System.Drawing.Point[7]; //Flags de vinculación de servicio de notificaciones public static bool twitterAutenticateOK = false; public static bool facebookAutenticateOK = false; public static bool emailAutenticateOK = false; private Wiimote mWiimote; //Array que contiene las cajas de selección que activan/ desactivan las capas de wiimotes private CheckBox[] wiimoteCheckBoxes = new CheckBox[7]; //Variables pertenecientes al dibujado del area de restricción public static bool drawingRestrictedArea = false; public static bool activatedRestrictedArea = false; //Flag que indica entrada en el modo de edición public static bool editButton = false; //Flag que indica la activación del seguimiento public bool trackingButton = false; //Flags para saber desde donde cambiamos los valores de ciertas variables public static bool changeFromText = false; public static bool changeFromPicture = false; //Flag que indica que estamos dibujando sobre la ventana de seguimiento bool drawingPictureBox = false; //Flag que indica que se está mostrando el ToolTip de la ventana de seguimiento bool showingToolTip = false; //Valores de los ángulos de vision de los wiimotes float visionHorizontal = 41F; float visionVertical = 31F; //Variable que indica el número de wiimotes que se han colocado en la ventana de seguimiento public static int numCircles = 0; 137 de 199 Código //Variables intermedias que guardan las coordenadas del último wiimote colocado en la ventana int posX, posY; //Variables pertenecientes a las acciones del ratón sobre la ventana de seguimiento bool clickOnSamePos = false; bool clickOnDown = false; public static int whatWiimoteIsMoving = 0; public static System.Drawing.Point MouseDownCoords; public static System.Drawing.Point MouseUpCoords; int selectedWiimote; int txtboxPosition; bool noArea = false; int j; //Variables que contienen los factores de semejanza de la ventana de seguimiento y la sala real public static float widthFactor = 1; public static float heightFactor = 1; float widthFactorIncrement = 0.5F; float heightFactorIncrement = 0.5F; public static int totalX; public static int totalY; public static double x0, x1, x2, y; public static float robotHeight = 0F; public static float oldRobotHeight = 0F; //Estructuras que contienen los datos de autenticación de las notificaciones vía Twitter public static OAuthTokens userTokens = new OAuthTokens(); public static OAuthTokens WIPSTokens = new OAuthTokens(); //Variables necesarias para la autenticación vía Twitter public static string copyConsumerKey; public static string copyConsumerSecret; public static string userScreenName; public static decimal userUserID; public static string WIPSScreenName; public static string WIPSUserID; public static string requestToken; public static string pin; public static int errorCounter = 0; public static OAuthTokenResponse accesToken = new OAuthTokenResponse(); public static string email; //Flag que indica si ya se envió una señal de alarma o no private static bool alertSended = false; public UserConf(Wiimote wm) : this() { mWiimote = wm; } public UserConf() { 138 de 199 Código CheckForIllegalCrossThreadCalls = false; InitializeComponent(); this.DoubleBuffered = true; /*Determinamos el incremento de los factores * de incremento de alto y ancho del futuro picturebox */ if (ZoneData.Length > 20) widthFactorIncrement = 0.3F; else if (ZoneData.Length > 40) widthFactorIncrement = 0.01F; if (ZoneData.Width > 20) heightFactorIncrement = 0.3F; else if (ZoneData.Width > 40) heightFactorIncrement = 0.01F; /*Aumentamos los factores de alto y ancho si están * dentro de los límites asignados para el futuro * picturebox */ while (((ZoneData.Length * widthFactor) < 500) && (ZoneData.Width * heightFactor) < 339) { widthFactor += widthFactorIncrement; heightFactor += heightFactorIncrement; } widthFactor -= widthFactorIncrement; heightFactor -= heightFactorIncrement; //Le damos el tamaño calculado al picturebox pbWiimotes.Size = new System.Drawing.Size(Convert.ToInt32 (ZoneData.Length * widthFactor), Convert.ToInt32 (ZoneData.Width * heightFactor)); //Creamos un nuevo bitmap para contener los dibujos del picturebox principal b = new Bitmap(pbWiimotes.Width, pbWiimotes.Height, PixelFormat.Format24bppRgb); g = Graphics.FromImage(b); //Creamos un nuevo bitmap para contener los circulos de visualización de mandos activos bActiveWiimoteTracking = new Bitmap(180, 80, PixelFormat.Format24bppRgb); gActiveWiimoteTracking = Graphics.FromImage (bActiveWiimoteTracking); trbRobotHeight.Value = 0; //Asinamos la altura de la sala como rango maximo de altura del objeto de visualización trbWiimoteHeight.SetRange(trbRobotHeight.Value, (int) (ZoneData.Height * 100)); posteriores //Creamos los arrays de brushes y pens para los dibujos for (int i = 0; i < 7; i++) { 139 de 199 Código translucidBrushes[i] = new SolidBrush(Color.FromArgb(170, Colors[i])); Brushes[i] = new SolidBrush(Colors[i]); translucidPens[i] = new Pen(Color.FromArgb(60, Colors [i]), 3); } Pens[i] = new Pen(Colors[i], 3); //Coordenadas donde dibujaremos los circulos de visualización de mandos activos for (int i = 0; i < circlesCoordinates.Length; i++) { circlesCoordinates[i] = new System.Drawing.Point(); } circlesCoordinates[0].X = 20; circlesCoordinates[0].Y = 10; circlesCoordinates[1].X = 60; circlesCoordinates[1].Y = 10; circlesCoordinates[2].X = 100; circlesCoordinates[2].Y = 10; circlesCoordinates[3].X = 140; circlesCoordinates[3].Y = 10; circlesCoordinates[4].X = 40; circlesCoordinates[4].Y = 50; circlesCoordinates[5].X = 80; circlesCoordinates[5].Y = 50; circlesCoordinates[6].X = 120; circlesCoordinates[6].Y = 50; LoadConfiguration(); } inicio //Cuando carga el form principal public void UserConf_Load(object sender, EventArgs e) { //Reseteamos las imagenes de los graficos creados al color de g.FillRectangle(blackBrush, 0, 0, pbWiimotes.Width, pbWiimotes.Height); gActiveWiimoteTracking.FillRectangle(controlBrush, 0, 0, bActiveWiimoteTracking.Width, bActiveWiimoteTracking.Height); //Asignamos el bitmap al picturebox principal pbWiimotes.Image = b; (); m"; m"; //Rellenamos las labels de datos principales lblNumWiimote.Text = MultipleWiimoteForm.numwiimotes.ToString lblHeight.Text = "Altura: " + ZoneData.Height.ToString() + " lblLength.Text = "Largo: " + ZoneData.Length.ToString() + " lblWidth.Text = "Ancho: " + ZoneData.Width.ToString() + " m"; pbWiimotes.Invalidate(); 140 de 199 Código seguimiento Twitter. } //Hacemos invisible la GroupBox de la información de gbTrackingInfo.Visible wiimoteCheckBoxes[0] = wiimoteCheckBoxes[1] = wiimoteCheckBoxes[2] = wiimoteCheckBoxes[3] = wiimoteCheckBoxes[4] = wiimoteCheckBoxes[5] = wiimoteCheckBoxes[6] = = false; cbW1; cbW2; cbW3; cbW4; cbW5; cbW6; cbW7; //Cargamos los datos que permiten usar el servicio de Son datos secretos WIPSTokens.AccessToken = "código secreto"; WIPSTokens.AccessTokenSecret = "código secreto"; WIPSTokens.ConsumerKey = "código secreto"; WIPSTokens.ConsumerSecret = "código secreto"; WIPSScreenName = "WIPS_Alerts"; WIPSUserID = "627482444"; //Si clicamos en algún lugar de la ventana contenida dentro de la pestaña UserConf private void UserConf_MouseClick(object sender, MouseEventArgs e) { gbInfoWiimote.Visible = false; selectedWiimote = 0; } private void UserConf_MouseMove(object sender, MouseEventArgs e) { //Si el tooltip ha estado visible antes if (showingToolTip) { //Lo escondemos ttpbWiimotes.Hide(pbWiimotes); showingToolTip = false; } } public Wiimote Wiimote { set { mWiimote = value; } } e) ratón private void pbWiimotes_MouseDown(object sender, MouseEventArgs { clickOnDown = true; MouseDownCoords = new System.Drawing.Point(e.X, e.Y); //Observamos que haya wiimotes conectados if (numCircles <= MultipleWiimoteForm.numwiimotes) { //Observamos si se ha pulsado el botón izquierdo del if (e.Button == MouseButtons.Left) { if (editWiimoteState.Checked) { if (numCircles <= MultipleWiimoteForm.numwiimotes) 141 de 199 Código { if (numCircles > 0) { for (int i = 0; i < numCircles; i++) { if ((((MappedWiimotes[i + 1].x + 10) >= e.X) && (e.X >= (MappedWiimotes[i + 1].x - 10))) && (((MappedWiimotes[i + 1].y + 10) >= e.Y) && (e.Y >= (MappedWiimotes[i + 1].y - 10)))) { //Flag que indica que en la zona pulsada existe un wiimote ya colocado clickOnSamePos = true; whatWiimoteIsMoving = i + 1; } } } } } //Entramos en modo de edición de area restringida else { if (editButton) { drawing = true; this.initialMousePos = e.Location; } } } } } private void pbWiimotes_MouseUp(object sender, MouseEventArgs e) { clickOnDown = false; MouseUpCoords = new System.Drawing.Point(e.X, e.Y); mismas //Si las coordenadas al clicar y al levantar el clic son las if (MouseDownCoords == MouseUpCoords) { if (e.Button == MouseButtons.Left) { if (editButton) { if (editWiimoteState.Checked) { if (numCircles < (MultipleWiimoteForm.numwiimotes)) { //Si hemos pulsado en una zona vacía, dibujamos el circulo que corresponde a un Wiimote y refrescamos el pictureBox if (!clickOnSamePos) { //Guardamos las coordenadas de la pulsación del ratón posX = e.X; posY = e.Y; 142 de 199 Código //Añadimos dicho Wiimote y sus coordendas como nuevo elemento de la colección MappedWiimotes MappedWiimotes.Add(++numCircles, new WiimoteMapDraw(e.X, e.Y, numCircles)); true; false; for (int i = 0; i < numCircles; i++) { wiimoteCheckBoxes[i].Visible = } for (int i=numCircles; i<7; i++) { wiimoteCheckBoxes[i].Visible = } //Todos los circulos deben estar contenidos completamente dentro del pictureBox //Nos aseguramos de ello con estas cuatro comprobaciones la izquierda 10; la derecha pbWiimotes.Size.Width) pbWiimotes.Size.Width - 10; la izquierda 10; la derecha pbWiimotes.Size.Height) pbWiimotes.Size.Height - 10; //Que la coordenada X no se salga por if ((e.X - 10) < 0) { MappedWiimotes[numCircles].x = } //Que la coordenada X no se salga por if ((e.X + 10) > { MappedWiimotes[numCircles].x = } //Que la coordenada Y no se salga por if ((e.Y - 10) < 0) { MappedWiimotes[numCircles].y = } //Que la coordenada Y no se salga por if ((e.Y + 10) > { MappedWiimotes[numCircles].y = } pbWiimotes.Invalidate(); (); lblCircles.Text = numCircles.ToString } clickOnSamePos = false; 143 de 199 Código } } //Si está activado el modo de edición de area de restricción else { } else { su información } //Si pulsamos el boton izquierdo y queremos ver for (int i = 0; i < numCircles; i++) { //Vemos si el punto en el que hemos clicado había un Wiimote dibujado if ((((MappedWiimotes[i + 1].x + 10) >= e.X) && (e.X >= (MappedWiimotes[i + 1].x - 10))) && (((MappedWiimotes[i + 1].y + 10) >= e.Y) && (e.Y >= (MappedWiimotes[i + 1].y - 10)))) { //Si es así, mostramos toda la información de dicho wiimote gbInfoWiimote.Visible = true; tbAlfa.Text = MappedWiimotes[i + 1].alfa.ToString(); tbBeta.Text = MappedWiimotes[i + 1].beta.ToString(); selectedWiimote = MappedWiimotes[i + 1].WiimoteID; lblWiimotePosX.Text = "Posición X: " + (MappedWiimotes[selectedWiimote].x / widthFactor).ToString("0.00") + " m"; lblWiimotePosY.Text = "Posición Y: " + (MappedWiimotes[selectedWiimote].y / heightFactor).ToString("0.00") + " m"; //Subimos flag para indicar que queremos visualizar el valor del ángulo, y que lo hacemos desde el picturebox changeFromPicture = true; asAlfa.Angle = (int)(MappedWiimotes[i + 1].alfa); asBeta.Angle = (int)(MappedWiimotes[i + 1].beta); tbHeight.Text = MappedWiimotes [selectedWiimote].height.ToString(); cargamos en la trackBar //Si la altura del Wiimote es correcta la if (((MappedWiimotes [selectedWiimote].height * 100) != 0) && ((MappedWiimotes [selectedWiimote].height * 100) > trbWiimoteHeight.Minimum)) trbWiimoteHeight.Value = (int) (MappedWiimotes[selectedWiimote].height * 100); trbWiimoteHeight.Minimum; //Sino cargamos el valor mínimo else trbWiimoteHeight.Value = //Bajamos flag 144 de 199 Código changeFromPicture = false; //Cargamos valor labels varias lblSelectedWiim.Text = "Wiimote nº\n" + MappedWiimotes[i + 1].WiimoteID.ToString(); lblAreaVisionWiimote.Text = "Área visión: " + MappedWiimotes[selectedWiimote].polygonArea.ToString("0.00") + " m" + ((char)0x00B2).ToString(); break; } //Sino ocultamos el GroupBox de información del Wiimote else { } raton } } } gbInfoWiimote.Visible = false; selectedWiimote = 0; //Si ya hemos colocado algún circulo en el pictureBox if (numCircles > 0) { //Observamos si se ha pulsado el boton derecho del if (e.Button == MouseButtons.Right) { if (editWiimoteState.Checked) { //Comprobamos que la pulsacion del boton derecho esté contenida en el cuadrado que circunscribe al circulo if ((((MappedWiimotes[numCircles].x + 10) >= e.X) && (e.X >= (MappedWiimotes[numCircles].x - 10))) && (((MappedWiimotes[numCircles].y + 10) >= e.Y) && (e.Y >= (MappedWiimotes[numCircles].y - 10)))) { //Si es así, dibujamos encima un circulo negro, limpiando la pantalla g.FillEllipse(blackBrush, MappedWiimotes [numCircles].x - 10, MappedWiimotes[numCircles].y - 10, 20, 20); colección //Borramos el último Wiimote de la g.FillRectangle(blackBrush, 0, 0, pbWiimotes.Width, pbWiimotes.Height); MappedWiimotes.Remove(numCircles--); for (int i = 0; i < numCircles; i++) { wiimoteCheckBoxes[i].Visible = true; } for (int i = numCircles; i < 7; i++) { wiimoteCheckBoxes[i].Visible = false; } selectedWiimote = 0; 145 de 199 Código tbHeight.Text = ((float) trbWiimoteHeight.Minimum / 100F).ToString(); trbWiimoteHeight.Value = trbWiimoteHeight.Minimum; asAlfa.Angle = 0; asBeta.Angle = 0; pbWiimotes.Invalidate(); } lblCircles.Text = numCircles.ToString(); } } else { //Si se ha pulsado el boton derecho } las mismas } } //Si las coordenadas al clicar y al levantar el clic no son else { e.Location; if (e.Button == MouseButtons.Left) { if (editButton) { if (editWiimoteState.Checked) { } else { drawing = false; System.Drawing.Point finalMousePos = // Creamos el area de restricción Rectangle drawnRect = Rectangle.FromLTRB( this.initialMousePos.X, this.initialMousePos.Y, finalMousePos.X, finalMousePos.Y); } } } clickOnSamePos = false; } e) } whatWiimoteIsMoving = 0; //Cuando movemos el raton por encima de la PictureBox principal private void pbWiimotes_MouseMove(object sender, MouseEventArgs { if (editButton) { if (editWiimoteState.Checked) { 146 de 199 Código if ((numCircles <= MultipleWiimoteForm.numwiimotes) && (numCircles > 0) && (clickOnDown) && (clickOnSamePos)) { if (e.Button == MouseButtons.Left) //Solo cuando se ha clicado el ratón { MappedWiimotes[whatWiimoteIsMoving].x = e.X; MappedWiimotes[whatWiimoteIsMoving].y = e.Y; izquierda 10; //Que la coordenada X no se salga por la if ((e.X - 20) < 0) { MappedWiimotes[whatWiimoteIsMoving].x = } derecha pbWiimotes.Size.Width - 10; //Que la coordenada X no se salga por la if ((e.X + 10) > pbWiimotes.Size.Width) { MappedWiimotes[whatWiimoteIsMoving].x = } izquierda 10; pbWiimotes.Size.Height - 10; posX = pbWiimotes.Size.Width - 10; //Que la coordenada Y no se salga por la if ((e.Y - 20) < 0) { MappedWiimotes[whatWiimoteIsMoving].y = } derecha posX = 10; posY = 10; //Que la coordenada Y no se salga por la if ((e.Y + 10) > pbWiimotes.Size.Height) { MappedWiimotes[whatWiimoteIsMoving].y = } posY = pbWiimotes.Size.Height - 10; g.FillRectangle(blackBrush, 0, 0, pbWiimotes.Width, pbWiimotes.Height); GenerateWiimoteAreaPoints(); //Rotamos las áreas de visión de los wiimotes si el ángulo alfa es distinto de 90 j = 1; for (int i = 1; i <= numCircles; i++) RotateWiimoteAreaPoints(MappedWiimotes [i].Points90, (90 - MappedWiimotes[i].alfa)); //Asignamos la imagen que hemos ido modificando al picturebox principal pbWiimotes.Invalidate(); } } } 147 de 199 Código //Si tenemos seleccionado el editor de area de restricción else { if (drawing) { //Guardamos la posición actual del ratón currentMousePos = e.Location; } } //Forzamos el repintado de la PictureBox pbWiimotes.Invalidate(); } //Mostramos el tooltip de coordenadas de posición showingToolTip = true; lblX.Text = "X: " + e.X.ToString() + "p"; lblY.Text = "Y: " + e.Y.ToString() + "p"; lblXMeters.Text = "X: " + (e.X / widthFactor).ToString ("0.00") + "m"; lblYMeters.Text = "Y: " + ((e.Y / heightFactor)).ToString ("0.00") + "m"; metros //Al lado del mouse saldrá un cuadro con las coordenadas en ttpbWiimotes.Show("(" + (e.X / widthFactor).ToString("0.00") + "m" + ", " + ((e.Y / heightFactor)).ToString("0.00") + "m" + ")", pbWiimotes, e.X + 10, e.Y + 10); } //Si pulsamos el botón de edición de wiimotes private void btnEditWiimotes_Click(object sender, EventArgs e) { //.. y no estabamos ya en edición if (!editButton) { btnEditWiimotes.Text = "Detener edición"; lblEdition.Text = "Añadir posición wiimote: Clic izquierdo" + "\n" + "Eliminar posición wiimote: Clic derecho" + "\n" + "Mover posición wiimote: Clic derecho, arrastrar y soltar"; //Activamos flag que indica que estamos en edición editButton = true; gbInfoWiimote.Visible = false; gbDatosSala.Visible = true; gbEditOptions.Visible = true; gbCapas.Visible = false; } //Desactivamos la posibilidad de iniciar el seguimiento btnActivateTracking.Enabled = false; //... y ya estabamos en edición else { btnEditWiimotes.Text = "Editar distribución"; 148 de 199 Código } //Desactivamos flag de edición editButton = false; //Permitimos la posibilidad de iniciar el seguimiento btnActivateTracking.Enabled = true; editWiimoteState.Checked = true; gbEditOptions.Visible = false; gbCapas.Visible = true; } e) y //Si clicamos el botón que activa la localización private void btnActivateTracking_Click(object sender, EventArgs { //Si queremos activar el seguimiento if (!trackingButton) { /* Cambiamos el texto y el color del botón de activación * deshabilitamos el uso de los objetos que posibilitan * el cambio de los parámetros de los mandos y por tanto * evitamos el redibujado de la PictureBox por medio de * estos*/ btnActivateTracking.Text = "Detener seguimiento"; btnActivateTracking.BackColor = Color.Tomato; gbInfoWiimote.Enabled = false; btnEditWiimotes.Enabled = false; trbRobotHeight.Enabled = false; gbInfoWiimote.Visible = false; gbTrackingInfo.Visible = true; pbActiveWiimoteTracking.Invalidate(); } y //Indicamos que estamos con el seguimiento activo trackingButton = true; //Si queremos desactivar el seguimiento else { /* Cambiamos el texto y el color del botón de activación * habilitamos el uso de los objetos que posibilitan * el cambio de los parámetros de los mandos y por tanto * permitimos el redibujado de la PictureBox por medio de * estos*/ btnActivateTracking.Text = "Iniciar seguimiento"; btnActivateTracking.BackColor = Color.LightGreen; trbRobotHeight.Enabled = true; gbInfoWiimote.Enabled = true; gbInfoWiimote.Visible = false; trbRobotHeight.Enabled = true; btnEditWiimotes.Enabled = true; gbTrackingInfo.Visible = false; /*Observamos si la función OnControlChange() está en uso; * si es así, esperamos 30 ms y volvemos a mirar*/ while (drawingPictureBox) { System.Threading.Thread.Sleep(30); 149 de 199 Código } Application.DoEvents(); //Indicamos que estamos con la localización desactivada trackingButton = false; /*Redibujamos el contenido de la PictureBox para eliminar los restos dejados tras la activación de la localización*/ pbWiimotes.Invalidate(); } } #region Funciones varias //Transformación de grados a radianes private double GaR(double grados) { return (grados * (Math.PI / 180)); } //Cálculo del area de visionado de cada wiimote private void CalculateTotalArea() { for (int z = 1; z <= numCircles; z++) { double area = 0F; int i, j = 3; for (i = 0; i < 4; i++) { area += ((MappedWiimotes[z].Points90[j].X + MappedWiimotes[z].Points90[i].X)) * ((MappedWiimotes[z].Points90[j].Y MappedWiimotes[z].Points90[i].Y)); j = i; } area /= 10000; MappedWiimotes[z].polygonArea = (float)Math.Abs(area); } if (selectedWiimote > 0) lblAreaVisionWiimote.Text = "Área visión: " + MappedWiimotes[selectedWiimote].polygonArea.ToString("0.00") + " m" + ((char)0x00B2).ToString(); } #endregion #region Funciones de dibujado //Gestión de cambios en los parámetros de dibujado del área de visión de los wiimotes public void OnControlChange() { Stopwatch sw = Stopwatch.StartNew(); //Indicamos que comenzamos a dibujar en la picturebox principal drawingPictureBox = true; //Rellenamos de negro el picturebox principal 150 de 199 Código g.FillRectangle(blackBrush, 0, 0, pbWiimotes.Width, pbWiimotes.Height); //Actualizamos los valores y las posiciones de las barras de desplazamiento UpdateScrollBars(); //Generamos los 4 puntos que delimitan el area de vision de cada wiimote activo GenerateWiimoteAreaPoints(); //Rotamos las áreas de visión de los wiimotes si el ángulo alfa es distinto de 90 j = 1; for (int i = 1; i <= numCircles; i++) RotateWiimoteAreaPoints(MappedWiimotes[i].Points90, (90 MappedWiimotes[i].alfa)); if (activatedRestrictedArea) { if (((MultipleWiimoteForm.RoomBaseCoords.X > areaRestricted.Left) && (MultipleWiimoteForm.RoomBaseCoords.X < areaRestricted.Right)) && ((MultipleWiimoteForm.RoomBaseCoords.Y > areaRestricted.Top) && (MultipleWiimoteForm.RoomBaseCoords.Y < areaRestricted.Bottom))) { if (!alertSended) EnteredRestrictedArea(); } } principal } pbWiimotes.Invalidate(); pbActiveWiimoteTracking.Invalidate(); //Calculamos el area de visionado de cada wiimote CalculateTotalArea(); //Indicamos que hemos terminado de dibujar en la picturebox drawingPictureBox = false; sw.Stop(); label1.Text = sw.ElapsedMilliseconds.ToString(); //Generación de los cuatro puntos que forman el área de visión de cada wiimote private void GenerateWiimoteAreaPoints() { //if (SelectedWiimote != 0) //{ for (int i = 1; i <= numCircles; i++) { //Observar el anexo de la memoria para saber gráficamente a qué corresponden las medidas calculadas abajo MappedWiimotes[i].x0 = Math.Tan(GaR(MappedWiimotes [i].beta - visionVertical / 2)) * (MappedWiimotes[i].height robotHeight); MappedWiimotes[i].x1 = Math.Tan(GaR(MappedWiimotes [i].beta)) * (MappedWiimotes[i].height - robotHeight) - MappedWiimotes [i].x0; 151 de 199 Código MappedWiimotes[i].x2 = Math.Tan(GaR(MappedWiimotes [i].beta + visionVertical / 2)) * (MappedWiimotes[i].height robotHeight) - MappedWiimotes [i].x0 - MappedWiimotes [i].x1; MappedWiimotes[i].y0 = Math.Tan(GaR(visionHorizontal / 2)) * ((MappedWiimotes[i].height - robotHeight) / Math.Cos(GaR (MappedWiimotes[i].beta + visionVertical / 2))) * 2; MappedWiimotes[i].z0 = Math.Tan(GaR(visionHorizontal / 2)) * ((MappedWiimotes[i].height - robotHeight) / Math.Cos(GaR (MappedWiimotes[i].beta - visionVertical / 2))) * 2; double maxX = ((MappedWiimotes[i].height - robotHeight) / Math.Cos(GaR(MappedWiimotes[i].beta + visionVertical / 2))); /*Si la perpendicular de un mando esta alejada más de 5,70 metros del objeto a seguir * recortamos el area de visualización */ if ((maxX > 5.70F) || (maxX < 0)) { //Nueva magnitud x2 double newX2 = Math.Sqrt(Math.Pow(5.70F, 2) Math.Pow(MappedWiimotes[i].height - robotHeight, 2)); newX2 -= MappedWiimotes[i].x0 + MappedWiimotes[i].x1; //Debemos redimensionar la magnitud y0 MappedWiimotes[i].y0 = Math.Tan(GaR (visionHorizontal / 2)) * 5.70F * 2; //Observamos el valor de la nueva magnitud x2 if (newX2 > 0) { MappedWiimotes[i].x2 = newX2; } else { MappedWiimotes[i].x2 = 0; //Nueva magnitud x1 double newX1 = Math.Sqrt(Math.Pow(5.70F, 2) Math.Pow(MappedWiimotes[i].height - robotHeight, 2)); newX1 -= MappedWiimotes[i].x0; dicho wiimote //Observamos el valor de la nueva magnitud x1 if (newX1 > 0) { MappedWiimotes[i].x1 = newX1; } else { //Si es negativo, no habrá area de vision de System.Drawing.Point(0, 0); System.Drawing.Point(0, 0); System.Drawing.Point(0, 0); System.Drawing.Point maxp0 = new System.Drawing.Point maxp1 = new System.Drawing.Point maxp2 = new 152 de 199 Código System.Drawing.Point(0, 0); maxp2, maxp3); } } System.Drawing.Point maxp3 = new MappedWiimotes[i].SavePoints90(maxp0, maxp1, //Flag de no area de visión noArea = true; } /*Si tenemos area de vision calcularemos los cuatro pares de coordenadas de los que constan * cada area de visión, según los factores de alto y ancho calculados al inicio de la carga * del programa */ if (!noArea) { System.Drawing.Point p0 = new System.Drawing.Point ((int)(MappedWiimotes[i].x + ((float)(MappedWiimotes[i].z0 / 2) * widthFactor)), (int)(MappedWiimotes[i].y - (float)MappedWiimotes[i].x0 * heightFactor)); System.Drawing.Point p1 = new System.Drawing.Point ((int)(MappedWiimotes[i].x - ((float)(MappedWiimotes[i].z0 / 2) * widthFactor)), (int)(MappedWiimotes[i].y - (float)MappedWiimotes[i].x0 * heightFactor)); System.Drawing.Point p2 = new System.Drawing.Point ((int)(MappedWiimotes[i].x - ((float)(MappedWiimotes[i].y0 / 2) * widthFactor)), (int)(MappedWiimotes[i].y - (float)(MappedWiimotes[i].x0 + MappedWiimotes[i].x1 + MappedWiimotes[i].x2) * heightFactor)); System.Drawing.Point p3 = new System.Drawing.Point ((int)(MappedWiimotes[i].x + ((float)(MappedWiimotes[i].y0 / 2) * widthFactor)), (int)(MappedWiimotes[i].y - (float)(MappedWiimotes[i].x0 + MappedWiimotes[i].x1 + MappedWiimotes[i].x2) * heightFactor)); MappedWiimotes[i].SavePoints90(p0, p1, p2, p3); } } alfa } //Reseteamos flag de no area noArea = false; //Rotación del área de visión de cada wiimote según sea el ángulo 153 de 199 Código private void RotateWiimoteAreaPoints(System.Drawing.Point[] p, float alfa) { System.Drawing.Point[] RPoint = new System.Drawing.Point[4]; for (int i = 0; i < p.Length; i++) { RPoint[i].X = MappedWiimotes[j].x + (int)(((p[i].X - MappedWiimotes[j].x) * Math.Cos(GaR(alfa))) - ((p[i].Y - MappedWiimotes[j].y) * Math.Sin(GaR(alfa)))); RPoint[i].Y = MappedWiimotes[j].y + (int)(((p[i].X - MappedWiimotes[j].x) * Math.Sin(GaR(alfa))) + ((p[i].Y - MappedWiimotes[j].y) * Math.Cos(GaR(alfa)))); } MappedWiimotes[j].Points = RPoint; j++; } wiimotes //Dibujado de la capa que contiene las áreas de visión de los private void DrawWiimoteAreaLayer() { if (cbAreas.Checked) for (int i = 0; i < numCircles; i++) { if ((MappedWiimotes[i + 1].Points90 != null) && (MappedWiimotes[i + 1].alfa == 90)) { g.FillPolygon(translucidBrushes[i], MappedWiimotes[i + 1].Points90, newFillMode); g.DrawPolygon(whitePen, MappedWiimotes[i + 1].Points90); } else if ((MappedWiimotes[i + 1].Points != null) && (MappedWiimotes[i + 1].alfa != 90)) { g.FillPolygon(translucidBrushes[i], MappedWiimotes[i + 1].Points, newFillMode); g.DrawPolygon(whitePen, MappedWiimotes[i + 1].Points); } } } //Dibujado de la capa que contiene la posición de los wiimotes private void DrawWiimotePosLayer() { if (cbWiimotes.Checked) for (int i = 0; i < numCircles; i++) { g.FillEllipse(whiteBrush, MappedWiimotes[i + 1].x - 10, MappedWiimotes[i + 1].y - 10, 20, 20); g.DrawEllipse(Pens[i], MappedWiimotes[i + 1].x - 10, MappedWiimotes[i + 1].y - 10, 20, 20); } } 154 de 199 Código //Dibujado de la capa que contiene los wiimotes que están viendo el objeto infrarrojo private void DrawWiimotePosLayer(Pen pen, int x, int y) { gActiveWiimoteTracking.FillEllipse(whiteBrush, x, y, 20, 20); gActiveWiimoteTracking.DrawEllipse(pen, x, y, 20, 20); } //Dibujado de la capa que contiene la numeración de los wiimotes private void DrawWiimoteNumLayer() { if (cbWiimotes.Checked) for (int i = 0; i < numCircles; i++) { g.DrawString(MappedWiimotes[i + 1].WiimoteID.ToString(), SystemFonts.DefaultFont, blackBrush, new System.Drawing.Point(MappedWiimotes[i + 1].x - 5, MappedWiimotes[i + 1].y - 5)); } } //Dibujado de la capa que contiene la numeración de los wiimotes que están viendo el objeto infrarrojo private void DrawWiimoteNumLayer(SolidBrush brush, int i, int x, int y) { gActiveWiimoteTracking.DrawString(i.ToString(), SystemFonts.DefaultFont, brush, new System.Drawing.Point(x + 5, y + 5)); } //Dibujado de la capa que contiene el origen de coordenadas del picturebox principal private void DrawOriginCoordinatesLayer() { g.DrawString("(0,0)", SystemFonts.DefaultFont, whiteBrush, new System.Drawing.Point(2, 2)); } //Funcion de visualización en la picturebox principal del objeto en seguimiento public void DrawViewedPoint(System.Drawing.Point p, System.Drawing.Point lastp) { if (cbObjeto.Checked) { g.FillEllipse(redBrush, p.X - 5, p.Y - 5, 10, 10); lblPxTrackingPosX.Text = "X:" + p.X.ToString() + " px"; lblPxTrackingPosY.Text = "Y:" + p.Y.ToString() + " px"; lblMtTrackingPosX.Text = "X:" + (p.X / widthFactor).ToString() + " m"; lblMtTrackingPosY.Text = "Y:" + (p.Y / heightFactor).ToString() + " m"; } 155 de 199 Código } instante //Dibujamos los mandos que están viendo el objeto en un mismo private void DrawUpdatedActiveTrackingWiimotes() { for (int j = 0; j < 7; j++) { /*Si existe este mando, si el wiimote ve el objeto y si ese Wiimote esta posicionado en el picturebox * dibujamos el circulo sin translucidez, indicando que este mando está viendo el objeto */ if ((j < MultipleWiimoteForm.vis.Length) && (MultipleWiimoteForm.vis[j]) && (j < numCircles)) { DrawWiimotePosLayer(Pens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y); DrawWiimoteNumLayer(blackBrush, j + 1, circlesCoordinates[j].X, circlesCoordinates[j].Y); } //Sino dibujamos los circulos de visualización con translucidez, indicando que dicho mando no ve el objeto else { DrawWiimotePosLayer(translucidPens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y); DrawWiimoteNumLayer(translucidBlackBrush, j + 1, circlesCoordinates[j].X, circlesCoordinates[j].Y); } } //Asignamos el bitmap de Wiimotes visualizando a la imagen del picturebox de visualización //pbActiveWiimoteTracking.Image = bActiveWiimoteTracking; } #endregion #region Funciones de tratamiento de datos de entrada //------CAJAS DE TEXTO - ANGULO ALFA WIIMOTE--------// //Cuando cambia el valor del ángulo alfa private void asAlfa_AngleChanged() { tbAlfa.Text = asAlfa.Angle.ToString(); if (selectedWiimote != 0) { MappedWiimotes[selectedWiimote].oldalfa = MappedWiimotes [selectedWiimote].alfa; MappedWiimotes[selectedWiimote].alfa = asAlfa.Angle; if ((!changeFromText) && (!changeFromPicture)) OnControlChange(); } } //Cuando pulsamos una tecla estando la caja de texto Alfa activa private void tbAlfa_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 8) { e.Handled = false; return; } 156 de 199 Código if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyChar.ToString(), "\\d+")) e.Handled = true; } //Cuando soltamos la pulsación de una letra estando la caja de texto Alfa activa private void tbAlfa_KeyUp(object sender, KeyEventArgs e) { switch (e.KeyValue) { case 13: if ((tbAlfa.Text != string.Empty) && ((! tbAlfa.Text.Contains("-")) || (tbAlfa.Text.Length > 1))) { //Nos aseguramos que el valor de alfa este entre los valores maximo y minimo if (int.Parse(tbAlfa.Text) > 180) tbAlfa.Text = "180"; if (int.Parse(tbAlfa.Text) < -180) tbAlfa.Text = "-180"; if (tbAlfa.Text.Length > 0) { //Flag que indica que cambiamos el angulo desde la caja de texto correspondiente changeFromText = true; //Actualizamos los valores del angulo alfa asAlfa.Angle = int.Parse(tbAlfa.Text); MappedWiimotes[selectedWiimote].oldalfa = MappedWiimotes[selectedWiimote].alfa; MappedWiimotes[selectedWiimote].alfa = asAlfa.Angle; } tbAlfa.SelectionStart = tbAlfa.Text.Length; OnControlChange(); changeFromText = false; } break; case 109: if (!tbAlfa.Text.Contains("-")) { tbAlfa.Text = tbAlfa.Text.Insert(0, "-"); if (tbAlfa.Text.Length == 1) tbAlfa.SelectionStart = 1; } else tbAlfa.Text = tbAlfa.Text.Remove(0, 1); tbAlfa.SelectionStart = tbAlfa.Text.Length; break; case 189: if (!tbAlfa.Text.Contains("-")) { tbAlfa.Text = tbAlfa.Text.Insert(0, "-"); if (tbAlfa.Text.Length == 1) tbAlfa.SelectionStart = 1; } 157 de 199 Código else tbAlfa.Text = tbAlfa.Text.Remove(0, 1); tbAlfa.SelectionStart = tbAlfa.Text.Length; break; } default: break; } //Cuando se carga el control tipo knob Alfa private void asAlfa_Load(object sender, EventArgs e) { //Debe estar tan solo para que el programa vea que exista } //------CAJAS DE TEXTO - ANGULO BETA WIIMOTE--------// //Cuando cambia el valor del ángulo beta private void asBeta_AngleChanged() { tbBeta.Text = asBeta.Angle.ToString(); if (selectedWiimote != 0) { MappedWiimotes[selectedWiimote].oldbeta = MappedWiimotes [selectedWiimote].beta; MappedWiimotes[selectedWiimote].beta = asBeta.Angle; if ((!changeFromText) && (!changeFromPicture)) OnControlChange(); changeFromText = false; } } //Cuando pulsamos una tecla estando la caja de texto Beta activa private void tbBeta_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 8) { e.Handled = false; return; } if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyChar.ToString(), "\\d+")) e.Handled = true; } //Cuando soltamos la pulsación de una tecla estando la caja de texto Beta activa private void tbBeta_KeyUp(object sender, KeyEventArgs e) { switch (e.KeyValue) { case 13: if ((tbBeta.Text != string.Empty) && ((! tbBeta.Text.Contains("-")) || (tbBeta.Text.Length > 1))) { //Nos aseguramos que los valores estén entre los máximos y los minimos deseados if (int.Parse(tbBeta.Text) > 90) 158 de 199 Código asBeta.Angle = 90; if (int.Parse(tbBeta.Text) < 0) asBeta.Angle = 0; if (tbBeta.Text.Length > 0) { //Flag de cambio de grados via caja de texto changeFromText = true; //Actualizamos los valores del grado beta asBeta.Angle = int.Parse(tbBeta.Text); MappedWiimotes[selectedWiimote].oldbeta = MappedWiimotes[selectedWiimote].beta; MappedWiimotes[selectedWiimote].beta = asBeta.Angle; } tbAlfa.SelectionStart = tbAlfa.Text.Length; OnControlChange(); } break; case 109: if (!tbBeta.Text.Contains("-")) { tbBeta.Text = tbBeta.Text.Insert(0, "-"); if (tbBeta.Text.Length == 1) tbBeta.SelectionStart = 1; } else tbBeta.Text = tbBeta.Text.Remove(0, 1); tbBeta.SelectionStart = tbBeta.Text.Length; break; case 189: if (!tbBeta.Text.Contains("-")) { tbBeta.Text = tbBeta.Text.Insert(0, "-"); if (tbBeta.Text.Length == 1) tbBeta.SelectionStart = 1; } else tbBeta.Text = tbBeta.Text.Remove(0, 1); tbBeta.SelectionStart = tbBeta.Text.Length; break; } default: break; } //Cuando se carga el control tipo knob Beta private void asBeta_Load(object sender, EventArgs e) { //Debe estar tan solo para que el programa vea que exista } //-----------CAJAS DE TEXTO - ENTRADA ALTURA WIIMOTE----------// activa //Cuando pulsamos una tecla estando la caja de texto Altura 159 de 199 Código e) private void tbHeight_KeyPress(object sender, KeyPressEventArgs { if (e.KeyChar == 8) { e.Handled = false; return; } if (e.KeyChar == '.') { txtboxPosition = tbHeight.SelectionStart; tbHeight.Text = tbHeight.Text.Insert (tbHeight.SelectionStart, ","); tbHeight.SelectionStart = ++txtboxPosition; e.Handled = true; } if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyChar.ToString(), "\\d+")) e.Handled = true; } //Cuando soltamos la pulsación de una tecla estando la caja de texto Altura activa private void tbHeight_KeyUp(object sender, KeyEventArgs e) { if (e.KeyValue == 13) if (tbHeight.Text != string.Empty) { //Evitamos que el valor esté fuera de los límites maximo y minimo if (float.Parse(tbHeight.Text) > ((float) trbWiimoteHeight.Maximum / 100F)) tbHeight.Text = ((float) trbWiimoteHeight.Maximum / 100F).ToString(); if (float.Parse(tbHeight.Text) < ((float) trbWiimoteHeight.Minimum / 100F)) tbHeight.Text = ((float) trbWiimoteHeight.Minimum / 100F).ToString(); //Actualizamos los valores de altura del mando MappedWiimotes[selectedWiimote].oldheight = MappedWiimotes[selectedWiimote].height; MappedWiimotes[selectedWiimote].height = float.Parse (tbHeight.Text); //Damos el mismo valor al TrackBar de la altura del Wiimote trbWiimoteHeight.Value = (int)(MappedWiimotes [selectedWiimote].height * 100F); OnControlChange(); } } #endregion #region Funciones concernientes a las barras deslizantes //Si movemos el control deslizante correspondiente a la altura del punto IR private void trbRobotHeight_Scroll(object sender, EventArgs e) { 160 de 199 Código } OnControlChange(); //Si movemos el control deslizante correspondiente a la altura del wiimote seleccionado private void trbWiimoteHeight_Scroll(object sender, EventArgs e) { tbHeight.Text = ((float)trbWiimoteHeight.Value / 100F).ToString(); MappedWiimotes[selectedWiimote].oldheight = MappedWiimotes [selectedWiimote].height; MappedWiimotes[selectedWiimote].height = ((float) trbWiimoteHeight.Value / 100F); OnControlChange(); } //Gestión de cambios en los valores de los controles deslizantes private void UpdateScrollBars() { robotHeight = ((float)trbRobotHeight.Value / 100F); lblRobotHeight.Text = robotHeight.ToString() + " m"; //Determinamos los rangos de la TrackBar de la altura del mando trbWiimoteHeight.SetRange(trbRobotHeight.Value, (int) (ZoneData.Height * 100)); //Mostramos el valor de altura del Wiimote seleccionado if (selectedWiimote > 0) MappedWiimotes[selectedWiimote].height = ((float) trbWiimoteHeight.Value / 100F); //Damos un valor mínimo a la altura de cada mando for (int i = 1; i <= numCircles; i++) if (MappedWiimotes[i].height < robotHeight) MappedWiimotes[i].height = ((float) trbRobotHeight.Value / 100F); tbHeight.Text = ((float)trbWiimoteHeight.Value / 100F).ToString(); } #endregion private void button1_Click(object sender, EventArgs e) { if (!MultipleWiimoteForm.firstPositionDataWrite) MultipleWiimoteForm.writePositionDataHeader(); MultipleWiimoteForm.savePositionData(); } public void PostTwiterPosition(object sender, EventArgs e) { } private void editWiimoteState_CheckedChanged(object sender, EventArgs e) { 161 de 199 Código if (editWiimoteState.Checked) { lblEdition.Text = "Añadir posición wiimote: Clic izquierdo" + "\n" + "Eliminar posición wiimote: Clic derecho" + "\n" + "Mover posición wiimote: Clic derecho, arrastrar y soltar"; } } private void editRestrictedArea_CheckedChanged(object sender, EventArgs e) { if (editRestrictedArea.Checked) { lblEdition.Text = "Haz clic, arrastra y suelta para dibujar un área restringida"; } } //Casillas de seleccion de visualizacion de capas private void cbW7_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW6_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW5_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW4_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW3_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW2_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbW1_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } e) private void cbWiimotes_CheckedChanged(object sender, EventArgs { } pbWiimotes.Invalidate(); private void cbAreas_CheckedChanged(object sender, EventArgs e) 162 de 199 Código { } pbWiimotes.Invalidate(); private void cbObjeto_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void cbRestriccion_CheckedChanged(object sender, EventArgs e) { pbWiimotes.Invalidate(); } private void pbWiimotes_Paint(object sender, PaintEventArgs e) { //Dibujamos las distintas capas existentes //Dibujado areas wiimote if (cbAreas.Checked) for (int i = 0; i < numCircles; i++) { if (wiimoteCheckBoxes[i].Checked) { if ((MappedWiimotes[i + 1].Points90 != null) && (MappedWiimotes[i + 1].alfa == 90)) { e.Graphics.FillPolygon(translucidBrushes [i], MappedWiimotes[i + 1].Points90, newFillMode); e.Graphics.DrawPolygon(whitePen, MappedWiimotes[i + 1].Points90); } else if ((MappedWiimotes[i + 1].Points != null) && (MappedWiimotes[i + 1].alfa != 90)) { e.Graphics.FillPolygon (translucidBrushes[i], MappedWiimotes[i + 1].Points, newFillMode); e.Graphics.DrawPolygon(whitePen, MappedWiimotes[i + 1].Points); } } } //Dibujado wiimotes if (cbWiimotes.Checked) for (int i = 0; i < numCircles; i++) { if (wiimoteCheckBoxes[i].Checked) { e.Graphics.FillEllipse(whiteBrush, MappedWiimotes[i + 1].x - 10, MappedWiimotes[i + 1].y - 10, 20, 20); e.Graphics.DrawEllipse(Pens[i], MappedWiimotes[i + 1].x - 10, MappedWiimotes[i + 1].y - 10, 20, 20); } } //Dibujado numeracion wiimotes if (cbWiimotes.Checked) for (int i = 0; i < numCircles; i++) { if (wiimoteCheckBoxes[i].Checked) 163 de 199 Código { 1].WiimoteID.ToString(), e.Graphics.DrawString(MappedWiimotes[i + SystemFonts.DefaultFont, blackBrush, new System.Drawing.Point (MappedWiimotes[i + 1].x - 5, MappedWiimotes[i + 1].y - 5)); } } //Dibujado origen de coordenadas e.Graphics.DrawString("(0,0)", SystemFonts.DefaultFont, whiteBrush, new System.Drawing.Point(2, 2)); //Dibujado de restriccion de area if (cbRestriccion.Checked) { Rectangle currentRect = Rectangle.FromLTRB( this.initialMousePos.X, this.initialMousePos.Y, currentMousePos.X, currentMousePos.Y); areaRestricted = currentRect; } e.Graphics.DrawRectangle(greenPen, currentRect); //Diubujado de objeto en seguimiento if ((trackingButton) && (MultipleWiimoteForm.almostOneCalibrated) && (MultipleWiimoteForm.almostOneSees)) { if (cbObjeto.Checked) { e.Graphics.FillEllipse(redBrush, MultipleWiimoteForm.RoomBaseCoords.X - 5, MultipleWiimoteForm.RoomBaseCoords.Y - 5, 10, 10); lblPxTrackingPosX.Text = "X:" + MultipleWiimoteForm.RoomBaseCoords.X.ToString() + " px"; lblPxTrackingPosY.Text = "Y:" + MultipleWiimoteForm.RoomBaseCoords.Y.ToString() + " px"; lblMtTrackingPosX.Text = "X:" + (MultipleWiimoteForm.RoomBaseCoords.X / widthFactor).ToString() + " m"; lblMtTrackingPosY.Text = "Y:" + (MultipleWiimoteForm.RoomBaseCoords.Y / heightFactor).ToString() + " m"; } } if (activatedRestrictedArea) 164 de 199 Código { if (((MultipleWiimoteForm.RoomBaseCoords.X > areaRestricted.Left) && (MultipleWiimoteForm.RoomBaseCoords.X < areaRestricted.Right)) && ((MultipleWiimoteForm.RoomBaseCoords.Y > areaRestricted.Top) && (MultipleWiimoteForm.RoomBaseCoords.Y < areaRestricted.Bottom))) { if (!alertSended) EnteredRestrictedArea(); } } } private void pbActiveWiimoteTracking_Paint(object sender, PaintEventArgs e) { for (int i = 0; i < 7; i++) { e.Graphics.FillEllipse(whiteBrush, circlesCoordinates [i].X, circlesCoordinates[i].Y, 20, 20); e.Graphics.DrawEllipse(translucidPens[i], circlesCoordinates[i].X, circlesCoordinates[i].Y, 20, 20); e.Graphics.DrawString((i + 1).ToString(), SystemFonts.DefaultFont, translucidBlackBrush, new System.Drawing.Point(circlesCoordinates[i].X + 5, circlesCoordinates[i].Y + 5)); } if ((trackingButton) && (MultipleWiimoteForm.almostOneCalibrated) && (MultipleWiimoteForm.almostOneSees)) { for (int j = 0; j < 7; j++) { /*Si existe este mando, si el wiimote ve el objeto y si ese Wiimote esta posicionado en el picturebox * dibujamos el circulo sin translucidez, indicando que este mando está viendo el objeto */ if ((j < MultipleWiimoteForm.vis.Length) && (MultipleWiimoteForm.vis[j]) && (j < numCircles)) { e.Graphics.FillEllipse(whiteBrush, circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawEllipse(Pens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawString((j + 1).ToString(), SystemFonts.DefaultFont, blackBrush, new System.Drawing.Point (circlesCoordinates[j].X + 5, circlesCoordinates[j].Y + 5)); } 165 de 199 Código //Sino dibujamos los circulos de visualización con translucidez, indicando que dicho mando no ve el objeto else { e.Graphics.FillEllipse(whiteBrush, circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawEllipse(translucidPens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawString((j + 1).ToString(), SystemFonts.DefaultFont, translucidBlackBrush, new System.Drawing.Point (circlesCoordinates[j].X + 5, circlesCoordinates[j].Y + 5)); } } } else if ((trackingButton) && (MultipleWiimoteForm.almostOneCalibrated) && (! MultipleWiimoteForm.almostOneSees)) for (int j = 0; j < 7; j++) { /*Si existe este mando, si el wiimote ve el objeto y si ese Wiimote esta posicionado en el picturebox * dibujamos el circulo sin translucidez, indicando que este mando está viendo el objeto */ if ((j < MultipleWiimoteForm.vis.Length) && (MultipleWiimoteForm.vis[j]) && (j < numCircles)) { e.Graphics.FillEllipse(whiteBrush, circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawEllipse(Pens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawString((j + 1).ToString(), SystemFonts.DefaultFont, blackBrush, new System.Drawing.Point (circlesCoordinates[j].X + 5, circlesCoordinates[j].Y + 5)); } //Sino dibujamos los circulos de visualización con translucidez, indicando que dicho mando no ve el objeto else { e.Graphics.FillEllipse(whiteBrush, circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawEllipse(translucidPens[j], circlesCoordinates[j].X, circlesCoordinates[j].Y, 20, 20); e.Graphics.DrawString((j + 1).ToString(), SystemFonts.DefaultFont, translucidBlackBrush, new System.Drawing.Point (circlesCoordinates[j].X + 5, circlesCoordinates[j].Y + 5)); } } } 166 de 199 Código private void EnteredRestrictedArea() { if (!alertSended) ActivateTiming(); //Si la alarma se ha activado una vez hace menos de tres minutos, desestimar la alarma if (alertSended) { ActivateTiming(); } //sino lanzar el aviso else { if (twitterAutenticateOK) { SendTwitterAlert(); alertSended = true; } } } if (emailAutenticateOK) { SendEmailAlert(); alertSended = true; } //Funcion que envia un mensaje de Twitter al usuario cuando el objeto entra en la zona restringida private void SendTwitterAlert() { TwitterDirectMessage.Send(WIPSTokens, userScreenName, "El objeto ha entrado dentro del área restringida"); } //Funcion que envia un email al usuario cuando el objeto entra en una zona restringida private void SendEmailAlert() { System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage(); message.To.Add(email); message.Subject = "Alerta de seguimiento"; message.From = new System.Net.Mail.MailAddress ("[email protected]"); message.Body = "El programa WIPS ha detectado que el objeto en seguimiento ha entrado en una zona prohibida"; System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("smtp.gmail.com", 587); System.Net.NetworkCredential credenciales = new System.Net.NetworkCredential("[email protected]", "JohnnyLeegmail"); smtp.Credentials = credenciales; smtp.EnableSsl = true; smtp.Send(message); } //Temporización para evitar notificaciones masivas al usuario private void ActivateTiming() { 167 de 199 Código System.Timers.Timer timer = new System.Timers.Timer(); double interval = 180000; timer.Interval = interval; timer.AutoReset = false; timer.Elapsed += new System.Timers.ElapsedEventHandler (UpdateTimer); timer.Start(); } private void UpdateTimer(object sender, System.Timers.ElapsedEventArgs e) { alertSended = false; } //Función que se encarga de cargar los datos de autenticación de las cuentas sincronizadas private int LoadConfiguration() { //Observamos si el archivo "AutenticateConfig.cfg" existe try { TextReader tr = new StreamReader("AutenticateConfig" + ".cfg"); //Observamos que cuentas están sincronizadas twitterAutenticateOK = bool.Parse(tr.ReadLine()); facebookAutenticateOK = bool.Parse(tr.ReadLine()); emailAutenticateOK = bool.Parse(tr.ReadLine()); //Si tenemos una cuenta de Twitter if (twitterAutenticateOK) { //Adquirimos los datos para la autenticación accesToken.Token = tr.ReadLine(); userTokens.AccessToken = accesToken.Token; accesToken.TokenSecret = tr.ReadLine(); userTokens.AccessTokenSecret = accesToken.TokenSecret; copyConsumerKey = tr.ReadLine(); userTokens.ConsumerKey = copyConsumerKey; copyConsumerSecret = tr.ReadLine(); userTokens.ConsumerSecret = copyConsumerSecret; } } userScreenName = tr.ReadLine(); userUserID = int.Parse(tr.ReadLine()); //Si tenemos una cuenta de email if (emailAutenticateOK) { //Adquirimos la dirección email email = tr.ReadLine(); } tr.Close(); catch (Exception x) 168 de 199 Código { } } return 0; return 1; //Funcion llamada cuando cambiamos el estado de la casilla de activacion de la restricción private void cbToggleRestriction_CheckedChanged(object sender, EventArgs e) { //Si la casilla está activada... if (cbToggleRestriction.Checked) { //... activamos la zona restringida activatedRestrictedArea = true; } else { //... sino la desactivamos activatedRestrictedArea = false; } } } } Código 2. UserConf.cs 169 de 199 Código 3.3 - ZoneData.cs Este archivo contiene las funciones que se encargan de lanzar la primera ventana que ve el usuario. Pide los datos físicos de la sala donde se va a realizar el seguimiento y los guarda para posteriores usos. Cuando el usuario ha terminado de introducir las dimensiones, se encarga de lanzar la ventana principal del programa, creando una instancia del Código 3.1. using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; namespace WIPS { public partial class ZoneData : Form { //Variables que contienen las dimensiones de la sala public static float Length; public static float Width; public static float Height; //Posición del cursor dentro de una caja de texto int txtboxPosition; //Flag que indica si el usurio introduce dos signos de puntuacion bool doubleComma; public ZoneData() { InitializeComponent(); } private void btnOkZoneData_Click(object sender, EventArgs e) { //Debemos rellenar todas las casillas de texto if ((tbLength.TextLength == 0)||(tbWidth.TextLength == 0)|| (tbHeight.TextLength == 0)) MessageBox.Show(this,"Has dejado alguna casilla en blanco","No se puede continuar",MessageBoxButtons.OK); else { //Guardamos los valores pasandolos a tipo float Length = float.Parse(tbLength.Text); Width = float.Parse(tbWidth.Text); Height = float.Parse(tbHeight.Text); this.Hide(); new MultipleWiimoteForm().Show(); } } 170 de 199 Código //Cuando apretamos una tecla mientras tenemos activo el cuadro de texto referente a la altura private void tbHeight_KeyPress(object sender, KeyPressEventArgs e) { KeyPressed(tbHeight, e); } e) private void tbLenght_KeyPress(object sender, KeyPressEventArgs { } KeyPressed(tbLength, e); private void tbWidth_KeyPress(object sender, KeyPressEventArgs e) { KeyPressed(tbWidth, e); } private void KeyPressed(TextBox tb, KeyPressEventArgs e) { txtboxPosition = 0; doubleComma = false; //Si pulsamos la telca de punto o coma... if ((e.KeyChar == '.') || (e.KeyChar == ',')) { txtboxPosition = tbHeight.SelectionStart; //...sin que hayamos introducido un numero anteriormente... if (txtboxPosition == 0) { //...sustituimos la , o el . por la cadena "0," tb.Text = tb.Text.Insert(tb.SelectionStart, "0,"); tb.SelectionStart = txtboxPosition + 2; } else { //Si el valor anterior era también una separacion decimal ... if (tb.Text.EndsWith(",")) { doubleComma = true; } //...reescribimos la coma para que no se escriban dos seguidas if (!doubleComma) { tb.Text = tb.Text.Insert(tb.SelectionStart, ","); tb.SelectionStart = ++txtboxPosition; } doubleComma = false; } } e.Handled = true; if (e.KeyChar == 8) { e.Handled = false; return; } 171 de 199 Código if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyChar.ToString(), "\\d+")) e.Handled = true; } } } //Cuando pulsamos el boton Salir, el programa se cierra private void btnExitZoneData_Click(object sender, EventArgs e) { Application.Exit(); } Código 3. ZoneData.cs 172 de 199 Código 3.4 - Calibration.cs Este archivo contiene las funciones que se encargan de lanzar la ventana de calibración. Conforme se van produciendo los distintos estados de la calibración, va dibujando las diferentes marcas de calibración que el usuario tiene que marcar con el tag infrarrojo. using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Drawing.Imaging; System.Linq; System.Text; System.Windows.Forms; namespace WIPS { public partial class Calibration : Form { Bitmap bCalibration; Graphics gCalibration; public Calibration() { InitializeComponent(); this.KeyDown += new System.Windows.Forms.KeyEventHandler (this.OnKeyPress); bCalibration = new Bitmap(pbCalibrate.Width, pbCalibrate.Height, PixelFormat.Format24bppRgb); gCalibration = Graphics.FromImage(bCalibration); gCalibration.Clear(Color.Black); //BeginInvoke((MethodInvoker)delegate() { pbCalibrate.Image = bCalibration; }); pbCalibrate.Image = bCalibration; } private void OnKeyPress(object sender, System.Windows.Forms.KeyEventArgs e) { //En caso de que se pulse una tecla, se lee si es "esc". Si lo es, se cierra la calibración if ((int)(byte)e.KeyCode == (int)Keys.Escape) { this.Dispose(); MultipleWiimoteForm.calibrationState = 0; return; } } size); //Función que dibuja una cruceta en la posicion indicada public void DrawCalibrationMarks(int x, int y, int size, Pen p) { gCalibration.Clear(Color.Black); gCalibration.DrawEllipse(p, x - size / 2, y - size / 2, size, gCalibration.DrawLine(p, x - size, y, x + size, y); gCalibration.DrawLine(p, x, y - size, x, y + size); 173 de 199 Código BeginInvoke((MethodInvoker)delegate() {pbCalibrate.Image = bCalibration;}); } //Reseteamos la variable de estado de calibración al cerrar el formulario private void Calibration_FormClosing(object sender, FormClosingEventArgs e) { MultipleWiimoteForm.calibrationState = 0; } } } Código 4. Calibration.cs 174 de 199 Código 3.5 - WiimoteMapDraw.cs Este archivo es una clase creada para almacenar todos los datos y características de los wiimotes necesarios para el dibujado de elementos en la ventana de seguimiento. Se almacenan los grados de inclinación de cada mando, la altura y las distintas distancias necesarias para calcular y almacenar los cuatro puntos que forman el área de visionado de cada wiimote. using using using using using using System; System.Collections.Generic; System.Drawing; System.Drawing.Imaging; System.Linq; System.Text; namespace WIPS { public class WiimoteMapDraw { /*Clase que almacena todos los parámetros y * características que tiene cada uno de los * Wiimotes */ public public public public public public public public public public public public public public public public public int x; int y; float height; float oldheight; float alfa; float oldalfa; float beta; float oldbeta; double x0; double x1; double x2; double y0; double z0; float polygonArea; float visibleArea; float nonVisibleArea; int WiimoteID; //Los cuatro pares de coordenadas que delimitan el area de vision de un Wiimote, a alfa public System.Drawing.Point[] Points; //Los cuatro pares de coordenadas que delimitan el area de vision de un Wiimote, a 90º public System.Drawing.Point[] Points90; //Deprecated public System.Drawing.Point[] RotatedPerimeter; public System.Drawing.Point[] ConvCalPoints; public System.Drawing.Point[] WiimSeenCalPoints; //Función para añadir la posición de un Wiimote public WiimoteMapDraw(int x_pos, int y_pos, int ID) { x = x_pos; y = y_pos; WiimoteID = ID; } 175 de 199 Código //Se guardan los cuatro puntos del wiimote para un ángulo alfa de 90º } } public void SavePoints90(Point a, Point b, Point c, Point d) { System.Drawing.Point[] Per90 = { a, b, c, d }; Points90 = Per90; } Código 5. WiimoteMapDraw.cs 176 de 199 Código 3.6 - WiimoteInfo.cs Este archivo contiene las funciones que permiten la creación de una ventana en la que se muestra el objeto visto por el wiimote sin ningún tipo de conversión. Se puede emplear para ayudar al usuario a colocar la plantilla de calibración. Además contiene las funciones necesarias para que el usuario entre las coordenadas reales de las cuatro marcas de calibración y pueda lanzar el proceso de calibración de cada wiimote. Se implementan los botones de cargado y guardado de los datos de calibración. using using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Drawing; System.Drawing.Imaging; System.Data; System.Linq; System.Text; System.Windows.Forms; WiimoteLib; namespace WIPS { public partial class WiimoteInfo : UserControl { private delegate void UpdateWiimoteStateDelegate (WiimoteChangedEventArgs args); //Variables necesarias para el dibujado del objeto del punto infrarrojo en la ventana propia de cada wiimote private Bitmap b = new Bitmap(512, 384, PixelFormat.Format24bppRgb); private Graphics g; SolidBrush redBrush = new SolidBrush(Color.Red); private Wiimote mWiimote; TextBox //Variable que indica la posición del cursor dentro de una int tbPosition = 0; //Flag que indica que un mando pide calibración public bool WantCalibration = false; //Flag que indica el uso de doble coma por parte del usuario en una TextBox public bool doubleComma = false; //Variable que almacena la carga de batería del wiimote public int Battery = 0; //Variables que almacenan las coordenadas reales de las marcas de calibración introducidas por el usuario public float P1X; public float P2X; public float P3X; public float P4X; public float P1Y; public float P2Y; public float P3Y; public float P4Y; 177 de 199 Código public WiimoteInfo() { InitializeComponent(); g = Graphics.FromImage(b); } //Se identifica cada mando public WiimoteInfo(Wiimote wm) : this() { mWiimote = wm; } public void UpdateState(WiimoteChangedEventArgs args) { //Si la información que envía elmando cambia, se actualizarán los datos BeginInvoke(new UpdateWiimoteStateDelegate (UpdateWiimoteChanged), args); } estado private void UpdateWiimoteChanged(WiimoteChangedEventArgs args) { //Se guardan los datos que envia el wiimote en la variable WiimoteState estado = args.WiimoteState; CheckForIllegalCrossThreadCalls = false; //Limpiamos el picturebox g.Clear(Color.Black); //Dibujamos un punto en pantalla correspondiente al objeto visto por el Wiimote UpdateIR(estado.IRState.IRSensors[0]); //Le asignamos el bitmap a la imagen del picturebox pbIR.Image = b; //Actualizamos los valores de batería mostrados por pantalla BeginInvoke((MethodInvoker)delegate() { pbBattery.Value = ((estado.Battery) > 0xc8 ? 0xc8 : ((int)(estado.Battery))); }); BeginInvoke((MethodInvoker)delegate() { lblBattery.Text = estado.Battery.ToString("0") + "%"; }); BeginInvoke((MethodInvoker)delegate() { Battery = (int) (estado.Battery); }); //Mostramos las coordenadas relativas al mando del objeto infrarrojo visto lblX.Text = "X: " + estado.IRState.IRSensors [0].RawPosition.X.ToString("0"); lblY.Text = "Y: " + estado.IRState.IRSensors [0].RawPosition.Y.ToString("0"); } private void UpdateIR(IRSensor irSensor) { if (irSensor.Found) { //Dibujamos un circulo según las coordenadas del objeto infrarrojo visto 178 de 199 Código } } g.FillEllipse(redBrush, (int)(irSensor.RawPosition.X / 2), (int)(irSensor.RawPosition.Y / 2), irSensor.Size + 20, irSensor.Size + 20); public Wiimote Wiimote { set { mWiimote = value; } } public void btnCalibrate_Click(object sender, EventArgs e) { //Queremos calibración WantCalibration = true; //Creamos el formulario si no existe if (MultipleWiimoteForm.cf == null) { MultipleWiimoteForm.cf = new Calibration(); MultipleWiimoteForm.cf.Show(); } if (MultipleWiimoteForm.cf.IsDisposed) { MultipleWiimoteForm.cf = new Calibration(); MultipleWiimoteForm.cf.Show(); } //Comenzamos la calibración MultipleWiimoteForm.doCalibration(); } #region Funciones de entrada de datos private void tbP1X_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP1X, ref P1X,e); } private void tbP1X_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP1X, e); } private void tbP1Y_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP1Y, ref P1Y, e); } private void tbP1Y_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP1Y, e); } private void tbP2X_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP2X, ref P2X, e); } private void tbP2X_KeyPress(object sender, KeyPressEventArgs e) { 179 de 199 Código } CheckOnKeyPress(tbP2X, e); private void tbP2Y_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP2Y, ref P2Y, e); } private void tbP2Y_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP2Y, e); } private void tbP3X_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP3X, ref P3X, e); } private void tbP3X_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP3X, e); } private void tbP3Y_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP3Y, ref P3Y, e); } private void tbP3Y_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP3Y, e); } private void tbP4X_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP4X, ref P4X, e); } private void tbP4X_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP4X, e); } private void tbP4Y_KeyUp(object sender, KeyEventArgs e) { CheckOnKeyUp(ref tbP4Y, ref P4Y, e); } private void tbP4Y_KeyPress(object sender, KeyPressEventArgs e) { CheckOnKeyPress(tbP4Y, e); } #endregion //Miramos si todos los cuadros de texto están llenos para mostrar el boton de calibración private void CheckFilledTextBox() { if ((tbP1X.Text != "") && (tbP2X.Text != "") 180 de 199 Código else && (tbP3X.Text != "") && (tbP4X.Text != "") && (tbP1Y.Text != "") && (tbP2Y.Text != "") && (tbP3Y.Text != "") && (tbP4Y.Text != "")) btnCalibrate.Visible = true; btnCalibrate.Visible = false; } /*Cuando la tecla pulsada vuelve, rellenamos la variable correspondiente * si se cumplen las condiciones */ private void CheckOnKeyUp(ref TextBox tb, ref float result, KeyEventArgs e) { //if (e.KeyValue == 13) if (tb.Text != string.Empty) { if (tb.Text == ",") { tb.Text = "0,"; tb.SelectionStart = 3; } result = float.Parse(tb.Text); } "0," CheckFilledTextBox(); } //Cuando se pulsa una telca estando una caja de texto activa private void CheckOnKeyPress(TextBox tb, KeyPressEventArgs e) { //Si pulsamos la tecla de punto o coma... if ((e.KeyChar == '.')||(e.KeyChar == ',')) { tbPosition = tb.SelectionStart; //...sin que hayamos introducido un número anteriormente if (tbPosition == 0) { //...sustituimos la coma o el punto por la cadena } else { decimal... seguidas tb.Text = tb.Text.Insert(tb.SelectionStart, "0,"); tb.SelectionStart = tbPosition + 2; //Si el valor anterior era también una separación if (tb.Text.EndsWith(",")) { doubleComma = true; } //...reescribimos la coma para que no se escriban dos if (!doubleComma) { tb.Text = tb.Text.Insert(tb.SelectionStart, ","); tb.SelectionStart = ++tbPosition; } 181 de 199 Código } doubleComma = false; } e.Handled = true; if (e.KeyChar == 8) { e.Handled = false; return; } if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyChar.ToString(), "\\d+")) e.Handled = true; } //Funcion para guardar los parámetros de calibración del Wiimote seleccionado private void btnSaveCalibration_Click(object sender, EventArgs e) { //Si el mando ha sido calibrado if (MultipleWiimoteForm.calibrated [MultipleWiimoteForm.actualTab]) //Guardamos los datos en un archivo MultipleWiimoteForm.saveCalibrationData (MultipleWiimoteForm.actualTab + 1); else //De no haber sido calibrado mostramos que el usuario debe hacerlo MessageBox.Show(this, "Debes marcar los cuatro puntos de la plantilla con el objeto infrarrojo", "Faltan datos", MessageBoxButtons.OK); } //Funcion para cargar los parámetros de calibración del Wiimote seleccionado private void btnLoadCalibration_Click(object sender, EventArgs e) { switch (MultipleWiimoteForm.loadCalibrationData (MultipleWiimoteForm.actualTab + 1)) { //Si la calibración ha sido satisfactoria, cargamos los valores obtenidos del archivo case 1: cbCalibratedWiimote.Checked = true; tbP1X.Text = (MultipleWiimoteForm.MdstX [MultipleWiimoteForm.actualTab][0] / UserConf.widthFactor).ToString(); tbP1Y.Text = (MultipleWiimoteForm.MdstY [MultipleWiimoteForm.actualTab][0] / UserConf.heightFactor).ToString(); tbP2X.Text = (MultipleWiimoteForm.MdstX [MultipleWiimoteForm.actualTab][1] / UserConf.widthFactor).ToString(); tbP2Y.Text = (MultipleWiimoteForm.MdstY [MultipleWiimoteForm.actualTab][1] / UserConf.heightFactor).ToString(); tbP3X.Text = (MultipleWiimoteForm.MdstX [MultipleWiimoteForm.actualTab][2] / UserConf.widthFactor).ToString(); tbP3Y.Text = (MultipleWiimoteForm.MdstY [MultipleWiimoteForm.actualTab][2] / UserConf.heightFactor).ToString(); tbP4X.Text = (MultipleWiimoteForm.MdstX [MultipleWiimoteForm.actualTab][3] / UserConf.widthFactor).ToString(); tbP4Y.Text = (MultipleWiimoteForm.MdstY [MultipleWiimoteForm.actualTab][3] / UserConf.heightFactor).ToString(); btnCalibrate.Visible = true; 182 de 199 Código break; saber al usuario //Si la calibración ha producido un error, lo hacemos case 0: MessageBox.Show(this, "Los datos no se han podido cargar. Introdúcelos manualmente","Error al cargar", MessageBoxButtons.OK); break; } } } } default: break; //Funcion para marcar la casilla de Wiimote calibrado public void ShowCalibrationOK() { cbCalibratedWiimote.Checked = true; } Código 6. WiimoteInfo.cs 183 de 199 Código 3.7 - WiimoteInfoLite.cs Este archivo es una versión reducida del código WiimoteInfo.cs. Es una pequeña ventana que muestra la posición del objeto visto por el wiimote, de forma gráfica y en coordenadas escritas. Además muestra la carga de batería del mando. No contiene apenas código puesto que los elementos internos se modifican en la función UpdateLiteTab(), contenida dentro del código 3.1. using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Drawing; System.Drawing.Imaging; System.Data; System.Linq; System.Text; System.Windows.Forms; namespace WIPS { public partial class WiimoteInfoLite : UserControl { public Bitmap bLite = new Bitmap(128, 96, PixelFormat.Format24bppRgb); public Graphics gLite; public WiimoteInfoLite() { InitializeComponent(); gLite = Graphics.FromImage(bLite); } } } Código 7. WiimoteInfoLite.cs 184 de 199 Código 3.8 - AllWiimoteInfo.cs Este archivo contiene las funciones necesarias para el relleno de la pestaña “Visión general”. Dentro de esa pestaña se incrusta una instancia de este formulario. Cuando se crea el formulario, se instancian tantos formularios WiimoteInfoLite.cs como wiimote existan en el sistema. De esta forma se puede tener una misma página todos los datos que están enviando los wiimotes conectados al sistema. using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Drawing; System.Data; System.Linq; System.Text; System.Windows.Forms; namespace WIPS { public partial class AllWiimoteInfo : UserControl { public static Dictionary<int, WiimoteInfoLite> mWiimoteInfoLiteMap = new Dictionary<int, WiimoteInfoLite>(); int j,k; public AllWiimoteInfo() { InitializeComponent(); for (int i = 1; i <= MultipleWiimoteForm.numwiimotes; i++) { //Creamos una instancia del formulario WiimoteInfoLite para cada mando activo mWiimoteInfoLiteMap[i] = new WIPS.WiimoteInfoLite(); //Suspendemos el dibujado de la ventana this.SuspendLayout(); //Creamos unas coordenadas e indices para colocar cada una de las instancias anteriores if (i < 5) { k = i; j = 0; } if (i > 4) { j = 418; k = i - 5; } las /*Modificamos algunas de las propiedades de cada una de * instancias anteriores durante su inicialización */ mWiimoteInfoLiteMap[i].Location = new System.Drawing.Point(j,((k-1) * 98)); mWiimoteInfoLiteMap[i].Name = "wiimoteInfoLite" + i.ToString(); mWiimoteInfoLiteMap[i].Size = new System.Drawing.Size (416, 97); mWiimoteInfoLiteMap[i].TabIndex = i; this.Controls.Add(mWiimoteInfoLiteMap[i]); 185 de 199 Código } } } } //Recuperamos el dibujado de la ventana this.ResumeLayout(false); Código 8. AllWiimoteInfo.cs 186 de 199 Código 3.9 - Preferences.cs Este archivo contiene las funciones necesarias para la autenticación de las cuentas que se pueden vincular al servicio de notificaciones. También se encarga de crear un archivo de configuración si el usuario desea que los datos introducidos puedan utilizarse en sesiones posteriores. using using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.IO; System.Linq; System.Text; System.Windows.Forms; Twitterizer; namespace WIPS { public partial class Preferencias : Form { public static string emailAddress; public static bool saveConfig = false; public Preferencias() { InitializeComponent(); } e) //Cuando pulsamos le botón Cancelar private void btnPreferencesCancel_Click(object sender, EventArgs { } this.Dispose(); //Cuando se carga el formulario public void Preferencias_Load(object sender, EventArgs e) { //Obsevamos si existe un archivo de configuración de una sesión anterior if (File.Exists("AutenticateConfig.cfg")) //Se marcará la casilla de selección que indica que los datos se guardarán para posteriores usos cbSaveConf.Checked = true; //Actualizamos el estado de los botones y paneles de imagen según los datos de configuración cargados UpdateLinkStates(); } paneles //Función que actualiza el aspecto de los botones y de los public void UpdateLinkStates() { //Si había una cuenta de Twitter configurada if (UserConf.twitterAutenticateOK) { //Lo indicamos en el aspecto del botón y del panel de imagen lateral 187 de 199 Código plTwitterLinkState.BackgroundImage = Properties.Resources.ok; btnLinkTwitterAccount.Text = "¡Cuenta de Twitter vinculada!"; } //Si no la había else { //Lo indicamos en el aspecto del botón y del panel de imagen lateral plTwitterLinkState.BackgroundImage = Properties.Resources.raemi_Cross_Out; btnLinkTwitterAccount.Text = "Vincular cuenta de Twitter"; } //Si había una cuenta de Facebook configurada if (UserConf.facebookAutenticateOK) { //Lo indicamos en el aspecto del botón y del panel de imagen lateral plFacebookLinkState.BackgroundImage = Properties.Resources.ok; } //Si no la había else { //Lo indicamos en el aspecto del botón y del panel de imagen lateral plFacebookLinkState.BackgroundImage = Properties.Resources.raemi_Cross_Out; } //Si había una cuenta de correo configurada if (UserConf.emailAutenticateOK) { //Lo indicamos en el aspecto del botón y del panel de imagen lateral plEmailLinkState.BackgroundImage = Properties.Resources.ok; btnLinkEmail.Text = "¡E-mail vinculado!"; emailAddress = UserConf.email; } //Si no la había else { //Lo indicamos en el aspecto del botón y del panel de imagen lateral plEmailLinkState.BackgroundImage = Properties.Resources.raemi_Cross_Out; btnLinkEmail.Text = "Vincular e-mail"; } } e) //Cuando pulsamos el botón "Vincular cuenta de Twitter" private void btnLinkTwitterAccount_Click(object sender, EventArgs { //Si ya hemos configurado una cuenta de Twitter if (UserConf.twitterAutenticateOK) { //Se le indica al usuario si quiere borrar los anteriores datos y configurar una nueva cuenta 188 de 199 Código DialogResult result = MessageBox.Show("Ya tienes una cuenta de Twitter vinculada, ¿deseas sustituirla por otra?", "Estás suscrito", MessageBoxButtons.YesNo); if (result == System.Windows.Forms.DialogResult.Yes) { //Lanzamos la autenticación de cuenta de Twitter new AutenticateTwitter().Show(); } } //Si es la primera vez que configuramos una cuenta de Twitter else { //Lanzamos la autenticación de cuenta de Twitter new AutenticateTwitter().Show(); } } //Cuando pulsamos el botón "Vincular cuenta de Facebook" private void btnLinkFacebookAccount_Click(object sender, EventArgs e) { MessageBox.Show("Función no disponible en la versión actual", "No disponible", MessageBoxButtons.OK, MessageBoxIcon.Information); } //Cuando hacemos clic en el boton "Vincular e-mail" private void btnLinkEmail_Click(object sender, EventArgs e) { //Si ya hemos configurado una cuenta de e-mail if (UserConf.emailAutenticateOK) { //Se le indica al usuario si quiere borrar los anteriores datos y configurar una nueva cuenta DialogResult result = MessageBox.Show("Ya tienes una dirección de e-mail vinculada, ¿deseas sustituirla por otra?", "Estás suscrito", MessageBoxButtons.YesNo); if (result == System.Windows.Forms.DialogResult.Yes) { //Lanzamos la autenticación de cuenta de Twitter new AutenticateEmail().Show(); } } //Si esta es la primera vez que autenticamos una cuenta de email else { //Lanzamos la autenticación de cuenta de Twitter new AutenticateEmail().Show(); } } //Cuando movemos el ratón sobre el panel de imagen al lado del botón "Vincular cuenta de Twitter" private void plTwitterLinkState_MouseMove(object sender, MouseEventArgs e) { if (UserConf.twitterAutenticateOK) ttPreferences.Show("Pulsa para eliminar la suscripción", plTwitterLinkState, 2000); } //Cuando hacemos clic con el ratón sobre el panel de imagen al lado del botón "Vincular cuenta de Twitter" 189 de 199 Código private void plTwitterLinkState_MouseClick(object sender, MouseEventArgs e) { if (UserConf.twitterAutenticateOK) { DialogResult result = MessageBox.Show("¿Estás seguro de que deseas desvincular la cuenta de Twitter?", "Desvincular cuenta", MessageBoxButtons.YesNo); if (result == System.Windows.Forms.DialogResult.Yes) { // emailAddress = ""; UserConf.twitterAutenticateOK = false; UpdateLinkStates(); } } } //Cuando movemos el ratón sobre el panel de imagen al lado del botón "Vincular e-mail" private void plEmailLinkState_MouseMove(object sender, MouseEventArgs e) { if (UserConf.emailAutenticateOK) ttPreferences.Show("Pulsa para eliminar la suscripción", plEmailLinkState, 2000); } //Cuando hacemos clic con el ratón sobre el panel de imagen al lado del botón "Vincular e-mail" private void plEmailLinkState_MouseClick(object sender, MouseEventArgs e) { if (UserConf.emailAutenticateOK) { DialogResult result = MessageBox.Show("¿Estás seguro de que deseas desvincular la cuenta de e-mail?", "Desvincular cuenta", MessageBoxButtons.YesNo); if (result == System.Windows.Forms.DialogResult.Yes) { emailAddress = ""; UserConf.emailAutenticateOK = false; UpdateLinkStates(); } } } //Cuando cambia el estado de la caja de selección que indica si queremos guardar los datos en un archivo de configuración o no private void cbSaveConf_CheckedChanged(object sender, EventArgs e) { if (cbSaveConf.Checked) saveConfig = true; else saveConfig = false; } //Cuando hacemos clic en el botón Aceptar private void btnPreferencesOK_Click(object sender, EventArgs e) { //Observamos si el usuario quiere guardar la configuración 190 de 199 Código if (saveConfig) { //Si es así, lanzamos la función que guarda la configuración, creando el archivo "AutenticateConfig.cfg" SaveConfiguration(); } //Si el usuario no quiere guardar los datos else { //Se borra el archivo "AutenticateConfig.cfg" si existiera File.Delete("AutenticateConfig.cfg"); } this.Dispose(); } //Función que crea el archivo que guarda los datos de autenticación de las cuentas private void SaveConfiguration() { //Creamos el archivo "AutenticateConfig.cfg" TextWriter tw = new StreamWriter("AutenticateConfig" + ".cfg"); //Que suscripciones hay guardadas tw.WriteLine(UserConf.twitterAutenticateOK); tw.WriteLine(UserConf.facebookAutenticateOK); tw.WriteLine(UserConf.emailAutenticateOK); vinculación //Si se ha vinculado una cuenta de Twitter if (UserConf.twitterAutenticateOK) { //Se almacenan los datos necesarios para una próxima } tw.WriteLine(UserConf.accesToken.Token); tw.WriteLine(UserConf.accesToken.TokenSecret); tw.WriteLine(UserConf.copyConsumerKey); tw.WriteLine(UserConf.copyConsumerSecret); tw.WriteLine(UserConf.userScreenName); tw.WriteLine(UserConf.userUserID); if (UserConf.facebookAutenticateOK) { //No está incluida en esta versión } } } //Si se ha vinculado una cuenta de e-mail if (UserConf.emailAutenticateOK) { //Se guarda la dirección de e-mail tw.WriteLine(emailAddress); } //Cerramos la escritura del archivo "AutenticateConfig.cfg" tw.Close(); } Código 9. Preferences.cs 191 de 199 Código 3.10 - AutenticateTwitter.cs Este archivo contiene las funciones necesarias para la autenticación de la cuenta de Twitter. Abre el navegador web predeterminado e indica al usuario las acciones a realizar para autorizar una cuenta de Twitter. Guarda los datos de autorización para toda la sesión. using using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; System.Diagnostics; Twitterizer; namespace WIPS { public partial class AutenticateTwitter : Form { public AutenticateTwitter() { InitializeComponent(); } //Función que se lanza al cargarse el formulario private void AutenticateTwitter_Load(object sender, EventArgs e) { //Proceso de autenticación OAuth UserConf.requestToken = OAuthUtility.GetRequestToken("código secreto", "código secreto", "oob").Token; //Se abre el navegador predeterminado del sistema y se carga la ventana que pide la autorización del usuario Process browserProcess = new Process(); browserProcess.StartInfo.FileName = OAuthUtility.BuildAuthorizationUri(UserConf.requestToken).AbsoluteUri; browserProcess.Start(); lblTwitterFeedback.Text = "Una vez autorizada la aplicación, introduce el pin de 6 dígitos y pulsa la tecla enter"; pbLoadingAnimation.Visible = false; txtTwitterPin.Visible = true; } //Cuando escribimos en la TextBox private void txtTwitterPin_KeyDown(object sender, KeyEventArgs e) { //Si la tecla pulsada es Enter if (e.KeyValue == 13) { //Observamos si el código introducido tiene 7 dígitos if (txtTwitterPin.Text.Length == 7) { //Si tenemos 7 digitos UserConf.pin = txtTwitterPin.Text.ToString(); try { //Conseguimos el token de acceso 192 de 199 Código UserConf.accesToken = OAuthUtility.GetAccessToken ("código secreto", "código secreto", UserConf.requestToken, UserConf.pin); } formulario catch (TwitterizerException ex) { //Si se ha producido un error if (ex.Message != "") { //Se le indica al usuario y se cierra el MessageBox.Show(this,"Has introducido un codigo incorrecto", "Error en el pin", MessageBoxButtons.OK); this.Dispose(); } } //Si todo está correcto observamos que el token de acceso recibido por Twiiter no esté vacío y por lo tanto erróneo if ((UserConf.accesToken.Token != null) || (UserConf.accesToken.TokenSecret != null) || (UserConf.accesToken.ScreenName != null) || (UserConf.accesToken.UserId ! = 0)) { //Si el token de acceso es correcto, procedemos a guardar todos los datos necesarios para la autorización UserConf.userTokens.AccessToken = UserConf.accesToken.Token; UserConf.userTokens.AccessTokenSecret = UserConf.accesToken.TokenSecret; UserConf.copyConsumerKey = "código secreto"; UserConf.userTokens.ConsumerKey = UserConf.copyConsumerKey; UserConf.copyConsumerSecret = "código secreto"; UserConf.userTokens.ConsumerSecret = UserConf.copyConsumerSecret; UserConf.userScreenName = UserConf.accesToken.ScreenName; UserConf.userUserID = UserConf.accesToken.UserId; //Se le envía un mensaje directo al usuario informándole de que se ha vinculado su cuenta con el programa WIPS TwitterDirectMessage.Send(UserConf.WIPSTokens, UserConf.userScreenName, "Esta cuenta será utilizada para enviarte notificaciones de la aplicación WIPS"); //Marcamos el flag de que Twitter está activado como servicio de notificaciones UserConf.twitterAutenticateOK = true; this.Dispose(); } } erróneo"; //Si no se han introducido 7 dígitos else { //Informamos del error al usuario UserConf.errorCounter++; if (UserConf.errorCounter > 1) lblException.Text = "El código sigue siendo lblException.Visible = true; 193 de 199 Código } } if (e.KeyValue == 8) { e.Handled = false; return; } if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyValue.ToString(), "\\d+")) e.Handled = true; } } } Código 10. AutenticateTwitter.cs 194 de 199 Código 3.11 - AutenticateEmail.cs Este archivo contiene las funciones necesarias para la autenticación de la cuenta de e-mail. Pide al usuario que introduzca una cuenta de e-mail correcta. Se verifica que la cuenta introducida tenga la forma correcta y se envía un correo a la cuenta de e-mail configurada informando al usuario de que se ha vinculado con el servicio de notificaciones del sistema WIPS. using using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Text.RegularExpressions; System.Windows.Forms; namespace WIPS { public partial class AutenticateEmail : Form { public AutenticateEmail() { InitializeComponent(); } //Cuando pulsamos una tecla en el TextBox private void txtEmail_KeyDown(object sender, KeyEventArgs e) { if (e.KeyValue == 13) { //Si pulsamos enter activamos la validación del formulario this.AutoValidate = AutoValidate.EnableAllowFocusChange; this.Close(); this.AutoValidate = AutoValidate.Disable; } if (e.KeyValue == 8) { e.Handled = false; return; } if (!System.Text.RegularExpressions.Regex.IsMatch (e.KeyValue.ToString(), "\\d+")) e.Handled = true; } e) private void txtEmail_Validating(object sender, CancelEventArgs { string errorMsg; if (!ValidEmailAddress(txtEmail.Text, out errorMsg)) { e.Cancel = true; txtEmail.Select(0, txtEmail.Text.Length); } this.epInvalidEmail.SetError(txtEmail, errorMsg); 195 de 199 Código } private void txtEmail_Validated(object sender, EventArgs e) { epInvalidEmail.SetError(txtEmail, ""); } public bool ValidEmailAddress(string emailAddress, out string errorMessage) { //Al validar la caja de Texto observamos que los datos introducidos correspondan con una cuenta de e-mail if (emailAddress.Length == 0) { errorMessage = ""; return true; } //Si se cumplen las condiciones que tiene que tener una cuenta de e-mail if (emailAddress.IndexOf("@") > -1) { if (emailAddress.IndexOf(".", emailAddress.IndexOf("@")) > emailAddress.IndexOf("@")) { //Se procede a crear el mensaje que se enviará al usuario errorMessage = ""; System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage(); message.To.Add(txtEmail.Text.ToString()); message.Subject = "Confirmación de suscripción a WiimoteLocation"; message.From = new System.Net.Mail.MailAddress ("[email protected]"); message.Body = "Recibes este mail confirmando tu suscripción a los avisos que envíe el programa WiimoteLocation"; System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("smtp.gmail.com", 587); //Credenciales de la cuenta de correo del programa. Datos secretos System.Net.NetworkCredential credenciales = new System.Net.NetworkCredential("[email protected]", "contraseña"); smtp.Credentials = credenciales; smtp.EnableSsl = true; //Enviamos el mensaje smtp.Send(message); //Guardamos la cuenta de e-mail para usarla durante esta sesión } } Preferencias.emailAddress = txtEmail.Text; UserConf.email = txtEmail.Text; UserConf.emailAutenticateOK = true; return true; //Si no se ha podido validar la TextBox se informa al usuario de que la ha escrito mal errorMessage = "Debes escribir un mail con formato válido.\n" + "Por ejemplo [email protected]"; 196 de 199 Código } } } return false; //Si se clica el botón cancelar se cierra el programa private void btnCancel_Click(object sender, EventArgs e) { this.Dispose(); } Código 11. AutenticateEmail.cs 197 de 199 Referencias Referencias 1. Wikipedia. (2006). Wiimote. Recuperado el 30 de Abril del 2011, de: http://es.wikipedia.org/wiki/Wiimote. 2. Desconocido. (2008). 1024x768 IR Camera? Recuperado el 18 de Agosto del 2012, de: http://nuigroup.com/ forums/viewthread/1353/. 3. Lee, Johnny Chung. (2007). Página personal. Recuperado el 16 de Diciembre del 2010, de: http:// johnnylee.net/. 4. Lee, Johnny Chung. (2007). Proyectos con el Wiimote. Recuperado el 19 de Enero del 2011, de: http:// johnnylee.net/projects/wii/. 5. Schmidt, Uwe. (2008). Wiimote Whiteboard. Recuperado el 19 de Enero del 2011, de: http:// wiki.uweschmidt.org/WiimoteWhiteboard/WiimoteWhiteboard. 6. Bonache, Ricardo. (2009). Wiimote Hack. Extraído el 22 de Diciembre de 2010 desde https://sauron.etse.urv.es/ public/PROPOSTES/pub/pdf/1231pub.pdf 7. Nakano, Yoshiaki. (2009). Wiimote Positioning System - An epoch-making system of indoor position detection. Extraído el 22 de Marzo de 2011 desde http://www.nakano.ac/edutech_yoshiaki_nakano.pdf 8. Tas, Baris; Altiparmak, Nihat; and Tosun, Ali Saman. (2009). Low Cost Indoor Location Management System using Infrared Leds and Wii Remote Controller. Extraído el desde http://www.cs.utsa.edu/~tosun/PAPERS/ IPCCC2009.pdf 9. Schill, Alexander. (2011). Location-based services. Extraído el 27 de Octubre de 2011 desde http:// www.rn.inf.tu-dresden.de/lectures/MCaMC/10_Location-based_Services.pdf 10. Foxall, James, Sams Teach Yourself Visual C# 2008 in 24 Hours. Primera edición 2008: Sams. 11. Varios. (2005). Rotate Polygon. Recuperado el 30 de Julio del 2011, de: http://www.codeguru.com/forum/ showpost.php?p=1179217&postcount=4. 12. Microsoft. Collection Classes (C# Programming Guide). Recuperado el del de: http://msdn.microsoft.com/enus/library/ybcx56wz(v=vs.90).aspx. 13. Desconocido, Autor. VCSKicks. Recuperado el 11 de Julio del 2011, de: http://www.vcskicks.com/csharpprogramming.php. 14. Desconocido, Autor. (2008). C# Angle and Altitude Selectors (Photoshop-Style). Recuperado el del de: http:// www.vcskicks.com/angle_user_control.php. 15. Criminisi, Antonio. (2003). Getting into the picture. Recuperado el 29 de Julio del 2011, de: http:// plus.maths.org/content/getting-picture. 16. Smith, Patrick. (2011). Twitterizer. Recuperado el 30 de Junio del 2012, de: http://www.twitterizer.net/. 17. Microsoft. How to: Send E-Mail Programmatically. Recuperado el 8 de Agosto del 2012, de: http:// msdn.microsoft.com/en-us/library/ms268749. 18. Microsoft. Send email error "STARTTLS"? Recuperado el 28 de Julio del 2012, de: http:// social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/476c5b80-ffc9-48d0-9055-568ac32fb964/. 198 de 199 Referencias 19. Microsoft. Control.Validating Event. Recuperado el 28 de Julio del 2012, de: http://msdn.microsoft.com/en-us/ library/system.windows.forms.control.validating.aspx. 199 de 199