Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria del proyecto desarrollado...
Transcript of Memoria del proyecto desarrollado en Sistemas Digitales II ... · Memoria 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
Í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
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
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
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.
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
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.
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.
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
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.
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
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.
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
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.
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.
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.
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
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
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]
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:
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.
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:
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.
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.
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.
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
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.
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
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
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);
}
/*
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.
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.
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.
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);
}
/*
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
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
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) {
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
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
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;
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
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/>.
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
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();
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
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>
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;
}
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/>.
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>");
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
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ó ningú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
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:
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.
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
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;
}