Serial.IO.Ports issue when using mono, works with dotnet core 3.1 / arm / raspberry pi 4

Viewed 601

Got a strange problem when using mono vs dotnet on a Raspberry Pi 4 and Serial I/O. When compiled with VS 2019 and running it using dotnet, it works as expected, but when compiled using mono, I never receive any data. For dotnet, I also used dotnet add package System.IO.Ports

These are the two files that make up the project:

using System;
using System.Text;
using SerialPortListener.Serial;

namespace radio
{
    class Program
    {
        private static SerialPortManager _spManager;

        static void Main(string[] args)
        {
            Console.WriteLine("Starting");

            _spManager = new SerialPortManager();
            _spManager.NewSerialDataRecieved += new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved);
            _spManager.StartListening();

            Console.WriteLine("Waiting here forever...");
            while (true) ;
        }

        static void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e)
        {
            string str = Encoding.ASCII.GetString(e.Data);
            Console.WriteLine(str);
        }
    }
}
using System;
using System.IO.Ports;

namespace SerialPortListener.Serial
{
    /// <summary>
    /// Manager for serial port data
    /// </summary>
    public class SerialPortManager : IDisposable
    {
        public SerialPortManager()
        {
        }

        ~SerialPortManager()
        {
            Dispose(false);
        }

        #region Fields
        private SerialPort _serialPort;
        public event EventHandler<SerialDataEventArgs> NewSerialDataRecieved;
        #endregion

        #region Event handlers
        void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Console.WriteLine("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

            int dataLength = _serialPort.BytesToRead;
            byte[] data = new byte[dataLength];
            int nbrDataRead = _serialPort.Read(data, 0, dataLength);
            if (nbrDataRead == 0)
                return;

            NewSerialDataRecieved?.Invoke(this, new SerialDataEventArgs(data));
        }

        #endregion

        #region Methods
        /// <summary>
        /// Connects to a serial port defined through the current settings
        /// </summary>
        public void StartListening()
        {
            if (_serialPort != null && _serialPort.IsOpen)
                _serialPort.Close();

            _serialPort = new SerialPort("/dev/ttyUSB0", 115200, Parity.None, 8, StopBits.One)
            {
                Handshake = Handshake.None,
                DtrEnable = false,
                RtsEnable = false,
                ReadTimeout = 400,
            };
            _serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            _serialPort.Open();
        }

        /// <summary>
        /// Closes the serial port
        /// </summary>n
        public void StopListening()
        {
            _serialPort.Close();
        }

        // Call to release serial port
        public void Dispose()
        {
            Dispose(true);
        }

        // Part of basic design pattern for implementing Dispose
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            }

            if (_serialPort != null)
            {
                if (_serialPort.IsOpen)
                    _serialPort.Close();

                _serialPort.Dispose();
            }
        }

        #endregion
    }

    /// <summary>
    /// EventArgs used to send bytes recieved on serial port
    /// </summary>
    public class SerialDataEventArgs : EventArgs
    {
        public SerialDataEventArgs(byte[] dataInByteArray)
        {
            Data = dataInByteArray;
        }

        /// <summary>
        /// Byte array containing data from serial port
        /// </summary>
        public byte[] Data;
    }
}

Compiling it using mcs Program.cs SerialPortManager.cs and mono Program.exe, I get this:

/dab/source/radio# mono Program.exe Starting Waiting here forever...

Using dotnet radio.dll, it works fine. Here is a small extract:

/dab/empeg# dotnet radio.dll Starting Waiting here forever... AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
serial_notify_thread.cpp: 180:@@ #70008000 0:01:46
serial_notify_thread.cpp: 180:@@ #70008000 0:04:47
serial_notify_thread.cpp: 180:@@ #70008000 0:01:46
serial_notify_thread.cpp: 180:@@ #70008000 0:04:48
serial_notify_thread.cpp: 180:@@ # Poll wait Poll fail

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Not sure what I've done wrong, or why it works with dotnet, and not mono. Any ideas as I need to use mono as dotnet is not available on Raspberry Pi Zero's.

John

1 Answers

Full fix:

using System;
using System.Text;
using SerialPortListener.Serial;

namespace radio
{
    class Program
    {
        private static SerialPortManager _spManager;

        static void Main(string[] args)
        {
            Console.WriteLine("Starting");

            _spManager = new SerialPortManager();
            _spManager.NewSerialDataRecieved += new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved);
            _spManager.StartListening();

            Console.WriteLine("Waiting here forever...");
            while (true) ;
        }

        static void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e)
        {
            string str = Encoding.ASCII.GetString(e.Data);
            Console.WriteLine(str);
        }
    }
}
using System;
using System.IO.Ports;

namespace SerialPortListener.Serial
{
    /// <summary>
    /// Manager for serial port data
    /// </summary>
    public class SerialPortManager : IDisposable
    {
        public SerialPortManager()
        {
        }

        ~SerialPortManager()
        {
            Dispose(false);
        }

        #region Fields
        private EnhancedSerialPort _serialPort;
        public event EventHandler<SerialDataEventArgs> NewSerialDataRecieved;
        #endregion

        #region Event handlers
        void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Console.WriteLine("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

            int dataLength = _serialPort.BytesToRead;
            byte[] data = new byte[dataLength];
            int nbrDataRead = _serialPort.Read(data, 0, dataLength);
            if (nbrDataRead == 0)
                return;

            NewSerialDataRecieved?.Invoke(this, new SerialDataEventArgs(data));
        }

        #endregion

        #region Methods
        /// <summary>
        /// Connects to a serial port defined through the current settings
        /// </summary>
        public void StartListening()
        {
            if (_serialPort != null && _serialPort.IsOpen)
                _serialPort.Close();

            _serialPort = new EnhancedSerialPort("/dev/ttyUSB0", 115200, Parity.None, 8, StopBits.One)
            {
                ReadTimeout = 400,
            };
            _serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            _serialPort.Open();
        }

        /// <summary>
        /// Closes the serial port
        /// </summary>n
        public void StopListening()
        {
            _serialPort.Close();
        }

        // Call to release serial port
        public void Dispose()
        {
            Dispose(true);
        }

        // Part of basic design pattern for implementing Dispose
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            }

            if (_serialPort != null)
            {
                if (_serialPort.IsOpen)
                    _serialPort.Close();

                _serialPort.Dispose();
            }
        }

        #endregion
    }

    /// <summary>
    /// EventArgs used to send bytes recieved on serial port
    /// </summary>
    public class SerialDataEventArgs : EventArgs
    {
        public SerialDataEventArgs(byte[] dataInByteArray)
        {
            Data = dataInByteArray;
        }

        /// <summary>
        /// Byte array containing data from serial port
        /// </summary>
        public byte[] Data;
    }
}
// /*
// 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;

namespace SerialPortListener.Serial
{
    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);
            }
        }
    }
}
Related