After I completed a working version of the GoPiGo v2 to work with Windows IOT on Raspberry Pi 3, I started working on the next level of my rover (called Mocha) Sorry I like coffee ;). The samples from Dexter Robotics allowed me to use a Windows 10 UWP project to control the robot. This was great and you can see the project previously on Hackster here
So I now wanted to see if I can control the robot/rover from other platforms and decided on GoogleVR using Unity.
A slight modification was needed in the socket communication so Unity C# script can communicate with the Windows IOT Socket Reader.
Previously UWP needed extra code to load sizes and number of bytes to read off the stream.
uint sizeFieldCount = await reader.LoadAsync(sizeof(uint));
if (sizeFieldCount != sizeof(uint))
{
return;
}
uint stringLength = reader.ReadUInt32();
uint actualStringLength = await reader.LoadAsync(3);
if (stringLength != actualStringLength)
{
return;
}
Message = reader.ReadString(actualStringLength);
But Unity C# does not have this classes on to send to the UWP Window IOT, So I decided to just have the Windows IOT to read up to 20 characters in a Partial Input Stream.
DataReader reader = new DataReader(_streamSocket.InputStream);
reader.InputStreamOptions = InputStreamOptions.Partial;
Then in a continuous loop to wait for data on the stream. the above code to read the field size and string length was not going to work. I simplified it down to
await reader.LoadAsync(20);
Message = reader.ReadString(reader.UnconsumedBufferLength);
This will read up to 20 bytes in the stream and all the current code I want to use from my Unity Client needed only 3 characters this was plenty with space for future enhancements.
Now that the rover is loaded and ready to accept connections for my Unity project, I created 3 classes in my Unity project using C# scripts. I wanted to use an EventManager class to global listen to Invoke methods from my Buttons.
So to keep it simple to show the functions calling the rover I create the Unity Project and included the current at the time Google VR (GoogleVRForUnity_1.130.1) I also just recently test the GoogleVRForUnity_1.17 and it worked as well. I the MochaDroidVR Unity Project there is a Canvas and Panel then 5 UI Buttons shown in the picture,
The EventManager.cs file
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
public class EventManager : MonoBehaviour
{
private Dictionary<string, UnityEvent> eventDictionary;
private static EventManager eventManager;
public static EventManager instance
{
get
{
if (!eventManager)
{
eventManager = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManager)
{
Debug.LogError("There needs to be one active EventManger script on a GameObject in your scene.");
}
else
{
eventManager.Init();
}
}
return eventManager;
}
}
void Init()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, UnityEvent>();
}
}
public static void StartListening(string eventName, UnityAction listener)
{
UnityEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new UnityEvent();
thisEvent.AddListener(listener);
instance.eventDictionary.Add(eventName, thisEvent);
}
}
public static void StopListening(string eventName, UnityAction listener)
{
if (eventManager == null) return;
UnityEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
public static void TriggerEvent(string eventName)
{
UnityEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke();
}
}
}
The Controller.cs file for making the initial socket connection and sending string messages to the rover
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class Controller : MonoBehaviour {
ClientSocketConnection client = null;
// Use this for initialization
void Start () {
client = new ClientSocketConnection();
client.Connect ("192.168.1.51", "8027");
EventManager.StartListening("RightButton", new UnityAction(RightButton));
EventManager.StartListening("LeftButton", new UnityAction(LeftButton));
EventManager.StartListening("ForwardButton", new UnityAction(ForwardButton));
EventManager.StartListening("ReverseButton", new UnityAction(ReverseButton));
EventManager.StartListening("StopButton", new UnityAction(StopButton));
}
public void RightButton()
{
Debug.Log ("Right Button");
client.SendMessage("4|0");
}
public void LeftButton()
{
Debug.Log ("Left Button");
client.SendMessage("3|0");
}
public void ForwardButton()
{
Debug.Log ("Forward Button");
client.SendMessage("1|0");
}
public void ReverseButton()
{
Debug.Log ("Reverse Button");
client.SendMessage("2|0");
}
public void StopButton()
{
Debug.Log ("Stop Button");
client.SendMessage("0|0");
}
// Update is called once per frame
void Update () {
}
}
This is a good start but now the tough part which is getting the Unity C# to talk to the Rover which is running Windows IOT
The final class ClientSocketConnection.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using UnityEngine;
public class ClientSocketConnection
{
//exposed to hook into server status changes
public event EventHandler<ConnectionStatusChangedEventArgs> ConnectStatusChanged;
private ConnectionStatus _status = ConnectionStatus.Idle;
public ConnectionStatus Status
{
get { return _status; }
set
{
if (value != _status)
{
_status = value;
var args = new ConnectionStatusChangedEventArgs { Status = _status };
var handler = ConnectStatusChanged;
if (handler != null)
handler.Invoke(Status, args);
}
}
}
private Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private BinaryReader nwRead;
private StreamWriter nwWrite;
public void Connect(string serverIP, string serverPort)
{
try
{
Status = ConnectionStatus.Connecting;
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(serverIP, int.Parse(serverPort));
NetworkStream ns = new NetworkStream(sock);
nwRead = new BinaryReader(ns);
nwWrite = new StreamWriter(ns);
Status = ConnectionStatus.Connected;
}
catch (Exception e)
{
Debug.Log (e.ToString ());
Status = ConnectionStatus.Failed;
//todo:report errors via event to be consumed by UI thread
}
}
public void SendMessage(string message)
{
Debug.Log ("SendMessage");
try
{
nwWrite.WriteLine(message);
nwWrite.Flush();
//await _writer2.FlushAsync();
}
catch (Exception exc)
{
Debug.Log ("Error");
Debug.Log (exc.ToString ());
Status = ConnectionStatus.Failed;
}
}
public void GetData()
{
try
{
while (true)
{
int j = nwRead.ReadInt32();
byte[] data = nwRead.ReadBytes(j);
Message = System.Text.ASCIIEncoding.ASCII.GetString(data);
}
}
catch (Exception e)
{
Status = ConnectionStatus.Failed;
//TODO:send a connection status message with error, then try to reconnect
}
}
public event EventHandler<MessageSentEventArgs> NewMessageReady;
private string _message;
public string Message
{
get { return _message; }
set
{
//System.Diagnostics.Debug.WriteLine("Robot Received: " + _message);
_message = value;
var args = new MessageSentEventArgs { Message = _message };
var handler = NewMessageReady;
if (handler != null)
handler.Invoke(Message, args);
}
}
}
public class ConnectionStatusChangedEventArgs : EventArgs
{
public ConnectionStatus Status;
}
public class MessageSentEventArgs : EventArgs
{
public string Message;
}
public enum ConnectionStatus
{
Idle = 0,
Connecting,
Listening,
Connected,
Failed = 99
}
I left some code in that is currently not used but wanted to include it for future us.
To test if you do not want to Deploy to Android device you can just it the Play in Unity and it will work just fine.
Comments