I have a MySerialPort class/object accessed from FormGUI. After creating a MySerialPort object, I want to open it, and keep receiving data continuously. The data will be stored and managed in the object's data buffer. FormGUI's memoEdit will show the received codes from the MySerialPort object's buffer.
How can I use "new Thread()" with the [port].ReadExisting method?
using System;
using System.IO.Ports;
using System.Threading;
class MySerialPort
{
public SerialPort CreatePort(string portName, int portSpeed, int portParity, int portDataSize, int portStopBits)
{
// fixed values while testing
var port = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One);
return port;
}
public void OpenPort(SerialPort port)
{
port.Open();
new Thread(() => port.ReadExisting).Start();
while (true)
{
// Send to buffer
// Maybe some break condition
}
}
The Observer pattern is working now, I got the data I was expecting from the serial port, thanks Henk.
public partial class MyGUI : Form
{
private MySerialPort MyPort = new MySerialPort();
public MyGUI()
{
InitializeComponent();
MyPort.OnDataBufferUpdate += DisplayNewData;
}
public void DisplayNewData(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { DisplayNewData(sender, e); }));
}
else
{
memoEdit.Text = MyPort.DataBuffer;
}
}
}
public class MySerialPort
{
public MySerialPort()
{
// Initialize serial port
_Port.DataReceived += _Port_DataReceived;
}
public delegate void HandleDataBufferUpdate(object sender, EventArgs e);
public event HandleDataBufferUpdate OnDataBufferUpdate = delegate {};
private void _Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// Set _Port.DataBuffer from serial port buffer, then activate event below to signal form
OnDataBufferUpdate(this, EventArgs.Empty);
}
}
Related
My question is how do you update your ui, with data from serialport, while still updating other ui components? I have tried using a background worker, but it seems to block the ui, while the data is streaming in.
Image of form:
My code is:
public partial class Form1 : Form
{
static bool _continue;
static SerialPort _serialPort;
BackgroundWorker dataWorker;
string message;
public delegate void UpdateListboxData();
private void buttonPortConnect_Click(object sender, EventArgs e)
{
// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
_serialPort.Open();
_continue = true;
dataWorker = new BackgroundWorker();
dataWorker.RunWorkerAsync();
dataWorker.DoWork += StartDataWork;
}
private void StartDataWork(object sender, DoWorkEventArgs e)
{
Delegate del = new UpdateListboxData(DataRead);
this.Invoke(del);
}
private void DataRead()
{
while (_continue)
{
try
{
message = _serialPort.ReadLine();
}
catch (TimeoutException) { }
}
}
}
you have to use Serial Port DataReceived Event and Delegate
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (TextBox.InvokeRequired)
TextBox.Invoke(new myDelegate(updateTextBox));
}
public delegate void myDelegate();
public void updateTextBox()
{
int iBytesToRead = serialPort1.BytesToRead;
//Your Codes For reding From Serial Port Such as this:
char[] ch = new char[?];
serialPort1.Read(ch, 0, ?? ).ToString();
//........
}
The way to update a winforms UI is to use if (control.InvokeRequired) {...} and Invoke as shown in the example here How do I update the GUI from another thread?
You should not read data inside the invoke'd function. This is blocking your UI.
Either read data directly in "DoWork" and invoke the delegate only with data.
Or you might use the DataReceived event of SerialPort.
I am making a program that reads values on NFC cards.
I have a class that reads values from Serial comms sent by an EMBED hooked up to an NFC IC. Once data has been received, the program (in the main form) must change 'page' and process the data.
I have used User Controls as different 'pages'.
I understand that there are multiple threads and i cannot change the GUI from a different thread without using some sort of Invoke/Deleagate?. However, i am unsure on how to implement this on my code.
Here is the code i have (with omissions of irrelevant code):
SerialData Class
public class SerialData
{
public SerialPort ComPort;
public String savedText;
public SerialData()
{
ComPort = new SerialPort();
// set port data
ComPort.PortName = "COM5";
ComPort.BaudRate = 9600;
ComPort.DataBits = 8;
ComPort.RtsEnable = true;
ComPort.DtrEnable = true;
}
public void readCard()
{
try
{
savedText = "";
ComPort.Open();
ComPort.DataReceived += ComPort_DataReceived;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
}
private void ComPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
savedText += data;
}
}
Form1 Code
public partial class Form1 : Form
{
List<UserControl> Pages = new List<UserControl>();
public int CurrentPage = 0;
SerialData SerialDataDriver = new SerialData();
public Form1()
{
InitializeComponent();
Pages.Add(page1);
Pages.Add(page2);
Pages.Add(page3);
// change to first page
ChangeToPage(0);
}
private void Page1ButtonClick(object sender, EventArgs e)
{
ChangeToPage(1);
SerialDataDriver.readCard();
}
void ComPort_DataReceived(string data)
{
SerialDataDriver.ComPort.Close(); /// close ComPort
ChangeToPage(2);
}
}
Here's an example of a custom event:
public class SerialData
{
public SerialPort ComPort;
public String savedText;
public delegate void Data(string message);
public event Data NewData;
private void ComPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
savedText += data;
if (NewData != null)
{
NewData(savedText);
}
}
}
...and how to wire up and receive it in the form:
public Form1()
{
InitializeComponent();
Pages.Add(page1);
Pages.Add(page2);
Pages.Add(page3);
// change to first page
ChangeToPage(0);
SerialDataDriver.NewData += SerialDataDriver_NewData;
}
private void SerialDataDriver_NewData(string message)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate() {
SerialDataDriver_NewData(message);
});
}
else
{
// do something with "message"
this.label1.Text = message;
}
}
You can make a wrapper that implements SerialPort events using custom add and remove accessors:
using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private readonly SerialData _data;
public Form1()
{
InitializeComponent();
Closing += (sender, args) => { _data.Dispose(); };
_data = new SerialData(new SerialPort("COM5", 9600));
_data.DataReceived += Serial_DataReceived;
}
private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// do something
}
}
public class SerialData : IDisposable
{
private readonly object _locker = new object();
private readonly SerialPort _port;
public SerialData(SerialPort port)
{
_port = port ?? throw new ArgumentNullException(nameof(port));
}
#region IDisposable Members
public void Dispose()
{
_port.Dispose();
}
#endregion
public event SerialDataReceivedEventHandler DataReceived
{
add
{
lock (_locker)
{
_port.DataReceived += value;
}
}
remove
{
lock (_locker)
{
_port.DataReceived -= value;
}
}
}
}
}
I'm writing a fairly basic application, where one of the tasks is communicating with an Arduino Uno card.
I would like to write the serial communication as a separate module, so the forms only calls for the input from Arduino, and the module will handle the creating and opening of the serialPort and reading data from it.
For testing purposes I wrote a program for the Arduino which prints the elapsed milliseconds every half second to the serialPort.
I would like to populate the textBox of my Form with the output from Arduino after I press a button.
I create the serialPort in the SerialComm class, and although I attach a DataReceived event handler to it, it never seems to fire.
Here is the code for the SerialComm class:
class SerialComm
{
private List<String> availablePorts;
private SerialPort arduino;
private string receivedText;
public String[] portList
{ get
{
EnumPorts();
return availablePorts.ToArray();
}
}
public string receivedData
{
get
{
return receivedText;
}
}
public void InitialiseSerial()
{
arduino = new SerialPort();
arduino.BaudRate = 9600;
arduino.DtrEnable = true;
// Add event handler
arduino.DataReceived += new SerialDataReceivedEventHandler(arduino_DataReceived);
}
public void EnumPorts()
{
availablePorts = new List<string>();
foreach (string s in SerialPort.GetPortNames())
{
availablePorts.Add(s);
}
}
public void StartMC(SerialPort serialPort, String portName)
{
arduino = serialPort;
if (arduino.IsOpen)
{
arduino.Close();
}
else
{
//Initialise Serial Port
arduino.PortName = portName;
arduino.Open();
}
}
//This never fires==================
private void arduino_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
receivedText = arduino.ReadExisting();
}
public void CloseMC()
{
if (arduino.IsOpen)
{
arduino.Close();
arduino.Dispose();
}
}
}
In the Form I call the text from Arduino like this: (I have a button (Button1), a combobox for selecting the COM port (comboBox1) and a textBox on the Form)
public partial class Form1 : Form
{
SerialComm arduino = new SerialComm();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Arduino
arduino.InitialiseSerial();
String[] portList = arduino.portList;
foreach (String COM in portList)
{
comboBox1.Items.Add(COM);
}
if (portList.Length > 0)
{
comboBox1.SelectedItem = comboBox1.Items[0];
}
else
{
comboBox1.Text = "No microcontroller found!";
}
}
private void button1_Click(object sender, EventArgs e)
{
arduino.StartMC(serialPort1, comboBox1.SelectedItem.ToString());
//as the DataReceived never fires arduino.receivedData stays null
if (arduino.receivedData != null)
{
for (int i = 0; i < 101; i++)
{
textBox1.AppendText(arduino.receivedData);
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
arduino.CloseMC();
}
}
Do you see any reason why the event handler doesn't trigger?
Thank you very much for your help in advance.
Best Regards,
Peter
I have a serial port class, and I would like to control send/receive via my GUI, and have the GUI update based on receipt of data from the serial port (or other events). So the two relevant classes are the serial class and the main window class.
I have the code below which compiles, but I get an exception when I try to run.
public class MySerThread
{
public SerialPort serport;
public event SerialDataReceivedEventHandler newSerData;
public MySerThread()
{
serport = new SerialPort("COM1", 115200);
serport.Open();
serport.DataReceived += DataReceivedHandler;
}
public void DataReceivedHandler(object s, SerialDataReceivedEventArgs e)
{
byte[] data = new byte[serport.BytesToRead];
serport.Read(data, 0, data.Length);
// here's where I think I'm going wrong?
if(newSerData != null)
newSerData(s,e);
}
}
And then in my GUI class...
public partial class MainWindow : Window
{
MySerThread myPort;
public MainWindow()
{
// Exception triggers here
myPort.newSerData += DisplaySerDataHandler;
}
private void DisplaySerDataHandler(object sender, SerialDataReceivedEventArgs e)
{
this.ReceivedCallback(e);
}
private void ReceivedCallback(SerialDataReceivedEventArgs e)
{
if(this.someTextBlock.Dispatcher.CheckAccess())
{
this.UpdateTextBlock(e);
}
else
{
this.someTextBlock.Dispatcher.BeginInvoke(new Action<SerialDataReceivedEventArgs>(this.UpdateTextBlock), e);
}
}
private void UpdateTextBlock(SerialDataReceivedEventArgs e)
{
someTextBlock.Text = "got new data";
}
}
So, what am I doing wrong here? What is the best way to do this?
You can't access myPort without creating an instance.
MySerThread myPort = new MySerThread();
I need to update an windows form label with a string received from the serial port. I've got two problems with the code I made already.
Because the reading of the serial port needs another thread I use a delegate method to update the label text.
The first problem is that the form window wont open when I start the program (it does open when I don't call initSerialPort() in Form1_Load()).
The second problem is that it seems like it doesn't reach Debug.Write(message) when I call _self.SetText(message) in Read(). When I comment out _self.SetText(message) it does log the message but also doesn't open the form window because initSerialPort() is called in Form1_Load()
I'm kind of an noob with C#, just so you know ;)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;
using System.Diagnostics;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
delegate void SetTextCallback(string text);
private static SerialPort _serialPort;
private static Boolean _continue;
private static StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
private static Thread readThread = new Thread(Read);
private static Form1 _self;
private static Label _lbl;
public Form1()
{
InitializeComponent();
_self = this;
_lbl = label1;
}
private void Form1_Load(object sender, EventArgs e)
{
initSerialPort();
}
public void setMessage(string mes)
{
label1.Text = mes;
}
private static void initSerialPort()
{
// Create a new SerialPort object with default settings.
_serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
_serialPort.Open();
_continue = true;
readThread.Start();
readThread.Join();
_serialPort.Close();
_serialPort = null;
}
public static void Read()
{
Debug.Write("testread");
while (_continue)
{
try
{
String message = _serialPort.ReadLine();
_self.SetText(message);
Debug.Write(message);
}
catch (TimeoutException) { }
}
}
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.label1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.label1.Text = text;
}
}
}
}
Don't Join to the new thread you create. That is going to block the thread until your Read method completes, which means it never completes.