I am working on a Windows form application, where I send data through buttons, and continuously receive data for updating some gauge values.
The trouble I am facing, is when reading the serial port, if data is coming too fast, my application responds very slow, for example I can have a data rate of 1000Hz when reading.
I have attached a simplified version of my code.
What could cause this application to hang? I read that I need some separate thread? But how to achieve this?
namespace Motor
{
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
delegate void SetTextCallback(string text);
public Form1()
{
InitializeComponent();
ComPort.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadExisting();
if (InputData != String.Empty)
{
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
}
}
private void SetText(string text)
{
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
This is most likely your issue
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
If your serial port is thrashing, its trying to marshal back to the UI thread all the time. Do you really need to update the main UI thread this fast? cant you just do it every second or periodically
In regards to the statement
What could cause this application to hang? I read that I need some
separate thread? But how to achieve this?
SerialPort.DataReceived Event
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object
I.e it is already running on secondary thread, so starting another thread is not going to help you
Update
In reference to your comment
1000hz is the data rate coming from the hardware, its no need for the
GUI to be that fast, but i want the gauges to move smooth.
You can still make them smooth, still at least preventing UI throttling would be your key, try buffering for 200-300 milliseconds or so. I.e just play around with it
Use ReadLine to avoid a chunk Data Recieved.
namespace Motor
{
delegate void SetTextCallback(string text);
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
SetTextCallback st;
public Form1()
{
InitializeComponent();
st = new SetTextCallback(SetText);
}
private void Form1_Load(object sender, EventArgs e)
{
OpenPort();
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadLine();
if (InputData != String.Empty)
{
this.Invoke(st,new object[] { InputData});
}
}
private void SetText(string text)
{
Console.WriteLine(text);//To check the return value
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
There's a lot of return value in this Part InputData;
List of Return Value (Be careful to check this value)
1. OK
2. >
3. + CMGS: ID_NUMBER_HERE
4. ERROR ERROR_NUMBER_HERE
And any others...
Now its's depends on your code when you want to let go or continue the code with that values return.
off course before this code you already opened the PORT. Coz this code doesn't work anymore if the PORT is closed.
GUIDE TO OPEN A PORT
private void OpenPort()
{
private System.IO.Ports.SerialPort ComPort;
ComPort= new System.IO.Ports.SerialPort("COM1");//depends on your
Device COM
ComPort.NewLine = System.Environment.NewLine;
ComPort.BaudRate = 115200;
ComPort.Parity = System.IO.Ports.Parity.None;
ComPort.DataBits = 8;
ComPort.StopBits = System.IO.Ports.StopBits.One;
ComPort.Handshake = System.IO.Ports.Handshake.None;
ComPort.ReadTimeout = 3600000;
ComPort.WriteTimeout = 3600000;
ComPort.Encoding = Encoding.GetEncoding("iso-8859-1");
GC.SuppressFinalize(ComPort);
SerialPortFixer.Execute("COM1");
ComPort.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
ComPort.Open();
GC.SuppressFinalize(ComPort.BaseStream);
ComPort.DtrEnable = true;
ComPort.RtsEnable = true;
}
Hope it helps.
I do not have any example code which is written but i can suggest you to use threads or background workers to read data, it is really easy to understand useage of background workers or threads. You can watch short tutorials. Try to create a button which starts background worker and with that background worker, you can read your data without freezing. Sorry i do not have time to write example but in my opinion these are the topics you should be focusing on (Bakcground workers or Threads)
I hope this helps.
try implement async await
more info:
https://learn.microsoft.com/en-us/dotnet/csharp/async
https://msdn.microsoft.com/en-us/library/hh156513(v=vs.120).aspx
Here's a quick example (although I can't verify it because I don't have the necessary requirements for running the code, but should give an idea):
namespace Motor
{
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
delegate void SetTextCallback(string text);
public Form1()
{
InitializeComponent();
ComPort.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
private async void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
var size = ComPort.ReadBufferSize;
var data = new byte[size];
await ComPort.BaseStream.ReadAsync(data,0,size);
InputData += ComPort.Encoding.GetString(data);
SetText(InputData);
}
private void SetText(string text)
{
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
Related
First I'd like to say sorry for doubleposting. I'm afraid the context and questions I posted before didn't clearify it enough and although one of the solutions worked I am still struggling to grasp the concept, that's why I decided to make a new one. Also because there are apparently many different opinions of how it should be done in the right manner.
First here is a clean running code example that I`m using to figure this out:
namespace serialInterfaceTest
{
public partial class Form1 : Form
{
string serialDataIn;
bool sent = false;
public Form1()
{
InitializeComponent();
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort.ReadExisting();
this.Invoke(new EventHandler(saveData));
}
public void saveData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox.Text += trimmedMsg;
if(trimmedMsg.Contains("*")) button_sendMsg.Enabled = true;
}
private void richTextBox_TextChanged(object sender, EventArgs e)
{
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
}
private void button_sendMsg_Click(object sender, EventArgs e)
{
send(textBox_message.Text);
button_sendMsg.Enabled = false;
//WAIT FOR RESPONSE BEFORE ALLOWING THE USER TO SEND ANOTHER MESSAGE
}
private void button_loopMsg_Click(object sender, EventArgs e)
{
button_loopMsg.Enabled = false;
for (int i = 0; i < 10; i++)
{
send(textBox_message.Text);
//WAIT FOR RESPONSE BEFORE CONTINUING THE LOOP
}
button_loopMsg.Enabled = true;
}
void send(String message)
{
serialPort.Write(message);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
}
}
It is a very simple GUI with a richTextBox to receive messages, a textbox to enter a message and two buttons that will send the message in two different ways.
The first case, where the user is sending the message manually is simple. Just deactivate the button so you can't spam it. The second case where I'm trying to send multiple messages in a loop seems way more complicated than I initially thought.
I try to outline my needs:
richTextBox always get's updated when a message arrives over serial
when user want's to send a message he has to wait for a reply before sending the next
when sending messages consecutively, the method for sending the message need's to stop and wait for a reply before proceeding with
the next message or send a timeout and stop the execution of the
method
answers from the serial port always end with a '*'
an answer can either resume a loop as described above or trigger a method from my GUI
all communication on serial is in ASCII.
GUI should stay responsive
Using the localHandler in the approved solution from my other question works and for one simple case that's fine but I quickly realized that it is not flexible enough. I tried to figure it out but didn't get anywhere.
In my code example that I posted above, I separated the serialPort.Write in it's own method. I'm thinking something in the terms of this:
UI is running in it's own thread and serialPort_DataReceived is running in it's own thread, which it is doing anyways as my reasearch showed. So now when I'm receiving data everything is fine, the UI gets updated everytime I receive a message from the serial port. Now for the sending part I guess the best way is to give it it's own thread as well? So I can simply pause the thread where my message is being sent, wait for a reply on the main thread and then continue. Or which other concept would fulfill my need here?
I'm new to object-oriented programming, most of the stuff I have done so far is C based so I could use any help here. Thanks for considering and again, sorry for the double post. I just hope my question is more clear now.
After 4 days of almost no sleep I figure it out and want to post the solution to anybody who is trying to have some sort of a flow control in their serial communication. In the end I was using async methods. I think this is as simple as it can get for somebody who doesn't have a lot of C# experience. Here is the code for the form:
namespace serialInterfaceTest
{
public partial class Form1 : Form
{
string serialDataIn;
public Form1()
{
InitializeComponent();
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort.ReadExisting();
this.Invoke(new EventHandler(saveData));
}
public void saveData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox.Text += trimmedMsg;
}
private void richTextBox_TextChanged(object sender, EventArgs e)
{
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
}
private async void button_sendMsg_Click(object sender, EventArgs e)
{
button_sendMsg.Enabled = false;
await Task.Run(() => send(textBox_message.Text));
button_sendMsg.Enabled = true;
//WAIT FOR RESPONSE BEFORE ALLOWING THE USER TO SEND ANOTHER MESSAGE
}
private async void button_loopMsg_Click(object sender, EventArgs e)
{
button_loopMsg.Enabled = false;
for (int i = 0; i < 10; i++)
{
await Task.Run(() => send(textBox_message.Text));
//WAIT FOR RESPONSE BEFORE CONTINUING THE LOOP
}
button_loopMsg.Enabled = true;
}
private async Task send(String message)
{
serialDataIn = "";
serialPort.Write(message);
while (!serialDataIn.Contains("*"))
{
//PROCESS ANSWERS HERE
serialDataIn = serialPort.ReadExisting();
if (serialDataIn.Contains("*"))
{
this.Invoke(new EventHandler(saveData));
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
}
}
I have the async method send data, and the two buttons are async as well. When I press them I'm just waiting for the task to complete before another input is allowed. I think this should be a good starting point for other projects as well. UI stays responsive, messages don't get queued up. The richTextBox on the UI get`s updated via Invoke so messages are displayed as soon as they arrive.
Here is the test code for an arduino:
#define println Serial.println
char serialIn;
String appendSerialData;
void setup()
{
Serial.begin(9600);
}
void loop()
{
appendSerialData = "";
while (Serial.available() > 0)
{
serialIn = Serial.read();
appendSerialData += serialIn;
}
/* asterisk functions as message identifier */
delay(1000);
if (appendSerialData != "") println(appendSerialData + " *");
for (int i = 0; i < 5; i ++)
{
println(i);
}
delay(100);
}
If there are any improvements I can make to this I`m happy to hear about it.
I'm facing an serious issue where data from one record is copied to another record (Overlay). I'm using MQ request for communicating to Mainframe systems using my C# code. we are facing issue which is very random/rarer where sending update request to Mainframe for one record copy information of another record previously processed by that thread. I'm using below code Background worker approach to create multi-threading on my servers.
My Question here is : Can objects created by one worker being used by another work ? is that possible ? this may be one of reason of overlay data.
Please help with you suggestion !!
BackgroundWorker worker;
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
}
struct SOmeData { }
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
SOmeData data = new SOmeData();
// backgroundWorker1 result
e.Result = data;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// backgroundWorker1 result
SOmeData data = (SOmeData)e.Result;
// start backgroundWorker2
backgroundWorker2.RunWorkerAsync(data);
}
private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
// backgroundWorker1 result
SOmeData data = (SOmeData)e.Argument;
}
}
can anybody check below codes for C#. There is a issue at float sıcaklık = Convert.ToByte(seriPort.ReadExisting()); But I couldn't find out what is wrong? I guess SerialPort couldn't get the data.
public partial class Form1 : Form
{
SerialPort seriPort;
public Form1()
{
InitializeComponent();
seriPort = new SerialPort();
seriPort.BaudRate = 9600;
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Start();
try
{
seriPort.PortName = textBox1.Text;
if (!seriPort.IsOpen)
MessageBox.Show("Bağlantı Kuruldu");
}
catch
{
MessageBox.Show("Bağlantı Kurulmadı!");
}
}
private void timer1_Tick(object sender, EventArgs e)
{
try
{
seriPort.Write("temperature");
float sıcaklık = Convert.ToByte(seriPort.ReadExisting());
textBox2.Text = sıcaklık.ToString();
comboBox1.Items.Add(textBox2.Text);
System.Threading.Thread.Sleep(100);
}
catch (Exception) {}
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
seriPort.Close();
}
}
First of all. After assigning the Name to the port you need to open it so that communication can take place:
seriPort.PortName = textBox1.Text;
// open the port
seriPort.Open();
Second thing is, that communication needs time to take place. Imagine you talk to someone. You ask him for the current temperature. Then you have to wait until your partner has spoken until the end. Only then you will have your desired information, which you can use.
Since you stick here to a synchronous approach you have to give the device as much time as it needs to answer. You could just wait:
seriPort.Write("temperature");
// wait for the response
System.Threading.Thread.Sleep(2000);
float sıcaklık = Convert.ToByte(seriPort.ReadExisting());
I would not really recommend this synchronous approach, but rather use the DataReceived. It will be fired when your device sends you data und you have received it.
SerialPort port = new SerialPort();
port.DataReceived += Port_DataReceived;
private static void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
float sıcaklık = Convert.ToByte(seriPort.ReadExisting());
// do what ever you want with this value
}
Hope that helps
I'm working a on project that continuously listens for packets on UDP port 514 (syslog). When a packet is received I need to parse the message so it can be displayed on my main form in a DataGridView control.
I have it so it updates the DataGridView with new messages, but I could get over 100 syslog messages for the same thing from my firewall so I want to search my dataGrid, see if the message already exists, and then update a "Hit count" column instead of making a new row for duplicate messages.
The problem is that I can't figure out how to invoke my DataGridView in a foreach loop. I'm not returning the value from the thread, I'm processing the data as it comes in, so I need to invoke the DataGridView control from the UI thread.
My code so far:
private async void Form1_Load(object sender, EventArgs e)
{
var udpCap = await Task.Run(() => captureSyslog());
}
public async Task<string> captureSyslog()
{
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
outputMessage(incomingSyslog);
}
catch (Exception ex2)
{
outputMessage(ex2.ToString());
}
}
}
private void outputMessage(string txt)
{
string[] chopped = txt.Split(',');
string descrip = chopped[32];
string severity = chopped[34];
string paloalto = chopped[59];
int rowIndex = -1;
foreach (DataGridViewRow row in syslogOutput.Rows)
{
try
{
if (row.Cells[2].Value.ToString().Equals(descrip) | row.Cells[1].Value.ToString().Equals(paloalto))
{
//Code to update DataGridView
}
}
catch (Exception ex3){ debugLabel1.Invoke(new Action(() => debugLabel1.Text = ex3.ToString()));}
}
if (syslogOutput.InvokeRequired)
{
syslogOutput.Invoke(new Action(() => syslogOutput.Rows.Add("1", chopped[59], chopped[34], chopped[32])));
}
}
When the code is run like this, I get a "System.NullReferenceException: Object reference not set to an instance of an object" which makes sense because the "syslogOuput" DataGridControl doesn't exist in the thread that's calling it. However the "syslogOutput.Invoke" line farther down works because it's being pulled in from the main UI thread.
Additionally, I could update my datagrid with the udpCap var in the Form1_load method, but when I do that I just get one message and the Task.Run thread quits.
So I guess my question can be phrased two ways:
How can I invoke a control in a foreach loop?
and/or
How can I get a return value from a task without ending the task that's returning the value?
UPDATE:
I dumped the Task and used the BackgroundWorker feature to capture the syslog messages. I abused the UserState in the ProgressChanged method to pass the syslog string so I can parse and then display it to my DataGridView natively in the UI thread. As far as I can tell the BackgroundWorker thread will run forever if there isn't a cancellation and/or the ReportProgress method never receives a "100" indicating that the process is 100% complete.
New code:
namespace ASA_SyslogCapture
{
public partial class Form1 : Form
{
BackgroundWorker bgWorker;
public Form1()
{
InitializeComponent();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.WorkerReportsProgress = true;
}
private async void Form1_Load(object sender, EventArgs e)
{
bgWorker.RunWorkerAsync();
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//put udp code here
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
bgWorker.ReportProgress(0,incomingSyslog);
}
catch (Exception ex2) { debugLabel1.Text = ex2.ToString(); }
}
}
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
debugLabel1.Text = e.UserState as String; //this will eventually be my datagridview code
}
}
}
Huge thanks to this thread for the suggestion to use UserState to pass the string: C# backgroundWorker reports string?
I am working on code that connects to a serial port of a stepper motor. I send a command to the stepper motor via textBox2 and am attempting to read in the return data from the command to textBox3. I am able to make a connection, send the command, and receive the data. But after my GUI populates textBox3 with the returned serial data it freezes.
I believe that the code is getting stuck in the try loop but I don't know how to break out of it. Here is my code:
private void button3_Click(object sender, EventArgs e)
{
if (isConnectedMotor)
{
string command = textBox2.Text;
portMotor.Write(command + "\r\n");
portMotor.DiscardInBuffer();
while (true)
{
try
{
string return_data = portMotor.ReadLine();
textBox3.AppendText(return_data);
textBox3.AppendText(Environment.NewLine);
}
catch(TimeoutException)
{
break;
}
}
}
}
DataReceived Code:
private void connectToMotor()
{
isConnectedMotor = true;
string selectedPort = comboBox2.GetItemText(comboBox2.SelectedItem);
portMotor = new SerialPort(selectedPort, 9600, Parity.None, 8, StopBits.One);
portMotor.RtsEnable = true;
portMotor.DtrEnable = true;
portMotor.Open();
portMotor.DiscardInBuffer();
portMotor.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
button4.Text = "Disconnect";
enableControlsMotor();
}
private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
textBox3.AppendText(indata);
textBox3.AppendText(Environment.NewLine);
}
I am getting an error saying:
An object reference is required for the non-static field, method, or property 'Form1.textBox3'
Invoke code:
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
portMotor.DiscardInBuffer();
incoming_data = portMotor.ReadExisting();
this.Invoke(new EventHandler(displayText));
}
private void displayText(object o, EventArgs e)
{
textBox3.Text += incoming_data;
}
Instead of looping to read data, use a DataReceived event to get at the incoming bytes asynchronously.
See the documentation and example.
Also see this question for troubleshooting.
P.S. Here is a code sample to avoid locking up the UI.
private void ReceivedHandler(object sender, SerialDataReceivedEventArgs e) {
var incoming_data = portMotor.ReadExisting();
// this is executing on a separate thread - so throw it on the UI thread
Invoke(new Action(() => {
textBox3.Text += incoming_data;
}));
}