Ok, this should be dirt simple. I'm trying to read charactes from a serial device. It's such that if I send a space character, it echos back a string of numbers and EOL. That's it.
I'm using Unity 3.3 (.Net 2.0 support), and the 'serial port' is a Prolific serial-to-USB adaptor. BTW: Using Hyperterminal, it all works perfectly, so I know it's not driver nor hardware.
I can open the port ok. It seems I can send my space with port.Write(" "); But if I even TRY to call ReadChar, ReadByte, or ReadLine (like polling), it freezes up until I unplug the USB, and my console output shows nothing (exceptions were caught).
So instead I set up a DataReceviedHandler, but it's never called.
I've read some posts where people have done just this type of thing with Arduinos etc. (this is not an Arduino but hey), using nothing more than ReadLine. Their code does not work for me (and no answers thus far from those authors).
So, any tips? Do I need to use a different thread? If you know any Unity (Mono) coding, any tips along those lines greatly appreciated.
This code a mashup from http://plikker.com/?p=163 and http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.datareceived.aspx#Y537
using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System;
public class SerialTest : MonoBehaviour {
SerialPort stream;
void Start () {
try {
stream = new SerialPort("COM3", 9600);
stream.Parity = Parity.None;
stream.StopBits = StopBits.One;
stream.DataBits = 8;
stream.Handshake = Handshake.None;
stream.DataReceived += new SerialDataReceivedEventHandler(DataReceviedHandler);
stream.Open();
Debug.Log("opened ok"); // it DOES open ok!
} catch (Exception e){
Debug.Log("Error opening port "+e.ToString()); // I never see this message
}
}
void Update () { // called about 60 times/second
try {
// Read serialinput from COM3
// if this next line is here, it will hang, I don't even see the startup message
Debug.Log(stream.ReadLine());
// Note: I've also tried ReadByte and ReadChar and the same problem, it hangs
} catch (Exception e){
Debug.Log("Error reading input "+e.ToString());
}
}
private static void DataReceviedHandler(
object sender,
SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender; // It never gets here!
string indata = sp.ReadExisting();
Debug.Log("Data Received:");
Debug.Log(indata);
}
void OnGUI() // simple GUI
{
// Create a button that, when pressed, sends the 'ping'
if (GUI.Button (new Rect(10,10,100,20), "Send"))
stream.Write(" ");
}
}
Make sure that you are opening the right port, using correct settings. Here is an example of how you could configure it:
serial = new SerialPort();
serial.ReadBufferSize = 8192;
serial.WriteBufferSize = 128;
serial.PortName = "COM1";
serial.BaudRate = 115200;
serial.Parity = Parity.None;
serial.StopBits = StopBits.One;
// attach handlers
// (appears to be broken in some Mono versions?)
serial.DataReceived += SerialPort_DataReceived;
serial.Disposed += SerialPort_Disposed;
serial.Open();
I recommend the open source RealTerm terminal, it has a rich set of features and can help you debug. Try writing a byte manually using such software, and if it works, then the problem is in your program. Otherwise it might be a driver problem (but more likely it isn't).
[Edit]
Calling SerialPort.ReadLine
is actually supposed to block the thread until SerialPort.NewLine
is received. Also ReadChar
and ReadByte
will hang until at least one byte is received. You need to make sure that you are actually receiving characters from the other side, and you won't be receiving them if your app is stuck and cannot send the space.
Since I never used Unity, I am not sure how Update
is called, but I am presuming it's fired on a foreground thread in regular intervals (otherwise your app wouldn't freeze).
The example that you linked (Arduino and Unity example) shows that Arduino is sending the data continuously, and that is why their Update
method is constantly receiving data (no space character needs to be sent towards the device). If they unplug the device, their app will hang just as well.
Well, maybe not, because in .NET 1.1, default value for ReadTimeout
was not infinite, like it is in .NET 2.0.
So, what you can do is:
a. Set the ReadTimeout
property to a reasonable value. Default in .NET 2.0 is InfiniteTimeout, which doesn't suit your needs. Cons: your update method will still hang for a while on each call, but not infinitely.
b. Someone said that events are not implemented in MONO SerialPort, so I guess using DataReceived
only is not an option.
c. Move your sending logic to the Update
method also, so that you don't read data at all, until it's time to read it:
private volatile bool _shouldCommunicate = false;
void Update ()
{
if (_shouldCommunicate) // this is a flag you set in "OnGui"
{
try {
stream.Write(" ");
Debug.Log(stream.ReadLine());
} catch (Exception e){
Debug.Log("Error reading input "+e.ToString());
}
}
}
void OnGUI() // simple GUI
{
if (GUI.Button (new Rect(10,10,100,20), "Send"))
_shouldCommunicate = true;
}
Note that, if your device is not sending data, it will also block at stream.ReadLine()
, so make sure your ReadTimeout
is set to a reasonable value. You will also want to stop sending at some point, but I leave that to you.
d. Send the space in OnGui
like you are doing now, but always check if there is data in your buffer before reading it:
void Update () { // called about 60 times/second
try {
// call our new method
Debug.Log(ReadLineNonBlocking());
} catch (Exception e){
Debug.Log("Error reading input "+e.ToString());
}
}
private StringBuilder sb = new StringBuilder();
string ReadLineNonBlocking()
{
int len = stream.BytesToRead;
if (len == 0)
return "";
// read the buffer
byte[] buffer = new byte[len];
stream.Read(buffer, 0, len);
sb.Append(ASCIIEncoding.ASCII.GetString(buffer));
// got EOL?
if (sb.Length < 2 ||
sb[sb.Length-2] != '\r' ||
sb[sb.Length-1] != '\n')
return "";
// if we are here, we got both EOL chars
string entireLine = sb.ToString();
sb.Length = 0;
return entireLine;
}
Disclaimer: this is directly out of my head, untested, so there may be some syntax errors which I am sure you will handle.
Events are not implemented in Mono SerialPort
class, so you won't get any notifications, you have to perform (blocking) read explicitly. Other possible problem - I'm not sure how Unity Behaviours work, are you certain all methods accessing the SerialPort
are invoked on the same thread? And you are not disposing you port object, this will also cause problems.
Maybe your problem is the configuration of the serial port. It is important not only to check for BaudRate or StopBits. Also you should configure DTR, RTS, Handshake, everything. This is important cause maybe another program set some ugly values and the configuration must be explicitly set at every start or some settings of the old connection can run you into trouble.
Also maybe take a look into one of these tools:
- com0com
- Serial Port Monitor
They can help you to stub your serial interface or to take a deeper look into the connection. Also maybe try to talk to your serial device by using HyperTerminal or some similar tool that's proven to work.
Had similar problem with Mono, upgrading to 2.6.7 helped.
Do not mix data event and blocking read. What do you expect to happen if data arrives? That both the read method and the event should get the same received data?
You should also read about:
- CTS: http://en.wikipedia.org/wiki/RS-232_RTS/CTS#RTS.2FCTS_handshaking
- DTR: http://en.wikipedia.org/wiki/Data_Terminal_Ready
Small serial port tutorial describing all states: http://www.wcscnet.com/Tutorials/SerialComm/Page1.htm