I'm new to c#.
I'm using the Nucleo board to send data through a serial port into my GUI. The data consists of pulse rate, number of steps and body temperature.
My code here works completely fine to display all the data into a single textbox, but I want to display each value in different textboxes.
This is what the incoming data looks like
S0E // where "S" is for steps and "E" is for the end
P5E // where "P" is for pulse rate and "E" is for the end
T22.5E // where "T" is for body temp. and "E" is for the end
Here is the code I am using:
private void showbtn_Click(object sender, EventArgs e) //Showbtn click
{
text.Clear(); //clears the text in the textbox
bool foundUser = false; // sets the boolean foundUser to false
int userindex = 0; // sets interger to zero
for (int i = 0; i < userlist.Count; i++)
{
if (userlist[i].name.Equals(Nametb.Text)) // if the user entered name equals to the name in the list
{
foundUser = true;
userindex = i;
}
}
if (foundUser == true)
{
string userText; // the following lines of code displays the user details in the textbox
userText = "name :" + userlist[userindex].name + "user :" + Environment.NewLine + "age:" + userlist[userindex].age +
Environment.NewLine + " gender:" + userlist[userindex].gender + Environment.NewLine + "height:" + userlist[userindex].height +
Environment.NewLine + "weight:" + userlist[userindex].weight + Environment.NewLine + "BMI :" + userlist[userindex].bmI;
text.Text = userText;
}
else
{
text.Text = "no user found"; // if the user not found displays as "no user Found"
}
t = comboBox1.Text.ToString();
sErial(t);
}
private void button2_Click(object sender, EventArgs e) // searches for available com ports
{
string[] ports = SerialPort.GetPortNames();
foreach (string port in ports)
{
comboBox1.Items.Add(port); // adds them to the combo box
}
}
string t;
private SerialPort SerialPort1;
void sErial(string Port_name)
{
SerialPort1 = new SerialPort(Port_name, 9600, Parity.None, 8, StopBits.One); //serial port properties
SerialPort1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
SerialPort1.Open(); //opens serial port
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort1 = (SerialPort)sender;
string w = serialPort1.ReadLine(); // assigns the data from the serial port to a string
if (w != String.Empty)
{
if (abort == false)
{
Invoke(new Action(() => rt1.AppendText(w))); // displays the data in a textbox
}
}
}
If I have understood correctly, you receive data from an external device and you wish to split apart a string that contains multiple numeric data elements delimited by single characters, so you can display the results to different textboxes.
This is a typical problem to be solved by people who receive data from an embedded system and need to learn how to handle it on a host PC, but are new to programming in C#.
There are many different ways to do this. Since you say you can receive and display the entire string in a single textbox, I will focus only on how to split it apart and display the parts in three different richTextBoxes.
For a c# beginner, string.split and substring are pretty straightforward ways to break apart a string. Here is one simple example using string.split.
I made a complete example for you: you can see below that it works. First I created a form containing three rich textboxes called rt1, rt2, and rt3, along with an input textbox called tbInput and a button called btnSplit.
Then in the designer I double-clicked on the button to add an event handler for btnInput_click and added the code below to that handler. That's it!
private void btnSplit_Click(object sender, EventArgs e)
{
string blah = tbInput.Text;
tbInput.Clear();
var lst = blah.ToUpper().Split('E').ToList();
foreach (var item in lst)
if (item.Trim().StartsWith("S"))
rt1.AppendText($"Steps: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("T"))
rt2.AppendText($"Temperature: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("P"))
rt3.AppendText($"Pulse: {item.Remove(0, 1)} \n");
}
The code converts the input to upper case, splits the string on the letter "E" and assigns the results to a list, so given the starting string containing three "E"s, you will now have a list containing three strings:
item01 == S111
item02 == T98.6
item03 == P70
I then trim leading and trailing white space and assign the result to one of three RichTextBoxes based on the leading character. I use item.Remove(0,1) to remove the leading character before appending the result to the textbox. I use interpolation to embed the result in a string of my choosing.
I should probably point out that unless you are running this code on a thread other than the UI thread, you do not need to use an Action delegate and you do not need to Invoke anything. If you're on the main thread, just append the text to the textbox.
If you are planning to make a cross-thread call to a control running on the UI, an Action<> delegate is not the right way to do this. If that's what you're trying to do, look at MethodInvoker(). However, I strongly recommend that you not try to use multiple threads until you are quite advanced as a programmer and have read several books on the subject. They certainly are not needed for a simple project like this one :)
Does this help?
#craig.Feied
this was the part which I altered.
thanks again for your help.
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort1 = (SerialPort)sender;
var rt1 = new RichTextBox();
var rt2 = new RichTextBox();
var rt3 = new RichTextBox();
string blah = serialPort1.ReadLine();
var lst = blah.Split('E').ToList();
foreach (var item in lst)
if (item.Trim().StartsWith("S"))
rt1.AppendText($"Steps: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("T"))
rt2.AppendText($"Temperature: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("P"))
rt3.AppendText($"Pulse: {item.Remove(0, 1)} \n");
}
Related
I Have created some code for communicating with a device over the serialport. By sending certain command with serialPort1.WriteLine I should receive answer with serialPort1.ReadExisting(). Well I'm able to detect the device but I need to read some other information but to get this information correct I only get it when I place the code in a button_Click function. The funny thing is that All is written in a function and I wish to get the information after the device is detected immediately. When I call the function after the device has been detected it doesn't work and when I call the function after the button is pressed I get the correct information. I do not understand why this is so.
The function for detecting the device is like this:
private void detectVM25ToolStripMenuItem_Click(object sender, EventArgs e)
{
// Detect VM25 and show connection is established
String device;
//Search all portnames
String[] ports = SerialPort.GetPortNames();
int totalPorts = ports.Length;
int count = 0 ;
//Test which enabled port is the VM25.
foreach (string port in ports)
{
count = count + 1;
serialPort1.PortName = port;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#S" + Environment.NewLine);
answer = serialPort1.ReadExisting();
if (answer != "")
{
device = answer.Substring(0, 4);
if (device == "VM25")
{
getRecordings();
statusLblDevice.ForeColor = Color.LawnGreen;
statusLblDevice.Text = port + " - " + device + " - Connected";
VM25Port = port;
}
}
else if (answer == "")
{
serialPort1.Close();
if (count == totalPorts)
{
MessageBox.Show("No device found");
}
}
}
}
}
The function getRecordings() should give me data. If I place this function in my form and get called after a button is pressed I get the correct info but when it is inside the above function it doesn't do anything.
private void getRecordings()
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#H" + Environment.NewLine);
memoryInfo = serialPort1.ReadExisting();
label1.Text = memoryInfo;
}
}
Does anybody knows why this is the case? I would like not to have press a button and get this information after it has detected the device. I also tried to create a delay with `Task.Delay()' unfortunately this did not help
It's surely because you have no synchronization whatsoever. .ReadExisting() does not get an entire response, it gets only what's already been received. So calling it right after .WriteLine(...)... well your data hasn't even reached the device yet, so there definitely won't be an answer ready to read.
Since you know the length of your expected answer (or at least the prefix you're interested in), set the read timeout and then call .Read() (or better, .BaseStream.ReadAsync()) with that length.
Even that is only going to mostly work when the port is freshly opened... for subsequent commands you risk getting data in your buffer from the tail end of a reply to an earlier command. Correct use of a serial port really requires the two ends to agree on some sort of framing, whether that is "a message contains only ASCII and ends with CR+LF" or "a message starts with the length of its payload" or some less common scheme.
with the point out of #Ben Voigt I discovered I should tackle this issue different.
I have use the method serialPort1.ReadTo() because I know that each message end with "/a".
therefore I fixed the function as follows
private void detectVM25ToolStripMenuItem_Click(object sender, EventArgs e)
{
// Detect VM25 and show connection is established
String device;
//Search all portnames
String[] ports = SerialPort.GetPortNames();
int totalPorts = ports.Length;
int count = 0 ;
//Test which enabled port is the VM25.
foreach (string port in ports)
{
count = count + 1;
serialPort1.PortName = port;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#S" + Environment.NewLine);
answer = serialPort1.ReadExisting();
if (answer != "")
{
device = answer.Substring(0, 4);
if (device == "VM25")
{
statusLblDevice.ForeColor = Color.LawnGreen;
statusLblDevice.Text = port + " - " + device + " - Connected";
VM25Port = port;
serialPort1.WriteLine("#H" + Environment.NewLine);
string input = serialPort1.ReadTo("/a");
label1.Text = input;
string totalMeasurements = input.Substring(0, 11);
string totalFFT = input.Substring(11, 11);
statusLblMeas.Text = totalMeasurements;
statusLblFFT.Text = totalFFT;
}
}
else if (answer == "")
{
serialPort1.Close();
if (count == totalPorts)
{
MessageBox.Show("No device found");
}
}
}
}
}
Note the changes of reading the serialport where I stated that it should read up to "/a"
I was just starting to use C# last week and tried to create a simple serial monitor program. I want my program to read data from serialport continuously while displaying those data in the Form Application at the same time.
I use the code from here as reference https://github.com/ahelsayd/Serial-Lab
I use the same BeginInvoke() function. However I can not pass the variable that I want to write.
This is the original code
private void rx_data_event(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (mySerial.IsOpen)
{
try
{
//=====Only this part is different=======================
int dataLength = mySerial.BytesToRead;
byte[] dataReceived = new byte[dataLength];
int nbytes = mySerial.Read(dataReceived, 0, dataLength);
if (nbytes == 0) return;
//=====Only this part is different=======================
this.BeginInvoke((Action)(() =>
{
data = System.Text.Encoding.Default.GetString(dataReceived);
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}));
}
catch { alert("Can't read form " + mySerial.PortName + " port it might be opennd in another program"); }
}
}
//And then update the UI
private void update_rxtextarea_event(object sender, DoWorkEventArgs e)
{
this.BeginInvoke((Action)(() =>
{
if (rx_textarea.Lines.Count() > 5000)
rx_textarea.ResetText();
rx_textarea.AppendText("[RX]> " + data);
}));
}
This code can read the Serialport and Write into the Form simultaneously. However, it does not receive all data from the serialport. So I modified the code to write the data into a buffer first until all data is received.
The modified code
private void rx_data_event(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (mySerial.IsOpen)
{
try
{
//=====Only this part is different=======================
string Data = mySerial.ReadExisting();
serialBuffer.Append(Data);
string bufferString = serialBuffer.ToString();
int index = -1;
do
{
index = bufferString.IndexOf(terminationSequence);
if (index > -1)
{
string message = bufferString.Substring(0, index);
bufferString = bufferString.Remove(0, index + terminationSequence.Length);
}
}
while (index > -1);
serialBuffer = new StringBuilder(bufferString);
byte[] bytes = new byte[30];
if (serialBuffer.Length == 15) {
Console.WriteLine("data:" + serialBuffer);
bytes = Encoding.ASCII.GetBytes(serialBuffer.ToString());
}
//=====Only this part is different=======================
this.BeginInvoke((Action)(() =>
{
data = Encoding.ASCII.GetString(bytes);
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}));
}
catch { alert("Can't read form " + mySerial.PortName + " port it might be opennd in another program"); }
}
}
The problem is in the Form application the value of bytes is always null although when I checked by writing the output value to console window I can see the value of bytes updated.
I am very confused why the variable dataReceived value can be accessed by BeginInvoke while the variable bytes keep having null value. Is there something that I've missed that causing the value not get updated?
So I wasn't able to do it yesterday, but I promised the OP I would write something in an answer related to a comment of mine. From what I understand he/she is trying to update something in a Form based on received data from a serial port. Here's how I do it:
First off you need to declare a delegate and an event (which is basically a list of delegates):
public delegate void Data_Received_EventHandler(object sender, Data_Received_EventArgs e);
public event Data_Received_EventHandler Data_Received;
These are used to substitute the original "data received" event and event args from the serial port.
I usually define the Data_Received_EventArgs type as something based of an array of bytes, for simplicity:
public class Data_Received_EventArgs : EventArgs
{
public byte[] data;
internal Data_Received_EventArgs(int length)
{
data = new byte[length];
}
}
Then, in the original data reception event (let's say your serial port is called serialPort1):
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// check how many bytes you need to read:
int bytesToRead = serialPort1.BytesToRead;
// declare your arguments for the event based on that number of bytes (check the Data_Received_EventArgs constructor):
Data_Received_EventArgs args = new Data_Received_EventArgs(bytesToRead);
// copy the bytes from your serial port into the arguments:
for (int i = 0; i < bytesToRead; i++)
args.data[i] = (byte)serialPort1.ReadByte();
// verify if there are subscribers to the event (list not empty) and fire your event using BeginInvoke:
if (Data_Received != null)
BeginInvoke((MethodInvoker)delegate () { Data_Received(this, args); });
}
What we now have is an event which is guaranteed to execute its handlers on the UI thread.
To use it, you can subscribe to the event like so:
Data_Received += My_Data_Received_Handler;
Your handler should have the same signature as the delegate we declared in the first line of code (should be void and have specified parameters):
private void My_Data_Received_Handler(object sender, Data_Received_EventArgs e)
{
// read bytes from the arguments as you normally would from an array and do whatever e.g.:
some_Label.Text = e.data[0].ToString(); // without worrying about crossthreading
}
I know this is not the answer the OP wanted, but I hope it helps in simplifying what he/she was trying to do in the first place.
I am developing an application to read data through Serial Port. What I am trying to read is a string that could be empty or with something in it.
My first attempt was creating an array where inside of it I would be able to insert what could come from Serial Port.
string[] pass = new string[4];
pass[0] = "";
pass[1] = "Something";
pass[2] = "To";
pass[3] = "Read";
for (int i = 0; i < pass.Length; i++)
{
string element = pass[i];
}
But this isn't work for me because I wanna read any thing from the Serial port.
In the next option, in the data.ToString() == "Any string I want".
string data = serPort.ReadExisting();
if (data.ToString() == "Any string I want")
{
Environment.Exit(0);
}
Basically, instead of the "Any string I want" I would like to every time I send something through the Arduino it will be recognized by the application.
Do you guys have any suggestions about this? In other words, if the incoming data is equal to the string written by the Arduino it will do something.
You need to decide on a termination char and add it to your arduino code that is sending the serial string and look for that char in the incoming data. I'm using carriage Return line feed.
private string receivedDate = string.Empty;
private System.IO.Ports.SerialPort mport;
private void Form1_Load(object sender, EventArgs e)
{
mport = new SerialPort("COM1", 9600, Parity.None,8, StopBits.One);
mport.DataReceived += new SerialDataReceivedEventHandler(mport_DataReceived);
}
private void mport_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
receivedDate += mport.ReadExisting();
if (receivedDate.Contains("\r\n"))
{
//show data
//Clear receivedDate
}
}
I'm in the middle of a project wherein I have to read Serial Data in CSV format from the sensors of an Arduino , parse the obtained values using C#, and display real time graphs.
I am new to the concept of multithreading and I'm confused about how many threads should I make, and what task each thread should be assigned.
Any Suggestions?
This is an initial sample code, so it may have errors.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
RxString = serialPort1.ReadExisting();
RxString = RxString.Replace("$", "");
this.Invoke(new EventHandler(DisplayText));
}
//display the parsed string List
private void DisplayText(object sender, EventArgs e)
{
richTextBox1.AppendText(RxString);
GlobalList.AddRange(parsed());
richTextBox2.Text = String.Join(Environment.NewLine, GlobalList);
}
//set the input rate
private void Start_Click(object sender, EventArgs e)
{
serialPort1.PortName = "COM32";
serialPort1.BaudRate = 9600;
serialPort1.DtrEnable=true;
serialPort1.Open();
if (serialPort1.IsOpen)
{
Start.Enabled = false;
Stop.Enabled = true;
richTextBox1.ReadOnly = false;
}
}
public List<String> parsed()
{
string line;
int loc = 0;
List<string> stringList;
line = richTextBox1.Text;
stringList = new List<string>(line.Split(','));
richTextBox3.AppendText("\n Pressure:" + stringList[loc]);
loc++;
richTextBox3.AppendText("\n Accelerometer:" + stringList[loc]);
loc++;
richTextBox3.AppendText("\n Temperature:" + stringList[loc]);
loc++;
richTextBox3.AppendText("\n Height:" + stringList[loc]);
loc++;
return stringList;
}
//plot an elementary graph from the values obtained
public void displayglobal()
{
for (int i = 0; i < GlobalList.Count; i++)
{
if (i % 3 == 0)
{
rtxtConsole.AppendText("\nPressure: " + GlobalList[i]);
chart1.Series["tempvspressure"].Points.AddXY(GlobalList[i], GlobalList[i + 2]);
}
}
}
I would strongly advise using a worker thread on anything that can block, since this freezes up the UI.
I would spin off a single thread to read the incoming stream and parse units of data. If the stream is CSV then your row delimiter, usually a new line, can be used by the stream parser to delimit units.
If the workload per row is very low, the stream reading thread can just invoke the UI thread for handling each time it has a complete unit of data. If the number of rows being handled causes the UI to lock up then another worker thread may be desired to batch insert them into the UI. For this you would require a thread safe queue.
I've done this many times in logging applications which listen to a network stream and the problem doesn't seem that different for a serial port.
With the new async support in C#, you shouldn't need multiple threads at all.
You can use port.BaseStream.ReadAsync() which will cooperate with UI message processing.
Im trying to get into some webpages and get some information, using web browser so that it remembers my login details. things worked till here but for multiple urls web browser document load is not working properly as i want.
My intention was go to url->wait till it loads--> get required data into text--> new url and same process.
i used for loop to change url but when i run all the url's passed one by one not waiting till document loads and writes to text. please help me.
private void button1_Click_1(object sender, EventArgs e)
{
String text = File.ReadAllText("links.txt");
var result = Regex.Split(text, "\r\n|\r|\n");
foreach (string s in result)
{
listBox1.Items.Add(s);
}
for (int i = 0; i < listBox1.Items.Count; i++)
{
this.Text = Convert.ToString(i + 1) + "/" + Convert.ToString(listBox1.Items.Count);
textBox1.Text += listBox1.Items[i];
String url = textBox1.Text;
webBrowser2.ScriptErrorsSuppressed = true;
webBrowser2.DocumentCompleted += webBrowser2_DocumentCompleted;
webBrowser2.Navigate(url);
}
}
void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
string sourceCode = webBrowser2.DocumentText;
try
{
/*someregax expressions to filter text */
StreamWriter sw = new StreamWriter("inks_info.txt", true);
sw.Write("url" + "~" + sourceCode + "\n");
sw.Close();
textBox1.Text = "";
}
catch
{
StreamWriter sw = new StreamWriter("inks_fail.txt", true);
sw.Write(textBox1.Text + "\n");
sw.Close();
textBox1.Text = "";
}
}
You have an event handler on the document load for each item, but you're not waiting for it to fire after the first navigation before you initiate the second navigation. Your for loop needs to be "more asynchronous". For example, placing items in a queue and requesting one at a time:
Queue<string> _items;
private void button1_Click_1(object sender, EventArgs e)
{
String text = File.ReadAllText("links.txt");
_items = new Queue<string>(Regex.Split(text, "\r\n|\r|\n"));
webBrowser2.ScriptErrorsSuppressed = true;
webBrowser2.DocumentCompleted += webBrowser2_DocumentCompleted;
RequestItem();
}
private void RequestItem()
{
if (_items.Any())
{
var url = _items.Dequeue(); // preprocess as required
webBrowser2.Navigate(url);
}
}
void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Handle result
RequestItem(); // Then request next item
}
Your code also looks like it's using UI elements (like a list box) as intermediate variables just for a logical purpose rather than display. You should separate out the logic (using regular variables, data structures such as lists and requesting data) from the display (showing the results in list boxes, updating text boxes, etc). It's not clear that you want to be using a WebBrowser even - it looks like you're just downloading text and should use WebClient or HttpClient. The code can then also be much cleaner using async/await:
foreach (var url in urls)
{
string text = await new WebClient().DownloadStringAsync(url);
// Handle text
}
Very Simple answer. The WebBorwser control sucks for this stuff but here is what you are looking for:
WHILE(webBrowser.ReadyState != WebBrowserReadyState.Ready)
{
Application.DoEvents()
}
Thats it.. It will not freeze your app or get you lost in code, it just waits till its don't navigating. You be most welcome.