Informe - Escuela de Ingeniería Eléctrica

Anuncio
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.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.
Descargar