desarrollo de aplicaciones para control y monitoreo de …148.206.53.84/tesiuami/uami13885.pdf ·...

93
DESARROLLO DE APLICACIONES PARA CONTROL Y MONITOREO DE DISPOSITIVOS REMOTOS PRESENTAN: CRISÓSTOMO BRAVO ENRIQUE ORTEGA TORRES ANA LILIA UNIDAD IZTAPALAPA DIVISIÓN DE CIENCIAS BÁSICAS E INGENIERIA PARA OBTENER EL TITULO DE LICENCIADO EN COMPUTACION México D. F. Julio 2007 ____________________________ M en C. Omar L. Cabrera Jiménez Asesor

Upload: trinhkiet

Post on 30-Sep-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

D E S A R R O L L O D E A P L I C A C I O N E S PA R A

C O N T R O L Y M O N I T O R E O D E D I S P O S I T I V O S R E M O T O S

PRESENTAN:

CRISÓSTOMO BRAVO ENRIQUE

ORTEGA TORRES ANA LILIA

UNIDAD IZTAPALAPA DIVISIÓN DE CIENCIAS BÁSICAS E INGENIERIA

PA R A O B T E N E R E L T I T U L O D E L I C E N C I A D O E N C O M P U TA C I O N

México D. F. Julio 2007

____________________________

M en C. Omar L. Cabrera Jiménez

Asesor

Resumen: Este trabajo describe el entorno de control y monitoreo remoto de un Robot conectado al puerto paralelo de una PC. Este entorno, desarrollado en lenguaje Java permite enviar órdenes de movimientos y acciones que el robot es capaz de realizar de acuerdo al estado presente en el registro de datos del puerto conectado en una computadora Servidor y mas aún, se tiene una estructura que permite lanzar programas residentes en la computadora remota mediante comandos en la computadora Cliente. De modo que esto nos permite incorporar nuevos programas o utilizar los que existen en el sistema para darle un conjunto escalable de funciones que el robot puede realizar; principalmente multimedia, controlados todos de manera remota independientemente de la red de que se disponga pero cuya disponibilidad debe estar garantizada. Esta versión ha sido probada sobre diferentes tecnologías de red como 802.11 y Bluetooth. También se incluyen los resultados de las pruebas realizadas.

2

Indice Presentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Justificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2. Marco Teórico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1 Programación con sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Hilos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.3 Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4 Ejemplo de construcción de un Applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.5 Programación del Puerto Paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.6 Driver UserPort.sys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 2.7 La API Genuts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

3. Especificación de requerimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.1 Estructura de las tramas de control del robot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.2 Comunicación entre cliente y servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.3 Plataforma de operación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.4 Velocidad de respuesta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.5 Control de programas remotos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.6 Interfaz gráfica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4. Modelo de requerimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.1 Modelo de casos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.2 Modelo de interfaz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 4.3 Modelo de dominio del problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

5. Aplicación desarrollada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

5.1 Descripción general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.2 Diseño conceptual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.3 Esquema general de comunicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.4 Diagramas de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

6. Pruebas realizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

7. Recomendaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

8. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

9. Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

Anexo A. Código fuente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 A.1. Programa Joystick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 A.2. Programa Monitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 A.3. Programa controlador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 A.4. Programa Sensor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

3

Presentación Se desea desarrollar y evaluar aplicaciones de software para control y monitoreo de dispositivos electromecánicos mediante redes de computadoras conectadas con diferentes medios.

Justificación El desarrollo de software ha sustituido en gran medida el proceso de desarrollo de hardware y esto, entre otras causas; ha hecho posible una disminución en costos y rápido desarrollo de nuevas tecnologías. La posibilidad de sustituir dispositivos electrónicos por un entorno programable de amplias posibilidades como una PC permite libertades en el diseño de algoritmos y diferentes soluciones para el control de dispositivos remotos que de otro modo nos restringirían a un diseño, el cual una vez llevado a la etapa de pruebas puede ser modificado pero con el costo de adquirir nuevos dispositivos y además con variaciones en las características eléctricas de los dispositivos que deben ser consideradas por mínimas que estas sean. Aún cuando existen dispositivos programables como PICs y PLCs que nos permiten un avance en este sentido, la tecnología de computadoras ha evolucionado notablemente al grado de contar con puertos de comunicación como BlueTooth, puerto paralelo, serial, redes inalámbricas de alta velocidad y USB ademas de aplicaciones multimedia y todo esto con un funcionamiento eficiente probado en diferentes entornos. De este modo entramos a un campo que originalmente dio soluciones con un punto de vista puramente electrónico mediante radio control y circuitos combinatorios y secuenciales en conjunto con dispositivos programables, memorias EPROM etc. Aprovechamos ahora el avance en el campo de nuevas tecnologías de información y comunicación para sustituir en gran medida la electrónica por el desarrollo de software, cambiando el laboratorio de electrónica por una computadora de escritorio y con un mínimo de interfaces electrónicas que incluso pueden ser modeladas por computadora.

4

Objetivos:

• Analizar diferentes esquemas para el control y monitoreo remoto de dispositivos electromecánicos proponiendo el uso de diferentes algoritmos.

• Establecer diversos medios de comunicación, los cuales permitan probar los programas

de cómputo a desarrollar para el control y monitoreo de dispositivos electromecánicos. • Construir diferentes esquemas de control y monitoreo remoto para dispositivos

electromecáncios implementando diferentes mecanismos de comunicación. • Programar diferentes algoritmos de control y monitoreo para dispositivos remotos

conectados a una red de computadoras. • Evaluar los resultados de diferentes pruebas de operación del sistema de control y

monitoreo desarrollado sobre diferentes medios y condiciones de operación. • Proponer algunas líneas de investigación y mejoras a los equemas y algoritmos que se

analicen basados en los resultados de sus pruebas de operación.

5

1. INTRODUCCIÓN Los avances en tecnologías de computo han tenido un mayor desarrollado en los últimos años a nivel mundial y se ha acentuado muchísimo más en los países del primer mundo, sin embargo en los países en vías de desarrollo es necesario el desarrollo y la investigación en este campo. En el caso de México el avance tecnológico se ha visto afectado al igual que en otros países en vías de desarrollo por la carencia del factor humano suficientemente preparado para analizar, diseñar, producir, coordinar, manejar o mantener estas tecnologías. Esta necesidad y la oportunidad de participar en este tipo de proyectos, nos ha motivado a trabajar, para lograr el resultado que estamos presentando. El control de dispositivos remotos es un área en la que se pueden encontrar una gran variedad de aplicaciones. El creciente desarrollo de internet y de redes locales, no solo nos da un medio a través del cual podemos comunicarnos para este propósito, sino que además nos permite incorporar nuevas tecnologías de acuerdo a necesidades específicas. Mas aún, es tecnología que se encuentra disponible ampliamente y que evoluciona proporcionando mejores características y mejor desempeño en poco tiempo. El esquema que presentaremos proporciona el control remoto específico de un robot conectado al puerto paralelo de una computadora (servidor) desde otra estación (cliente), comunicándose en un entorno de red local, convencional o inalámbrica; mediante el protocolo TCP/IP. Este esquema se puede aplicar para el control remoto de otros dispositivos que tengan una interfaz para conectarse al puerto paralelo con un mínimo de modificaciones al software desarrollado. El diseño permite desarrollar e incorporar nuevas aplicaciones, a ejecutarse en la estación servidor, las cuales pueden ser invocadas remotamente desde la línea de comandos del cliente, o bien utilizar programas ya disponibles en el servidor. En el cliente se cuenta con un monitor que recibe mensajes y los publica, estos mensajes pueden ser enviados por cualquier aplicación actual en el servidor o que sea desarrollada a futuro. El servidor envía la salida de cualquier aplicación invocada desde el cliente a este monitor lo que nos permite saber lo que se están ejecutando. La programación de esta aplicación ha sido desarrollada en Java, lenguaje que nos permite independencia de plataforma para el programa cliente. Por otro lado Java nos proporciona diferentes conjuntos de clases que ya han sido programadas y satisfacen una amplia variedad de necesidades que al ser reutilizadas nos permiten un rápido avance en el desarrollo de la aplicación. Dado que el servidor esta programado para interactuar con la línea de comandos de MS-DOS, y además utiliza bibliotecas de enlace dinámicas DLL para el control del puerto paralelo, este se ejecuta solo en plataforma Windows. Durante el desarrollo del proyecto se enfrentaron diferentes problemáticas cuya solución nos llevó a la conclusión del trabajo que estamos presentando. Desde la elección del lenguaje de programación que se contempló como la mejor opción para resolver el problema de ejecución independiente del sistema operativo, la capacidad de interactuar con otros programas desarrollados en lenguaje nativo C si se requiriera, la capacidad de invocar programas ejecutables, etc., hasta la forma en que Windows XP, NT o 2000 bloquean los puertos por cuestiones de seguridad y que dificultan su interacción con el programador.

6

2. MARCO TEÓRICO Hemos agregado los conceptos fundamentales que han hecho posible el desarrollo de este trabajo. Omitimos conceptos básicos de programación orientada a objetos, dado que no es el objetivo el presentar un tratado extensivo sobre paradigmas de programación. Aquí se muestra de forma general de conceptos que han sido considerados para el desarrollo de la aplicación y para poder extender la misma. Iniciamos con los conceptos involucrados de la programación en Java referentes a la manera en que se llevan a cabo las comunicaciones entre los diferentes programas o módulos que conforman la aplicación. Posteriormente presentamos la estructura del puerto paralelo con su programación y finalmente se explica la teoría que nos ayuda a resolver el problema de lectura/escritura en los puertos en entorno Windows que por razones de seguridad es restringido a los usuarios en versiones XP, 2000 y NT 2.1. Programación con sockets Para nadie es ya desconocida la necesidad existente de comunicación de datos independientemente de la distancia que separe las estaciones a ambos extremos y, sobre todo, independientemente de la red en que estén localizadas. Desafortunadamente, la mayor parte de las redes son entidades independientes, establecidas para servir a las necesidades de un grupo único. Lo que es más, es imposible construir una red mundial con una única tecnología de hardware porque ninguna cumpliría con las necesidades de todos los usuarios (algunos necesitarían grandes velocidades de comunicación a corta distancia, mientras que otros desearían conectar computadoras a miles de kilómetros). Es por tanto necesario proporcionar procedimientos que permitan la interconexión de redes distintas, independiente de la tecnología de hardware utilizada. Este tipo de problemas son los que se denominan internetworking (o interconexión de redes) y su solución ha permitido extender las posibilidades de intercambio de información entre los distintos usuarios de distintas redes independientemente de su tecnología y de su localización, a través de la Internet (también llamada red de redes). La tecnología Internet es un ejemplo de interconexión de sistemas abiertos. Se llama sistema abierto porque, al contrario que los sistemas de comunicaciones propietarios, las especificaciones están al alcance del público. Así, cualquiera puede construir el software necesario para comunicarse a través de Internet. Además, toda la tecnología ha sido diseñada para conseguir la comunicación entre máquinas de diferentes arquitecturas, con distintos sistemas operativos y usando casi cualquier tipo de red. TCP/IP ha sido implementado en muchos y diferentes tipos de redes comerciales. Cada una de estas redes, dependiendo de su tecnología y forma de funcionamiento, adapta el protocolo TCP/IP a sus características de una forma diferente. IP es un protocolo de red en el que los hosts “cliente y servidor” se identifican globalmente con la dirección IP, que consiste en octetos (32 bits) de la forma 156.35.14.2, donde parte de la dirección identifica a la red y parte al host dentro de esa red. No obstante, los programas utilizan usualmente direcciones del tipo hal.ugr.es que identifican al host y al dominio al que pertenece.

7

En la capa de transporte las entidades se identifican con el número de puerto (16 bits), que identifica el proceso dentro del host al que esta destinada o del que proviene la información. TCP/IP reserva una serie de puertos bien conocidos para las aplicaciones estándar (por ejemplo, 7 para el servidor de ECHO, 13 para el servidor de DAYTIME, 21 para el servidor FTP, 23 para el servidor TELNET y 25 para el servidor SMTP) y deja sin definir otros para las aplicaciones no estándares desarrolladas por los usuarios (advertencia: para aplicaciones de usuario se deben usar números de puerto por encima de 1000 ya que los primeros están reservados para root). La programación de aplicaciones sobre TCP/IP se basa en el modelo cliente-servidor. Básicamente, la idea consiste en que al iniciar un intercambio de información, una de las partes debe “iniciar” el diálogo (cliente) mientras que la otra debe estar indefinidamente preparada para recibir peticiones de establecimiento de dicho diálogo (servidor). Cada vez que un usuario cliente desee entablar un diálogo, primero deberá contactar con el servidor, mandar una petición y posteriormente esperar la respuesta. Los servidores pueden clasificarse atendiendo a si están diseñados para admitir múltiples conexiones simultáneas (servidores concurrentes), por oposición a los servidores que admiten una sola conexión por aplicación ejecutada (llamados iterativos). Evidentemente, el diseño de estos últimos será mucho más sencillo que el de los primeros. Otro concepto importante es la determinación del tipo de interacción entre el cliente y el servidor, que puede ser orientada a conexión o no orientada a conexión. Estas corresponden, respectivamente, con los dos protocolos característicos de la capa de transporte: TCP y UDP. TCP (orientado a conexión) garantiza toda la “seguridad” requerida para que la transmisión esté libre de errores: verifica que todos los datos se reciben, automáticamente retransmite aquellos que no fueron recibidos, garantiza que no hay errores de transmisión y además, numera los datos para garantizar que se reciben en el mismo orden con el que fueron transmitidos. Igualmente, elimina los datos que por algún motivo aparecen repetidos, realiza un control de flujo para evitar que el emisor envíe más rápido de lo que el receptor puede consumir y, finalmente, informa a las aplicaciones (tanto al cliente como al servidor) si los niveles inferiores de red no pueden entablar la conexión. Por el contrario, UDP (no orientado a conexión) no introduce ningún procedimiento que garantice la seguridad de los datos transmitidos, siendo en este caso responsabilidad de las aplicaciones la realización de los procedimientos necesarios para subsanar cualquier tipo de error. En el desarrollo de aplicaciones sobre TCP/IP es imprescindible conocer como éstas pueden intercambiar información con los niveles inferiores, es decir, conocer la interfaz con los protocolos TCP o UDP. Esta interfaz es bastante parecida al procedimiento de entrada/salida ordinario en el sistema operativo UNIX que, como se sabe, está basado en la secuencia abrir-leer/escribir-cerrar. En particular, la interfaz se basa en la definición de un elemento abstracto denominado “socket” concepto muy similar a los descriptores de archivos usados en las operaciones convencionales de entrada-salida en UNIX. Recuérdese que en las operaciones entrada/salida es necesario realizar la apertura del archivo antes de que la aplicación pueda acceder a dicho archivo a través del ente abstracto “descriptor de archivo”. En la interacción de las aplicaciones con los protocolos TCP o UDP, es necesario que éstas obtengan antes el descriptor o “socket”, y a partir de ese momento dichas aplicaciones intercambiarán información con el nivel inferior a través del socket creado. Una vez creados, los sockets pueden ser usados por el servidor para esperar indefinidamente el establecimiento de una conexión (sockets pasivos) o, por el contrario, pueden ser usados por el cliente para iniciar la conexión (socket activos).

8

Todos los elementos necesarios para llevar a cabo comunicaciones utilizando los sockets de lenguajes Java se encuentran definidos en el paquete java.net. A continuación revisaremos las clases contenidas en este y los conocimientos de programación necesarios para la comunicación en red.

2.1.1. La clase InetAddress Cualquier máquina conectada a Internet, lo que se conoce también como host, ha de tener una dirección de 32 bits, denominada dirección IP, que la identifica de forma única, salvo ocasionales conflictos transitorios de direcciones. Por otra parte, dentro de un mismo host puede haber diferentes procesos comunicándose de manera independiente con otros procesos de otras máquinas, o incluso con diferentes threads (hilos) dentro de un mismo proceso en otra máquina. Por ejemplo, es frecuente que una computadora tenga establecidas simultáneamente varias sesiones FTP con distintos servidores, y a la vez éste recibiendo correo mediante SMTP y leyendo news TNP. Lo que se hace para poner de acuerdo a las distintas entidades que se van a comunicar y repartir la información entre ellas es asignar a cada servicio un identificador, también de 32 bits, denominado número de puerto (port number), De esta forma, una conexión queda completamente especificada por los números de puerto que emplean los interlocutores y las direcciones IP de las máquinas en que residen. Como alternativa a las direcciones IP, existen unos identificadores simbólicos para las máquinas, basados en el servicio de nombre de dominio. De esta forma, podemos emplear nombre como xamanek.uam.mx o www.iztapalapa.uam.mx en vez de las direcciones IP de estos servidores. La clase InetAddress permite encapsular una dirección IP y su nombre de servidor correspondiente, de un modo que facilita su uso. Esta clase no tiene constructores accesibles para el código de usuario, sino que para instanciarla hay que invocar a unos métodos estáticos especiales, denominados métodos fábrica (factory methods). Existen tres de estos métodos:

• getLocalHost(): devuelve un objeto InetAddress conteniendo la dirección de la máquina sobre la que se está ejecutando el programa. • getByName(String nombre): devuelve un objeto InetAddress con la dirección de la máquina cuyo nombre se le pasa como parámetro. Si no se encuentra ninguna máquina con ese nombre, se produce una excepción de tipo UnKnowHostException. • getAllByNAme(String Nombre): devuelve un arreglo de objetos InetAddress conteniendo las direcciones de todas las máquinas que tienen el nombre pasado como parámetro. Si no se encuentra ninguna se produce una excepción de tipo UNKnownHostException.

9

Además de los métodos fábrica que sirven para instanciar la clase, existen algunos métodos normales (de instancia):

• getHostName(): devuelve un String que contiene el nombre de las máquina a la que corresponde la dirección. • getAddress(): devuelve un arreglo de 4 bytes con la dirección IP. • topString(): devuelve un String con el nombre y la dirección IP.

2.1.2. Sockets del lado del cliente El mecanismo de sockets se emplea para establecer una comunicación bidireccional fiable entre dos procesos. Estos procesos pueden residir en la misma máquina o en máquinas diferentes conectadas en red. La creación de un objeto de tipo Socket también establece la conexión. Se pueden utilizar los siguientes constructores:

• Socket(String host, int port): crea un socket que enlaza el host local con el host y el puerto especificados. Puede producir las excepciones UnkownHostException e IOException. • Socket(InetAddress dirección, int port): análogo al anterior, sólo que en este caso el host se especifica pasando un objeto InetAddress. En caso de haber problemas se producirá una excepción de tipo IOException.

Se pueden conocer en cualquier momento los atributos de un objeto de tipo Socket mediante los siguientes métodos:

• getInetAddress(): devuelve un objeto InetAddress con la dirección del host remoto al que está conectado el socket. • getPort(): devuelve el número de puerto al que está conectado el socket en el host remoto. • getLocalPort(): devuelve el número del puerto local al que está conectado el socket.

Una vez creado un socket, el envío y recepción de datos a través del mismo son exactamente iguales que la escritura y la lectura de los datos de un archivo. Para obtener y cerrar los streams de entrada y salida asociados al socket, se invocan los siguientes métodos:

• getInputStream(): devuelve el objeto de tipo InputStream asociado con el socket. • getOutputStream(): devuelve el objeto de tipo OutputStream asociado con el socket. • close(): cierra tanto el stream de entrada como el de salida.

10

Con los streams obtenidos se pueden efectuar las operaciones que ya vimos en la sección anterior. Si se produce algún problema durante las transmisión o la recepción, como puede ser una pérdida de conexión, se generará una excepción IOException. 2.1.3. Sockets del lado del servidor Si se desea que un programa actúe como servidor, es necesario algún mecanismo que permita esperar a recibir conexiones por tiempo indefinido. El hilo principal del servidor puede estar permanentemente en un bucle, dentro del cual se queda bloqueado esperando a que una máquina remota establezca una conexión con él. En el momento en que comienza a existir dicha conexión, el servidor queda desbloqueado y crea un hilo hijo que atiende al socket establecido con ese cliente concreto. Una vez hecho esto, el hilo principal vuelve a quedar en espera de la siguiente conexión y el hilo hijo atiende a las peticiones del cliente hasta que se cierra el socket que le enlaza con él, momento en el cual se autodestruye. Esta arquitectura permite conseguir de un modo muy sencillo un reparto de carga equitativo entre los clientes que intentan acceder al servicio. Para implementar esta funcionalidad se usa la clase ServerSocket, que admite los siguientes constructores:

• ServerSocket(int puerto): crea un ServerSocket sobre el puerto especificado. • ServerSocket(int puerto, int timeout): crea un ServerSocket sobre el puerto especificado. En caso de que ese puerto esté ocupado, espera un tiempo de hasta timeout milisegundos a que quede libre. En caso de no quedar libre en este tiempo, se produce una excepción.

Ambos constructores genera una excepción IOException en caso de problemas. Para esperar a que un cliente “llame” y establezca una conexión, se invoca el método accept(). Este método deja bloqueado el thread que lo llama hasta que se produce la conexión. Una vez que ésta queda establecida, la llamada a accept() devuelve un objeto de tipo Socket, que se puede usar para la comunicación de la forma descrita en el apartado anterior. 2.2. Hilos 2.2.1. Programas de flujo único Un programa de flujo único o mono-hilado utiliza un único flujo de control (thread) para controlar su ejecución. Pues muchos programas no necesitan la potencia o utilidad de múltiples flujos de control.

Por ejemplo, en el siguiente código:

11

public class HolaMundo { static public void main( String args[] ) { System.out.println( "Hola Mundo!" ); } }

Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurre dentro de un único hilo de ejecución (thread).

Debido a que la mayor parte de los entornos operativos no solían ofrecer un soporte razonable para múltiples hilos de control, los lenguajes de programación tradicionales, tales como C++, no incorporaron mecanismos para describir de manera elegante situaciones de este tipo. La sincronización entre las múltiples partes de un programa se llevaba a cabo mediante un ciclo infinito. Estos entornos son de tipo síncrono, gestionados por sucesos. Entornos tales como el de Macintosh de Apple, Windows de Microsoft y X11/Motif fueron diseñados en torno al modelo de bucle de suceso.

2.2.2 Programas de flujo múltiple

En el ejemplo anterior “Hola mundo”, no se ve el hilo de ejecución que corre el programa. Sin embargo, Java posibilita la creación y control de hilos de ejecución explícitamente. La utilización de hilos (threads) en Java, permite una enorme flexibilidad a los programadores a la hora de plantearse el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar hilos, permite que se puedan implementar aplicaciones portables y robustas, lo cual no es posible con otros lenguajes de tercera generación.

Los navegadores utilizan diferentes hilos ejecutándose en paralelo para realizar varias tareas, "aparentemente" concurrentemente. Por ejemplo, en muchas páginas Web, se puede desplazar la página e ir leyendo el texto antes de que todas las imágenes estén presentes en la pantalla. En este caso, el navegador está obteniendo las imágenes en un hilo de ejecución y soportando el desplazamiento de la página en otro hilo.

Las aplicaciones multihilo utilizan muchos contextos de ejecución para cumplir su trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas e independientes. Se puede utilizar un hilo de ejecución para cada subtarea.

Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas secuencialmente, un programa multihilo permite que cada thread comience y termine tan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.

Vamos a modificar el programa de saludo creando tres hilos de ejecución individuales, que imprimen cada uno de ellos su propio mensaje de saludo, MultiHola.java:

class TestTh extends Thread { private String nombre; private int retardo;

12

// Constructor para almacenar nombre y retardo public TestTh( String s,int d ) { nombre = s; retardo = d; } // El metodo run() es similar al main(), pero para // threads. Cuando run() termina el thread muere public void run() { // Retrasamos la ejecución el tiempo especificado try { sleep( retardo ); } catch( InterruptedException e ) { ; } // Ahora imprimimos el nombre System.out.println( "Hola Mundo! "+nombre+" "+retardo ); } } public class MultiHola { public static void main( String args[] ) { TestTh t1,t2,t3; // Creamos los threads t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) ); t2 = new TestTh( "Thread 2",(int)(Math.random()*2000) ); t3 = new TestTh( "Thread 3",(int)(Math.random()*2000) ); // Arrancamos los threads t1.start(); t2.start(); t3.start(); } } 2.2.3. La clase Thread Es la clase que encapsula todo el control necesario sobre los hilos de ejecución (threads) y para ello se sirve de los métodos que se exponen en las secciones siguientes.

2.2.3.1 Métodos de Clase

Estos son los métodos estáticos que deben llamarse de manera directa en la clase Thread.

13

currentThread()

Este método devuelve el objeto thread que representa al hilo de ejecución que se está ejecutando actualmente.

yield()

Este método hace que el intérprete cambie de contexto entre el hilo actual y el siguiente hilo ejecutable disponible. Es una manera de asegurar que los hilos de menor prioridad no sufran inanición.

sleep( long )

El método sleep() provoca que el intérprete ponga al hilo en curso a dormir durante el número de milisegundos que se indiquen en el parámetro de invocación. Una vez transcurridos esos milisegundos, dicho hilo volverá a estar disponible para su ejecución.

2.2.3.2 Métodos de Instancia

Presentamos solo algunos de los métodos de la clase Thread, los cuales utilizaremos constantemente dada su importancia.

start()

Este método indica al intérprete de Java que cree un contexto del hilo del sistema y comience a ejecutarlo. A continuación, el método run() de este hilo será invocado en el nuevo contexto del hilo. Hay que tener precaución de no llamar al método start() más de una vez sobre un hilo determinado.

run()

El método run() constituye el cuerpo de un hilo en ejecución. Este es el único método del interfaz Runnable. Es llamado por el método start() después de que el hilo apropiado del sistema se haya inicializado. Siempre que el método run() devuelva el control, el hilo actual se detendrá.

stop()

Este método provoca que el hilo se detenga de manera inmediata. A menudo constituye una manera brusca de detener un hilo, especialmente si este método se ejecuta sobre el hilo en curso. En tal caso, la línea inmediatamente posterior a la llamada al método stop() no llega a ejecutarse jamás, pues el contexto del hilo muere antes de que stop() devuelva el control. Una forma más elegante de detener un hilo es utilizar alguna variable que ocasione que el método run() termine de manera ordenada. En realidad, nunca se debería recurrir al uso de este método.

suspend()

El método suspend() toma el hilo y provoca que se detenga su ejecución. Si la ejecución de un hilo se suspende, puede llamarse a resume() sobre el mismo hilo para lograr que vuelva a ejecutarse.

14

resume()

El método resume() se utiliza para revivir un hilo suspendido. No hay garantías de que el hilo comience a ejecutarse inmediatamente, ya que puede haber un hilo de mayor prioridad en ejecución actualmente, pero resume() ocasiona que el hilo vuelva a ser un candidato a ser ejecutado.

setPriority( int )

El método setPriority() asigna al hilo la prioridad indicada por el valor pasado como parámetro. Hay bastantes constantes predefinidas para la prioridad, definidas en la clase Thread, tales como MIN_PRIORITY, NORM_PRIORITY y MAX_PRIORITY, que toman los valores 1, 5 y 10, respectivamente.

getPriority()

Este método devuelve la prioridad del hilo de ejecución en curso, comprendido entre uno y diez.

setName( String )

Este método permite identificar al hilo con un nombre mnemónico. De esta manera se facilita la depuración de programas multihilo. Este nombre aparecerá en todas las líneas de trazado que se muestran cada vez que el intérprete Java imprime excepciones no capturadas.

getName()

Este método devuelve el valor actual, de tipo cadena, asignado como nombre al hilo en ejecución mediante setName().

2.2.4. Creación de un Thread

Hay dos modos de conseguir hilos de ejecución (threads) en Java. Una es implementando la interfaz Runnable, la otra es extender la clase Thread.

La implementación de la interfaz Runnable es la forma habitual de crear hilos. Las interfaces proporcionan al programador una forma de agrupar el trabajo, se utilizan para diseñar los requerimientos comunes. La interfaz define el trabajo y la clase, o clases, que implementan la interfaz para realizar ese trabajo

Es importante distinguir las diferencias entre una clase y una interfaz. Primero, una interfaz solamente puede contener métodos abstractos y/o variables estáticas y finales (constantes). Las clases, por otro lado, pueden implementar métodos y contener variables que no sean constantes. Segundo, una interfaz no puede implementar cualquier método. Una clase que implemente una interfaz debe implementar todos los métodos definidos en ese interfaz. Una interfaz tiene la posibilidad de poder extenderse de otras interfaces y, al contrario que las clases, puede extenderse de múltiples interfaces. Además, una interfaz no puede ser instanciada con el operador new; por ejemplo, la siguiente sentencia no está permitida:

Runnable a = new Runnable(); // No se permite

15

El primer método de crear un hilo de ejecución es simplemente extender la clase Thread:

class MiThread extends Thread { public void run() { . . . } }

El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobrescribe el método Thread.run() por su propia implementación. El método run() es donde se realizará todo el trabajo de la clase. Extendiendo la clase Thread, se pueden heredar los métodos y variables de la clase padre. En este caso, solamente se puede extender o derivar una vez de la clase padre. Esta limitación de Java puede ser superada a través de la implementación de Runnable:

public class MiThread implements Runnable { Thread t; public void run() { // Ejecución del thread una vez creado } }

En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda ejecutar el proceso como un hilo. Además, el método abstracto run() está definido en la interfaz Runnable y tiene que ser implementado. La única diferencia entre los dos métodos es que este último es mucho más flexible. En el ejemplo anterior, todavía está la oportunidad de extender la clase MiThread, si fuese necesario. La mayoría de las clases creadas que necesiten ejecutarse como un hilo, implementarán el interfaz Runnable, ya que probablemente extenderán alguna de su funcionalidad a otras clases.

No se debe pensar que la interfaz Runnable está haciendo alguna cosa cuando la tarea se está ejecutando, solamente contiene métodos abstractos.

package java.lang; public interface Runnable { public abstract void run() ; }

Como se ve, una interfaz sólo proporciona un diseño para las clases que vayan a ser implementadas. En el caso de Runnable, obliga a la definición del método run(), por lo tanto, la mayor parte del trabajo se hace en la clase Thread. Las siguientes líneas de código explican esto.

public class Thread implements Runnable { ... public void run() { if( tarea != null ) tarea.run() ; } } ... }

16

De este segmento de código se desprende que la clase Thread también implemente la interfaz Runnable. “tarea.run()” se asegura de que la clase con que trabaja (la clase que va a ejecutarse como un hilo) no sea nula y ejecuta el método run() de esa clase. Cuando esto suceda, el método run() de la clase hará que corra como un hilo.

A continuación se presenta un ejemplo que implementa la interfaz Runnable para crear un programa multihilo.

class javaMultiHilo { static public void main( String args[] ) { Thread hiloA = new Thread( new MiHilo(),"hiloA" ); Thread hiloB = new Thread( new MiHilo(),"hiloB" ); hiloA.start(); hiloB.start(); try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){} System.out.println( Thread.currentThread() ); hiloA.stop(); hiloB.stop(); } } class NoHaceNada { // Esta clase existe solamente para que sea heredada por la clase // MiHilo, para evitar que esta clase sea capaz de heredar la clase // Thread, y se pueda implementar el interfaz Runnable en su // lugar } class MiHilo extends NoHaceNada implements Runnable { public void run() { // Presenta en pantalla información sobre este hilo en particular System.out.println( Thread.currentThread() ); } }

Como se puede observar, el programa define una clase MiHilo que extiende a la clase NoHaceNada e implementa el interfaz Runnable. Se redefine el método run() en la clase MiHilo para presentar información sobre el hilo.

La única razón de extender la clase NoHaceNada es proporcionar un ejemplo de situación en que haya que extender alguna otra clase, además de implementar el interfaz.

En el ejemplo javaMultiHilo2.java muestra el mismo programa básicamente, pero en este caso extendiendo la clase Thread, en lugar de implementar el interfaz Runnable para crear el programa multihilo.

17

class javaMultiHilo2 { static public void main( String args[] ) { Thread hiloA = new Thread( new MiHilo(),"hiloA" ); Thread hiloB = new Thread( new MiHilo(),"hiloB" ); hiloA.start(); hiloB.start(); try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){} System.out.println( Thread.currentThread() ); hiloA.stop(); hiloB.stop(); } } class MiHilo extends Thread { public void run() { // Presenta en pantalla información sobre este hilo en particular System.out.println( Thread.currentThread() ); } }

En ese caso, la nueva clase MiHilo extiende la clase Thread y no implementa el interfaz Runnable directamente (la clase Thread implementa el interfaz Runnable, por lo que indirectamente MiHilo también está implementando ese interfaz). El resto del programa es similar al anterior.

Y todavía se puede presentar un ejemplo más simple, utilizando un constructor de la clase Thread que no necesita parámetros, tal como se presenta en el ejemplo javaMultiHilo3.java. En los ejemplos anteriores, el constructor utilizado para Thread necesitaba dos parámetros, el primero un objeto de cualquier clase que implemente el interfaz Runnable y el segundo una cadena que indica el nombre del hilo (este nombre es independiente del nombre de la variable que referencia al objeto Thread).

class javaMultiHilo3 { static public void main( String args[] ) { Thread hiloA = new MiHilo(); Thread hiloB = new MiHilo(); hiloA.start(); hiloB.start(); try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){}

18

System.out.println( Thread.currentThread() ); // Se detiene la ejecución de los dos hilos hiloA.stop(); hiloB.stop(); } } class MiHilo extends Thread { public void run() { System.out.println( Thread.currentThread() ); } }

Las sentencias en este ejemplo para instanciar objetos Thread, son mucho menos complejas, siendo el programa, en esencia, el mismo de los ejemplos anteriores.

2.2.5. Arranque de un Thread

Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar natural para crear y arrancar otros hilos. La línea de código:

t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );

crea un nuevo hilo de ejecución. Los dos argumentos representan el nombre del hilo y el tiempo que se desea que espere antes de imprimir el mensaje.

Al tener control directo sobre los hilos, hay que arrancarlos explícitamente. En el ejemplo con:

t1.start();

start(), en realidad es un método oculto en el hilo de ejecución que llama a run().

2.2.6. Manipulación de un Thread

Si todo fue bien en la creación del hilo, t1 debería contener un thread válido, que controlaremos en el método run().

Una vez dentro de run(), se pueden comenzar las sentencias de ejecución como en otros programas. run() sirve como rutina main() para los hilos; cuando run() termina, también lo hace el hilo. Todo lo que se quiera que haga el hilo de ejecución ha de estar dentro de run(), por eso cuando se dice que un método es Runnable, es obligatorio escribir un método run().

En este ejemplo, se intenta inmediatamente esperar durante una cantidad de tiempo aleatoria (pasada a través del constructor):

sleep( retardo );

19

El método sleep() simplemente le dice al hilo de ejecución que duerma durante los milisegundos especificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del hilo. sleep() no consume recursos del sistema mientras el hilo duerme. De esta forma otros hilos pueden seguir funcionando. Una vez hecho el retardo, se imprime el mensaje "Hola Mundo!" con el nombre del hilo y el retardo.

2.2.7. Suspensión de un Thread

Puede resultar útil suspender la ejecución de un hilo sin marcar un límite de tiempo. Si, por ejemplo, está construyendo un applet con un hilo de animación, seguramente se querrá permitir al usuario la opción de detener la animación hasta que quiera continuar. No se trata de terminar la animación, sino desactivarla. Para este tipo de control de los hilos de ejecución se puede utilizar el método suspend().

t1.suspend();

Este método no detiene la ejecución permanentemente. El hilo es suspendido indefinidamente y para volver a activarlo de nuevo se necesita realizar una invocación al método resume():

t1.resume();

2.2.8. Parada de un Thread

El último elemento de control que se necesita sobre los hilos de ejecución es el método stop(). Se utiliza para terminar la ejecución de un hilo:

t1.stop();

Esta llamada no destruye el hilo, sino que detiene su ejecución. La ejecución no se puede reanudar ya con t1.start(). Cuando se liberen las variables que se usan en el hilo, el objeto Thread (creado con new) quedará marcado para eliminarlo y el garbage collector se encargará de liberar la memoria que utilizaba.

En el ejemplo, no se necesita detener explícitamente el hilo de ejecución. Simplemente se le deja terminar. Los programas más complejos necesitarán un control sobre cada uno de los hilos que lancen, el método stop() puede utilizarse en esas situaciones.

Si se necesita, se puede comprobar si un hilo está vivo o no; considerando vivo un hilo que ha comenzado y no ha sido detenido.

t1.isAlive();

Este método devolverá true en caso de que el hilo t1 esté vivo, es decir, ya se haya llamado a su método run() y no haya sido parado con un stop() ni haya terminado el método run() en su ejecución.

En el ejemplo no hay problemas de realizar una parada incondicional, al estar todos los hilos vivos. Pero si a un hilo de ejecución, que puede no estar vivo, se le invoca su método stop(), se generará una excepción. En este caso, en los que el estado del hilo no puede conocerse de antemano es donde se requiere el uso del método isAlive().

20

2.3. APPLETS

2.3.1. Introducción a los applets

Los applets son programas que se incluyen en las páginas Web. Los applets son ejecutados en la máquina cliente, con lo que no existe un exceso de carga del servidor o saturación del ancho de banda. Permiten cargar a través de la red una aplicación portable que se ejecuta en el navegador. Para que esto ocurra tan sólo hace falta que el navegador sea capaz de interpretar Java.

A las páginas que contienen applets se las denomina páginas Java-Powered. Las applets pueden ser visualizadas con la herramienta appletviewer, incluido en el JDK de Java.

Los applets no son exactamente aplicaciones Java, ya que presentan las siguientes diferencias respecto a las aplicaciones normales Java:

• Se cargan mediante un navegador, no siendo lanzados por el intérprete Java. • Son cargados a través de la red por medio de páginas HTML y no residen en el disco duro

de la máquina que los ejecuta. • Poseen un ciclo de vida diferente; mientras que una aplicación se lanza una vez, un applet

se arranca (inicia) cada vez que el usuario recarga la página en la que se encuentra dicho applet.

• Tienen menos derechos que una aplicación clásica, por razones de seguridad. De modo predeterminado en el puesto que los ejecuta no pueden ni leer ni escribir archivos, ni lanzar programas, ni cargar DLLs. Sólo pueden comunicarse con el servidor Web en que se encuentra la página Web que las contiene.

2.3.2. Consideraciones sobre la seguridad en los applets

Como ya se ha dicho los applets tienen una serie de restricciones de programación que los hacen "seguros".

Estas restricciones de seguridad son especialmente importantes, ya que evitarán que se cargue por error un applet que destruya datos de la máquina, que obtenga información restringida, o que produzca otros daños inesperados.

Los applets no dejan de ser "ejecutables" que funcionan dentro de una aplicación, como puede ser un visualizador de páginas Web (browser). Este ejecutable puede obtenerse de una red, lo que significa que hay código posiblemente no fiable que se ejecuta dentro de la aplicación.

Java tiene muchas restricciones de seguridad que minimizan el riesgo de la ejecución de applets, pero estas también limitan a los programadores de applets en su capacidad de programación.

El modelo de seguridad para los applets en Java lo trata como código no fiable ejecutándose dentro de un entorno fiable. Por ejemplo, cuando un usuario instala una copia de un navegador Web en una máquina cree que su código será funcional en el entorno. Normalmente los usuarios tienen cuidado de qué instalan cuando proviene de una red. Un applet, por el contrario, se carga desde la red sin ninguna comprobación de su fiabilidad.

El lenguaje Java y los applets son escritos para que eviten applets no fiables.

21

Estas restricciones son implementadas para verificar que los códigos de byte de las clases de los applets, no rompen las reglas básicas del lenguaje ni las restricciones de acceso en tiempo de ejecución. Sólo cuando las mismas son satisfechas se le permite a la applet ejecutar su código. Cuando se ejecuta, se le marca para señalar que se encuentra dentro del intérprete. Esta marca permite a las clases de tiempo de ejecución determinar cuándo a una fracción del código se le permite invocar a cierto método. Por ejemplo, un applet está restringido en los hosts en los que se puede abrir una conexión de red o en un conjunto de URLs a las que puede acceder.

En su conjunto estas restricciones constituyen una política de seguridad. En el futuro, Java tendrá políticas más ricas, incluyendo algunas que usen encriptación y autentificación para permitir a los applets una mayor capacidad.

La actual política de seguridad afecta a los recursos que un applet puede usar, cuyos principales puntos son:

• Los accesos que pueden realizar los applets a los archivos son restringidos. En particular escribir y/o leer en archivos no será una capacidad estándar que se pueda realizar en los navegadores que soporten applets de Java.

• Las conexiones de red serán restringidas a conectar solo con el host del que proviene el applet.

• Un applet no es capaz de usar ningún método que pueda resultar en una ejecución arbitraria, código no revisado o ambos. Esto incluye métodos que ejecuten programas arbitrarios (métodos nativos) así como la carga de bibliotecas dinámicas.

Se anticipa en cualquier caso que en el futuro los modelos de seguridad permitirán a las applets autentificadas superar estas restricciones.

22

2.3.3. LA CLASE APPLET

2.3.3.1 Situación de la clase Applet en la API de Java

La clase Applet Java, de la cual han de heredar todos los programas Java que vayan a actuar como applets, es la única clase que contiene el paquete java.applet de la API de Java.

Esta clase hereda de Object (como todas las clases Java), pero además hereda de Component y Container, que son dos clases del paquete gráfico AWT. Esto ya perfila las posibilidades gráficas de este tipo de aplicaciones Java.

2.3.3.2 Métodos del ciclo de vida

Como ya se ha indicado un applet no tiene un ciclo de vida tan "sencillo" como el de una aplicación, que simplemente se ejecuta hasta que finaliza su método main().

La siguiente figura representa el ciclo de vida de un applet:

Fig 3.1 Ciclo de vida de un applet

Cada círculo representa una fase en el ciclo de vida del applet. Las flechas representan transiciones y el texto representa la acción que causa la transición. Cada fase está marcada con una invocación a un método del applet:

• void init(); Es invocado cuando se carga el applet. Aquí se suelen introducir las iniciaciones que el applet necesite.

• void start(); Es invocado cuando el applet, después de haber sido cargado, ha sido parado (cambio de página Web, minimización del navegador,...), y de nuevo activado (vuelta a la página, restauración del navegador,...). Se informa al applet que tiene que empezar su funcionamiento.

• void stop(); Es invocado para informar al applet que debe detener su ejecución. Así un applet que utilice threads, debería detenerlos en el código de este método.

• void destroy(); Es invocado para informar al applet que su espacio está siendo solicitado por el sistema, es decir el usuario abandona el navegador. El applet debe aprovechar este momento para liberar o destruir los recursos que está utilizando.

• void paint(); Es invocado cada vez que hay que el navegador redibuja el applet.

23

Al crear un applet no es necesario implementar todos estos métodos.

Cuando un navegador carga una página Web que contiene un applet, suele mostrar en su parte inferior un mensaje como:

initializing... starting...

Esto indica que el applet, se está cargando:

1. Una instancia de la clase applet es creada.

2. El applet es iniciado, mediante su método init().

3. El applet empieza a ejecutarse, mediante su método start().

Cuando el usuario se encuentra con una página Web, que contiene un applet y salta a otra página, entonces el applet se detiene invocando a su método stop(). Si el usuario retorna a la página donde reside el applet, éste vuelve a ejecutarse nuevamente invocando a su método start().

Cuando el usuario sale del navegador el applet tiene un tiempo para finalizar su ejecución y hacer una limpieza final, mediante el método destroy().

2.3.3.3 La clase URL

Una URL (Uniform Resource Locator) es una dirección de Internet. Cada recurso (archivo, página Web, imagen...) tiene uno propio. En Java existe una clase denominada URL que modela esta clase de objetos.

La clase URL pertenece al paquete java.net, y tiene una cierta importancia en el desarrollo de los applets, puesto que muchos de los métodos de la clase Applet la utilizan para acceder a determinado recurso de Internet o para identificarse.

Podemos especificar un URL de manera absoluta:

URL URLabsoluto = new URL("http://www.host.com/dir/fich.htm");

O bien podemos especificar un URL de manera relativa:

URL URLhost = new URL("http://www.Javasoft.com/");

URL URLrelativo = new URL( URLhost, "dir/fich.htm");

Ambos ejemplos corresponderían al URL "http://www.host.com/dir/fich.htm".

24

2.3.3.4 Inclusión de un applet en una página Web

Para incluir un applet en una página Web, una vez compilado el applet, debe incluirse entre el código HTML de la página Web una etiqueta <APPLET>, e indispensablemente los siguientes tres parámetros:

code: Especifica el URL del archivo de clase Java (*.class) que contiene el applet.

width: Especifica la anchura inicial del applet (en pixels).

heigth: Especifica la altura inicial del applet (en pixels).

Además, de la etiqueta inicial, un applet puede tener parámetros que se especificarán mediante etiquetas <PARAM>, y básicamente contener dos parámetros:

name: Indica el nombre del parámetro del applet al que esta etiqueta hace referencia.

value: Establece este valor al parámetro indicado en name de la misma etiqueta.

Así un ejemplo de esto sería:

<applet code="AppletDiagonal.class" width=200 height=200> <param name=Parametro1 value=Valor1> <param name=Parametro2 value=Valor2> </applet>

En este ejemplo el applet puede entender los parámetros Parametro1 y Parametro2, mediante los métodos que se describen posteriormente, y se obtendría Valor1 y Valor2 respectivamente.

Se observa que además de la etiqueta <applet> en el código HTML también aparece una etiqueta </applet>. Esto sucede porque HTML es un lenguaje, en el que casi todas las etiquetas de inicio de elemento (<etiq>) tienen una etiqueta de fin (</etiq>).

2.3.3.5 Obtención de los parámetros de un applet

Cuando se incluye un applet en una página Web se utiliza la etiqueta HTML <applet>. Las etiquetas HTML permiten utilizar parámetros, y la etiqueta <applet> hace lo propio, permitiendo al applet recibir parámetros de ejecución, tal y como una aplicación los recibe en el parámetro s (un vector de cadenas) de su método main(String[] s).

Los siguientes métodos se utilizan para extraer información de los parámetros que recibió el applet cuando fue llamado mediante código HTML:

• URL getDocumentBase(); Devuelve el URL del documento que contiene el applet. • URL getCodeBase(); Devuelve el URL del applet. • String getParameter(String name); Devuelve el valor de un parámetro (etiquetas <param>)

que aparece en el documento HTML.

Tenemos la llamada a un applet, con el siguiente código HTML:

25

<applet code="AppletParam.class" width=50 height=50> <param name=Color value="red"> </applet>

Una llamada en este applet al método getParameter("Color") devolverá "red".

2.3.3.6 Obtención de información sobre un applet

Algunos métodos se utilizan para comunicar información o mostrar mensajes en la pantalla referentes al applet:

• boolean isActive(); Comprueba si el applet está activo. • void showStatus(String status); Muestra una cadena del estado en la pantalla. • String getAppletInfo(); Devuelve información relativa al applet como el autor, Copyright o

versión. • String[ ][ ] getParameterInfo(); Devuelve un vector que describe algún parámetro específico

del applet. Cada elemento en el vector es un vector de tres cadenas que tienen la forma: {nombre, tipo, comentario}.

Un ejemplo de como definir este método para un applet que permita un solo parámetro, color, sería:

public String[][] getParameterInfo() { String info[][] = { {"Color","String","foreground color"} }; return info; }

2.3.3.7 Manipulación del entorno de un applet

Algunos applets pueden afectar al entorno en que están ejecutándose. Para ello se utilizan los métodos:

• AppletContext getAppletContext(); Devuelve un AppletContext, que permite al applet afectar a su entorno de ejecución.

• void resize( int ancho, int largo); Solicita que se modifique el tamaño del applet. También permite recibir un único parámetro Dimension.

• Locale getLocale(); Devuelve el valor Locale del applet si este fue establecido. • void setStub( AppletStub s ); Establece el stub de este applet.

2.3.3.8 Soporte multimedia

La clase Applet también incluye métodos para trabajar con imágenes y archivos de sonido de Internet mediante la utilización de URLs. Para ello implementa los métodos:

• Image getImage(URL u, String s); Obtiene una imagen de un URL u que será absoluto si no se especifica una ruta relativa s.

• AudioClip getAudioClip(URL u, String s); Obtiene un clip de sonido de un URL u que será absoluto si no se especifica una ruta relativa s.

26

• void play(URL ur1, String name); Ejecuta directamente un archivo de sonido de un URL u que será absoluto si no se especifica una ruta relativa s.

• static audioClip newAudioClip(URL u); Obtiene un nuevo archivo de sonido del URL u.

Mediante el uso adecuado de varios de estos métodos se pueden combinar sonidos e imágenes para conseguir efectos espectaculares.

2.4. EJEMPLO DE CONSTRUCCIÓN DE UN APPLET

2.4.1 Código

Para crear un applet normalmente será necesario importar al menos las bibliotecas java.awt.* y java.applet.*.

La clase que represente al applet se debe declarar como una subclase de la clase Applet, para poder sobrescribir los métodos de la clase Applet.

Siempre conviene sobrescribir al menos el método paint() que será llamado por los navegadores que soporten applets para mostrarles por pantalla.

Vamos a construir un applet denominada AppletDiagonal que simplemente dibuje una línea diagonal. Un posible código sería:

import java.awt.*; import java.applet.*; public class AppletDiagonal extends Applet { public void paint(Graphics g) { g.setColor( Color.red ); g.drawLine(0, 0, getWidth(), getHeight() ); } }

A continuación se describe el funcionamiento de este código:

1. El método paint() recibe un objeto de la clase Graphics. La clase Graphics, incluida en el paquete AWT, contiene métodos para mostrar varios tipos de gráficos.

2. Mediante el método setColor() de la clase Graphics se establece el color de primer plano a rojo, que es uno de los colores predefinidos de la clase Color.

3. Por último, mediante drawLine() se dibuja una línea dadas las coordenadas de su esquina superior izquierda y de la inferior derecha. En este caso se indican la esquina superior izquierda del applet mediante las coordenadas (0,0), y la esquina inferior derecha se obtiene mediante dos métodos de la clase Dimension (getWidth(), getHeight() ).

2.4.2 Ejecución

Para ejecutar el applet, una vez compilado el archivo, se introduce la llamada al applet en una página Web (por ejemplo AppletDiagonal.htm), introduciendo entre su código HTML lo siguiente:

27

<applet code="AppletDiagonal.class" width=200 height=200>

</applet>

Cuando se cargue esta página Web en un navegador compatible con Java o mediante el visualizador de applets (appletviewer) se verá algo como:

Fig. 4.1: Applet "Línea"

Se podría dibujar un rectángulo con cambiar la línea de código de drawLine() por otra que llamase al método drawRect():

g.drawRect(10, 10, r.width –20, r.height –20);

2.4.3 Creación de una aplicación que utilice un applet (AWT)

A continuación usaremos AWT para crear una aplicación que de un resultado igual que la ejecución del "applet Línea". Será una aplicación que creará un Frame de AWT para incluir el applet que ya fue creado.

El main() de la aplicación sólo creará un objeto de este tipo (indicándole altura y anchura, como hacíamos en el applet mediante los parámetros de la etiqueta HTML).

El código fuente de la aplicación sería el siguiente:

import java.awt.*; import java.awt.event.*; class FrameLinea extends Frame { private AppletDiagonal unApplet; // Se mostrará public static void main( String[] s ) { new FrameLinea( 200, 230 ); }

28

public FrameLinea( int ancho, int largo ) { super(); // Constructor de Component // Se añade un oyente que cerrara la aplicación addWindowListener( new OyenteLinea() ); // Se crea una applet de diagonal unApplet=new AppletDiagonal(); unApplet.init(); unApplet.start(); // Se mete la applet en frame add( unApplet ); setSize(ancho,largo); // ajusta frame setVisible(true); // muestra frame } // Clase anidada class OyenteLinea extends WindowAdapter { // Sobreescribo el método de "cuando se cierra ventana" public void windowClosing(WindowEvent e) { unApplet.stop(); unApplet.destroy(); System.exit(0); } } }

Se crea un Frame en el que se incluye el applet unApplet que será de la clase AppletDiagonal, creado anteriormente.

Lo que hace la aplicación es crear un oyente de la clase OyenteLinea, que será el encargado de capturar el evento de cerrar la ventana del Frame.

En el constructor se inicia el applet (init() y start()) y se añade al Frame mediante el método add() de la clase Container (Frame es hija de Container).

Por último se establece el tamaño del Frame (recibido por parámetro) mediante setSize() y por último se muestra el Frame que ya tiene en su interior el applet (setVisible()).

Cuando se cierra la ventana, el OyenteLinea se encarga de cerrar el applet, mediante stop() y destroy(), y de finalizar la aplicación mediante System.exit().

2.4.4 Creación de una aplicación que utilice el applet (Swing)

Esta misma aplicación se puede crear utilizando Swing con solo cambiar las siguientes cosas:

1. Se debe incluir la biblioteca de Swing:

import javax.swing.*;

2. Se deben cambiar los nombres de la clase Frame de AWT por la clase JFrame de Swing.

29

3. Se crea un contentPane mediante un objeto JPanel, justo antes de llamar al oyente:

setContentPane( new JPanel() );

4. Para añadir el applet se debe agregar al contentPane:

getContentPane().add( unaApplet );

2.5. Programación del Puerto Paralelo

El puerto paralelo se apega al estándar IEEE 1284 liberado en 1994 y que define 4 modos de operación soportados aún en la actualidad: 1. Puerto Paralelo Estándar (SPP) 2. Puerto Paralelo PS/2 (Bidireccional) 3. Puerto Paralelo Mejorado (EPP) 4. Puerto Paralelo con Capacidades Extendidas (ECP). La mayoría de las computadoras personales recientes, tanto de escritorio como portátiles, presentan por omisión una configuración del puerto paralelo en dos direcciones de datos (bidireccional) para cualquier sistema operativo. Los sistemas operativos menos recientes, hablando de Windows 98 y anteriores, también son capaces de soportar este tipo de esquema para recibir y enviar datos por el puerto de impresión, siempre y cuando se configure manualmente dicha característica, preferentemente desde el SETUP. En esta sección se discutirá el modo de operación bidireccional. Para el análisis mostrado se consideran dos vertientes: la programación del puerto bajo el modo MS –DOS (Microsoft Disk Operating System – Sistema Operativo en Disco), y como segunda derivación, la programación en Modo Windows. En ambos casos se revisan interfaces unidireccionales y bidireccionales. 2.5.1 Programación del Puerto Paralelo en Modo MS - DOS. El modo MS- DOS es válido en Windows 98 y versiones anteriores (Windows 95, Windows 3.1, etc.). En esta condición es posible escribir directamente a los registros del puerto. 2.5.1.1 Puerto Paralelo Unidireccional. Considerando el modo de una sola dirección, comúnmente llamado Puerto Paralelo Estándar (SPP), existen tres direcciones consecutivas asociadas con un puerto paralelo; estas direcciones pertenecen al registro de datos (Data Register), el registro de estado (Status Register) y el registro de control (Control Register). Se le denomina dirección base a la que indica la propia del registro de datos, por lo general 0x378; así se tendría para el registro de estado la dirección inmediata siguiente 0x379 y para el registro de control la dirección 0x37A Existen alternativas diferentes para encontrar la dirección de los puertos, debido a que puede cambiar dependiendo de la arquitectura y organización interna de la PC. Es posible acceder directamente al panel de control de Windows y verificar el sistema; dentro de los recursos hardware se encuentra el administrador de dispositivos. El puerto paralelo se utiliza para la conexión de impresoras, por lo que aparecen etiquetados como LPT1, LPT2, LPT3 ó LPT4, según las características de la PC.

30

Es importante recordar que en el modo estándar, el puerto de datos sólo es de salida, de ahí que se le conozca como unidireccional, y es de 8 bits. El puerto de estado es de sólo entrada con 5 bits referidos en el conector y el propio de control tiene 4 bits de sólo salida. En resumen, tenemos que en las portátiles, se tiende a encaminar todos los periféricos hacia USB, eliminando en algunos casos el puerto paralelo y el puerto de juegos; así como la unidad de disco flexible, obligando a que ésta sea externa, o bien, utilizar la alternativa del Disco Compacto de este modo se tienen 12 líneas de salida (de las cuales, 3 son de tipo activa bajo) y sólo 5 de entrada (con una sola línea de tipo activo bajo), tal y como se aprecia en la figura 1.a. El puerto paralelo utiliza un conector hembra clase D de 25 pines (DB-25), definido como TIPO A por el estándar IEEE 1284 En las figuras 1.a y 1.b, se aprecia la distribución física de los pines en el conector DB-25. Para fines de análisis, se considera que los tres registros del puerto son de 8 bits, por lo que se tiene un orden significativo que es necesario respetar cuando se forma una palabra de configuración, por ejemplo, en el caso del registro de estado, se tiene disponible a partir del bit 4 y hasta el bit 8 (S7, S6, S5, S4, S3), los demás están comprometidos o reservados para otros propósitos. De acuerdo al diagrama interno aproximado mostrado en la figura 1.b, el bit más significativo del registro de estado (S7), trabaja con lógica negativa y está físicamente ubicado en el pin 11 del conector. Si se requiere leer una palabra de entrada a través de este registro, es importante considerar con qué lógica funciona cada pin.

El registro de control, para fines prácticos en el diseño de interfaces en modo estándar, es sólo de salida y utiliza los primeros cuatro bits (C3, C2, C1, C0) del registro, los restantes cuatro están reservados. En este registro, los bits C3, C1 y C0, trabajan con lógica invertida y están localizados físicamente en los pines 17, 14 y 1 del conector. Como ya se comentó con anterioridad, los ocho bits del registro de datos se utilizan sólo como salidas y todos trabajan con lógica positiva, ubicándose en orden significativo del pin 2 al pin 9 del conector, tal y como se aprecia en la figura 1.a. El siguiente programa escrito en Lenguaje C, muestra de manera sencilla cómo se envían y reciben datos. El circuito secuenciador de la figura 2 se activa con el botón externo conectado al bit 4 del registro de estado (S3), ubicado físicamente en el pin 15 del conector.

31

/*Se incluyen las bibliotecas generales, No se utilizan todas en este ejemplo*/ #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> void main(void) {

int entrada; /*Datos de la secuencia, en decimal*/ int leds[9] = {0,1,2,4,8,16,32,64,128}; int i; while(1) { entrada = inportb(0x379); /*Si el bit S3 está en 0, el botón está presionado*/ if (((entrada)&0x08)==0) {

for(i = 0; i < 9; i++) { /*Recorre uno a uno los bits de izquierda a derecha*/

outport(0x378,leds[i]); sleep(1);

} } else outportb(0x378,0x00); /*Si el botón no es presionado, los LEDs se apagan*/ }

} Las conexiones básicas requieren los pines 2, 3, 4, 5, 6, 7, 8 y 9 del conector como D0, D1, D2, D3, D4, D5, D6 Y D7 del registro de datos, por lo que se conectarán leds tal y como lo indica el diagrama siguiente (figura 2). Para la conexión del botón de activación se utilizará el pin 15 del conector. Se recomienda utilizar una fuente externa de 5 volts y acoplar las tierras.

32

Como ya se indicó con anterioridad, este programa sólo funciona en modo MS- DOS. Para que funcione también sobre sistemas operativos superiores a Windows 98, es necesaria una biblioteca de enlace dinámico (dll) que declare nuevas funciones para acceder al puerto paralelo, esta cuestión se detallará más tarde en este mismo documento. 2.5.1.2 Puerto Paralelo Bidireccional. En algunas aplicaciones prácticas se requieren más líneas de entrada que las disponibles en el puerto de estado; por ejemplo, leer los 8 bits de un convertidor analógico – digital paralelo o interactuar con una pantalla de LCD. Dado que las entradas del puerto de estado están restringidas a sólo 5 bits, es necesario adecuar el programa escrito hacia una lógica de multiplexaje que lea un dato de 4 bits, se almacene en una localidad de memoria y después de un tiempo se lea la otra parte del dato, pensando en una entrada de 8 bits ó más. Es admisible configurar el puerto de datos para que sus ocho pines puedan ser también entradas. Esto se logra accediendo al puerto de control y cambiando el bit número 6 del registro de un estado natural bajo a un estado alto. Cuando C5 está a 0 lógico, las 8 líneas del puerto de datos son salidas y cuando C5 está a 1 lógico, se comportan como entradas. La figura 3, muestra la disposición física de los pines del registro del puerto de control. Considerando una dirección base 378H para el puerto de datos, se lista el siguiente fragmento en Lenguaje C para explicar de manera más concreta la idea anterior. {

unsigned int Valor, temp; outportb(0x37A, 0x20); Valor=inport(0x378); printf ("Valor Leído: %u \n", Valor); getch();

} Obsérvese que en la primera instrucción outportb(0x37A, 0x20) se escribe al puerto de control con la dirección 0x37A un valor hexadecimal 0x20, traducido a binario de 8 bits como 00100000, especificando que el bit número 6 se establece a un nivel lógico alto por lo que el puerto de datos (0x378) está configurado como entrada. En la siguiente instrucción Valor=inport(0x378) se leen las 8 líneas de datos y se asignan a una variable sin signo previamente definida. Como ejemplo práctico, se considera un ADC0804 supervisado a través del puerto paralelo en modo bidireccional, como lo expone el diagrama de la figura 4. En este caso no se expone a detalle el funcionamiento del convertidor por lo que se recomienda consultar la hoja de especificaciones del dispositivo. Para este diseño en particular, la entrada analógica a convertir proviene directamente de una resistencia variable; sin embargo, ésta se puede sustituir (con las adecuaciones necesarias) por algún sensor, por ejemplo, un dispositivo LM35 para concebir un termómetro digital.

33

Para las señales de control WR y RD, se consideran los bits 2 y 0, respectivamente del puerto de control (0x37A). Como ya se mencionó, C5 es el bit que permite configurar el puerto de datos como entrada o como salida, por lo que no tiene un pin exterior. En el programa en Lenguaje C listado a continuación, se decidió utilizar C2 para Write y C0 para Read del ADC. Físicamente, y con referencia a la figura 3, C2 está asignado al pin 16 del conector y C0 al pin 1 del mismo. Nótese que el hardware interno del puerto de control especifica que el pin 1 del conector tiene lógica negativa, por lo que se debe considerar este aspecto en el momento de generar las señales de escritura y lectura. Para leer el bit correspondiente a la señal INTR que indica el fin de la conversión del ADC, se utiliza el bit S3 del puerto de estado (0x379), ubicado en el pin 15 del conector. Para capturar un dato convertido, se predispone utilizar los 8 bits del puerto de datos (0x378) conectados de manera tradicional del menos significativo al más significativo; en otras palabras, DB0 corresponderá al D0 del puerto (pin 2), DB1 a D1 (pin 3) y así, sucesivamente, hasta DB7 que corresponderá a D7 (pin 9). Al principio del programa listado se solicita el número de muestras a convertir (cuántas veces se repetirá el programa). Es una manera sencilla de terminar el programa después de “n” lecturas. También es posible asignar una tecla para salir. Se recomiendan pocas muestras cuando el delay es de un valor alto. /* ADC0804 por el puerto paralelo 8-bits utilizando modo bidireccional (ECP).*/ #include <stdio.h> #include <dos.h> main() {

unsigned int pdatos, pestado, pcontrol, temp, dato_leido; int contador, muestra; pdatos=0x378; pestado=pdatos+1; pcontrol=pdatos+2;

34

/*permite restaurar el valor original del puerto*/ temp=inportb(pcontrol); clrscr(); /* "muestra" indica el número de lecturas que deseas realizar*/ for (muestra=0; muestra<50; muestra++) {

/* Comienza la conversión poniendo en bajo "write" y manteniendo C5 en alto para la bidireccionalidad. Posteriormente pondremos en alto "write" para eshabilitarlo; así se genera el pulso negativo para accionar "write"*/ outportb(pcontrol, 0x20); delay(10000); outportb(pcontrol, 0x24); /* Espera hasta que la conversión haya concluido recibiendo el bit INTR proveniente del ADC*/ /* "contador" permite terminar el programa después de 256 ciclos si no se ha conectado el ADC al puerto*/ contador=0; do { contador++; } while (((inportb(pestado) & 0x08)==0) && (contador!=256)); if (contador==256) printf ("No hay convertidor!!!\n"); else { delay(10000); /*Es posible omitir el retardo*/ outportb(pcontrol, 0x25); delay(10000); dato_leido=inportb(pdatos); outportb(pcontrol, 0x24); clrscr(); printf ("ADC value:%i",dato_leido); /* Únicamente espera por una tecla, una vez que concluyó el programa*/ getch();

} }

/*restaura puerto de control a su valor original*/ outportb(pcontrol, temp); return 0;

} De igual forma que en el modo unidireccional, este código funciona correctamente sólo en modo MS-DOS. 2.5.2. Programación del Puerto Paralelo en Modo Windows Windows NT, 2000 y XP no permiten manejar el puerto paralelo en modo MS-DOS, como sucedía con las versiones anteriores de este mismo sistema operativo. Para solucionar este inconveniente, dando acceso a puerto en cualquier versión de Windows (en modo Windows) se requieren bibliotecas de enlace dinámico (dll) que se compilan en algún lenguaje de alto nivel que las soporte y éstas gestionan ante el sistema operativo el manejo del puerto.

35

Una biblioteca dll se diseña, por lo general, en C++ y se utiliza en lenguajes de alto nivel como el mismo C++, Delphi o Java, entre otros. A continuación tenemos el empleo de un archivo dll para crear interfaces en Visual Basic 6.0. Ninguno de los compiladores para Windows ha incluido un componente para el manejo de puertos en forma general sólo para comunicaciones e impresión. La ventaja que tiene Visual. Basic sobre otros lenguajes es la facilidad de su entorno. La biblioteca io.dll se descarga gratuitamente de http://www.geekhideout.com y se copia al directorio ubicado en C:/Windows/System32/. En Internet existen disponibles otras bibliotecas similares; la que aquí se indica se ha probado con éxito en todos los diseños presentados en esta sección. El siguiente paso consiste en crear un proyecto estándar en Visual Basic y adicionar un módulo que incluya los prototipos de la dll para Visual Basic, listados en la misma página web de donde se descargó la dll (referirse a la Tabla 1).

Tabla 1. Prototipos de io.dll a escribirse en un módulo de Visual Basic. Public Declare Sub PortOut Lib "IO.DLL" (ByVal Port As Integer, ByVal Data As Byte) Public Declare Sub PortWordOut Lib "IO.DLL" (ByVal Port As Integer, ByVal Data As Integer) Public Declare Sub PortDWordOut Lib "IO.DLL" (ByVal Port As Integer, ByVal Data As Long) Public Declare Function PortIn Lib "IO.DLL" (ByVal Port As Integer) As Byte Public Declare Function PortWordIn Lib "IO.DLL" (ByVal Port As Integer) As Integer Public Declare Function PortDWordIn Lib "IO.DLL" (ByVal Port As Integer) As Long Public Declare Sub SetPortBit Lib "IO.DLL" (ByVal Port As Integer, ByVal Bit As Byte) Public Declare Sub ClrPortBit Lib "IO.DLL" (ByVal Port As Integer, ByVal Bit As Byte) Public Declare Sub NotPortBit Lib "IO.DLL" (ByVal Port As Integer, ByVal Bit As Byte) Public Declare Function GetPortBit Lib "IO.DLL" (ByVal Port As Integer, ByVal Bit As Byte) As Boolean Public Declare Function RightPortShift Lib "IO.DLL" (ByVal Port As Integer, ByVal Val As Boolean) As Boolean Public Declare Function LeftPortShift Lib "IO.DLL" (ByVal Port As Integer, ByVal Val As Boolean) As Boolean Public Declare Function IsDriverInstalled Lib "IO.DLL" () As Boolean Lo anterior se logra activando la opción del menú Proyecto y seleccionando Adicionar Módulo. En el espacio de edición del mismo módulo se escriben los prototipos de la Tabla 1. Para salvar el proyecto completo se requiere nombrar primeramente la Forma, después el Proyecto y finalmente el Módulo. Obsérvese la pantalla de la figura 1. Básicamente y en correspondencia con los prototipos del dll, se tienen 13 funciones para manejo del puerto paralelo, nos enfocaremos sólo a cuatro: PortOut, PortIn, SetPortBit y ClrPortBit. Las dos primeras permiten enviar y recibir respectivamente un byte por puerto indicado. SetPortBit y ClrPortBit, manipulan sólo un bit del registro implicado, ya sea para establecerlo o para limpiarlo.

36

Figura 5. Pantalla de diseño en Visual Basic 6.0, adicionando un módulo.

2.5.2.1. Programación Unidireccional en Visual Basic Considerando el mismo ejemplo en modo estándar propuesto por el diagrama de la figura 2; en Visual Basic no se cuentan con las instrucciones Sleep ni Delay, propias de C, por lo que para temporizar se requiere un Timer sincronizado en milisegundos. La pantalla mostrada en la figura 6, es la que se utilizó para concretar el secuenciador de ejemplo. Una vez creado el proyecto, y después de haber adicionado el módulo de declaraciones de la dll, se procede a colocar una caja de texto (TextBox) que por omisión llevará el nombre Text1. Después se colocará debajo de la caja una etiqueta (Label) que por default está referida como Label1. Dentro de las propiedades de este objeto se debe buscar la que se refiere a Caption (Texto de etiqueta) y escribir “Valor enviado al puerto”. Se coloca un botón de acción (CommandBotton) que por omisión se llamará Command1. Dentro de sus propiedades, y en Caption, se escribe “Inicio”. Por último, se inserta un Timer, con el nombre Timer1. Este objeto sólo se visualiza en tiempo de diseño, es decir, en la aplicación ejecutable no se verá.

Figura 6. Pantalla en tiempo de diseño para el secuenciador.

37

En el editor de la forma (View Code) se copia el código siguiente y se presiona el botón Run para ejecutar la aplicación. Option Explicit 'Programa que envía y recibe datos del puerto Dim i As Integer 'paralelo de la PC. Se utiliza io.dll. Dim ValorIn As Byte Dim Compara As Byte Private Sub Form_Load() 'no hagas nada, cuando se abra la aplicación End Sub Private Sub Command1_Click() 'Cuando se presione el botón "Inicia" While (1) 'se valida el comienzo del programita Comienza Wend End Sub Sub Comienza() ValorIn = PortIn(&H379) 'Lee puerto de datos Compara = ValorIn And &H8 'Comprobamos que s3 esté a cero (Botón presionado) If Compara = 0 Then escribe_pto 'Rutina del secuenciador Else Text1.Text = "Presiona el botón" 'No está presionado el botón End If End Sub Sub escribe_pto() PortOut &H378, 0 For i = 0 To 7 'Inicia ciclo para establecer (poner a 1) el bit Espera 0.5 'correspondiente SetPortBit &H378, i Text1.Text = 2 ^ i Next i End Sub Sub Espera(t As Double) 'Rutina de tiempo Timer1.Interval = t * 1000 'Milisegundos Timer1.Enabled = True 'Habilitamos timer Do While Timer1.Enabled DoEvents Loop End Sub Private Sub Timer1_Timer() 'Habilitación del timer1, relacionada Timer1.Enabled = False 'con la rutina Espera End Sub Private Sub Form_OKClick() 'Salir de la aplicación App.End End Sub El siguiente código, diseñado para una interfaz en modo unidireccional, permite enviar un dato escrito en la caja de texto correspondiente a “Enviar dato” directamente al puerto de datos (0x378). También es posible leer un dato del puerto de estado (0x379) en la caja de texto correspondiente a “Leer dato”. La pantalla en tiempo de diseño se muestra en la figura 7.

38

Option Explicit 'Programa que envía y recibe datos del puerto Private Sub Form_Load() 'Condiciones al momento de abrir la aplicación PortOut &H378, 0 'Limpia el puerto de datos End Sub Private Sub Command1_Click() 'Al hacer click en el botón Command1 ("Envía") Dim dato_out As Variant 'Dato a escribir en la caja de texto en decimal dato_out = datoout.Text PortOut &H378, dato_out 'Limpia puerto End Sub Private Sub Command2_Click() 'Al hacer click en el botón Command2 ("Lee Puerto") Dim ValorIn As Byte 'El resultado se asignará a la variable ValorIn ValorIn = PortIn(&H379) dato_in.Text = ValorIn & " , está en decimal" End Sub Private Sub Form_OKClick() 'Salir de la aplicación App.End End Sub

Figura 7. Interfaz unidireccional de propósito general.

2.5.2.2 Programación Bidireccional en Visual Basic Para la programación bidireccional del puerto de datos se sigue la misma lógica descrita en los programas en Lenguaje C anteriores, es decir, se establece el bit 6 (C5) del puerto de control para que el registro de datos acepte un byte proveniente del exterior y se limpia el mismo bit si se desea que el registro de datos sea sólo de salida. Retomando la interfaz para monitorear el ADC0804, algunos autores desestiman utilizar todas las señales del ADC. Con una frecuencia mucho menor que los 8KHz naturales del dispositivo, es posible obtener circuitos más simples como el denominado Free Running que propone utilizar un inversor hacia RD de la señal WR, obligando a que RD siempre sea el complemento de WR y viceversa (ver figura 8). El inversor se puede implementar dentro de código, y dado que la frecuencia de trabajo para reportar lecturas se considera lenta, es posible omitir la espera de la respuesta de la señal INTR.

39

Se recomienda reestablecer el puerto de control a su estado original, antes de salir de la aplicación. La preparación del puerto desde el SETUP es fundamental para que funcione correctamente este programa.

Figura 8. Conexión Free Runnig para el ADC0804.

El siguiente código en Visual Basic muestra la solución propuesta. Obsérvese que sólo se envía la señal de reloj por el puerto de control, a la vez que se reciben de forma paralela los 8 bits del ADC por el puerto de datos de la PC. Option Explicit 'Programa que recibe datos de un ADC0804 Dim DatoPuerto As Byte 'con conexiones mínimas Free Running Dim Temporal As Byte 'Modo del puerto paralelo: ECP (Bidireccional) Private Sub Form_Load() 'Limpia puerto de datos al cargar la aplicación PortOut &H378, 0 End Sub Private Sub Command1_Click() 'Con el botón "Inicio" Temporal = PortIn(&H37A) 'Almacena el valor del puerto de control While (1) 'se valida el comienzo de la adquisición Comienza Wend End Sub Private Sub Command2_Click() 'Botón "Restaurar Puerto" PortOut &H37A, Temporal 'Regresa a la configuración original pto. de control PortOut &H378, 0 'Limpia nuevamente el puerto de datos; detiene programa End Sub Sub Comienza() PortOut &H37A, &H20 'Configura C5, bidireccional y genera flanco negativo Espera 0.5 'Rutina de espera (1/2 segundo) PortOut &H37A, &H24 'Mantiene bidireccionalidad y genera flanco positivo Espera 0.5 DatoPuerto = PortIn(&H378) 'Lee puerto de datos y escribe equivalente en Volts Text1.Text = DatoPuerto * 0.0196 & " Volts" '255 binario = 5 Volts analógicos End Sub Sub Espera(t As Double) 'Rutina de tiempo Timer1.Interval = t * 1000 'Milisegundos

40

Timer1.Enabled = True 'Habilitamos timer Do While Timer1.Enabled DoEvents Loop End Sub Private Sub Timer1_Timer() 'Habilitación del timer1, relacionada Timer1.Enabled = False 'con la rutina Espera End Sub Private Sub Form_OKClick() 'Salir de la aplicación App.End End Sub Las siguientes figuras, 9a y 9b, muestran de manera respectiva, las pantallas correspondientes al tiempo de diseño y al tiempo de ejecución de la interfaz en dos direcciones para el ADC0804. Particularmente en 9b se aprecia el resultado arrojado por la aplicación para un dato de 25510 equivalente a 5 Volts, enviado por el ADC trabajando sobre Windows XP.

2.5.2.3 Programación del Puerto Paralelo en Java La programación del puerto paralelo en Java se vuelve muy sencilla una vez que se disponen de clases o paquetes diseñados para este fin. Esta es la gran ventaja de la programación orientada a objetos en la cual, si alguien ya diseñó una clase que contemple los métodos para lectura o escritura del puerto paralelo, solo hay que utilizarla creando un objeto de dicha clase. De este modo se cuenta con varias clases de comunicación con el puerto paralelo, una de ellas es la clase ParallelPort perteneciente al package parport el cual se encuentra fácilmente en la red con el código disponible para las adecuaciones que se crean convenientes si esto es necesario. De modo que solo hay que importar el paquete parport dentro del proyecto y crear un objeto de la clase correspondiente, a través del cual se puede escribir o leer del puerto paralelo. A continuación se muestra el código en Java que corresponde a la clase ParralellPort package parport;

41

public class ParallelPort { /** La direccion base del puerto (e.g. 0x378 es la direccion base para LPT1) */ private int portBase; /** Para construir un objeto de la clase ParallelPort, * se necesita la direccion base del puerto */ public ParallelPort (int portBase) { this.portBase = portBase; } /** Lee un byte de los pines del registro de ESTADO del puerto paralelo. * * El byte leido contiene 5 bits validos, que corresponden a los 5 bits de * entrada del registro de ESTADO del puerto paralelo (El registro de ESTADO * se encuentra en "portBase + 1", e.g. la direccion del registro de ESTADO * para LPT1 es 0x379). * * La siguiente tabla muestra el contenido del byte: * * Bit | Pin # | Estado de impresora | Invertido * -----+-------+----------------------+----------- * 7 | ~11 | Busy | Si * 6 | 10 | Acknowledge | * 5 | 12 | Out of paper | * 4 | 13 | Selected | * 3 | 15 | I/O error | * * Note que el Pin 11 esta invertido, esto significa que una entrada "Alto" * en el pin es un 0 en el bit 7 y una entrada "Bajo" significa 1 en bit 7 */ public int read () { return ParallelPort.readOneByte (this.portBase+1); } /** Escribe un byte al registro de DATOS del puerto paralelo. * El byte es escrito a los pines de DATOS del puerto. Los pines de datos se * localizan en la direccion base del puerto (e.g. La direccion del registro * de DATOS para LPT1 es 0x378). * * La siguiente tabla muestra como es escrito el byte * * Bit | Pin # | DATOS de impresora * -----+-------+-------------- * 7 | 9 | DATA 7 * 6 | 8 | DATA 6 * 5 | 7 | DATA 5 * 4 | 6 | DATA 4 * 3 | 5 | DATA 3 * 2 | 4 | DATA 2 * 1 | 3 | DATA 1 * 0 | 2 | DATA 0 */ public void write (int oneByte) { ParallelPort.writeOneByte (this.portBase, oneByte); }

42

/** Lee un byte de la direccion especificada. * (normalmente la direccion es el registro de ESTADO del puerto) */ public static native int readOneByte (int address); /** Escribe un byte a la direccion especificada * (normalmente la direccion es el registro de DATOS del puerto) */ public static native void writeOneByte (int address, int oneByte); static { System.loadLibrary("parport"); } } Una manera de utilizar esta en un programa se muestra a continuación en el siguiente segmento: import parport.ParallelPort;

. . .

public void run( ) { try { int valorDeSalida;

ParallelPort lpt1 = new ParallelPort(0x378); //Puerto de datos valorDeSalida = 255; // los 8 bits en 1

lpt1.write(valorDeSalida); // escribe byte al puerto paralelo 2.6. Driver UserPort.sys UserPort.sys es un driver del kernell para Windows 2000/NT que proporciona a los programas en modo usuario acceso a los puertos de entrada/salida. Esto permite a los programas ejecutables normales accesar al hardware directamente como se hace en sistemas operativos como Windows 95/ 98/Me. Este driver no funciona en Windows 95/98/Me y no hay necesidad de utilizarlo en estos sistemas operativos. El driver puede ser utilizado para los siguientes propósitos:

a) Ejecutar software en Windows 2000/NT/XP que normalmente solo funciona en Windows 95/98/Me. b) Accesar fácilmente a hardware conectado al puerto paralelo y otros puertos de entrada/salida mediante programas ejecutándose en modo usuario.

Se puede utilizar este driver para operaciones de escritura y lectura del puerto paralelo, dándole el acceso al archivo UserPort.sys y efectuando otros pasos como se ejemplifica en el código en C++ de programa UserPort.exe, el cual se anexa. Si se desea puede hacer simplemente lo siguiente: Copiar UserPort.SYS a %WINDIR%\SYSTEM32\DRIVERS y ejecutar el programa UserPort.exe que permite utilizar este driver para abrir los puertos que usted indique. De este modo puede ejecutar los programas que normalmente solo funcionarían en Windows 95/98/Me y que utilizan los puertos de entrada/salida.

43

Descripción técnica: El driver da a los programas en modo usuario acceso a los puertos seleccionados cambiando el IOPM (I/O Permission Map) de los procesadores x86 la figura muestra el funcionamiento del driver, para una descripción detallada consulte los manuales de procesadores INTEL

El tamaño original del TSS (Task State Segment) es 0x20ab y el driver cambia a 0x2135. El offset por defecto del IOPM es 0x20ad. Y este valor es reescrito por el Sistema Operativo en cada cambio de tarea a ejecutar. El offset del IOPM debe por lo tanto ser cambiado con la función Ke386IoSetAccessProcess, la cual establece el offset del IOPM a 0x88. El AllProcessesIOPM es escrito a 0x20ad porque este es el valor por defecto para todos los procesos y el ThroughCreateFileIOPM es escrito con 0x88 porque la función Ke386IoSetAccessProcess establece el offset del IOPM a 0x88. La función Ke386IoSetAccessProcess es llamada cuando un programa en modo usuario abre el archivo “\\.\UserPort”. El driver carga los dos IOPMs de: HKEY_LOCAL_MACHINE\Software\UserPort\AllProcessesIOPM HKEY_LOCAL_MACHINE\Software\UserPort\ThroughCreateFileIOPM Nota El driver esta influenciado e inspirado en un artículo escrito por Dale Roberts 8/30/95 publicado en mayo de 1996 por el Dr. Dobbs Journal, veáse www.ddj.com. 2.7. La API Genuts La API Genuts proporciona un marco para desarrollo de juegos de manera consistente en Java. Contiene una biblioteca con clases que principalmente fueron desarrolladas para la concepción de juegos, incluyendo funciones para manipulación y detección de colisiones. Adicionalmente, proporciona un nivel de abstracción para algunos juegos relacionados con la API de Java, que incluyen manipulación de imágenes y manejo de eventos. Su objetivo principal es:

• Juegos Web • Teléfonos móviles • PDA’s

La API Genuts es compatible con Java 1.1, Java 2 y Java Me (MID P 1.0).

44

3. ESPECIFICACION DE REQUERIMIENTOS 3.1. Estructura de las tramas de control del robot Al momento de iniciar este análisis se dispone de una interfaz en hardware que se conecta entre el puerto paralelo de la computadora servidor y el robot, esta interfaz proporciona la potencia necesaria a la entrada del robot que consiste de un registro de 8 bits en el cual la parte alta corresponde a las señales que controlan mediante un circuito combinatorio, los movimientos del robot: adelante, atrás, izquierda, derecha, sus posiciones intermedias y alto. Cada bit de la parte baja del registro corresponde a una señal que controla los movimientos independientes del brazo izquierdo, brazo derecho, cabeza con movimiento vertical y cabeza con movimiento horizontal. La aplicación a desarrollar debe ser capaz de enviar desde el cliente la cadena de bits correspondiente al servidor, para que esta se envíe al puerto paralelo y sea recibido por la interfaz de acuerdo al movimiento que se desea que realice el robot. Esta descripción ha sido especificada desde el diseño del robot y se describe en la siguiente tabla:

S1 S2 S3 S4 CONDICION OBSERVACION m0 0 0 0 0 Todos los switches cerrados IMPOSIBLE m1 0 0 0 1 3 switches cerrados IMPOSIBLE m2 0 0 1 0 3 switches cerrados IMPOSIBLE m3 0 0 1 1 2 switches opuestos cerrados IMPOSIBLE m4 0 1 0 0 3 switches cerrados IMPOSIBLE m5 0 1 0 1 Esquina S1 y S3 cerrados VUELTA IZQ - ADELANTE PIVOTE m6 0 1 1 0 Esquina S1 y S4 cerrados VUELTA DER - ADELANTE PIVOTE m7 0 1 1 1 S1 cerrado ADELANTE m8 1 0 0 0 3 switches cerrados IMPOSIBLE m9 1 0 0 1 Esquina S2 y S3 cerrados VUELTA - IZQ ATRÁS PIVOTE m10 1 0 1 0 Esquina S2 y S4 cerrados VUELTA DER - ATRÁS PIVOTE m11 1 0 1 1 S2 cerrado ATRÁS m12 1 1 0 0 2 switches opuestos cerrados IMPOSIBLE m13 1 1 0 1 S3 cerrado VUELTA - IZQ m14 1 1 1 0 S4 cerrado VUELTA - DER m15 1 1 1 1 Todos los switches abiertos POSICION NORMAL

Donde las variables S1, S2, S3, y S4 corresponden a las zonas que abarcaría un Joystick con la siguiente descripción:

45

Las condiciones descritas las interpretan 4 motores con los que cuenta el robot para desarrollar su dinámica de movimiento. Los estados en los que se encuentren operando estos motores de manera combinada generan las siguientes funciones:

MOTOR FUNCION 0 0 DETENIDO0 1 ATRÁS 1 0 ADELANTE1 1 DETENIDO

De modo que la tabla inicial puede ser expresada como se muestra a continuación:

S1 S2 S3 S4 MDA MDB MIA MIB m0 0 0 0 0 * * * * m1 0 0 0 1 * * * * m2 0 0 1 0 * * * * m3 0 0 1 1 * * * * m4 0 1 0 0 * * * * m5 0 1 0 1 1 0 A A m6 0 1 1 0 B B 1 0 m7 0 1 1 1 1 0 1 0 m8 1 0 0 0 * * * * m9 1 0 0 1 C C 0 1 m10 1 0 1 0 0 1 D D m11 1 0 1 1 0 1 0 1 m12 1 1 0 0 * * * * m13 1 1 0 1 1 0 0 1 m14 1 1 1 0 0 1 1 0 m15 1 1 1 1 0 0 0 0

MD = MOTOR DERECHO MI = MOTOR IZQUIERDO

De este modo las cadenas de bits que se deben enviar al puerto paralelo para los diferentes movimientos son:

VUELTA IZQ – ADELANTE PIVOTE 1 0 A A

VUELTA DER – ADELANTE PIVOTE B B 1 0

ADELANTE 1 0 1 0

VUELTA – IZQ ATRÁS PIVOTE C C 0 1

VUELTA DER – ATRÁS PIVOTE 0 1 D D

ATRÁS 0 1 0 1

VUELTA – IZQ 1 0 0 1

VUELTA – DER 0 1 1 0

POSICION NORMAL 0 0 0 0

(A, B, C, D) ∈ {0, 1}

46

Además de las señales independientes para controlar los brazos y cabeza del robot como ya se mencionó anteriormente. La cadena enviada entre cliente y servidor puede tener una estructura diferente, pero se requiere que la cadena enviada a la interfaz con el robot cumpla las especificaciones descritas en las tablas anteriores, dado que la lógica combinatoria del circuito que controla al robot está diseñada en base a estas tablas. 3.2 Comunicación entre cliente y servidor Se requiere que las aplicaciones desarrolladas se ejecuten independientemente de la red de que se disponga, siempre que sea empleado el protocolo TCP/IP. Para ello solo se deben especificar las direcciones IPs de cliente y servidor así como los puertos correspondientes en la aplicación a desarrollar. Se debe tomar como hipótesis que la calidad de servicio de la red está garantizada. Una vez establecida la red su conexión no será interrumpida y los paquetes que se envían o reciben no tendrán pérdidas. 3.3. Plataforma de operación La aplicación debe ejecutarse en ambiente Windows sin restricciones en la versión de que se disponga como Windows 98, Me, 2000, NT o XP. Se resalta la característica de seguridad de Windows 2000, NT y XP que bloquea el acceso a los puertos, ya sea para lectura o escritura por lo que los programas desarrollados deben considerar dicha característica para que esto no sea un problema para los usuarios de la aplicación. 3.4. Velocidad de respuesta La aplicación estará enviando paquetes de datos constantemente y a su vez en versiones posteriores, será capaz de recibir multimedia como puede ser la transmisión de audio y video, por lo que se desea que la aplicación maneje de manera eficiente el envío de paquetes de control sin saturar el canal, para permitir que las aplicaciones futuras funcionen eficientemente. Así se debe minimizar el tamaño y la cantidad de paquetes a enviar. La aplicación debe responder de manera rápida y no consumir demasiados recursos de cómputo. 3.5. Control de programas remotos Se desea la ejecución de programas de manera remota con una terminal o mediante el uso de una aplicación adicional con el fin llevar a cabo la reproducción multimedia en el lado del servidor y tener control desde el cliente. Así también se puede iniciar la ejecución de programas o terminación de estos desde el cliente en cualquier momento y esto debe ser con procesos ligeros que no sobrecarguen los recursos utilizados por cada programa, de modo que se pudiera hacer lenta la ejecución de los programas en el servidor, repercutiendo en un desempeño pobre de las aplicaciones. 3.6. Interfaz gráfica Se desea el uso de un Joystick Virtual y se recomienda el uso de un applet para el diseño del mismo.

47

4. Modelo de Requerimientos 4.1. Modelo de Casos El siguiente diagrama muestra los casos de uso más relevantes del sistema

Estos se describen a continuación: Caso de uso: Enviar Movimiento Precondiciones: La conexión con el servidor se encuentra establecida. Postcondiciones: La conexión con el servidor continúa establecida y el estado del registro de

datos en el servidor se ha actualizado Paso Descripción

1 El usuario mueve el mouse, coloca el puntero dentro del área del joystick y hace click

sobre un punto de la misma, entra arrastrando el puntero de una región a otra o bien habilita o deshabilita una casilla de verificación.

2 El programa cliente redibuja la palanca del joystick creando la apariencia de movimiento hacia el punto sobre el que se coloca el puntero mientras el botón izquierdo del mouse es presionado y se encuentra dentro del área del joystick.

3 El programa cliente reconoce un cambio de región en la que ahora se encuentra colocada la palanca del joystick, o bien reconoce si una casilla de verificación fue habilitada/deshabilitada y envía el paquete de información correspondiente, desde el cliente al servidor; con la instrucción del movimiento a realizar.

4 El programa servidor recibe la instrucción, la interpreta y envía el valor numérico que corresponde al puerto paralelo para ser leído por la interfaz del robot.

48

5 Se repite desde el paso 1 al 4 hasta que el botón izquierdo del mouse es liberado. Si no cambia de región solo se repiten los pasos 1 y 2.

6 Fin de caso de uso Caso de uso: Ejecutar Comando Precondiciones: La conexión con el servidor se encuentra establecida. Postcondiciones: La conexión con el servidor continúa establecida.

Paso Descripción

1 El usuario escribe un comando en la línea de comandos del programa cliente y

presiona Enter o Aceptar. 2 El programa cliente limpia el cuadro de texto, empaqueta el comando y lo envía al

servidor. 3 El programa servidor reconoce que se trata de un comando y lo ejecuta. 4 El programa servidor realiza el caso de uso Enviar mensaje de estatus 5 Fin de caso de uso Caso de uso: Enviar mensaje de estatus Precondiciones: El monitor esta en disponibilidad de aceptar una nueva conexión. Postcondiciones: Se cierra la conexión con el monitor.

Paso Descripción

1 El programa servidor guarda la salida de ejecución del comando ejecutado, se

comunica con el monitor y solicita establecer la conexión. 2 El monitor acepta la conexión. 3 El servidor envía el mensaje con la salida al monitor en la computadora cliente. 4 El programa monitor recibe el mensaje y lo despliega en pantalla. 5 El programa monitor cierra su conexión y queda en posibilidad de recibir otro mensaje. 6 Fin de caso de uso. Caso de uso: Iniciar Monitor Precondiciones: La computadora cliente y la computadora servidor se encuentran en red. Postcondiciones: El monitor esta listo para aceptar cualquier conexión.

Paso Descripción 1 El usuario inicia el servicio de monitoreo haciendo click en aceptar con el número de

puerto correspondiente ya capturado en el cuadro de texto indicado para ese fin. 2 El monitor se encuentra en una condición de aceptar cualquier conexión que se

solicite. 3 Fin de caso de uso.

49

4.2. Modelo de Interfaz No hay navegación entre pantallas. La pantalla de Control Remoto es redibujada para crear la apariencia de movimiento del joystick y su botón Conectar/Desconectar cambia de mensaje de acuerdo al estado en el que se encuentre. 4.2.1 Pantallas en el lado del Cliente:

Pantalla Control Remoto

Permite controlar los movimientos del robot

Pantalla de servicio de monitoreo remoto

Permite visualizar el resultado de ejecución de los programas ejecutándose remotamente, así como los mensajes de lectura del puerto paralelo que detecta el Sensor ejecutándose el Servidor

50

4.2.2. Pantallas en el lado del servidor:

Pantalla de Controlador del Robot

Esta es la aplicación del servidor que se pone en servicio para que se pueda establecer comunicación desde el cliente y que recibe las órdenes del joystick para transmitirlas a la interfaz del robot a través del puerto paralelo principalmente.

Pantalla de Sensor de escritura en el Puerto Paralelo

Permite poner en marcha el servicio de lectura del Puerto paralelo además de monitorear localmente el estado del mismo.

51

4.3. Modelo de Dominio del Problema 4.3.1 Diagramas de Secuencia Los diagramas de Secuencia muestran una interacción ordenada según la secuencia temporal de eventos. El eje vertical representa el tiempo, y en el eje horizontal se colocan los objetos y actores participantes en la interacción, sin un orden prefijado. Cada objeto o actor tiene una línea vertical, y los mensajes se representan mediante flechas entre los distintos objetos. El tiempo fluye de arriba abajo. Los diagramas de secuencia mostrados están simplificados para mostrar las interacciones principales que se desean entre cada objeto. 4.3.1.1 Caso de Uso Enviar Movimiento

Joystick Cliente Servidor LPT1

Se redibuja Joystick en PantallaEl usuario

mueve el Joystick o activa un movimiento Se empaqueta el

moviento a enviar

Se pasa el paquete al objeto Cliente

El cliente envia el paquete por la red al servidor

Se obtiene el dato a escribir en el puerto y se lo pasa LPT1

Se escribe en el puerto paralelo

4.3.1.2 Caso de Uso Enviar Comando

Joystick Cliente Monitor Servidor HiloLanzador

El usuario escribe un comando y presiona enter o enviar

Se empaqueta el comando a enviar

Se pasa el paquete al objeto Cliente

El cliente envia el paquete por la red al servidor

Se inicia servicio de monitoreo

Ejecuta comando

Se envia salida de ejecución del comando o programa

Se crea un hilo para atender petición

52

5. APLICACIÓN DESARROLLADA 5.1. Descripción general El sistema que estamos presentando consta de cuatro programas: dos en el lado del Servidor y dos en el lado del Cliente. Este conjunto de módulos, permite controlar al robot conectado en el puerto paralelo en el lado del servidor, además proporciona acceso a programas instalados en la estación en el lado del Servidor, así como monitoreo mediante una terminal que recibe la salida de los programas ejecutados remotamente; los cuales pueden ser invocados por línea de comandos desde el cliente. Esta terminal de monitoreo también puede recibir los mensajes que son enviados a la misma, desde alguna otra aplicación que los envíe a la dirección y puerto correspondientes, tal como lo hace un programa sensor que lee el registro de estado del puerto paralelo reportando cualquier cambio en este. De este modo se pueden incorporar sensores al robot que reporten alguna condición y esta sea enviada al programa monitor. Una vez establecida la red, conectado el robot al puerto paralelo e instalados cliente y servidor, se tiene la siguiente arquitectura.

Fig V-1

5.2. Diseño conceptual El sistema desarrollado esta conformado por 4 programas independientes pero que se comunican entre si. En el lado del cliente:

1. Joystick 2. Monitor

En el lado del servidor:

3. Controlador 4. Sensor

53

El Programa Joystick se comunica con el Programa Controlador enviándole cadenas que contienen la información a procesar por este último, pudiéndose tratar de instrucciones de control del robot, o bien comandos (programas) a ejecutar. El controlador crea un hilo por cada petición de un comando o programa a ejecutar y las salidas son manejadas por nuevos hilos ya sean resultados de la ejecución o mensajes de error. Se utiliza este esquema con el fin de garantizar que el servidor siga atendiendo nuevas peticiones de manera eficiente, sin bloquearse por algún error en la ejecución del comando. Si no fuera de esta manera se tendría el problema de retardo para el control del robot al estar esperando el mensaje de respuesta generado por cada vez que se envía un comando o señal de control del robot. Existe por ello un programa de monitoreo que recibe las salidas que obtiene cada hilo que es generado por el Servidor, además de recibir el status del programa Sensor. Si hubiera algún problema con la ejecución de un hilo o con el tiempo en que tarda en terminar su ejecución, en el peor caso se puede reiniciar el monitor, sin perder el manejo del robot. Esta idea permite escalar el funcionamiento global de las aplicaciones que pudieran incorporarse y ser invocadas de manera remota y monitoreadas incluso si se agregan sensores enlazados con programas que se comuniquen a su vez con nuevos programas en el modelo cliente/servidor. 5.3. Esquema general de comunicaciones

Fig V-2

Cliente

Monitor

Servidor

Robot

P1

P2

Pi

Hilos generados por cada invocación de programas o comandos desde el cliente

Señales de control del robot

Sensor

54

El programa cliente (Joystick) se comunica con el servidor (Controlador), el cual genera hilos que a su vez se comunican con el Programa Monitor. El programa Servidor envía las señales de control correspondientes al puerto Paralelo, según lo solicite el programa Cliente. El programa Sensor monitorea constantemente el registro de estado por si ha cambiado su valor, el cual puede ser modificado con un voltaje de 0 o 5 Volts directamente en alguno de los pines que corresponden al registro de estado del puerto paralelo. Si este ha cambiado se manda la información correspondiente al programa Monitor. 5.4. Diagramas de Clases 5.4.1 Programa Joystick El Programa Joystick es en si, un llamado a la clase principal (con el método main) la cual instancia diferentes clases y estas a su vez otras tal como se muestra en el diagrama de clases siguiente. Se muestran solo los métodos y atributos más ilustrativos de cada clase, para detalles precisos consultar el código en Java presentado en la sección VI. Los métodos que se ilustran nos dan una idea del funcionamiento de todo el programa.

com.gnuts.gameuiCliente

cliente()envia()cerrarSocket()

LanzadorDeApplet

run()close()

JoystickdirIPTextBoxptoTextBox

Joystick()mueveBola()mouseClicked()mouseEntered()mouseExited()mousePressed()mouseReleased()enviaComando()enviaMovimiento()

Package gnuts.jar

utiliza

crea

utiliza

Fig V-3. Clases del Programa Control Remoto La clase Joystick que es un Applet es instanciada por la clase LanzadorDeApplet, la cual monta el Applet sobre un Frame para poder ser visualizado sin navegador. Además la clase Cliente es la encargada de establecer la comunicación con el Servidor y enviar las instrucciones a ejecutar correspondientes.

55

Se hace uso del paquete gnuts.jar, el cual fue creado para desarrollar juegos para computadora y posee un conjunto muy amplio de clases para estos propósitos incluyendo manejo de audio. En nuestro caso es empleada para el desarrollo del Joystick virtual que controla al robot. Los métodos de esta clase se pueden dividir en aquellos que reconocen una acción del mouse dentro del área del Joystick virtual, el método mueveBola que redibuja la palanca del Joystick apoyado en las clases del paquete gnuts y las clases que se encargan de empaquetar la información para el servidor y solicitar su transmisión. Estos últimos apoyándose en la clase cliente quien es la que establece la comunicación con el servidor y envía los paquetes correspondientes. Si por alguna razón se deseara que esta aplicación sea operada desde un navegador, se cuenta prácticamente con todo el proyecto para que esto sea llevado a cabo, ya que todas las clases visuales son extendidas de un applet y solo faltaría modificar algunos valores para darle la presentación adecuada. 5.4.2. Programa Monitor Consta de las clases que se muestran en el siguiente diagrama, la clase VentanaDeMonitoreo abre un socket para aceptar conexiones del Servidor y recibir las cadenas de mensajes que conforman la salida de los programas ejecutados remotamente, esta es instanciada por la clase LanzadorDeApplet que monta el applet sobre un Frame. Además se utilizan hilos para atender cada uno de los mensajes que son enviados desde el servidor. La Clase VentanaDeMonitoreo crea un hilo que se queda en espera de una nueva conexión por la que se recibirá el mensaje a publicar e inmediatamente se crea un nuevo hilo en espera de una nueva conexión. Al terminar de publicarse la información el hilo es terminado. Esto permite que la aplicación trabaje de manera continua sin bloqueos o saturación por la atención de diversos mensajes.

LanzadorDeApplet

run()close()

VentanaDeMonitoreoptoTextLabeltextArea

ventanaDeMonitoreo()init()main()

HiloM

hiloM()run()

Cada mensaje que llega es atendido por un nuevo hilo

utiliza

crea

Fig V-4. Clase del programa Monitor

56

5.4.3. Programa Controlador Consta de 7 clases como se muestra en la figura siguiente. La clase VentanaControlador que es instanciada por la clase LanzadorDeApplet es quien se encarga de iniciar al controlador, una vez creado un objeto de esta clase, se tiene un proceso al que se conecta el Joystick a través de un socket y por este se reciben los paquetes con las instrucciones o comandos correspondientes, el paquete es analizado y si se trata de un movimiento se hace uso de la clase ParallelPort para escribir el valor correspondiente en el puerto paralelo. Si el paquete recibido corresponde a la ejecución de un comando, entonces se instancia a la clase LanzadorDOS, la cual recibe el comando a ejecutar y a través del programa cmd.exe o command.com (según la versión del sistema operativo) este lo ejecuta recibiendo los parámetros correspondientes. Si la invocación del comando o programa genera una respuesta, esta es enviada con ayuda de la clase Transmisor. Cada comando o mensaje es manejado por un hilo mediante la clase HiloMensajero. La estructura general de cada clase se muestra en el siguiente diagrama.

LanzadorDeApplet

run()close()

VentanaControlador

ventanaControlador()init()main()

utiliza

crea

utiliza

ParallelPort

read()write()

utiliza

Controlador

controlador()lanza()run()

creautiliza

LanzadorDOS

lanzadorDOS()

Transmisor

transmisor()

HiloMensajero

run()hiloMensajero()

Fig V-5 Clases del Programa Manejador 5.4.4. Programa Sensor Una vez credo un objeto de la clase LectorLPT1 se monitorea el registro de estado del puerto paralelo con ayuda de la clase ParallelPort y si hay un cambio se envía un mensaje al monitor con ayuda de la clase Transmisor. La clase VentanaSensor permite además ver el estado del puerto ya que se muestra este a través de la misma. La estructura de este programa se muestra a continuación.

57

LanzadorDeApplet

run()close()

VentanaSensor

ventanaSensor()init()main()

utiliza

crea

utiliza

ParallelPort

read()write()

utiliza

LectorLPT1

lectorLPT1()run()

Transmisor

transmisor()

6.. PRUEBAS REALIZADAS

Entre las pruebas realizadas enlistamos las siguientes con las observaciones señaladas. Pruebas con red garantizada La respuesta del envío de comandos es satisfactoria, las instrucciones son recibidas sin retrasos aparentes no obstante los cambios de comando se envíen continuamente en intervalos de tiempo cortos. El applet que dibuja el Joystick es pintado eficientemente sin retrasos aparentes Prueba de control al perder la red El robot permanece con el último comando enviado y no se puede retomar el control. De este modo la red local debe estar garantizada como ya se había considerado. Pruebas en diferentes Sistema Operativos La aplicación se instala y opera correctamente en diferentes sistemas operativos Windows (win 95, Win 98, Win Me, Win 2000 y Windows XP La necesidad de utilizar la ventana de línea de comandos de manera virtual para ejecutar comandos remotamente hace lenta la ejecución de los mismos en el servidor por lo que se recomienda que este cuente con una memoria RAM superior a 512 MB y un procesador superior a 1.7GHz.

58

7. RECOMENDACIONES

El presente trabajo puede ser mejorado en varios aspectos, estas observaciones son el resultado de probar con este producto y haber obtenido resultados satisfactorios, con lo que se pueden pasar a considerar aspectos no tan prioritarios; pero que mejoran la calidad de la aplicación así como su mantenimiento y expansión. Se sugiere plantear una solución distinta al uso de sockets dada la complejidad de sincronización de los mismos sobre todo si se van a enviar diferentes tipos de paquetes. El esquema de control mediante el puerto paralelo funciona adecuadamente para las velocidades de respuesta de los dispositivos controlados remotamente. Sin embargo se propone un avance en este sentido con el uso del Puerto USB dada su tendencia a sustituir otros puertos de comunicaciones como el puerto serie o paralelo. Se propone el desarrollo de un sensor de red para casos en los que la red no esté garantizada y de este modo ejecutar una instrucción de paro cuando esto ocurra, de tal modo que no se pierda el control del robot a pesar de la posibilidad de perder la red.

59

8. CONCLUSIONES El desarrollo de aplicaciones bajo el protocolo TCP/IP para control y monitoreo de dispositivos remotos en ambiente de red nos brinda la seguridad de que estas seguirán funcionando con las nuevas tecnologías dado que se desarrollan bajo este estándar. El uso de sockets nos permite el envío de datos proporcionando únicamente un número de puerto y la dirección IP del servidor. De manera transparente, ya sea que se tenga una red convencional o inalámbrica. Java como lenguaje de programación nos proporciona independencia de la plataforma en la que se ejecutan las aplicaciones generadas. Además el desarrollo de clases nos permite su reutilización, logrando mejorar las mismas con nuevos métodos o bien avanzar en los detalles o características particulares de la aplicación. De modo que se aprovechan las características de la programación orientada a objetos y se logran mayores avances en menor tiempo con mejor calidad. El uso hilos para repartir tareas que pueden ser realizadas concurrentemente con otras que el procesador o los procesadores deban realizar, nos permite un mejor desempeño de la aplicación siempre que se usen adecuadamente. Sin embargo, pueden generar problemas de sincronización si el orden de ejecución es importante y un hilo toma más tiempo en ejecutarse de lo que se esperaba. El uso de hilos para optimizar el envió de señales al puerto paralelo fue descartado dado que se requiere de un orden estricto en la secuencia de instrucciones a realizar. No obstante resultó adecuado para mejorar el rendimiento y la velocidad de respuesta del servidor dado que las tareas de diferente naturaleza y ejecución esporádica pueden ser llevadas a cabo de manera independiente y su respuesta no es crítica en orden y tiempo. Optimizar el código generado resulta esencial en la parte del programa que envía instrucciones mediante el joystick al puerto paralelo, ya que el movimiento de este genera una señal cada vez que cambia de posición, por lo que en un barrido de una posición a otra se podrían estar generando demasiadas instrucciones en un intervalo de tiempo muy corto. El uso de computadoras para el control remoto de aplicaciones nos da grandes ventajas como lo es la disponibilidad de periféricos, puertos de comunicaciones, programas existentes y programas que aún no han sido desarrollados pero que pueden ser incorporados. Además de todas las tecnologías que serán desarrolladas en los próximos años. El uso del puerto Paralelo facilita la comunicación con dispositivos externos, sin embargo la tendencia es la de migrar nuevo hardware y equipo a controlar hacia el uso de Puertos de alta velocidad como USB. De modo que un siguiente paso será el de realizar esta conversión. La arquitectura del sistema desarrollado, permitirá modificar fácilmente el código si así se requiere, sustituyendo únicamente la clase ParallelPort por la que corresponda para la comunicación con el puerto USB así como el diseño o uso del driver correspondiente en cuanto a software se refiere. La capacidad de invocar programas o comandos de manera remota en el servidor nos ayuda a incorporar elementos multimedia en las aplicaciones, dado el soporte que proporciona la computadora haciendo la función de servidor, a la que se conecta el dispositivo a controlar. Además nos permite el monitoreo de los procesos en ejecución. Si se desean agregar sensores para monitoreo del robot, estos pueden comunicar su estado mediante nuevos sockets entre los dos equipos con nuevos programas que se deben desarrollar. Finalmente podemos decir que se cuenta con una arquitectura de software para control y monitoreo de dispositivos remotos que es escalable e independiente de plataforma, bajo un ambiente de red, fácilmente adaptable al control de otros dispositivos.

60

9. REFERENCIAS [1] Eckel Bruce. Piensa en Java. Pearson educación (2002)

[2] “Programming the Parallel Port: Interfacing the PC for Data Acquisition & Process Control”.

Dhananjay V. Gadre. Ed. CMP Books, 1999.

[3]. http://www.doc.ic.ac.uk/~ih/doc/par/ : Interfacing to the IBM-PC Parallel Printer Port. [ONLINE] (Mayo 2005)

[4] http://www.lvr.com/parport.htm: Parallel Port Central [ONLINE] (Mayo, 2005)

[5] http://www.geekhideout.com. (Mayo 2005)

[6] http://algoval.essex.ac.uk/cec2005/race/apps/UserPort.zip (Junio 2005)

[7]. http://www.genuts.com/api. (Junio 2005)

[8]. Herrera Lozada. J.C. Interfaces para el puerto paralelo de la PC, en modo bidireccional.

CIDETEC – IPN)

[9]. http://soft0.upc.es/pc/sockets [ONLINE], (Junio 2005) [10]. www.ddj.com [ONLINE]

61

ANEXO A. CODIGO FUENTE A continuación se proporciona el código fuente de las clases desarrolladas. A.1. Programa Joystick /* Archivo: ClienteRobot.java */ package cliente; import java.net.*; import java.io.*; /** * Clase que crea un socket cliente, establece la conexión y lee los datos * del servidor, escribiéndolos en pantalla. */ public class ClienteRobot { Socket socket; BufferedReader lectorDeSocket; PrintWriter escritorDeSocket; boolean bandera = true; ClienteRobot(String dir_ip, String puerto) { try { socket = new Socket(dir_ip,Integer.valueOf(puerto).intValue()); } catch(Exception exc) { System.out.println("No se pudo establecer la conexion"); } } public boolean envia(String comando){ bandera = true; try { escritorDeSocket = new PrintWriter(socket.getOutputStream(),true); escritorDeSocket.println(comando); } catch(Exception exc) { System.out.println("No hay comunicacion con el Servidor"); bandera = false; } return bandera; } public void cerrarSocket(){ try { socket.close(); } catch(Exception exc) { System.out.println("Socket cerrado"); } } }

62

/* Archivo: Joystick.java */ /* Se utiliza clase com.gnuts.gameui para Joystick * Mas información en: The Genuts Project - http://www.genuts.com * Version 2006 */ package cliente; import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.genuts.gameui.*; /** * Applet: Joystick y Linea de comandos para control remoto de Robot y computadora */ public class Joystick extends JApplet implements MouseListener, MouseMotionListener { PlayField f; int xb, yb; int d; int w, h; int p; int xc, yc; int L; int s; int H; int xc2, yc2; int region = 0; // La parte alta de la cadena es logica inversa al diseño original del hardware StringBuffer cadenaDeControl = new StringBuffer("00000000"); // ESTADO INICIAL ClienteRobot cliente; private JButton setButton = new JButton(); private JLabel dirIPLabel = new JLabel(); private JTextField dirIPTextField = new JTextField(); private JLabel ptoLabel = new JLabel(); private JTextField ptoTextField = new JTextField(); private JTextField commandTextField = new JTextField(""); private JLabel avisos = new JLabel(); private JButton enviarButton = new JButton(); private JButton salirButton = new JButton(); private JLabel shellLabel = new JLabel(); private JLabel checkGroupLabel = new JLabel("Activar:"); private JCheckBox brazoIzqCB = new JCheckBox(" B. Izquierdo"); private JCheckBox brazoDerCB = new JCheckBox(" B. Derecho"); private JCheckBox cabezaNoCB = new JCheckBox(" NO - Cabeza"); private JCheckBox cabezaSiCB = new JCheckBox(" SI - Cabeza"); private void enviaComando(String comando, String mensajeEnPantalla) { if(!comando.equals("")){ cliente.envia(comando); System.out.println(mensajeEnPantalla); } }

63

private void enviaMovimiento(int xs, int ys) { // Regiones: 4 | 3 | 2 // Se envia movimiento de Robot segun zona del Joystick 5 | 0 | 1 // (solo si hubo una modificacion de la cadena a enviar) 6 | 7 | 8 // s1 s2 s3 s4 MDA MDB MIA MIB REGION OBSERVACION // m5 0 1 0 1 1 0 A A 4 VUELTA IZQ-ADE PIVOTE // m6 0 1 1 0 B B 1 0 2 VUELTA DER-ADE PIVOTE // m7 0 1 1 1 1 0 1 0 3 ADELANTE // - - - - - - - - - // m9 1 0 0 1 C C 0 1 6 VUELTA IZQ-ATR PIVOTE // m10 1 0 1 0 0 1 D D 8 VUELTA DER-ATR PIVOTE // m11 1 0 1 1 0 1 0 1 7 ATRAS // m12 - - - - - - - - // m13 1 1 0 1 1 0 0 1 5 VUELTA IZQ // m14 1 1 1 0 0 1 1 0 1 VUELTA DERECHA // m15 1 1 1 1 0 0 0 0 0 POSICION NORMAL // EN EL CLIENTE SE USARAN LOS 4 BITS MDA MDB MIA y MIB PARA // CONTROLAR LOS 2 MOTORES DEL ROBOT QUE RESPONDEN DE ACUERDO A LA // SIG TABLA // MOTOR FUNCION // 0 0 DETENIDO MD = MOTOR DERECHO (A o B) // 0 1 ATRAS MI = MOTOR IZQUIERDO (A o B) // 1 0 ADELANTE // 1 1 DETENIDO // ESTRUCTURA DE cadenaDeControl: |BI|BD|NO|SI|MDA|MDB|MIA|MIB| if(xs > w*9/14){ if(ys > h*9/14) {if(region != 8 ) { cadenaDeControl.setCharAt(4,'0'); cadenaDeControl.setCharAt(5,'1'); cadenaDeControl.setCharAt(6,'1'); cadenaDeControl.setCharAt(7,'1'); enviaComando("#lud "+cadenaDeControl,"VUELTA DER-ATRAS PIVOTE"); region = 8;} // SE SABE EN QUE REGION ESTA PARA NO REPETIR } // ENVIOS SIN NECESIDAD else if(ys > h*5/14) {if(region != 1){ cadenaDeControl.setCharAt(4,'0'); cadenaDeControl.setCharAt(5,'1'); cadenaDeControl.setCharAt(6,'1'); cadenaDeControl.setCharAt(7,'0'); enviaComando("#lud "+cadenaDeControl,"VUELTA DER"); region = 1;} } else {if(region != 2){ cadenaDeControl.setCharAt(4,'1'); cadenaDeControl.setCharAt(5,'1'); cadenaDeControl.setCharAt(6,'1'); cadenaDeControl.setCharAt(7,'0'); enviaComando("#lud "+cadenaDeControl,"VUELTA DER-ADELANTE PIVOTE"); region = 2;} } } else if(xs > w*5/14){ if(ys > h*9/14) {if(region != 7){ cadenaDeControl.setCharAt(4,'0'); cadenaDeControl.setCharAt(5,'1'); cadenaDeControl.setCharAt(6,'0'); cadenaDeControl.setCharAt(7,'1'); enviaComando("#lud "+cadenaDeControl,"ATRAS"); region = 7;} }

64

else if(ys > h*5/14) {if(region != 0){ cadenaDeControl.setCharAt(4,'0'); cadenaDeControl.setCharAt(5,'0'); cadenaDeControl.setCharAt(6,'0'); cadenaDeControl.setCharAt(7,'0'); enviaComando("#lud "+cadenaDeControl,"ALTO"); region = 0;} } else {if(region != 3){ cadenaDeControl.setCharAt(4,'1'); cadenaDeControl.setCharAt(5,'0'); cadenaDeControl.setCharAt(6,'1'); cadenaDeControl.setCharAt(7,'0'); enviaComando("#lud "+cadenaDeControl,"ADELANTE"); region = 3;} } } else{ if(ys > h*9/14) {if(region != 6){ cadenaDeControl.setCharAt(4,'1'); cadenaDeControl.setCharAt(5,'1'); cadenaDeControl.setCharAt(6,'0'); cadenaDeControl.setCharAt(7,'1'); enviaComando("#lud "+cadenaDeControl,"VUELTA IZQ-ATRAS PIVOTE"); region = 6;} } else if(ys > h*5/14) {if(region != 5){ cadenaDeControl.setCharAt(4,'1'); cadenaDeControl.setCharAt(5,'0'); cadenaDeControl.setCharAt(6,'0'); cadenaDeControl.setCharAt(7,'1'); enviaComando("#lud "+cadenaDeControl,"VUELTA-IZQ"); region = 5;} } else {if(region != 4){ cadenaDeControl.setCharAt(4,'1'); cadenaDeControl.setCharAt(5,'0'); cadenaDeControl.setCharAt(6,'1'); cadenaDeControl.setCharAt(7,'1'); enviaComando("#lud "+cadenaDeControl,"VUELTA IZQ-ADELANTE PIVOTE"); region = 4;} } } } class DetectorEnter implements ActionListener { //Accion a realizar al presionar ENTER public void actionPerformed(ActionEvent e) { if(commandTextField.getText()!= ""){ enviaComando(commandTextField.getText(),"Enviar comando por shell"); commandTextField.setText(""); } else avisos.setText("Escriba comando y Presione ENTER"); } } public void init() { commandTextField.disable(); commandTextField.setBackground(new Color(225,225,225)); //Gris claro Container cp = getContentPane();

65

//Listeners para botones, Check Group y Enter commandTextField.addActionListener(new DetectorEnter()); brazoIzqCB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(brazoIzqCB.isSelected()){ cadenaDeControl.setCharAt(0,'1'); enviaComando("#lud "+cadenaDeControl,"Brazo Izquierdo Activado"); } else { cadenaDeControl.setCharAt(0,'0'); enviaComando("#lud "+cadenaDeControl,"Brazo Izquierdo Desactivado"); } avisos.setText("Listo..."); } }); brazoDerCB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(brazoDerCB.isSelected()){ cadenaDeControl.setCharAt(1,'1'); enviaComando("#lud "+cadenaDeControl,"Brazo Derecho Activado"); } else { cadenaDeControl.setCharAt(1,'0'); enviaComando("#lud "+cadenaDeControl,"Brazo Derecho Desactivado"); } avisos.setText("Listo..."); } }); cabezaNoCB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(cabezaNoCB.isSelected()){ cadenaDeControl.setCharAt(2,'1'); enviaComando("#lud "+cadenaDeControl,"NO Cabeza Activado"); } else { cadenaDeControl.setCharAt(2,'0'); enviaComando("#lud "+cadenaDeControl,"NO Cabeza Desactivado"); } avisos.setText("Listo..."); } }); cabezaSiCB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(cabezaSiCB.isSelected()){ cadenaDeControl.setCharAt(3,'1'); enviaComando("#lud "+cadenaDeControl,"SI Cabeza Activado"); } else { cadenaDeControl.setCharAt(3,'0'); enviaComando("#lud "+cadenaDeControl,"SI Cabeza Desactivado"); } avisos.setText("Listo..."); } });

66

salirButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(setButton.getText()!="Desconectar"){ stop(); destroy(); LanzadorDeApplet.cerrar(); } else{ avisos.setText("Si desea salir Desconectese"); } } }); enviarButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(commandTextField.getText()!= ""){ enviaComando(commandTextField.getText(),"Boton Enviar comando Presionado"); commandTextField.setText(""); } else { avisos.setText("Escriba un comando..."); } } }); setButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { if(setButton.getText()=="Conectar"){ //falta validar por return correcto cliente = new ClienteRobot(dirIPTextField.getText(),ptoTextField.getText()); if(cliente.envia("#lud "+cadenaDeControl)==true){ dirIPTextField.disable(); ptoTextField.disable(); commandTextField.enable(); dirIPTextField.setBackground(new Color(225,225,225)); ptoTextField.setBackground(new Color(225,225,225)); commandTextField.setBackground(java.awt.Color.white); setButton.setText("Desconectar"); avisos.setText("Listo..."); brazoIzqCB.setEnabled(true); brazoDerCB.setEnabled(true); cabezaNoCB.setEnabled(true); cabezaSiCB.setEnabled(true); enviarButton.setEnabled(true); f.setEnabled(true); System.out.println("Conexion Establecida"); }else{ avisos.setText("No se puede establecer la conexion"); System.out.println("No se puede establecer la conexion"); } System.out.println("Boton Conectar Presionado"); } else { cliente.cerrarSocket(); dirIPTextField.setBackground(java.awt.Color.white); ptoTextField.setBackground(java.awt.Color.white); commandTextField.setBackground(new Color(225,225,225)); //aqui dirIPTextField.enable(); ptoTextField.enable(); commandTextField.disable(); setButton.setText("Conectar"); avisos.setText("Establezca IP y Puerto ....."); brazoIzqCB.setEnabled(false);

67

brazoDerCB.setEnabled(false); cabezaNoCB.setEnabled(false); cabezaSiCB.setEnabled(false); enviarButton.setEnabled(false); f.setEnabled(false); System.out.println("Boton Desconectar Presionado - Desconectado"); } } }); /* ++++++++++++++++++++++ Posiciones de controles y texto ++++++++++++++++++++++++ */ cp.setLayout(null); brazoIzqCB.setEnabled(false); brazoDerCB.setEnabled(false); cabezaNoCB.setEnabled(false); cabezaSiCB.setEnabled(false); enviarButton.setEnabled(false); // Botones etc.. cp.add(setButton); setButton.setBounds(270, 24, 90, 25); setButton.setText("Conectar"); setButton.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 9)); cp.add(dirIPLabel); dirIPLabel.setBounds(20, 30, 33, 20); dirIPLabel.setText("Dir. IP"); dirIPLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); cp.add(ptoLabel); ptoLabel.setBounds(168, 30, 33, 20); ptoLabel.setText("Puerto"); ptoLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); cp.add(avisos); avisos.setBounds(22, 275, 170, 15); avisos.setText("Cargando Joystick..."); avisos.setForeground(Color.red); avisos.setFont(new java.awt.Font("Dialog", java.awt.Font.ITALIC, 10)); cp.add(shellLabel); shellLabel.setBounds(20, 225, 139, 15); shellLabel.setText("Linea de Comandos"); shellLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); cp.add(dirIPTextField); dirIPTextField.setBounds(55, 25, 99, 25); dirIPTextField.setText(""); cp.add(ptoTextField); ptoTextField.setBounds(203, 25, 52, 25); ptoTextField.setText("8086"); cp.add(commandTextField); commandTextField.setBounds(20, 245, 167, 25); commandTextField.setText(""); cp.add(enviarButton); enviarButton.setBounds(205, 245, 69, 25); enviarButton.setText("Enviar"); enviarButton.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));

68

cp.add(salirButton); salirButton.setBounds(290, 245, 69, 25); salirButton.setText("Salir"); salirButton.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); cp.add(checkGroupLabel); cp.add(brazoIzqCB); cp.add(brazoDerCB); cp.add(cabezaNoCB); cp.add(cabezaSiCB); checkGroupLabel.setBounds(250,60,110,20); brazoIzqCB.setBounds(250,90,110,20); brazoDerCB.setBounds(250,125,110,20); cabezaNoCB.setBounds(250,160,110,20); cabezaSiCB.setBounds(250,195,110,20); checkGroupLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); brazoIzqCB.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); brazoDerCB.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cabezaNoCB.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cabezaSiCB.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); checkGroupLabel.setForeground(java.awt.Color.darkGray); brazoIzqCB.setForeground(java.awt.Color.darkGray); brazoDerCB.setForeground(java.awt.Color.darkGray); cabezaNoCB.setForeground(java.awt.Color.darkGray); cabezaSiCB.setForeground(java.awt.Color.darkGray); // Creacion de Joystick // anchura y altura w = getSize().width - getSize().width/2; h = getSize().height - getSize().height/2; // profundidad p = (int)(Math.min(w,h)*0.05); // diametro de la bolita d = (int)(Math.min(w,h)*0.35); // ancho del palito s = d/3; // centro xc = (w-p)/2; yc = (h-p)/2; // altura de la bolita H = p*2; // centro xc2 = xc-H; yc2 = yc-H; // radio de accion L = Math.min(w,h)/2-d/2-H-p; // iniciar movimiento de la bolita xb = xc-H; yb = yc-H;

69

f = new PlayField(w,h) { public void paintPlayField(Graphics g) { // Zona de joystick g.setColor(Color.white); g.fillRect(0,0,w,h); // caja g.setColor(new Color(204,204,204)); g.fillRect(0+w/4,0+h/4,w-p-w/2,h-p-h/2); g.setColor(new Color(102,102,102)); int tx1[] = {w-p-w/4,w-w/4,w-w/4,w-p-w/4}; int ty1[] = {0+h/4,p+h/4,h-h/4,h-p-h/4}; g.fillPolygon(tx1, ty1, 4); g.setColor(new Color(153,153,153)); int tx2[] = {0+w/4,w-p-w/4,w-w/4,p+w/4}; int ty2[] = {h-p-h/4,h-p-h/4,h-h/4,h-h/4}; g.fillPolygon(tx2, ty2, 4); // palito g.setColor(new Color(102,102,102)); g.fillOval(xc-s/2,yc-s/2,s,s); drawThickLine(g, xc, yc, xb, yb, s); // bolita g.setColor(new Color(99,174,203)); g.fillOval(xb-d/2,yb-d/2,d,d); // efecto de 3D de bolita g.setColor(Color.white); g.fillOval(xb-d/4,yb-d/4,d/6,d/6); } }; f.setSleepTime(1000/25); f.setDisplayManager(null); f.addMouseListener(this); f.addMouseMotionListener(this); //Area de Joystick cp.add(f); f.setBounds(27, 68, 184, 144); avisos.setText("Establezca IP y Puerto..."); avisos.setForeground(Color.black); f.setEnabled(false); } public void start() { f.setPause(false); } public void stop() { f.stop(); } public void mouseClicked(MouseEvent e) { //System.out.println("Mouse clicked"); enviaMovimiento(e.getX(),e.getY()); }

70

public void mouseEntered(MouseEvent e) { avisos.setText("Listo..."); //System.out.println("Mouse entra"); //showStatus(getAppletInfo()); } public void mouseExited(MouseEvent e) { //System.out.println("Mouse sale"); //showStatus(""); } public String getAppletInfo() { return "Control Remoto"; } public void mousePressed(MouseEvent e) { //System.out.println("Mouse Presionado y moviendo bola"); mueveBola(e.getX(),e.getY()); //enviaMovimiento(e.getX(),e.getY()); } void mueveBola(int nx, int ny) { xb = nx; yb = ny; // xb y yb estan en un cuadrado LxL if (xb > xc2+L) xb = xc2+L; else if (xb < xc2-L) xb = xc2-L; if (yb > yc2+L) yb = yc2+L; else if (yb < yc2-L) yb = yc2-L; f.repaint(); } public void mouseReleased(MouseEvent e) { //System.out.println("Mouse liberado y moviendo bola"); mueveBola(xc2,yc2); } public void mouseDragged(MouseEvent e) { //System.out.println("Mouse arrastrado y mover bola"); enviaMovimiento(e.getX(),e.getY()); mueveBola(e.getX(),e.getY()); } public void mouseMoved(MouseEvent e) { //System.out.println("Mouse movido"); // Nada... } /** * Referencias: Real's HowTo, http://www.rgagnon.com/ */ public void drawThickLine(Graphics g, int x1, int y1, int x2, int y2, int paloLong) { // Linea (es un poligono) int dX = x2 - x1; int dY = y2 - y1;

71

// longitud de la linea double longitud = Math.sqrt(dX * dX + dY * dY); double escala = (double)(paloLong) / (2 * longitud); // X y Y se incrementan... double ddx = -escala * (double)dY; double ddy = escala * (double)dX; ddx += (ddx > 0) ? 0.5 : -0.5; ddy += (ddy > 0) ? 0.5 : -0.5; int dx = (int)ddx; int dy = (int)ddy; // Puntos en las esquinas... int xPoints[] = new int[4]; int yPoints[] = new int[4]; xPoints[0] = x1 + dx; yPoints[0] = y1 + dy; xPoints[1] = x1 - dx; yPoints[1] = y1 - dy; xPoints[2] = x2 - dx; yPoints[2] = y2 - dy; xPoints[3] = x2 + dx; yPoints[3] = y2 + dy; g.fillPolygon(xPoints, yPoints, 4); } public static void main(String[] args) { LanzadorDeApplet.run(new Joystick(), 385, 330, "Robot - Control Remoto"); } }

72

/* * Archivo LanzadorDeApplet.java Creado: 19 de Junio de 2005 * * Clase que agrega un applet a un Frame para poder visualizarlo * sin navegador y desde la linea de comandos * */ package cliente; /** * @author Ignacio Andrés Sánchez * @author Enrique Crisóstomo Bravo * @author Ana Lilia Ortega Torres * @author Iván Olaf Vivas Trejo * @author Erik Torres Ramírez * * @author - Responsable de Proyecto: * @author M. en C. Omar Cabrera UAM - Iztapalapa * */ import javax.swing.*; import java.awt.event.*; import java.applet.*; public class LanzadorDeApplet { static JFrame frame = null; public static void setupClosing(JFrame frame) { frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void run(Applet applet, int width, int height, String mensaje) { frame = new JFrame(mensaje); setupClosing(frame); frame.getContentPane().add(applet); frame.setSize(width, height); frame.setVisible(true); applet.init(); applet.start(); } public static void cerrar(){ frame.dispose(); } }

73

A.2. Programa Monitor /* Archivo: VentanaDeMonitoreo.java */ package principal; import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.border.*; public class VentanaDeMonitoreo extends JApplet { private JLabel ptoLabel = new JLabel(); private JLabel titulo = new JLabel("Información del Servidor:"); public JTextField ptoTextField = new JTextField(); private JLabel avisos = new JLabel(); String nueva_linea = ""; // cadena que recibe informacion en tiempo real int i = 0; JButton conectarBtn = new JButton("Iniciar"), limpiarBtn = new JButton("Limpiar"), salirBtn = new JButton("Salir"); public JTextArea txtArea = new JTextArea("", 10, 10); JScrollPane scPanel = new JScrollPane(txtArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); //Iniciar class B1L implements ActionListener { public void actionPerformed(ActionEvent e) { //new HiloM(ptoTextField, txtArea); //* HiloM hilito = new HiloM(ptoTextField, txtArea); //hilito.run(); //hilito.setDaemon(true); //hilito.run(); //*/ conectarBtn.setToolTipText("El servidor ya esta habilitado"); ptoTextField.disable(); conectarBtn.disable(); conectarBtn.setEnabled(false); } } //Limpiar class B2L implements ActionListener { public void actionPerformed(ActionEvent e) { nueva_linea = null; txtArea.setText("Servicio de Monitoreo"); avisos.setForeground(Color.blue); avisos.setText("Listo..."); } }

74

public void init() { Container cp = getContentPane(); cp.setLayout(null); Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black); scPanel.setBorder(brd); cp.add(ptoLabel); ptoLabel.setBounds(180, 30, 40, 20); ptoLabel.setText("Puerto:"); ptoLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(ptoTextField); ptoTextField.setBounds(230, 25, 50, 25); ptoTextField.setText("8080"); cp.add(titulo); titulo.setBounds(35, 60, 170, 15); titulo.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(avisos); avisos.setBounds(35, 300, 170, 15); avisos.setText("Listo..."); avisos.setForeground(Color.red); avisos.setFont(new java.awt.Font("Dialog", java.awt.Font.ITALIC, 10)); conectarBtn.addActionListener(new B1L()); cp.add(conectarBtn); conectarBtn.setBounds(307, 25, 90, 25); conectarBtn.setToolTipText("Iniciar Monitor"); limpiarBtn.addActionListener(new B2L()); cp.add(limpiarBtn); limpiarBtn.setBounds(90, 270, 90, 25); limpiarBtn.setToolTipText("Limpiar Area de Texto"); cp.add(salirBtn); salirBtn.setBounds(250, 270, 90, 25); salirBtn.setToolTipText("Haga click si desea terminar"); salirBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stop(); destroy(); LanzadorDeApplet.cerrar(); } }); cp.add(scPanel); scPanel.setBounds(32, 90, 365, 160); //txtArea.setText(""); txtArea.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 11)); txtArea.setEditable(false); } public static void main(String[] args) { LanzadorDeApplet.run(new VentanaDeMonitoreo(), 440, 360,"SERVICIO DE MONITOREO

REMOTO"); } }

75

/* Archivo: HiloM.java */ package principal; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import javax.swing.JTextArea; import javax.swing.JTextField; class HiloM extends Thread{ String valor = ""; JTextArea myArea; public HiloM(JTextField puerto,JTextArea area) { valor = puerto.getText(); myArea = area; setDaemon(true); start(); } public void run() { String textCache = "Bienvenido al servicio de Monitoreo"; textCache = "Servicio de Monitoreo Iniciado"; try { ServerSocket serverSocket = new ServerSocket(Integer.valueOf(valor).intValue()); myArea.setText(textCache); while(true){ Socket incoming = serverSocket.accept(); try { BufferedReader in = new BufferedReader( new InputStreamReader( incoming.getInputStream() )); //Lee por el socket String newline = in.readLine(); textCache = textCache + "\n" +newline; //concatena con nueva linea myArea.setText(textCache); myArea.setCaretPosition(myArea.getDocument().getLength()); int idealSize = 5000; int maxExcess = 3000; int excess = myArea.getDocument().getLength() - idealSize; if (excess >= maxExcess) { myArea.replaceRange("", 0, excess); } System.out.println(newline); } catch(Exception exc) { System.out.println("Error en la conexion"); } yield(); } } catch (NumberFormatException e) { e.printStackTrace(); } catch (IOException e) { } } }

76

/* * Archivo LanzadorDeApplet.java Creado: 15 de Julio, 2005 * * Clase que agrega un applet a un Frame para poder visualizarlo * sin navegador y desde la linea de comandos * */ package principal; /** * @author Ignacio Andrés Sánchez * @author Ana Lilia Ortega Torres */ import javax.swing.*; import java.awt.event.*; import java.applet.*; public class LanzadorDeApplet { static JFrame frame = null; public static void setupClosing(JFrame frame) { frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void run(Applet applet, int width, int height, String mensajeTitulo) { frame = new JFrame(mensajeTitulo); setupClosing(frame); frame.getContentPane().add(applet); frame.setSize(width, height); applet.init(); applet.start(); frame.setVisible(true); } public static void cerrar(){ frame.dispose(); } }

77

A.3. Programa Controlador /* Archivo: VentanaControlador.java */ package servidor; import java.awt.Color; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.border.Border; public class VentanaControlador extends JApplet { private JLabel ptoLabel = new JLabel(); private JLabel pto2Label = new JLabel(); private JLabel ipLabel = new JLabel(); private JLabel titulo = new JLabel("Información de control:"); public JTextField ptoTextField = new JTextField(); public JTextField pto2TextField = new JTextField(); public JTextField ipTextField = new JTextField(); private JLabel avisos = new JLabel(); String nueva_linea = ""; // cadena que recibe informacion en tiempo real int i = 0; JButton conectarBtn = new JButton("Iniciar"), limpiarBtn = new JButton("Limpiar"), salirBtn = new JButton("Salir"); public JTextArea txtArea = new JTextArea("", 10, 10); JScrollPane scPanel = new JScrollPane(txtArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); //Iniciar class B1L implements ActionListener { public void actionPerformed(ActionEvent e) { Controlador oDemonio = new Controlador(ipTextField,ptoTextField,pto2TextField, txtArea); ptoTextField.setEnabled(false); pto2TextField.setEnabled(false); ipTextField.setEnabled(false); conectarBtn.setEnabled(false); conectarBtn.setToolTipText("Servidor listo"); } } //Limpiar class B2L implements ActionListener { public void actionPerformed(ActionEvent e) { nueva_linea = null; txtArea.setText(""); avisos.setForeground(Color.blue); avisos.setText("Listo..."); } }

78

public void init() { Container cp = getContentPane(); cp.setLayout(null); Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black); scPanel.setBorder(brd); cp.add(ptoLabel); ptoLabel.setBounds(32, 28, 120, 20); ptoLabel.setText("Puerto de Monitoreo:"); ptoLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(pto2Label); pto2Label.setBounds(240, 28, 120, 20); pto2Label.setText("Puerto de Joystick:"); pto2Label.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(ptoTextField); ptoTextField.setBounds(160, 25, 45, 25); ptoTextField.setText("8080"); cp.add(pto2TextField); pto2TextField.setBounds(356, 25, 45, 25); pto2TextField.setText("8086"); cp.add(ipLabel); ipLabel.setBounds(32, 68, 65, 20); ipLabel.setText("IP cliente:"); ipLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(ipTextField); ipTextField.setBounds(95, 65, 135, 25); ipTextField.setText(""); conectarBtn.addActionListener(new B1L()); cp.add(conectarBtn); conectarBtn.setBounds(265, 65, 100, 25); conectarBtn.setToolTipText("Iniciar Servicio"); cp.add(titulo); titulo.setBounds(35, 105, 170, 15); titulo.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(avisos); avisos.setBounds(35, 310, 170, 15); avisos.setText("Listo..."); avisos.setForeground(Color.red); avisos.setFont(new java.awt.Font("Dialog", java.awt.Font.ITALIC, 10)); limpiarBtn.addActionListener(new B2L()); cp.add(limpiarBtn); limpiarBtn.setBounds(80, 280, 100, 25); limpiarBtn.setToolTipText("Limpiar Area de Información de Control"); cp.add(salirBtn); salirBtn.setBounds(265, 280, 100, 25); salirBtn.setToolTipText("Haga click si desea terminar la aplicación"); salirBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stop(); destroy(); LanzadorDeApplet.cerrar(); } });

79

cp.add(scPanel); scPanel.setBounds(32, 130, 370, 130); txtArea.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 11)); txtArea.setEditable(false); } public static void main(String[] args) { LanzadorDeApplet.run(new VentanaControlador(), 440, 365); } } /* Archivo : Controlador.java */ package servidor; import java.net.*; import parport.ParallelPort; import java.io.*; import javax.swing.JTextArea; import javax.swing.JTextField; import coordinador.tareas.extras.*; public class Controlador extends Thread { public static String dirIPCliente = ""; public static String puerto2 = "8080";//Pto de monitor public static boolean hayComunicacion = true; public static int valor; JTextArea myArea; public Controlador(JTextField ip,JTextField ptoM,JTextField ptoJ,JTextArea area) { dirIPCliente = ip.getText(); puerto2 = ptoM.getText(); valor = Integer.valueOf(ptoJ.getText()).intValue(); myArea = area; setDaemon(true); start(); } private static void lanza(String sentencia) //sentencia { LanzadorDOS lanzador = new LanzadorDOS(sentencia); } public void run() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(valor); myArea.setText("Servidor atendiendo peticiones..." + "\n"); ParallelPort lpt1 = new ParallelPort(0x378); Socket incoming = serverSocket.accept();//bajar? char [] arreglo = {'0','0','0','0','0','0','0','0'}; int valorDeSalida, i,j,potencia;

80

while(true) { try { BufferedReader lectorBufferDeEntrada = new BufferedReader(new

InputStreamReader(incoming.getInputStream())); PrintWriter escritorDeSalida = new

PrintWriter(incoming.getOutputStream(), true); //Lee el comando por el socket String comando = lectorBufferDeEntrada.readLine(); if(comando == null) { myArea.append("Esperando Conexion..." + "\n"); incoming = serverSocket.accept(); } else { myArea.append("Comando recibido: " +comando + "\n"); if(comando.startsWith("#l")==true) { comando.getChars(5,13,arreglo,0); valorDeSalida = 0;

for(i=0;i<8;i++){ // Se calcula el entero que corresponde a cadena potencia = 1; for(j=0;j<i;j++){ potencia = potencia * 2; } //la cadena original contiene la subcadena de bits a enviar al if(comando.charAt(i+5)=='1'){ //puerto a partir de la posicion 5 valorDeSalida = valorDeSalida | potencia; } } lpt1.write(valorDeSalida); // escribe byte al puerto paralelo myArea.append("Escritura en el Puerto Paralelo: " + valorDeSalida +

"\n"); } else { //Se lanza el programa que corre en DOS con sus parametros lanza(comando); } } // Se posiciona al final de el area de texto: myArea.setCaretPosition(myArea.getDocument().getLength()); // limita el tamano del texto int idealSize = 5000; int maxExcess = 3000; int excess = myArea.getDocument().getLength() - idealSize; if (excess >= maxExcess) { myArea.replaceRange("", 0, excess); } } catch(Exception exc) {System.out.println("Error! - " + exc.toString()); } } } catch(Exception exc) {System.out.println("El Numero de Puerto No fue ingresado correctamente!");} } }

81

/* Archivo: Transmisor.java */ package servidor; import java.net.*; import java.io.*; public class Transmisor { Socket socket; PrintWriter writer; public Transmisor(String dir_ip, String puerto, String cadena) { boolean exito = true; try { socket = new Socket(dir_ip, Integer.valueOf(puerto).intValue()); writer = new PrintWriter(socket.getOutputStream(), true); try { writer.println(cadena); } catch (Exception exc) { System.out.println("Error! No se pueden enviar datos"); Controlador.hayComunicacion = false; } try { socket.close(); } catch (Exception exc) { System.out.println("No se cerro la comunicacion de manera normal"); } } catch (Exception exc) { System.out.println("Aun no existe Monitor disponible en la direccion y puerto especificados!"); Controlador.hayComunicacion = false; } } }

82

/* * Archivo LanzadorDeApplet.java Creado: 19 de Junio de 2005 * * Clase que agrega un applet a un Frame para poder visualizarlo * sin navegador y desde la linea de comandos * */ package cliente; /** * @author Ignacio Andrés Sánchez * @author Enrique Crisóstomo Bravo * @author Ana Lilia Ortega Torres * @author Iván Olaf Vivas Trejo * @author Erik Torres Ramírez * * @author - Responsable de Proyecto: * @author M. en C. Omar Cabrera UAM - Iztapalapa * */ import javax.swing.*; import java.awt.event.*; import java.applet.*; public class LanzadorDeApplet { static JFrame frame = null; public static void setupClosing(JFrame frame) { frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void run(Applet applet, int width, int height) { frame = new JFrame("CONTROLADOR DEL ROBOT"); setupClosing(frame); frame.getContentPane().add(applet); frame.setSize(width, height); applet.init(); applet.start(); frame.setVisible(true); } public static void cerrar(){ frame.dispose(); } }

83

/* Archivo: ParallelPort.java */ package parport; public class ParallelPort { /** The port base address (e.g. 0x378 is base address for LPT1) */ private int portBase; /** To cunstruct a ParallelPort object, * you need the port base address */ public ParallelPort (int portBase) { this.portBase = portBase; } /** Reads one byte from the STATUS pins of the parallel port. * * The byte read contains 5 valid bits, corresponing to 5 pins of input * from the STATUS pins of the parallel port (the STATUS is located * at "portBase + 1", e.g. the STATUS address for LPT1 is 0x379). * * This diagram shows the content of the byte: * * Bit | Pin # | Printer Status | Inverted * -----+-------+-----------------+----------- * 7 | ~11 | Busy | Yes * 6 | 10 | Acknowledge | * 5 | 12 | Out of paper | * 4 | 13 | Selected | * 3 | 15 | I/O error | * * Note that Pin 11 is inverted, this means that "Hi" input on pin * means 0 on bit 7, "Low" input on pin means 1 on bit 7. */ public int read () { return ParallelPort.readOneByte (this.portBase+1); } /** Writes one byte to the DATA pins of parallel port. * The byte is written to the DATA pins of the port. The DATA pins are * located at the base address of the port (e.g. DATA address for LPT1 * is 0x378). * * This diagram shows how the byte is written: * * Bit | Pin # | Printer DATA * -----+-------+-------------- * 7 | 9 | DATA 7 * 6 | 8 | DATA 6 * 5 | 7 | DATA 5 * 4 | 6 | DATA 4 * 3 | 5 | DATA 3 * 2 | 4 | DATA 2 * 1 | 3 | DATA 1 * 0 | 2 | DATA 0 */

84

public void write (int oneByte) { ParallelPort.writeOneByte (this.portBase, oneByte); } /** Reads one byte from the specified address. * (normally the address is the STATUS pins of the port) */ public static native int readOneByte (int address); /** Writes one byte to the specified address * (normally the address is the DATA pins of the port) */ public static native void writeOneByte (int address, int oneByte); static { System.loadLibrary("parport"); } } /* Archivo: HiloMensajero.java */ package coordinador.tareas.extras; import java.io.*; import servidor.Controlador; import servidor.Transmisor; public class HiloMensajero extends Thread{ InputStream is; String type; HiloMensajero(InputStream is, String type) { this.is = is; this.type = type; } public void run() { try { InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line=null; while ( (line = br.readLine()) != null){ System.out.println(type + ">" + line); Transmisor s = new Transmisor(Controlador.dirIPCliente,Controlador.puerto2,type + ">" + line); } } catch (IOException ioe) { ioe.printStackTrace(); } } }

85

/* Archivo LanzadorDOS.java */ package coordinador.tareas.extras; public class LanzadorDOS { public LanzadorDOS(String programaXLanzar) { //programaXLanzar (incluir parametros) entre comillas // El programa DOS a lanzar no debe esperar interaccion try { String sistemaOperativo = System.getProperty("os.name" ); String[] sentencia = new String[3]; //Para ver nombre de OS donde se ejecuta //System.out.println(sistemaOperativo); if( sistemaOperativo.equals( "Windows 95" )|| sistemaOperativo.equals( "Windows 98" )||sistemaOperativo.equals( "Windows Me" )) { sentencia[0] = "command.com" ; sentencia[1] = "/C" ; sentencia[2] = programaXLanzar; } else if(sistemaOperativo.equals( "Windows NT" )|| sistemaOperativo.equals( "Windows XP" )||sistemaOperativo.equals( "Windows 2000" ))// Win XP, NT y 2000 { sentencia[0] = "cmd.exe" ; sentencia[1] = "/C" ; sentencia[2] = programaXLanzar; } else { System.out.println("Sistema Operativo No Soportado "); System.exit(1); } Runtime rt = Runtime.getRuntime(); System.out.println("Ejecutando " + sentencia[0] + " " + sentencia[1] + " " + sentencia[2]); Process proc = rt.exec(sentencia);// // Hilo para manejar algun mensaje de error y reportarlo al cliente HiloMensajero hiloDeError = new HiloMensajero(proc.getErrorStream(), "ERROR"); // Hilo para manejar la salida y reportarla al cliente HiloMensajero hiloDeSalidas = new HiloMensajero(proc.getInputStream(), "..\\"); // Se encargan de lo indicado hiloDeError.start(); hiloDeSalidas.start(); // Valor de retorno de llamada al programa MS-DOS int valorSalida = proc.waitFor(); System.out.println("Valor De Retorno: " + valorSalida); } catch (Throwable t) { t.printStackTrace(); } } }

86

A.4. Programa Sensor /* Archivo: VentanaSensor.java */ package servidor; import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.border.*; public class VentanaSensor extends JApplet { private JLabel tituloPrincipal = new JLabel(); private JLabel ptoLabel = new JLabel(); private JLabel ipLabel = new JLabel(); private JLabel titulo = new JLabel("Información del SENSOR:"); public JTextField ptoTextField = new JTextField(); public JTextField ipTextField = new JTextField(); private JLabel avisos = new JLabel(); String nueva_linea = ""; // cadena que recibe informacion en tiempo real int i = 0; JButton conectarBtn = new JButton("Iniciar"), limpiarBtn = new JButton("Limpiar"), salirBtn = new JButton("Salir"); public JTextArea txtArea = new JTextArea("", 10, 10); JScrollPane scPanel = new JScrollPane(txtArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); //Iniciar class B1L implements ActionListener { public void actionPerformed(ActionEvent e) { LectorLPT1 oSensor = new LectorLPT1(ipTextField,ptoTextField, txtArea); ptoTextField.setEnabled(false); ipTextField.setEnabled(false); conectarBtn.setEnabled(false); conectarBtn.setToolTipText("SENSOR FUNCIONANDO"); } } //Limpiar class B2L implements ActionListener { public void actionPerformed(ActionEvent e) { nueva_linea = null; txtArea.setText("Servicio de SENSOR"); avisos.setForeground(Color.blue); avisos.setText("Listo..."); } } public void init() { Container cp = getContentPane(); cp.setLayout(null); Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black); scPanel.setBorder(brd);

87

cp.add(ipLabel); ipLabel.setBounds(32, 30, 60, 20); ipLabel.setText("IP cliente:"); ipLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(ipTextField); ipTextField.setBounds(90, 25, 99, 25); ipTextField.setText(""); cp.add(ptoLabel); ptoLabel.setBounds(202, 30, 50, 20); ptoLabel.setText("Puerto:"); ptoLabel.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(ptoTextField); ptoTextField.setBounds(247, 25, 52, 25); ptoTextField.setText("8080"); conectarBtn.addActionListener(new B1L()); cp.add(conectarBtn); conectarBtn.setBounds(320, 25, 90, 25); conectarBtn.setToolTipText("Iniciar SENSOR"); cp.add(titulo); titulo.setBounds(35, 70, 170, 15); titulo.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); cp.add(avisos); avisos.setBounds(35, 300, 170, 15); avisos.setText("Listo..."); avisos.setForeground(Color.red); avisos.setFont(new java.awt.Font("Dialog", java.awt.Font.ITALIC, 10)); limpiarBtn.addActionListener(new B2L()); cp.add(limpiarBtn); limpiarBtn.setBounds(90, 280, 90, 25); limpiarBtn.setToolTipText("Limpiar Area de Texto"); cp.add(salirBtn); salirBtn.setBounds(260, 280, 90, 25); salirBtn.setToolTipText("Haga click si desea terminar"); salirBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stop(); destroy(); LanzadorDeApplet.cerrar(); } }); cp.add(scPanel); scPanel.setBounds(32, 95, 385, 160); txtArea.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 11)); txtArea.setEditable(false); } public static void main(String[] args) { LanzadorDeApplet.run(new VentanaSensor(), 450, 365,"LECTURA EN PUERTO PARALELO"); } }

88

/* Archivo: LectorLPT1.java */ package servidor; import javax.swing.JTextArea; import javax.swing.JTextField; import parport.ParallelPort; import servidor.Transmisor; class LectorLPT1 extends Thread{ String dirIPCliente; String puerto2; JTextArea myArea; public LectorLPT1(JTextField dirIPc, JTextField pto,JTextArea area) { dirIPCliente = dirIPc.getText(); puerto2 = pto.getText(); //Puerto de Monitoreo default = 8080 myArea = area; setDaemon(true); start(); } public void run() { try { ParallelPort lpt1 = new ParallelPort(0x378); // Direccion Base = 378,

// PtoControl = 378h+2 = 37Ah try { Transmisor z = new Transmisor(dirIPCliente,puerto2,"Se habilito escritura para Sensores del Robot"); } catch(Exception exc) {System.out.println("Error! - " + exc.toString()); } int valorLeido = 0; int valorAnterior; String textCache = "Servicio de Sensor en Puerto Paralelo"; while(true) { valorAnterior = valorLeido; valorLeido = lpt1.read(); if(valorLeido != valorAnterior) { System.out.println("Valor de codigo de Sensores leido: " +valorLeido +"\n"); Transmisor s = new Transmisor(dirIPCliente,puerto2,"Status = " + valorLeido); } textCache = textCache +"Valor de lectura:" +valorLeido +"\n"; //concatena myArea.setText(textCache); //posiciona al final de JTextArea: myArea.setCaretPosition(myArea.getDocument().getLength()); // restringe el tamano del texto int idealSize = 3000; int maxExcess = 2000; int excess = myArea.getDocument().getLength() - idealSize; if (excess >= maxExcess) { myArea.replaceRange("", 0, excess); } Thread.sleep(1000);//El muestreo ocurre cada 1 segundo yield(); }//fin de ciclo while infinito } catch(Exception exc) {System.out.println("Error!");} System.out.println("Programa Terminado"); } }

89

/* Archivo: Transmisor.java */ package servidor; import java.net.*; import java.io.*; public class Transmisor { Socket socket; PrintWriter writer; public Transmisor(String dir_ip, String puerto, String cadena) { boolean exito = true; try { socket = new Socket(dir_ip, Integer.valueOf(puerto).intValue()); writer = new PrintWriter(socket.getOutputStream(), true); try { writer.println(cadena); } catch (Exception exc) { System.out.println("Error! No se pueden enviar datos"); } try { socket.close(); } catch (Exception exc) { System.out.println("No se cerro la comunicacion de manera normal"); } } catch (Exception exc) { System.out.println("No existe Monitor o Falta indicar IP como Parametro"); } } }

90

/* * Archivo LanzadorDeApplet.java * Clase que agrega un applet a un Frame para poder visualizarlo * sin navegador y desde la linea de comandos * */ package servidor; import javax.swing.*; import java.awt.event.*; import java.applet.*; public class LanzadorDeApplet { static JFrame frame = null; public static void setupClosing(JFrame frame) { frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void run(Applet applet, int width, int height, String mensaje) { frame = new JFrame(mensaje); setupClosing(frame); frame.getContentPane().add(applet); frame.setSize(width, height); applet.init(); applet.start(); frame.setVisible(true); } public static void cerrar(){ frame.dispose(); } } /* Archivo: ParalellPort.java */ package parport; public class ParallelPort { /** La direccion base del puerto (e.g. 0x378 es la direccion base para LPT1) */ private int portBase; /** Para construir un objeto de la clase ParallelPort, * se necesita la direccion base del puerto */ public ParallelPort (int portBase) { this.portBase = portBase; } /** Lee un byte de los pines del registro de ESTADO del puerto paralelo. * * El byte leido contiene 5 bits validos, que corresponden a los 5 bits de * entrada del registro de ESTADO del puerto paralelo (El registro de ESTADO * se encuentra en "portBase + 1", e.g. la direccion del registro de ESTADO * para LPT1 es 0x379).

91

* * La siguiente tabla muestra el contenido del byte: * * Bit | Pin # | Estado de impresora | Invertido * -----+-------+----------------------+----------- * 7 | ~11 | Busy | Si * 6 | 10 | Acknowledge | * 5 | 12 | Out of paper | * 4 | 13 | Selected | * 3 | 15 | I/O error | * * Note que el Pin 11 esta invertido, esto significa que una entrada "Alto" * en el pin es un 0 en el bit 7 y una entrada "Bajo" significa 1 en bit 7 */ public int read () { return ParallelPort.readOneByte (this.portBase+1); } /** Escribe un byte al registro de DATOS del puerto paralelo. * El byte es escrito a los pines de DATOS del puerto. Los pines de datos se * localizan en la direccion base del puerto (e.g. La direccion del registro * de DATOS para LPT1 es 0x378). * * La siguiente tabla muestra como es escrito el byte * * Bit | Pin # | DATOS de impresora * -----+-------+-------------- * 7 | 9 | DATA 7 * 6 | 8 | DATA 6 * 5 | 7 | DATA 5 * 4 | 6 | DATA 4 * 3 | 5 | DATA 3 * 2 | 4 | DATA 2 * 1 | 3 | DATA 1 * 0 | 2 | DATA 0 */ public void write (int oneByte) { ParallelPort.writeOneByte (this.portBase, oneByte); } /** Lee un byte de la direccion especificada. * (normalmente la direccion es el registro de ESTADO del puerto) */ public static native int readOneByte (int address); /** Escribe un byte a la direccion especificada * (normalmente la direccion es el registro de DATOS del puerto) */ public static native void writeOneByte (int address, int oneByte); static { System.loadLibrary("parport"); }

}

92