In the code below, the strings received within myReceivedLines appear when connecting with my serial port (when connecttodevice is true). However they disapear when I launch another command (when homeall is true).
I added the field called myReceivedLines within the class so that I could use the method String.Add() to all the feedback received and commands sent (having like a console within the program).
Why does the feedback dispear when a command is sent and how can I make sure all the strings stay in the variable myReceivedLines? Is the string going to myReceivedLine disapearing because they happen within a subscriber method? How do I solve that?
NB: GH_DataAccess.SetDataList(Int32, IEnumerable) is a method from the Kernel a software called Grasshopper to assign values to an output (it has to be used within the GH_Component.SolveInstance() method which is also from this Kernel), I am using this to visualise myReceivedLines.
code:
public class SendToPrintComponent : GH_Component
{
//Fields
List<string> myReceivedLines = new List<string>();
SerialPort port;
//subscriber method for the port.DataReceived Event
private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
while (sp.BytesToRead > 0)
{
try
{
myReceivedLines.Add(sp.ReadLine());
}
catch (TimeoutException)
{
break;
}
}
}
protected override void SolveInstance(IGH_DataAccess DA)
{
//Opening the port
if (port == null)
{
string selectedportname = default(string);
DA.GetData(1, ref selectedportname);
int selectedbaudrate = default(int);
DA.GetData(2, ref selectedbaudrate);
//Assigning an object to the field within the SolveInstance method()
port = new SerialPort(selectedportname, selectedbaudrate, Parity.None, 8, StopBits.One);
//Enables the data terminal ready (dtr) signal during serial communication (handshaking)
port.DtrEnable = true;
port.WriteTimeout = 500;
port.ReadTimeout = 500;
}
//Event Handling Method
bool connecttodevice = default(bool);
DA.GetData(3, ref connecttodevice);
**if (connecttodevice == true)**
{
if (!port.IsOpen)
{
port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
DA.SetDataList(0, myReceivedLines);
port.Open();
}
}
else
if (port.IsOpen)
{
port.DataReceived -= new SerialDataReceivedEventHandler(DataReceivedHandler);
port.Close();
}
if (port.IsOpen)
{
DA.SetData(1, "Port Open");
}
//If the port is open do all the rest
if (port.IsOpen)
{
bool homeall = default(bool);
DA.GetData(5, ref homeall);
//Home all sends all the axis to the origin
**if (homeall == true)**
{
port.Write("G28" + "\n");
myReceivedLines.Add("G28" + "\n");
DA.SetDataList(2, myReceivedLines);
}
}
else
{
DA.SetData(1, "Port Closed");
}
}
}
If you are trying to append to a string, I would reccomend a StringBuilder object.
Or the less cleaner resolution, use the += operator,
string s = "abcd";
s+="efgh";
Console.WriteLine(s); //s prints abcdefgh
First of all your variables (myReceivedLines and port) are not static. I'm not sure if you want them to be static because I can't see how your using SendToPrintComponent class.
And could you explain DA.SetDataList(0, myReceivedLines); or better yet include the code because the problem could be there...
Related
I am using .net 4.5 and since i got aware about some internet discussion about .net having wrong code implementation to rad serial data, i went with the code below.
The problems is however even despite i can see i create a COM port connection (to the right com port number), its never firing on data received.
The data receiving is based upon a simple Arduino app, (Arduino monitor does show data gets send over serial), but .net never seams to fire upon serial events.
I've put breakpoints on eventread, it never gets hit
i've looked at other discussions here like .NET SerialPort DataReceived event not firing but so far they don't resolve the issue i have. I tried various combination of serial line setups, and believe the below ones are correct.
as for Arduino part the line is setup as:
Serial.begin(9600);
I call my class like : `InfraredSensor mySens = new InfraredSensor("COM4",9600);'
class InfraredSensor
{
private string Eventlogapp = "IRlogging";
private SerialPort Port;
public InfraredSensor(string COMport, int baudrate) //constructor
{
if (applicationlog != "") this.EventlogSapec = applicationlog;
WriteEventLog(EventlogSapec, EventSource, "Initializing-IR:" + COMport, info, EventIRStarted);
// I found that the .net standard implementation for com port reading is not OK (.net doesnt follow win32 api).
// There are numerous readings about it, but a good way to read seams to be to use Serial BaseStream.ReadAsync
// code below is based upon : http://www.c-sharpcorner.com/code/2880/serial-port-with-efficient-data-reading-in-c-sharp.aspx
this.comport = COMport;
SerialPort Port = new SerialPort(comport);
Port.BaudRate = baudrate;
Port.DataBits = 8;
Port.Parity = Parity.None;
Port.StopBits = StopBits.One;
Port.Handshake = Handshake.None;
Port.NewLine = Environment.NewLine;
Port.ReceivedBytesThreshold = 2; // + linefeed
Port.DataReceived += ReadEvent;
Port.Open();
Port.DtrEnable = true;
// i've tested from here i do have an open connection
// its just dat ReadEvent never fires...
}
private void ReadEvent(object sender, SerialDataReceivedEventArgs e)
{
byte[] buffer = new byte[2];//todo i only send "A" or "B", for the debug moment
Action kickoffRead = null;
kickoffRead = (Action)(() => Port.BaseStream.BeginRead(buffer, 0, buffer.Length, delegate (IAsyncResult ar)
{
try
{
int count = Port.BaseStream.EndRead(ar);
byte[] dst = new byte[count];
Buffer.BlockCopy(buffer, 0, dst, 0, count);
RaiseAppSerialDataEvent(dst);
}
catch (Exception ex)
{
WriteEventLog(Eventlogapp, "IR", "Failure-IR:" + ex.Message, info, 204);
}
kickoffRead();
}, null)); kickoffRead();
}
private void RaiseAppSerialDataEvent(byte[] Data)
{
// code never gets to here
string msg = Encoding.Default.GetString(Data);
int breakpointhere = 0;
if (msg.Contains('A')) WriteEventLog(Eventlogapp, "IR", "Sensor A", info, 213);
if (msg.Contains('B')) WriteEventLog(Eventlogapp, "IR", "Sensor B", info, 214);
}
}
I ran out of ideas (and hair as its driving me nuts) what could cause this behaviour ?
finally this took me 2 days ...
in arduino code i now use
serial.print("Mytext\n"); // using \n to be sure of escape character
for the serial line config i now use
this.comport = COMport;
this.Port = new SerialPort(comport);
this.Port.BaudRate = baudrate;
this.Port.DataBits = 8;
this.Port.Parity = Parity.None;
this.Port.StopBits = StopBits.One;
this.Port.Handshake = Handshake.None;
this.Port.RtsEnable = true;
// Port.NewLine = "\n";
// Port.ReceivedBytesThreshold = 2; // + linefeed
this.Port.DataReceived += new new SerialDataReceivedEventHandler(ReadEvent);
....
..
SerialDataReceivedEventHandler(ReadEvent);
//code didnt change inside but the method has changed see the +=
The DataReceived event fires when there is data coming through the serial port, but it can fire randomly. So you could get part of the message in one event and the other half of the message in another event. You should have some sort of key to know when you have the whole message. So build your string until you have the whole message then do something based on the message.
For example, my device's message is done when there is a Line Feed. So my code looks something like:
char ESC = (char)27;
char CR = (char)13;
char LF = (char)10;
StringBuilder sb = new StringBuilder();
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Data = serialPort1.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sb.Append(c);
//we have our message, do something, maybe like
if (sb.ToString().Contains("A"))
{
//print A
}
else
{
//print B
}
}
else
{
sb.Append(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
I am using the SerialPort.ReadLine() method to display the data received from a Serial Port (code below).
Previously the code looked like that and received data but it did not send data. Now it is the other way around:
Since I placed the port.DataReceived event within the if(port==null) statement and added SerialPort port; as field, I don't receive data anymore. Can placing the event within an if statement change the way data is received and displayed? How can I fix that?
//Fields
List<string> myReceivedLines = new List<string>();
SerialPort port;
//subscriber method for the port.DataReceived Event
private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
while (sp.BytesToRead > 0)
{
try
{
myReceivedLines.Add(sp.ReadLine());
}
catch (TimeoutException)
{
break;
}
}
}
protected override void SolveInstance(IGH_DataAccess DA)
{
//Opening the port
if (port == null)
{
string selectedportname = default(string);
DA.GetData(1, ref selectedportname);
int selectedbaudrate = default(int);
DA.GetData(2, ref selectedbaudrate);
bool connecttodevice = default(bool);
DA.GetData(3, ref connecttodevice);
//Assigning an object to the field within the SolveInstance method()
port = new SerialPort(selectedportname, selectedbaudrate, Parity.None, 8, StopBits.One);
//Event Handling Method
if (connecttodevice == true)
{
port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
DA.SetDataList(0, myReceivedLines);
}
//Enables the data terminal ready (dtr) signal during serial communication (handshaking)
port.DtrEnable = true;
port.Open();
}
//Displays if port if opened
if (port.IsOpen)
{
DA.SetData(1, "Port Open");
}
//If the port is open do all the rest
if (port.IsOpen)
{
//Assigning the input to variables for the code.
List<string> gcode = new List<string>();
DA.GetDataList(0, gcode);
bool sendtoprint = default(bool);
DA.GetData(4, ref sendtoprint);
bool homeall = default(bool);
DA.GetData(5, ref homeall);
//What happens when input is set
if (sendtoprint == true)
{
if (homeall == true)
{
port.Write("G28" + "\n");
}
}
else
{
DA.SetData(1, "Port Closed");
}
}
Try something like this, removing the attaching of the eventhandler out of the port creation section
if (port == null)
{
string selectedportname = default(string);
DA.GetData(1, ref selectedportname);
int selectedbaudrate = default(int);
DA.GetData(2, ref selectedbaudrate);
bool connecttodevice = default(bool);
DA.GetData(3, ref connecttodevice);
//Assigning an object to the field within the SolveInstance method()
port = new SerialPort(selectedportname, selectedbaudrate, Parity.None, 8, StopBits.One);
//Enables the data terminal ready (dtr) signal during serial communication (handshaking)
port.DtrEnable = true;
}
if (connecttodevice == true)
{
if(!port.IsOpen)
{
port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
DA.SetDataList(0, myReceivedLines);
port.Open();
}
}
else
{
if(port.IsOpen)
{
port.DataReceived -= new SerialDataReceivedEventHandler(DataReceivedHandler);
// DA.SetDataList(0, myReceivedLines); // Not sure how you want to remove this
port.Close();
}
}
The code below builds with no errors, the connection to the device seems to work too although I do not get any feedback from the device.
on V.S., I have placed a break point at the linemyReceivedLines = sp.ReadExisting(); and the variable myReceivedLines comes back null.
On another similar program connecting to the same device, couple lines of feedback appear (see below), why is this variable null in my case?
Lines that appear on other program:
Connecting...
start
Printer is now online.
echo:Marlin: 1.0.0 RC2
echo: Last Updated: 2012-05-22-1 | Author: eMAKER
...etc...
Code:
//Fields
string myReceivedLines;
//subscriber method for the port.DataReceived Event
private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
myReceivedLines = sp.ReadExisting();
}
protected override void SolveInstance(IGH_DataAccess DA)
{
List<string> gcode = new List<string>();
DA.GetDataList(0, gcode);
string selectedportname = default(string);
DA.GetData(1, ref selectedportname);
int selectedbaudrate = default(int);
DA.GetData(2, ref selectedbaudrate);
bool connecttodevice=default(bool);
DA.GetData(3, ref connecttodevice);
bool sendtoprint= default(bool);
DA.GetData(4, ref sendtoprint);
if (!DA.GetDataList(0, gcode)) return;
if (!DA.GetData(1, ref selectedportname)) return;
if (!DA.GetData(2, ref selectedbaudrate)) return;
if (!DA.GetData(3, ref connecttodevice)) return;
if (!DA.GetData(4, ref sendtoprint)) return;
SerialPort port = new SerialPort(selectedportname, selectedbaudrate, Parity.None, 8, StopBits.One); //Create the serial port
port.DtrEnable = true; //enables the Data Terminal Ready (DTR) signal during serial communication (Handshaking)
port.Open(); //Open the port
port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
if (gcode == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Specify a valid GCode");
return;
}
if (connecttodevice == true)
{
DA.SetDataList(0, myReceivedLines);
}
else
{
port.Close();
}
if (sendtoprint == true)
{
foreach (String s in gcode)
{
port.WriteLine(s);
}
}
}
if (connecttodevice == true)
{
DA.SetDataList(0, myReceivedLines);
}
SerialPort.ReadExisting() cannot return null, at worst you'll get an empty string. The simple explanation is that you are using myReceivedLines before any data was received. Which is very likely in this case, you are using it right after you open the port. The odds that DataReceived will fire before you use myReceivedLines are vanishing small. The code is pretty inscrutable, you'll need to substantially revise it. Just keep in mind that the serial port will receive data at an entirely unpredictable moment in time. You'll need to have your DataReceived event handler push progress.
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.