I grew up with C in the 80's but a complete beginner in c# and object oriented programming so some of you may have a good laugh at this.
I am trying to write a simple application to decode telemetry data from a lab instrument. I plan to write the final code using Windows Forms for look and feel but I made an experimental implementation of the core mechanics as a console app.
The data stream contain 72 byte messages starting with 0x55 0x2f 0x48, then contains payload data and ends with a checksum. I want to "catch" these messages, discard those with bad checksums and decode parts of the payload for display.
The test app work well but simply prints the incoming data. (Although because of its primitive design it does of course take 0x55 in the payload as the start of a new message)
What would be the best practice here? Should I put a state machine into the event handler for the serial port or have a timer that periodically checks the input buffer? I am actually not even sure how to access to the incoming data "outside" the event handler. Any help or pointers would be much appreciated.
The code of my experimental app is this:
using System;
using System.IO.Ports;
namespace Serial_port_experiment_console
{
class Program
{
static void Main(string[] args)
{
var enable_telemetry = new byte[] { 0x55, 0x92, 4, 0x15 };
var disable_telemetry = new byte[] { 0x55, 0x91, 4, 0x16 };
SerialPort sp = new SerialPort("COM12", 9600, Parity.None, 8, StopBits.One);
sp.Open();
sp.Write(enable_telemetry, 0, enable_telemetry.Length); // Start telemetry
sp.DataReceived += port_OnReceiveData;
Console.ReadLine(); // Wait for keyboard RETURN before closing program
sp.Write(disable_telemetry, 0, disable_telemetry.Length); // Stop telemetry
sp.Close();
}
private static void port_OnReceiveData(object sender, SerialDataReceivedEventArgs e)
{
SerialPort spL = (SerialPort)sender;
byte[] buf = new byte[spL.BytesToRead];
spL.Read(buf, 0, buf.Length);
foreach (Byte b in buf)
{
if (b == 0x55) { Console.WriteLine(); } // Start of a new report
Console.Write(b.ToString("X")); // Write the hex code
}
}
}
}
Some encapsulation would help you - I would separate the logic of the reading from the port from the actual line processing into a couple of classes. Something like this:
static void Main(string[] args)
{
Console.WriteLine("Starting reading data");
var reader = new LinesReader();
Console.WriteLine("Reading data...");
try
{
reader.Start();
Console.ReadKey();
}
finally
{
reader.Stop();
}
foreach (var line in reader.ValidLines)
{
line.DoSomethingWithBytes();
}
Console.ReadKey();
}
internal sealed class LinesReader
{
private static readonly byte[] EnableCode = new byte[] { 0x55, 0x92, 4, 0x15 };
private static readonly byte[] DisableCode = new byte[] { 0x55, 0x91, 4, 0x16 };
private readonly SerialPort sp;
private readonly List<LineInput> lines = new List<LineInput>();
public LinesReader()
{
sp = new SerialPort("COM12", 9600, Parity.None, 8, StopBits.One);
lines.Add(new LineInput());
}
public void Start()
{
sp.DataReceived += DataReceived;
sp.Open();
// Start telemetry
sp.Write(EnableCode, 0, EnableCode.Length);
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
LineInput current;
byte[] buf = new byte[sp.BytesToRead];
sp.Read(buf, 0, buf.Length);
for (int offset = 0; offset < buf.Length; )
{
current = GetCurrentLine();
offset = current.AddBytes(offset, buf);
}
}
private LineInput GetCurrentLine()
{
if (lines.Count == 0 || lines[lines.Count - 1].IsComplete)
{
var ret = new LineInput();
lines.Add(ret);
Console.WriteLine($"Starting line {lines.Count}");
return ret;
}
return lines[lines.Count - 1];
}
public IEnumerable<LineInput> ValidLines => lines.Where(e => e.IsValid);
public void Stop()
{
// Stop telemetry
sp.Write(DisableCode, 0, DisableCode.Length);
sp.DataReceived -= DataReceived;
sp.Close();
}
}
internal sealed class LineInput
{
private readonly List<byte> bytesInLine = new List<byte>();
public static byte StartCode { get; } = 0x55;
public bool IsComplete { get; private set; }
public void DoSomethingWithBytes()
{
//Here I'm just printing the line.
Console.WriteLine(bytesInLine);
}
public bool IsValid
{
get
{
if (!IsComplete) return false;
//TODO - checksum (return true if checksum correct)
return true;
}
}
/// <summary>
/// Adds bytes until the end of the array or a 0x55 code is read
/// </summary>
/// <param name="bytes"></param>
/// <returns>The new offset to start the next segment at.</returns>
public int AddBytes(int offset, byte[] bytes)
{
int bytePosition = offset;
while (bytePosition < bytes.Length)
{
var currentByte = bytes[bytePosition];
if (currentByte == StartCode)
{
IsComplete = true;
break;
}
bytesInLine.Add(currentByte);
bytePosition++;
}
return bytePosition;
}
}
It probably isn't best practice to put all the logic in the event handler. I would suggest that port_OnReceiveData should only be concerned with extracting the 72 byte message. Then you might have another method (or possibly another class) which accepts this message and parses it and extracts whatever data it contains and then in turn may pass that data to another method or class for further processing or saving to a database etc.
I would start by developing port_OnReceiveData to reliably capture the incoming messages. I think you should check for sequence 0x55 0x2f 0x48 rather than just 0x55. Presumably 0x55 could also appear anywhere in the payload, not just the start. Can you contact the manufacturer of the device (or check their website) for details of the message protocol?
A serial port is a difficult thing to test so the idea would be to limit the amount of code dependent on the SerialPort logic so that you can test the actual code you care about (i.e. the processing of the message) in isolation. For instance, once you have captured a few of the 72 byte messages you can keep them and use them to develop your unit tests and logic for the code that will parse these messages without your PC or Laptop having to be physically connected to the lab device.
Another thing you can do if you really want to test your logic is to create a virtual serial port and transmit known data. This technique can be used to ensure your logic is capturing the message correctly. For instance, I once had to read data from a variety of medical devices including a heart rate monitor. I then had to plot this data on a graph. It is obviously crucial that this be correct. I created a test app that generated known wave patterns: a sin wave, saw tooth wave, flat line etc all of known amplitudes and frequencies. I created a virtual com port pair and my test app sent this test wave form data into the com port using the same serial port message format as the real device and I was able to validate that the data I captured and output on the screen was identical to my known wave forms.
I found the question interesting, so whipped up a quick example of what I'd (somewhat naively, without too much planning) do in this case.
Pretty idiomatic C#. Something (MySerialMessageReader) is responsible for reading the data, and fires off an event with a nicely parsed message for the consumer to react to.
Hope that helps?
public static class Program
{
public static void Main(string[] args)
{
using (var reader = new MySerialMessageReader())
{
reader.MessageReaceived += (s, e) =>
{
// TODO: Process message / react to it. Probably put it in a queue to be processed
Console.WriteLine("Received a new message: " + Encoding.UTF8.GetString(e.Message.Data));
};
Console.WriteLine("Waiting for data from serial port...");
Console.ReadLine();
}
}
}
class MySerialMessageReader : IDisposable
{
private byte[] mBuffer = new byte[72];
private int mReceivedCount = 0;
private bool mIsReadingMessage;
SerialPort mPort = new SerialPort("COM12", 9600, Parity.None);
public event EventHandler<MessageEventArgs> MessageReaceived;
public MySerialMessageReader()
{
}
public void Start()
{
mPort.DataReceived += Port_DataReceived;
mPort.Open();
}
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (e.EventType == SerialData.Eof)
{
if (mIsReadingMessage && mReceivedCount < Message.MESSAGE_LENGTH)
{
Debug.Fail("Received EOF before an entire message was read.");
return;
}
}
// Put the read buffer into our cached buffer
int n = 0;
while (n < mPort.BytesToRead)
{
var readByte = (byte)mPort.ReadByte();
if (!mIsReadingMessage && readByte == 0x55)
{
mIsReadingMessage = true; // New start of message
}
if (mIsReadingMessage)
{
// We're reading a message, put the message into a temporary buffer
// whilst we read(/wait for) the rest of the message
mBuffer[mReceivedCount++] = (byte)mPort.ReadByte();
}
if (mReceivedCount == Message.MESSAGE_LENGTH)
{
// We've got enough to construct a message
Message m = null;
if (Message.TryParse(mBuffer, ref m))
{
if (this.MessageReaceived != null)
this.MessageReaceived(this, new MessageEventArgs(m));
}
else
{
Console.WriteLine("Invalid message received. Checksum error?");
}
mReceivedCount = 0;
Array.Clear(mBuffer, 0, 72);
}
}
}
public void Dispose()
{
if (mPort != null)
{
mPort.DataReceived -= Port_DataReceived;
mPort.Dispose();
}
}
}
class Message
{
private const int CHECKSUM_LENGTH = 3;
private const int START_INDICATOR_LENGTH = 3;
public const int MESSAGE_LENGTH = 72;
public byte[] Data;
public byte[] Checksum;
private Message(byte[] data, byte[] checkSum)
{
this.Data = data;
this.Checksum = checkSum;
}
public static bool TryParse(byte[] data, ref Message message)
{
if (data.Length != MESSAGE_LENGTH)
return false; // Unexpected message length
if (data[0] != 0x55 || data[1] != 0x2F || data[2] != 0x48)
return false; // Invalid message start
var withotStartIndicator = data.Skip(START_INDICATOR_LENGTH);
var justData = withotStartIndicator.Take(MESSAGE_LENGTH - START_INDICATOR_LENGTH - CHECKSUM_LENGTH).ToArray();
var justChecksum = withotStartIndicator.Skip(justData.Length).ToArray();
// TODO: Insert checksum verification of justChecksum here
// TODO: parse justData into whatever your message look like
message = new Message(justData, justChecksum);
return true;
}
}
class MessageEventArgs : EventArgs
{
public Message Message { get; set; }
public MessageEventArgs(Message m)
{
this.Message = m;
}
}
Edit: You can obviously take this as far as you'd like, with async, queues, and whatever have you. But if you're not reading too much data, it's not such a bad thing to do it all in the event handler (in my opinion).
Related
I am trying to send a flatteded DBl array from labview to c# through tcp.
i am receiving the flattened string in c#.
i dont know how to convert them back to DBL array?
This is my code in C#:
{
private TcpClient socketConnection;
private Thread clientReceiveThread;
void Start()
{
ConnectToTcpServer();
}
void Update()
{
}
private void ConnectToTcpServer()
{
try
{
clientReceiveThread = new Thread(new ThreadStart(ListenForData));
clientReceiveThread.IsBackground = true;
clientReceiveThread.Start();
}
catch (Exception e)
{
Debug.Log("On client connect exception " + e);
}
}
private void ListenForData()
{
try
{
socketConnection = new TcpClient("localhost", 5333);
Byte[] bytes = new Byte[4000];
while (true)
{
using (NetworkStream stream = socketConnection.GetStream())
{
int length;
while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
{
Debug.Log(bytes.Length);
var incomingData = new byte[length];
Array.Copy(bytes, 0, incomingData, 0, length);
// Convert byte array to string message.
string serverMessage = Encoding.ASCII.GetString(incomingData);
Debug.Log("server message received as: " + serverMessage);
Debug.Log(serverMessage.Length);
}
}
}
}
catch (SocketException socketException)
{
Debug.Log("Socket exception: " + socketException);
}
}
}
This is my labview code:
This is my labview output: (Float array and flattened string)
This is the output in C#:
Since you wired FALSE to the "prepend array or string size", the only data in the buffer is the flattened doubles themselves. But you need to explicitly set "little endian" in order to talk to C#.
If you make that change in the G code, then this should work:
double[] dblArray = (double[])Array.CreateInstance(typeof(System.Double), length / 8);
// I'm not sure if you need the cast or not.
You have the constant "4000" in your code. I would change that to a declared constant and put a comment next to it that says, "Make sure this is a multiple of 8 (number of bytes in double data type)."
Im trying to read data from serial port and to compare it, but i cant get it working, data that i read isnt that i need to get and sometimes its incomplete
basicly what i want when data from serial port comes and if data data is equal to an array to write some data to serial port
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
var Serial1 = (SerialPort)sender;
Serial1.DtrEnable = true;
Serial1.RtsEnable = true;
int bytes = Serial1.BytesToRead;
byte[] buffer = new byte[bytes];
Serial1.Read(buffer, 0, bytes);
string buffer1 = System.Text.Encoding.UTF8.GetString(buffer);
newform(buffer1);
showinwindow(buffer);
}
private void showinwindow(byte[] buffer)
{
byte[] array1 = { 0x03, 0x2F, 0x2C };
bool a = array1.SequenceEqual(buffer);
if (a == true)
{
byte[] upisipodatak = { 0x03, 0x20, 0x23 };
serialPort1.Write(upisipodatak, 0, upisipodatak.Length);
}
}
private void newform(string buffer1)
{
BeginInvoke(new EventHandler(delegate
{
textBox1.AppendText(buffer1);
}));
}
I think your problem is that when you start the read, not all bytes are available so only a partial amount is returned. You might want to try a blocking read instead, along these lines:
/// <summary>
/// Attempts to read <paramref name="count"/> bytes into <paramref name="buffer"/> starting at offset <paramref name="offset"/>.
/// If any individual port read times out, a <see cref="TimeoutException"/> will be thrown.
/// </summary>
public void BlockingRead(SerialPort port, byte[] buffer, int offset, int count)
{
while (count > 0)
{
// SerialPort.Read() blocks until at least one byte has been read, or SerialPort.ReadTimeout milliseconds
// have elapsed. If a timeout occurs a TimeoutException will be thrown.
// Because SerialPort.Read() blocks until some data is available this is not a busy loop,
// and we do NOT need to issue any calls to Thread.Sleep().
int bytesRead = port.Read(buffer, offset, count);
offset += bytesRead;
count -= bytesRead;
}
}
Note that this will throw an exception on timeout (and you can configure the timeout for the serial port using SerialPort.ReadTimeout.)
However, be aware that the .Net SerialPort implementation has some flaws. See this article for details.
In particular, SerialPort.Read() is a blocking call, which you would normally want to avoid, but doing so will mean that you will have to do some reading up yourself!
I found a solution that works for me like 90 of 100 times just removed
Serial1.DtrEnable = true;
Serial1.RtsEnable = true;
I am trying to send and receive data by serial port, and I can get data from DataReceived event normally.
Now, I make a library, which is developed to send and receive data by serial port. I don't know how to send command and retrieve value by two different methods.
It makes me confused, help me, please!
static void Main(string[] args)
{
FXReader reader = new FXReader();
reader.ComPort = port;
reader.Connect();
Console.WriteLine(reader.SetPortPower(1));
Console.ReadLine();
Console.WriteLine(reader.GetPortStatus(1));
Console.ReadLine();
}
public class FXReader : IDisposable
{
private SerialPort rs232 = null;
public string ComPort
{
get { return rs232.PortName; }
set { rs232.PortName = value; }
}
public bool IsConnected
{
get { return rs232.IsOpen; }
}
public F520Reader()
{
rs232 = new SerialPort();
rs232.BaudRate = 115200;
rs232.Parity = Parity.None;
rs232.DataBits = 8;
rs232.StopBits = StopBits.One;
rs232.DataReceived += new SerialDataReceivedEventHandler(rs232_DataReceived);
}
public void Connect()
{
rs232.Open();
}
public void Disconnect()
{
rs232.Close();
}
public int GetPortPower(int port)
{
if (rs232.IsOpen)
{
byte[] command = { 0x5A, 0x00, 0x19, 0x00, 0x01, 0xFF, 0xFF, 0x7E };
rs232.Write(command, 0, command.Length);
//How can i get value from the event of "rs232_DataReceived"
return powervalue;
}
}
public int GetPortStatus(int port)
{
if (rs232.IsOpen)
{
byte[] command = { 0x5A, 0x00, 0x19, 0x00, 0x01, 0xFF, 0xFF, 0x7E };
rs232.Write(command, 0, command.Length);
//How can i get value from the event of "rs232_DataReceived"
return statusvalue;
}
}
void rs232_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
System.Threading.Thread.Sleep(50);
if (rs232.BytesToRead > 0)
{
byte[] recvBuffer = new byte[1024];
int recvLength = rs232.Read(recvBuffer, 0, recvBuffer.Length);
Array.Resize(ref recvBuffer, recvLength);
PharseResponse(recvBuffer);
}
}
private void PharseResponse(byte[] buffer)
{
if (buffer[0] == 0x5A && buffer[buffer.Length - 1] == 0x7E)
{
#region Response for Get Port Power
if (buffer[1] == 0x01 && buffer[2] == 0x13)
{
//How can I return value by the method of "GetPortPower"
return buffer[7];
}
#endregion
#region Response for Get Port Status
if (buffer[1] == 0x01 && buffer[2] == 0x14)
{
//How can I return value by the method of "GetPortStatus"
return buffer[7];
}
#endregion
}
}
#region IDisposable Members
public void Dispose()
{
if (rs232.IsOpen)
rs232.Close();
rs232.Dispose();
}
#endregion
}
This is certainly not the "best way". You are not getting any benefit whatsoever from using the DataReceived event. It is useful in an event-driven application, a GUI app for example. But not in app like this where you must get a response before you can proceed in your program.
So don't use it at all, call the Read() method directly instead. Your program will block until the device sends something back.
Which will also help you write correct code, it is not correct right now. Reading from a serial port normally produces only one or two bytes. The return value of Read() tells you how many you got. You cannot parse the response until you got a complete one. It isn't very obvious from your current code what "complete" looks like, but you need to have received at least 8 bytes before your PharseResponse() method will stop crashing with an IndexOutOfRangeException. A serial protocol often uses a specific byte value to indicate the end of the response.
I've been trying to write a C# application that send data to a stage light using the Enttec DMX USB Pro box. There are provided C# wrappers that I'm using and I've gotten the light to respond as expected but it very rarely works. I seem to have to switch between using from off the shelf application to "reset" the connection a few times before I can get it to start responding to my writes.
My DMX Code is
class DmxDriver
{
const int DMX_PACKET_SIZE = 513;
private bool connected;
private FTDI device;
private int startAddr;
private byte[] packet;
public DmxDriver(int baseDmxAddr)
{
startAddr = baseDmxAddr;
device = new FTDI();
FTDI.FT_STATUS result = device.OpenByIndex(0);
if (result == FTDI.FT_STATUS.FT_OK)
{
connected = true;
Console.WriteLine("DMX connected");
}
else
{
connected = false;
Console.WriteLine("DMX CONNECTION FAILED");
}
packet = new byte[DMX_PACKET_SIZE];
for (int i = 0; i < DMX_PACKET_SIZE; i++)
{
packet[i] = 0;
}
}
~DmxDriver()
{
device.Close();
}
public bool deviceConnected()
{
return connected;
}
public void sendData()
{
if (packet.Length != 513)
{
return;
}
uint written = 0;
FTDI.FT_STATUS result;
byte[] header = new byte[4];
header[0] = 0x7E; //start code
header[1] = 6; //DMX TX
header[2] = 255 & 0xFF; //pack length logical and with max packet size
header[3] = 255 >> 8; //packet length shifted by byte length? DMX standard idk
result = device.Write(header, 4, ref written);//send dmx header
Console.WriteLine(result);
Console.WriteLine(written);
result = device.Write(packet, 513, ref written);//send data array
Console.WriteLine(result);
Console.WriteLine(written);
byte[] endcode = new byte[1];
endcode[0] = 0xE7;
device.Write(endcode, 1, ref written);//send dmx end code
}
Just to explain, I have a dev board that is, on command, shooting out 4 chars (Bytes). The terminal program (RealTerm) i'm using to debug this sees all 4 bytes. I've now moved on to writing the desktop software, and my program is only paying attention to 1 out of 4 bytes sent. TO clarify, i don't know which 1 of 4 bytes (first , last, middle two) but i can find out if its really necessary.
At first I thought the SerialPort.DataReceived event would fire for each byte that was received. This isn't true, I don't know what causes it to fire but its not the receiving of a singular Byte.
So I tried looping over SerialPort.BytesToRead, but this also only gets the first byte, even though it recognizes there are 3 bytes to read (why not 4??)
Its not essential for me to receive this data at the exact time it hits the port, but obviously I dot want to be loosing 3/4 of my data. However it wont always be 4 bytes, that's just what its doing now. I just want to get all bytes that are ready to be read.
Event Handler:
private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
while (comPort.BytesToRead > 0)
{
RxString = comPort.ReadExisting();
RxByte = comPort.ReadByte();
byte[] myByte = new byte[6];
for (int i = 0; i < 6; i++)
{
myByte[i] = 0000000;
}
comPort.Read(myByte, 0, comPort.BytesToRead);
for (int i=0;i<6;i++)
{
if (myByte[i] != null)
{
thisBytes.Add(myByte[i]);
}
}
RxString = RxByte + "";
try
{
this.Invoke(new EventHandler(dealWithByte));
}
catch
{
}
}
}
private void dealWithByte(object sender, EventArgs e)
{
foreach (byte item in thisBytes)
{
RxByte = Convert.ToInt16(item);
string binary = Convert.ToString(RxByte, 2).PadLeft(8, '0');
//processTime(binary);
}
}
I am not a C# person but the code is pretty simple, pseudo code
numBytes As Int = SerialPort1.BytesToRead 'get # of bytes available
buf(numBytes - 1) As Byte 'allocate a buffer
br As Int = SerialPort1.Read(buf, 0, numBytes) 'read the bytes
If br <> numBytes {
Resize(buf, br) 'resize the buffer
}
at this point store the bytes into a list. This list can then be processed for messages.