Volante bluetooth – lego nxt + unity3d

Ter idéias boas para desenvolver coisas com o lego nxt é muito difícil, um dia jogando Dirt 2 no Xbox com o volante wireless tive a idéia de fazer um volante desse com o lego, isso ficou na minha cabeça por um tempo, com esse feriado de páscoa, resolvi por em prática já que não tinha nada para fazer mesmo.
O negócio funciona assim, o lego manda para um servidor socket escrito em java os dados de rotação e de dois botões(acelerador e freio, os dois juntos da a marcha ré), esse servidor manda para o unity3D uma string com as informações concatenadas, o unity processa a string e separa as informações.
Utilizei um jogo de carro de corrida já pronto, disponível em : JCar Demo , Todos os créditos para o autor, apenas modifiquei um script que controla o carro para adaptar na minha lógica.

O vídeo abaixo demonstra o funcionamento.


Para essa brincadeira foi desenvolvido 3 códigos.
O primeiro é para o cérebro do Lego, que apenas pega os dados de rotação de um motor e os estados de 2 sensores do toque, concatena em uma string separando por “/” as informações, exemplo:
“-20/true/false”, que seria -20 graus , acelerador apertado e freio não apertado.

import lejos.nxt.*;
import lejos.nxt.comm.*;
import java.io.*;

public class VolanteLego {

	public static void main(String [] args)  throws Exception 
	{
		String connected = "Conectando";
        String waiting = "Aguardando...";
        String closing = "Fechando...";
        String texto;
        //Tira a força do Motor para poder girar manualmente
        Motor.A.setPower(0);
        //Pega os sensores
        TouchSensor acelerador = new TouchSensor(SensorPort.S1);
        TouchSensor freio = new TouchSensor(SensorPort.S2);
		
			LCD.drawString(waiting,0,0);
			LCD.refresh();
			//fica aguardando o pc conectar
	        BTConnection btc = Bluetooth.waitForConnection();
	        
			LCD.clear();
			LCD.drawString(connected,0,0);
			LCD.refresh();	
			//cria o canal de saida
			DataOutputStream dos = btc.openDataOutputStream();
			
			while (!Button.ENTER.isPressed()) {
				LCD.clear();
				//monta uma string com os dados, eles serão filtrados no unity
				texto = Motor.A.getTachoCount() + "/" + acelerador.isPressed() + "/" + freio.isPressed();
				//apenas para Debug
				LCD.drawString(texto, 0, 0);
				LCD.refresh();
				//envia os daos e atualiza o canal
				// \n necessario para falar que acabou a string
				dos.writeBytes(texto + "\n");
				dos.flush();				
			}	
			//fecha e limpa tudo depois que terminou
			dos.close();
			Thread.sleep(100); 
			LCD.clear();
			LCD.drawString(closing,0,0);
			LCD.refresh();
			btc.close();
			LCD.clear();		
	}
}



O segundo código é um servidor em java, ele se conecta com o lego, pega os dados e transmite via socket, para ser capturado no unity3D.

import lejos.pc.comm.*;
import java.io.*;
import java.net.*;


public class VolantePC {	
	// variaveis para UDP socket
	static DatagramSocket udpSocket;
	static int porta = 23175;
	static InetAddress localhost;	
	static DataOutputStream dos ;
	static DataInputStream dis ;
	//string que será compartilhada
	static String bufferStr="";
	
	public static void main(String[] args) {
		// Conecta o nxt
		NXTConnector conn = new NXTConnector();				
		boolean connected = conn.connectTo("btspp://");
		
		if (!connected) {
			System.err.println("Falhou a conexao com o NXT");
			System.exit(1);
		}
		
		//canais de entrada e saida
		 dos = conn.getDataOut();
		 dis = conn.getDataIn();
		
		// cria o socket para enviar
		try
		{
			localhost = InetAddress.getLocalHost();
			udpSocket = new DatagramSocket();
		}
		catch(SocketException e)
		{
			System.out.println("Não abriu o socket");
		}
		catch(UnknownHostException e)
		{
			System.out.println("Não achou o localhost!");
		}
		
		// Inicia uma nova thread para enviar os dados para o pc
		System.out.println("Transmitindo dados UDP na porta "+porta);
		(new Thread(new EnviarDados(udpSocket))).start();
		
		//loop de leitura dos dados do lego
		while(true)
		{			
			try {
				bufferStr = dis.readLine() + "\n";				
			} catch (IOException ioe) {
				System.out.println("IO Exception lendo os bytes:");
				System.out.println(ioe.getMessage());
				break;
			}
		}
		
	}
//  thread para enviar os dados
	public static class EnviarDados implements Runnable
	{
				DatagramSocket datagrama;
		
		public EnviarDados(DatagramSocket datagrama)
		{
			this.datagrama = datagrama;
		}
		
		public void run()
		{			
			// buffer para os dados de saida
			DatagramPacket pacote;
			byte[] pacoteBuffer;
						
			try
			{
				// le os dados para o buffer
				while(true)
				{				
					// converte para byte array para enviar o pacote
					pacoteBuffer = stringToByteArray(bufferStr);
					pacote = new DatagramPacket(pacoteBuffer, pacoteBuffer.length, VolantePC.localhost, VolantePC.porta);
					datagrama.send(pacote);		
					System.out.println(bufferStr);					
					Thread.sleep(33);
				}				
			}catch(IOException e){
				System.out.println("erro");
			}catch(InterruptedException e) {				
				e.printStackTrace();
			}
		}		
		//le a string e converte para um array de bytes
		byte[] stringToByteArray(String str)
		{
			int length = str.length();
			byte[] byteArray = new byte[length];
			for(int i=0; i<length; i = i + 1)
			{
				byteArray[i] = (byte)(str.charAt(i) & 0xff);
			}
			return byteArray;
		}
	}
}

O terceiro é um javascript que se conecta com o servidor socket, filtra a string com os dados e fornece funções para passar os dados para o jogo.

#pragma strict

import System.Net.Sockets;
import System.Net;
import System.Text;

// qual porta o socket usará
var porta:int = 23175;
//string recebida no socket
private var dados:String = "";
// buffer dos dados pegos
private var buffer:byte[];
// Referencia para o socket UDP
private var cliente:UdpClient;
private var source:IPEndPoint;
private var rotacao:String;
private var aceleracao:String;
private var freio:String;

//inicia o socket
function Awake()
{
	IniciarSocket();
}
//le os dados
function Update()
{
	buffer = cliente.Receive(source);
	dados = Encoding.ASCII.GetString(buffer);
	dados = dados.Replace("\n", "");//limpa o finalizador da string
	//filtra os dados
	rotacao = dados.Substring(0,dados.IndexOf("/"));
	dados = dados.Substring(dados.IndexOf("/")+1);
	aceleracao = dados.Substring(0,dados.IndexOf("/"));
	freio = dados.Substring(dados.IndexOf("/")+1);
}
public function getRotation():float{
	//transforma os dados em um range de -1 até 1, depois inverte o sinal 
	return parseFloat(Mathf.Clamp(parseInt(rotacao), -90, 90)/100.0f)*-1.0f;
}
function getAcceleration():float{
	//se só acelerar vai para frente, se aceletar e freiar da ré , ou desacelera
	return aceleracao=="true"?(freio=="true"?-1:1):0;
}
function getBrake():boolean{
	//só freiará se apenas o freio estiver apertado
return (freio=="true" && aceleracao=="false");
}

/**
* Inicia a comunicação com o servidor
*/
function IniciarSocket()
{
	try
	{
		cliente = new UdpClient(porta);		
		source = new IPEndPoint(IPAddress.Any, 0);
	}
	catch(e:System.Exception)
	{
		Debug.Log(e.ToString());
	}
}

Mais um código em C# foi modificado no JCar, para ler as informações desse último código, ele apenas pega os dados e utiliza no jogo.
Para você refazer esse negócio na sua casa, coloquei os códigos para download, apenas tenho umas observações.
O código javascript deve ficar dentro da pasta plugin, pois o unity tem uma frescura no compilador que atrapalha a referência na chamada de um código javascript por um script em c#, colocando dentro da pasta chamada plugins ele é compilado antes, não causando mais problemas.
Passos para refazer:
- Baixar o demo do JCar que está no link acima e substituir o arquivo JControlledCar.cs.
- Copiar a pasta Plugins que contem o arquivo ClienteSocket.js na pasta Assets.
- Dentro do unity3D, colocar o ClienteSocket.js no gameobject Car.
- O JCar usa o torque do motor em 560, acho muito, então diminui para 400, para ficar mais fácil de controlar.

Depois disso é só executar os programas na sequência – Lego , servidor , jogo no Unity e pronto.

PS: Se alguém tiver uma idéia boa de algum projeto bacana que seria legal desenvolver com o lego e quiser compartilhar, posta nos comentários.

DOWNLOADS:
Código fonte

Share

2 Comments

  • Daniel |

    eu quero ele rodando no play 3 p que eu possa jogar dirt com volatinho do lego!:D

So, what do you think ?