4

Cómo programar V-Rep C++ en Linux

Aviso: Hay que leer este tutorial hasta el final. El código no funcionará si se copia directamente.

Buenas! Hoy vamos a aprender como programar el simulador de robots V-Rep con C++ en Linux. Construiremos un ejemplo muy sencillo que consistirá en un pequeño robot esquivador de obstáculos.

V-Rep sólo puede programarse con Lua desde dentro del programa. Si queremos programarlo en otro lenguaje como C++ tenemos que utilizar la API remota de V-Rep. Esta API nos permite interactuar con la simulación escribiendo un programa externo.

El primer paso será instalar la versión de V-Rep para Linux. Podemos descargar la última versión del simulador desde aquí. También tenemos que descargar la escena básica con el robot que vamos a controlar. Se trata de un pequeño robot diferencial con un sensor de distancia que esquiva obstáculos, nada del otro mundo.

Nuestro pequeño héroe robótico :3

 

Ahora vamos a crear una carpeta donde guardaremos nuestro programa C++. Yo la he nombrado vrep_cpp_test. Dentro de esta carpeta creamos un fichero de texto que llamaremos ‘main.cpp’. Este fichero llevará el código para conectarse con V-Rep y controlar el robot.

Antes de empezar a programar nuestro pequeño héroe robótico, tenemos que configurar la escena de V-Rep. He dejado la escena bastante preparada, pero falta crear un script en Lua para que la API Remota de C++ pueda comunicarse con el programa. Abrimos la escena básica y pulsamos el botón de Scripts:

Se abrirá una ventanita donde sale una lista con cada uno de los scripts en Lua que hay en la escena. De momento sólo hay el “Main Script”, que es el archivo que se encarga de activar las físicas del simulador, hace que se puedan usar los sensores, calcula la iluminación… viene por defecto al crear una nueva escena y normalmente no se toca.

Vamos a crear un nuevo script que active la Api Remota. Pulsamos “Insert New Script” y en “Script Type” seleccionamos “Child script (threaded)”. Lo abrimos con doble click y añadimos simExtRemoteApiStart(19999) después de la linea 41.

Para que este script funcione hay que asociarlo a algún objeto. Volvemos a la ventana de Scripts y con el script seleccionado abrimos el menú desplegable “Associated Object”. Nos saldrá una lista con los objetos con los que podemos asociar el Script. Seleccionamos “Default Camera” y marcamos la casilla “Execute just once” si no lo está ya.

Ya hemos acabado con el simulador. Vamos a minimizarlo y abrimos el fichero ‘main.cpp’ donde crearemos nuestro código de ejemplo para mover el robot sin que el pobre se dé de bruces contra la pared. Copiamos este código:

/*   CODIGO DE ROBOT ESQUIVA-OBSTACULOS CON V-REP

     Escrito por Nano en beneficio de los seres humanos
     www.robologs.net
*/
#include <iostream>

extern "C" {
    #include "extApi.h"
}

using namespace std;




int main()
{

	//Variables para guardar motores
	int motor_derecho;
	int motor_izquierdo;

	//Variable que guarda el obstaculo detectado
	simxUChar obstaculo;

	//Variable para guardar el sensor de distancia
	int sensor;


	int clientID=simxStart("127.0.0.1",19999,true,true,100,5); //Conectar con la simulacion
	if(clientID == -1) //Si clientID vale -1, es que no se ha conectado con exito
	{
		cout << "ERROR: No se ha podido conectar\n";
		simxFinish(clientID); //Siempre hay que cerrar la conexion
		return 0;
	}

	//Aqui guardamos los dos motores y el sensor
	int valido = simxGetObjectHandle(clientID, "motor_derecho", &motor_derecho, simx_opmode_blocking);
	int valido2 = simxGetObjectHandle(clientID, "motor_izquierdo", &motor_izquierdo, simx_opmode_blocking);
	int valido_sensor = simxGetObjectHandle(clientID, "sensor", &sensor, simx_opmode_blocking);

	//Si no se ha podido acceder a alguno de estos componentes mostramos un mensaje de error y salimos del programa
	if(valido != 0 || valido2 != 0 || valido_sensor != 0)
	{
		cout << "ERROR: No se ha podido conectar con el robot" << endl;
		simxFinish(clientID);
		return 0;
	}

	//Inicializar el sensor
	simxReadProximitySensor(clientID,sensor,&obstaculo,NULL,NULL,NULL,simx_opmode_streaming);
	
	

	while (simxGetConnectionId(clientID)!=-1) //Este bucle funcionara hasta que se pare la simulacion
	{	
			simxReadProximitySensor(clientID,sensor,&obstaculo,NULL,NULL,NULL,simx_opmode_buffer); //Leer el sensor
			if(obstaculo == 0) //Si no se detecta ningun obstaculo, avanzar
			{
				simxSetJointTargetVelocity(clientID,motor_derecho,-2,simx_opmode_oneshot);
				simxSetJointTargetVelocity(clientID,motor_izquierdo,-2,simx_opmode_oneshot);
			}
			else //Giraremos si encontramos algo en el camino del robot
			{
				simxSetJointTargetVelocity(clientID,motor_derecho,2,simx_opmode_oneshot);
				simxSetJointTargetVelocity(clientID,motor_izquierdo,-2,simx_opmode_oneshot);
			}


	}

	simxFinish(clientID); //Siempre hay que parar la simulación
	cout << "Simulacion finalizada" << endl;
	return 0;
}

Vamos a ver un poco qué hace cada parte. Primero citamos todas las librerías que vamos a necesitar: iostream (que ya la deberíamos conocer si hemos trabajado con C++) y extApi.h, que contiene las funciones de la Api Remota de V-Rep.

#include <iostream>

extern "C" {
    #include "extApi.h"
}

using namespace std;

Dentro del main declaramos las variables para guardar los motores, el sensor de distancia y la lectura del sensor:

	//Variables para guardar motores
	int motor_derecho;
	int motor_izquierdo;

	//Variable que guarda el obstaculo detectado
	simxUChar obstaculo;

	//Variable para guardar el sensor de distancia
	int sensor;

Ahora viene una parte muy importante: vamos a iniciar la comunicación con el simulador. Esto lo hacemos con la función simxStart(), que va a devolver -1 si algo ha ido mal y no podemos conectarnos:

	int clientID=simxStart("127.0.0.1",19999,true,true,100,5); //Conectar con la simulacion
	if(clientID == -1) //Si clientID vale -1, es que no se ha conectado con exito
	{
		cout << "ERROR: No se ha podido conectar\n";
		simxFinish(clientID); //Siempre hay que cerrar la conexion
		return 0;
	}

Como ya estamos conectados, podemos guardar los dos motores y el sensor como variables. La función simxGetObjectHandle() se encargará de esto. Esta función recibe cuatro parámetros: la ID del cliente, el nombre del objeto dentro de la escena del simulador, un puntero a la variable donde queremos guardar el objeto y el modo de operación:

	//Aqui guardamos los dos motores y el sensor
	int valido = simxGetObjectHandle(clientID, "motor_derecho", &motor_derecho, simx_opmode_blocking);
	int valido2 = simxGetObjectHandle(clientID, "motor_izquierdo", &motor_izquierdo, simx_opmode_blocking);
	int valido_sensor = simxGetObjectHandle(clientID, "sensor", &sensor, simx_opmode_blocking);

Al igual que antes vamos a comprobar que todos los objetos se hayan guardado bien:

	//Si no se ha podido acceder a alguno de estos componentes mostramos un mensaje de error y salimos del programa
	if(valido != 0 || valido2 != 0 || valido_sensor != 0)
	{
		cout << "ERROR: No se ha podido conectar con el robot" << endl;
		simxFinish(clientID);
		return 0;
	}

Antes de empezar a usar el sensor de distancia, hay que inicializarlo:

	//Inicializar el sensor
	simxReadProximitySensor(clientID,sensor,&obstaculo,NULL,NULL,NULL,simx_opmode_streaming);

Ahora entramos en el bucle principal, que correrá hasta que se cierre la simulación de V-Rep con el botón de STOP. Dentro de este bucle leemos el sensor de distancia. Haremos girar el robot si se detecta un obstáculo, y avanzará en linea recta si el camino está despejado.

Leemos el sensor con la función simxReadProximitySensor(), que recibe como argumentos la ID del cliente, la variable del sensor, un puntero a la variable que guardará los obstáculos, y el modo de operación. Los tres argumentos nulos sirven para si queremos saber cúal es el objeto detectado, las coordenadas [x,y,z] del punto donde el rayo del sensor colisiona con el obstáculo o su vector normal.

La velocidad de los motores (que en realidad son objetos de tipo Joint) se cambia con simxSetJointTargetVelocity(). Esta función recibe como argumentos la ID del cliente, la variable que guarda el motor, una variable double con la velocidad y el modo de operación:

	while (simxGetConnectionId(clientID)!=-1) //Este bucle funcionara hasta que se pare la simulacion
	{	
			simxReadProximitySensor(clientID,sensor,&obstaculo,NULL,NULL,NULL,simx_opmode_buffer); //Leer el sensor
			if(obstaculo == 0) //Si no se detecta ningun obstaculo, avanzar
			{
				simxSetJointTargetVelocity(clientID,motor_derecho,-2,simx_opmode_oneshot);
				simxSetJointTargetVelocity(clientID,motor_izquierdo,-2,simx_opmode_oneshot);
			}
			else //Giraremos si encontramos algo en el camino del robot
			{
				simxSetJointTargetVelocity(clientID,motor_derecho,2,simx_opmode_oneshot);
				simxSetJointTargetVelocity(clientID,motor_izquierdo,-2,simx_opmode_oneshot);
			}


	}

Al salir del programa, hay que terminar siempre la conexión con el cliente con simxFinish():

	simxFinish(clientID); //Siempre hay que parar la simulación
	cout << "Simulacion finalizada" << endl;
	return 0;
}

Ahora mismo, este código no va a compilar… por si solo. El compilador g++ no sabe donde encontrar la librería “extApi.h”. Esta librería se encuentra dentro de los archivos de V-Rep, pero incluso si la copiamos dentro de la misma carpeta donde está el fichero ‘main.cpp’ tampoco va a compilar.

Para compilar el programa, lo más fácil es hacer un makefile. Cuando las órdenes para compilar un fichero son muy largas (como lo serán en nuestro caso), los programadores utilizamos un makefile, que es una lista con todas las instrucciones, parámetros y direcciones de las librerías que le pasamos al compilador. Por tanto, nos vamos a la carpeta donde tengamos el ‘main.cpp’ y creamos otro fichero de texto al que llamaremos ‘makefile’.

Antes de escribir el makefile tenemos que saber en qué directorio está la librería “extApi.h”. Y no sólo eso, esta librería apunta a otras librerías que también hay que saber donde se encuentran. Abrimos la carpeta de Vrep y nos vamos a la carpeta que pone “programming”. Los directorios con las librerías que nos interesan son “include” y “remoteApi”, por tanto tenemos que recordar exactamente el path completo a estas dos carpetas para poder pasarlo al compilador.

Mi path hacia la carpeta “include” es:

/home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/include

y el que lleva a la carpeta “remoteApi”:

/home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/remoteApi

Ahora abrimos el fichero makefile que hemos creado hace un momento y escribimos estas instrucciones:

CFLAGS = -I/home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/remoteApi -I/home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/include -DNON_MATLAB_PARSING -DMAX_EXT_API_CONNECTIONS=255 -D__linux

all: 
	@rm -f *.o
	@rm -f main
	gcc -c /home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/remoteApi/extApi.c -o extApi.o $(CFLAGS)
	gcc -c /home/nano/V-REP_PRO_EDU_V3_3_1_64_Linux/programming/remoteApi/extApiPlatform.c -o extApiPlatform.o $(CFLAGS)
	g++ -c main.cpp -o main.o $(CFLAGS)
	g++ extApi.o extApiPlatform.o main.o -o main -lpthread

Veamos qué hace cada línea.

CFLAGS son instrucciones adicionales que pasaremos al compilador de C. Le vamos a pasar la localización de la carpeta remoteApi y include con el comando -I. Además, según la documentación de V-Rep, hay que pasarle las definiciones NON_MATLAB_PARSING y MAX_EXT_API_CONNECTIONS=255. También le diremos al compilador que queremos compilar para linux. Y no os olvidéis de cambiar el path /home/nano/V-REP_PRO_EDU…. por el vuestro.

Después del ‘all:’, borramos los ficheros de objeto acabados en .o, y también el ejecutable del main. Compilamos las librerías “extApi.c” y “extApiPlatform.c” (otra vez, hay que acordarse de cambiar mi path por el vuestro). Al final compilamos el ‘main.cpp’.

Para compilar el programa tenemos que abrir una terminal dentro de la carpeta dónde hay el makefile y escribir:

make -f makefile

(el fichero ‘main.cpp’ debe estar en la misma carpeta).

Bien, ya hemos acabado. ¡Vamos a probar la simulación! Abrimos la escena de V-Rep y pulsamos el botón de play.

Tranquilos, es normal que no se mueva. Para que el robot haga algo tenemos que ejecutar el programa que acabamos de escribir en C++. Abrimos una terminal dentro de la carpeta donde tengamos el ejecutable del ‘main’ y escribimos:

./main

Y el robot empezará a moverse.

Descargar la escena de V-Rep finalizada
Descargar código C++ (hay que modificar los paths del makefile)

N4n0

Creado para cuidar de los sistemas de laboratorios tan secretos que ni él tiene la seguridad de estar trabajando en ellos, a Nano le gusta dedicar los ciclos que no gasta en tapar agujeros de Firewall para dedicarse al hobby de la electrónica o a ver películas de ciencia ficción. Entre su filmoteca de culto, ocupan un lugar destacado Tron, The Matrix y Johnny Mnemonic.

Antes de comentar, por favor, lee las Normas

4 Comentarios en "Cómo programar V-Rep C++ en Linux"

avatar
Ordenar por:   más nuevos primero | más antiguos primero
Aprendiz
Humano

Enorme, a por las redes neuronales y todos los que pongas. Sólo comentar que quizás deberías añadir que hay que asociar el nuevo script al robot, porque, a mi al menos, si no lo hago así no funciona. No se si será por mi desconocimiento de V-rep o que soy muy malo siguiendo instrucciones.
Gracias y esperando más.

AARON MIRANDA
Humano

seguiras subiendo mas tutoriales de vrep?

wpDiscuz