Mono SerialPort.DataReceived Event Workaround – Using a Derived Class

By | September 25, 2013

A followup to an older post of mine: http://antanas.veiverys.com/enabling-serialport-datareceived-event-in-mono/. The previous approach required rebuilding Mono with a small patch. Here is a derived class that does the same.

Advantages:

  • a standard Mono distribution can be used

Disadvantages:

  • reflection is used to access three private members by their names. In case of future changes in Mono the application will break;
  • currently DataReceived(object sender, SerialDataReceivedEventArgs e) event handler gets null value for parameter e. The only constructor of Mono SerialDataReceivedEventArgs class is marked internal and I have not figured out how to create the objects yet. Need more sleep and coffee;
  • SerialPort.Open() is not a virtual method, therefore I have hidden the method in order to add the thread creation. Keep in this in mind: http://stackoverflow.com/questions/856449/overloading-overriding-and-hiding. The port variable must be declared as EnhancedSerialPort type or the wrong Open() method will get called;

TO DO:

  • test the code running by both Microsoft .NET and MONO

Source code:


// /*
// Copyright 2013 Antanas Veiverys www.veiverys.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// */
//
using System;
using System.IO.Ports;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Threading;

namespace testapp
{

public class EnhancedSerialPort : SerialPort
{
public EnhancedSerialPort () :base()
{
}

public EnhancedSerialPort (IContainer container) : base (container)
{
}

public EnhancedSerialPort (string portName) : base(portName)
{
}

public EnhancedSerialPort (string portName, int baudRate) :base(portName, baudRate)
{
}

public EnhancedSerialPort (string portName, int baudRate, Parity parity) : base(portName, baudRate, parity)
{
}

public EnhancedSerialPort (string portName, int baudRate, Parity parity, int dataBits) : base(portName, baudRate, parity, dataBits)
{
}

public EnhancedSerialPort (string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits):base(portName, baudRate, parity, dataBits,stopBits)
{
}

// private member access via reflection
int fd;
FieldInfo disposedFieldInfo;
object data_received;

public new void Open ()
{
base.Open();

if (IsWindows == false) {
FieldInfo fieldInfo = BaseStream.GetType().GetField("fd", BindingFlags.Instance | BindingFlags.NonPublic);
fd = (int)fieldInfo.GetValue(BaseStream);
disposedFieldInfo = BaseStream.GetType().GetField("disposed", BindingFlags.Instance | BindingFlags.NonPublic);
fieldInfo = typeof(SerialPort).GetField("data_received", BindingFlags.Instance | BindingFlags.NonPublic);
data_received = fieldInfo.GetValue(this);

new System.Threading.Thread(new System.Threading.ThreadStart(this.EventThreadFunction)).Start();
}
}

static bool IsWindows {
get {
PlatformID id = Environment.OSVersion.Platform;
return id == PlatformID.Win32Windows || id == PlatformID.Win32NT; // WinCE not supported
}
}

private void EventThreadFunction( )
{
do
{
try
{
var _stream = BaseStream;
if (_stream == null){
return;
}
if (Poll (_stream, ReadTimeout)){
OnDataReceived(null);
}
}
catch
{
return;
}
}
while (IsOpen);
}

void OnDataReceived (SerialDataReceivedEventArgs args)
{
SerialDataReceivedEventHandler handler = (SerialDataReceivedEventHandler) Events [data_received];

if (handler != null) {
handler (this, args);
}
}

[DllImport ("MonoPosixHelper", SetLastError = true)]
static extern bool poll_serial (int fd, out int error, int timeout);

private bool Poll(Stream stream, int timeout)
{
CheckDisposed (stream);
if (IsOpen == false){
throw new Exception("port is closed");
}
int error;

bool poll_result = poll_serial (fd, out error, ReadTimeout);
if (error == -1) {
ThrowIOException ();
}
return poll_result;
}

[DllImport ("libc")]
static extern IntPtr strerror (int errnum);

static void ThrowIOException ()
{
int errnum = Marshal.GetLastWin32Error ();
string error_message = Marshal.PtrToStringAnsi (strerror (errnum));

throw new IOException (error_message);
}

void CheckDisposed (Stream stream)
{
bool disposed = (bool)disposedFieldInfo.GetValue(stream);
if (disposed) {
throw new ObjectDisposedException (stream.GetType().FullName);
}
}
}

}

Test application:


// /*
// Copyright 2013 Antanas Veiverys www.veiverys.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// */
//
using System;
using System.IO.Ports;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Threading;

namespace testapp
{
class MainClass
{
static EnhancedSerialPort port;
public static void Main (string[] args)
{
port = new EnhancedSerialPort("/dev/ttyUSB0", 9600);
port.DataReceived += HandlePortDataReceived;
port.ReadTimeout = 100;
port.Open();

while (Console.ReadKey(true).KeyChar != 'x'){
port.Write("012");
}
port.Close();
}

static void HandlePortDataReceived (object sender, SerialDataReceivedEventArgs e)
{
while (port.BytesToRead > 0)
{
int bt = port.ReadByte();
Console.WriteLine("{0}", bt);
}
}
}

}

Is it working for you?

21 thoughts on “Mono SerialPort.DataReceived Event Workaround – Using a Derived Class

  1. Tom

    Great article! Your derived class helped me port some of our .NET code to Mono really easily.

    Reply
  2. vksalian

    Hello,

    I have been trying to re-write a .NET based C# application under Monodevelop which required SerialPort implementation. The original SerialPort was giving problem, but your solution solved hanging problem.

    I used it under Ubuntu 12.04 with Monodevelop Ver 4.0 & 5.01 (and also on default Monodevelop) and it worked well for following devices.

    1. Beagleboard Rev. C3.
    2. EZ430 dongle (A Texas Instruments product).
    3. TI-Launchpad.

    Thanks for this beautiful post which is helpful for a lot of people struggling in the implementation.


    Regards,
    VKSALIAN

    Reply
    1. vksalian

      Hello,

      EDIT : I am facing below problem,

      1. Sometimes the sent bytes are echo’d back as data recieved. i.e. recievedData = sentData.
      2. Sometimes it failes to catch all the characters and hence there is a loss of data

      Please suggest if there exists any remedies for this.


      Thanks,
      VKSALIAN

      Reply
      1. Antanas Post author

        Hi,
        you could check your physical connection – may there be a short between TX and RX? Normally the sent bytes should not be received. Try disconnecting your cable and device and just send a bunch of data. If you do not receive anything back, the problem is in your device, or the cable.

        Antanas

  3. vksalian

    Hello,

    Finally, I got the code working. The problem was with UART-USB hardware itself which is not working properly under Linux. So I used FT232R chip to solve this problem. After this, I tested it with the same code and it worked as per my expectations. Thanks for the help.


    Thanks & Regards,
    VKSALIAN

    Reply
  4. Ervin

    Hi Antanas,

    You solution works for me too, thank you. Since you have copyright over, can you make modification on official Mono SerialPort source version?

    Reply
  5. 12sd

    Hello! Thank you for a great class.
    So what about event args which’ constructor is marked internal?
    Have you solved this?

    Thanks!

    Reply
  6. Ervin

    Hi Antanas,
    Thank you for your solution it works for me also. Since you have copyright did you think to publish your solution into official Mono framework?

    Reply
    1. Antanas Post author

      Hi Ervin,
      I do not believe this should go into Mono framework. This is just a workaround that seems to work with older Mono versions without patching them. The missing functionality should be implemented properly, but I had not figured out how at the time. Unfortunately I don’t work on the project that needed Mono anymore, so this is not a pressing issue for me.

      Reply
      1. Ervin

        Thank you for your reply. Ok I agree with you about implementing your workaround into the Mono. Even trunk version of Mono don’t have right implementation of OnDataReceived event so this issue is still active.

        Can I use your code with your name in Firmata project (https://github.com/SolidSoils/Arduino) and make pull request?

      2. Antanas Post author

        Hi Ervin,
        yes, of course you can use it in your project.

  7. Bob ONeil

    Are there any updates to the concepts described in this article over the past two years, for example different approaches and/or updates to Mono under Linux.

    Reply
  8. Bob Herzberg

    When running in a Windows form under xinit the window manager doesn\’t exit when the application terminates. When running with the standard SerialPort it goes back to the command prompt. Any ideas why this might happen? Thanks.

    Reply
    1. Antanas Post author

      Sorry about the late reply, the spam filter got your comment.
      I would suspect that the thread does not terminate. Do you close the port before exiting the main process?

      Reply
  9. Giovanni Garcia

    Hello Antanas, your solution works perfectly in a Raspberry.

    I’m developing a program that scans a sensor array trough Raspberry serial port . I begin to develop in C# and using Mono on Raspbian and works until the serial reception, because I was using an event handler for reception. Later I found that serial port over Mono doesn’t work with event handler UNTIL I found your solution.

    Just I have to replace with your class, EnhancedSerialPort, and works like it was over Windows.

    Thanks a lot.

    Giovanni G

    Reply
  10. Brett

    Just dropping a note to say thanks! Saved me a bunch of messing around trying to get something going on a Raspberry Pi.

    Reply
  11. ji

    Hi guys,

    I am very curious if anyone can help me out..
    First of, thanks for the great work! Pleas keep it up!

    Now to my problem:
    Looking for an event handler that works with mono, i found your code and was able to use it out of the box on a Mac System and am really greatfull for that.

    Running this on a windows machine in Mono develop also works great!! Out of curiosity i put the script in Unity 3d to read serial data from there, too (unity beeing multiplatform and using mono 4.6 for coding/building). While it ran out of the box on Mac, nothing works on windows -> same code, but no data…
    Has anyone any idea what to do/encountered similiar poblems?

    Reply
  12. FooFarley

    Recently started doing some mono developing. I ported an older serial port event driven windows program with the use of your EnhancedSerialPort. Works perfectly. Thank you … with one exception… When running in Mono, the program hangs when I go to close it. It just stops responding. I need to kill it from the command line.

    I’ve tried putting a call to Dispose() in the FormClosing() event, but, to no avail.

    Any tips or advice is much appreciated.

    Reply

Leave a Reply

Your email address will not be published.