I have a problem working with the serial data received event handler. Half of the time the data displays on the textbox and half of the time does not. It should be the issue with cross thread operation.
This is my Arduino code:
int Loop = 1;
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(Loop);
Loop++;
delay(1000);
}
And here is my C# 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 arduino_test
{
public partial class Form1 : Form
{
SerialPort sPort;
public Form1()
{
InitializeComponent();
initialiseArduino();
}
public void initialiseArduino()
{
sPort = new SerialPort();
sPort.BaudRate = 9600;
sPort.PortName = "COM16";
sPort.Open();
//sPort.DataReceived += new SerialDataReceivedEventHandler(sPort_DataReceived);
}
void sPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string data = sp.ReadExisting();
displayMessage(data);
}
public void displayMessage(string data)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(displayMessage), new object[] { data });
return;
}
textBox1.Text = data;
}
private void button1_Click(object sender, EventArgs e)
{
while (true)
{
string data = sPort.ReadLine();
textBox1.Text = data;
}
}
}
}
When i use the serial data received event handler, it gives me that problem even after invoking.
So i tried running a same thread operation by clicking a button and it works perfectly fine.
Can anybody advise me on what have i done wrong?
The obvious difference and the cause of your problem is the two different way you do this. You use ReadExisting() in your DataReceived event handler but ReadLine() in your Click event handler.
ReadExisting() just doesn't do what you hope it does, you only get 1 or 2 characters. Whatever is "existing", never much since the DataReceived event fires quickly and modern desktop computers are very fast. Then the event fires again and you read another 1 or 2 chars. Your TextBox only shows whatever came last.
Use ReadLine() instead.
You might want to update displayMessage method with
public void displayMessage(string data)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(displayMessage), new object[] { data });
return;
}
**textBox1.Text = textBox1.Text + data;**
}
In this way you'll never clear the textBox1 content and you'll have all values.
Please take in consideration (depends by your data) that your incoming data could contain control chars or other things that could not be nicely shown in textbox control.
Related
I'm new to C sharp writing a program to read data from a serial port. I'm used to writing in C and admittedly do not have much experience with object oriented languages. I want a piece of code to run to open a serial port without having to be called, ie. it will run as if it's in the int main() in C. I have the code below to try to figure out my problem.
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 Nav_Monitor
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void t_tick(object sender, EventArgs e)
{
textBox1.Text += Environment.NewLine;
textBox1.Text += DateTime.Now.ToString("hh:mm:ss tt");
}
SerialPort serialPort;
private void openSerialPort(object sender, EventArgs e)
{
serialPort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One);
serialPort.Handshake = Handshake.None;
}
}
}
if I move the code outside of the method openSerialPort then I get an error 'the name "serialPort" does not exist in the current context'.
serialPort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One);
serialPort.Handshake = Handshake.None;
private void openSerialPort(object sender, EventArgs e)
{
}
So do I need something analogous to main() in C to run code automatically? It doesn't seem like I should put code in my program.cs file which seems to be the analogous place. I'm lost!
The most analogous method is in fact inside Program.cs. Main() is the entry point for C# applications. However, you might consider kicking off any background work after the page has loaded or in the constructor if the work is non-blocking and can be run in the background.
I see you're working in Windows Forms, where the Form.Load event may be a good place to open your serial port.
im trying to do a program that read a string from a website e send it to another. the process to read string for the first works correctly, and also the function to send a string to the other works, but have problem when this function was called from the timer..
here is part of 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.Timers;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using CefSharp.WinForms.Internals;
namespace CodePinger
{
public partial class Form1 : Form
{
public bool login = true;
private static System.Timers.Timer TimerCheck;
public Form1()
{
InitializeComponent();
TimerCheck = new System.Timers.Timer(5000);
TimerCheck.Elapsed += new ElapsedEventHandler(CheckEvent);
TimerCheck.AutoReset = true;
CheckForIllegalCrossThreadCalls = false;
webBrowser1.ScriptErrorsSuppressed = true;
chromiumWebBrowser1.Load("https://firstwebsite.com");
webBrowser1.Navigate("http://secondwebsite.com");
}
private async void CheckEvent(Object source, ElapsedEventArgs e)
{
if (login) {
string script = "document.getElementById('SecondSDISP').innerText;";
JavascriptResponse jr = chromiumWebBrowser1.EvaluateScriptAsync(script).Result;
if (jr.Success)
{
if (jr.Result.ToString().Contains("01"))
{
label2.ForeColor = Color.Red;
label2.Text = jr.Result.ToString();
sendCode(jr.Result.ToString());
}
}
else
{
label2.ForeColor = Color.Black;
label2.Text = "no data";
}
label4.Text = timer.ToString();
}
}
private void button2_Click(object sender, EventArgs e)
{
TimerCheck.Enabled = true;
TimerCheck.Start();
button2.Enabled = false;
}
public void sendCode(string code)
{
string msg = "";
if (code == "1") msg = "1 coda";
else msg = code.ToString() + " code";
var textarea = webBrowser1.Document.GetElementsByTagName("textarea")[0];
textarea.InnerHtml = msg;
textarea.Focus();
var allsvg = webBrowser1.Document.GetElementsByTagName("svg");
foreach (HtmlElement svg in allsvg)
{
if (svg.GetAttribute("className").Contains("-send"))
{
svg.InvokeMember("click");
break;
}
}
}
private void button4_Click(object sender, EventArgs e)
{
sendCode("1");
}
}
}
also after i started the timer, if i click to button4 to test the function, it works correctly. instead of when its called from timer
the error is:
System.InvalidCastException
HResult=0x80004002
Message=Specified cast is not valid.
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.UnsafeNativeMethods.IHTMLDocument2.GetLocation()
at System.Windows.Forms.WebBrowser.get_Document()
at CodePinger.Form1.sendCode(String code) in C:\Users\Flynns82\source\repos\CodePinger\Form1.cs:line 105
at CodePinger.Form1.<CheckEvent>d__9.MoveNext() in C:\Users\Flynns82\source\repos\CodePinger\Form1.cs:line 63
the indicated line are:
105) var textarea = webBrowser1.Document.GetElementsByTagName("textarea")[0];
63) sendCode(jr.Result.ToString());
can someone explain me what is the problem?
Most likely your problem is you are trying to access the WebBrowser from a non-STA thread (aka the 'UI Thread')
When you use the button4_click handler your code is running on the STA Thread (by default), however, when a Time Event handler is called back it happens in a different thread (which is not the STA one) thus you will have problems invoking/accessing properties on ActiveX/Components (who reside in the STA thread) if you do not "invoke" back into the STA.
I recommend to take a look to the following SO Question: STA vs MTA for a technical explanation.
For solving the invoke problem look into the following SO Question: Automating the InvokeRequired code pattern
On the other hand the browser exposes a NavigateComplete event, you do not need to have a time checking when the page is loaded, just hook yourself to the event and wait for it after navigate, the DOM will be stable once this event fires.
I have a stream of strings coming in via serial Port continuously at a fixed baud rate. And I need to separate a specific string every time it is received, split it into useful parts and print those parts in Winform interface. Any help and/or example is appreciated
I am receiving GPS data and have connected it to PC via UART to serial port. Setting up serial port with WinForms and receiving data is no problem. Placing filters to synthesize required messages and handling them is what I need help for.
////////////// This code is only for reference to the GUI ////////////
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;
using System.IO.Ports;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Clear_Click(object sender, EventArgs e)
{
richTextBox1.Clear();
}
private void Start_Click(object sender, EventArgs e)
{
serialPort1.Open();
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
richTextBox1.ScrollToCaret();
}
private void Read_Click(object sender, EventArgs e)
{
string data = serialPort1.ReadExisting(); //reads the whole
readbuffer
if (data.Length >= 10)
{
richTextBox1.Text = data;
}
}
private void Stop_Click(object sender, EventArgs e)
{
serialPort1.Close();
}
}
}
For Example: If you see the GPS data in the image
(link: https://www.google.com/imgres?imgurl=https%3A%2F%2Fidyl.io%2Fwp-content%2Fuploads%2F2017%2F05%2Fraw-gps-data-8.png&imgrefurl=https%3A%2F%2Fidyl.io%2Farduino%2Fhow-to%2Finterface-gps-module-arduino%2F&docid=zNdHsCmZc93QIM&tbnid=P0_E1tvN1Ur0HM%3A&vet=10ahUKEwjh9NudvqTgAhVL4eAKHYtyDHYQMwhbKA0wDQ..i&w=597&h=496&bih=938&biw=1920&q=gps%20data&ved=0ahUKEwjh9NudvqTgAhVL4eAKHYtyDHYQMwhbKA0wDQ&iact=mrc&uact=8#h=496&imgdii=P0_E1tvN1Ur0HM:&vet=10ahUKEwjh9NudvqTgAhVL4eAKHYtyDHYQMwhbKA0wDQ..i&w=597)
you will see that different messages are coming in cyclic order. I am interested in every message that starts with "$GPGGA". I want to read only this message every time it occurs, split it into useful parts, print them, and then go back to receive the next message that starts with "$GPGGA".
Note: When reception is not good, gps skips the positions in message that are reserved for actual readings so the string size gets smaller.
You could do this with a regular expression, or by splitting the received string using '$' - since each line starts with this character.
// Holds the partial/unprocessed data we've read from the serial port.
private string _serialRxString = string.Empty;
private void Start_Click(object sender, EventArgs e)
{
serialRxString = string.Empty;
serialPort1.Open();
}
private void Read_Click(object sender, EventArgs e)
{
// Add the new data to what we have.
_serialRxString += serialPort1.ReadExisting();
// Each line starts with a '$'.
string[] lines = _serialRxString.Split(
new char[] { '$' },
StringSplitOptions.None);
// Don't process the last one yet, it might not be complete.
for (int i = 0; i < lines.Length - 1; i++)
{
// Check if it's the line we're looking for.
if (lines[i].StartsWith("GPGGA"))
{
string[] values = lines[i].Split(new char[] { ',' });
// Do what you want with the values from the $GPGGA line.
}
}
// Keep the last line, since it might not be complete yet.
_serialRxString = lines.Last();
}
You also might want to subscribe to the serial port data received event. This way any data received on the serial port will automatically be processed, without having to click the read button. You can do this either through the designer or in your form constructor. Then the data received event would be the same as what's in the Read_Click function above.
To subscribe to the DataReceived event in the designer:
Or in the constructor:
public Form1()
{
InitializeComponent();
serialPort1.DataReceived += serialPort1_DataReceived;
}
The data received event then would be:
private void serialPort1_DataReceived(
object sender,
SerialDataReceivedEventArgs e)
{
// Add the new data to what we have.
_serialRxString += serialPort1.ReadExisting();
// Each line starts with a '$'.
string[] lines = _serialRxString.Split(
new char[] { '$' },
StringSplitOptions.None);
// Don't process the last one yet, it might not be complete.
for (int i = 0; i < lines.Length - 1; i++)
{
// Check if it's the line we're looking for.
if (lines[i].StartsWith("GPGGA"))
{
string[] values = lines[i].Split(new char[] { ',' });
// Do what you want with the values from the $GPGGA line.
}
}
// Keep the last line, since it might not be complete yet.
_serialRxString = lines.Last();
}
Then whenever anything is received on the serial port this function automatically gets called.
There are also some packages available for parsing NMEA messages that might be helpful. Such as this one: NmeaParser
I feel like this question comes up a lot. You've already done the hard part, that is sending/receiving data. Now you just need to parse it. Looks like from the example image, everything is comma delimited and each line ends with a Carriage Return Line Feed. See my answer to this, you should be able to build a string for each line then split it on commas and get the field you are looking for.
I have a simple form with a text box, a command button and a couple of timers. The only purpose of the form is to advise the user what is happening. The program executes all the code as required EXCEPT for the textbox changes. I know the code to implement the textbox changes is executed because the form and the command button properties change as required.
I have added this.refresh and this.textbox1.refresh to no avail.
I am new to C# and most of the time I do not have Visual Studios available, so your assistance would be most appreciated. I have read other posts on this topic and probably the answer has already been given, but I have not understood the solution.
The simplified code is given below:
//PROGRAM.CS
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Forms;
using WindowsFormsApplication1;
namespace PostBinaryFile
{
static class Program
{
/// The main entry point for the application.
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args));
}
}
}
//FORM1.CS
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;
using System.Web;
using System.Net;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
string sUrl;
string sFileName;
string sCorNo;
public Form1(string[] args)
{
sUrl = args[0];
sFileName = args[1];
sCorNo = args[2];
InitializeComponent();
timer1.Enabled = true;
timer1.Start();
timer2.Enabled = true;
timer2.Start();
}
public void PostCode()
{
InitializeComponent();
string sToken;
string sPath;
const string boundary = "----WebKitFormBoundaryePkpFF7tjBAqx29L";
try
{
//Do all general code work here.
//Alter form to show successful post to web
this.button1.Visible = true;
this.button1.Enabled = true;
this.BackColor = System.Drawing.Color.FromArgb(189,194,241);
this.textBox1.Text = sCorNo + " Outlook file saved to FuseDMS."; // this code is executed but is not reflected on the Form
this.textBox1.BackColor= System.Drawing.Color.FromArgb(189,194,241); // this code is executed but is not reflected on the Form
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
timer1.Enabled = false;
PostCode();
}
private void timer2_Tick(object sender, EventArgs e)
{
timer2.Stop();
timer2.Enabled = false;
this.textBox1.Text = "Saving Message " + sCorNo + ".";
}
private void button1_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
}
As #DavidG pointed out, you should not call InitializeComponent() periodically or even more then once, do it as the first thing in the constructor.
This is because any controls and properties that you add/set from the designer are created and initialized in this method.
Another thing to point out is Timer.Enabled = true and Timer.Start() effectively do the same thing
From: System.Windows.Forms.Timer.Enabled
Calling the Start method is the same as setting Enabled to true. Likewise, calling the Stop method is the same as setting Enabled to false.
Both timers namely timer1 and timer2 fire asynchronously and run on separate threads which are completely independent of each other. Even if timer2's tick event would be setting/refreshing the text appropriately through below code:
this.textBox1.Text = "Saving Message " + sCorNo + ".";
you can never say with guarantee that it will happen only after timer1's tick event has completed the execution of its callback method. In all likelyhood your above code is setting the text property of a dangling text box instance as your InitializeComponent function (being called from timer1's tick event) must be reinstantiating a new instance of all the form controls.
Your call to InitializeComponent function in PostCode method which gets called from tick event of timer1's tick event isn't right as it resets all the instances of form controls to new ones. It should be called only once in the constructor of the form. Just remove that piece of code and you should be good. Your PostCode function should actually look like this after you get rid of that piece of code:
public void PostCode()
{
string sToken;
string sPath;
const string boundary = "----WebKitFormBoundaryePkpFF7tjBAqx29L";
try
{
//Do all general code work here.
//Alter form to show successful post to web
this.button1.Visible = true;
this.button1.Enabled = true;
this.BackColor = System.Drawing.Color.FromArgb(189,194,241);
this.textBox1.Text = sCorNo + " Outlook file saved to FuseDMS."; // this code is executed but is not reflected on the Form
this.textBox1.BackColor= System.Drawing.Color.FromArgb(189,194,241); // this code is executed but is not reflected on the Form
}
All these comes from the idea that i want to use the SerialPort class in .Net , but the only way is by calling dll . Because i can only get interfaces from the program calling this dll. mycode is below.
i wrote a class about serialport,
public class CommClass
{
public SerialPort _port;
private string _receivedText;
public string receivedText
{
get { return _receivedText; }
set
{
_receivedText = value;
}
}
public CommClass(string _pname)
{
portList = SerialPort.GetPortNames();
_port = new SerialPort(portList[0]);
if (portList.Length < 1)
_port= null;
else
{
if(portList.Contains(_pname.ToUpper()))
{
_port = new SerialPort(_pname);
_port.DataReceived += new SerialDataReceivedEventHandler(com_DataReceived);
}
}
}
private void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string indata = _port.ReadExisting();
receivedText = indata;
}
}
from Bytestoread i can see there r data coming in and i can get data from port.ReadExisting(), but receivedText did not change ,it did not hit the SerialDataReceived event . Is my way wrong?any suggestion?thanks
i created a dll from CommClass ,then i call it in my winform program which has a button and a textbox . Clicking the button , then i initialize the port
public Form1()
{
InitializeComponent();
}
public CommClass mycom;
private void button1_Click(object sender, EventArgs e)
{
mycom = new CommClass("com3");
mycom._port.Open();
textbox.Text=mycom.receivedText;//i add a breakpoint at this line ,
}
when hitting it , i check mycom._port.PortName is "com3", its IsOpen() is "Open" , i use virtual port to send data . i send "1111",then check the mycom._port.BytestoRead is 4, and mycom._port.ReadExisting() is "1111", but mycom.receivedText is null. My puzzle is that i have no idea when the data is coming . How to use the DataReceived event in my winform without code "using System.Io.Ports",just with reference CommClass.dll. Did i make it clear? Thanks for help.
mycom._port.Open();
textbox.Text=mycom.receivedText;//i add a breakpoint at this line ,
That code cannot work, it is a threading race bug. The DataReceived event does not fire instantly after you open the port. It will take a microsecond or so, give or take. A threadpool thread has to get started to fire the event. And of course the device actually has to send something, they usually only do so when you transmit something first.
Which clearly did not happen, your DataReceived event handler has a bug as well. It is not allowed to update the Text property of a control in that event since it runs on a worker thread. Your program will bomb with an InvalidOperationException.
You'll have to write something like this instead:
private void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string indata = _port.ReadExisting();
this.BeginInvoke(new Action(() => {
textbox.AppendText(indata);
}));
}
With the additional stipulation that you must not leave it this way, updating the Text property of a TextBox and making it visible on the screen is an expensive operation that's going to turn your user interface catatonic when the device starts transmitting data at a high rate.