Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado...

55
Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado Rpi-Face Autores (orden alfabético): Francisco Javier García Gómez Julio Alberto González Marín Código de la pareja: MT-1 Sistemas Digitales II Universidad Politécnica de Madrid Departamento de Ingeniería Electrónica E.T.S.I. de Telecomunicación

Transcript of Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado...

Page 1: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto

desarrollado en Sistemas

Digitales II (SDII)

Curso 2012/2013

Título del proyecto desarrollado “Rpi-Face”

Autores (orden alfabético): Francisco Javier García Gómez

Julio Alberto González Marín

Código de la pareja: MT-1

Sistemas Digitales II

Universidad Politécnica de Madrid

Departamento de Ingeniería Electrónica

E.T.S.I. de Telecomunicación

Page 2: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

ÍNDICE GENERAL

1 INTRODUCCIÓN ........................................................................................................................ 2

2 DIAGRAMA DE SUBSISTEMAS ............................................................................................ 3

3 DESCRIPCIÓN DEL SUBSISTEMA HARDWARE ............................................................ 5

3.1 CONEXIÓN ENTRE LA RASPBERRY PI Y LA CABEZA ROBÓTICA .............................................. 5

3.2 CONEXIÓN ENTRE LA RASPBERRY PI Y EL ORDENADOR ........................................................ 6

3.3 CONEXIÓN ENTRE LA RASPBERRY PI Y EL ALTAVOZ ............................................................. 7

4 DESCRIPCIÓN DEL SUBSISTEMA SOFTWARE ............................................................. 8

4.1 INTERFAZ CON LA CABEZA ROBÓTICA .................................................................................. 10

4.1.1 Interfaz con el puerto UART: rpi_uart.c, rpi_uart.h ................................................. 10

4.1.2 Interfaz de control de servos: servo_controller.c, servo_controller.h ...................... 12

4.1.3 Interfaz de control de la cabeza: face_controller.c, face_controller.h ..................... 14

4.2 INTERFAZ CON LA SÍNTESIS DEL HABLA Y EL ALTAVOZ ....................................................... 15

4.2.1 Festival: festival.h ........................................................................................................ 15

4.2.2 Interfaz de comunicación con festival: speech_synthesis.c, speech_synthesis.h ...... 16

4.3 ANALIZADOR DE TEXTOS ...................................................................................................... 16

4.4 PROGRAMA DE CÁLCULO DE POSICIÓN ................................................................................. 17

4.5 SERVIDOR WEB ..................................................................................................................... 18

5 DESCRIPCIÓN DE LAS MEJORAS .................................................................................... 24

6 PRINCIPALES PROBLEMAS ENCONTRADOS .............................................................. 25

7 MANUAL DE USUARIO ......................................................................................................... 26

8 BIBLIOGRAFÍA ........................................................................................................................ 27

9 ANEXO I: CÓDIGO DEL PROGRAMA DEL PROYECTO FINAL ............................. 28

Page 3: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

2

1 Introducción

Esta memoria pretende ser un resumen del trabajo realizado para el proyecto Rpi-face.

El objetivo de este proyecto ha sido realizar un sistema lo más empotrado posible sensible a las

emociones. Es empotrado porque todo el código se encuentra y se ejecuta en la placa Raspberry Pi, solo

se necesitan los periféricos como la pantalla, altavoces y la cabeza robótica. Este tipo de sistemas

permitirá en un futuro mejorar la interacción máquina-persona, haciendo más agradable e intuitivo el

manejo de las máquinas: GPS, reproductores de música, ordenadores, despertadores... Nosotros

imaginamos un futuro en el cual un usuario se levante y su despertador le dé los buenos días, le pregunte

qué tal ha dormido, le informe del tiempo; y todo ello mientras detecta si el usuario está triste, cansado,

enfermo... y sea empático con él, incluso proponiéndole distintas cosas en función de cómo se encuentre.

A nosotros esa idea nos ha fascinado y es por eso por lo que realizamos este proyecto. Nuestro sistema se

centra en que la máquina pueda tener una apariencia física que nos recuerde a un ser humano, con la

posibilidad de tener distintos gestos que utilizará en función de la situación y memoria de los estados

emocionales anteriores.

Cinco palabras que definirían nuestro proyecto son: emocional, empotrabilidad, innovación,

ergonómico y rápido.

El sistema básico consiste específicamente en la interacción y el manejo de una cara robótica. Esta

cara será movida por una controladora de servos y tiene la posibilidad de realizar distintos gestos.

En nuestro caso hemos decidido que toda la programación vaya empotrada en la placa Raspberry

Pi, por lo que tendremos ahí el sistema que se comunique con la controladora de servos. Además

tenemos también un sistema de síntesis del habla implementado con el programa Festival. El núcleo

principal de la práctica consistirá en un programa desarrollado por el Departamento de Ingeniería

Electrónica de la Universidad Politécnica de Madrid con una licencia de software libre. Este analiza lo

positivo o negativo que es el mensaje recibido del servidor y lo evalúa. Esto nos permitirá ofrecerle al

usuario una experiencia emocional en su interacción con el robot.

Al usuario se le ofrece una interfaz web con la posibilidad de incluir un texto y enviarlo. Este texto

será analizado y evaluado por el programa y la cara se moverá en función de eso y de su estado anterior.

Por otra parte el usuario tiene unos botones mediante los cuales puede poner directamente la cara

con la emoción que él elija.

Todo ello está secundado por un sistema de síntesis de voz, que dirá algo cada vez que el usuario

interactúe con el sistema.

El desarrollo del proyecto se puede seguir a través del blog http://rpiface.wordpress.com/ .

Además, el código del proyecto está publicado y continuamente actualizado en GitHub:

https://github.com/javierggomez/Rpi-Face

Page 4: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

3

2 Diagrama de subsistemas

Servidor

web

Controlador de

la cara

Analizador

de textos

Cálculo de

posición

Sintetizador de

sonido

Usuario Administrador

Mensaje

Acción directa

Page 5: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

4

El sistema completo es demasiado grande para verlo bien en un solo diagrama. Se pueden

ver diagramas más detallados de cada parte en los capítulos dedicados a la parte hardware y a la parte

software.

El elemento central de la práctica es la Raspberry Pi. En la parte hardware, tenemos tres

subsistemas: la conexión a la cabeza robótica, la conexión al altavoz y la conexión al ordenador:

- Conexión a la cabeza robótica: se realiza mediante el puerto UART de la Raspberry Pi

(GPIO 14 y 15). Hace falta además la conexión de masa y de alimentación. Como la

Raspberry utiliza lógica de 3.3 V y la cabeza utiliza niveles RS232 (entre ±5.5 V y ±15

V), es necesario colocar en medio un conversor de nivel MAX3232.

- Conexión al altavoz: la Raspberry Pi se conecta a un altavoz por un conector jack.

Debido a la baja potencia que puede dar la Raspberry Pi, utilizamos un altavoz

alimentado externamente para que se puedan oír mejor los mensajes.

- Conexión al ordenador: se realiza a través de un cable Ethernet y sirve para hacer

pruebas y transferir código, además de para conectar la Raspberry Pi a Internet

provisionalmente. Cuando la práctica esté terminada, se podrá sustituir por una

conexión directa a Internet.

La parte software se compone de 5 módulos:

- Controlador de la cabeza: se ocupa de controlar la cabeza robótica a través del puerto

UART.

- Sintetizador de voz: sintetiza los mensajes recibidos y los mensajes de estados y los

reproduce por el altavoz.

- Analizador de textos: analiza los mensajes recibidos y los etiqueta según lo positivos o

negativos que sean, para enviar el resultado al programa de cálculo de posición.

- Programa de cálculo de posición: calcula la nueva posición de la cara a partir de la

antigua y de la valoración obtenida del analizador de textos para el mensaje nuevo.

Envía la nueva posición al controlador de la cara, y el mensaje a reproducir al

sintetizador de voz.

- Servidor web: se ocupa de mostrar la interfaz web y gestionar las interacciones con el

usuario. Recibe los mensajes y los envía al analizador de textos, o redirige las

solicitudes de acciones directas al controlador de la cabeza.

Page 6: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

5

-

3 Descripción del subsistema Hardware

Nuestro sistema tiene varios módulos hardware:

Conexión entre la Raspberry Pi y la cabeza robótica. Para ello se utilizarán los puertos

GPIO de la Raspberry Pi, que se conectarán a un adaptador que traduce la lógica a RS232

(la conexión que tiene la cabeza)

Conexión con el portátil

Conexión con los altavoces

3.1 Conexión entre la Raspberry Pi y la cabeza robótica

Los puertos de propósito general (GPIO) de la Raspberry Pi, y en particular los pines

correspondientes al puerto serie (UART TX y RX, 8 y 10) funcionan a 3.3 V. La cabeza robótica se

Page 7: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

6

compone de 8 servos controlados por la controladora de servos NetMedia Servo 8 Torque Board, que

funciona con niveles RS232 (entre ±5.5 V y ±15 V). Por tanto, para conectar ambos sistemas es

necesario usar un conversor de nivel. Hemos utilizado el MAX3232; el esquemático de la conexión es el

siguiente:

Los cuatro condensadores utilizados deben ser de 100 nF, como indica la hoja de características

del MAX3232. La alimentación es proporcionada por el pin 3.3 Vcc (pin 1) de la Raspberry Pi (las

funciones de los pins del módulo BCM2835 de la Raspberry Pi se pueden ver en

http://elinux.org/RPi_Low-level_peripherals). El integrado MAX3232 convierte la lógica de 3.3 V de la

Raspberry Pi en la lógica RS232 de la controladora de servos.

La principal dificultad para realizar las conexiones fue mantener los cables de conexión en sus

posiciones el conector RS232. Para solucionarlo, soldamos los cables a los pines correspondientes de un

conector macho RS232 y conectamos éste al conector hembra de la controladora.

La controladora de servos funciona como un puerto serie que recibe comandos a 19200 bps con 8

bits de datos, uno de parada y sin paridad. Estos comandos vienen detallados en la hoja de características

de la controladora, y los que usamos en la práctica se explican en la parte de software. Para comprobar el

funcionamiento de nuestras conexiones, escribimos el programa servo_controllerTest.c, que prueba

todos los comandos que pudiéramos necesitar, y verificamos que la cabeza se movía correctamente. Este

programa se explica en el apartado de software, y el código completo se encuentra en el Anexo y en el

repositorio de GitHub.

3.2 Conexión entre la Raspberry Pi y el ordenador

Se trata de una conexión Ethernet entre el portátil (con sistema operativo Ubuntu) y la Raspberry

Pi (con Raspbian, una distribución de Debian optimizada para el procesador de la raspberry Pi). El

objetivo principal se trata de tener un medio gráfico de ver las operaciones que realizamos dentro de la

Raspberry, tanto de edición de código como depuración.

La Raspberry Pi se conecta a Internet a través del ordenador, utilizando la funcionalidad de

Ubuntu Network Manager para compartir conexión.

Además, actualmente sirve de interfaz con el usuario, pero en un futuro se podrá prescindir de esto

y que en la interacción solo sea necesario un micrófono para comunicarnos con la cara.

El control de la placa y la transferencia de código se realizan mediante el protocolo de

comunicaciones SSH (Secure Shell), que permite acceder a una terminal de la Raspberry desde el

ordenador a través de una conexión cifrada.

Page 8: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

7

3.3 Conexión entre la Raspberry Pi y el altavoz

Se trata de una conexión mediante conector jack. Esta conexión permite transmitir en analógico el

audio que ha sintetizado festival a los altavoces. Se ha optado por unos altavoces con alimentación, ya

que la potencia que sacasen de la Raspberry unos que no tuviesen sería insuficiente para una experiencia

de audio satisfactoria.

Page 9: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

8

4 Descripción del subsistema Software

Arquitectura del sistema software

index.html

rpi_uart.c

Cabeza

robótica

servo_controller.c

face_controller.c

move_face.c

CGI

text_analyzer

Acción

directa

savedata.mfc

semaphore.t3

fichero.raw

server_query.c mensaje

t3

Usuario Administrador

speech_synthesis.c

festival.h

Altavoz

Page 10: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

9

En el diagrama anterior se puede ver la arquitectura del sistema software de la práctica, que se

compone de cinco partes: la interfaz con la cabeza robótica, el sintetizador de voz, el analizador de textos,

el programa de cálculo de la nueva posición y el servidor web.

La interfaz con la cabeza robótica está estructurada en tres niveles, que empezando por el más

bajo son: interfaz con el puerto UART de la Raspberry Pi (rpi_uart.c), interfaz para el control arbitrario

de los servos de la controladora de la cabeza (servo_controller.c) y programa con funciones para poner

caras determinadas (face_controller.c). Cada uno de estos niveles utiliza únicamente las funciones del

inferior para ofrecer funciones de más alto nivel al superior.

El sintetizador de voz es el programa festival ( http://www.cstr.ed.ac.uk/projects/festival/ ),

proyecto del Centre for Speech Technology Research (CSTR) en la Universidad de Edimburgo,

distribuido bajo una licencia de software libre. El programa speech_synthesis.c utiliza las APIs de

Festival para sintetizar los mensajes recibidos del servidor y reproducirlos.

El analizador de textos se basa en el programa t3. Este programa, desarrollado por el

Departamento de Ingeniería Electrónica de la Universidad Politécnica de Madrid y también con una

licencia de software libre, analiza lo positivo o negativo que es el mensaje recibido del servidor y lo

evalúa entre 1 y 9, pasando el resultado al programa de cálculo de posición. Como la inicialización del

programa t3 es compleja computacionalmente y dura unos 4 segundos en la Raspberry Pi, hemos

modificado el programa t3 para que solo se inicialice una vez y se quede en espera de mensajes de

servidor, analizándolos a medida que llegan. Para ello utilizamos el fichero data/fichero.raw, en el que el

servidor pasa los mensajes a t3; y el fichero data/semaphore.t3, que es una especie de semáforo que el

servidor crea cuando hay un nuevo mensaje y t3 elimina cuando termina de procesar el mensaje.

El programa de cálculo de posición (move_face.c) calcula la nueva posición de la cabeza a partir

de la nota obtenida de t3 y de la posición anterior. La posición se guarda cada vez en el fichero

data/savedata.mfc.

El servidor web está implementado utilizando Apache 2. La página web tiene botones para mover

la cara a posiciones predeterminadas y un campo de texto para escribir un mensaje y enviarlo al

analizador de textos. El programa server_query.c implementa estas dos funcionalidades, llamando a la

interfaz con la cabeza si se pulsa un botón y mandando el fichero con el mensaje y el semáforo a t3 si se

envía un mensaje.

Page 11: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

10

4.1 Interfaz con la cabeza robótica

Este bloque se ocupa de proporcionar funciones al resto del programa para mover la cabeza

robótica. Se estructura en tres niveles: interfaz con el puerto UART de la Raspberry Pi (rpi_uart.c),

interfaz para el control arbitrario de los servos de la controladora de la cabeza (servo_controller.c) y

programa con funciones para poner caras determinadas (face_controller.c). Cada nivel utiliza solo

funciones del nivel inferior para proporcionar funciones de más alto nivel al nivel superior.

4.1.1 Interfaz con el puerto UART: rpi_uart.c, rpi_uart.h

- Configuración previa:

La Raspberry Pi tiene un sistema operativo Linux, donde se puede acceder al puerto UART como

si fuera un archivo, concretamente /dev/ttyAMA0 . La función predeterminada de este puerto es enviar

mensajes de diagnóstico al encender y apagar el sistema. Para poder acceder arbitrariamente al puerto y

utilizarlo para nuestros propósitos, antes de ejecutar este código es necesario desconfigurar esa función y

liberar el puerto. Para esto, basta con modificar los dos siguientes archivos de configuración:

- /boot/cmdline.txt Editar este archivo y eliminar o comentar las opciones referidas al puerto serie, que por defecto son

las siguientes:

console=ttyAMA0,115200 kgdboc=ttyAMA0,115200

- /etc/inittab Eliminar o comentar la línea referente al puerto serie:

T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 Una vez hecho esto, es necesario dar permisos de lectura y escritura en el puerto:

sudo chmod a+rw /dev/ttyAMA0

rpi_uart.c

Cabeza

robótica

servo_controller.c

face_controller.c

Page 12: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

11

Ahora ya se puede utilizar el programar rpi_uart.c para acceder al puerto. Este programa ofrece

funciones para enviar y recibir datos arbitrarios por el puerto UART. La funcionalidad de este programa

ha sido probada mediante un test: el rpi_uart_test.c, en el que se ha probado el correcto funcionamiento

de todas las funciones. Ofrece las siguientes funciones: int uart_initialize(int read, int write, int flags);

void uart_close(int fd);

void uart_setBaudRate(int fd, int baudRate);

void uart_setBlockForInput(int fd, int nChars);

int uart_read(int fd, char *buffer, int nChars);

int uart_write(int fd, char *string, int nChars);

La función uart_initialize inicia la comunicación con el puerto serie, que en Linux equivale a abrir

su archivo asociado y configurar las opciones dadas en read, write y flags. Su valor de retorno es el

descriptor de archivo que se utilizará en las demás funciones para enviar o recibir datos. La configuración

predeterminada son 19200 bps, 8 bits de datos, 1 de parada y sin paridad.

- Entradas:

o read: 1 si se quiere acceso de lectura, 0 si no.

o write: 1 si se quiere acceso de escritura, 0 si no.

o flags: flags adicionales para la apertura del puerto, definidos en <fcntl.h>. Para nuestro

caso, basta con poner 0.

- Valor de retorno: descriptor de archivo para usar en las demás funciones.

La función uart_close cierra la comunicación con el puerto, lo que equivale a cerrar su archivo

asociado.

- Entradas:

o fd: descriptor de archivo obtenido de uart_initialize.

La función uart_setBaudRate configura la velocidad de transmisión.

- Entradas:

o fd: descriptor de archivo obtenido de uart_initialize.

o baudRate: constante definida en <fcntl.h> correspondiente a la nueva velocidad de

transmisión. Por ejemplo, para 19200 bps es B19200.

La función uart_setBlockForInput configura si las operaciones de lectura sobre el puerto serán

bloqueantes o no, y en caso afirmativo cuántos caracteres se esperarán antes de retornar. Si no son

bloqueantes (nChars=0), la función uart_read retornará instantáneamente aunque no haya caracteres

nuevos que leer en el puerto. En caso contrario, la función uart_read bloqueará la ejecución del programa

hasta que haya al menos nChars caracteres que leer.

- Entradas:

o fd: descriptor de archivo obtenido de uart_initialize.

o nChars: número de caracteres mínimos de cada operación de lectura, 0 para no bloquear

en lectura.

La función uart_write envía los nChars caracteres de string por el puerto UART, o todos si

strlen(string)<=nChars. Devuelve el número de caracteres enviados.

- Entradas:

o fd: descriptor de archivo obtenido de uart_initialize.

o string: cadena de caracteres a enviar.

o nChars: número máximo de caracteres a enviar. Se enviarán menos si hay menos en

string.

Page 13: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

12

- Valor de retorno: Número de caracteres que fueron enviados, o -1 si hubo error de escritura.

La función uart_read lee un máximo de nChars del puerto UART (o menos si no hay suficientes y

se alcanza el mínimo de caracteres leídos en una sola operación definido en uart_setBlockForInput). La

función bloquea la ejecución hasta haber leído al menos el número mínimo definido en

uart_setBlockForInput. Una vez alcanzado el mínimo, leerá todos los que haya en ese momento sin

superar nChars y retornará inmediatamente. Si se definió un mínimo de 0 en uart_setBlockForInput, la

función uart_read nunca bloquea la ejecución. Los caracteres leídos se almacenan en buffer, y la función

devuelve el número de caracteres leídos.

- Entradas:

o fd: descriptor de archivo obtenido de uart_initialize.

o buffer: cadena donde se almacenarán los caracteres leídos.

o nChars: número máximo de caracteres que leer. Se leerán menos si hay menos en el

puerto.

- Valor de retorno: Número de caracteres que fueron leídos, o -1 si hubo error de escritura.

4.1.2 Interfaz de control de servos: servo_controller.c, servo_controller.h

Este programa ofrece a los niveles superiores funciones de control de los servos. Utiliza la interfaz

proporcionada por rpi_uart.c para ofrecer funciones que envían los comandos correspondientes a la

controladora de servos. Estos comandos deben enviarse a 19200 bps con 8 bits de datos, 1 de espera y sin

paridad, y están definidos en la hoja de características de la controladora

(http://www.basicx.com/Products/servo/srv8-t-V1.4.pdf ). La funcionalidad ha sido probada mediante un

test: el servo_controller_test.c, en el que se ha probado el correcto funcionamiento de todos los métodos.

Estas son las funciones disponibles en servo_controller.c: int servo_read(int fd, char *buffer, int nChars, int useconds);

int servo_initialize();

int servo_setServoPosition(int fd, int servo, unsigned char position);

int servo_setAllServosPositions(int fd, unsigned const char *positions);

int servo_turnOff(int fd, int servo);

int servo_setAsHome(int fd, int servo);

int servo_goToHome(int fd, int servo);

int servo_setWidthResolution(int fd, int servo, char resolution);

unsigned char servo_getPosition(int fd, int servo, int *error);

int servo_getTorque(int fd, int servo, int *error);

int servo_setBaudRate(int fd, int rate);

void servo_close(int fd);

La función servo_initialize inicializa el acceso al puerto UART. Se limita a llamar a uart_initialize

con acceso de lectura y escritura.

La función servo_read lee datos que presumiblemente haya enviado la controladora de servos.

Se utiliza para control de errores y para consultar posiciones de servos.

- Entradas:

o -fd: descriptor de archivo obtenido de uart_initialize.

o -buffer: array donde se almacenarán losdatos leídos.

o -nChars: número máximo de datos que se pretende leer.

o -useconds: número de microsegundos que se espera para leer (para dejar

tiempo a la controladora a que reaccione)

- Valor de retorno: -1 si hubo error de lectura, o el número de datos

Page 14: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

13

leídos realmente.

La function servo_setServoPosition mueve un servo a una posición determinada. Para ello, envía el

comando correspondiente a la controladora, que son 5 bytes con el siguiente formato:

‘>1{servo}a{posición}’, donde {servo} es el carácter ASCII correspondiente al número de servo (49

para el 1, 50 para el 2… 56 para el 8) y {posición} es la posición, un byte entre 1 y 255.

- Entradas:

o -fd: descriptor de archivo obtenido de servo_initialize.

o -servo: número de servo a mover.

o -position: posición en la que se quiere poner.

- Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente.

La función servo_setAllServosPositions mueve todos los servos a las posiciones definidas por un

array. El comando que construye para esto es ‘>1m{posiciones}’, donde {posiciones} son 8 bytes con las

posiciones.

- Entradas:

o -fd: descriptor de archivo obtenido de servo_initialize.

o -positions: array con las posiciones en la que se quieren poner.

- Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente.

La función servo_turnOff apaga un servo. Apagar un servo lo deja muerto en la posición en la que

esté en ese momento. El comando que envía es el mismo que servo_setServoPosition cambiando la

posición por un 0.

- Entradas:

o fd: descriptor de archivo obtenido de servo_initialize.

o -servo: número de servo a mover.

- Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente.

La función servo_setAsHome establece la posición actual de un servo como posición por defecto.

Esta es una funcionalidad de la controladora de servos que permite guardar una posición en memoria

para recuperarla más tarde. Para esto, envía por UART el comando ‘>1{servo}c’.

- Entradas:

o -fd: descriptor de archivo obtenido de servo_initialize.

o -servo: número de servo.

- Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente.

La función servo_goToHome devuelve un servo a su posición por defecto establecida en

servo_setAsHome. Para esto, envía por UART el comando ‘>1{servo}c’.

- Entradas:

o -fd: descriptor de archivo obtenido de servo_initialize.

o -servo: número de servo.

- Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente.

La función servo_close finaliza la comunicación con el puerto UART. Se limita a llamar a

uart_close.

Page 15: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

14

- Entradas:

o fd: descriptor de archivo obtenido de

servo_initialize.

4.1.3 Interfaz de control de la cabeza: face_controller.c, face_controller.h

Este programa utiliza las funciones de servo_controller.c para proporcionar al resto del programa

una interfaz con la cabeza robótica. La funcionalidad ha sido probada mediante un test: el

face_controller_test.c, en el que se ha probado el correcto funcionamiento de todos los métodos. Las

funciones disponibles son las siguientes: int face_initialize();

int face_setAsHome(int fd);

int face_goToHome(int fd);

int face_setFace(int fd, unsigned const char *positions);

int face_turnOff(int fd);

int face_blink(int fd, int times, unsigned char current);

void face_close(int fd);

Las funciones face_initialize y face_close abren y cierran el puerto UART llamando directamente a

servo_initialize y servo_close. Como en los casos anteriores, face_initialize devuelve un descriptor de

archivo fd que debe usarse como primer argumento de todas las demás funciones de este programa.

La función face_setAsHome coloca la posición actual de la cara entera como posición por defecto.

- Entradas:

o fd: descriptor de archivo obtenido de face_initialize.

La función face_goToHome devuelve la cara a la posición por defecto establecida en

face_setAsHome.

- Entradas:

o fd: descriptor de archivo obtenido de face_initialize.

La función face_setFace coloca la cara en la posición determinada por el parámetro positions.

- Entradas

o fd: descriptor de archivo obtenido de face_initialize.

- Valor de retorno: 1 si ha habido algún error, y 0 si todo ha ido bien.

La función face_turnOff apaga todos los servos de la cara.

- Entradas:

o fd: descriptor de archivo obtenido de face_initialize.

La función face_blink hace que la cara parpadee un número de veces. Esta función se ha escrito

con el propósito de probar la velocidad de respuesta de los servos.

- Entradas:

o fd: descriptor de archivo obtenido de face_initialize.

o times: número de veces a parpadear.

o current: posición actual de los ojos

- Valor de retorno: 1 si ha habido algún error, y 0 si todo ha ido bien.

Page 16: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

15

speech_synthesis.c

festival.h

Altavoz

4.2 Interfaz con la síntesis del habla y el altavoz

En esta sección se ha empaquetado la funcionalidad del programa festival en una interfaz con las

funciones que necesitamos de este.

4.2.1 Festival: festival.h

Esta librería nos permite acceder a las funciones de síntesis del habla de festival. Para ello nos

facilita una serie de funciones predefinidas que nos serán de utilidad. Explicamos las que serán de

utilidad para nuestro proyecto.

La función festival_initialize inicializa y configura festival.

- Entradas: El tamaño de la pila para las instrucciones y un número que indica si se carga el archivo

de configuración por defecto.

La función festival_eval_command evalúa el comando que se la pase a festival. Sirve por ejemplo

para cambiar la voz a utilizar.

La función festival_say_file sintetiza el texto que aparece en un fichero.

- Entradas: Se le pasa como parámetro el archivo de texto que se quiere sintetizar.

La función festival_say_text sintetiza el texto que se quiera.

- Entradas: Se le pasa como parámetro el texto que se quiere sintetizar.

La función festival_wait_for_spooler detiene la ejecución del programa hasta que se termine de

sintetizar.

Page 17: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

16

4.2.2 Interfaz de comunicación con festival: speech_synthesis.c,

speech_synthesis.h

Aquí hemos incluido dos funciones que nos serán de utilidad para nuestro proyecto.

void say_file(const char *file);

void say_text(const char *text);

La función say_file hace que se sintetice el texto que se encuentra en un archivo, inicializando

primero el festival con el archivo de configuración, seleccionando la voz y bloqueando la ejecución del

programa mientras se está sintentizando.

- Entradas: Se le pasa como parámetro el nombre del archivo de texto que se quiere sintetizar.

La función say_text hace que se sintetice un texto que se le pase, inicializando primero el festival

con el archivo de configuración, seleccionando la voz y bloqueando la ejecución del programa mientras

se está sintentizando.

- Entradas: Se le pasa como parámetro el texto que se quiere sintetizar.

4.3 Analizador de textos

El analizador de textos es una modificación del programa t3, desarrollado por el Departamento de

Ingeniería Electrónica de la Universidad Politécnica de Madrid, y publicado bajo una licencia de software

libre. Este programa analiza el mensaje recibido utilizando un modelo de trigramas, en el que a cada

palabra se le asigna una puntuación (1 muy negativo, 9 muy positivo) en función de la frecuencia con la

que tanto esa palabra (monograma) como el conjunto de esa y la anterior (bigrama) y el conjunto de esa y

las dos anteriores (trigrama) formaban parte de un mensaje positivo o negativo en los textos de

entrenamiento, También tiene en cuenta las probabilidades de transición de positivo a negativo en sus

cálculos, y asigna probabilidades residuales a palabras o combinaciones que no aparecen en los textos de

entrenamiento.

El entrenamiento del programa t3 se llevó a cabo utilizando una amplia (aproximadamente

1700000 palabras) colección de críticas de cine, comparadas con la valoración dada a la película en

cuestión.

Adaptación del programa t3 a la Raspberry Pi:

Para poder compilar este programa en la Raspberry Pi fue necesario hacer una pequeña

modificacion en el código: la función getopts en Debian/Ubuntu no se comporta de la misma manera que

en Raspbian. El carácter que devuelve cuando no quedan más opciones es EOF en Debian/Ubuntu y 255

en Raspbian. Fue necesario modificar la función get_options para tener en cuenta esto y evitar un bucle

infinito en la línea:

Una vez compilado con éxito el programa t3 en la Raspberry Pi, comprobamos que había otro

problema. Cada vez que se llama al programa, realiza un largo proceso de inicialización antes de ponerse

a etiquetar el mensaje. En un ordenador este proceso dura unos 60 ms, pero en la Raspberry Pi, debido a

las limitaciones del procesador de 700 GHz y la RAM de 512 MB, se extendía a 4.5 segundos. Un

intervalo de más de 4 segundos entre la introducción del mensaje por parte del usuario y la respuesta del

programa era demasiado largo. Por tanto, hubo que modificar el programa para que se inicializara a

Page 18: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

17

petición del administrador y se mantuviera a la espera de mensajes, analizándolos a medida que llegaran

sin tener que inicializarse cada vez. Esto redujo el tiempo de respuesta a menos de 1 ms.

Para realizar esta modificación, nuestra primera solución ha sido añadir una función loop al código

de t3.c para que se mantenga a la espera de mensajes, e implementar un sistema de comunicación entre

procesos (servidor y t3) basado en un fichero donde va el mensaje y otro que funciona como semáforo.

Para esto, la función loop se mantiene en un bucle de espera hasta que existe el fichero semáforo

(data/semaphore.t3). Cuando éste existe, lee el mensaje de data/fichero.raw, lo analiza y llama a

move_face con la nota asignada para que éste calcule la nueva posición de la cara y reproduzca el

mensaje. Una vez hecho esto, borra el fichero semáforo y vuelve al bucle de espera.

4.4 Programa de cálculo de posición

Este es el programa cuya rutina principal calcula la nueva posición de la cara a partir de la antigua

(guardada en el archivo POSITION_FILE) y la valoración del último mensaje. Después mueve la cara,

reproduce el mensaje de texto y guarda la nueva posición. Además, en función del nuevo estado de

ánimo de la cara comparado con el anterior, el robot proferirá un mensaje poniendo entre ambos

mensajes un retardo para no solaparlos. Si el nuevo estado es uno más positivo se reproducirá el mensaje

guardado en TEXT_HAPPIER.txt, si es más negativo se reproducirá TEXT_SADDER.txt y si es

significativamente parecido reproducirá TEXT_NOCHANGE.txt. Se encuentra en el archivo

move_face.c y en él están definidas las siguientes funciones:

int loadPosition(unsigned char *position, const char *filename);

int savePosition(unsigned char *position, const char *filename);

void weightedAverage(unsigned char *result, const unsigned char *array1, const unsigned char

*array2, float weight1, float weight2, int length);

void calculatePosition(unsigned char *result, float tag);

La función load_position carga las posiciones de cada servo en un array.

- Entradas

o position: Array donde se guardarán las posiciones cargadas.

o filename: Nombre del archivo donde se buscarán las posiciones.

- Valor de retorno: 1 si ha habido algún error, y 0 si todo ha ido bien.

La función save_position guarda las posiciones de cada servo en un fichero.

- Entradas

o position: Array que contiene las posiciones.

o filename: Nombre del archivo donde se guardarán las posiciones.

- Valor de retorno: 1 si ha habido algún error, y 0 si todo ha ido bien.

La función weightedAverage calcula la media ponderada de dos arrays.

- Entradas

o result: array donde se devolverá el resultado

o array1: primer array de datos

o array2: segundo array de datos

o weight1: peso del primer array en la media

o weight2: peso del segundo array en la media

o length: longitud de los arrays

Page 19: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

18

La función calculate_position calcula una posición de cara en función de una nota.

- Entradas

o result: Array donde se guardarán.

o tag: Nota de la cual se calculará la posición

La función main combina todas las anteriores funciones para conseguir la funcionalidad necesaria.

4.5 Servidor Web

El servidor web está implementado utilizando Apache 2:

sudo apt-get install apache2 La ejecución de código a partir de las interacciones del usuario con la página web se hace a través

de CGI (Common Gateway Interface). La configuración se hace editando el archivo /etc/apache2/sites-

enabled/000-default:

- El directorio raíz, donde estarán los archivos HTML accesibles al público, se determina en las

líneas:

DocumentRoot /var/www

y

<Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory>

sustituyendo /var/www por el directorio deseado.

- Para ejecutar código CGI, deben cambiarse las líneas:

ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory>

sustituyendo /var/www/cgi-bin por el directorio donde estarán los programas. Si este directorio no

pertenece al directorio DocumentRoot definido en el paso anterior, es necesario crear un enlace

simbólico en DocumentRoot:

cd [directorio definido como DocumentRoot] ln -s [directorio cgi-bin]

Page 20: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

19

Comprobamos que la configuración de DocumentRoot es correcta accediendo a la dirección IP de

la RPi desde el ordenador. El resultado es la página HTML que viene cargada por defecto en /var/www

al instalar Apache 2:

Una vez hecho esto, podemos empezar a crear la página web, modificando el archivo index.html o

añadiendo nuevos archivos en el directorio DocumentRoot.

Interfaz CGI:

Para ejecutar código en la Raspberry Pi en función de la interacción del usuario, utilizamos CGI,

que es una interfaz que permite llamar a programas ejecutables escritos en cualquier lenguaje (en nuestro

caso, C) que pueda escribir en stdout desde el archivo HTML. Lo que escriba el programa en stdout es lo

que se cargará como nueva página. Para no tener que escribir dos veces el archivo HTML, lo primero que

hacemos es configurar Apache para que en el primer acceso a la página ejecute también el código en

lugar de mostrar index.html. Para esto basta con editar el archivo /etc/apache2/mods-enabled/dir.conf y añadir la ruta de nuestro programa delante de index.html.

El programa que se va a ejecutar cada vez que el usuario interaccione con nuestra página está en

server_query.c . La página web está en index.html, y tiene este aspecto:

Page 21: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

20

Se comporta de la siguiente manera:

- Si el usuario escribe un mensaje y pulsa Enviar, envía una solicitud HTTP GET con la variable

message definida en la QUERY_STRING, con valor el mensaje escrito. Por ejemplo, si el

usuario escribe “Adiós mundo”, la URL solicitada será:

http://10.42.0.74/cgi-bin/server_query.cgi?message=Adi%F3s+mundo - Si el usuario pulsa sobre uno de los botones de posición concreta de la cara, envía una solicitud

HTTP GET con la variable face definida en la QUERY_STRING, con un valor de 0 a 4 en

función de la posición solicitada. Por ejemplo, si se pulsa Cara triste, esta será la URL

solicitada:

http://10.42.0.74/cgi-bin/server_query.cgi?face=1

Las solicitudes HTTP GET son procesadas por server_query.cgi (que no es más que

server_query.c compilado). Este programa lee la QUERY_STRING y actúa en consecuencia:

- Si encuentra la variable face, llama a la función face_setFace del programa face_controller para

mover la cara a la posición pedida, obtenida a partir de la función getValue. Una vez hecho

esto, reproduce un mensaje predeterminado asociado a esa posición, y guarda la posición en

data/savedata.mfc para que move_face sepa que esta es la posición anterior en su próximo

cálculo.

- Si encuentra la variable message, obtiene el mensaje de la QUERY_STRING utilizando la

función getValue. Como se puede ver en el ejemplo anterior, este mensaje viene codificado con

el siguiente criterio: los espacios se transforman en el signo ‘+’, y los caracteres no

alfanuméricos se transforman en el símbolo ‘%’ seguido de dos cifras hexadecimales

correspondientes a su codificación en ISO-8859-15. Lo primero que hace server_query.cgi es

por tanto decodificar el mensaje, utilizando la función parseValue.

Page 22: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

21

Una vez decodificado, lo pasa a minúsculas, para lo que utiliza lowerCase, que se apoya en

lowerChar. La función lowerCase pasa a minúsculas tanto las letras normales como las letras

especiales del idioma español (vocales acentuadas y Ñ, codificadas en ISO-8859-15). Además,

esta función elimina todos los caracteres que no sean alfanuméricos o espacios en blanco.

Cuando el mensaje está decodificado y en minúsculas, la función writeParsed lo escribe en el

fichero data/fichero.raw, y el programa crea el semáforo data/semaphore.t3 que, como se ha

explicado antes, provoca que el analizador de textos t3 lea el mensaje, lo analice y mueva la

cara.

Una vez movida la cara, el programa server_query.cgi vuelve a mostrar la página web. En caso de

que se haya procesado un mensaje, la página se recarga con el mensaje escrito en el formulario y en el pie

de página. En caso contrario, el formulario aparece vacío y el pie de página muestra “No se recibió

ningún mensaje”.

De esto se ocupa la función render. Esta función lee el archivo index.html y utiliza la función

string_replace para reemplazar las ocurrencias de “<%r1%>”, “<%r2%>”, etc por los argumentos que se

le pasan, en este caso el mensaje recibido en los formatos adecuados. De este modo, se puede usar el

mismo archivo HTML para mostrar la misma página con distintos mensajes.

A modo de resumen, el programa server_query.c dispone de las siguientes funciones:

int getValue(char *result, char *query, const char *key);

void parseValue(char *result, char *value);

void lowerCase(char *result, char *value);

unsigned char lowerChar(unsigned char c);

void writeParsed(char *result);

int savePosition(const unsigned char *position, const char *filename);

void render(const char *filename, ...);

char *string_replace(const char *string, const char *replace, const char *with);

La función getValue obtiene el valor de una variable de la QUERY_STRING a partir de su

nombre.

Entradas:

- result: cadena donde se almacenará el resultado;

- query: QUERY_STRING de la petición, precedida y seguida por

el carácter '&'; key, nombre de la variable a obtener.

Valor de retorno: 1 si se encontró la variable, 0 si no.

La función parseValue sustituye los caracteres de control de value por sus equivalentes, y

almacena el resultado en result. En particular, sustituye los caracteres '+' por ‘ ‘, y las cadenas '%XX' por

el carácter equivalente ISO-8859-15 de 0xXX.

Entradas:

- result: array donde se almacenará el resultado

- value: cadena a analizar. Puede ser la misma zona de memoria que result, en cuyo caso el

resultado se almacena sobreescribiendo value.

La función lowerCase pasa la cadena a minúsculas (incluyendo vocales acentuadas y Ñ y elimina

los caracteres distintos de 0-9, a-z, ñ, vocales acentuadas y el espacio en blanco:

Page 23: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

22

Entradas:

- result: array donde se almacenará el resultado

- value: cadena a analizar. Puede ser la misma zona de memoria que result, en cuyo caso el

resultado se almacena sobreescribiendo value.

La función lowerChar es una función auxiliar para lowerCase que pasa un carácter (ISO-8859-15)

a minúsculas, incluyendo 'Ñ' y letras acentuadas:

Entradas:

- c: carácter a transformar.

Valor de retorno: carácter transformado.

La función writeParsed escribe la cadena en el fichero de mensajes, sustituyendo espacios por

saltos de línea para que t3 los entienda:

Entradas:

-result: cadena a escribir.

La función render tiene un número variable de argumentos. Esta función lee el archivo HTML

apuntado por filename, sustituye las ocurrencias de <%r1%> en el fichero HTML por el valor del

primer argumento después de filename, las de <%r2%> por el segundo, etc. y muestra la página.

La sustitución es en la memoria del programa: el archivo no se modifica.

Entradas:

- filename: nombre del archivo HTML a mostrar.

- Siguientes argumentos (opcionales): cadena de caracteres que debe sustituir a <%r1%>,

<%r2%>, etc. en el archivo HTML. El último argumento debe ser NULL, para indicar que

acaba la lista de argumentos.

La función string_replace es una función auxiliar para render que sustituye en la cadena string

todas las ocurrencias de la cadena replace por la cadena with, devolviendo el resultado en una nueva

cadena. Ninguna de las entradas es modificada en el proceso:

Entradas:

- string: cadena donde sustituir.

- replace: cadena a sustituir.

Page 24: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

23

- with: cadena por la que sustituir las ocurrencias de replace en string.

Valor de retorno: cadena sustituida, en una nueva posición de memoria.

Page 25: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

24

5 Descripción de las mejoras

En este momento no consideramos que el proyecto contenga mejoras. Todo lo hecho hasta ahora

se considera práctica básica, y a partir de aquí iremos introduciendo los cambios y variaciones que

consideremos oportunos.

Page 26: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

25

6 Principales problemas encontrados

A continuación se nombran brevemente los aspectos de la práctica que más dificultades técnicas

nos han causado y más nos han ralentizado.

Para hacer funcionar la UART fue necesario configurarla primero. Para esto encontramos

información fácilmente en internet, ya que la Raspberry es una placa con mucho soporte.

Un problema recurrente durante la realización de la práctica ha sido el sistema de permisos.

Muchas de las funcionalidades necesarias, como el acceso al puerto UART y a la reproducción de

sonido, requieren permisos sobre determinados archivos. Los programas que se ejecutan desde el

servidor necesitan permisos para crear archivos en la Raspberry. Pero dar demasiados permisos al usuario

remoto es un agujero de seguridad. Ha habido que hacer una separación clara entre programas ejecutados

por el administrador (con acceso root) y programas ejecutados por el usuario a través de CGI (con el

usuario por defecto de Apache, www-data), y dar permisos al usuario con cuidado de no crear

vulnerabilidades.

Uno de los problemas que nos encontramos fue que la lógica de la Raspberry Pi no era compatible

con la del protocolo RS232, por lo que tuvimos que comprar el adaptador max3232.

Para hacer funcionar el programa de reconocimiento emocional en la raspberry fue necesario hacer

cambios tanto en el programa de compilación make como en el código interno del programa.

Los caracteres especiales del idioma español (vocales acentuadas y ñ) nos dieron ciertos problemas

para procesar el mensaje escrito por el usuario, debido a conflictos entre sistemas de codificación. La

solución fue asegurar que todos los mensajes estaban codificados en ISO-8859-15, donde estos caracteres

ocupan solo 1 byte, y es más sencillo procesarlos en un programa C.

Aunque conseguimos hacer funcionar el analizador de textos este iba muy lento (tardaba unos 4

segundos en ejecutarse), pues cada vez que analizaba necesitaba cargar toda la base de datos de palabras,

por lo que lo solucionamos modificando el programa para que la cargara al principio y se quedara a la

espera de mensajes, analizándolos a medida que llegaban sin tener que volver a cargar la base de datos

cada vez. Ahora la respuesta es prácticamente instantánea.

Esta es la puntuación que esperamos en la práctica básica:

Módulos Puntuación máxima

(sobre 100)

Puntuación esperada

(sobre 100)

Requisitos mínimos 45 43

Memoria 15 13

Software 25 25

Hardware 5 5

Mejoras

TOTAL

Page 27: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

26

7 Manual de usuario

El sistema básico que es el que está implementado actualmente, funciona de la siguiente manera.

Un usuario se sitúa delante de una pantalla, en la cual tendrá una interfaz gráfica para interactuar

con la cara de varias formas:

Por un lado tiene una serie de botones en la sección Mover cara para poner la cara en diversas

posiciones. Dar al botón provocará una respuesta de la cara y que esta diga unas palabras en función de

su estado de ánimo.

Por otro lado puede escribir un texto en la sección Enviar mensaje. Este texto será analizado por

un programa que moverá la cara en función de lo positivo o negativo que sea y de la posición anterior.

Además se podrá escuchar el texto por los altavoces.

Page 28: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

27

8 Bibliografía

Max3232 http://datasheets.maximintegrated.com/en/ds/MAX3222-MAX3241.pdf

Pins Raspberry http://elinux.org/RPi_Low-level_peripherals

Festival http://www.cstr.ed.ac.uk/projects/festival/

Controladora de servos http://www.basicx.com/Products/servo/srv8-t-V1.4.pdf

Page 29: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

28

9 ANEXO I: Código del programa del proyecto final

9.1- Interfaz con la cabeza robótica

- Interfaz con la UART:

rpi_uart.h

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#ifndef RPI_UART

#define RPI_UART

int uart_initialize(int read, int write, int flags);

void uart_close(int fd);

void uart_setBaudRate(int fd, int baudRate);

void uart_setBlockForInput(int fd, int nChars);

int uart_read(int fd, char *buffer, int nChars);

int uart_write(int fd, char *string, int nChars);

#endif

rpi_uart.c

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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

Page 30: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

29

// along with Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

/*

Código para acceso sencillo a la UART de la Raspberry Pi

(GPIO 14: TX y 15: RX). Antes, desbloquear UART siguiendo

estos pasos:

http://raspberrypihobbyist.blogspot.com.es/2012/08/raspberry-pi-serial-port.html

y añadir permisos con el siguiente comando:

sudo chmod a+rw /dev/ttyAMA0

*/

#include <unistd.h>

#include <stdio.h>

#include <fcntl.h>

#include <termios.h>

#include "rpi_uart.h"

#define UART_PATH "/dev/ttyAMA0"

#define DEFAULT_BAUDRATE B19200

/*

Configuración inicial de la UART. Por defecto, 9600bps,

8 bits de datos, sin paridad, 1 bit de parada y no bloquear

en lectura.

Argumentos: read: acceso de lectura (1) o no (0),

write: acceso de escritura (1) o no (0), flags: otros flags

de acceso a archivos (ver fcntl.h)

Devuelve: descriptor de archivo, para ser usado como

parámetro en todas las demás funciones de UART, o -1

si hay algún error.

*/

int uart_initialize(int read, int write, int flags) {

// obtener flag de lectura/escritura

int rw_flag;

if (read&&write) rw_flag=O_RDWR;

else if (read) rw_flag=O_RDONLY;

else if (write) rw_flag=O_WRONLY;

else return -1;

flags|=rw_flag;

// O_NOCTTY: no controlado por terminal.

// O_NDELAY: abrir inmediatamente

flags=flags|O_NOCTTY|O_NDELAY;

int result=open(UART_PATH, flags);

if (result>-1) {

uart_setBaudRate(result, DEFAULT_BAUDRATE);

uart_setBlockForInput(result, 0);

}

return result;

}

// Cerrar el puerto UART.

void uart_close(int fd) {

close(fd);

}

/*

Page 31: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

30

Define si el método uart_read debe bloquearse esperando datos.

Argumentos: fd: descriptor de archivo obtenido de

uart_initialize. nChars: número de caracteres que espera el

método uart_read para desbloquearse. Poner a 0 para que no

se bloquee nunca.

*/

void uart_setBlockForInput(int fd, int nChars) {

struct termios options;

tcgetattr(fd, &options);

// Modificar la opción c_cc[VMIN]: mínimo número de caracteres

// de una lectura.

options.c_cc[VMIN]=nChars;

tcsetattr(fd, TCSANOW, &options);

}

/*

Define la velocidad del puerto serie.

Argumentos: fd: descriptor de archivo obtenido de

uart_initialize. baudRate: constante definida en termios.h

para la velocidad deseada (ejemplos: B9600, B115200, etc).

*/

void uart_setBaudRate(int fd, int baudRate) {

struct termios options;

tcgetattr(fd, &options);

// CS8: 8 bits de datos, sin paridad, 1 bit de parada.

// CLOCAL: conexión local, sin control externo.

// CREAD: habilitar recepción de datos.

options.c_cflag=baudRate|CS8|CLOCAL|CREAD;

// IGNPAR: ignorar paridad

// ICRNL: igualar retorno de carro y salto de línea.

options.c_iflag=IGNPAR|ICRNL;

options.c_oflag=0;

options.c_lflag=0;

// Vaciar buffer de datos

tcflush(fd, TCIFLUSH);

tcsetattr(fd, TCSANOW, &options);

}

/*

Transmite datos por UART.

Argumentos: fd: descriptor de archivo obtenido de

uart_initialize. string: buffer o cadena de caracteres a

enviar. nChars: número de caracteres que se quiere enviar

Devuelve: número de caracteres enviados realmente (menor

o igual que nChars, puede ser menor debido a limitaciones

de la velocidad o a que no haya suficientes en string).

Devuelve -1 si hay error de escritura.

*/

int uart_write(int fd, char *string, int nChars) {

return write(fd, string, nChars);

}

/*

Lee datos del buffer de recepción de UART.

Argumentos: fd: descriptor de archivo obtenido de

uart_initialize. buffer: array donde se almacenarán los

datos leídos. nChars: número máximo de datos que se pretende

leer.

Page 32: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

31

Devuelve: -1 si hubo error de lectura, o el número de datos

leídos realmente.

*/

int uart_read(int fd, char *buffer, int nChars) {

return read(fd, buffer, nChars);

}

- Interfaz con los servos:

servo_controller.h

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#ifndef SERVO_CONTROLLER

#define SERVO_CONTROLLER

int servo_read(int fd, char *buffer, int nChars, int useconds);

int servo_initialize();

int servo_setServoPosition(int fd, int servo, unsigned char position);

int servo_setAllServosPositions(int fd, unsigned const char *positions);

int servo_turnOff(int fd, int servo);

int servo_setAsHome(int fd, int servo);

int servo_goToHome(int fd, int servo);

int servo_setWidthResolution(int fd, int servo, char resolution);

unsigned char servo_getPosition(int fd, int servo, int *error);

int servo_getTorque(int fd, int servo, int *error);

int servo_setBaudRate(int fd, int rate);

void servo_close(int fd);

#endif

servo_controller.c

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

Page 33: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

32

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

/*

Código de control de los servos srv8-t a través de una UART

*/

#include <unistd.h>

#include <fcntl.h>

#include <stdio.h>

#include <termios.h>

#include "servo_controller.h"

#include "rpi_uart.h"

#define HEADER '>'

#define MODULE '1'

#define STANDARD '0'

#define EXTENDED '1'

#define ACK 13

#define ACK_TIMEOUT 100000

/*

Inicializa el acceso a los servos

*/

int servo_initialize() {

return uart_initialize(1,1,0);

}

/*

Lee datos que presumiblemente haya enviado la controladora de servos.

Se utiliza para control de errores y para consultar posiciones de servos.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- buffer: array donde se almacenarán losdatos leídos.

- nChars: número máximo de datos que se pretende leer.

- useconds: número de microsegundos que se espera para leer (para dejar

tiempo a la controladora a que reaccione)

Valor de retorno: -1 si hubo error de lectura, o el número de datos

leídos realmente.

*/

int servo_read(int fd, char *buffer, int nChars, int useconds) {

usleep(useconds);

return uart_read(fd, buffer, nChars);

}

/*

Mueve un servo a una posición determinada.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- servo: número de servo a mover.

- position: posición en la que se quiere poner.

Page 34: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

33

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_setServoPosition(int fd, int servo, unsigned char position) {

if (!(servo==1||(servo>4&&servo<9))) {

perror("Número de servo no válido");

return 1;

}

// Construir el comando '>1[servo]a[posición]'

char command[6];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='a';

command[4]=position;

command[5]=0;

return uart_write(fd, command, 5); // enviar el comando

}

/*

Mueve todos los servos a posiciones determinadas.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- positions: array con las posiciones en la que se quieren poner.

Calor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_setAllServosPositions(int fd, unsigned const char *positions) {

// Construir el comando '>1m[posiciones]'

char command[13];

command[0]=HEADER;

command[1]=MODULE;

command[2]=49;

command[3]='m';

for (int i=0;i<8;i++) {

command[i+4]=positions[i];

}

command[12]=0;

return uart_write(fd, command, 12); // enviar el comando

}

/*

Apaga un servo.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- servo: número de servo a mover.

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_turnOff(int fd, int servo){

return servo_setServoPosition(fd,servo,0);

}

/*

Page 35: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

34

Pone la posición actual del servo como home.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- servo: número de servo.

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_setAsHome(int fd, int servo){

// Construir el comando '>1[servo]c'

char command[5];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='c';

command[4]=0;

int w = uart_write(fd, command, 4); // Enviar comando

if (w ==-1) return 1;

char response=0;

if (servo_read(fd, &response, 1, ACK_TIMEOUT)==1&&response==ACK) {

return 0;

} else {

return 1;

}

}

/*

Mueve un servo a su posición home.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- servo: número de servo.

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_goToHome(int fd, int servo){

// Construir el comando '>1[servo]h'

char command[5];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='h';

command[4]=0;

int w = uart_write(fd, command, 4); // Enviar comando

if (w ==-1) return 1;

char response=0;

if (servo_read(fd, &response, 1, ACK_TIMEOUT)==1&&response==ACK) {

return 0;

} else {

return 1;

}

}

/*

Fija la resolución de un servo en standard o extended.

Entradas:

- fd: descriptor de archivo obtenido de uart_initialize.

- servo: número de servo.

- resolution:resolución deseada.

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

Page 36: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

35

ido correctamente

*/

int servo_setWidthResolution(int fd, int servo, char resolution){

if (resolution!=STANDARD && resolution!=EXTENDED) {

perror("Resolución inválida");

return 1;

}

// Construir el comando '>1[servo]w[resolución]'

char command[6];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='w';

command[4]=resolution;

command[5]=0;

int w = uart_write(fd, command, 5); // Enviar comando

if (w ==-1) return 1;

char response=0;

if (servo_read(fd, &response, 1, ACK_TIMEOUT)==1&&response==ACK) {

return 0;

} else {

return 1;

}

}

/*

Consulta a la controladora la posición actual de un servo.

Entradas: fd: descriptor de archivo obtenido de

uart_initialize. servo: número de servo.

error: flag que indica si todo ha ido bien o ha habido errores

Valor de retorno: 0 si ha habido algún error, o la posición si todo ha

ido correctamente

*/

unsigned char servo_getPosition(int fd, int servo, int *error){

*error=1;

char command[5];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='g';

command[4]=0;

int w = uart_write(fd, command, 4);

if (w ==-1) return 0;

char response[2];

if (servo_read(fd, response, 2, ACK_TIMEOUT)==2&&response[1]==ACK) {

*error=0;

return response[0];

} else {

return 0;

}

}

/*

Consulta a la controladora de servos la torque de un servo.

Entradas: fd: descriptor de archivo obtenido de

uart_initialize. servo: número de servo.

error: flag que indica si todo ha ido bien o ha habido errores

Valor de retorno: 0 si ha habido algún error, o la torque si todo ha

Page 37: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

36

ido correctamente

*/

int servo_getTorque(int fd, int servo, int *error){

*error=1;

char command[5];

command[0]=HEADER;

command[1]=MODULE;

command[2]=servo+48;

command[3]='t';

command[4]=0;

return uart_write(fd, command, 4);

char response[3];

if (servo_read(fd, response, 3, ACK_TIMEOUT)==3&&response[2]==ACK) {

*error=0;

return (int)(response[1])*256+(int)(response[0]);

} else {

return 0;

}

}

/*

Configura la tasa de símbolo tanto en la controladora como en la Rapsberri

Entradas: fd: descriptor de archivo obtenido de

uart_initialize. rate: tasa de símbolo.

Valor de retorno: 1 si ha habido algún error, o 0 si todo ha

ido correctamente

*/

int servo_setBaudRate(int fd, int rate){

char rateCode;

switch (rate) {

case B2400:

rateCode='3';

break;

case B4800:

rateCode='2';

break;

case B9600:

rateCode='1';

break;

case B19200:

rateCode='0';

break;

default:

perror("Resolución inválida");

return 1;

}

char command[6];

command[0]=HEADER;

command[1]=MODULE;

command[2]=49;

command[3]='b';

command[4]=rateCode;

command[5]=0;

int w = uart_write(fd, command, 5);

if (w ==-1) return 1;

char response=0;

if (servo_read(fd, &response, 1, ACK_TIMEOUT)==1&&response==ACK) {

Page 38: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

37

uart_setBaudRate(fd, rate);

return 0;

} else {

return 1;

}

}

/*

Cierra la conexión con la UART

argumentos: fd: descriptor de archivo obtenido de

uart_initialize.

*/

void servo_close(int fd) {

uart_close(fd);

}

- Interfaz con la cabeza

face_controller.h

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#ifndef FACE_CONTROLLER

#define FACE_CONTROLLER

extern const unsigned char FACE_HAPPY[8];

extern const unsigned char FACE_SAD[8];

extern const unsigned char FACE_SURPRISE[8];

extern const unsigned char FACE_ANGRY[8];

extern const unsigned char FACE_NEUTRAL[8];

int face_initialize();

int face_setAsHome(int fd);

int face_goToHome(int fd);

int face_setFace(int fd, unsigned const char *positions);

int face_turnOff(int fd);

int face_blink(int fd, int times, unsigned char current);

void face_close(int fd);

#endif

face_controller.c

// Copyright (C) 2013 Javier García, Julio Alberto González

Page 39: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

38

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

// Código para controlar la cara.

#include <unistd.h>

#include "face_controller.h"

#include "servo_controller.h"

// Posiciones predeterminadas de la cara

const unsigned char FACE_HAPPY[8]= {127,0, 0, 0, 127, 127, 240,240};

const unsigned char FACE_SAD[8]= {127,0, 0, 0, 30, 210, 10,10};

const unsigned char FACE_SURPRISE[8]= {200,0, 0, 0, 20, 170, 127,127};

const unsigned char FACE_ANGRY[8]= {80,0, 0,0, 215, 30, 127,127};

const unsigned char FACE_NEUTRAL[8]= {127,0, 0,0, 127, 127, 127,127};

/*

Inicializa el acceso a la cara

*/

int face_initialize() {

return servo_initialize();

}

/*

Pone la cara actual como cara por defecto

Argumentos: fd: descriptor de archivo obtenido de uart_initialize.

Devuelve: 1 si ha habido algún error, y 0 si todo ha ido bien.

*/

int face_setAsHome(int fd){

int e=0; // flag de error, si alguna de las operaciones da error será distinto de 0

// poner en cada servo la posición actual como posición por defecto

e+=servo_setAsHome(fd, 1);

for (int i=5;i<9;i++) {

e+=servo_setAsHome(fd, i);

}

if(e>0){

return 1;

}else{

return 0;

}

}

/*

Retorna al estado de cara por defecto

Page 40: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

39

Argumentos: fd: descriptor de archivo obtenido de uart_initialize.

Devuelve: 1 si ha habido algún error, y 0 si todo ha ido bien.

*/

int face_goToHome(int fd){

int e=0; // flag de error, si alguna de las operaciones da error será distinto de 0

// volver cada servo a su posición por defecto

e+=servo_goToHome(fd, 1);

for (int i=5;i<9;i++) {

e+=servo_goToHome(fd, i);

}

if(e>0){

return 1;

}else{

return 0;

}

}

/*

Pone una cara determinada

Argumentos: fd: descriptor de archivo obtenido de uart_initialize.

Devuelve: 1 si ha habido algún error, y 0 si todo ha ido bien.

*/

int face_setFace(int fd, unsigned const char *positions){

return servo_setAllServosPositions(fd, positions);

}

/*

Hace que la cara parpadee un número de veces

Argumentos: fd: descriptor de archivo obtenido de uart_initialize.

times: número de veces a parpadear

current: posición actual de los ojos

Devuelve: 1 si ha habido algún error, y 0 si todo ha ido bien.

*/

int face_blink(int fd, int times, unsigned char current){

for (int i=0;i<=times;i++) {

servo_setServoPosition(fd, 1, 1); // cerrar ojos

usleep(10000);

servo_setServoPosition(fd, 1, current); // volver a la posición inicial

}

return 1;

}

/*

Apaga todos los servos de la cara

Argumentos: fd: descriptor de archivo obtenido de uart_initialize.

Devuelve: 1 si ha habido algún error, y 0 si todo ha ido bien.

*/

int face_turnOff(int fd){

int e=0; // flag de error, si alguna de las operaciones da error será distinto de 0

// apagar cada servo

e+=servo_turnOff(fd, 1);

for (int i=5;i<9;i++) {

e+=servo_turnOff(fd, i);

}

if(e>0){

return 1;

}else{

return 0;

Page 41: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

40

}

}

/*

Termina el acceso a la cara

Argumentos: fd: descriptor de archivo obtenido de face_initialize.

*/

void face_close(int fd){

servo_close(fd);

}

- Interfaz con el sintentizador y el altavoz.

speech_synthesis.h

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#ifndef SPEECH_SYNTHESIS

#define SPEECH_SYNTHESIS

void speech_initialize();

void say_file(const char *file);

void say_text(const char *text);

void speech_close();

#endif

speech_synthesis.c

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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

Page 42: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

41

// along with Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#include <festival.h>

#include <stdio.h>

#include "speech_synthesis.h"

// Inicializa la librería Festival con los parámetros por defecto

// Hay que llamar a esta función antes de llamar a cualquier otra

// función de síntesis de texto.

void speech_initialize() {

static int init=0;

if (init) return;

festival_initialize(true, 210000);

init=1;

}

// Sintetiza y reproduce en español el mensaje contenido en un archivo de texto

// Entradas:

// - file: nombre del archivo

void say_file(const char *file) {

festival_eval_command("(voice_el_diphone)"); // voz en español

festival_say_file(file);

//festival_wait_for_spooler(); // bloquear ejecución hasta que acabe de decirlo

}

// Sintetiza y reproduce en español el mensaje pasado como argumento

// Entradas:

// - text: texto a sintetizar

void say_text(const char *text) {

festival_eval_command("(voice_el_diphone)"); // voz en español

festival_say_text(text);

//festival_wait_for_spooler(); // bloquear ejecución hasta que acabe de decirlo

}

// Bloquea la ejecución hasta que se acabe de decir todo. Importante llamar a esto al

// final del programa.

void speech_close() {

festival_wait_for_spooler();

}

- Controlador de la cara

move_face.h

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

Page 43: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

42

#ifndef MOVE_FACE

#define MOVE_FACE

int loadPosition(unsigned char *position, const char *filename);

int savePosition(unsigned char *position, const char *filename);

void weightedAverage(unsigned char *result, const unsigned char *array1, const unsigned char

*array2, float weight1, float weight2, int length);

void calculatePosition(unsigned char *result, float tag);

#endif

move_face.c

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>

#include <stdlib.h>

#include <errno.h>

#include "move_face.h"

#include "face_controller.h"

#include "speech_synthesis.h"

// Tiempo de espera para mover la cara tras inicializarla

#define FACE_DELAY 100000

// Longitud de los datos de posición (8 caracteres)

#define POSITION_LENGTH 8

// Archivo donde se guarda la posición anterior

#define POSITION_FILE "data/savedata.mfc"

// Peso de la posición anterior en la ponderación

#define WEIGHT_PREVIOUS 0.5

// Peso de la posición calculada en la ponderación

#define WEIGHT_CURRENT 0.5

// Nota máxima de un mensaje

#define TAG_MAX 9

// Nota mínima de un mensaje

#define TAG_MIN 1

// Posición correspondiente a la nota máxima

#define POSITION_MAX FACE_HAPPY

// Posición correspondiente a la nota mínima

#define POSITION_MIN FACE_SAD

Page 44: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

43

// Nota por defecto, si se llama al programa sin argumentos

#define DEFAULT_TAG TAG_MAX

// Archivo donde está guardado el mensaje

#define MESSAGE_FILE "data/fichero.raw"

// Mensaje cuando mejora el estado de ánimo

#define FILE_HAPPIER "messages/TEXT_HAPPIER.txt"

// Mensaje cuando empeora el estado de ánimo

#define FILE_SADDER "messages/TEXT_SADDER.txt"

// Mensaje cuando el cambio es pequeño

#define FILE_NOCHANGE "messages/TEXT_NOCHANGE.txt"

// Tolerancia de cambio de estado

#define MOOD_TOLERANCE 8

// Programa para calcular la nueva posición de la cara a partir de la antigua

// (guardada en el archivo POSITION_FILE) y la valoración del último mensaje

// (pasada como argv[1]). Calcula la nueva posición, mueve la cara, reproduce

// el mensaje (que está en el archivo MESSAGE_FILE) y guarda la nueva posición.

int main(int argc, char **argv) {

int exit_status=0;

// Iniciar comunicación con la cabeza

int fd=face_initialize();

// Leer etiqueta

float tag=DEFAULT_TAG;

if (argc>=2) {

tag=atof(argv[1]);

if (tag<TAG_MIN) {

tag=TAG_MIN;

fprintf(stderr, "%s: etiqueta no válida.\n", argv[1]);

} else if (tag>TAG_MAX) {

tag=TAG_MAX;

fprintf(stderr, "%s: etiqueta no válida.\n", argv[1]);

}

}

// Cargar última posición

unsigned char last_position[POSITION_LENGTH];

if (!loadPosition(last_position, POSITION_FILE)) weightedAverage(last_position,

POSITION_MIN, POSITION_MAX, 0.5, 0.5, POSITION_LENGTH);

// Obtener nueva posición

unsigned char calculatedPosition[POSITION_LENGTH];

calculatePosition(calculatedPosition, tag);

unsigned char position[POSITION_LENGTH];

weightedAverage(position, last_position, calculatedPosition, WEIGHT_PREVIOUS,

WEIGHT_CURRENT, POSITION_LENGTH);

// Mover la cabeza

usleep(FACE_DELAY);

face_setFace(fd, position);

// Guardar posición

if (!savePosition(position, POSITION_FILE)) exit_status=1;

face_close(fd);

speech_initialize();

// Reproducir mensaje

say_file(MESSAGE_FILE);

// Reproducir mensaje correspondiente al cambio de estado de ánimo

usleep(FACE_DELAY);

if (position[7]>last_position[7]+MOOD_TOLERANCE) say_file(FILE_HAPPIER);

else if (position[7]<last_position[7]-MOOD_TOLERANCE) say_file(FILE_SADDER);

else say_file(FILE_NOCHANGE);

speech_close();

Page 45: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

44

return exit_status;

}

// Carga la posición anterior del fichero

// Entradas:

// - position: array donde se devolverá el resultado

// - filename: nombre del archivo donde está guardada la posición

// Valor de retorno: 0 si hay error, 1 si no

int loadPosition(unsigned char *position, const char *filename) {

FILE *file=fopen(filename, "r"); // abrir archivo

if (file==NULL) {

perror("move_face: ");

return 0;

}

for (int i=0;i<POSITION_LENGTH;i++) {

// Leer 8 números entre 0 y 255 (unsigned char, %hhu)

if(fscanf(file, "%hhu ", position+i)<=0) {

fprintf(stderr, "Formato de datos guardados no válido");

fclose(file);

return 0;

}

}

fclose(file); // cerrar archivo

return 1;

}

// Guarda la posición en el fichero

// Entradas:

// - position: posición a guardar

// - filename: nombre del fichero

// Valor de retorno: 0 si hay error, 1 si no

int savePosition(unsigned char *position, const char *filename) {

FILE *file=fopen(filename, "w"); // abrir o crear archivo

if (file==NULL) {

perror("move_face: No se pudo guardar la posición: ");

return 0;

}

for (int i=0;i<POSITION_LENGTH;i++) {

// Escribir 8 números entre 0 y 255

if(fprintf(file, "%hhu ", position[i])<=0) {

perror("move_face: No se pudo guardar la posición: ");

fclose(file);

return 0;

}

}

fclose(file);

return 1;

}

// Calcula la media ponderada de dos arrays de unsigned char

// Entradas:

// - result: array donde se devolverá el resultado

// - array1: primer array de datos

// - array2: segundo array de datos

// - weight1: peso del primer array en la media

// - weight2: peso del segundo array en la media

// - length: longitud de los arrays

Page 46: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

45

void weightedAverage(unsigned char *result, const unsigned char *array1, const unsigned char

*array2, float weight1, float weight2, int length) {

for (int i=0;i<length;i++) {

result[i]=(unsigned char)(weight1*(float)array1[i]+weight2*(float)array2[i]);

fprintf(stderr, "%hhu ", result[i]);

}

fprintf(stderr, "\n");

}

// Calcula la posición correspondiente a la nota tag, utilizando

// una interpolación lineal entre POSITION_MIN y POSITION_MAX

// según el valor de tag entre TAG_MIN y TAG_MAX

// Entradas:

// - result: array donde se devolverá el resultado

// - tag nota del mensaje

void calculatePosition(unsigned char *result, float tag) {

// Asegurar que tag está dentro de los límites

if (tag<TAG_MIN) tag=TAG_MIN;

else if (tag>TAG_MAX) tag=TAG_MAX;

// interpolación lineal para cada elemento del array

for (int i=0;i<POSITION_LENGTH;i++) {

result[i]=(unsigned char)(POSITION_MIN[i]+(float)(POSITION_MAX[i]-

POSITION_MIN[i])*(float)(tag-TAG_MIN)/(float)(TAG_MAX-TAG_MIN));

}

}

- Servidor web

index.html

<!DOCTYPE html>

<html>

<head>

<meta charset='utf-8' />

<title>Rpi-face</title>

<link rel='STYLESHEET' type='text/css' href='/style/style.css'></link>

</head>

<body>

<div class='center'>

<h1>Bienvenido a Rpi-face</h1>

<div class='text'>

Bienvenido a Rpi-face.

Desde aquí podrás interactuar con el sistema.

Por un lado dispones de la sección enviar mensajes, desde la cual le

podrás enviar un mensaje al robot

Por otro lado podrás moverle la cara a la posición deseada.

</div>

<h1>Enviar mensaje</h1>

<div id='form'>

<span class='text'>Texto a enviar: </span>

<form action='/cgi-bin/server_query.cgi' method='GET'>

<textarea name='message' cols=40

rows=2><%r1%></textarea><br/>

<input class='input' type='submit' value='Enviar'/>

</form>

Page 47: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

46

</div>

<h1>Mover cara</h1>

<div id="actions">

<ul class='menu'>

<li><a href='/cgi-bin/server_query.cgi?face=0' >Cara

contenta</a></li>

<li><a href='/cgi-bin/server_query.cgi?face=1' >Cara

triste</a></li>

<li><a href='/cgi-bin/server_query.cgi?face=2' >Cara

enfadada</a></li>

<li><a href='/cgi-bin/server_query.cgi?face=3' >Cara

sorprendida</a></li>

<li><a href='/cgi-bin/server_query.cgi?face=4' >Cara

neutral</a></li>

</ul>

</div>

<p class="center"><%r2%></p>

</div>

</body>

</html>

style/style.css h1{

font-size: 38px;

font-style: italic;

color: #333;

padding: 10px;

margin: 10px;

text-shadow: -1px -1px 0px #101010, 1px 1px 0px #505050;

}

body {

background: #484850;

align: center;

}

ul.menu{

border: 1px solid #7C7C7C;

border-bottom: none;

list-style: none;

margin: 0;

padding: 0;

width: 130px;

}

ul.menu li {

background: #F4F4F4;

border-bottom: 1px solid #7C7C7C;

border-top: 1px solid #FFF;

}

ul.menu li a {

color: #333;

display: block;

padding: .2em 0 .2em .5em;

text-decoration: none;

}

.input { border: 1px solid #006; }

div{

margin:15px;

padding: 15px;

}

Page 48: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

47

.text{

color: #AFAFAF;

width: 391px;

text-align: justify;

}

.center{

width: 391px;

padding-left: 50%;

margin-left: -196px;

}

server_query.h // Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

#ifndef SERVER_QUERY

#define SERVER_QUERY

int getValue(char *result, char *query, const char *key);

void parseValue(char *result, char *value);

void lowerCase(char *result, char *value);

unsigned char lowerChar(unsigned char c);

void writeParsed(char *result);

int savePosition(const unsigned char *position, const char *filename);

void render(const char *filename, ...);

char *string_replace(const char *string, const char *replace, const char *with);

#endif

server_query.c

// Copyright (C) 2013 Javier García, Julio Alberto González

// This file is part of Rpi-Face.

// Rpi-Face 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 3 of the License, or

// (at your option) any later version.

// Rpi-Face 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 Rpi-Face. If not, see <http://www.gnu.org/licenses/>.

Page 49: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

48

// Programa para procesar los parámetros de una URL (CGI

// método GET).

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

#include <unistd.h>

#include <stdarg.h>

#include "server_query.h"

#include "face_controller.h"

#include "speech_synthesis.h"

// Longitud máxima de una QUERY_STRING

#define MAX_LENGTH 65536

// Formato para añadir un carácter antes y despues de una cadena

#define FORMAT "%c%s%c"

// Nombre de la variable asociada al mensaje en la QUERY_STRING

#define KEY_MESSAGE "message"

// Nombre de la variable asociada a comandos directos en la QUERY_STRING

#define KEY_FACE "face"

// Nombre del fichero donde irá el mensaje

#define FICHERO "data/fichero.raw"

// Nombre del fichero que actuará de semáforo

#define SEMAPHORE "data/semaphore.t3"

// Tiempo de espera para mover la cara tras inicializarla (us)

#define DELAY 100000

// Archivo donde se guarda la posición tras mover la cara

#define FILE_POSITION "data/savedata.mfc"

// Longitud de los datos de posición (8 caracteres)

#define POSITION_LENGTH 8

#define FILE_INDEX "/var/www/index.html"

// Programa que actúa de servidor web. Obtiene las variables de la QUERY_STRING.

// Si hay un mensaje, lo envía a t3. Si hay un comando directo, mueve la cara.

int main(int argc, char **argv, char **env) {

// Posiciones por defecto

const unsigned char* POSITIONS[]={FACE_HAPPY, FACE_SAD, FACE_SURPRISE, FACE_ANGRY,

FACE_NEUTRAL};

// Mensajes asociados a las posiciones por defecto

const char POSITIONS_MSGS[5][256]={"messages/FACE_HAPPY.txt",

"messages/FACE_SAD.txt","messages/FACE_SURPRISE.txt","messages/FACE_ANGRY.txt","messages/FACE_NEU

TRAL.txt",};

// Iniciar la salida HTML

printf("Content-type:text/html\n\n");

// printf("<!Doctype html>\n\n");

// printf("<html><head><meta charset='utf-8' /><title>Rpi-face</title><link

rel='STYLESHEET' type='text/css' href='/style/style.css'></link></head><body>");

// printf("<div class='center'><h1>Bienvenido a Rpi-face</h1><div class='text'>");

// printf("Bienvenido a Rpi-face. Desde aquí podrás interactuar con el sistema. Por un

lado dispones de la sección enviar mensajes, desde la cual le podrás enviar un mensaje al robot,

y por otro lado podrás moverle la cara a la posición deseada");

// printf("</div>");

Page 50: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

49

// Obtenemos QUERY_STRING y añadimos ''&' al principio y al final

char query[MAX_LENGTH+3]="&";

strncat(query, getenv("QUERY_STRING"), MAX_LENGTH);

int length=strlen(query);

char *result=(char*)malloc(length); // array para almacenar valores de variables

char *message=(char*)malloc(length); // array para almacenar el mensaje

result[0]=0;

message[0]=0;

// si hay algo en la QUERY_STRING

if (length>1) {

strncat(query, "&", 1);

if (getValue(result, query, KEY_MESSAGE)) {

// si la variable message existe, sustituir caracteres

parseValue(result, result);

strcpy(message, result);

} else if (getValue(result, query, KEY_FACE)) {

// si hay comando directo, mover la cara

int face=atoi(result);

if (face>=0&&face<5) {

int fd=face_initialize();

usleep(DELAY);

face_setFace(fd, POSITIONS[face]);

face_close(fd);

speech_initialize();

say_file(POSITIONS_MSGS[face]);

speech_close();

savePosition(POSITIONS[face], FILE_POSITION);

}

}

}

// printf("<h1>Enviar mensaje</h1><div id='form'><span class='ext'>Texto a enviar:

</span>"

// "<form action=\"/cgi-bin/server_query.cgi\" method=\"GET\">"

// "<textarea name=\"message\" cols=40 rows=2>%s</textarea>"

// "<br/><input class='input' type=\"submit\"

value=\"Enviar\"/></form>", message);

// printf("<h1>Mover cara</h1><div id='actions'><ul class='menu'></li>");

// printf("<li><a href='/cgi-bin/server_query.cgi?face=0' >Poner cara

contenta</a><br/></li>");

// printf("<li><a href='/cgi-bin/server_query.cgi?face=1' >Poner cara

triste</a><br/></li>");

// printf("<li><a href='/cgi-bin/server_query.cgi?face=2' >Poner cara

sorprendida</a><br/></li>");

// printf("<li><a href='/cgi-bin/server_query.cgi?face=3' >Poner cara

enfadada</a><br/></li>");

// printf("<li><a href='/cgi-bin/server_query.cgi?face=4' >Poner cara

neutral</a><br/></li></ul></div>");

if ((*message)!=0) {

// Si hubo mensaje

char *footer=(char*)malloc(length+25);

sprintf(footer, "<b>Mensaje recibido: </b>%s", message); // mostrarlo

render(FILE_INDEX, message, footer, NULL); // recargar el formulario

lowerCase(message, message); // pasar a minúsculas y quitar caracteres

extraños

Page 51: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

50

writeParsed(message); // escribirlo en el archivo de mensajes para pasárselo a

t3

FILE *semaphore;

semaphore = fopen(SEMAPHORE, "w"); //Se crea un semáforo para que t3 analice

el fichero

fclose(semaphore);

free(footer);

} else {

// printf("<p>No se recibi&oacute; ning&uacute;n mensaje</p>");

render(FILE_INDEX, "", "No se recibió ningún mensaje", NULL); // recargar el

formulario

}

// printf("</div></body></html>");

free(result);

free(message);

return 0;

}

// Obtiene el valor de una variable de URL y lo almacena en result.

// Entradas:

// - result: cadena donde se almacenará el resultado;

// - query: QUERY_STRING de la petición, precedida y seguida por

// el carácter '&'; key, nombre de la variable a obtener.

// Valor de retorno: 1 si se encontró la variable, 0 si no.

int getValue(char *result, char *query, const char *key) {

// Buscamos el nombre de la variable precedido de '&' y seguido de '='

int length=strlen(key);

char *mod_key=(char*)malloc(length+3);

sprintf(mod_key, "%c%s%c", '&', key, '=');

char *position=strstr(query, mod_key);

if ((position!=NULL)) {

// si lo encontramos, obtenemos el valor, que empieza

// en *(position+length+2) y acaba en el siguiente '&'

sscanf(position+length+2, "%[^&]&", result);

free(mod_key);

return 1;

}

else {

free(mod_key);

return 0;

}

}

// Sustituye los caracteres de control de value por sus equivalentes,

// y almacena el resultado en result. En particular, sustituye los

// caracteres '+' por ' ', y las cadenas '%XX' por el carácter

// equivalente ASCII de 0xXX. result debe tener al menos el mismo

// espacio que la longitud de value. result y value pueden ser iguales,

// en cuyo caso el resultado se almacena sobreescribiendo value.

// Entradas:

// - result: array donde se almacenará el resultado

// - value: cadena a analizar. Puede ser la misma zona de memoria

// que result, en cuyo caso el resultado se almacena

// sobreescribiendo value

void parseValue(char *result, char *value) {

unsigned int hexCode; // aquí se guardaran los códigos hexadecimales

char *pValue=value; // puntero para recorrer value

char *pResult=result; // puntero para recorrer result

Page 52: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

51

char *end=value+strlen(value); // dónde acabar la conversión

while (pValue<end) {

switch (*pValue) {

case '+':

*pResult=' ';

pValue++;

break;

case '%':

// obtener el valor hexadecimal de 2 cifras y convertirlo en

un carácter

sscanf(pValue+1, "%2x", &hexCode);

*pResult=hexCode;

pValue+=3;

break;

default:

*pResult=*pValue;

pValue++;

break;

}

pResult++;

}

*pResult=0; // terminar result con '\0'

}

// Pasa la cadena a minúsculas y elimina los caractéres especiales

// Entradas:

// - result: array donde se almacenará el resultado

// - value: cadena a analizar. Puede ser la misma zona de memoria

// que result, en cuyo caso el resultado se almacena

// sobreescribiendo value

void lowerCase(char *result, char *value) {

char *pValue=value; // puntero para recorrer value

char *pResult=result; // puntero para recorrer result

char *end=value+strlen(value); // dónde acabar la conversión

while (pValue<end) {

*pResult=lowerChar((unsigned char)*pValue);

if((*pResult>=97 && *pResult <= 122) || (*pResult>=48 && *pResult <= 57) ||

(*pResult==' ') || (*pResult==225) || (*pResult==233) || (*pResult==237) || (*pResult==243) ||

(*pResult==250) || (*pResult==241)){//Minúsculas, números sin acentos

pResult++;

}

pValue++;

}

*pResult=0; // terminar result con '\0'

}

// Pasa un carácter (ISO-8859-15) a minúsculas, incluyendo 'Ñ' y letras acentuadas

// Entradas:

// - c: carácter a transformar

// Valor de retorno: carácter transformado

unsigned char lowerChar(unsigned char c) {

switch (c) {

case 193:

case 201:

case 205:

Page 53: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

52

case 211:

case 218:

case 209:

return c+32;

break;

default:

return tolower(c);

}

}

// Escribe la cadena en el fichero de mensajes, sustituyendo

// espacios por saltos de línea para que t3 los entienda.

// Entradas:

// -result: cadena a escribir

void writeParsed(char *result) {

FILE *fichero;

fichero = fopen(FICHERO, "w"); // abrir fichero

char *s;

char temp;

// Recorrer la cadena y escribir en el fichero

for(s = result; *s; s++){

temp=*s;

if(temp==' '){

temp='\n';

}

fputc(temp, fichero);

}

fputc('\n', fichero); // salto de línea al final para que t3 lo entienda

fclose(fichero);

}

// Guarda la posición en el fichero

// Entradas:

// - position: posición a guardar

// - filename: nombre del fichero

// Valor de retorno: 0 si hay error, 1 si no

int savePosition(const unsigned char *position, const char *filename) {

FILE *file=fopen(filename, "w"); // abrir o crear archivo

if (file==NULL) {

perror("server_query: No se pudo guardar la posición: ");

return 0;

}

for (int i=0;i<POSITION_LENGTH;i++) {

// Escribir 8 números entre 0 y 255

if(fprintf(file, "%hhu ", position[i])<=0) {

perror("server_query: No se pudo guardar la posición: ");

fclose(file);

return 0;

}

}

fclose(file);

return 1;

}

//Lee el fichero filename, sustituye cada cadena de <%ri%> por el argumento número i,

//aceptando un número variable de argumentos. Posteriormente se muestra el archivo.

//Entradas:

// -filename: nombre del archivo a generar y mostrar.

Page 54: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

53

// -siguientes argumentos (opcionales): cadena de caracteres que sustituiran a <%ri%>. El

último argumento

// será NULL para indicar que se acaba la lista de argumentos.

void render(const char *filename, ...) {

// Leer fichero y guardarlo en contents

FILE *file=fopen(filename, "r");

char contents[65536];

char *pContents=(char*)contents;

int c;

while ((c=fgetc(file))!=EOF) {

*(pContents++)=(char)c;

}

*pContents=0;

pContents=(char*)contents;

fclose(file);

if (strlen(contents)==0) return;

// Recorrer la lista de argumentos opcionales

va_list ap;

va_start(ap, filename);

const char *arg;

int i=1;

char replace[16];

while ((arg=va_arg(ap, const char*))!=NULL) {

sprintf(replace, "<%%r%d%%>", i);

// reemplazar "<%ri%>" con el argumento i

pContents=string_replace(pContents, replace, arg);

i++;

}

va_end(ap);

printf("%s\n", pContents);

}

//Reemplaza en el contenido de la cadena string todas las coincidencias de replace con el

//contenido de with sin modificar los string originales.

//Entradas:

// -string: cadena original

// -replace: patrón a encontrar y sustituir

// -with: patrón que sustituirá a replace.

//Valor de retorno: cadena sustituida.

char *string_replace(const char *string, const char *replace, const char *with) {

// Longitudes de las cadenas

int sLength=strlen(string);

int rLength=strlen(replace);

int wLength=strlen(with);

int count=0;

// Primero, contar el número de ocurrencias de replace para calcular la longitud de

// la nueva cadena

const char *sPointer=string; // puntero para recorrer string

const char *temp=string; // puntero auxiliar

// Número de ocurrencias de replace en string

while ((sPointer=strstr(sPointer, replace))!=NULL) {

sPointer+=rLength;

count++;

}

// Longitud destino

int nLength=sLength+(wLength-rLength)*count;

char *result=(char*)malloc(nLength+1); //reservar memoria

char *pResult=result; // puntero para recorrer result

Page 55: Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) Curso 2012/2013 Título del proyecto desarrollado

Memoria del proyecto desarrollado en Sistemas Digitales II (SDII) curso 2012/2013

54

sPointer=string;

// Reemplazar las ocurrencias

while ((temp=strstr(sPointer, replace))!=NULL) {

// Para cada ocurrencia, copiar en result lo que hay en string antes de ella

strncpy(pResult, sPointer, temp-sPointer);

pResult+=(temp-sPointer);

// y copiar el contenido de with después

strcpy(pResult, with);

pResult+=wLength;

sPointer=temp+rLength; // poner sPointer al final de la ocurrencia

}

strcpy(pResult, sPointer); // copiar lo que queda tras la última ocurrencia

return result;

}