Multiple Serial Ports in C# / Trouble using List <> - c#

I have a system that sends an "at" command to a serial port and displays the return on a MessageBox.
But I needed to do this in all available serial ports. So I created a List and I'm adding all the ports on it.
I managed to send the command, but could not continue the rest of the code to catch the return because I am having trouble handling the lists. I am a beginner in C #.
Below is my current code.
The part that is commented out is what I'm struggling to continue.
This part belongs to the old code (when it was just one serial port).
public partial class Form1 : Form
{
List<SerialPort> serialPort = new List<SerialPort>();
// delegate is used to write to a UI control from a non-UI thread
private delegate void SetTextDeleg(string text);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var portNames = SerialPort.GetPortNames();
foreach (var port in portNames) {
SerialPort sp;
sp = new SerialPort(port, 19200, Parity.None, 8, StopBits.One);
sp.Handshake = Handshake.None;
//sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
sp.ReadTimeout = 500;
sp.WriteTimeout = 500;
serialPort.Add(sp);
listPorts.Items.Add(port);
}
}
private void listPorts_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
foreach (var sp in serialPort) {
// Open port
try
{
if (!sp.IsOpen)
sp.Open();
MessageBox.Show(sp.PortName + " aberto!");
sp.Write("at\r\n");
}
catch (Exception ex)
{
MessageBox.Show("Error opening/writing to serial port :: " + ex.Message, "Error!");
}
}
}
/* HELP START
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(500);
string data = sp.ReadLine();
this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data });
}
private void si_DataReceived(string data)
{
String retorno = data.Trim();
MessageBox.Show(retorno);
// Fecha a porta após pegar o retorno
sp.Close();
}
HELP END */
}
What to put in place 'sp.ReadLine ();' and 'sp.Close ();'?
I don't know do this because of the List <>

The simplest approach would be to use a lambda expression which would capture the port you're using. A lambda expression is a way of building a delegate "inline" - and one which is able to use the local variables from the method you declare it in.
For example:
foreach (var port in portNames)
{
// Object initializer to simplify setting properties
SerialPort sp = new SerialPort(port, 19200, Parity.None, 8, StopBits.One)
{
Handshake = Hanshake.None,
ReadTimeout = 500,
WriteTimeout = 500
};
sp.DataReceived += (sender, args) =>
{
Thread.Sleep(500); // Not sure you need this...
string data = sp.ReadLine();
Action action = () => {
MessageBox.Show(data.Trim());
sp.Close();
};
BeginInvoke(action);
};
serialPort.Add(sp);
listPorts.Items.Add(port);
}
A few notes about this:
Just because some data has been received doesn't mean that a whole line has, so ReadLine may still block
If you only need to show a message box, you may not need Control.BeginInvoke. (If you need to do more here, you might want to extract most of that code into a separate method which just takes a string, then create an action which would call that method.)
Are you sure you want to close the serial port as soon as the first line has been received?

You can chage your sp_DataReceived method as,
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(500);
SerialPort sp = (SerialPort)sender;
string data = sp.ReadLine();
this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data });
sp.Close();
}
and remove the sp.Close(); from si_DataReceived method.

If you want to have a Serial port value in your si_DataReceived method,
you should pass it there:
// First, add port into your delegate
private delegate void SetTextDeleg(SerialPort port, string text);
...
/* HELP START */
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(500);
SerialPort sp = (SerialPort) sender; // <- Obtain the serial port
string data = sp.ReadLine();
// Pass the serial port into si_DataReceived: SetTextDeleg(sp, ...
this.BeginInvoke(new SetTextDeleg(sp, si_DataReceived), new object[] { data });
}
// "SerialPort sp" is added
private void si_DataReceived(SerialPort sp, string data) {
String retorno = data.Trim();
MessageBox.Show(retorno);
// Fecha a porta após pegar o retorno
sp.Close();
}
/* HELP END */
See also:
http://msdn.microsoft.com/library/system.io.ports.serialport.datareceived.aspx

Related

Is there code to receive data from a serial port?

I am working on a program that receives data from a serial port and displays this on a GUI. I am very new to C# and this is a part of a project I am working on. I have tried to look for code for the past few weeks and watched every tutorial on this matter, all to fail. I am still very new to programming and OOP.
The issue I am having is that the received data is not being pasted in the display box. I have verified that the serial port works using Putty so that cannot be the issue.
Edit: Quick update, upon learning how to use the meter I am working with and closer inspection of the user manual, I discovered that I was able to connect to the meter using serialportname.open(). The issue was that I was not requesting data. For example, by writing "READ?" into the meter, a reading would be returned.
I see that you're not using the DataReceived event.
It could be an approach to what you're trying to achieve; it will trigger each time your serial port receives data, so you could use it to insert into the textbox1
private void serialPort1_DataReceived(object sender,SerialDataReceivedEventArgs e)
{
string Data= serialPort1.ReadLine();
displayToTxt(Data);
}
private void displayToTxt(string Data)
{
BeginInvoke(new EventHandler(delegate
{
textBox1.AppendText(Data);
}));
}
I used delegate to avoid thread errors.
first you need to add EventHandler
SerialPort1.DataReceived += new SerialDataReceivedEventHandler(ReceiveData);
then get data and update UI
public void ReceiveData(object sender, SerialDataReceivedEventArgs e)
{
System.Threading.Thread.Sleep(30);
SerialPort _SerialPort = (SerialPort)sender;
int _bytesToRead = _SerialPort.BytesToRead;
byte[] recvData = new byte[_bytesToRead];
_SerialPort.Read(recvData, 0, _bytesToRead);
this.BeginInvoke(new SetTextDeleg(UpdateUI), new object[] { recvData });
}
private void UpdateUI(byte[] data)
{
string str = BitConverter.ToString(data);
textBox1.Text += str;
}
I know this is old. But since it is still showing up in searches, I thought I would add my answer.
As mentioned in another answer, you need to assign a handler to the serial port.
public void OpenPorts()
{
foreach (string nm in SerialPort.GetPortNames())
{
SerialPort sp = new()
{
PortName = nm,
ReadBufferSize = 2048,
DiscardNull = true,
RtsEnable = true,
DtrEnable = true,
ReceivedBytesThreshold = 1
};
try
{
//This should throw an exception if the port is already open.
sp.Open();
sp.DataReceived += DataReceived;
//Make sure the buffer is empty
if (sp.BytesToRead != 0)
{
sp.DiscardInBuffer();
}
}
catch (Exception ex)
{
//Handle the exception here
}
}
public void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
while ((sender as SerialPort).BytesToRead > 0)
{
SerialBuffer += (sender as SerialPort).ReadExisting();
}
(sender as SerialPort).DiscardInBuffer();
}
catch (InvalidOperationException ex)
{
_ = MessageBox.Show("exception thrown at DataReceived.", "Crashed", MessageBoxButton.OK, MessageBoxImage.Information);
}
}

Multiple Serial Port listening in C#

There are loads of questions about listening of devices on Serial Port using C#.
However I couldn't find something about my problem.
Connected two serial port device one transmitter and one receiver.
When i start one application exe and check two port for listen async, after a
while the reading stops for one of the com ports.
There is no problem when I start separate application for each port.
ScreenShoot
public Form1()
{
InitializeComponent();
// Get a list of serial port names.
string[] ports = SerialPort.GetPortNames();
foreach (var item in ports)
{
checkedListBox1.Items.Add(item);
}
}
private void btnListenPort_Click(object sender, EventArgs e)
{
doWork(checkedListBox1);
}
private async Task doWork(CheckedListBox cbl)
{
try
{
foreach (var itemChecked in cbl.CheckedItems)
{
string sPort = itemChecked.ToString();
await Task.Run(() =>
{
ListenAsync(sPort);
});
}
}
catch (Exception ex)
{
Console.WriteLine("Exception occurred: {0}", ex.Message);
}
}
private void ListenAsync(string strPort) {
var serialPort = new SerialPort(strPort, 9600, Parity.None, 8, StopBits.One);
serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
serialPort.Open();
}
private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort spL = (SerialPort)sender;
string incomSting = spL.ReadLine();
setText(spL.PortName + " " + incomSting);
}
delegate void serialCalback(string val);
private void setText(string val)
{
if (this.richTextBox1.InvokeRequired)
{
serialCalback scb = new serialCalback(setText);
this.Invoke(scb, new object[] { val });
}
else
{
richTextBox1.Text += Environment.NewLine;
richTextBox1.Text += val + Environment.NewLine;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
'''
If you use spL.ReadLine() to read data, you must add a new line ("\n") at the end of your data.
ex: spL.Write("your transmitte data \n");
Solution: Try to replace with spL.ReadLine() with spL.ReadExisting()

C# SerialPort class receives incorrect data

I currently have a program made using VB6 code that uses the MSCOMM control to pull back data from the serial port. This manages to successfully receive the data from my serial port, in which a Denso BHT-904B device is connected.
I am now trying to move this code over to C# so it fits in with a new piece of software that i am developing. To do this i am using the SerialPort class. However, the issue is that when i open the port up the data received event only fires when the device fails to communicate (which im guessing is due to a timeout). The data then received in the event is '↑↑↑↑↑'.
My SerialPort control settings are the following:
DtrEnable = True
PortName = COM3
ReadBufferSize = 1024
WriteBufferSize = 512
The code that i am using behind my form control is:
namespace BHTTestingDotNet
{
public partial class Form1 : Form
{
private string rxString;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
serialPort.DtrEnable = true;
serialPort.Encoding = Encoding.Default;
serialPort.DataReceived += serialPort_DataReceived;
serialPort.ErrorReceived += serialPort_ErrorReceived;
serialPort.Open();
}
private void serialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
MessageBox.Show(e.ToString());
}
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var test = serialPort.BytesToRead;
SerialPort sr = (SerialPort)sender;
rxString = sr.ReadExisting();
this.BeginInvoke(new EventHandler(displayText));
}
private void displayText(object o, EventArgs e)
{
txtBHT.AppendText(rxString);
}
}
}
I have already tried to set both RtsEnable and DtrEnable to true but that didn't make any difference.
UPDATE - I have now changed to protocol settings on the device but i now only receive pipes and then a return symbol, for example like so:
|||||¬
I am using SerialPort class often and for my purposes I have made my own class
public class SerialPortDataSource : SerialPort
where SerialPort.DataReceived handler invoke this method:
private void SerialPortDataSource_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (BytesToRead > 0)
{
var buffor = new byte[BytesToRead];
Read(buffor, 0, buffor.Length);
_receivedBytes = buffor;
//wConsole.WriteLine(ArrayExtension.ToString(buffor));
var dataLogger = DataLogger;
if (dataLogger != null)
{
dataLogger.WriteLine("- DR - {0}", true, BitConverterExtension.ToHexString(buffor));
}
if (OnDataReceived != null)
{
OnDataReceived(this, buffor);
}
}
}
catch (InvalidOperationException)
{
// sometimes DataReceived event is invoked after port is closed which causes InvalidOperationException
}
}
This method is working for me in many applications with variety serial port settings.

how to call received data in Serial Port C#

i'm newbie in C# serial port...
i have a virtual serial port driver and try this code...
private string strPortData = null;
private void okButton_Click(object sender, EventArgs e)
{
if (!serialPort1.IsOpen)
{
serialPort1.Open();
}
string strPortData= "CMD1";
serialPort1.WriteLine(strPortData);
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
textBox1.Text = serialPort1.ReadLine();
}
but do not serialPort1_DataReceived ever call.
What should i do for call DataReceived?
Try creating a new console application with code similar to the following
void Main()
{
using (SerialPort serialPort1 = new SerialPort("COM1"))
using (SerialPort serialPort2 = new SerialPort("COM2"))
{
serialPort1.DataReceived += (sender, args) => {
Console.WriteLine("COM1 Received: " + serialPort1.ReadLine());
};
serialPort2.DataReceived += (sender, args) => {
Console.WriteLine("COM2 Received: " + serialPort2.ReadLine());
};
serialPort1.Open();
serialPort2.Open();
serialPort1.WriteLine("Hello, COM2!");
Thread.Sleep(200);
}
}
The above code opens both serial ports, sets up the data received events, and sends data through it. If you run that code you should see "COM2 Received: Hello, COM2!" output.

serialport and dispatchertimer, how to make my thread pause when com port is open?

i have a serial port that will iterate through the ports with this method:
foreach (string s in SerialPort.GetPortNames())
{
var serialOneOfMany = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
if (serialOneOfMany.IsOpen)
{
serialOneOfMany.Close();
}
else
{
try
{
serialOneOfMany.Open();
}
catch
{
var openSerial = new System.Timers.Timer(3100);
openSerial.Elapsed += (o, e) =>
{
serialOneOfMany.Open();
openSerial.Enabled = false;
openSerial.Dispose();
};
openSerial.Enabled = true;
}
}
if (serialOneOfMany.IsOpen)
{
string received;
try
{
lblPortNum.Content = s;
lblPortNum.Refresh();
serialOneOfMany.Write(testMessage);
serialOneOfMany.DataReceived += new SerialDataReceivedEventHandler(testSerialPort_DataReceived);
}
catch (TimeoutException e)
{
serialOneOfMany.Close();
continue;
}
}
}
so, i want to open the port, send it a message, listen for the response, then close it. as everyone knows, every comport found in GetPortNames isn't a valid serial port. so, what i've been doing is setting a timer with a dispatcher timer:
DispatcherTimer time = new DispatcherTimer();
time.Interval = TimeSpan.FromMilliseconds(3000);
time.Tick += new EventHandler(someEventHandler);
time.Start();
here's the other method handled here:
private void someEventHandler(Object sender, EventArgs args)
{
SerialPort serial = (SerialPort)sender;
if (serial.IsOpen)
serial.Close();
serial.Dispose();
//if you want this event handler executed for just once
DispatcherTimer thisTimer = (DispatcherTimer)sender;
thisTimer.Stop();
}
so, it'll open the com port, if it doesn't get a response within 3 seconds, it will close the port. the problem i'm having is that the foreach loop will just barrel through the code and open the comport several times, i'll get a message saying The COM Port is open already and can't be used. so basically it's not pausing in openSerial.
i want it to open a new serial port, and if it's not accessible, wait 3100 milliseconds and try again. how do i do that?
UPDATED CODE:
private void button1_Click(object sender, RoutedEventArgs e)
{
CheckPorts();
}
private void checkPorts()
{
SendMessage("messageToDevice1", 19200);
SendMessage("Message2", 9600);
}
private void SendMessage(string testMessage, int baudRate)
{
int baudRate = 9600;
string testMessage = "test";
txtPortName.Text = "Testing all serial ports";
foreach (string s in SerialPort.GetPortNames())
{
SerialPort newPort = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
if (!newPort.IsOpen)
{
try
{
newPort.Open();
}
catch { }
}
if (newPort.IsOpen)
{
openPorts.Add(newPort);
newPort.Write(testMessage);
newPort.DataReceived += new SerialDataReceivedEventHandler(serialOneOfMany_DataReceived);
}
else
{
newPort.Dispose();
}
}
txtPortName.Text = "Waiting for response";
tmrPortTest.Enabled = true;
}
my new problem is that it just blows through the com ports, i need it to stop for each one, take a second to listen, then close it. it just blows through the foreach loop.
now, the reason why i don't just open up the port and keep it open through all the messages is that my devices have different baud rates, and i can't adjust them to all match. so, i need to open the ports, then send messages, listen, if they don't respond to the first round of messages, then open them up at the new baudrate and send a new batch of messages. but the foreachloop doens't pause for me to listen.
I think this more or less agrees with rare's answer. The port where you receive a response (you would probably want to check the response as well) will remain open and all the others should close.
private List<SerialPort> openPorts = new List<SerialPort>();
private void button3_Click(object sender, EventArgs e)
{
int baudRate = 9600;
string testMessage = "test";
txtPortName.Text = "Testing all serial ports";
foreach (string s in SerialPort.GetPortNames())
{
SerialPort newPort = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
if (!newPort.IsOpen)
{
try
{
newPort.Open();
}
catch { }
}
if (newPort.IsOpen)
{
openPorts.Add(newPort);
newPort.Write(testMessage);
newPort.DataReceived += new SerialDataReceivedEventHandler(serialOneOfMany_DataReceived);
}
else
{
newPort.Dispose();
}
}
txtPortName.Text = "Waiting for response";
tmrPortTest.Enabled = true;
}
private void serialOneOfMany_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
txtPortName.Text = ((SerialPort)sender).PortName;
}
private void tmrPortTest_Tick(object sender, EventArgs e)
{
tmrPortTest.Enabled = false;
foreach (SerialPort port in openPorts)
{
if (port.PortName != txtPortName.Text)
{
port.Close();
port.Dispose();
}
}
}
Here's how I would do this --
First, try to open all the serial ports. The ones that actually do open are put in a list.
Assign all serial ports in the list to the same DataReceived event handler. The event handler is where you will save the port name (it's in the args) and kill the timer if you rx'd the response
Send your testMessage out all the open ports
Set just one timer for 3.1 seconds
Close the ports once the timer fires or the event handler rx's the response.

Categories

Resources