lundi 11 mai 2015

Serial Port - reading data/updating canvas element

I've been having issues for a few weeks now reading in data from a serial port and updating a canvas element based on the message being received. The canvas element to be updated is a small image, that is supposed to rotate to a certain angle based on the third section of the message being received. I'm not sure what's going wrong, it seems that a full message isn't always being received. I'm going to give as much detail about the port and data. Structure - 8 data bits, 1 start bit, 1 stop bit, no parity. Message frequency is 15 Hz (the number of lines written every second). The default baud rate is 9,600.

There are five sections: 1. Section1 - two decimal places. 2. Section2 - two decimal places. 3. Angle - multiplied by 10 – i.e. 256.0 degrees is shown as 2560. 4. Section4 multiplied by -100 – i.e. 6.66 degrees is -666. 5. Section5 multiplied by 100 – i.e. 55.5 degrees is 555. A message starts with a colon symbol (:) and ends with . A field that contains asterisks, '***', indicates that no value is defined for that field.

An example to illustrate:

Column 
1 15 22 29 36 43 
1.00 *** 0 0 0 
: 1.00 20.20 2460 0 0
: 2.40 20.45 2460 10000 -10000 
: 3.00 20.45 2355 1000 554

I have the latest message received being shown at the top of the window to ensure the user that data is being received, but I noticed the message is only show bits and pieces of what it should be, and thus messing up the rotation of the canvas element. So for instance, the message might be : 20 500 at first, and then get a complete message.

Here's a screenshot of the data being sent: Data received

Here's relative code in my MainWindow.cs:

    private Port port = new Port();
    private double rovHeading;
    Point rotate_Origin = new Point(0.5, 0.5);
    public string LastCOMMessage
    {
        get { return (string)this.GetValue(LastCoMMessageProperty); }
        set { this.SetValue(LastCoMMessageProperty, value); }

    }
    private void Connect_Port(object sender, RoutedEventArgs e)
    {
        if (port.Status == Port.PortStatus.Disconnected)
        {
            try
            {
                port.Connect("COM1", 9600, false, 250); //When set to false, test data is used on Connect. Last parameter is how often the UI is updated. Closer to 0, the faster it updates.

                //Sets button State and Creates function call on data recieved
                Connect_btn.Content = "Disconnect";

                port.OnMessageReceived += new Port.MessageReceivedHandler(port_OnMessageReceived);
            }

            catch (Exception)
            {
                System.Windows.MessageBox.Show("Port is not available.");
            }
        }

        else
        {
            try // just in case serial port is not open, could also be achieved using if(serial.IsOpen)
            {
                port.Disconnect();
                Connect_btn.Content = "Connect";
            }

            catch
            {
            }
        }
    }

    /// <summary>
    /// Reads in the data streaming from the port, taking out the third value which is the ROV's heading and assigning it to rovHeading.
    /// It then calls orientRovImage.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void port_OnMessageReceived(object sender, Port.MessageEventArgs e)
    {
        LastCOMMessage = e.Message;

        if (rovImage != null && e.Message.EndsWith("\r\n")) //messages end in <CR><LF>. Was having issues where only half a message would be received and updated the heading to wrong number.
        {
            string[] messageComponents = LastCOMMessage.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

            if (messageComponents.Length >= 3)
            {
                double.TryParse(messageComponents[3], out rovHeading);

                if (rovHeading > 0)
                {
                    rovHeading /= 10;

                    orientRovImage();
                }
            }
        }
    }

    /// <summary>
    /// Rotates the ROV icon based on the heading being streamed in.
    /// </summary>
    private void orientRovImage()
    {
        RotateTransform rotateRov = new RotateTransform(rovHeading + angle_Offset);
        rovImage.RenderTransformOrigin = rotate_Origin;
        rovImage.RenderTransform = rotateRov;
    }

Here's my Port.cs:

class Port
{
    private SerialPort serial = null;
    private DispatcherTimer testTimer;
    private string[] testData = new string[] { ": 3.00 20.45 2355   1000 554\r\n", ": 5.00 78.09 1725 3200 121\r\n", ": 9.20 10.12 1492 8820 197\r\n" }; //test data to be sent when liveData is set to false.
    private int testDataIndex = -1;
    private DateTime dateOflastMessageHandled;

    public enum PortStatus
    {
        Connected,
        Disconnected
    }
    public PortStatus Status { get; private set; }
    public bool LiveData { get; private set; }
    public int MillisecondsDelayBetweenMessages { get; private set; }

    public class MessageEventArgs : EventArgs
    {
        public string Message { get; private set; }

        public MessageEventArgs(string message)
        {
            Message = message;
        }
    }
    public delegate void MessageReceivedHandler(object sender, MessageEventArgs e);
    public event MessageReceivedHandler OnMessageReceived;


    private void MessageReceived(string message)
    {
        if (OnMessageReceived == null)
        {
            return;
        }

        OnMessageReceived(this, new MessageEventArgs(message));
    }


    public Port()
    {
        Status = PortStatus.Disconnected;
    }

    public void Connect(string portName, int baudRate, bool liveData, int millisecondsDelayBetweenMessages)
    {
        LiveData = liveData;
        MillisecondsDelayBetweenMessages = millisecondsDelayBetweenMessages;

        Disconnect();

        if (liveData)
        {
            serial = new SerialPort();

            serial.PortName = portName;
            serial.BaudRate = baudRate;
            serial.Handshake = Handshake.None;
            serial.Parity = Parity.None;
            serial.DataBits = 8;
            serial.StopBits = StopBits.One;
            serial.ReadTimeout = 200;
            serial.WriteTimeout = 50;

            serial.Open();

            serial.DataReceived += new SerialDataReceivedEventHandler(Receive);
        }

        else
        {
            testTimer = new DispatcherTimer();
            testTimer.Interval = new TimeSpan(0, 0, 0, 0, 3);
            testTimer.Tick += new EventHandler(testTimer_Tick);
            testTimer.Start();
        }

        Status = PortStatus.Connected;
    }


    private void testTimer_Tick(object sender, EventArgs e)
    {
        if (dateOflastMessageHandled == null || DateTime.Now.Subtract(dateOflastMessageHandled).TotalMilliseconds >= MillisecondsDelayBetweenMessages)
        {
            dateOflastMessageHandled = DateTime.Now;
            MessageReceived(testData[testDataIndex]);

            testDataIndex++;
            if (testDataIndex >= testData.Length)
            {
                testDataIndex = 0;
            }
        }
    }


    private void Receive(object sender, SerialDataReceivedEventArgs e)
    {
        if (dateOflastMessageHandled == null || DateTime.Now.Subtract(dateOflastMessageHandled).TotalMilliseconds >= MillisecondsDelayBetweenMessages)
        {
            dateOflastMessageHandled = DateTime.Now;

            Application.Current.Dispatcher.BeginInvoke(new Action(
                delegate()
                {
                    //MessageReceived(serial.ReadLine());
                    MessageReceived(serial.ReadExisting());
                }));
        }
    }


    public void Disconnect()
    {
        if (testTimer != null)
        {
            testTimer.Stop();
        }

        testDataIndex = 0;

        Status = PortStatus.Disconnected;

        if (serial != null)
        {
            serial.Close();
        }
    }
}

And finally in my MainWindow.xaml, this just shows the last message received:

        <Button Content="Connect" Click="Connect_Port" Name="Connect_btn" />
        <TextBlock Text="{Binding LastCOMMessage, ElementName=this}" />

Any hints or help with this would be much appreciated, as this is the first time I've worked w/ ports. Thanks!

Aucun commentaire:

Enregistrer un commentaire