31 Aug 2015

Reading serial data in Unity using C#

 Create your own motion controller using an Android phone.

In this tutorial, we will turn an Android phone into a motion controller like the PlayStation Move or Nintendo Wii Remote for a game developed using Unity C# scripting. I am not going to explain what is Unity and C# scripting language, because probably you won’t be here if you didn’t know what are those.

Requirements
  1. Unity game engine (I am using Unity 5 free edition)

  2. An Android phone, running  Kitkat or above (I have not tried any lower versions, feel free to let me know if there are any issues)

  3. SensoDuino app – Google Play | Official website

Overview

Here, we are going to use the SensoDuino app to send sensor data to the system through bluetooth and let Unity receive it through serial communication. SensoDuino is a simple app that lets you read sensor data, log them and/or transmit them through bluetooth. It also shows the list of available sensors in your phone. Each sensor row in SensoDuino has 3 check boxes –

ON – Turns the sensor On/Off, which means whether SensoDuino is reading the sensor data or not (does not turn off the circuit for real).

Tx – When checked, SensoDuino will transmit the sensor data through bluetooth to any connected device.

Log – Sensor readings are logged to a comma-delimited text file by the name sensoduino.txt in the root directory of your SD card.

It is very important to adjust the frequency in which the sensors are read by the app. This will help in smoothing the data curve. The more data we receive, the more smooth the value differences will be. You can set this from the SensoDuino settings – between 100 millisecond (10 Hz) to 10 minutes. Some sensors generate data once per second (1Hz) while others generate upto 10 times per second (10Hz) and some in microseconds.

The sensitivity of the sensors in your phone are subjected to environmental factors (Gyro, Accelerometer, Magnetometer) like magnetic fields, which is why the phone is still reading sensor values even when the phone is in a static position. The app is very power hungry because of the sensors and bluetooth working simultaneously, keeping your device awake. You might want to close all the background apps before you open SensoDuino, consider even putting the phone in flight mode.

Setting up Unity project

Since the project is about testing serial communication, we will create a 2D project with 2D assets loaded. Before we start with the project, you need to change the Player settings to support serial port API. Goto Edit>Project settings>Player and in Other settings, under Optimization, change the API compatibility level to .NET 2.0 instead of .NET 2.0 subset. This will allow unity to access serial.io class. Create appropriate folders to place scripts and textures in the project directory. In your textures folder, create a 2D sprite, here I have made a paddle and pulled it into the scene. We will move this paddle using the accelerometer values later.

serial_tut_1

Now, in Scripts folder, create a new C# script by right click menu and open it in Mono Develop.

The code

If you want to use SerialPort communication, use the following namespace.

 using System.IO.Ports; 

Declare a Serialport public variable

public SerialPort sp;

Configure the port in start() function

void Start () {
sp = new SerialPort("COM3", 9600);
}

Now, we can write a function to create a connection so that we can call it whenever we want. If we try to connect the device in the start function itself, you will have to quickly do the pairing or will have to increase the time-out time, which is a bad design. So next, we will write the connect() function.

    void connect() {
        Debug.Log ("Connection started");
        try {
            sp.Open();
            sp.ReadTimeout = 400;
            sp.Handshake = Handshake.None;
            serialThread = new Thread(recData);
            serialThread.Start ();
            Debug.Log("Port Opened!");
        }catch (SystemException e)
        {
            Debug.Log ("Error opening = "+e.Message);
        }
    }

In the above piece of code, some bluetooth communication requires Handshake to be explicitly mentioned. Here, we don’t need Handshake, hence we disable it. Here, we are using Threads because serial communication will create lag or makes the UI thread hang. The recData function is to read the incoming data. We will write that next.

     void recData() {
        if ((sp != null) && (sp.IsOpen)) {
            byte tmp;
            string data = "";
            string avalues="";
            tmp = (byte) sp.ReadByte();
            while(tmp !=255) {
                data+=((char)tmp);
                tmp = (byte) sp.ReadByte();
                if((tmp=='>') && (data.Length > 30)){
                    avalues = data;
                    parseValues(avalues);
                    data="";
                }
            }
        }
    }

As long as the port is open and available, we read from the port using ReadByte(), convert the data into a character and attach it to a string. This input processing is on the basis of how SensoDuino sends the data. The following is the format in which SensoDuino transmits Accelerometer data.

 >1,347,0.33021545,3.562912,8.662018
                                          >1,348,0.5024872,3.4002075,9.114258
                                                                             >1,349,0.48095703,3.4468536,8.971893

Here, we need only the characters after the first 6 characters. parseValues() function is to convert the string values to float.

    void parseValues(string av) {
        string[] split = av.Split (',');
        x1 = float.Parse (split [2]);
        y1 = float.Parse (split [3]);
        z1 = float.Parse(split[4]);
        readyToMove = true;
    }

Now we have the accelerometer values, lets write the moveObj() function. Because it is a pong paddle, here I am using only the Y value.

    void moveObj(float x, float y, float z) {
        int speed = 10;
        Vector3 move = Vector3.zero ;
        move.y = y;
        move.Normalize();
        move.y = Mathf.Lerp(move.y,prevY,speed*Time.deltaTime);
        transform.Translate(Vector3.up * (move.y * speed) * Time.deltaTime,Space.World);
        readyToMove = false;
        prevY = move.y;
    }

Lerp is used to get a smooth curve of values so that the movement is gradient.

Now add the script to the game object, ie the paddle and call connect() wherever you want. In SensoDuino, check the ON and Tx boxes of Accelerometer and from menu, choose connect to connect to your device. That’s it!

Download code file