#c# #serial-port
#c# #последовательный порт
Вопрос:
итак, я новичок в программировании на C #. Я запрограммировал приложение C # Forms в Visual Studio для связи с устройством / инициализации устройства через последовательный порт. Обмен данными между устройствами осуществляется в кодировке COBS, поэтому нет 0x00 байт, кроме как в конце каждого сообщения. Отправленные и полученные сообщения имеют разную длину.
На данный момент моя проблема заключается в том, что сообщения, которые я получаю, не являются полными или начинаются с середины сообщения, поэтому я не могу инициировать отправленные сообщения с определенным значением в полученных сообщениях. Вы можете определить конец сообщения с полученным 0x00 (0x00 означает конец сообщения в данных в кодировке COBS)
Итак, что мне нужно, так это что-то для обработки полного сообщения и помещения его в массив байтов для анализа, т. е. байт [11] для определенного значения.
Вот что я сделал до сих пор:
private bool b_portopen = false;
private byte[] b_rcv_buffer = new byte[256];
private void button1_Click(object sender, EventArgs e) {
//InitTimer();
if (b_portopen == false)
{
serialPort1.PortName = comboBox1.SelectedItem.ToString();
serialPort1.DataReceived = new SerialDataReceivedEventHandler(DataReceivedHandler);
serialPort1.Open();
b_portopen = true;
button1.Text = "Close";
button2.Enabled = true;
Console.WriteLine("Serial Port Opened");
}
else if (b_portopen == true)
{
serialPort1.Close();
b_portopen = false;
button1.Text = "Open";
button2.Enabled = false;
Console.WriteLine("Serial Port Closed");
}
}
private async void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
serialPort1.Read(b_rcv_buffer, 0, 256);
//serialPort1.Read(b_rcv_buffer1, 11, 2);
richTextBox1_receive.Invoke(new Action(() =>
{
richTextBox1_receive.AppendText(BitConverter.ToString(b_rcv_buffer) "n");
richTextBox1_receive.ScrollToCaret();
}));
switch (b_rcv_buffer[10])
{
case b_state_startup:
do something
case b_state_be_start_conf:
do something
case b_state_keepalive_conf:
do something
case b_state_unprepare_conf:
do something
case b_state_prepare_conf:
do something
}
}
Комментарии:
1. Возможно, измените ваш DataReceivedHandler, чтобы он просто буферизовал входящие данные (добавляя к текущему буферу) и имел отдельный поток, обрабатывающий этот буфер, проверяющий наличие завершенных команд и удаляющий связанные данные из буфера. Возможно, используйте что-то вроде потокобезопасного класса, такого как ConcurrentQueue, для буфера, чтобы входящие данные можно было безопасно добавлять во время обработки очереди.
2. Спасибо за ответ. Как вы думаете, каким был бы наилучший способ добавления полученных данных? Если я использую массив байтов для заполнения входящими данными, в какой-то момент он будет заполнен, и я также не смогу вырезать из него полные сообщения. Итак, мне нужно использовать какой-то динамический массив, в котором я могу хранить простые байтовые значения и вырезать их с самого начала, если есть полное сообщение. Существует ли какой-либо тип данных, подобный этому?
3. Я предложил ConcurrentQueue<T> в качестве возможности — использовать Enqueue для постановки в очередь входящих данных побайтно и использовать метод Contains extension для поиска байта 0x00. Обратите внимание, что могут быть классы получше для использования — это была моя первая мысль, поскольку это потокобезопасно.
4. @PaulF:
ConcurrentQueue
не обеспечивает адекватной синхронизации между потоками для этой цели. (Он синхронизирует каждый байт отдельно, что означает, что вы можете получать несколько входящих сообщений, смешанных вместе в очереди)
Ответ №1:
Итак, я нашел решение с использованием ConcurrentQueue:
ConcurrentQueue<byte> b_rcv_buffer = new ConcurrentQueue<byte>();
private Timer timer2;
public void InitTimer()
{
timer2 = new System.Windows.Forms.Timer();
timer2.Tick = new EventHandler(timer2_Tick);
timer2.Interval = 1; // in miliseconds
timer2.Start();
}
private async void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
int bytes = serialPort1.BytesToRead;
byte[] buf = new byte[bytes];
serialPort1.Read(buf, 0, serialPort1.BytesToRead);
for(int i = 0; i < buf.Length; i )
{
b_rcv_buffer.Enqueue(buf[i]); //Enqueue every received Byte in Concurrentqueue
}
}
private async void timer2_Tick(object sender, EventArgs e)
{
if (b_rcv_buffer.Contains<byte>(0x00))
{
byte[] array = b_rcv_buffer.ToArray();
richTextBox1_receive.Invoke(new Action(() =>
{
richTextBox1_receive.AppendText(BitConverter.ToString(array) "n");
//richTextBox1_receive.ScrollToCaret();
}));
byte ignored;
while (b_rcv_buffer.TryDequeue(out ignored));
}
Комментарии:
1. Это неверно — если во время выполнения первого
Enqueue
цикла получено больше данных,DataReceivedHandler
он будет выполняться в нескольких потоках одновременно и буферы данных будут чередоваться.DataReceived
Событие не вызывает ничего, кроме проблем. Просто создайте одинasync
цикл, который вызываетawait port.BaseStream.ReadAsync(...);
, обрабатывает данные, повторяет. Таким образом, каждый буфер полностью обрабатывается в полученном порядке. В качестве бонуса это экономит потоки, позволяет избежать других условий гонки, может взаимодействовать с пользовательским интерфейсом безInvokeRequired
, не требуетConcurrentQueue
и в целом более эффективен.