I've a serial port communication class which has the following code inside (among others but only the relevant part is shown):
public Form1 m_parent;
private delegate void ProcessPacketDelegate(byte[] packet);
public SerialPort comPort = new SerialPort();
//Constructor code
....setting baudrate, portname etc.
... setting m_parent as the main form
...setting other things
//add an event handler
comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
//constructor code end
void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int bytes = comPort.BytesToRead;
byte[] comBuffer = new byte[bytes];
comPort.Read(comBuffer, 0, bytes);
comPort.DiscardInBuffer();
m_parent.Invoke(new ProcessPacketDelegate(m_parent.ProcessPacket), comBuffer);
}
I'm instantiate this class from my main form, which address is stored in m_parent. The main form has a method called ProcessPacket, which processes the incoming packet. So far so good.
Now, I want to handle 2 serial ports so I need 2 instances of this class. However, I don't want them to use the same ProcessPacket method which could lead to packet collisions. I'd like to modify the serial port communication class in such way that in the constructor or by getsets I'd set callback method dynamically so instance #1 would invoke m_parentProcessPacket_A, instance #2 would invoke m_parentProcessPacket_B. Unfortunately this seems to be beyond my .NET skills, so any help would be great!
Thank you!
You can define a variable in constructor and use that.
if(condition)
handler = comPort_DataReceived;
else
handler = comPort_SomeElseMethod;
And then bind this
comPort.DataReceived += handler
Have a constructor like that:
YourClass(ProcessPacketDelegate process_packet)
{
// ...
this.process_packet = process_packet;
}
In your class, have also:
ProcessPacketDelegate process_packet;
Then in your comPort_DataReceived do:
m_parent.Invoke(this.process_packet, comBuffer);
In you main form when your instantiating your class do:
instance1 = new YourClass(new ProcessPacketDelegate(this.ProcessPacket_A));
instance2 = new YourClass(new ProcessPacketDelegate(this.ProcessPacket_B));
Related
After a long time i need to program again.
I need to constantly send a command through serial port from a car ecu(? data).
Then i need to receive that data which i will process to be shown on a display(thinking racing display with car parameters like temperature etc).
I need to do this constantly
I wonder before i start whats best way to do this?
1 thread for constantly asking and receiving data
main thread for showing data in screen.
(store data in buffer and save once a minute or so)
anyone has any tips a guide or so how to start on this.
i tested receiving data with terminal and i got data back so config is working.
sent ? data => i got data back.
You could just use the SerialPort class and configure the BaudRate, DataBits etc.. and then just wait for the DataReceived event to fire:
public class SerialPortReader
{
public SerialPortReader(string yourPortName)
{
var serialPort = new SerialPort() {
PortName = yourPortName,
BaudRate = 57600; //This will control the rate at what you receive the data
}
serialPort.DataReceived += new SerialDataReceivedEventHandler(OnDataReceived);
serialPort.Open();
}
}
public void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
// Process your data reading the stream with Read, ReadLine etc...
}
The approach we are using in our SerialPort Handler is, to have an AutoResetEvent to get notified as soon as there is an answer from the port.
SerialPort class of the FrameWork has a few issues with the integrated DataReceived event. It is sometimes fired when there is no complete package available (in case you defined the answer length). So you should check for the answer length you expect.
Our very stripped down implementation:
public class Serialport
{
private SerialPort _serialPort;
private List<byte> _buffer;
private AutoResetEvent _autoResetEvent;
private const int WriteTimeOut = 5;
private event EventHandler ReceivedDataChanged;
public Serialport()
{
_serialPort = new SerialPort();
// set PortName, BaudRate etc
_serialPort.Open();
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.DataReceived += ReceiveData;
}
private void ReceiveData(object sender, SerialDataReceivedEventArgs e)
{
var bytes = _serialPort.BytesToRead;
byte[] buffer = new byte[bytes];
if (_serialPort.IsOpen)
{
_serialPort.BaseStream.Read(buffer, 0, bytes);
_buffer.AddRange(buffer);
}
ReceivedDataChanged?.Invoke(this, new ReceivedBytesEventArgs(_buffer.ToArray()));
_buffer.Clear();
}
private void SendData(byte[] message, int answerLength)
{
_serialPort.ReceivedBytesThreshold = answerLength;
_serialPort.WriteTimeout = WriteTimeOut;
_serialPort.Write(message, 0, message.Length);
}
public string SendDataCommand()
{
if (_serialPort.IsOpen)
{
ReceivedDataChanged += InterpretAnswer;
SendData(message, length);
if (_autoResetEvent.WaitOne(100))
{
ReceivedDataChanged -= InterpretAnswer;
//Data Received and interpreted and send to the caller
return _requestAnswer;
}
ReceivedDataChanged -= InterpretAnswer;
}
return "Connection not open";
}
private void InterpretAnswer(object sender, EventArgs e)
{
// handle all interpretation
// Set the event
_autoResetEvent.Set();
}
}
The serialPort is initialised and opened. After that, we wire up all needed events and call the SendDataCommand() Method. This method is the public visible method which is called from some task. This calls the method SendData. As soon as there is an answer, the event is triggered and the interpretation is started. If the interpretation is done in the specified amount of time (_autoResetEvent.WaitOne(msToWait)) the result is given back to the calling method.
This should be done in a separate task, so the ui will not Block while you wait for the answer
As mentioned, this is a very stripped down example. You should do more checking in the received handler of SerialPort, because there are some issues with the event. With this approach you will have a bit more of abstraction to your business logic.
Hope this helps.
I am very new to c#, and I'm creating a serial port class for a board I have designed. In which this class contains methods to open/close a serial port connected to the board. It should also read messages from the board and write messages from the UI to the board (I am using a forms application to input and display values).
I read the internal input buffer and place the bytes into my own software buffer, when a message is complete, this will prompt the form to analyse the message...
For this I have created an indexer to point to the array (from the form) and take the bytes that it desires.
uint[] serialPortReceiveBuffer = new uint[3];
public delegate void Del();
Del promptFormAction = Form1.MsgReceived;
public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
for (int i = 0; i <= 2; i++)
{
serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
}
promptFormAction();
}
public uint this[uint i]
{
get { return serialPortReceiveBuffer[i]; }
}
this is the code within my pcbSerialPort class, and the code related to it in the Form1 class is as follows:
public static void MsgReceived()
{
Form1 _frm = new Form1();
_frm.analyzeIncomingMessage();
}
public void analyzeIncomingMessage()
{
if (PCB[0] == 63)
{
setBoardDesignator(PCB[1], PCB[2]);
}
}
My problem is that the when I use the indexer to access the serialPortReceiveBuffer, it doesn't see the changes that I made to it when placing received bytes into the same array. For example, when I receive a string of my own protocol --> "?10" the buffer is filled with [63][49][48]
Though when I try to access this buffer using the indexer I get [0][0][0]
Please can anyone help? Also, I'm aware there is probably a few other things I could have done better so if you have any general tips that would be great. Also in a language I may understand. I am just getting my head around many of the c# aspects, I have been doing embedded software for the past year but I wouldn't consider my self to be a competent programmer.
Thank you
From your code I'm not quite sure that the PCB object you're working with in your form is actually the one that receives the data. Might well be that you're working with two different instances, especially as you're creating a new instance of Form1 whenever data comes in!
(EDIT: From your comment to the question it is clear that this is exactly the problem. Follow these instructions to get closed to what you want).
I suggest that you redesign your code to pass the received message as an event to the existing form instance instead of how you do it now. Another problem you might run into will be that data you think you get will be overridden by the next message coming in, das the DataReceived event is asynchronous.
I'd declare an event that the form instance can subscribe to, passing the data to be analyzed into the event:
public class MessageReceivedEventArgs: EventArgs
{
public MessageReceivedEventArgs(byte[] data) : base()
{
Data = data;
}
public byte[] Data
{
get;
private set;
}
}
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
Then, I'd change your DataReceivedevent as follows:
public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
for (int i = 0; i <= 2; i++)
{
serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
}
byte[] dataCopy = new byte[serialPortReceiveBuffer.Length];
Array.Copy(serialPortReceiveBuffer, dataCopy, dataCopy.Length);
promptFormAction(dataCopy);
}
private void promptForAction(byte[] data)
{
if (MessageReceived != null)
MessageReceived(this, new MessageReceivedEventArgs(data));
}
Also I'd keep the serialPortReceiveBuffer totally private to that class, as I said, you may run into synchronization issues if you don't. That'y why I copy the array before passing it to the event.
This change allows any subscriber to register for notifications whenever you realize that new data came in.
To use this, Form1 should look like this (roughly);
public class Form1
{
pcbSerialPort PCB; // The name of that class I don't know from your code
public Form1()
{
PCB = new pcbSerialPort();
PCB.MessageReceived += MessageReceived;
}
private void MessageReceived(object sender, pcbSerialPort.MessageReceivedEventArgs e)
{
analyzeIncomingMessage(e.Data);
}
private void analyzeIncomingMessage(byte[] data)
{
if (data[0] == 63)
{
setBoardDesignator(data[1], data[2]);
}
}
}
Another piece of advice on how you handle serial data: You need to decide whether you read from a serial port in a loop or whether you rely on the DataReceived event. Putting a loop into the event is not a good idea, as the event may be called upon arriving data again while you're waiting.
What you need to do is create a buffer that takes all the information from the serial port that's available. If you don't have enough data, don't wait for it. Instead add to the buffer whenever DataReceived is called and handle a message when enough data is present.
I think Thorsten's answer is good and it would make a lot of sense to redesign it along those lines, but as an absolute minimum, if you want to have it such that you create a new Form1 instance for every received message, then you will need to pass the instance of pcbSerialPort to MessageReceived and then to the constructor of your Form1 class. Something like:
Action<pcbSerialPort> promptFormAction = Form1.MsgReceived;
public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// as Thorsten noted, you need to rethink this loop anyway
// what if there aren't at least three bytes to read?
for (int i = 0; i <= 2; i++)
{
serialPortReceiveBuffer[i] = (uint)serialPort1.ReadByte();
}
promptFormAction(this);
}
And your static method:
public static void MsgReceived(pcbSerialPort pcb)
{
Form1 _frm = new Form1(pcb);
_frm.analyzeIncomingMessage();
}
And you constructor for Form1:
public Form1(pcbSerialPort pcb)
{
PCB = pcb;
}
I have two objects (of the same class) which each contain a SerialPort object. The class has a method which handles the SerialPort.DataReceived event and is used by both SerialPort objects.
When I instantiate each object in a separate application, each port handles its DataReceived event individually as expected.
When I instantiate two instances of the COM_Front_End class in the same application and send data from one serial port to the other, both port's DataReceived event handlers fire. For short, I'll call this "cross-talk".
My class structure looks something like this:
public class COM_Front_End
{
private SerialPort_custom port;
private LockObject;
public COM_Front_End(string PortName, string BaudRate)
{
// Other code
port = new SerialPort_custom(PortName, BaudRate, new SerialDataReceivedEventHandler(SerialDataReceived));
port.Open();
}
private void SerialDataReceived(object sender, SerialDataReceivedEventArgs e)
{
//lock (LockObject) // A lock is not needed here. Only one SerialDataReceived event can fire at a time
//{
SerialPort port;
try
{
port = sender as SerialPort;
if (port != null)
{
byte[] buffer = new byte[port.BytesToRead];
int bytesRead = port.Read(buffer, 0, buffer.Length);
foreach (byte inByte in buffer)
{
// Byte processing code
}
}
}
catch (Exception ex)
{
// Exception handling code
}
//}
}
}
The class containing the actual SerialPort class looks like:
public class SerialPort_custom : SerialPort
{
public SerialPort_custom(string PortName, int BaudRate, SerialDataReceivedEventHandler DataReceivedHandler)
{
this.PortName = PortName;
this.BaudRate = BaudRate;
this.Parity = System.IO.Ports.Parity.None;
this.DataBits = 8;
this.StopBits = System.IO.Ports.StopBits.One;
this.Handshake = System.IO.Ports.Handshake.None;
this.RtsEnable = true;
this.DtrEnable = true;
this.DiscardNull = false;
this.Encoding = Encoding.ASCII;
this.DataReceived += DataReceivedHandler;
}
// Other methods
}
I have two instances of the COM_Front_End class in the same application. Whenever one instance receives data, both objects' SerialDataReceived methods fire.
Why does the DataReceived event handler fire for both serial ports when they are instantiated in the same application? Furthermore, how can I ensure that multiple instantiation of this class does not cause "cross-talk"?
I've found the root cause of my problem:
The project in which the COM_Front_End resides has two static classes. One of these classes is the Receive Buffer and the other the Transmit Buffer. Changing these classes so that they are not static solved my problem. Within each COM_Front_End object is a task which polls the Receive Buffer. Since they both use the same static class, they both were pulling from this buffer which explains why
A. The SerialDataReceived for both objects fired.
B. The data received for each was mangled/partial.
TL;DR: Non-static objects containing static objects will yield shared resources whether it is intended or not.
Please correct me wherever my explanation is faulty or incomplete.
For multiple instances, when event handlers are declared like below, do they tend to overlap and set all the instances of a variables
For say,
Something class = new Something();
is declared ~ 3 times and on all 3 times, the eventhandlers are declared and used.
public class something
{
public string str = "1";
public string str2 = "2";
public void onconnect(object sender, ConnectedEventArgs e)
{
if (e.Connected)
{
this.ses = e.NetworkSession;
this.ses.OnHandshakeHandler += new EventHandler<HSEventArgs>(OnHandshakeHandler);
this.ses.onreceive+= new EventHandler<PEventArgs>(onreceive);
return;
}
}
}
now lets say
public void onreceive(object sender, PEventArgs e)
{
str = "3";
}
Would str all be turned to "3" onrecieve? Because in my case, i'm starting to believe the eventhandler is shared between all instances of the class. Therefore rendering all variables in all instances of the class to be the same.
If this is not the case, could eventhandlers be the reason behind why my variables are all getting changed by the same event raised?
If so, how would you isolate the class and assign the handler to each individual class?
onconnect is called by
public void Connect(string ip, short port)
{
try
{
this.connector = new Connector();
this.connector.OnClientConnected += new EventHandler<ConnectedEventArgs>(onConnect);
this.connector.Connect(ip, port); //Logs into server
}
catch
{
updateLog("[Error]Servers exploded!!!", this);
}
}
and ^ is called when a button is pushed on a gui
If I read the documentation correctly (http://msdn.microsoft.com/en-us/library/bb975820.aspx) there will be one NetworkSession per user. You are one user, so you have one NetworkSession.
You created three instances of Something, and I get the feeling they are all called by the same event: the software os connected to the game network.
All 3 onconnect methods get in e the same instance of NetworkSession and you connect your eventhanlder onreceive to NetworkSession.onreceive.
So if the NetworkSession receives something, all the 3 eventhandlers of your 3 instances are called.
The creating a seperate NetworkSession per instance by using NetworkSession.BeginCreate (http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.net.networksession.begincreate.aspx).
After much research I am still stumped. I have a serial port object which is reading data continuously. What I am able to do it generate the dataReceived event, communicate with the port, and output the received values to the debug window. So, I'm pretty sure it's all working physically. The problem is when I try to pass the serial port output to my original thread I get an error. It says I can't have thread cross talk (or something to that effect). I've been trying to use a backgroundWorker but I'm not sure that is the solution I want plus with my novice skills it's a little over my head. And I tried to use invoke but the method doesn't seem to be available. (I might be calling from the wrong object?) Anyway section is below.
namespace Photometer
{
class csRadiometerILT1700
{
//manufacturer specs for baud rate, databits, and stop bits
static string portName="COM1";
static int baudRate = 1200;
static int dataBits = 8;
//instantialize a serial port object for the Radiometer
private SerialPort RadiometerSerial = new SerialPort(portName, baudRate, Parity.None, dataBits, StopBits.One);
//constructor
//public csRadiometerILT1700(Form ParentForm, Chart outputChart)
public csRadiometerILT1700()
{
//two handshaking properties of the ILT1700. Handshaking is enabled and
//http://stackoverflow.com/questions/6277619/problem-reading-serial-port-c-net-2-0-to-get-weighing-machine-output
RadiometerSerial.Handshake= Handshake.RequestToSend;
RadiometerSerial.DtrEnable = true;
RadiometerSerial.DataReceived += new SerialDataReceivedEventHandler(RadiometerSerial_DataReceived);
}
public void openPort()
{
if (!RadiometerSerial.IsOpen)
{
RadiometerSerial.Open();
}
}
public void closePort()
{
RadiometerSerial.Close();
}
string RadiometerVoltageReadingString;
int RadiometerVoltageReadingInt;
private void RadiometerSerial_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//It's here that this.invoke()... cannot be called.
RadiometerVoltageReadingString= (RadiometerSerial.ReadExisting().ToString()); //y-value
Debug.Print(RadiometerVoltageReadingString.ToString());
makeRadioReadingDouble(RadiometerVoltageReadingString);
}
private void makeRadioReadingDouble(string inputVoltageString)
{
List<double> outputVoltageDouble=new List<double>(2);
if (!(inputVoltageString == "\r\n" || inputVoltageString == ""))
{
string[] voltageValAndExpo = inputVoltageString.Split(new string[] { "e", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
for (int inCounter = 0; inCounter < voltageValAndExpo.Count(); inCounter=inCounter+2)
{
double voltageVal = Convert.ToDouble(voltageValAndExpo[inCounter]);
double voltageExpo = Convert.ToDouble(voltageValAndExpo[inCounter + 1]);
outputVoltageDouble.Add(Math.Pow(voltageVal, voltageExpo));
}
}
}
}
}
This is all called when I form loads with the code
csRadiometerILT1700 Radiometer;
...
Radiometer = new csRadiometerILT1700();
Radiometer.openPort();
Any insight is appreciated.
EDIT:
I altered my csRadiometerILT1700 constructor to
public csRadiometerILT1700(Form inputForm)
{
//inputForm.Invoke(
//two handshaking properties of the ILT1700. Handshaking is enabled and
//http://stackoverflow.com/questions/6277619/problem-reading-serial-port-c-net-2-0-to-get-weighing-machine-output
RadiometerSerial.Handshake= Handshake.RequestToSend;
RadiometerSerial.DtrEnable = true;
RadiometerSerial.DataReceived += new SerialDataReceivedEventHandler(RadiometerSerial_DataReceived);
inputForm.Invoke(DataReceived);
}
and declare
public event Delegate DataReceived;
in the csRadiometerILT1700 class. But this gives me the error of "Datareceived must be of a delegate type." How do I resolve this now? Am I on the right track?
Your RadiometerILT1700 class needs an event to report it's received (and processed) data.
Your Form subscribes to that event
The Forms eventhandler uses this.Invoke() to overcome the cross-threading issue.
Invoke is a method on a Delegate or a Form or Control, since csRadiometerILT1700 is none of these it is not inheriting an Invoke implemenation from those classes.
You will need to raise another event to the caller of csRadiometerILT1700 and hadle that on you GUI somewhere (along with any cross thread issues.) Alternatively, you could provide csRadiometerILT1700 with a delegate it could use to callback, kind of like a hand rolled event.
Once you have the data in your Form you can use Control.InokeRequired to detect a cross thread situation and Control.Invoke to make the cross thread call.
anytime you try to post to the main thread from a different thread you'll get this error. You need to use a delegate and Invoke on the control you want to call from the thread that isn't the main form thread.
Assuming the error is actually:
Control control name accessed from a thread other than the thread it
was created on.
The problem is you can't affect your GUI from another thread. You need to invoke the form or control to pass control so it can be modified. You should be able to interact with most other elements, but not a form.
You should not call this.Invoke there, the way you are doing it is like trying to sync your own thread with your own instead with the UI. you should call formhandle.Invoke, or inside form class register for the event and then you can use this.invoke