Anthony
Anthony

Reputation: 811

C# serial port sometimes missing data

Hi i have create a program in C# to receive a string of ASCII the string is an XML markup.

The computer i receive the data from i have no control over and does not accept a response it sends out data on the COM port about every 10 mins

The Console app i have collects and stores this data but it does not always work i would say about 50% of the time data is missing like a packet or byte was lost and the XML string wont read into XmlDocument

I have been trying for about a week now to make this more stable but this is my first time in C# and would like some help is there anyway to improve this.

CODE

  class SerialPortProgram : IDisposable
  {
    // Create the serial port with basic settings
    private SerialPort port = new SerialPort("COM1",
       115200, Parity.None, 8, StopBits.One);

    string sBuffer = null;
    string filePath1 = @"C:\Data\data1.xml";
    string filePath2 = @"C:\Data\data2.xml";

    [STAThread]
    static void Main(string[] args)
    {
        // Instatiate this class
        new SerialPortProgram();

    }

    private SerialPortProgram() 
    {
        Console.WriteLine("Started Data Monitoring:");

        //Attach a method to be called when there
        //is data waiting in the port's buffer
        port.ReadBufferSize = 20971520;
        port.ReceivedBytesThreshold = 1;
        port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived);

        //Begin communications
        port.Open();

        //Enter an application loop to keep this thread alive
        Application.Run();

    }

    private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        //Show all the incoming data in the port's buffer
        SerialPort sp = (SerialPort)sender;
        sBuffer += sp.ReadExisting();

        if (sBuffer.Length > 26000) // check the file size
        {
            if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
            {
                Console.WriteLine("Found: Processing...");
                //Thread.Sleep(1000);
                ProcessXML();
                sBuffer = null;
                Console.WriteLine("Done!");
                DateTime now = DateTime.Now;
                Console.WriteLine(now);
                Console.WriteLine("Monitoring...");
            }
            else
            {
                Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
            }
        }
        else
        {
            Console.WriteLine("Receiving Data: " + sBuffer.Length);
        }
    }
 private void ProcessXML()
    {
        XmlDocument xmlDoc = new XmlDocument();
        try
        {
            xmlDoc.LoadXml("<wrapper>" + sBuffer + "</wrapper>");
            int index = 0;
            XmlNodeList xnl = xmlDoc.SelectNodes("wrapper/xml");
            foreach (XmlNode node in xnl)
            {
                // Console.WriteLine(index.ToString());
                if (index == 0)// xml file 1
                {
                    using (XmlReader r = new XmlNodeReader(node))
                    {
                        DataSet ds = new DataSet();
                        ds.ReadXml(r);
                        ds.WriteXml(filePath1);
                        Console.WriteLine("NEW Data1");
                        ds.Dispose();
                        var db = new Database();
                        db.SaveMetersToDatabase(ds);
                    }
                }
                else if (index == 1)// xml file 2
                {
                    using (XmlReader r1 = new XmlNodeReader(node))
                    {
                        DataSet dst = new DataSet();
                        dst.ReadXml(r1);
                        dst.WriteXml(filePath2);
                        Console.WriteLine("NEW Data2");
                        dst.Dispose();
                    }
                }

                index++;
            }
        }
        catch
        {
            Console.WriteLine("Error: in data");
            try
            {
                string now = DateTime.Now.ToString("yyyyMMddHHmmss");
                System.IO.File.WriteAllText(@"C:\Data\log" + now + ".xml", "<wrapper>" + sBuffer + "</wrapper>");
            }
            catch
            {
                Console.WriteLine("Failed to write to log");
            }
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing && port != null)
        {
            port.Dispose();
            port = null;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

  }
}

UPDATED CODE:

  private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        //Show all the incoming data in the port's buffer
        SerialPort sp = (SerialPort)sender;
        sBuffer += sp.ReadExisting();

        new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            if (sBuffer.Length > 25000) // check the file size
            {
                if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
                {
                    Console.WriteLine("Found: Processing...");
                    Task.Run(() =>
                    {
                        ProcessXML();
                        sBuffer = null;
                        Console.WriteLine("Done!");
                        DateTime now = DateTime.Now;
                        Console.WriteLine(now);
                        Console.WriteLine("Monitoring...");
                    });
                }
                else
                {
                    Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
                }
            }
            else
            {
                Console.WriteLine("Receiving Data: " + sBuffer.Length);
            }
        }).Start();
    }

UPDATE

still having this issue could it be possible that the sending computer sometimes does not send all the data or there is packet loss i have tried everything i have tried this new code below using Serial Port BaseStream BeginRead

  private SerialPortProgram() 
    {
        Console.WriteLine("Started Data Monitoring:");

        //Attach a method to be called when there
        try
        {
            Port.BaudRate = 115200;
            Port.DataBits = 8;
            Port.Parity = Parity.None;
            Port.StopBits = StopBits.One;
            Port.Handshake = Handshake.None;
            Port.DtrEnable = true;
            Port.NewLine = Environment.NewLine;
            Port.ReceivedBytesThreshold = 2048;
            Port.Open();

            byte[] buffer = new byte[35000];
            Action StartRead = null;
            StartRead = () => {
                Port.BaseStream.BeginRead(buffer, 0, buffer.Length, async (IAsyncResult ar) =>
                {
                    try
                    {
                        int actualLength = Port.BaseStream.EndRead(ar);
                        byte[] received = new byte[actualLength];
                        Buffer.BlockCopy(buffer, 0, received, 0, actualLength);
                        await Task.Run(() =>
                        {
                            sBuffer += Encoding.ASCII.GetString(received);
                            CheckBuffer();
                        });
                    }
                    catch (Exception exc)
                    {
                        Console.WriteLine(exc);
                    }
                    StartRead();
                }, null);
            };
            StartRead();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error accessing port." + ex);
            Port.Dispose();
            Application.Exit();
        }

        //Enter an application loop to keep this thread alive
        Application.Run();
    }

    private void CheckBuffer()
    {
        if (sBuffer != null && sBuffer.Length > 26000) // check the file size
        {
            if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
            {
                new Thread(async () =>
               {
                   Console.WriteLine("Found: Processing...");

                   await Task.Run(() => ProcessXML());

                   sBuffer = null;
                   Console.WriteLine("Done!");
                   DateTime now = DateTime.Now;
                   Console.WriteLine(now);
                   Console.WriteLine("Monitoring...");
               }).Start();
            }
            else
            {
                Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
            }
        }
        else if (sBuffer != null && sBuffer.Length > 0)
        {
            Console.WriteLine("Receiving Data: " + sBuffer.Length);
        }

    }

Upvotes: 2

Views: 4457

Answers (2)

Ozgur Saklanmaz
Ozgur Saklanmaz

Reputation: 564

Maybe it's not exactly the desired answer,

but I am sharing the solution to the data evasion that I encountered. Actually, I was not escaping data. In the SerialPort default settings, ReadBufferSize is set to 4096. When data packet was over 4096 Bytes it was ignoring other data and was not displayed in DataReceived.

Setting the ReadBufferSize to a value that suited me fixed the issue.

Upvotes: 1

Peuczynski
Peuczynski

Reputation: 4733

This is my implementation. You should get the general idea

First - get the data from the connection

internal class SerialListener : Listener
{
    private SerialPort sp;
    private ConnectionInfo _connection;
    private Timer _listenerTimer;
    private bool should_exit = false;
    private bool busy = false;
    ConcurrentQueue<byte> fifo_peekonly = null;
    BlockingCollection<byte> fifo_queue = null;

    public SerialListener(ConnectionInfo connection)
        : base(connection)
    {
        _connection = connection;
        InitSerialConnection();
    }

    private void InitSerialConnection()
    {
        sp = new SerialPort(_connection.ifname_ip);
        sp.BaudRate = _connection.baudrate_port;
        sp.Parity = _connection.parity;
        sp.DataBits = _connection.charactersize;
        sp.StopBits = _connection.stopbits;
        sp.Handshake = _connection.flowcontrol;
        sp.DtrEnable = true;
        sp.ReadTimeout = 100;
        sp.Open();
        fifo_peekonly = new ConcurrentQueue<byte>();
        fifo_queue = new BlockingCollection<byte>(fifo_peekonly);
        sp.DataReceived += (sender, e) => 
        { 
            byte[] buffer = new byte[sp.BytesToRead];
            if (!sp.IsOpen)
            {
                throw new System.InvalidOperationException("Serial port is closed.");
            }
            sp.Read(buffer,0,sp.BytesToRead);
            foreach (var b in buffer)
                fifo_queue.Add(b);
        };
    }

    public override byte GetByteFromDevice()
    {
        byte b;
        b = fifo_queue.Take();
        return b;
    }

    public override byte PeekByteFromDevice ()
    {
        byte b;
        bool peeked = false;
        do {
            peeked = fifo_peekonly.TryPeek(out b);
            if (!peeked)
                Thread.Sleep(100);
        } while (!peeked);
        return b;
    }

    public override void Close()
    {
        base.Close();
        sp.DiscardInBuffer();
        sp.DiscardOutBuffer();
        Thread.Sleep(3000);
        sp.Close();
    }
}

Then implement another class that will call the GetByteFromDevice method. Unfortunatelly the code is too specific to bo posted here as it would be only confusing for you.

So - separate the logic, and push all the bytes in real time, and in a separate thread (some loop, timer etc.) get the bytes read previously and then analyze them.

Also the method you are using for xml detection is not optimal in my opinion. I have implemented detecting the start and end sequence in the stream. Let me know if you'd like to look at the code

Upvotes: 1

Related Questions