Universidad de Costa Rica Facultad de Ingeniería Escuela de Ingeniería Eléctrica IE – 0502 Proyecto Eléctrico Envío de video hacia un cliente remoto utilizando el sistema video4linux y desarrollo de una aplicación gráfica GTK sobre una plataforma GNU/Linux, para el control del brazo robot Stäubli RX90 Por: Carlos Alberto Montes Solano Ciudad Universitaria Rodrigo Facio Diciembre del 2005 Envío de video hacia un cliente remoto utilizando el sistema video4linux y desarrollo de una aplicación gráfica GTK sobre una plataforma GNU/Linux, para el control del brazo robot Stäubli RX90 Por: Carlos Alberto Montes Solano Sometido a la Escuela de Ingeniería Eléctrica de la Facultad de Ingeniería de la Universidad de Costa Rica como requisito parcial para optar por el grado de: BACHILLER EN INGENIERÍA ELÉCTRICA Aprobado por el Tribunal: _________________________________ Ing. Francisco Siles Canales, Lic. Profesor Guía _________________________________ Ing. Federico Ruiz Ugalde, Lic. Profesor lector _________________________________ Ing. Peter Zeledón Méndez, Lic. Profesor lector ii DEDICATORIA A Dios, por haberme dado salud y todas las cosas necesarias en la vida, para alcanzar una meta más en mis estudios. A mis padres, quienes por haberme brindado todo su apoyo y por mostrarme siempre el buen ejemplo, han hecho de mis estudios una obligación moral con la cual espero retribuirles un poco de satisfacción. A mis hermanos, por estar en todo momento a mi lado y cuyos consejos me han sido de mucha utilidad. A mis demás familiares y profesores, a quienes también agradezco su apoyo y los conocimientos que me han enseñado. Nuevamente a Dios por haber puesto a mi lado a todas estas personas, sin las cuales lo alcanzado hasta ahora no hubiera sido posible. iii RECONOCIMIENTOS Particularmente agradezco al ingeniero Francisco Siles Canales su interés y la ayuda brindada desde el inicio de este proyecto. iv ÍNDICE GENERAL ÍNDICE DE FIGURAS..................................................................................vii ÍNDICE DE TABLAS.................................................................................. viii NOMENCLATURA........................................................................................ix RESUMEN.......................................................................................................xi CAPÍTULO 1: Introducción .........................................................................12 1.1 1.2 Justificación .................................................................................................................12 Objetivos......................................................................................................................13 1.1.1 Objetivo general..................................................................................................13 1.2.1 Objetivos específicos ..........................................................................................13 1.3 Metodología .................................................................................................................13 1.4 Publicación del código.................................................................................................14 CAPÍTULO 2: Desarrollo teórico ................................................................15 2.1 2.2 2.3 2.4 2.5 2.6 Sistema operativo de licencia libre GNU/Linux..........................................................15 2.1.1 El sistema Video4Linux......................................................................................16 Comunicación por sockets ...........................................................................................16 Hilos de procesamiento (pthreads) ..............................................................................17 2.3.1 pthread_create() ..................................................................................................18 2.3.2 pthread_exit()......................................................................................................18 2.3.3 pthread_join()......................................................................................................19 2.3.4 Mutex ..................................................................................................................19 Comunicación y programación del puerto serial .........................................................20 2.4.1 El protocolo RS232.............................................................................................20 2.4.2 Ajustes de la comunicación (el miembro c_cflag)..............................................22 2.4.3 Acceso y programación del puerto serial............................................................24 Aplicaciones gráficas GTK..........................................................................................25 2.5.1 Programación básica de ventanas .......................................................................25 La robótica ...................................................................................................................27 CAPÍTULO 3: El brazo robot Stäubli RX90 ..............................................29 3.1 3.2 Componentes externos del robot..................................................................................30 Comunicación con el robot ..........................................................................................31 v 3.2.1 Pines relevantes del conector serial ....................................................................31 3.2.2 Ajuste de las variables de comunicación ............................................................32 3.3 El lenguaje V+ y algunos comandos............................................................................32 CAPÍTULO 4: Desarrollo y funcionamiento de la comunicación clienteservidor............................................................................................................33 4.1 Funcionamiento del servidor .......................................................................................33 4.1.1 Configuración del puerto serial...........................................................................35 4.1.2 Recepción y ejecución de comandos provenientes del cliente ...........................36 4.1.3 Función de interrupción y lectura de datos del puerto serial ..............................37 4.2 Funcionamiento del cliente..........................................................................................39 4.2.1 Aplicación GTK..................................................................................................39 4.2.2 GTK y la programación con múltiples hilos de procesamiento..........................40 4.2.3 Recepción y despliegue de mensajes en la aplicación gráfica............................41 CAPÍTULO 5: Control remoto utilizando aplicación GTK ......................44 5.1 5.2 5.3 Rutina de calibración ...................................................................................................44 Pasos para energizar el robot y deshabilitar el modo de simulación ...........................44 Creación de una rutina de movimientos con el comando drive...................................45 CAPÍTULO 6: Conclusiones y recomendaciones .......................................49 6.1 6.2 Conclusiones................................................................................................................49 Recomendaciones ........................................................................................................50 BIBLIOGRAFÍA............................................................................................52 APÉNDICES...................................................................................................53 ANEXOS .........................................................................................................67 vi ÍNDICE DE FIGURAS Figura 2.1 Conector DB25 macho ....................................................................................21 Figura 2.2 Conector DB9 macho ......................................................................................21 Figura 2.3 Elementos del brazo robot ...............................................................................28 Figura 3.1 El brazo robot Stäubli RX90 ...........................................................................29 Figura 3.2 Robot y sus componentes externos (Laboratorio de Control UCR)................30 Figura 4.1 Diagrama de flujo del programa servidor.c.....................................................34 Figura 4.2 Interfaz gráfica GTK .......................................................................................39 Figura 5.1 Energizando el robot y deshabilitando el modo de simulación.......................45 Figura 5.2 Método para crear rutina .................................................................................46 Figura 5.3 Comando de fin de rutina ................................................................................47 Figura 5.4 Ejecución de la rutina ......................................................................................48 vii ÍNDICE DE TABLAS Tabla 2.1 Asignación de pines, conector DB25................................................................21 Tabla 2.2 Asignación de pines, conector DB9..................................................................21 Tabla 2.3 Constantes del miembro c_cflag.......................................................................23 Tabla 2.4 Archivos asociados al puerto serial, sistemas UNIX........................................24 Tabla 3.1 Comandos del lenguaje V+...............................................................................32 viii NOMENCLATURA GNU/Linux: GNU/Linux es la denominación defendida por Richard Stallman y otros para el sistema operativo que utiliza el «kernel» Linux en conjunto con las aplicaciones de sistema creadas por el proyecto GNU. Comúnmente este sistema operativo es denominado simplemente Linux. UNIX: UNIX® (o Unix) es un sistema operativo portable, multitarea y multiusuario; desarrollado en principio por un grupo de empleados de los laboratorios Bell de AT&T, entre los que figuran Ken Thompson, Dennis Ritchie y Douglas McIlroy. socket: Objeto de software utilizado por un cliente para conectarse a un servidor; los componentes básicos incluyen el número de puerto y la dirección de red del host local. IEEE: Corresponde a las siglas en inglés de «Institute of Electrical and Electronics Engineers», Instituto de Ingenieros Eléctricos y Electrónicos, una asociación estadounidense dedicada a la estandarización. Es una asociación internacional sin fines de lucro formada por profesionales de las nuevas tecnologías, como ingenieros de telecomunicaciones, ingenieros electrónicos, Ingenieros en informática, etc. POSIX: Acrónimo de «Portable operating system interface, Unix based» (Sistema operativo portable basado en UNIX). Una familia de estándares de llamadas al sistema definidos por el IEEE y especificados formalmente en el IEEE 1003, intenta estandarizar las interfaces de los sistemas operativos para que las aplicaciones se ejecuten en distintas plataformas. baud rate: Velocidad a la cual son transmitidos los datos de telecomunicación, medida en bytes por segundo. GNOME: Entorno de escritorio para sistemas operativos de tipo Unix bajo tecnología X Window, se encuentra disponible actualmente en más de 35 idiomas. Forma parte oficial del proyecto GNU. Ambiente gráfico de la red GNU, modelado por objetos. pthreads: Abreviatura para hilos POSIX y una biblioteca que proporciona funciones POSIX para crear y manipular hilos de programación. La biblioteca se ix encuentra con mayor frecuencia en sistemas Linux y Unix, sin embargo existe también una versión para Windows. textview: Objeto de la librería GTK, utilizado para desplegar texto dentro de una ventana. frame: Objeto de la librería GTK, utilizado para generar un marco dentro de la ventana. Gas (GNU): Programa del proyecto GNU, que lleva a cabo la conversión de código de ensamblador a código binario, o código de máquina. Ld (GNU): Programa enlazador del proyecto GNU, que utiliza un script como guía para diseñar un archivo a partir del código de ensamblador. TCP/IP: Conjunto básico de protocolos de comunicación de redes, popularizado por Internet, que permiten la transmisión de información en redes de computadoras. El nombre TCP/IP proviene de dos protocolos importantes de la familia, el «Transmission Control Protocol» (TCP) y el «Internet Protocol» (IP). UDP: Acrónimo de «User Datagram Protocol» (Protocolo de datagrama a nivel de usuario), perteneciente a la familia de protocolos TCP/IP. x RESUMEN El objetivo principal de este proyecto es controlar, en forma remota, el brazo robot Stäubli RX90 bajo una plataforma abierta. En este caso se utilizó el sistema operativo de licencia libre GNU/Linux. Para lograr la visualización a distancia del robot, se adaptó un programa existente que hace uso de la herramienta video4Linux, y que permite una comunicación cliente-servidor. Posteriormente al análisis de las fuentes bibliográficas se establecieron las principales necesidades que debían cubrirse. A partir de este punto se dio inicio a la programación en el lenguaje C que debía abarcar, en el lado del servidor, la comunicación serial con el controlador del robot y la sincronización de recepción de datos, provenientes tanto del cliente como del controlador mismo; mientras tanto en el lado del cliente existía la necesidad de generar una interfaz gráfica, amigable para el operador y capaz de enviar y recibir mensajes del servidor, además de desplegar el video en tiempo real. Los tres puntos de interés fundamentales del proyecto fueron: la programación de la comunicación a través del puerto serial, la programación de aplicaciones gráficas utilizando la librería GTK y la implementación de múltiples hilos de procesamiento. Uno de los principales problemas que surgió a lo largo del proyecto, fue el hecho de que la librería GTK no soporta, por sí misma, la programación de sus objetos dentro de múltiples hilos de procesamiento. La solución al problema surgió con la utilización de algunas funciones de la librería GLib, las cuales permitieron la manipulación ordenada de las variables GTK dentro de los distintos hilos. Como resultado se obtuvo una comunicación bidireccional entre las dos partes principales: el cliente y el servidor. La primera parte (el servidor) se encarga de capturar y enviar las imágenes de video, además atiende y ejecuta los comandos provenientes del cliente y, finalmente, recibe la información generada por el robot y se la envía al cliente. La segunda parte (el cliente) presenta una interfaz gráfica, donde se muestra el video del robot en tiempo real y la información del estado del robot, además permite accionar los distintos motores del brazo mecánico, por medio de botones y entradas de texto. De esta forma se alcanzó el objetivo principal del proyecto: el control remoto del robot Stäubli RX90. Tanto el sistema operativo, como el lenguaje de programación empleados, demostraron poseer las cualidades necesarias (por ejemplo el sistema video4Linux y la librería GTK) para la ejecución y creación de programas operados a distancia y en tiempo real. Finalmente y luego de llevar a cabo algunas pruebas prácticas al programa total, se recomienda la implementación de un algoritmo de compresión para las imágenes de video, debido a que la tasa de transmisión actual no es suficiente para ser implementada en una red real. xi CAPÍTULO 1: Introducción 1.1 Justificación El control remoto de elementos electromecánicos es una de las áreas ingenieriles más estudiadas en la actualidad. La razón principal de este hecho es probablemente el impulso del ser humano de llegar e investigar lugares desconocidos para él, que incluso podrían no ofrecer las condiciones necesarias para la vida humana. Por otra parte campos tales como la medicina, han incursionado también en la implementación de artefactos electrónicos controlados a distancia. Las cada vez más pequeñas cámaras de captura de video, y los elementos accionados en forma remota han venido a reducir riesgos y malestares en pacientes que requieren operaciones de alta precisión. El análisis anterior revela la importancia de algunos de los principales componentes de este proyecto: la captura, envío y reproducción de video en tiempo real. Estos aspectos representan un pilar en el control remoto de elementos; además en aplicaciones tales como la telefonía celular, conforman uno de los campos de investigación e inversión más grandes de la tecnología de telecomunicación. Se debe también hacer hincapié en la plataforma sobre la cual se pretende realizar este proyecto. El sistema operativo de licencia libre GNU/Linux presenta las cualidades necesarias para la creación de software de alta calidad, además ofrece altos niveles de robustez. La idea revolucionaria de códigos publicados en forma abierta y con la libertad de ser reusados por cualquier otro programador, ha venido a representar una aceleración en el desarrollo de programas para aplicaciones en todo tipo de áreas de investigación y comercialización. De esta forma la adquisición de compiladores y programas para el procesamiento de imágenes de video, como los utilizados en este proyecto, son procedimientos totalmente gratuitos y legales. Esto representa una facilidad de adquisición de herramientas, para cualquier desarrollador de software que desee utilizar estos componentes, con el objetivo de producir proyectos personales o laborales. 12 13 1.2 Objetivos 1.1.1 Objetivo general Controlar el brazo mecánico Stäubli RX90 desde un cliente remoto, utilizando el sistema video4Linux. 1.2.1 Objetivos específicos • Utilizar el sistema operativo de licencia libre GNU/Linux para capturar y transmitir, mediante un servidor, imágenes de video en tiempo real. • Crear una interfaz gráfica para el control del brazo robot, por parte del cliente, utilizando los objetos de la librería GTK. • Utilizar el sistema Video4Linux para la captura del video en tiempo real. • Realizar pruebas de tráfico y velocidad de conexión para el sistema desarrollado, con el fin de brindar un punto de comparación, para proyectos futuros. 1.3 Metodología La metodología empleada en la realización de este trabajo fue: • Se lleva a cabo una estructuración inicial del proyecto, donde se determinan el objetivo general y los objetivos específicos del mismo. • Se plantean los diferentes componentes o áreas de investigación que conforman el trabajo, basándose en el objetivo general y en los objetivos específicos. • Una vez que se tienen claras las áreas de investigación, se recopilan aquellos proyectos y tesis anteriores, referentes a las áreas de interés. • Se estudian a fondo los elementos que componen el manejo básico (no remoto) y la comunicación con el robot Stäubli RX90. 14 1.4 • Se lleva cabo una investigación sobre los protocolos de comunicación entre el controlador, al cual está conectado el brazo robot, y la computadora que hará la función de servidor. • Se genera un programa inicial que corre en lado del servidor, y que permite controlar algunos movimientos del robot. • Luego, se da inicio a la modificación y adaptación de dos programas que permitan establecer una comunicación entre el servidor y el cliente [1]. La aplicación en el lado del servidor es la encargada de enviar el video en tiempo real, y además recibe información (comandos) provenientes del cliente. La aplicación que corre en el lado del cliente presenta un mayor grado de complejidad, ya que posee una interfaz gráfica capaz de desplegar el video y la información del robot, además de enviar comandos que provocan los movimientos del brazo. • Finalmente se llevan a cabo algunas pruebas de tráfico y velocidad de conexión, para determinar la estabilidad y seguridad con la que el programa desarrollado puede operar. Publicación del código La publicación del software generado se llevó a cabo bajo los términos de la licencia pública general del proyecto GNU, también conocida como GPL «GNU General Public License». De esta forma se pretende colaborar con la distribución y creación de software libre, así como fomentar la continuidad de esta gran ideología. 15 CAPÍTULO 2: Desarrollo teórico 2.1 Sistema operativo de licencia libre GNU/Linux El sistema operativo GNU/Linux es una reproducción libre del sistema UNIX. Linux fue desarrollado primeramente por Linus Torvals en la Universidad de Helsinki en Finlandia, quien construyó el primer núcleo o «kernel» Linux y que, desde entonces es la base del sistema operativo del mismo nombre. Sin embargo el funcionamiento de esta plataforma va más allá de su núcleo, Linux posee utilidades, comandos y otros programas desarrollados en su mayoría por la fundación de software libre GNU, razón por la cual desde un principio se adoptó el nombre formal de GNU/Linux. El hecho de ser éste un sistema operativo abierto (de código abierto) ha venido a contribuir con su rápido crecimiento. Programadores alrededor del mundo y conectados a través de internet, han colaborado también en el mejoramiento y creación de nuevas aplicaciones exclusivas para Linux, lo que ha dado como resultado un trabajo conjunto con un fin común: proveer a la informática de un sistema cada vez más útil y estable. Linux ofrece todas las interfaces de programación comunes de los sistemas estándar Unix, además de las utilidades comunes Unix. Otro aspecto positivo del sistema GNU/Linux es el empleo eficiente que éste hace de los recursos de hardware, provistos en aquellas máquinas compatibles con IBM. De esta forma se obtiene una plataforma robusta capaz de dar respaldo a software de alta calidad. En lo referente a los lenguajes de programación, Linux posee un amplio soporte, además de compiladores para los lenguajes actualmente en uso. El lenguaje de programación empleado en la realización de este proyecto es C, pues éste permite la creación de programas estructurados por definiciones y llamado de funciones. El flujo de los programas es controlado usando lazos, si las declaraciones o la función son llamadas. Al igual que otros lenguajes, las entradas y salidas de los programas pueden ser direccionadas a la terminal de consola o a archivos específicos. Sin embargo, C ofrece ciertas ventajas que hacen que algunos programadores lo prefieran sobre otros lenguajes. Por la estructuración de sus programas, la posibilidad de almacenamiento de datos juntos, en series o estructuras se pueden generar aplicaciones más concisas y eficientes. El leguaje C ha sido empleado, incluso, en el desarrollo de sistemas operativos tales como Linux. En los procesos de generación de tales sistemas, los más expertos programadores han implementado un gran número de funciones, las cuales se encuentran disponibles para cualquier desarrollador de software a través de librerías. 16 La compilación del código generado se lleva a cabo utilizando el compilador gcc1 que es rápido, flexible y riguroso con el estándar de C ANSI. gcc se encarga de realizar (o encargar el trabajo a otras utilidades) el preprocesado del código, la compilación, y el enlazado. El resultado de esta compilación es un código binario ejecutable para nuestra arquitectura. No está de más mencionar el hecho de que, realmente gcc no genera directamente el código binario, sino más bien genera un código en ensamblador. El paso de lenguaje de ensamblador al lenguaje de máquina lo lleva a cabo el ensamblador de GNU (gas), y el enlazado de los objetos resultantes lo realiza el enlazador de GNU (ld). A pesar de la subdivisión de estos procesos, el compilador gcc se encarga de generar el binario ejecutable a partir del programa en código C automáticamente. 2.1.1 El sistema Video4Linux Video4Linux (V4L) es una interfaz para aplicaciones de programación de video para la plataforma GNU/Linux. Este sistema permite el acceso al hardware empleado en la captura de imágenes de video, tales como las cámaras web («webcams»). El extenso y creciente soporte de dispositivos de video, así como las rutinas de bajo nivel provistas por V4L, lo hacen una herramienta atractiva para los desarrolladores de aplicaciones relacionadas con el procesamiento de imágenes. Es importante recalcar que las etapas de captura y envío de video no comprimido y los aspectos básicos de la comunicación entre el cliente y el servidor, de este proyecto, fueron tomados de [1]. El trabajo de graduación mencionado anteriormente profundiza en el tema del significado de Video4Linux, además explica a fondo una forma de implementarlo en aplicaciones de captura de video. Se recomienda al lector interesado consultar la referencia nombrada, para un mejor entendimiento al respecto. 2.2 Comunicación por sockets Al igual como se mencionó en el apartado 2.1.1, la comunicación y el envío de datos entre el cliente y el servidor será un tema que se discutirá poco en este trabajo [1], no obstante se hace a continuación una aclaración de los elementos básicos que representan la utilización de sockets. De igual forma se comenta la implementación que se le da a estos, en la comunicación cliente-servidor. 1 Originalmente acrónimo de GNU C Compiler. Actualmente se refiere a GNU Compiler Collection, debido a la posibilidad de compilar otros lenguajes como Ada, Java o Fortran. 17 Un socket es una interfaz de entrada-salida de datos que permite la intercomunicación entre procesos. Los procesos pueden estar ejecutándose en el mismo o en distintos sistemas, unidos mediante una red. Las librerías y funciones de sockets son usadas para representar la conexión entre un programa de cliente y un programa de servidor. Hay dos tipos de sockets extensamente usados: sockets de flujo (stream sockets) y sockets de datagrama (datagram sockets). Los sockets de flujo son los más utilizados y hacen uso del protocolo TCP, el cual provee un flujo de datos bidireccional, secuenciado, sin duplicación de paquetes y libre de errores. Este socket presenta una conexión punto a punto, lo que implica una comunicación particular y segura entre el cliente y el servidor. Por esta razón este es el socket empleado en el envío y recepción de los comandos que accionan el brazo robot, que se controla desde el cliente remoto. Los sockets de datagrama hacen uso del protocolo UDP, el cual provee un flujo de datos bidireccional, pero los paquetes pueden llegar fuera de secuencia, pueden no llegar o contener errores. A estos sockets también se les conoce como sockets sin conexión, porque no hay que mantener una conexión activa, como en el caso de los stream sockets. Debido a que el procesamiento y confiabilidad de la información es menor, el envío y posible recepción de los datos se lleva a cabo en un menor tiempo. Por tal razón, este protocolo es preferido para el intercambio de información en tiempo real, y por consiguiente es el empleado en el envío de video cliente-servidor, para el control del brazo robot. 2.3 Hilos de procesamiento (pthreads) Normalmente cuando se habla de programas de alto nivel, se piensa en programas secuenciales, es decir que posee un inicio donde se declaran variables y funciones, una secuencia determinada de ejecución y un final. En cualquier tiempo dado, existe en el programa un solo punto de ejecución. Los hilos de procesamiento, también conocidos como threads, presentan un comportamiento similar al de los programas secuenciales, en el aspecto de que se desarrollan de igual forma a lo largo del programa dentro del que están corriendo. No obstante los hilos no representan programas por sí solos, por el contrario estos dependen de un programa base que los crea y que los provee de recursos. La idea principal detrás de la programación con múltiples hilos, se basa en la creación de programas capaces de realizar diferentes tareas al mismo tiempo. De esta forma, un programa puede tener un hilo destinado a la realización continua de una tarea específica, mientras un segundo hilo realiza en forma paralela e independiente otra labor. Para los sistemas UNIX, los hilos estandarizados para el lenguaje de programación C han sido especificados por el estándar de la IEEE POSIX, y se conocen como pthreads. 18 El flujo de control independiente, que proporcionan los hilos, se basa en que estos poseen elementos propios de cada uno: punteros de pila, registros, propiedades de coordinación, señales de bloqueo y mantenimiento en espera, además de información específica del hilo. A continuación se presentan algunas de las principales funciones relacionadas con esta librería. 2.3.1 pthread_create() Esta función crea el thread: #include <pthread.h> int pthread_create (pthread_t *thread_id, const pthread_attr_t *attributes, void *(*thread_function)(void *), void *arguments); permite asociar la creación del nuevo hilo, con el nombre del mismo hilo definido en algún punto anterior del programa. Los atributos son otro tipo de datos que permiten el ajuste de algunos parámetros del hilo. Si se quieren usar los ajustes predeterminados de la librería se pasa el argumento nulo (NULL). thread_function es la función que el nuevo hilo ejecuta, el hilo se terminará cuando esta función termine, o cuando éste sea explícitamente eliminado. *arguments es un puntero vacío, y representa el único argumento que se le puede pasar a esta función. pthread_t 2.3.2 pthread_exit() Esta es la función que se emplea para terminar explícitamente el hilo. #include <pthread.h> int pthread_exit (void *status); status es el valor de retorno de la función. 19 2.3.3 pthread_join() La subrutina pthread_join bloquea la llamada de un hilo, hasta que otro hilo termine su ejecución. El estado de terminación del hilo que está corriendo es devuelto en el parámetro de estado **value_ptr. #include <pthread.h> int pthread_join (pthread_t thread, void **value_ptr); 2.3.4 Mutex Mutex es un objeto de programa que permite a múltiples hilos del programa compartir el mismo recurso, como el acceso a un archivo, pero no en forma simultáneamente. Los mutex son utilizados para prevenir las inconsistencias de datos del programa, relacionadas a múltiples cambios producidos por distintos hilos, que se ejecutan al mismo tiempo. Esto ocurre cuando dos o más hilos necesitan realizar operaciones sobre el mismo espacio de memoria y los resultados de las operaciones dependen del orden en que éstas fueron ejecutadas. Entonces los mutex permiten establecer un orden de ejecución consecutivo y no paralelo, sobre aquellas variables o recursos compartidos por los distintos hilos del programa. Cada vez que un recurso global es accesado por más de un hilo en un mismo instante, este recurso debe tener un mutex asociado a sí mismo. Existen 5 funciones principales relacionadas con mutex: int pthread_mutex_init (pthread_mutex_t *mut, const pthread_mutexattr_t *attr); donde la variable en cuestión es mut y attr representa los atributos relacionados con la misma. Para llevar a cabo bloqueos o liberar variables se tienen las siguientes funciones: int pthread_mutex_lock (pthread_mutex_t *mut); int pthread_mutex_unlock (pthread_mutex_t *mut); int pthread_mutex_trylock (pthread_mutex_t *mut); int pthread_mutex_destroy (pthread_mutex_t *mut); 20 2.4 Comunicación y programación del puerto serial La comunicación serial, como su nombre lo indica, emplea un protocolo de envío y recepción de datos en forma secuencial. Es decir, se presenta una transferencia de información a un ritmo de un bit por instante de tiempo. De esta forma los bytes son enviados como una serie de bits, espaciados por periodos cortos de tiempo y seguidos unos de otros. Por esta razón se dice que los bits son enviados o recibidos uno a la vez. Dentro de los elementos de comunicación serial se encuentran un gran número de dispositivos, tales como teclados, ratones y módems. 2.4.1 El protocolo RS232 Este protocolo es un estándar de interfases eléctricas, para la comunicación serial, definido por la Asociación de Industrias Electrónicas (EIA por sus siglas en ingles: «Electronic Industries Association»). Las señales del RS232 son representadas por niveles de voltaje, referidas a una tierra común. El estado de marca (activo) de la señal, posee un valor negativo respecto a la referencia común, cuyo rango de activación es de -3V a -12V. Por su parte el estado espacio (inactivo) tiene el valor positivo respecto a la misma referencia, el cual oscila en un rango de igual magnitud, pero positivo (3V-12V). El estándar EIA232 denomina al elemento principal de la conexión con las siglas DTE «Data Terminal Equipment» (Equipo de Terminal de Datos, que por lo general es la computadora), el cual posee un conector macho DB25 (25 pines), o DB9 (9 pines) como es el caso del que se empleará en la comunicación con el controlador del robot en este proyecto. El dispositivo en el otro extremo de la conexión se le conoce como DCE «Data Communication Equipment» (Equipo de Comunicación de Datos). Como se mencionó anteriormente, tanto el DTE como el DCE poseen conectores tipo macho, por lo que el cable que comunique a ambos elementos deberá tener terminales adecuadas según el número de pines y tipo hembra. Las tablas 2.1 y 2.2 presentan una descripción de los pines, tanto para el conector DB25 como para el DB9. 21 Figura 2.1 Conector DB25 macho Tabla 2.1 Asignación de pines, conector DB25 Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 Descripción Tierra TXD - Datos transmitidos RXD - Datos recibidos RTS - Petición de envío CTS - Limpia para enviar DSR - Data Set preparado GND - Tierra Lógica DCD - Detección de acarreo Reservado Reservado No asignado DCD secundario CTS secundario Pin 14 15 16 17 18 19 20 21 22 23 24 25 Descripción TXD secundario Reloj de transmisión RXD secundario Reloj receptor No asignado RTS secundario DTR - Terminal de datos preparada Señal de detección de calidad Detección de Ring Selección del Data Rate Reloj de transmisión No asignado Figura 2.2 Conector DB9 macho Tabla 2.2 Asignación de pines, conector DB9 Pin 1 2 3 4 5 Descripción DCD - Detección de acarreo RXD - Datos recibidos TXD - Datos transmitidos DTR - Terminal de datos preparada GND - Tierra Lógica Pin 6 7 8 9 Descripción Data Set preparado RTS - Petición de envío CTS - Limpia para enviar Detección de Ring 22 2.4.2 Ajustes de la comunicación (el miembro c_cflag) La primera variable que debe definirse, para poder establecer una comunicación serial, es la razón del cambio de estado de la señal por segundo, o baud rate. En el caso de la programación en el lenguaje C, la librería <termios.h> define la estructura de la terminal de control así como las funciones de control POSIX. El miembro c_cflag es el encargado de controlar el baud rate, para lo cual posee las constantes descritas en la tabla 2.3, para el soporte de las distintas configuraciones. Otro ajuste relevante es la definición del tamaño de los datos con los que se va trabajar. Como se puede observar en la tabla 2.3, el miembro c_cflag también determina el valor de esta variable. De esta forma se pueden implementar diálogos de comunicación con palabras de 5, 6, 7 y 8 bits según sea el caso. El indicador c_cflag también establece el tipo de paridad, el número de bits de stop y el control de flujo por hardware. Los controladores («drivers») de los seriales UNIX son capaces de soportar bits de paridad impares, pares y ninguna paridad por completo. En los formatos de datos asincrónicos es común la definición de datos 8N1, 7E1, 7O1 y 7S1. Por ejemplo, 8N1 significa que se utilizarán palabras de 8 bits, no habrá paridad y se usará un bit de stop. En algunas ocasiones es necesario regular el flujo de datos entre dos interfases de comunicación serial. Para este motivo existen dos métodos comunes de la comunicación asincrónica: el control de flujo por software, y el control de flujo por hardware. Para el segundo método, nótese que su activación también se hace por medio del miembro c_cflag, según se indica en la tabla 2.3. 23 Tabla 2.3 Constantes del miembro c_cflag Constante CBAUD B0 B50 B75 B110 B134 B150 B200 B300 B600 B1200 B1800 B2400 B4800 B9600 B19200 B38400 B57600 B76800 B115200 EXTA EXTB CSIZE CS5 CS6 CS7 CS8 CSTOPB CREAD PARENB PARODD HUPCL CLOCAL LOBLK CNEW_RTSCTS CRTSCTS Descripción Máscara de bits para el baud rate 0 baud (drop DTR) 50 baud 75 baud 110 baud 134.5 baud 150 baud 200 baud 300 baud 600 baud 1200 baud 1800 baud 2400 baud 4800 baud 9600 baud 19200 baud 38400 baud 57,600 baud 76,800 baud 115,200 baud Reloj externo Reloj externo Máscara de bits para datos 5 bits de datos 6 bits de datos 7 bits de datos 8 bits de datos 2 bits de stop(1 de otra forma) Habilita receptor Habilita bit de paridad Utiliza paridad impar en lugar de par Cuelga (bota al DTR) en último cierre Línea local – no cambiar "dueño" del puerto Bloquea la salida del control de trabajo Habilita el control de flujo por hardware (no soportado en todas las plataformas) 24 2.4.3 Acceso y programación del puerto serial Una ventaja de programación muy particular de los sistemas UNIX es su forma de accesar sus puertos de entrada y salida, por medio de archivos. Cada uno de los puertos seriales tiene asignado a sí mismo uno o más archivos, tal y como se observa en la tabla 2.4. Tabla 2.4 Archivos asociados al puerto serial, sistemas UNIX Sistema Puerto 1 Puerto 2 IRIX® /dev/ttyf1 /dev/ttyf2 HP-UX /dev/tty1p0 /dev/tty2p0 Solaris®/SunOS® /dev/ttya /dev/ttyb Linux® /dev/ttyS0 /dev/ttyS1 Digital UNIX® /dev/tty01 /dev/tty02 En este caso, para accesar el puerto serial simplemente se debe abrir el archivo de este dispositivo, empleando el comando open como se muestra a continuación: #include <termios.h> int fd; /* Identificador del archivo del puerto */ fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); Como se puede ver en las líneas de código anteriores la función open recibe otros argumentos, también llamados banderas, además del nombre del archivo correspondiente al puerto. La bandera O_RDWR especifica que se empleará un modo de lectura y escritura en el manejo de este archivo. La bandera O_NOCTTY le indica al sistema operativo que este programa no será la terminal de control del puerto. Si esto no se especificara cualquier señal de entrada, tal como la señal de abortar proveniente del teclado, podría afectar el proceso de comunicación. Finalmente la bandera O_NDELAY establece que a este programa no le importa el estado en que se encuentre el pin DCD, es decir el programa enviará datos al puerto, sin tomar en cuenta el estado de disponibilidad del otro extremo de la comunicación. Escribir o leer datos del puerto es un procedimiento sencillo, el cual emplea las funciones write y read. Para cualquiera de las dos funciones se debe especificar el nombre del indicador del puerto, el elemento que se va a escribir o leer, y el número de bytes 25 correspondientes al tamaño de este elemento. A continuación se presenta el código necesario para escribir la palabra “Hola” al puerto: n = write(fd, "Hola", 4); if (n < 0) fputs("Error en envío de los 4 bytes!\n", stderr); Nótese que la palabra “Hola” está compuesta por 4 bytes, por lo que el número 4 dentro del paréntesis de la función write, indica la cantidad de datos que serán enviados. Finalmente cuando se desea terminar la comunicación, y al igual que como con cualquier otro archivo, es importante cerrar el recurso que fue abierto al inicio del programa. Para cerrar el puerto se usa la función close, como se observa a continuación: close (fd); 2.5 Aplicaciones gráficas GTK GTK (GIMP Toolkit) es una librería desarrollada para la creación de interfaces gráficas, originalmente diseñada para el Programa de Manipulación de Imágenes GNU (GIMP por sus siglas en ingles: «GNU Image Manipulation Program »). A pesar de haber sido creado en un principio para el lenguaje C, GTK es una interfaz de programación de aplicaciones o API («Application Programming Interface») orientada a objetos. Actualmente el proyecto ha sido extendido a otros lenguajes tales como C++, perl, python, java entre otros. Programas como GNOME (por sus siglas en inglés: «GNU Network Object Model Environment»), han sido desarrollados utilizando GTK. Para la utilización de esta librería es necesaria la instalación previa del paquete libgtk2.0dev. 2.5.1 Programación básica de ventanas Como primer paso de la programación de la ventana debe incluirse, por supuesto, la librería de GTK gtk/gtk.h. Ésta declara todas las variables, funciones y estructuras utilizadas en la aplicación GTK que se va a generar. 26 Seguidamente se da inicio al la función principal (main), donde se pueden incluir los elementos necesarios para crear una ventana. Una sintaxis sencilla, para el código de una ventana se presenta en el programa llamado ventana.c, mostrado a continuación: //ventana.c #include <gtk/gtk.h> int main(int argc, char *argv[]) { GtkWidget *window; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window); gtk_main (); return 0; } La línea GtkWidget *window sirve para declarar el objeto ventana, que pertenece a la clase de elementos GTK (GtkWidget). La línea siguiente: gtk_init (&argc, &argv), debe aparecer en todas las aplicaciones GTK y su función es ajustar algunos aspectos visuales, así como el mapa de colores predeterminado de la librería. Las dos líneas siguientes especifican algunos atributos de posición y el final de los ajustes para que la ventana sea desplegada, respectivamente. Finalmente la línea gtk_main() permite el ingreso del programa al lazo principal GTK. Esta función debe incluirse también en todas las aplicaciones GTK. Cuando el control llega a este punto, el programa permanece en un modo de espera de eventos relacionados a los objetos dentro de la ventana. Para compilar el programa desde consola se debe utilizar la línea de código: gcc ventana.c –o ventana `pkg-config --cflags --libs gtk+-2.0` El comando anterior crea el objeto ventana a partir del código ventana.c, y le indica al compilador que agregue las banderas de la librería de GTK 27 2.6 La robótica En la actualidad el desarrollo acelerado de la electrónica y la computación ha traído consigo una creciente industria especializada en la construcción de máquinas y robots programables. Por otra parte, la búsqueda por autamatizar y perfeccionar procesos, antes realizados en largos periodos de tiempo por uno o más operarios, también han proporcionado el mercado necesario para la consolidación de robótica en la economía mundial. En general un robot industrial puede definirse como un equipo mecánico programable, para la realización de tareas específicas. Sin embargo, las áreas de investigación y desarrollo actuales sobre robótica, van mucho más allá de la simple programación y puesta en marcha de elementos electromecánicos, con alcances tales como la generación de redes neuronales o robots humanoides. No obstante en esta sección se llevará a cabo un enfoque más cercano a los equipos industriales, por la relación de este tema con el presente proyecto. En su gran mayoría los robots empleados en la industria poseen ciertas características físicas, basadas en la figura humana. Cabe destacar que la característica antropomórfica más común en nuestros días es la de un brazo mecánico, el cual realiza diversas tareas industriales. Uno de los componentes principales de estos brazos mecánicos es, sin duda alguna, la base o soporte principal del mismo. Sobre ésta se monta toda la estructura restante del robot y determina un eje central para los posibles movimientos que puedan realizarse. El resto de los componentes se basa principalmente de articulaciones y sus respectivos elementos (Figura 2.3). 28 Figura 2.3 Elementos del brazo robot2 A parte de los componentes propios del brazo, vistos en la figura 2.3, otros elementos que forman parte del robot son un controlador, mecanismos de entrada y salida de datos y dispositivos especiales. El controlador es el encargado de manipular el brazo, basándose en las señales recibidas en los puertos de entrada y salida. Además el controlador posee su propia unidad de disco duro, donde se almacenan rutinas de inicio, calibración y rutinas generadas para la realización de tareas específicas. Como se dijo anteriormente, el controlador recibe y envía señales a otras máquinas o herramientas (por medio de señales de entrada/salida). Los mecanismos de entrada y salida, más comunes son: teclado, monitor y caja de comandos. A pesar de que el control del brazo robot puede llevarse a cabo utilizando simplemente la caja de comandos, es usual la conexión de una computadora acoplada al controlador a través del puerto serial o el puerto paralelo. De esta forma se pueden mandar comandos especiales al robot, e incluso se pueden emplear lenguajes de alto nivel, para elaborar rutinas mucho más complejas. 2 Fuente: http://www.chi.itesm.mx/~cim/robind/robotica.html 29 CAPÍTULO 3: El brazo robot Stäubli RX90 El brazo Stäubli RX90 está compuesto de segmentos o miembros interconectados por uniones o “joints”. Cada una de estas uniones tiene asociado un eje sobre el que se generan las rotaciones de los elementos. Los movimientos se llevan a cabo gracias a los motores internos, particulares de cada articulación, y cuya velocidad de funcionamiento puede ser regulada tanto con la caja de comandos, como con comandos de código de alto nivel como se verá en el capítulo siguiente. Los principales componentes del brazo robot son: la base (A), el hombro (B), el brazo (C), el codo (D), el antebrazo (E) y la muñeca (F) (la figura 3.1). Figura 3.1 El brazo robot Stäubli RX901 1 Figura tomada de las hojas del fabricante. 30 3.1 Componentes externos del robot Los componentes externos asociados al robot Stäubli RX90, y que permiten el control del mismo son: Controlador Caja de comandos Computadora con sistema operativo GNU/Linux Base del robot La figura 3.2 presenta algunos de los elementos mencionados anteriormente, los cuales se encuentran el laboratorio de control de la Universidad de Costa Rica, y que se emplearon en la realización de este proyecto. Figura 3.2 Robot y sus componentes externos (Laboratorio de Control UCR) 31 3.2 Comunicación con el robot El robot Stäubli RX90 se conecta directamente a un controlar, el cual posee su propia unidad de disco duro además de otros elementos adicionales. Dentro de los dispositivos del controlador se encuentran una unidad de disquete, varios botones de control, así como una terminal destinada a la conexión de la caja de comandos y un puerto serial, que permite la comunicación con una computadora externa. Dentro de la unidad de memoria del controlador se almacenan datos tales como la rutina de calibración, la rutina de inicio, y también nuevas rutinas generadas por el operador empleando lenguaje de alto nivel (V+ y C). 3.2.1 Pines relevantes del conector serial Como se mencionó anteriormente, la comunicación entre el controlador y la computadora adicional se lleva a cabo por medio de los puertos seriales de ambos equipos. Por lo tanto para este motivo se emplea un cable con conectores DB9 hembra en sus terminales. Debido a que la comunicación entre el controlador y la computadora externa es de tipo asincrónica, la cantidad de pines necesarios para el envío y recepción de datos es reducida. En el caso de las comunicaciones sincrónicas (conectores DB25), ambas partes de la conexión (emisor y receptor) comparten una misma línea, llamada reloj de bits, cuya función es sincronizar ambos dispositivos. Otro aspecto referente a la cantidad de pines empleados, es el control de flujo. La comunicación serial con el controlador del robot Stäubli RX90 no requiere ningún tipo de control de flujo. En el caso del control de flujo por hardware se utilizan los pines CTS y RTS, para establecer un control del flujo de datos basado en las señales de dichos pines. Por consiguiente, la comunicación serial controlador-computadora únicamente requiere 3 pines2 para su correcto funcionamiento: RXD – Datos recividos «Received Data» TXD – Datos transmitidos «Transmitted Data» GND – Tierra lógica «Logic Groud» Es importante recalcar que los pines RXD y TXD deben estar conectados en forma cruzada en los extremos del conector DB9 hembra. Es decir, un cable debe comunicar el pin RXD de un extremo, con el pin TXD del otro extremo para lograr el comportamiento deseado. El 2 Ver tablas 2.1 y 2.2 de asignación de pines. 32 pin de GND (tierra) permite a ambos dispositivos tener una referencia común, para la interpretación de los voltajes. 3.2.2 Ajuste de las variables de comunicación Según lo especificado en el capítulo 2, las tres variables principales de ajuste de la comunicación serial son: el tamaño de los datos, la razón de envío de datos y el flujo de control. En el caso del controlador del robot Stäubli RX90 el tamaño de los caracteres, que recibe y envía, es de 8 bits. Además no emplea ningún bit de paridad y utiliza un bit de stop. Por lo tanto el formato de los datos de la conexión es 8N1. La razón de envío de datos esta definida a 9600 baudios. Por último y según lo explicado en la sección 3.2.1, no se emplea en la comunicación ningún tipo de control de flujo. 3.3 El lenguaje V+ y algunos comandos Cada vez que se pretendan controlar dispositivos electrónicos programables, se debe tener en cuenta el tipo de comandos aceptados por dicho artefacto. En este caso el robot Stäubli RX90 utiliza comandos del lenguaje V+. En la tabla 3.1 se presentan algunos de los principales comandos, utilizados para accionar el brazo robot Stäubli RX90. Se recomienda al lector consultar [2], donde se incluyen en formato digital varios manuales y referencias sobre el lenguaje V+. Tabla 3.1 Comandos del lenguaje V+ Comando edit (do) ready drive Xjoint, Ygrados, Zvelocidad Descripción Edita rutinas o programas Coloca el robot en la posición definida en la rutina de calibración Rota la articulación X, Y grados a una velocidad del Z% open Abre la pinza (en caso que se tenga) close Cierra la pinza (en caso que se tenga) e Indica el fin de la rutina store Salva la rutina especificada load Carga la rutina especificada ex Ejecuta la rutina especificada 33 CAPÍTULO 4: Desarrollo y funcionamiento de la comunicación cliente-servidor Antes de entrar de lleno en el desarrollo de los programas, es importante mencionar que los códigos que se comentan a continuación tienen como base tres archivos principales tomados de [1]. El código servidor.c de este proyecto se basa en el código sockets.c. De la misma forma, el código main.c, de este proyecto, tiene como base los códigos main.c y net.c. 4.1 Funcionamiento del servidor En la figura 4.1 se presenta el diagrama de flujo del programa que corre en lado del servidor (servidor.c). Como se puede apreciar, primeramente se lleva a cabo la configuración de los elementos necesarios para la comunicación con el puerto serial, que posibilita el intercambio de datos con el controlador del robot. Una vez hecho esto, se crea el socket que permite establecer contacto con cualquier cliente, a través de la red. En este punto el programa espera la conexión de algún cliente, para generar de esta forma una comunicación bidireccional e iniciar el envío de video. Después de los pasos anteriores, el programa ingresa en un ciclo infinito de atención de comandos provenientes del cliente. En caso de que se reciba un comando proveniente del cliente, éste es interpretado como un comando para el robot, por lo que es enviado directamente al puerto serial. Sin embargo, el ciclo infinito se interrumpe al haber presencia de datos provenientes del puerto serial. En tales ocasiones, los datos recibidos en el puerto serial son enviados al cliente, para que este último pueda dar seguimiento al estado actual del robot. 34 Figura 4.1 Diagrama de flujo del programa servidor.c 35 4.1.1 Configuración del puerto serial La interfaz termios, que forma parte de la librería <termios.h>, es la encargada de dar soporte a los dispositivos de comunicación asincrónica. De esta forma es posible el almacenamiento de los datos del dispositivo (puerto com1), en una estructura propia de esta misma librería. Por tal razón, el primer paso en la configuración del puerto serial es la creación de dos estructuras termios, con el fin de guardar en ellas los ajustes regulares (actuales, antes de alterarlos) del puerto serial y los nuevos ajustes, particulares de la comunicación con el controlador del robot. La idea de mantener en memoria los ajustes regulares del puerto, tiene como fundamento el hecho de que estos ajustes deben ser reestablecidos una vez que nuestro programa finalice. struct termios oldtio, newtio; ... tcgetattr(fd,&oldtio); // se salvan los ajustes actuales del puerto /* nuevos ajustes del puerto para procesamiento de entrada canonical */ newtio.c_cflag = BAUDRATE | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD; //IGNPAR: ignora bytes con errores de paridad newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; //NON-CANONICAL; newtio.c_cc[VMIN]=1; newtio.c_cc[VTIME]=0; tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); Nótese en el código anterior que, después de la declaración de la estructura regular (oldtio) y la nueva estructura (newtio), se procede a guardar los atributos actuales del puerto utilizando la función tcgetattr() y se asocian estos atributos a la estructura oldtio. En este caso el indicador fd es el descriptor del puerto serial. Las siguientes líneas del código se utilizan para ajustar las nuevas variables de la estructura del dispositivo serial. La bandera de control c_flag habilita la recepción de información y define el formado de los datos que se emplean en la comunicación. La bandera c_iflag describe el control de entrada de la terminal. La opción IGNPAR se utiliza por seguridad y le indica al programa que ignore los bytes con errores de paridad. La bandera de salida c_oflag permiten procesar los datos de salida, sin embargo para nuestra comunicación serial este elemento no es necesario. 36 La bandera local c_lflag define el modo de procesamiento de los datos de entrada, que en este caso es no canónico. En el modo no canónico los datos de entrada no son leídos como líneas y, en general, no se aplica ningún tipo de procesamiento a los datos entrantes. Sin embargo, dos parámetros controlan el comportamiento de este modo: c_cc[VTIME] ajusta el tiempo límite de espera y c_cc[VMIN] ajusta el mínimo número de caracteres por recibir, antes de satisfacer la función de lectura read. Como se observa en el código anterior, el servidor requiere de al menos un carácter para satisfacer la lectura, y no tiene tiempo límite de espera. Finalmente la función tcsetattr() es la que activa todos los ajustes realizados a los atributos de la nueva estructura. 4.1.2 Recepción y ejecución de comandos provenientes del cliente Aquella información proveniente del cliente, y que no forma parte del grupo de palabras reservadas para el inicio de la comunicación o el envío de video, es interpretada por el servidor como un comando para el robot. Por tal razón estos datos son enviados directamente al controlador del robot, a través del puerto serial. Al ejecutarse por primera vez el ciclo infinito de atención de comandos provenientes del cliente, el programa del servidor recibe algunos datos relacionados exclusivamente con el establecimiento de la comunicación y ajustes de captura y envío de video. Estos datos son procesados en forma particular, y lógicamente no son enviados a la terminal serial. Después de haber hecho esto, el programa ejecuta el ciclo de atención de comandos en forma regular. Es decir, el programa simplemente espera el ingreso de datos por parte del cliente, los cuales son interpretados como comandos para accionar el robot Stäubli RX90. El código a continuación muestra la forma en que se procesa la información enviada por el cliente: else { } // Comando para el puerto serial byte_counter=0; for (i=0; i<256; i++) { byte_counter++; if (in_buf[i] == '\0') { in_buf[i] = 0x0d; in_buf[i+1] = 0x0a; in_buf[i+2] = '\0'; break; } } write(fd, in_buf, byte_counter+2); } 37 Los datos que recibe el servidor son almacenados en la cadena de caracteres in_buf. La estructura del for permite localizar el caracter de fin de línea, dentro de los datos de entrada. Nótese que luego de haber encontrado el caracter de fin de línea, se le suman a la cadena de datos dos bytes más. Los caracteres 0x0d y 0x0a, en formato hexadecimal, indican retorno de carro («carriage return») y línea de alimentación o nueva línea («line feed») respectivamente. Estos dos bytes son necesarios para que el comando pueda ser enviado e interpretado correctamente por el controlador del robot. Obsérvese también que hay una variable entera llamada byte_counter, que cuenta la cantidad de bytes que serán enviados a través del puerto serial. Finalmente se emplea la función write para la escritura de la información a la terminal serial. 4.1.3 Función de interrupción y lectura de datos del puerto serial La señal SIGIO pertenece a un grupo de señales asociadas a instalaciones de entrada y salida asincrónicas. Al ajustar en forma explícita, mediante la función fcntl(), se permite a un descriptor de archivo particular generar estas señales de interrupción. De esta forma, la señal es enviada cuando un descriptor de archivo está listo para realizar la entrada (lectura) o salida (escritura) de datos (se conoce también por el símbolo I/O). En la mayoría de sistemas operativos, los únicos archivos capaces de realizar esta función son aquellos relacionados a terminales o a sockets. Para la implementación de la función fcntl() es necesario incluir las librerías <fcntl.h> y <unistd.h>. En el caso del programa servidor.c, la variable fd es el descriptor del puerto com1 para la comunicación serial. La señal SIGIO se emite según el estado de este descriptor (estado del puerto serial). Con esto se logra crear en el código una interrupción para atender y manejar aquellos datos que el robot devuelve, y que son relevantes para el cliente. Entonces, los datos disponibles en el puerto serial son leídos y procesados sin ningún tiempo de espera. Con la implementación de la señal SIGIO, se evita también la creación de un nuevo hilo de procesamiento para la atención de datos recibidos en el puerto serial. El código a continuación muestra la sintaxis que se emplea para lograr este comportamiento. /* Se habilita el proceso para que pueda recibir SIGIO */ fcntl(fd, F_SETOWN, getpid()); /* Se hace el descriptor fd asincrónico */ fcntl(fd, F_SETFL, FASYNC); 38 El argumento F_SETOWN dentro de la primera llamada a la función fcntl(), establece el ID1 del proceso que atenderá las interrupciones. En este caso el proceso que atiende las interrupciones es el programa mismo, por lo que el argumento getpid() se encarga de recopilar el número de proceso asignado a este programa. La segunda llamada a la función fcntl(), permite hacer al descriptor fd asincrónico. Nuevamente se utiliza una estructura para el almacenamiento de los ajustes, relacionados al manejador de la interrupción, tal y como se observa en el código a continuación. Nótese además que, el manejador de interrupciones se asocia a la función signal_handler_IO, la cual se ejecuta cada vez que hay datos disponibles para ser leídos en el puerto serial. struct sigaction saio; //ajuste del manejador de interrupción serial ... /* Instalación del manejador serial antes de establecer la comunicación asincrónica */ saio.sa_handler = signal_handler_IO; // el manejador de //interrupciones se asigna a esta función sigemptyset(&saio.sa_mask); // se borran los ajustes actuales saio.sa_flags = 0; // ajuste a cero la bandera sa_flags saio.sa_restorer = NULL; // sin restitución sigaction(SIGIO,&saio,NULL); // aplicación de nuevos ajustes Finalmente la labor de la función signal_handler_IO es leer los datos disponibles en la terminal, y enviarlos al cliente a través del socket. Para lograr esto el programa emplea las funciones read() y write(), como los haría con cualquier otro archivo que estuviera manejando. void signal_handler_IO (int status) { res = read(fd,buf,255); buf[res]=0; // el último caracter de la línea es nulo /* Envío de datos al cliente */ strcpy(out_buf, buf); write(sd2, out_buf, BUF_SIZE); } 1 Cada proceso que corre dentro del sistema operativo GNU/Linux posee su propio número identificador o PID, con el cual es identificado y atendido dentro del mismo sistema. 39 4.2 Funcionamiento del cliente La aplicación que corre en el lado del cliente, tiene tres propósitos fundamentales: desplegar las imágenes enviadas por el servidor, mostrar los datos referentes al robot, recibidos a través de la comunicación con el servidor y dotar al usuario de una interfaz amigable, para introducir comandos y accionar el robot. 4.2.1 Aplicación GTK Como se mencionó en el capítulo 2, para la programación de la interfaz gráfica se utilizó la librería GTK. La figura 4.2 muestra el formato de la aplicación, que corre bajo el nombre de cliente1. Figura 4.2 Interfaz gráfica GTK Como se puede ver en la figura anterior, la aplicación del cliente utiliza un textview, localizado en la parte superior izquierda de la ventana, para desplegar los mensajes referentes al robot. Por este medio, el usuario tiene conocimiento en todo momento del 40 estado actual del robot. La ejecución de la rutina de inicio, las opciones de la rutina de calibración y la información sobre la creación de nuevas rutinas, son algunos de los mensajes recibidos y desplegados en el textview. En el extremo superior derecho de la ventana, se muestra el video proveniente del servidor, capturado en tiempo real y que permite al cliente observar al robot. En el nivel inferior de la ventana, se encuentra un espacio divido en tres marcos o frames: “Comando Drive”, “Línea de Comandos” y el marco de “Rutinas”. El primer marco de izquierda a derecha es “Comando Drive”. Bajo este marco se encuentran 3 botones de ajuste y un botón de ejecución “DRIVE”. Los botones de ajuste están diseñados para incrementar o disminuir su valor, respecto a la cantidad de articulaciones del robot, los grados y velocidad de giro de las uniones. De esta forma el usuario no tiene que digitar el comando drive utilizando la sintaxis explicada en la tabla 3.1, sino solamente debe indicar la unión, la cantidad de grados y la velocidad con la que se realizará el movimiento. El segundo marco encierra la línea general de comandos. Cualquier instrucción que se desee enviar al robot, se debe hacer a través del elemento de entrada de texto ubicado dentro de este marco. Luego de introducir el comando, el botón “Ejecutar” se encarga de enviar los datos al servidor y de limpiar la línea de entrada de texto. Es importante mencionar que el textview donde se despliegan los mensajes relacionados con el robot, no es editable. Es decir, no se pueden escribir datos directamente en el textview, por lo que la única forma de accionar al robot es por medio de la línea de entrada de texto, de este marco. Por último, el marco “Rutinas” evita nuevamente que el usuario deba recordar la sintaxis exacta de los comandos relacionados con la creación y ejecución de nuevas rutinas. Dentro de la línea de entrada de texto se debe digitar el nombre de la rutina que se quiere crear o ejecutar, empleado la extensión .v2. Por ejemplo si se deseara crear una rutina llamada “prueba1”, se tendría que introducir “prueba1.v2” como el nombre de la rutina. Mediante este funcionamiento se pretende dotar a la interfaz de elementos que la hagan más amigable, para la operación del robot. 4.2.2 GTK y la programación con múltiples hilos de procesamiento La librería de GTK es capaz de operar al lado de procesamientos paralelos generados por múltiples hilos, sin embargo GTK no tiene soporte propio para la programación de sus objetos dentro de distintos hilos en un mismo programa. Es decir, es posible crear programas que contengan varios hilos y además hacer uso de la librería GTK, pero sólo uno 41 de los hilos (generalmente el hilo principal del programa) puede accesar sin problemas los elementos GTK. La librería GLib es el núcleo de bajo nivel que forma las bases de GTK y GNOME. Entre otras cosas, GLib provee soporte para la funcionalidad de eventos tales como los hilos de procesamiento. Para que la librería GLib soporte la programación de los hilos, es necesario utilizar las funciones g_thread_init(NULL) y gdk_threads_init(). En el código a continuación se observa que las funciones previamente mencionadas, deben llamarse antes de la función principal de GTK, y sólo una vez dentro del programa: /* Inicialización del soporte de threads en GLib */ g_thread_init(NULL); gdk_threads_init(); /* Inicialización GTK */ gtk_init(&argc, &argv); Además del paso anterior, es obligatorio hacer uso del control de bloqueo global provisto por las funciones gdk_threads_enter() y gdk_threads_leave(), para poder manejar los objetos GTK en los diferentes hilos del programa. Todas aquellas funciones, timeouts2 y partes de código que se ejecuten fuera del lazo principal de GTK, y que incluyan elementos pertenecientes a esta librería deben estar encerradas por las funciones de bloqueo. De esta forma se logra que sólo un hilo utilice los objetos GTK en un instante dado, y no hayan problemas de invalidación de segmento, o errores con los valores de algunas variables. Las funciones de g_threas están en la librería libgthread-2.0, por tanto para usar estas funciones se debe compilar con `pkg-config --libs gthread-2.0`. 4.2.3 Recepción y despliegue de mensajes en la aplicación gráfica La comunicación cliente-servidor, empleada en el control remoto del robot Stäubli RX90, es de tipo bidireccional. Es decir, tanto el cliente como el servidor deben tener un ciclo continuo de espera de comandos, provenientes del otro miembro de la conexión. Por esta razón se implementó un hilo, en el programa main.c, destinado a la espera y recepción de comandos enviados por el servidor. /* Crea el hilo de recepción de mensajes */ pthread_attr_init(&attr); 2 Método empleado para la ejecución repetida de una función, después de un tiempo definido. 42 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&recmessage_thread, &attr, rec_message_loop, NULL); printf("Recepcion de Mensajes Iniciada\n"); Todos aquellos datos recibidos por el cliente, y que no están relacionados con las imágenes de video en tiempo real, son interpretados por la aplicación gráfica como información referente al robot. Para que el usuario tenga acceso a estos mensajes se hizo uso del objeto textview (pertenece a la librería GTK), que permite desplegar el texto recibido en la ventana. Nótese en el código anterior que, cuando se lleva a cabo la creación del hilo de recepción de mensajes, se da inicio a la ejecución de la función rec_message_loop. A continuación se muestra la programación de ésta función: /* rec_message_loop: Ciclo de recepción de mensajes */ void *rec_message_loop(void *null){ char *buf_ptr, in_buf[BUF_SIZE], out_buf[BUF_SIZE]; int bytes_left, n_read; while(1) { /* Lectura de los datos enviados por el cliente */ bytes_left = BUF_SIZE; buf_ptr = in_buf; while((n_read = read(sd, buf_ptr, bytes_left)) > 0) { buf_ptr += n_read; bytes_left -= n_read; } if(n_read < 0) continue; /* Los datos recibidos son desplegados en el textview */ gdk_threads_enter(); textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); mark = gtk_text_buffer_get_insert (textbuffer); gtk_text_buffer_get_iter_at_mark(textbuffer, &iter, mark); gtk_text_buffer_insert (textbuffer, &iter, in_buf, -1); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), mark, 0.0, TRUE, 0.0, 0.0); gdk_threads_leave(); } pthread_exit(NULL); } 43 Obsérvese que la recepción de mensajes se implementa mediante una estructura while(1), lo que supone un ciclo infinito de espera y procesamiento de los datos. Es importante hacer hincapié en la forma en que se despliega en el texto en la aplicación gráfica. Tal y como se mencionó en la sección 4.2.2, la programación de los elementos GTK en diferentes hilos dentro de un mismo programa, requiere la utilización de las funciones globales de bloqueo. El código anterior muestra el uso que se le debe dar a las funciones gdk_threads_enter() y gdk_threads_leave(), cuando se desean programar los objetos GTK, en hilos diferentes al principal. 44 CAPÍTULO 5: Control remoto utilizando aplicación GTK 5.1 Rutina de calibración La rutina de calibración debe ser ejecuta sólo una vez, durante el tiempo que se utilice el robot Stäubli RX90 y debe realizarse justo después de iniciar la alimentación de poder del controlador. Tanto las opciones de calibración, como los diferentes menús de esta rutina son desplegados en el textview. Los pasos necesarios para cargar y ejecutar la rutina de calibración, las opciones y otros datos relacionados con este tema, se encuentran desarrollados a fondo en [2]. 5.2 Pasos para energizar el robot y deshabilitar el modo de simulación Luego de haber finalizado y almacenado en el disco la rutina de calibración, es necesario reiniciar el controlador presionando el botón RESET, localizado en la esquina superior izquierda de este dispositivo. Una vez hecho esto, el controlador ejecuta la rutina de inicio y carga todos los ajustes determinados en la rutina de calibración. El usuario tiene acceso a toda la información relacionada a estos procesos, a través del textview de la aplicación. En este punto el controlador se encuentra preparado para recibir instrucciones provenientes del cliente. Antes de introducir cualquier comando de ejecución de movimientos o de rutinas, es necesario energizar el robot y deshabilitar el modo de simulación, que por defecto se encuentra activo cada vez que el controlador se reinicia. Los comandos “en po” (abreviatura de «enable power») y “dis dry” (abreviatura de «disable DRY/RUN mode») se emplean para realizar estas tareas. La figura 5.1 muestra la forma en que se llevan cabo estos procesos, utilizando la línea de entrada de texto y el botón “Ejecutar”, dentro del marco de “Línea de Comandos”. 45 Figura 5.1 Energizando el robot y deshabilitando el modo de simulación 5.3 Creación de una rutina de movimientos con el comando drive Una de las cualidades más importantes del controlador del robot Stäubli RX90, es que posee su propia unidad de disco de memoria, en la cual es posible almacenar rutinas elaboradas por el operador. El marco de “Rutinas” de la aplicación gráfica tiene como propósito facilitar el manejo de nuevas rutinas. Los pasos requeridos para la creación y ejecución de una nueva rutina son: 1. Digitar el nombre de la rutina, en la línea de entrada de texto, utilizando la extensión .v2 que es necesaria para el reconocimiento del nombre. Luego se debe accionar el botón “Crear nueva Rutina”. 2. Ingresar los comandos que van a conformar la rutina de movimientos, utilizando los botones de ajuste del marco “Comando Drive”. Es importante tomar en cuenta, a la hora de la introducción de los comandos, que la suma de los movimientos programados no provoque la autodestrucción del robot. 46 3. Utilizar la línea de entrada de texto, dentro del marco “Línea de Comandos” para indicar el fin de la rutina, utilizando el comando “e”. 4. Utilizar el botón “Ejecutar Rutina” para ejecutar la rutina. La figura 5.2 muestra cómo crear una rutina llamada “prueba1.v2”. Nótese que al hacer clic sobre el botón “Crear nueva Rutina”, automáticamente se editan en pantalla las primeras dos líneas de la nueva rutina; además la segunda línea es precedida por los caracteres “2?”. El número dos indica que se trata de la segunda línea, la primera línea es la que determina el nombre de la rutina que se está editando. En este momento es posible iniciar la introducción de los comandos que van a conformar la rutina. Figura 5.2 Método para crear rutina 47 El marco del comando drive facilita al usuario la utilización de esta instrucción, en la creación nuevas rutinas. En la figura 5.3 se observan las líneas de comandos de la rutina “prueba1.v2”, realizadas con los botones de ajuste y el botón “DRIVE”, que se encuentran bajo ese marco. Nótese además que, para indicar el fin de la rutina, se debe ingresar el comando “e”. De esta forma finaliza la edición de la rutina “prueba1.v2”, y ésta queda almacenada en el disco. Ahora el programa del controlador espera nuevos comandos del cliente. Figura 5.3 Comando de fin de rutina 48 Finalmente el botón “Ejecutar Rutina” se encarga de correr la rutina. Es importante recalcar que los tres botones que se encuentran bajo el marco “Rutinas”, generan la sintaxis del su comando basándose en el nombre de la rutina, digitado en la línea de entrada de texto de este marco. Por esta razón, si se desea continuar trabajando con una misma rutina, no es recomendable borrar su nombre de la línea de texto, a menos que se desee crear otra rutina. Figura 5.4 Ejecución de la rutina 49 CAPÍTULO 6: Conclusiones y recomendaciones 6.1 Conclusiones • La interfaz gráfica GTK, generada en el proyecto permite controlar en forma remota el brazo robot. Los elementos de control, propios de la aplicación la hacen una herramienta amigable para el operador. Igualmente el sistema de video4Linux, captura en forma eficiente las imágenes de video, las cuales son transmitidas y desplegadas en tiempo real en la aplicación gráfica. • El control remoto se fundamenta en una comunicación bidireccional entre dos máquinas o computadoras, generalmente a través de internet. El sistema operativo de licencia libre GNU/Linux demostró ser una plataforma estable para la comunicación cliente-servidor. • Uno de los aspectos positivos de la utilización del sistema video4Linux, es el acceso al código de las imágenes capturadas. La disponibilidad de este código, en el programa empleado, abre las puertas a futuros proyectos que apliquen filtros o compresiones a los cuadros, con lo que se mejoraría el control y visualización remota del robot. • El sistema GNU/Linux está respaldado por una amplia documentación, accesible en forma gratuita a través de sitios de internet o en publicaciones independientes como libros y revistas. Igualmente los programas y demás librerías empleadas bajo esta plataforma no tienen costo alguno, lo que facilita la creación de nuevas aplicaciones de software. • El lenguaje de programación C posee las cualidades necesarias para la generación de programas que se comunican entre sí, bajo el formato cliente-servidor. • El manejo y comunicación con los puertos de E/S en los sistemas POSIX, se lleva a cabo siguiendo el protocolo de manejo de archivos comunes. Por esta razón, las funciones de la lectura y escritura de datos a estas terminales son muy conocidas y de fácil uso. • La programación con múltiples hilos de procesamiento permite la creación de aplicaciones, que ejecutan varios procesos en forma paralela e independiente. Además se da un mejor aprovechamiento de las cualidades multitarea, del sistema operativo GNU/Linux. 50 • La implementación de los elementos de la librería GTK, en un programa con varios hilos, requiere la utilización de funciones de bloqueo global pertenecientes a la librería GLib. La librería GLib, que forma parte de las bases de GTK, posee las funciones necesarias para dar soporte a la programación de los objetos GTK, en diferentes hilos dentro de un mismo programa. • La compresión de video, para el manejo remoto de dispositivos electromecánicos, es un elemento fundamental dentro de este contexto. Un control remoto preciso y eficiente requiere da la más alta calidad y velocidad de envío de imágenes, de lo contrario no es posible la ejecución de tareas delicadas. Si el video recibido en la aplicación remota no es claro o no tiene una velocidad óptima, no se logrará tener un control y sólo se le podrá dar seguimiento a los movimientos realizados por el elemento que se está accionando. 6.2 Recomendaciones • La implementación de un algoritmo para la compresión de las imágenes de video, permitiría controlar en forma más segura y eficiente el robot Stäubli RX90. La utilización de un formato como MPEG4 podría llevar a cabo esta tarea, con lo que se tendría un flujo de imágenes de video menos pausado. • Debido a que el controlador del robot Stäubli RX90 no posee ningún mecanismo de protección, contra un programa que provoque su autodestrucción, es recomendable la generación y utilización de un algoritmo que calcule la suma de vectores de movimiento. Basándose en el resultado obtenido, el programa podría permitir o no al usuario la ejecución de tal rutina. • Basándose en la aplicación gráfica existente, es posible adicionarle a ésta más elementos de control, para llevar a cabo todo tipo de tareas que requieran alta precisión. Un modelo virtual del robot permitiría al usuario observar la ejecución de una rutina específica, antes de llevar a cabo los mismos movimientos en forma real. • Otro elemento que mejoraría el control remoto del robot es la implementación y despliegue de un eje de coordenadas en las imágenes de video. De esta forma se podría saber con mayor certeza la posición exacta del robot y de los objetos a su alrededor. Esto permitiría también que los movimientos fueran calculados antes de ser ejecutados, disminuyendo así la ejecución de movimientos adicionales de ajuste. • Para disminuir el tiempo perdido en ajustes iniciales como la rutina de calibración, se podría llevar a cabo una investigación a fondo sobre el funcionamiento del controlador del robot Satubli RX90. El objetivo en este caso sería lograr la 51 ejecución de la rutina de calibración, como parte de la rutina de inicio del controlador. Así la rutina de calibración representaría un proceso automático, ejecutado cada vez que se inicie el controlador. • Como es de suponerse, el mejoramiento de los dispositivos de hardware en el ámbito de la electrónica y la computación, siempre trae beneficios para cualquier usuario. Por consiguiente la utilización de una cámara web con mayor resolución, que la empleada en este proyecto, permitiría capturar imágenes de mejor calidad. Por otra parte, no debemos olvidar que los drivers del sistema operativo GNU/Linux son diferentes a aquellos del sistema Windows. Este aspecto debe tomarse en cuenta a la hora de adquirir uno de estos dispositivos, si se desea operar bajo una plataforma GNU/Linux. BIBLIOGRAFÍA Proyectos y tesis de grado: [1] Díaz Soto, A. “Implementación de un sistema de captura de video y detección de movimiento utilizando el sistema Video4Linux”, proyecto para el grado de bachillerato en Ingeniería eléctrica, Universidad de Costa Rica, 2005. [2] Zeledón Chaves, E. y Zeledón Méndez, P. “Creación de una interfase gráfica humano-máquina para el control, monitoreo y supervisión del brazo robot Stäubli RX90 vía internet”, tesis para el grado de licenciatura en Ingeniería eléctrica, Universidad de Costa Rica, 2004. Libros: [3] Welsh, M. & Kalle Dalheimer, M. & Kaufman, L. “Running Linux”, 3a edición. O'Reilly, 1999. [4] Kernighan, B y Ritchie, D. “The C programming language”, segunda edición. Prentice Hall, Estados Unidos, 1988. [5] Deitel, H. M. y Deitel, P. J. “Cómo programar en C/C++”, segunda edición. Prentice Hall, México, 1995. Referencias electrónicas: [6] www.gtk.org. “Threads”, http://www.gtk.org/api/2.6/gdk/gdk-Threads.html. [7] Gale, T. & Main I. & GTK team “GTK+ 2.0 Tutorial”, http://www.gtk.org/tutorial/. [8] R. Sweet, M. “Serial Programming Guide for POSIX Operating System”, 5ta edición, http://www.easysw.com/~mike/serial/serial.html, 1994-2005. [9] Frerking, G. “Serial Programming HOWTO”, revisión 1.01, http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/, 2001. [10] “A Linux serial port test program”, http://www.comptechdoc.org/os/linux/programming/c/linux_pgcserial.html. 52 APÉNDICES Programación del Servidor: Para accesar a la documentación del resto de los archivos, a partir de los cuales se genera el ejecutable llamado robolinux y que corre en el servidor, ver [1] . servidor.h: Definiciones y declaración de funciones /* * robolinux v0.8 * Programa para el control remoto del robot Staubli RX90 * servidor.h: Declaraciones y definiciones para las rutinas de comunicación con * el cliente y transmisión de video * Copyright (C) 2005 Carlos Montes Solano <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include "global.h" #include "video.h" #include "motion.h" #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <sys/signal.h> #ifndef SERVIDOR_H #define SERVIDOR_H /* Definiciones */ #define PORT 24001 // Puerto del servidor #define BUF_SIZE 256 // Tamaño del buffer de datos #define BAUDRATE B9600 // Definicion de los baudios a ser enviados al puerto #define MODEMDEVICE "/dev/ttyS0" // Puerto serial com00 #define _POSIX_SOURCE 1 // fuente de compilación POSIX #define FALSE 0 #define TRUE 1 /* Propiedades del cuadro enviado */ struct video_prop { int width; // Ancho int height; // Altura }; /* Declaración de funciones */ /* tcpsocket: Servidor TCP para el hilo principal */ void tcpsocket(void); /* udpsocket: Envío de los cuadros de video al cliente */ 53 54 void *udpsocket(void *); /* signal_handler_IO: Control de señales de entrada del puerto serial */ void signal_handler_IO (int status); #endif /* SERVIDOR_H */ servidor.c: Programa principal del servidor /* * robolinux v0.8 * Programa para el control remoto del robot Staubli RX90 * servidor.c: Implementación de las rutinas de comunicación con * el cliente, comunicación con el robot y transmisión de video * Copyright (C) 2005 Carlos Montes Solano <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include "servidor.h" /* Variable de estatus de entrada del puerto serial */ int status; /* Variables de configuración de la comunicación serial */ long DATABITS; long STOPBITS; long PARITYON; long PARITY; char buf[255]; //buffer de recepción de datos del puerto serial int fd, res; //fd = descriptor del puerto serial int sd, sd2; char out_buf[BUF_SIZE], in_buf[BUF_SIZE]; /* tcpsocket: Servidor TCP para el hilo principal */ void tcpsocket(void) { /**************************************************************************************/ /****************** Configuracion del puerto serial ********************************/ int c, i, error, byte_counter; /* lugar de almacenamiento de los viejos y los nuevos ajustes del puerto serial */ struct termios oldtio, newtio; struct sigaction saio; //ajuste del manejador de interrupción serial /* Ajuste de las variables de la comunicación serial */ DATABITS = CS8; STOPBITS = 0; PARITYON = 0; PARITY = 0; /* Se abre el dispositivo (puerto com) con el formato non-blocking (la lectura se hace en forma inmediata) */ fd = open(MODEMDEVICE , O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) {perror(MODEMDEVICE); exit(-1); } /* Instalación del manejador serial antes de establecer la comunicación asincrónica */ saio.sa_handler = signal_handler_IO; // el manejador de interrupciones se asigna a esta función sigemptyset(&saio.sa_mask); // se borran los ajustes actuales saio.sa_flags = 0; // ajuste a cero la bandera sa_flags saio.sa_restorer = NULL; // sin restitución sigaction(SIGIO,&saio,NULL); // aplicación de nuevos ajustes 55 /* Se habilita el proceso para que pueda recibir SIGIO */ fcntl(fd, F_SETOWN, getpid()); /* Se hace el descriptor fd asincrónico */ fcntl(fd, F_SETFL, FASYNC); tcgetattr(fd,&oldtio); // se salvan los ajustes actuales del puerto /* nuevos ajustes del puerto para procesamiento de entrada no canónical */ newtio.c_cflag = BAUDRATE | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD; //IGNPAR: ignora bytes con errores de paridad newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; //no canónica; newtio.c_cc[VMIN]=1; newtio.c_cc[VTIME]=0; tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); /***************** Fin de configuraciones del puerto serial **************************/ /*****************************************************************************************/ int client_len; struct sockaddr_in server, client; char *buf_ptr; pthread_t capture_thread, udp_thread, motion_thread; pthread_attr_t attr; /* Creación del socket */ if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "Problemas creando el socket\n"); pthread_exit((void *)-1); } /* Almacenamiento de los parámetros de conexión en la estructura sockaddr_in */ memset((char *)&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); /* Enlace del socket */ if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) { fprintf(stderr, "Error al enlazar el socket\n"); pthread_exit((void *)-1); } /* El socket espera conexiones */ listen(sd, 5); /* Ciclo infinito de atención de conexiones */ while(1) { /* Acepta la conexión entrante */ client_len = sizeof(client); if((sd2 = accept(sd, (struct sockaddr *)&client, &client_len)) == -1) { fprintf(stderr, "No se puede aceptar la conexion\n"); pthread_exit((void *)-1); } /* Envía mensaje de bienvenida */ strcpy(out_buf,"robolinux v1.8"); write(sd2, out_buf, BUF_SIZE); int n_read, bytes_left; while(1) { /* Para que el ciclo no ocupe todo el procesador */ usleep(100); /* Lectura de los datos enviados por el cliente */ bytes_left = BUF_SIZE; buf_ptr = in_buf; while((n_read = read(sd2, buf_ptr, bytes_left)) > 0) { buf_ptr += n_read; bytes_left -= n_read; } if(n_read < 0) continue; if((strcmp(in_buf,"CAPTURE")) == 0) { // Inicio de la captura if(!capture) { capture = 1; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&capture_thread, &attr, capture_loop, NULL); printf("Captura Iniciada\n"); } } else if((strcmp(in_buf, "NOCAPTURE")) == 0) { // Fin de la captura if(capture) { capture = 0; pthread_join(capture_thread, NULL); 56 printf("Captura detenida\n"); } } else if((strcmp(in_buf, "VIDEOPROP")) == 0) { // Envío de las propiedades del video capturado struct video_prop prop; char *prop_ptr = (char *)&prop; prop.width = video_buf.width; prop.height = video_buf.height; write(sd2, prop_ptr, sizeof(struct video_prop)); printf("Propiedades de video enviadas\n"); } else if(strcmp(in_buf, "STREAM") == 0) { // Inicio del envío del video al cliente if(!stream) { stream = 1; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&udp_thread, &attr, udpsocket, NULL); printf("Transmisión del video iniciada\n"); } } else if(strcmp(in_buf, "NOSTREAM") == 0) { // Fin del envío del video if(stream) { stream = 0; pthread_join(udp_thread, NULL); } } else if(strcmp(in_buf, "MOTION") == 0) { // Inicio de la detección de movimiento if(!motion) { motion = 1; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&motion_thread, &attr, motion_detect, NULL); printf("Detección de movimiento iniciada\n"); } } else if(strcmp(in_buf, "NOMOTION") == 0) { // Fin de la detección de movimiento if(motion) { motion = 0; pthread_join(motion_thread, NULL); } } else if(strcmp(in_buf, "CLOSE") == 0) { // Finalización de la conexión printf("Conexión finalizada\n"); break; } else { // Comando para el puerto serial byte_counter=0; for (i=0; i<256; i++) { byte_counter++; if (in_buf[i] == '\0') { in_buf[i] = 0x0d; in_buf[i+1] = 0x0a; in_buf[i+2] = '\0'; break; } } write(fd, in_buf, byte_counter+2); } } printf("Conexión con el cliente finalizada.\n"); close(sd2); // se restauran los ajustes del puerto serial tcsetattr(fd,TCSANOW,&oldtio); close(fd); // cierre del puerto serial } close(sd); } /*************************************************************************** * signal_handler_IO: Interrumpe al lazo de arriba, indicando que se * recibieron caracteres del puerto serial. Envía los datos al cliente. ***************************************************************************/ void signal_handler_IO (int status) { res = read(fd,buf,255); buf[res]=0; // el último caracter de la línea es nulo /* Envío de datos al cliente */ strcpy(out_buf, buf); write(sd2, out_buf, BUF_SIZE); } /* udpsocket: Envío de los cuadros de video al cliente */ void *udpsocket(void *null) { int sd, client_len; char buf[BUF_SIZE]; struct sockaddr_in server, client; int fc = 0, prev_fc = -1; * * 57 int counter = 0; /* Creación del socket UDP */ if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { fprintf(stderr, "Problemas creando el socket\n"); pthread_exit((void *)-1); } /* Información de la conexión */ memset((char *)&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); /* Enlace del socket */ if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) { fprintf(stderr, "Problemas al enlazar el socket\n"); pthread_exit((void *)-1); } int n; /* Espera que el cliente solicite los datos */ client_len = sizeof(client); if((n = recvfrom(sd, buf, BUF_SIZE, 0, (struct sockaddr *)&client, &client_len)) < 0) { fprintf(stderr, "Problemas recibiendo datagramas del ciente\n"); pthread_exit((void *)-1); } while(stream) { /* Envía el cuadro anterior al que está siendo capturado */ if(framecounter) fc = framecounter - 1; else fc = BUF_FRAME_NUM; if(prev_fc == fc) continue; prev_fc = fc; unsigned char *fp = main_buf + framesize * fc; /* Se divide el frame en paquetes UDP de 4096 bytes */ int n_packets = framesize / 4096; int i; for(i = 0; i <= n_packets; i++) { if(sendto(sd, (void *)(fp + i*4096), 4096, 0, (struct sockaddr *)&client, client_len) == -1) printf("Error enviando el paquete #%d\n", i); /* Para que no se pierdan paquetes en interfaces muy rápidas (ej. loopback) */ usleep(10); } /* Envío del paquete de sincronización */ int sync = n_packets; // Valor arbitrario, lo que importa es el tamaño if(sendto(sd, (char *)&sync, sizeof(int), 0, (struct sockaddr *)&client, client_len) == -1) printf("Error enviando paquete de sincronización\n"); counter++; } close(sd); pthread_exit(NULL); } 58 Programación del Cliente net.h: Definiciones y declaración de funciones /* * robolinux v0.8 * Programa para el control remoto del robot Staubli RX90 * net.h: Declaraciones y definiciones para la comunicación y la recepción * de video en el cliente * Copyright (C) 2005 Carlos Montes Solano <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #ifndef NET_H #define NET_H /* Definiciones */ #define BUF_SIZE 256 // Tamaño del buffer para el envío de datos //#define FALSE 0 //#define TRUE 1 /* Propiedades del cuadro recibido */ struct video_prop { int width; // Ancho int height; // Altura }; /* Información de la conexión UDP */ struct stream_data { char *addr; // Dirección del servidor int port; // Puerto de conexión int width; // Ancho del cuadro int height; // Altura del cuadro }; /* Declaración de funciones */ /* open_conn: Inicia la conexión con el servidor */ int open_conn(char *addr, int port); /* close_conn: Finaliza la conexión con el servidor */ int close_conn(void); /* send_tcp_msg: Envía mensaje al servidor */ int send_tcp_msg(char *msg, void *retdata, int size); /* start_vid_stream: Inicia recepción de los cuadros de video */ void *start_vid_stream(void *strmdata); /* rec_message_loop: Ciclo de recepcion de mensajes */ void *rec_message_loop(void *null); #endif /* NET_H */ 59 main.c: Programación principal del cliente /* * robolinux v0.8 * Programa para el control remoto del robot Staubli RX90 * main.c: Rutina principal del cliente * Copyright (C) 2005 Carlos Montes Solano <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include <stdio.h> #include <stdlib.h> #include <gtk/gtk.h> #include <pthread.h> #include "net.h" static int sd; unsigned char *frame; static GtkWidget *image; static GdkPixbuf *video_pixbuf; static struct video_prop prop; extern unsigned char *frame; /* Variable global de envío de mensajes */ int msg_status = -1; /* Elementos de la ventana */ GtkWidget *window; GtkWidget *main_vbox, *vbox2, *vbox3, *vbox4, *vbox5; GtkWidget *label, *label_frame3; GtkWidget *all_frames_hbox, *hbox1, *hbox_frame3; GtkWidget *separator; GtkWidget *frame1, *frame2, *frame3; GtkWidget *button; GtkWidget *spinner0, *spinner1, *spinner2; GtkWidget *val_label; GtkWidget *entry_frame2, *entry_frame3; GtkAdjustment *adj; GtkWidget *scrolledwindow; GtkWidget *textview; GtkTextBuffer *textbuffer; GtkTextIter iter; GtkTextMark *mark; /* refresh: Despliega la imagen recibida */ void refresh(void) { printf("En refresh()\n"); if(frame != NULL) { /* Crea el objeto GdkPixbuf */ video_pixbuf = gdk_pixbuf_new_from_data((const guchar *)frame, GDK_COLORSPACE_RGB, FALSE, 8, prop.width, prop.height, prop.width * 3, NULL, NULL); /* Inicializa image a partir de video_pixbuf */ gtk_image_set_from_pixbuf(image, video_pixbuf); /* Despliega image */ gtk_widget_show(image); } } /* get_value: Función del botón DRIVE */ static void get_value(GtkWidget *widget, gpointer data) { char buffer[256]; char *to = buffer; 60 gchar *buf; GtkSpinButton *spin0, *spin1, *spin2; spin0 = GTK_SPIN_BUTTON (spinner0); spin1 = GTK_SPIN_BUTTON (spinner1); spin2 = GTK_SPIN_BUTTON (spinner2); /* Se concatenan los valores de los botones spin, para generar el comando dirve */ to = stpcpy (to, "drive "); buf = g_strdup_printf ("%d", gtk_spin_button_get_value_as_int (spin0)); to = stpcpy (to, buf); to = stpcpy (to, ", "); buf = g_strdup_printf ("%d.0", gtk_spin_button_get_value_as_int (spin1)); to = stpcpy (to, buf); to = stpcpy (to, ", "); buf = g_strdup_printf ("%d", gtk_spin_button_get_value_as_int (spin2)); to = stpcpy (to, buf); /* Se envía el comando */ if((msg_status = send_tcp_msg(buffer, NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } g_free (buf); } /* callback: función del botón de la línea de comandos generales */ static void callback(GtkWidget *widget, GtkWidget *entry_frame2) { const gchar *entry_text; /* Captura de la línea de comando */ entry_text = gtk_entry_get_text (GTK_ENTRY (entry_frame2)); /* Envío del mensaje */ if((msg_status = send_tcp_msg(entry_text, NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } gtk_entry_set_text (GTK_ENTRY (entry_frame2), ""); } /* create_r: función del botón de crear rutina */ static void create_r(GtkWidget *widget, GtkWidget *entry_frame3) { const gchar *entry_text; char buffer[256]; char *to = buffer; /* Captura del nombre de la función */ entry_text = gtk_entry_get_text (GTK_ENTRY (entry_frame3)); to = stpcpy (to, "edit "); to = stpcpy (to, entry_text); /* Envío del mensaje */ if((msg_status = send_tcp_msg(buffer, NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } } /* load_r: función del botón de cargar rutina */ static void load_r(GtkWidget *widget, GtkWidget *entry_frame3) { const gchar *entry_text; char buffer[256]; char *to = buffer; /* Captura del nombre de la función */ entry_text = gtk_entry_get_text (GTK_ENTRY (entry_frame3)); to = stpcpy (to, "load "); to = stpcpy (to, entry_text); /* Envío del mensaje */ if((msg_status = send_tcp_msg(buffer, NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } } /* execute_r: función del botón de ejecutar rutina */ static void execute_r(GtkWidget *widget, GtkWidget *entry_frame3) { const gchar *entry_text; char buffer[256]; 61 char *to = buffer; /* Captura del nombre de la función */ entry_text = gtk_entry_get_text (GTK_ENTRY (entry_frame3)); to = stpcpy (to, "ex "); to = stpcpy (to, entry_text); /* Envío del mensaje */ if((msg_status = send_tcp_msg(buffer, NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } } /****************** Inicion de la funcion main *******************/ int main(int argc, char *argv[]) { char *frame = NULL; struct stream_data strmdata; pthread_t stream_thread, recmessage_thread; pthread_attr_t attr; int trc; /* Inicialización del soporte de threads en GLib */ g_thread_init(NULL); gdk_threads_init(); /* Inicialización GTK */ gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Control Remoto"); gtk_window_set_default_size (GTK_WINDOW (window), 1000, 500); gtk_container_set_border_width(GTK_CONTAINER(window), 40); /* Manejo de señales GTK */ g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(gtk_widget_destroy), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); /* Establecimiento de la conexión */ if(open_conn("localhost", 24001) != 0) { fprintf(stderr, "Error iniciando la conexión con el servidor.\n"); exit(EXIT_FAILURE); } /* Solicitud de inicio de captura */ if((msg_status = send_tcp_msg("CAPTURE", NULL, 0)) != 0) { fprintf(stderr, "Problemas solicitando el inicio de la captura\n"); exit(EXIT_FAILURE); } /* Petición de envío de las propiedades de video */ if((msg_status = send_tcp_msg("VIDEOPROP", (void *)&prop, sizeof(struct video_prop))) != 0) { fprintf(stderr, "Error obteniendo propiedades de video\n"); exit(EXIT_FAILURE); } sleep(5); /* Solicita al servidor el envío de los cuadros */ if((msg_status = send_tcp_msg("STREAM", NULL, 0)) != 0) { fprintf(stderr, "Error obteniendo el frame\n"); exit(EXIT_FAILURE); } /* Datos de la conexión UDP */ strmdata.addr = "localhost"; strmdata.port = 24001; strmdata.width = prop.width; strmdata.height = prop.height; /* Crea el hilo de recepción de video */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&stream_thread, &attr, start_vid_stream, (void *)&strmdata); printf("Stream Iniciado\n"); /* Crea el hilo de recepción de mensajes */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&recmessage_thread, &attr, rec_message_loop, NULL); printf("Recepcion de Mensajes Iniciada\n"); /***************** Creacion de la ventana *************************************** ********************************************************************************/ 62 main_vbox = gtk_vbox_new (FALSE, 5); gtk_container_add (GTK_CONTAINER (window), main_vbox); label = gtk_label_new ("COMUNICACION CON ROBOT\t\t\t\t\t\t\t\t\t\t\t\t\tVideo en tiempo real"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, TRUE, 0); hbox1 = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (main_vbox), hbox1, FALSE, FALSE, 0); scrolledwindow = gtk_scrolled_window_new (NULL, NULL); gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow, TRUE, TRUE, 0); textview = gtk_text_view_new (); gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE); gtk_container_add (GTK_CONTAINER (scrolledwindow), textview); image = gtk_image_new(); gtk_box_pack_start (GTK_BOX (hbox1), image, FALSE, FALSE, 0); // separador vertical separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (main_vbox), separator, FALSE, TRUE, 10); all_frames_hbox = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (main_vbox), all_frames_hbox, FALSE, FALSE, 0); /************* Primer frame ***********/ frame1 = gtk_frame_new ("Comando Drive"); gtk_box_pack_start (GTK_BOX (all_frames_hbox), frame1, TRUE, TRUE, 0); vbox2 = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox2), 15); gtk_container_add (GTK_CONTAINER (frame1), vbox2); label = gtk_label_new ("Joint #:"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (1.0, 1.0, 6.0, 1.0, 5.0, 0.0); spinner0 = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner0), TRUE); gtk_box_pack_start (GTK_BOX (vbox2), spinner0, FALSE, TRUE, 0); label = gtk_label_new ("Grados:"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (0.0, -180.0, 180.0, 10.0, 5.0, 0.0); spinner1 = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner1), TRUE); gtk_box_pack_start (GTK_BOX (vbox2), spinner1, FALSE, TRUE, 0); label = gtk_label_new ("Velocidad (%):"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, TRUE, 0); adj = (GtkAdjustment *) gtk_adjustment_new (0.0, 0.0, 100.0, 5.0, 5.0, 0.0); spinner2 = gtk_spin_button_new (adj, 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner2), TRUE); gtk_box_pack_start (GTK_BOX (vbox2), spinner2, FALSE, TRUE, 0); val_label = gtk_label_new (""); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox2), separator, FALSE, TRUE, 5); button = gtk_button_new_with_label ("DRIVE"); gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, TRUE, 0); g_object_set_data (G_OBJECT (button), "user_data", val_label); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (get_value), GINT_TO_POINTER (1)); /************ Fin del primer frame **********/ // separadores horizontales separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (all_frames_hbox), separator, FALSE, TRUE, 10); /*********** Segundo Frame *******************/ frame2 = gtk_frame_new ("Linea de Comandos"); gtk_box_pack_start (GTK_BOX (all_frames_hbox), frame2, TRUE, TRUE, 0); vbox3 = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox3), 10); gtk_container_add (GTK_CONTAINER (frame2), vbox3); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox3), separator, FALSE, TRUE, 15); entry_frame2 = gtk_entry_new (); 63 gtk_entry_set_text (GTK_ENTRY (entry_frame2), "Comando"); gtk_box_pack_start (GTK_BOX (vbox3), entry_frame2, FALSE, TRUE, 0); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox3), separator, FALSE, TRUE, 10); button = gtk_button_new_with_label ("Ejecutar"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), (gpointer) entry_frame2); gtk_box_pack_start (GTK_BOX (vbox3), button, FALSE, TRUE, 0); /************ Fin del segundo frame **********/ // separadores horizontales separator = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (all_frames_hbox), separator, FALSE, TRUE, 10); /*********** Tercer Frame *******************/ frame3 = gtk_frame_new ("Rutinas"); gtk_box_pack_start (GTK_BOX (all_frames_hbox), frame3, TRUE, TRUE, 0); hbox_frame3 = gtk_hbox_new (FALSE, 5); gtk_container_add (GTK_CONTAINER (frame3), hbox_frame3); vbox4 = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox4), 30); gtk_box_pack_start (GTK_BOX (hbox_frame3), vbox4, FALSE, FALSE, 0); label_frame3 = gtk_label_new ("Nombre de la Rutina"); gtk_misc_set_alignment (GTK_MISC (label_frame3), 0, 0.5); gtk_box_pack_start (GTK_BOX (vbox4), label_frame3, FALSE, TRUE, 0); entry_frame3 = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (vbox4), entry_frame3, FALSE, TRUE, 0); vbox5 = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox5), 10); gtk_box_pack_start (GTK_BOX (hbox_frame3), vbox5, FALSE, FALSE, 0); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox5), separator, FALSE, TRUE, 10); button = gtk_button_new_with_label ("Crear nueva Rutina"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (create_r), (gpointer) entry_frame3); gtk_box_pack_start (GTK_BOX (vbox5), button, FALSE, TRUE, 0); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox5), separator, FALSE, TRUE, 10); button = gtk_button_new_with_label ("Cargar Rutina"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (load_r), (gpointer) entry_frame3); gtk_box_pack_start (GTK_BOX (vbox5), button, FALSE, TRUE, 0); separator = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (vbox5), separator, FALSE, TRUE, 10); button = gtk_button_new_with_label ("Ejecutar Rutina"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (execute_r), (gpointer) entry_frame3); gtk_box_pack_start (GTK_BOX (vbox5), button, FALSE, TRUE, 0); /************ Fin del tercer frame **********/ gtk_widget_show_all (vbox2); gtk_widget_show_all (vbox3); gtk_widget_show_all (vbox4); gtk_widget_show_all (vbox5); gtk_widget_show_all (frame1); gtk_widget_show_all (frame2); gtk_widget_show_all (frame3); gtk_widget_show_all (all_frames_hbox); gtk_widget_show_all (hbox1); gtk_widget_show_all (hbox_frame3); gtk_widget_show_all (main_vbox); gtk_widget_show (main_vbox); gtk_widget_show(window); /********************** Fin de elementos de la ventana *********************************** *********************************************************************************/ /* refresh se ejecuta cada 10 ms */ g_timeout_add(10, refresh, NULL); /* Ciclo principal GTK */ gdk_threads_enter(); gtk_main(); gdk_threads_leave(); /* Al finalizar el programa */ if((msg_status = send_tcp_msg("NOSTREAM", NULL, 0)) != 0) { fprintf(stderr, "Error iniciando detección de movimiento\n"); exit(EXIT_FAILURE); 64 } if((msg_status = send_tcp_msg("NOMOTION", NULL, 0)) != 0) { fprintf(stderr, "Error iniciando detección de movimiento\n"); exit(EXIT_FAILURE); } if((msg_status = send_tcp_msg("NOCAPTURE", NULL, 0)) != 0) { fprintf(stderr, "Error iniciando detección de movimiento\n"); exit(EXIT_FAILURE); } if((msg_status = send_tcp_msg("CLOSE", NULL, 0)) != 0) { fprintf(stderr, "Error cerrando la conexión\n"); exit(EXIT_FAILURE); } close_conn(); return 0; } /* open_conn: Inicia la conexión con el servidor */ int open_conn(char *addr, int port) { struct hostent *hp; struct sockaddr_in server; char *buf_ptr, in_buf[BUF_SIZE]; int bytes_left; int n_read; /* Creación del socket */ if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "No se puede abrir el socket\n"); return -1; } /* Información de la conexión */ memset((char *)&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(port); if((hp = gethostbyname(addr)) == NULL) { fprintf(stderr, "No se puede resolver el nombre del servidor\n"); return -1; } memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length); /* Conexión con el servidor */ if(connect(sd, (struct sockaddr *)&server, sizeof(server)) == -1) { fprintf(stderr, "No se puede establecer la conexión con el servidor\n"); return -1; } printf("Conexión establecida con %s\n", hp->h_name); /* Recepción del mensaje de bienvenida */ bytes_left = BUF_SIZE; buf_ptr = in_buf; while((n_read = read(sd, buf_ptr, bytes_left)) > 0) { buf_ptr += n_read; bytes_left -= n_read; } return 0; } /* close_conn: Finaliza la conexión con el servidor */ int close_conn(void) { close(sd); printf("Conexión finalizada.\n"); return 0; } /* send_tcp_msg: Envía mensaje al servidor */ int send_tcp_msg(char *msg, void *retdata, int size) { char *buf_ptr, in_buf[BUF_SIZE], out_buf[BUF_SIZE]; int bytes_left, n_read; /* Envía el mensaje */ strcpy(out_buf, msg); write(sd, out_buf, BUF_SIZE); /* Recepción de datos */ if(retdata != NULL) { bytes_left = size; buf_ptr = retdata; while((n_read = read(sd, buf_ptr, bytes_left)) > 0) { 65 buf_ptr += n_read; bytes_left -= n_read; } } return 0; } /* start_vid_stream: Inicia recepción de los cuadros de video */ void *start_vid_stream(void *strmdata) { struct sockaddr_in server, client; struct hostent *hp; int sdu, server_len, n; char buf[BUF_SIZE]; unsigned char *temp_frame; struct stream_data *sdata = (struct stream_data *)strmdata; int framesize = sdata->width * sdata->height * 3; /* Creación del socket UDP */ if((sdu = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { fprintf(stderr, "Error creando el socket UDP\n"); exit(-1); } /* Información del servidor */ memset((char *)&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(sdata->port); if((hp = gethostbyname(sdata->addr)) == NULL) { fprintf(stderr, "Error resolviendo la dirección del servidor\n"); exit(-1); } memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length); /* Información del cliente */ memset((char *)&client, 0, sizeof(client)); client.sin_family = AF_INET; client.sin_port = htons(0); client.sin_addr.s_addr = htonl(INADDR_ANY); /* Enlace del socket */ if(bind(sdu, (struct sockaddr *)&client, sizeof(client)) == -1) { fprintf(stderr, "Error enlazando el socket\n"); exit(-1); } sleep(1); /* Espera a que el UDP en el servidor esté listo */ server_len = sizeof(server); strcpy(buf, "2+2=5"); if(sendto(sdu, buf, BUF_SIZE, 0, (struct sockaddr *)&server, server_len) == -1) { fprintf(stderr, "Problemas enviando datagrama al servidor\n"); exit(-1); } /* Número de paquetes */ int n_packets = framesize / 4096; /* Cantidad de bytes sobrantes */ int leftover = framesize % 4096; int pc = 0; int p_size; char buf2[4096]; /* Reserva memoria para los cuadros */ temp_frame = malloc(framesize); frame = malloc(framesize); /* Recibe los paquetes UDP con el cuadro */ while(1) { p_size = recvfrom(sdu, buf2, 4096, 0, (struct sockaddr *)&server, &server_len); if(p_size < 0) { // Si hay error en la recepción fprintf(stderr, "Error recibiendo el paquete %d\n", pc); continue; } else if(p_size == sizeof(int)) { // Paquete de sincronización if(pc == n_packets + 1) { memcpy(frame, temp_frame, framesize); } else { fprintf(stderr, "Imagen con información incorrecta\n"); } pc = 0; } else if(p_size == 4096) { // Paquete con datos if(pc == n_packets) memcpy(temp_frame + pc*4096, buf2, leftover); else memcpy(temp_frame + pc*4096, buf2, 4096); 66 pc++; } } /* Libera memoria */ free(temp_frame); free(frame); close(sdu); pthread_exit(NULL); } /* rec_message_loop: Ciclo de recepcion de mensajes */ void *rec_message_loop(void *null){ char *buf_ptr, in_buf[BUF_SIZE], out_buf[BUF_SIZE]; int bytes_left, n_read; while(1) { /* Lectura de los datos enviados por el cliente */ bytes_left = BUF_SIZE; buf_ptr = in_buf; while((n_read = read(sd, buf_ptr, bytes_left)) > 0) { buf_ptr += n_read; bytes_left -= n_read; } if(n_read < 0) continue; /* Los datos recibidos son desplegados en el textview */ gdk_threads_enter(); textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); mark = gtk_text_buffer_get_insert (textbuffer); gtk_text_buffer_get_iter_at_mark(textbuffer, &iter, mark); gtk_text_buffer_insert (textbuffer, &iter, in_buf, -1); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), mark, 0.0, TRUE, 0.0, 0.0); gdk_threads_leave(); } pthread_exit(NULL); 67 ANEXOS Rutina de Calibración [2] Antes de poder operar el robot este se debe calibrar. El robot Staübli cuenta con una rutina de calibración que viene escrita desde la fábrica en el controlador. Antes de ejecutar la rutina de calibración se debe asegurar que el robot se encuentra en la POSICION READY de fábrica. Los siguientes cuadros muestran paso a paso, cómo se ve en pantalla el proceso de calibración (ejecución de comandos y líneas de respuesta del controlador). Además se agrega una pequeña descripción de cada una de ellas. Después de que el controlador arranca esta es la primera pantalla que se obtiene. Los comandos “a, a 1, zero” son para eliminar rutinas activas, el comando load carga la rutina spec.v2 (calibración) y el comando “ex” ejecuta rutinas. 68 Al cargar la rutina de calibración esta es la pantalla que aparece Aquí se selecciona la opción uno (#1) y se introduce la clave: SPEC_CAL Una vez introducida la clave aparece la siguiente pantalla que muestra la opción para cambiar las especificaciones del robot. Se selecciona la opción número dos (#2). 69 Luego aparece la siguiente pantalla de ROBOT SPECIFICATIONS. Aquí es posible cambiar muchas de las especificaciones del robot. Aquí se debe seleccionar la opción tres (#3). Al seleccionar la opción tres (#3) aparece la siguiente pantalla “ROBOT 1/MOTOR 1 CALIBRATION”. Aquí se selecciona la opción cinco (#5). Nuevamente aparece en pantalla un recordatorio que el robot debe estar en la POSICION READY (la de fábrica). 70 Después de cada proceso se vuelve a la pantalla anterior. Ahora se debe salir de esta aplicación y salvar las especificaciones. Aquí se selecciona la opción cero (#0). Volvemos a la siguiente pantalla. Aquí nuevamente la opción cero (#0) y volvemos a la siguiente pantalla. 71 En el “Main Menu” se selecciona la opción cinco (#5), para guardar todas las especificaciones en el disco y aparece la siguiente pantalla. 72 Al presionar ENTER se vuelve a la pantalla anterior. Ahora lo que queda es seleccionar la opción cero (#0). Ya el robot está calibrado y el controlador debe ser reiniciado para que los nuevos parámetros puedan ser cargados. El controlador cuenta con un pequeño botón de RESET en la esquina superior izquierda. Una vez que el controlador ha cargado se puede probar la calibración ejecutando dos comandos. Primero el “DO READY” y después el “WHERE”. Si el comando “WHERE” despliega la siguiente pantalla el proceso fue ejecutado correctamente.