recieveing and splitting serial data from Arduino in C#
-
Hi, I am fairly new to C# coding so be gentle :laugh: I am trying to receive measurement data from a two sensors DHT11 and soimoisture sensor connected to my Arduino. At this stage I just want to receive the raw measurement data and represent them in separate text boxes. I have almost managed it but I am getting some errors. More specifically I get this error:
System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index'
Here is my code:using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;namespace PlantMonitoringApp
{
public partial class Form1 : Form
{private SerialPort myport; private DateTime datetime; private string in\_data; public Form1() { InitializeComponent(); } private void start\_btn\_Click(object sender, EventArgs e) { myport = new SerialPort(); myport.BaudRate = 9600; myport.PortName = port\_name\_tb.Text; myport.Parity = Parity.None; myport.DataBits = 8; myport.StopBits = StopBits.One; myport.DataReceived += Myport\_DataReceived1; try { myport.Open(); time\_text\_box.Text = ""; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } // timer1.Start(); } void Myport\_DataReceived1(object sender, SerialDataReceivedEventArgs e) { in\_data = myport.ReadLine(); this.Invoke(new EventHandler(displaydata\_event)); /\*String dataFromArduino = myport.ReadLine(); String\[\] dataTempHumidMoisture = dataFromArduino.Split(); int Temperature = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[0\]), 0)); //int Humidity = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[1\]), 0)); // int SoilMoisture= (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[2\]), 0)); txtTemperature.Text = Temperature.ToString() + "C"; // txtHumidity.Text = Humidity.ToString() + "%"; //txtHumidity.Text = SoilMoistu
-
Hi, I am fairly new to C# coding so be gentle :laugh: I am trying to receive measurement data from a two sensors DHT11 and soimoisture sensor connected to my Arduino. At this stage I just want to receive the raw measurement data and represent them in separate text boxes. I have almost managed it but I am getting some errors. More specifically I get this error:
System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index'
Here is my code:using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;namespace PlantMonitoringApp
{
public partial class Form1 : Form
{private SerialPort myport; private DateTime datetime; private string in\_data; public Form1() { InitializeComponent(); } private void start\_btn\_Click(object sender, EventArgs e) { myport = new SerialPort(); myport.BaudRate = 9600; myport.PortName = port\_name\_tb.Text; myport.Parity = Parity.None; myport.DataBits = 8; myport.StopBits = StopBits.One; myport.DataReceived += Myport\_DataReceived1; try { myport.Open(); time\_text\_box.Text = ""; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } // timer1.Start(); } void Myport\_DataReceived1(object sender, SerialDataReceivedEventArgs e) { in\_data = myport.ReadLine(); this.Invoke(new EventHandler(displaydata\_event)); /\*String dataFromArduino = myport.ReadLine(); String\[\] dataTempHumidMoisture = dataFromArduino.Split(); int Temperature = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[0\]), 0)); //int Humidity = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[1\]), 0)); // int SoilMoisture= (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[2\]), 0)); txtTemperature.Text = Temperature.ToString() + "C"; // txtHumidity.Text = Humidity.ToString() + "%"; //txtHumidity.Text = SoilMoistu
-
There's no guarantee that tokens has data.
txtTemperature.Text = tokens[0];
"(I) am amazed to see myself here rather than there ... now rather than then". ― Blaise Pascal
-
Hi, I am fairly new to C# coding so be gentle :laugh: I am trying to receive measurement data from a two sensors DHT11 and soimoisture sensor connected to my Arduino. At this stage I just want to receive the raw measurement data and represent them in separate text boxes. I have almost managed it but I am getting some errors. More specifically I get this error:
System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index'
Here is my code:using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;namespace PlantMonitoringApp
{
public partial class Form1 : Form
{private SerialPort myport; private DateTime datetime; private string in\_data; public Form1() { InitializeComponent(); } private void start\_btn\_Click(object sender, EventArgs e) { myport = new SerialPort(); myport.BaudRate = 9600; myport.PortName = port\_name\_tb.Text; myport.Parity = Parity.None; myport.DataBits = 8; myport.StopBits = StopBits.One; myport.DataReceived += Myport\_DataReceived1; try { myport.Open(); time\_text\_box.Text = ""; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } // timer1.Start(); } void Myport\_DataReceived1(object sender, SerialDataReceivedEventArgs e) { in\_data = myport.ReadLine(); this.Invoke(new EventHandler(displaydata\_event)); /\*String dataFromArduino = myport.ReadLine(); String\[\] dataTempHumidMoisture = dataFromArduino.Split(); int Temperature = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[0\]), 0)); //int Humidity = (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[1\]), 0)); // int SoilMoisture= (int)(Math.Round(Convert.ToDecimal(dataTempHumidMoisture\[2\]), 0)); txtTemperature.Text = Temperature.ToString() + "C"; // txtHumidity.Text = Humidity.ToString() + "%"; //txtHumidity.Text = SoilMoistu
Hi, Reading from a SerialPort isn't always easy. Your code may fail to work reliably for several reasons: 1. The SerialPort.DataReceived event is fired when one or more characters have been received; there is no strict definition of when it fires. And it is not synchronized to anything, it is not aware of what you consider a message (such as a piece of text terminated by a newline). 2. SerialPort.ReadLine() returns one line of text assuming a newline has been received; if not a timeout exception will occur. 3. You are using two threads, and communicating between them using a single string (in_data). It is conceivable that two DataReceived events occur before the other thread manages to execute displaydata_event(). In that case, you would loose data. If you are in charge of software on both sides, the simplest approach would be like this: 1. don't let the peripheral send data at will, make it a slave that doesn't have the initiative, instead it executes commands when it receives them 2. send a command to the peripheral requesting (a measurement and) the return of data 3. wait a short time 4. read the data synchronously So basically that would be:
SerialPort port;
...
port.Open();
...
port.WriteLine("T"); // the command to measure and send a temperature
Thread.Sleep(100); // wait for measurement and transmission
string temp=port.ReadLine(); // receive the result
textbox.Text=temp;The delay should be sufficient for the peripheral to perform the measurement, and the serial port to transmit and receive the data. Since 9600 Baud would correspond to approx 1 msec per character, delaying for 100 msec seems reasonable, and not too harmful for your GUI interface. Of course, the proper way to do communication is asynchronous, but then you need to take care of proper synchronisation and data transfer; that would include: 1. reading the port with ReadExisting, not ReadLine; 2. collecting the incoming characters; 3. identifying message boundaries; 4. passing results to another thread, using some real data structure (possibly a queue). Hope this helps. :)
Luc Pattyn [My Articles] Nil Volentibus Arduum
-
Why is there no guarantee? Sometimes it works, that's the strange part . Any suggestions? :confused:
I always tied my serial port "Read()", and the "bytes read" that it returns, with the "BytesToRead" as indicated by the port. In other words, I never have occasion to just "read" without first requesting data via a "write". The reading is managed within the timeout "window". Write ... wait (check the time) ... (expecting bytes to be read > 0 ) read ... more bytes to be read? ... wait (timeout?) .... read again ... etc. until all read or timeout; then the next write for data request. You build a message until no more bytes to be read; then fire it off to the rest of the processing; insuring you actually split 3 tokens and didn't receive garbage in the first place.
"(I) am amazed to see myself here rather than there ... now rather than then". ― Blaise Pascal
-
Hi, Reading from a SerialPort isn't always easy. Your code may fail to work reliably for several reasons: 1. The SerialPort.DataReceived event is fired when one or more characters have been received; there is no strict definition of when it fires. And it is not synchronized to anything, it is not aware of what you consider a message (such as a piece of text terminated by a newline). 2. SerialPort.ReadLine() returns one line of text assuming a newline has been received; if not a timeout exception will occur. 3. You are using two threads, and communicating between them using a single string (in_data). It is conceivable that two DataReceived events occur before the other thread manages to execute displaydata_event(). In that case, you would loose data. If you are in charge of software on both sides, the simplest approach would be like this: 1. don't let the peripheral send data at will, make it a slave that doesn't have the initiative, instead it executes commands when it receives them 2. send a command to the peripheral requesting (a measurement and) the return of data 3. wait a short time 4. read the data synchronously So basically that would be:
SerialPort port;
...
port.Open();
...
port.WriteLine("T"); // the command to measure and send a temperature
Thread.Sleep(100); // wait for measurement and transmission
string temp=port.ReadLine(); // receive the result
textbox.Text=temp;The delay should be sufficient for the peripheral to perform the measurement, and the serial port to transmit and receive the data. Since 9600 Baud would correspond to approx 1 msec per character, delaying for 100 msec seems reasonable, and not too harmful for your GUI interface. Of course, the proper way to do communication is asynchronous, but then you need to take care of proper synchronisation and data transfer; that would include: 1. reading the port with ReadExisting, not ReadLine; 2. collecting the incoming characters; 3. identifying message boundaries; 4. passing results to another thread, using some real data structure (possibly a queue). Hope this helps. :)
Luc Pattyn [My Articles] Nil Volentibus Arduum
Well you just made me realize how much I don't know :doh: . Where specifically in my code can I put this delay?
Thread.Sleep(100);
I really appreciate your effort ,but it would be way more helpful if you could comment on where in my code I can implement this?
-
Well you just made me realize how much I don't know :doh: . Where specifically in my code can I put this delay?
Thread.Sleep(100);
I really appreciate your effort ,but it would be way more helpful if you could comment on where in my code I can implement this?
Hi, I've thrown out most all of your code! My code example replaces everything (it still needs port initialization). Under the assumptions I made (master-slave operation, PC sends command then peripheral replies), there is no need for events, secondary threads, Invoke, etc. If you can't turn your peripheral into a slave, which would mean it can still send results whenever it likes, then you need more of your code, however even then a delay could save your day: put it anywhere between the start of the DataReceived event and the line that holds SerialPort.ReadLine; this would guarantee (actually increase the probability) that an entire message up to and including newline is present in the serial buffer when ReadLine gets called. However now the delay also increases the probability that you would loose a message, so it makes sense to opt for a shorter delay (I would at first not go below 20 msec though). :)
Luc Pattyn [My Articles] Nil Volentibus Arduum
-
Hi, I've thrown out most all of your code! My code example replaces everything (it still needs port initialization). Under the assumptions I made (master-slave operation, PC sends command then peripheral replies), there is no need for events, secondary threads, Invoke, etc. If you can't turn your peripheral into a slave, which would mean it can still send results whenever it likes, then you need more of your code, however even then a delay could save your day: put it anywhere between the start of the DataReceived event and the line that holds SerialPort.ReadLine; this would guarantee (actually increase the probability) that an entire message up to and including newline is present in the serial buffer when ReadLine gets called. However now the delay also increases the probability that you would loose a message, so it makes sense to opt for a shorter delay (I would at first not go below 20 msec though). :)
Luc Pattyn [My Articles] Nil Volentibus Arduum
I have solved this problem and the code is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Diagnostics;namespace PlantMonitoringApp
{
public partial class Form1 : Form
{public SerialPort myport; private DateTime datetime; string in\_data; public Form1() { Sensor newSensor; newSensor = new Sensor(); InitializeComponent(); } private void start\_btn\_Click(object sender, EventArgs e) { myport = new SerialPort(); myport.BaudRate = 9600; myport.PortName = "COM3";//port\_name\_tb.Text; myport.Parity = Parity.None; myport.DataBits = 8; myport.StopBits = StopBits.One; myport.DataReceived += Myport\_DataReceived1; try { myport.Open(); time\_text\_box.Text = ""; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } time\_text\_box.Text = ""; } void Myport\_DataReceived1(object sender, SerialDataReceivedEventArgs e) { in\_data = myport.ReadLine(); this.Invoke(new EventHandler(displaydata\_event)); } private void displaydata\_event(object sender, EventArgs e) { datetime = DateTime.Now; string time = datetime.Hour + ":" + datetime.Minute + ":" + datetime.Second; time\_text\_box.Text = time; string\[\] sensorData = in\_data.Split(new char\[\] { ' ', ' ' }); List tokens = new List(); // int tmp1 = int.Parse(tokens\[0\]); try { foreach (string s in sensorData) { if (s.Length != 0) { tokens.Add(s); } } txtTemperature.Text = tokens\[0\]; txtHumidity.Text = tokens\[1\]; txtSoil\_moisture.Text = tokens\[2\]; // int tmp1 = int.Parse(tokens\[0\]);
-
I have solved this problem and the code is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Diagnostics;namespace PlantMonitoringApp
{
public partial class Form1 : Form
{public SerialPort myport; private DateTime datetime; string in\_data; public Form1() { Sensor newSensor; newSensor = new Sensor(); InitializeComponent(); } private void start\_btn\_Click(object sender, EventArgs e) { myport = new SerialPort(); myport.BaudRate = 9600; myport.PortName = "COM3";//port\_name\_tb.Text; myport.Parity = Parity.None; myport.DataBits = 8; myport.StopBits = StopBits.One; myport.DataReceived += Myport\_DataReceived1; try { myport.Open(); time\_text\_box.Text = ""; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } time\_text\_box.Text = ""; } void Myport\_DataReceived1(object sender, SerialDataReceivedEventArgs e) { in\_data = myport.ReadLine(); this.Invoke(new EventHandler(displaydata\_event)); } private void displaydata\_event(object sender, EventArgs e) { datetime = DateTime.Now; string time = datetime.Hour + ":" + datetime.Minute + ":" + datetime.Second; time\_text\_box.Text = time; string\[\] sensorData = in\_data.Split(new char\[\] { ' ', ' ' }); List tokens = new List(); // int tmp1 = int.Parse(tokens\[0\]); try { foreach (string s in sensorData) { if (s.Length != 0) { tokens.Add(s); } } txtTemperature.Text = tokens\[0\]; txtHumidity.Text = tokens\[1\]; txtSoil\_moisture.Text = tokens\[2\]; // int tmp1 = int.Parse(tokens\[0\]);
Hi, I see no problems; create a Sensor class that has: - a private SerialPort object; - three private numeric results (temp, moist, soilMoist); could be int, float, double,...; - a constructor taking the parameters you may need (e.g. port name) and creating the serial port; this is also where you should wire the DataReceived event, so it executes once, not for every start! - a public Start() method that does what your start button does now; you should remove the DataReceived wiring here. - a public Stop() method that does what your stop button does now; - a DataReceived event handler that does what your current one does, except it interprets the incoming string, turns the fields into numbers using
float.Parse()
orfloat.TryParse()
or something similar, and stuffs the results in the numeric class fields temp, moist, soilMoist; saving results in simple numeric variables does not violate thread safety, there will be no need to use any Invoke. - three public properties Temp, Moist, SoilMoist that offer a getter to read those results; - a public Dispose() method that calls SerialPort.Dispose(), something you have omitted so far. As a result, a Sensor instance would collect whatever you receive on the serial port from calling Start() till calling Stop() or Dispose(). And reading the result properties would immediately return the most recent value that is available (and zero if no results yet). In order to see the results in your current Form's TextBoxes, you could use a System.Windows.Forms.Timer that periodically (say once every second) gets the results and moves them into the TextBoxes, usingfloat.ToString()
. Of course you then would call Start() and Stop/Dispose() only once. Possible improvement: you could also add a private DateTime measured and a public property DateTime Measured, which will offer the DateTime when the data was last successfully updated. PS: you can format the current time simply withDateTime.Now.ToString("HH:mm:ss");
:)Luc Pattyn [My Articles] Nil Volentibus Arduum
-
Hi, I see no problems; create a Sensor class that has: - a private SerialPort object; - three private numeric results (temp, moist, soilMoist); could be int, float, double,...; - a constructor taking the parameters you may need (e.g. port name) and creating the serial port; this is also where you should wire the DataReceived event, so it executes once, not for every start! - a public Start() method that does what your start button does now; you should remove the DataReceived wiring here. - a public Stop() method that does what your stop button does now; - a DataReceived event handler that does what your current one does, except it interprets the incoming string, turns the fields into numbers using
float.Parse()
orfloat.TryParse()
or something similar, and stuffs the results in the numeric class fields temp, moist, soilMoist; saving results in simple numeric variables does not violate thread safety, there will be no need to use any Invoke. - three public properties Temp, Moist, SoilMoist that offer a getter to read those results; - a public Dispose() method that calls SerialPort.Dispose(), something you have omitted so far. As a result, a Sensor instance would collect whatever you receive on the serial port from calling Start() till calling Stop() or Dispose(). And reading the result properties would immediately return the most recent value that is available (and zero if no results yet). In order to see the results in your current Form's TextBoxes, you could use a System.Windows.Forms.Timer that periodically (say once every second) gets the results and moves them into the TextBoxes, usingfloat.ToString()
. Of course you then would call Start() and Stop/Dispose() only once. Possible improvement: you could also add a private DateTime measured and a public property DateTime Measured, which will offer the DateTime when the data was last successfully updated. PS: you can format the current time simply withDateTime.Now.ToString("HH:mm:ss");
:)Luc Pattyn [My Articles] Nil Volentibus Arduum
-
Wow, that's a lot of things :sigh: Have no clue how to transform that into code :confused:. Thanks for the reply anyways
Quote:
Have no clue how to transform that into code
I suggest you get yourself a book on C# and study that. It will teach you the language, the relevant library classes, how the create your own classes, how to work with objects, etc. I haven't seen it myself, lots of people recommend the e-book .NET Book Zero by Charles Petzold[^]. Personally I prefer a dead tree edition where you can easily navigate and annotate. And once you get to know some of the fundamentals, start looking at other people's code, there are plenty of excellent articles here at CodeProject. :)
Luc Pattyn [My Articles] Nil Volentibus Arduum