我正在使用我正在开发的Winform GUI与串行端口上的微控制器进行通信。
我根据预定义的协议发送一组命令,并从微控制器接收反馈字符串。
我想知道是否有一种简单的方法可以在发送命令后等待某个反馈。
例如
- 发送命令
- 等待设定的时间量(可能是几秒钟到几分钟(
- 及时显示反馈并继续发出下一个命令/操作
如果未及时收到反馈,将触发超时并显示失败消息。如果数据及时返回,则应立即停止等待方法并继续下一步操作。我也不想在等待反馈时阻止 UI。
我使用以下代码来接收数据。
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);
}
}
我不精通线程和手动重置事件等。
请帮助了解如何最好地等待数据,最好是代码示例。
多谢。
这是一个简单的解决方案: https://1drv.ms/u/s!AnSTW4R3pQ5uitAnyiAKTscGPHpxYw
这个想法是,当你开始发送命令时,你创建一个单独的线程, 这是通过以下方式完成的:
Task.Factory.StartNew(() => {
}, _canecellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
这里的参数主要不言自明:
- 是我想在这个线程/任务上执行的函数
- 取消令牌,以便我可以在不再需要发送命令时终止线程
- "长时间运行"选项表示这将是长时间运行的任务。您可以在此处阅读更多内容。
- 传递默认调度程序。
接下来,您需要做的是创建自动重置事件的实例。它是如何工作的,你可以在MSDN上阅读。但简而言之,它是一个具有两种状态的开关,打开和关闭。默认情况下,您希望将其关闭,这就是构造函数中的 false 参数的用途。 在串行端口(DataReceived(的事件处理程序中,您希望"打开"AutoResetEvent。所以你这样做:
dataReceivedEvent.Set();
现在,当您发出命令时,您等待 AutoResetEvent 被"打开",并指定您愿意等待的时间,如下所示:
var succeeded = dataReceivedEvent.WaitOne(TimeSpan.FromSeconds(3));
这意味着,如果在 3 秒内未打开自动重置事件,请停止等待并报告失败。基本上,如果在给定的时间范围内未打开,则返回 false,如果打开,则返回 true。由于它是"自动"重置事件,它将在完成等待后自动"关闭"自己,因此您不必手动重置。
其余的都是你已经拥有的。使用调用与 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 保留在其自己的线程上,不受影响。 由于我没有可用的微控制器,我不得不编写一个虚拟模拟器:)