You are here

Relatório 5 - Projeto

A proposta desse projeto é utilizar a arquitetura cognitiva Clarion para controlar um robô no simulador de robôs v-rep, ou virtual robot experimentation platform. A solução como um todo foi batizada de v-Happy e, para efeitos de testes, o robô escolhido foi um robô móvel Khepera 3. O código foi escrito de modo a permitir que novos comandos sejam transmitidos ao robô, através de um protocolo simples e extensível. Além disso, novas classes de robôs podem ser adicionadas futuramente. A idéia é que o projeto sirva como um "v0" para uma futura implementação do World Server 3D no v-rep, e a arquitetura cognitiva Clarion foi escolhida por ser a única dentre as estudadas nessa disciplina escrita em C#, isto é, sem suporte nativo por parte do simulador v-rep. Assim, entre outras coisas, esse projeto também supre essa demanda.

O download do software v-rep pode ser feito aqui e, neste ponto, não há restrições de Sistema Operacional, isto é, a solução abaixo se conectará ao v-rep em qualquer plataforma, podendo ser, inclusive numa máquina remota.

v-Happy Socket

Como mencionado acima, o v-rep não suporta C#. Seus controladores podem ser escritos em C/C++, Python, Java, Lua, Matlab, Octave ou Urbi. A primeira parte deste projeto consiste neste software, responsável pela ponte entre o Clarion e o v-rep.

Inteiramente escrito em C++, exceto pela API remota (application program interface) do v-rep, que é escrita em C, o código fonte está distribuído em dois diretórios, "v-Happy", que contém as classes específicas do v-Happy Socket, incluíndo a API do v-rep; e "api", que contém classes de propósito gerais escritas por mim já há algum tempo, como por exemplo, classes de tratamento de exceção. O estudo detalhado dessas classes está fora do escopo deste projeto.

Os arquivos extApi.h, extApi.c, extApiInternal.h, extApiPlatform.h e extApiPlatform.c compõe a API do v-rep. Neles, são encontradas funções responsáveis para iniciar uma simulação, carregar um cenário ou interagir com as partes que compõe um robô. A API é escrita em baixo nível e mover um braço do robô é uma tarefa que envolve controlar cada articulação específica, definindo torque e a rotação em radianos, entre outros. Assim, cada robô precisa ser programado no v-Happy Socket segundo suas características específicas.

As funções que compõe a API do v-rep podem ser reconhecidas pelo prefixo "simx". No nosso exemplo, o robô Khepera 3 foi programado para dar movimento aos atuadores dos motores das rodas, podendo ser os sensores e o braço mecânico programados posteriormente.

A classe VREPRobot implementa os métodos moveFront, moveLeft, moveRight, moveReverse e stop, controlando os atuadores dos motores do Khepera 3 através de um handle passado para a função simxSetJointTargetVelocity. O handle é obtido através da função simxGetObjectHandle, que recebe entre os parâmetros o nome do objeto a ser controlado e que foi definido pelo modelador do robô dentro do simulador, tal qual "K3_leftWheelMotor". Mover a articulção do braço do robô Khepera envolve handles específicos para os seguintes atuadores: K3_gripper_armJoint1, K3_gripper_armJoint2, K3_gripper_armAuxJoint1, K3_gripper_armAuxJoint2, K3_gripper_armAuxJoint3, K3_gripper_armAuxJoint4, K3_gripper_fingers, K3_gripper_fingersAux, K3_gripper_fingersAux0; e a complexidade aumenta severamente para robôs mais sofisticados, como o NAO.

A classe VHappy, por sua vez, é responsável por abrir a conexão com o v-rep e aceitar conexões entrantes no v-Happy Socket. É nela também que está definido o protocolo. O protocolo foi simplificado usando o nome dos métodos definidos em VREPRobot, a saber, moveFront, moveLeft, moveRight, moveReverse e stop, além do comando "bye", que encerra a conexão com o cliente e com o v-rep. O trecho de código abaixo é responsável por criar as conexões e tratar o protocolo de comunicação:

int VHappy::main( )
{
	std::string strIpAddress = static_cast< std::string >( "127.0.0.1" );
	int iPort = 19997;
	int iVHappyPort = 35000;

	if( this->getArgumentValue( "--vrep_ip" ) != "" )
		strIpAddress = this->getArgumentValue( "--vrep_ip" );

	if( this->getArgumentValue( "--vrep_port" ) != "" )
		iPort = atoi( this->getArgumentValue( "--vrep_port" ).c_str( ) );

	if( this->getArgumentValue( "--vHappy_port" ) != "" )
		iVHappyPort = atoi( this->getArgumentValue( "--vHappy_port" ).c_str( ) );

	// Connect to v-Rep
	std::cout << "Trying to connect to v-rep on [ " << strIpAddress << " : " << iPort << " ]" << std::endl;
	this->connect( strIpAddress, iPort );
	std::cout << "Connected to v-rep!" << std::endl;

	VREPRobot vrepRobot( this->simxClientId );

	// Accept connections from Clarion
	ServerSocket serverSocket;

	std::cout << "Waiting for connections in port [ " << iVHappyPort << " ]..." << std::endl;
	serverSocket.accept( iVHappyPort );
	std::cout << "Client connected!" << std::endl;

	while( true )
	{
		std::string strReceived = serverSocket.receive( );
		std::string strReceivedTruncated = strReceived.substr( 0, strReceived.find_last_of( '\n' ) - 1 );

		std::cout << "Received command [ " << strReceivedTruncated << " ] " << std::endl;

		if( strReceivedTruncated == "moveLeft" )
		{
			vrepRobot.moveLeft( );
		}
		else if( strReceivedTruncated == "moveFront" )
		{
			vrepRobot.moveFront( );
		}
		else if( strReceivedTruncated == "stop" )
		{
			vrepRobot.stop( );
		}
		else if( strReceivedTruncated == "moveReverse" )
		{
			vrepRobot.moveReverse( );
		}
		else if( strReceivedTruncated == "moveRight" )
		{
			vrepRobot.moveRight( );
		}
		else if( strReceivedTruncated == "bye" )
		{
			std::cout << "Exiting..." << std::endl;
			break;
		}
		else
		{
			std::cout << "Nothing to do with command [ " << strReceivedTruncated << " ]" << std::endl;
		}
	}

	this->disconnect( );
	return 0;
}

Neste ponto, já estamos aptos a testar o v-Happy Socket. Para fazê-lo, inicia-se o software v-rep. No Windows, após instalado, ele pode ser encontrado em "Start -> V-REP PRO EDU". No Linux, após descompactar o arquivo "tar.gz", execute "vrep.sh", dentro do diretório de descompactação. No Mac, a aplicação está em "Applications -> v-rep". Após iniciar a aplicação, carregue a cena v-Happy.ttt, que contém o robô Khepera 3 com o código padrão de movimentação em script Lua inteiramente comentado. Em algumas distribuições Linux foi necessário clicar em "Help -> Debug -> Debug C API access (will drastically slow down V-REP)", pois o plugin do v-rep para a API remota não estava habilitado por padrão.

O download do binário do v-Happy Socket compilado para Linux 64 bits pode ser encontrado aqui. Faça o download e execute-o com:

./vHappy --vrep_ip=127.0.0.1 --vrep_port=19997 --vHappy_port=35000

Onde --vrep_ip é o endereço IP da máquina que está rodando o v-rep e --vrep_port é sua porta (lembre-se, por padrão, 19997). O parâmtro --vHappy_port representa a porta que será escutada pelo ServerSocket do v-Happy Socket para conexões entrantes. Neste ponto, caso a conexão com o v-rep tenha acontecido com sucesso, você receberá a seguinte mensagem na tela:

Waiting for connections in port [ 35000 ]...

Para testar o socket, abra um outro terminal e digite:

telnet -r 127.0.0.1 35000

Pronto! Envie os seguintes comandos para o v-rep para ver o v-Happy Socket em ação:

moveLeft

moveFront

stop

moveReverse

moveRight

bye

A tela de saída do v-Happy Socket será algo semelhante à isso:

Trying to connect to v-rep on [ 127.0.0.1 : 19997 ]
Connected to v-rep!
Waiting for connections in port [ 35000 ]...
Client connected!
Received command [ moveRight ]
Received command [ moveFront ]
Received command [ stop ]
Received command [ moveReverse ]
Received command [ moveLeft ]
Received command [ bye ]
Exiting...

Para fins de consulta, o download do código fonte do v-Happy Socket pode ser feito aqui. Para compilá-lo, execute:

make clean all

Abaixo podemos ver o v-HappySocket em ação.

Freemake

vRepClarion

A segunda parte do trabalho consiste em conectar a arquitetura cognitiva Clarion ao v-Happy Socket, para que este repasse os comandos para o v-rep, comandos estes, baseados nas regras de tomadas de decisões programadas na arquitetura cognitiva Clarion. O vRepClarion foi escrito em C# e o código responsável pela conexão pode ser visto aqui:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

//namespace vRepClarion {

public class ClientSocket
{
    private bool bIsConnected;
    private Socket socket;

    public ClientSocket( )
    {
        this.socket = null;
        this.bIsConnected = false;
    }

    ~ClientSocket( )
    {
        this.disconnect( );
    }

    public void connect( String strIpAddress, int iPort )
    {
        this.socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
        this.socket.Connect( new IPEndPoint( IPAddress.Parse( strIpAddress ), iPort ) );
        this.bIsConnected = true;
    }

    public void disconnect( )
    {
        if( this.bIsConnected )
        {
            this.socket.Shutdown( SocketShutdown.Both );
            this.bIsConnected = false;
        }

        if( this.socket != null )
        {
            this.socket.Close( );
            this.socket = null;
        }
    }

    public void send( String strMessage )
    {
        this.socket.Send( Encoding.ASCII.GetBytes( strMessage ) );
    }

    public String receive( )
    {
        byte[ ] bytes = new byte[ 8192 ];
        int iBytesReceived = this.socket.Receive( bytes );

        return Encoding.ASCII.GetString( bytes, 0, iBytesReceived );
    }
}
//}

A classe VREPRobotClient foi escrita fazendo uso da biblioteca ClarionLibrary.dll, com o objetivo de tomar as decisões de maneira simples, baseadas em um número aleatório. Assim, foram criados DimensionValuePairs para cada um dos cinco movimentos implementados no protocolo de v-Happy Socket, bem como seus ExternalActionChunks. Um agente "vRepRobot" é inicializado com um algorítmo simplificado de Backpropagation. Foi criado também um sensor de informações, responsável por informar ao agente se suas decisões foram boas ou não. A classe VREPRobotClient pode ser vista aqui:

using System;
using System.Threading;

using Clarion;
using Clarion.Framework;

//namespace vRepClarion {

public class VREPRobotClient
{
	public VREPRobotClient( ){ }

	public static int Main( string[ ] args )
	{
		String strIpAddress = "127.0.0.1";
		int iPort = 35000;
		
		int iNumberOfMovements = 100;
		int iProgress = 0;
		
		if( args.Length >= 1 )
			strIpAddress = args[ 0 ];
		
		if( args.Length >= 2 )
			iPort = ( int ) Convert.ToInt64( args[ 1 ] );
		
		Console.WriteLine( "Initializing the vRepClarion..." );
		Console.WriteLine( "Connecting to vHappy on [ " + strIpAddress + " : " + iPort + " ]" );
		
		ClientSocket clientSocket = new ClientSocket( );
		clientSocket.connect( strIpAddress, iPort );
		
		DimensionValuePair dvMoveLeft = World.NewDimensionValuePair( "Movement", "Left" );
		DimensionValuePair dvMoveFront = World.NewDimensionValuePair( "Movement", "Front" );
		DimensionValuePair dvStop = World.NewDimensionValuePair( "Movement", "Stop" );
		DimensionValuePair dvMoveReverse = World.NewDimensionValuePair( "Movement", "Reverse" );
		DimensionValuePair dvMoveRight = World.NewDimensionValuePair( "Movement", "Right" );
		
		ExternalActionChunk eacMoveLeft = World.NewExternalActionChunk( "Left" );
		ExternalActionChunk eacMoveFront = World.NewExternalActionChunk( "Front" );
		ExternalActionChunk eacStop = World.NewExternalActionChunk( "Stop" );
		ExternalActionChunk eacMoveReverse = World.NewExternalActionChunk( "Reverse" );
		ExternalActionChunk eacMoveRight = World.NewExternalActionChunk( "Right" );
		
		// Initialize the Agent
		Agent agent = World.NewAgent( "vRepRobot" );
		
		SimplifiedQBPNetwork simplifiedQBPNetwork = AgentInitializer.InitializeImplicitDecisionNetwork(
			agent, SimplifiedQBPNetwork.Factory
		);
		
		simplifiedQBPNetwork.Input.Add( dvMoveLeft );
		simplifiedQBPNetwork.Input.Add( dvMoveFront );
		simplifiedQBPNetwork.Input.Add( dvStop );
		simplifiedQBPNetwork.Input.Add( dvMoveReverse );
		simplifiedQBPNetwork.Input.Add( dvMoveRight );
		simplifiedQBPNetwork.Output.Add( eacMoveLeft );
		simplifiedQBPNetwork.Output.Add( eacMoveFront );
		simplifiedQBPNetwork.Output.Add( eacStop );
		simplifiedQBPNetwork.Output.Add( eacMoveReverse );
		simplifiedQBPNetwork.Output.Add( eacMoveRight );
		
		agent.Commit( simplifiedQBPNetwork );
		
		simplifiedQBPNetwork.Parameters.LEARNING_RATE = 1;
		agent.ACS.Parameters.PERFORM_RER_REFINEMENT = false;
		
		Console.WriteLine( "Running the Simple v-rep Clarion Controller..." );
		
		Random random = new Random( );
		SensoryInformation sensoryInformation;
		ExternalActionChunk eacChosen;
		
		for( int i = 0; i < iNumberOfMovements; i++ )
		{
			sensoryInformation = World.NewSensoryInformation( agent );
			
			// Move
			if( random.NextDouble( ) < 0.2 )
			{
				sensoryInformation.Add( dvMoveLeft, agent.Parameters.MAX_ACTIVATION );
				sensoryInformation.Add( dvMoveFront, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvStop, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveReverse, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveRight, agent.Parameters.MIN_ACTIVATION );
				clientSocket.send( "moveLeft" );
			}
			else if( random.NextDouble( ) < 0.4 )
			{
				sensoryInformation.Add( dvMoveLeft, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveFront, agent.Parameters.MAX_ACTIVATION );
				sensoryInformation.Add( dvStop, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveReverse, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveRight, agent.Parameters.MIN_ACTIVATION );
				clientSocket.send( "moveFront" );
			}
			else if( random.NextDouble( ) < 0.6 )
			{
				sensoryInformation.Add( dvMoveLeft, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveFront, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvStop, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveReverse, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveRight, agent.Parameters.MAX_ACTIVATION );
				clientSocket.send( "moveRight" );
			}
			else if( random.NextDouble( ) < 0.8 )
			{
				sensoryInformation.Add( dvMoveLeft, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveFront, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvStop, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveReverse, agent.Parameters.MAX_ACTIVATION );
				sensoryInformation.Add( dvMoveRight, agent.Parameters.MIN_ACTIVATION );
				clientSocket.send( "moveReverse" );
			}
			else
			{
				sensoryInformation.Add( dvMoveLeft, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveFront, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvStop, agent.Parameters.MAX_ACTIVATION );
				sensoryInformation.Add( dvMoveReverse, agent.Parameters.MIN_ACTIVATION );
				sensoryInformation.Add( dvMoveRight, agent.Parameters.MIN_ACTIVATION );
				clientSocket.send( "stop" );
			}
			
			// Perceive the sensory information
			agent.Perceive( sensoryInformation );
			
			// Choose an action
			eacChosen = agent.GetChosenExternalAction( sensoryInformation );
			
			// Deliver appropriate feedback to the agent
			if( eacChosen == eacMoveLeft )
			{
				if( sensoryInformation[ dvMoveLeft ] == agent.Parameters.MAX_ACTIVATION )
				{
					// The agent was right
					agent.ReceiveFeedback( sensoryInformation, 1d );
				}
				else
				{
					// The agent was wrong
					agent.ReceiveFeedback( sensoryInformation, 0d );
				}
			}
			else if( eacChosen == eacMoveFront )
			{
				if( sensoryInformation[ dvMoveFront ] == agent.Parameters.MAX_ACTIVATION )
				{
					// The agent was right
					agent.ReceiveFeedback( sensoryInformation, 1d );
				}
				else
				{
					// The agent was wrong
					agent.ReceiveFeedback( sensoryInformation, 0d );
				}
			}
			else if( eacChosen == eacStop )
			{
				if( sensoryInformation[ dvStop ] == agent.Parameters.MAX_ACTIVATION )
				{
					// The agent was right
					agent.ReceiveFeedback( sensoryInformation, 1d );
				}
				else
				{
					// The agent was wrong
					agent.ReceiveFeedback( sensoryInformation, 0d );
				}
			}
			else if( eacChosen == eacMoveReverse )
			{
				if( sensoryInformation[ dvMoveReverse ] == agent.Parameters.MAX_ACTIVATION )
				{
					// The agent was right
					agent.ReceiveFeedback( sensoryInformation, 1d );
				}
				else
				{
					// The agent was wrong
					agent.ReceiveFeedback( sensoryInformation, 0d );
				}
			}
			else if( eacChosen == eacMoveRight )
			{
				if( sensoryInformation[ dvMoveRight ] == agent.Parameters.MAX_ACTIVATION )
				{
					// The agent was right
					agent.ReceiveFeedback( sensoryInformation, 1d );
				}
				else
				{
					// The agent was wrong
					agent.ReceiveFeedback( sensoryInformation, 0d );
				}
			}
			
			iProgress = ( int ) ( ( i + 1d ) / iNumberOfMovements * 100d );
			Thread.Sleep( 1000 );
		}
		
		// Report results
		Console.WriteLine( "Results for the Simple v-rep Clarion Controller" );
		Console.WriteLine( "At the end of the task, v-rep robot had learned the following rules:" );
		
		foreach( var commitableActionRules in agent.GetInternals( Agent.InternalContainers.ACTION_RULES ) )
		{
			Console.WriteLine( commitableActionRules );
		}
		
		// Kill the agent to end the task
		Console.WriteLine( "Killing the agent to end the program..." );
		agent.Die( );
		Console.WriteLine( "The agent is Dead!" );
		
		Console.Write( "Exiting..." );
		clientSocket.send( "bye" );
		return 0;
	}
}
//}

Para executarmos o vRepClarion, iniciamos o v-rep e carregamos nele a cena v-Happy.ttt. Após isso, executamos o v-Happy Socket com o comando:

./vHappy --vrep_ip=127.0.0.1 --vrep_port=19997 --vHappy_port=35000

Nesse caso, não podemos executar o comando telnet, já que v-Happy Socket suporta apenas uma conexão entrante. Agora, faça o download do binário do vRepClarion, descompacte-o e execute-o com o comando:

mono vRepClarion.exe 127.0.0.1 35000

Onde o primeiro argumento é o enderço IP da máquina que está rodando o v-Happy Socket e o segundo argumento é a porta. Após a execução, um sumário é gerado em tela, similar à este:

Initializing the vRepClarion...
Connecting to vHappy on [ 127.0.0.1 : 35000 ]
Running the Simple v-rep Clarion Controller...
Results for the Simple v-rep Clarion Controller
At the end of the task, v-rep robot had learned the following rules:
Condition:
        Dimension Movement, Weight = 1
                (Dimension = Movement, Value = Left) Setting = False,
                (Dimension = Movement, Value = Front) Setting = False,
                (Dimension = Movement, Value = Stop) Setting = False,
                (Dimension = Movement, Value = Reverse) Setting = True,
                (Dimension = Movement, Value = Right) Setting = False
Action:
        ExternalActionChunk Reverse:
                 Dimension SemanticLabel, Weight = 1
                        (Dimension = SemanticLabel, Value = Reverse)
Condition:
        Dimension Movement, Weight = 1
                (Dimension = Movement, Value = Left) Setting = False,
                (Dimension = Movement, Value = Front) Setting = False,
                (Dimension = Movement, Value = Stop) Setting = False,
                (Dimension = Movement, Value = Reverse) Setting = False,
                (Dimension = Movement, Value = Right) Setting = True
Action:
        ExternalActionChunk Right:
                 Dimension SemanticLabel, Weight = 1
                        (Dimension = SemanticLabel, Value = Right)
Condition:
        Dimension Movement, Weight = 1
                (Dimension = Movement, Value = Left) Setting = False,
                (Dimension = Movement, Value = Front) Setting = True,
                (Dimension = Movement, Value = Stop) Setting = False,
                (Dimension = Movement, Value = Reverse) Setting = False,
                (Dimension = Movement, Value = Right) Setting = False
Action:
        ExternalActionChunk Front:
                 Dimension SemanticLabel, Weight = 1
                        (Dimension = SemanticLabel, Value = Front)
Condition:
        Dimension Movement, Weight = 1
                (Dimension = Movement, Value = Left) Setting = False,
                (Dimension = Movement, Value = Front) Setting = False,
                (Dimension = Movement, Value = Stop) Setting = True,
                (Dimension = Movement, Value = Reverse) Setting = False,
                (Dimension = Movement, Value = Right) Setting = False
Action:
        ExternalActionChunk Stop:
                 Dimension SemanticLabel, Weight = 1
                        (Dimension = SemanticLabel, Value = Stop)
Killing the agent to end the program...
The agent is Dead!
Exiting...

O código fonte do vRepClarion pode ser obtido aqui.

Abaixo podemos ver o vRepClarion em ação.

Freemake

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer