Mono SerialPort.DataReceived event workaround

By | March 6, 2012

On a related note to the previous post, here is a patch that enables the SerialPort.DataReceived event. It is not officially supported in the Linux version of Mono. This workaround works in the following way:

When a port is opened, a new thread is started. It polls the port for incoming data and generates the DataReceived events. The thread exits automatically when the serial port is closed by the application.

Update: The solution has been tested in Ubuntu and OpenEmbedded Linux during the last year. No problems found.

Update: added another way to work around the problem. It does not require recompiling Mono from source, only changes to the application. Link

diff --git a/mcs/class/System/System.IO.Ports/SerialPort.cs b/mcs/class/System/System.IO.Ports/SerialPort.cs
index 657d4b1..ad01dbc 100644
--- a/mcs/class/System/System.IO.Ports/SerialPort.cs
+++ b/mcs/class/System/System.IO.Ports/SerialPort.cs
@@ -578,14 +578,39 @@ namespace System.IO.Ports
                        if (IsWindows) // Use windows kernel32 backend
                                stream = new WinSerialStream (port_name, baud_rate, data_bits, parity, stop_bits, dtr_enable,
                                        rts_enable, handshake, read_timeout, write_timeout, readBufferSize, writeBufferSize);
-                       else // Use standard unix backend
+                       else { // Use standard unix backend
 #endif
                                stream = new SerialPortStream (port_name, baud_rate, data_bits, parity, stop_bits, dtr_enable,
                                        rts_enable, handshake, read_timeout, write_timeout, readBufferSize, writeBufferSize);
-
+
+                               new System.Threading.Thread(new System.Threading.ThreadStart(this.EventThreadFunction)).Start();
+                       }
                        is_open = true;
                }

+               private void EventThreadFunction( )
+               {
+                       SerialPortStream _stream = stream as SerialPortStream;
+                       if (_stream == null){
+                               return;
+                       }
+
+                       do
+                       {
+                               try
+                               {
+                                       if (_stream.Poll(ReadTimeout)){
+                                               OnDataReceived(new SerialDataReceivedEventArgs(SerialData.Chars));
+                                       }
+                               }
+                               catch
+                               {
+                                       return;
+                               }
+                       }
+                       while (is_open);
+               }
+
                public int Read (byte[] buffer, int offset, int count)
                {
                        CheckOpen ();

diff --git a/mcs/class/System/System.IO.Ports/SerialPortStream.cs b/mcs/class/System/System.IO.Ports/SerialPortStream.cs
index 61ca049..1e9d10c 100644
--- a/mcs/class/System/System.IO.Ports/SerialPortStream.cs
+++ b/mcs/class/System/System.IO.Ports/SerialPortStream.cs
@@ -127,6 +127,17 @@ namespace System.IO.Ports
[DllImport ("MonoPosixHelper", SetLastError = true)]
static extern bool poll_serial (int fd, out int error, int timeout);

+               internal bool Poll(int timeout)
+               {
+                       CheckDisposed ();
+                       int error;
+                       bool poll_result = poll_serial (fd, out error, read_timeout);
+                       if (error == -1)
+                               ThrowIOException ();
+
+                       return poll_result;
+               }
+
public override int Read ([In,Out] byte[] buffer, int offset, int count)
{
CheckDisposed ();

16 thoughts on “Mono SerialPort.DataReceived event workaround

    1. Antanas Post author

      I consider it to be a workaround, although it works very well

      Reply
  1. Silas

    So what is your preferred method to patch it, just copy the SerialPort.cs file to your solution and patch it there or patch it in the mono source and rebuild the mono source?

    Reply
    1. Antanas Post author

      I did a custom Mono build with a few more changes. I haven’t tried to implement this in an application, guess the stream object is not accessible.

      Reply
  2. Thomas

    Hi
    I downloaded the mono-master but the SerialPortStream
    does not seem to contain the Poll methed. did you implement this your self too?

    Reply
    1. Antanas Post author

      Hi Thomas,
      thanks for noticing, indeed the change was missing. I have updated the article

      Reply
  3. sam walsh

    I have implemented this workaround however it seems to give me very erratic results.

    If I do a “readExisting” then it only seems to read half a string,

    I have sent “hello world” and I only get back “hell” or “orld” with a read existing.

    I then tried to implement sp.BytesToRead to find out if the number of bytes coming back were right and it only has 2 bytes to read for a loop back test of hello world.

    Maybe I am implementing your code wrong but I just can’t seem to get it to perform properly.

    Reply
    1. Antanas Post author

      Hello Sam,
      this seems to be a timing problem. ReadExisting is called while the data is still incoming. Are you losing data after multiple events? It is normal to receive “he”, “llo w” “orld” in your example. The exact splitting of the data depends on the OS scheduler, serial port driver, buffer size, baud rate etc. For me it is not a problem – my stream parsers always wait for complete packets that are read in one or more events.

      You could provide the relevant part of your application code for a quick review

      Antanas

      Reply
  4. Pavel Petrov

    I`m using this code and the first run everything works. However when I close and reopen the application the note collector(this is what i`m communicating with) behaves erratically and the connection falls appart. My hunch is that the thread with the event listener remains orphaned? I tried closing the connection on close so that is_open stops the thread but it doesn`t make any difference. I`ll try setting “is_open” as volatile, but i`m not sure that causes it any ideas?

    Reply
    1. Antanas Post author

      Hi Pavel,
      please enable stepping into Mono source (assuming You are using monodevelop) and put some breakpoints to see if the thread loop finishes properly. Or perhaps your ReadTimeout is set to too long value? In this case the thread would wait for that long to check is_open. It would be better to wake up the thread when the port is closed. Unfortunately I’m not working on that project anymore and don’t have the source/tools readily set up.

      Reply
      1. Pavel Petrov

        The library i`m using sets the Read&Write timeouts to 500 is it likely that it fails while a note is being read and that causes all the problems? I managed to decompile this library i`ll try setting them to a bigger value to test

      2. Pavel Petrov

        btw thanks for the fast response 🙂 much appreciated you`re basically the only useful info on this matter

  5. Felix Mann

    Hi! I tried to apply these patches but I get an error “fatal: corrupt patch at line 46”. The command I use is
    git apply --ignore-space-change --ignore-whitespace changes.diff
    Any hints? For now I will be applying the patch mnually, but I would like to know what I’m doing wrong…

    Thanks!

    Reply
    1. Antanas Post author

      Hi,
      perhaps you could try adding a new line at the end of the patch file. Line 46 is the last line, just guessing. Please let me know if you find a working solution, I’ll update the post

      Reply
  6. Felix Mann

    The first diff applied successfully after appending a newline. The second one fails on line 6… I applied the second patch manually. Thanks for your quick response!

    fatal: corrupt patch at line 6
    C:UsersFelixDocumentsGitHubmono [mono-2-10-9 +1 ~1 -0 !]>

    Reply
  7. esben

    I’ve successfully implemented this class on a linux machine. Now i need to use the same code on a windows machine, but it does not seem to work. The windows is using Mono instead of .NET, as it’s running in the Godot Game engine, which ships Mono.
    Any ideas?

    Reply

Leave a Reply

Your email address will not be published.