Detector de Posición mediante Wiimote

Anuncio
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:
wips.contact@gmail.com. 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 wips.contact@gmail.com. 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
("wips.contact@gmail.com");
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("wips.contact@gmail.com", "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
("wips.contact@gmail.com");
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("wips.contact@gmail.com", "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 miemail@ejemplo.com";
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
Descargar