C#等待serialPort_DataReceived事件的反馈

时间:2017-07-12 03:30:22

标签: c# winforms user-interface serial-port timeout

我正在使用Winform GUI与串口上的微控制器通信,我正在开发。

我根据预定义的协议发送一组命令,并从微控制器接收反馈字符串。

我想知道在发送命令后是否有一种简单的方法可以等待某个反馈。

E.g。

  1. 发送命令
  2. 等待设定的时间(可能是几秒到几分钟)
  3. 如果及时显示反馈并继续发出下一个命令/操作
  4. 如果未及时收到反馈,将触发超时并显示失败消息。如果数据及时返回,则应立即停止等待方法并继续执行下一步操作。我也不想在等待反馈的同时阻止用户界面。

    我使用以下代码接收数据。

        delegate void SetTextCallback(string text);
    
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                string receivedData = serialPort1.ReadExisting();
                SetText(receivedData);
            }
            catch (IOException exception)
            {
                MessageBox.Show(exception.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    
        private void SetText(string text)
        {
            if (this.textBoxReceive.InvokeRequired)
            {
                SetTextCallback d = SetText;
                this.Invoke(d, new object[] { text });
                //this.Invoke(new Action(() => { this.textBoxReceive.AppendText(text); }));
    
            }
            else
            {
                if (text.Length > 15)
                {
                    CheckPosition(text); //To check for a position number from the feedback string
                }
                this.textBoxReceive.AppendText(text);
    
            }
        }
    

    这是我的写法。

        private void SendCommand(int move, int trigger)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    string cmd = string.Empty;
                    //Format: { “move”: 0, “trigger”: 0}
                    cmd = "{ " + "\"move\": " + move + ","
                          + " \"trigger\": " + trigger 
                          + " }"
                          + Environment.NewLine;
                    textBoxReceive.AppendText("Send:" + cmd + Environment.NewLine); // Display sent command
                    serialPort1.DiscardOutBuffer();
                    serialPort1.DiscardInBuffer();
                    serialPort1.Write(cmd);
    
                }
                else if (serialPort1.IsOpen != true)
                {
                    MessageBox.Show(@"Lost COM Port.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
    
            }
            catch (IOException e)
            {
                MessageBox.Show(e.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
            }
    
        }
    

    我有一个单击按钮的方法,我一直在努力解决延迟(///开始等待)。

        private void buttonDet_Click(object sender, EventArgs e)
        {
    
            ResetPositionMark();
            serialPort1.DiscardInBuffer(); 
            //Position 1 at Initial Stage
            textBoxStatus.AppendText("Position 1: Init/Home." + Environment.NewLine);
            SendCommand(0,1);   //Trigger LED1
            textBoxStatus.AppendText("LED1 triggered." + Environment.NewLine);
            Thread.Sleep(200);
            ///Camera Capture
    
            //============== Position 2 ==============
            SendCommand(2,0);   //Move to Position 2
            textBoxStatus.AppendText("Moving to Position 2..." + Environment.NewLine);
            **///Start waiting**
            if (timeout)
            {
                textBoxStatus.AppendText("Position 2 Timeout reached." + Environment.NewLine);
            }
            else
            {
                textBoxStatus.AppendText("Data received in time." + Environment.NewLine);
                textBoxStatus.AppendText("Position 2 OK." + Environment.NewLine);
                SendCommand(0, 2);  //Trigger LED2 once the motor reaches the position 2
                textBoxStatus.AppendText("LED2 triggered." + Environment.NewLine);
            }
    
    
            ///Camera Capture
    
            //============== Position 3 ==============
            SendCommand(3,0);   //Move to Position 3
            textBoxStatus.AppendText("Moving to Position 3..." + Environment.NewLine);
            **///Start waiting**
            if (timeout)
            {
                textBoxStatus.AppendText("Position 3 Timeout reached." + Environment.NewLine);
            }
            else
            {
                textBoxStatus.AppendText("Data received in time." + Environment.NewLine);
                textBoxStatus.AppendText("Position 3 OK." + Environment.NewLine);
                SendCommand(0, 3);  //Trigger LED3 once the motor reaches the position 2 
                textBoxStatus.AppendText("LED3 triggered." + Environment.NewLine);
            }
    
            ///Camera Capture
    
    
            SendCommand(1, 0);  //Move back to Home position (position 1)
            textBoxStatus.AppendText("Moving Home to Position 1..." + Environment.NewLine);
            **///Start waiting**
            if (timeout)
            {
                textBoxStatus.AppendText("Back to Home Timeout!" + Environment.NewLine);
            }
            else
            {
                textBoxStatus.AppendText("Data received in time." + Environment.NewLine);
                textBoxStatus.AppendText("Home now." + Environment.NewLine);
            }
        }
    

    我不熟悉线程和ManualResetEvent等。

    请帮助了解如何最好地等待数据,最好是使用代码示例。

    非常感谢。

1 个答案:

答案 0 :(得分:1)

这是一个简单的解决方案:https://1drv.ms/u/s!AnSTW4R3pQ5uitAnyiAKTscGPHpxYw

这个想法是你在开始发送命令时创建一个单独的线程,  这是通过:

完成的
            Task.Factory.StartNew(() => {
        }, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

这里的参数主要是为自己说话:

  1. 是我想在此线程/任务上执行的函数
  2. 取消令牌,以便在我不再需要发送命令时终止线程
  3. " LongRunning"选项表示这将是长时间运行的任务。您可以在其上阅读更多内容here
    1. 传递默认调度程序。
  4. 接下来,您需要创建一个AutoResetEvent实例。它的工作原理你可以在MSDN上阅读。但简而言之,它是一个具有两个状态,开放和关闭的开关。默认情况下,您希望它被关闭,这是构造函数中的false参数。 在来自串口(DataReceived)的事件处理程序中,您希望"打开"的AutoResetEvent。所以你这样做:

    dataReceivedEvent.Set(); 
    

    现在,当您发出命令时,等待AutoResetEvent被打开"你指定一个你愿意等待的时间,如下:

    var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
    

    这意味着如果在3秒内未打开AutoResetEvent,则停止等待并报告失败。基本上,如果它没有在给定的时间范围内打开,则返回false,如果没有,则返回true。因为它是" Auto"重置事件它将自动"关闭"完成等待后,您不必手动重置。

    其余的都是你已经拥有的。使用invoke与UI交互并读取/发送命令。

     public class Communicator
    {
        CancellationTokenSource _canecellationTokenSource = new CancellationTokenSource();
        List<Command> _scenario = new List<Command>(6)
        {
            Command.Trigger(1),
            Command.MoveTo(2),
            Command.Trigger(2),
            Command.MoveTo(3),
            Command.Trigger(3),
            Command.MoveTo(1)
        };
    
        public void Start(ListBox feedbackControl)
        {
            Task.Factory.StartNew(() => {
                var dataReceivedEvent = new AutoResetEvent(false);
                var ct = _canecellationTokenSource.Token;
                var controller = new DummyMicrocontroller();
                DataReceivedEventHandler onDataReceived = (cmd) => { dataReceivedEvent.Set(); };
                controller.DataReceived += onDataReceived;
                foreach (var cmd in _scenario)
                {
                    if (ct.IsCancellationRequested)
                    {
                        AddItemSafe(feedbackControl, $"Operation cancelled...");
                        break;
                    }
    
                    AddItemSafe(feedbackControl, cmd.GetMessage(Command.MessageType.Info));
                    controller.Send(cmd);
                    var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
                    var messageType = succeeded ? Command.MessageType.Success : Command.MessageType.Error;
                    AddItemSafe(feedbackControl, cmd.GetMessage(messageType));
                }
    
                AddItemSafe(feedbackControl, $"Finished executing scenario.");
    
                controller.DataReceived -= onDataReceived;
    
            }, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    
        }
    
        public void Stop(ListBox feedbackControl)
        {
            AddItemSafe(feedbackControl, $"Attempting to cancel...");
            _canecellationTokenSource.Cancel();
        }
    
        private void AddItemSafe(ListBox feedbackControl, object item)
        {
            if (feedbackControl.InvokeRequired)
            {
                feedbackControl.Invoke((MethodInvoker)delegate { AddItemSafe(feedbackControl, item); });
            }
            else
            {
                feedbackControl.Items.Add(item);
            }
        }
    }
    

    UI保留在自己的线程上,不会受到影响。 由于我没有可用的微控制器,我不得不写一个虚拟模拟器:)