|
I am trying to data from a serial port to store in a text file. I want to save each data burst (which are infrequent and relatively short) in discrete text files, but some data bursts may include a pause of a minute or so.
Even for short bursts without the pause, .ReadExisting does not capture everything, breaking it up into several pieces when displaying it on screen and creating several text files. I came up with an unsatisfactory hack-y solution for that, but I need advice/help with how to continue capturing until I reach a timeout duration of some number of milliseconds with no further data received and store all data received from the DataReceived event firing until the timeout is reached.
My original whack at it, would always break the data up into chunks and create multiple files:
try
{
String tempReadData = myPort.ReadExisting();
showLoggedData(tempReadData);
storeLog(tempReadData, fileName);
}
catch (Exception ex)
Throwing some sleep cycles into a while(myPort.BytesToRead > 0) loop gives it time to wait for more to arrive and keeps it all together in one file and displays it in a text box without having to stitch it all together.
try
{
while(myPort.BytesToRead > 0)
{
logText += myPort.ReadExisting();
System.Threading.Thread.Sleep(500);
}
showLoggedData(logText);
writeLogFile(logText, fileName);
}
catch (Exception ex)
But I can't figure out how to get it to to read all data from the time that myPort_DataReceived(object sender, SerialDataReceivedEventArgs e) fires until some specified amount of time has passed, and store it all in one text file.
|
|
|
|
|
Hi,
serial ports are tricky, and the best approach depends very much on what exactly it is you need.
A few facts that may help you find the best way for your situation:
1. the DataReceived event isn't very reliable, what I mean is for N bytes coming in on the serial port it may fire an arbitrary number of times, anywhere from 1 to N.
2. Asynchronous event handlers get executed on threads from the ThreadPool, not on the main (or "UI") thread, so you cannot directly manipulate your Form's Controls.
3. We all hope only one DatReceived event will be active at any one time, although I have never seen that being promised...
4. incoming data is buffered several times before it reaches your C# code (hardware in the serial port, buffers in the driver, etc).
5. when asking for a delay (e.g. by using Thread.Sleep) what you get will not exactly be what you asked for, the system clock may have limited resolution (e.g. 15 msec) and the system may not immediately put your thread back in charge.
Here is one concept that has worked for me on several occasions:
if you are sure all data will arrive in a period of M milliseconds once the first byte is sent, then have a very simple DataReceived handler that starts by sleeping slightly more than M milliseconds (so all the message data is guaranteed to have gotten time to be received), and then perform a ReadExisting or a Read(chararray) when working with text, or a Read(bytearray) when not working with text.
The one advantage of it is simplicity. However there are a few drawbacks to this approach:
1. there is artificial latency, i.e. nothing happens while the handler is sleeping, e.g. you don't see the data slowly coming in.
2. you are not immediately ready to get the next burst of data, as your sleep period must be slightly larger than your worst-case time required to get one message, and then you still need to process the data.
3. Your sleep period depends on your port settings (baud rate, number of stop bits, etc).
If the above isn't suitable, I tend to go to the other extreme:
1. consider it binary data (even when it is text) so you don't get confused by newline handling, multibyte Unicode characters and the like.
2. set up a mechanism that reads into a byte array; you can do this in the DataReceived handler; I don't, I prefer to do it periodically (e.g. in a BackGroundWorker that uses a loop
with a Thread.Sleep in there).
3. You can set a ReadTimeout to the serialport to keep the loop spinning, and to make an abort or application exit slightly easier.
4. Make sure to use the return value of the Read(bytearray) method, indicating the number of bytes actually read. Append the newly received bytes to a large buffer holding all bytes not yet processed.
5. Do whatever it takes to detect message boundaries in that larger buffer, then process and remove the messages when appropriate.
Good luck,
Luc Pattyn [My Articles]
If you can't find it on YouTube try TikTok...
|
|
|
|
|
Thanks for the reply, Luc.
I'm OK with all of the points you posted - It's a fairly simple data logger, so wonky multiple events firing and millisecond timing isn't important (75-100ms was still occasionally creating a second file for the last dozen or two characters, so I just bumped it up to .75s) and I'm manipulating the UI by display code in another class from within the thread.
The artificial latency of the first suggestion doesn't bother me, but I don't think that will work. Although all of the data from a single burst would arrive in X ms, occasionally two data bursts may come close together, so a static wait time could feasibly end too early.
I need to figure out a way to to capture everything from the initial DataReceived event until X ms after the line goes silent. I had a ReadTimeout set on the port, but it didn't behave the way I was expecting with ReadExisting (that is, it didn't keep trying to read until hitting the time limit). I'll look into the rest of your second suggestion. Thanks again.
|
|
|
|
|
ReadExisting() does not block, it simply returns whatever the buffer holds.
I forgot to mention: if your data is lines of text, terminated by a specific sequence (say CR LF), you should consider using ReadLine() without timeout; ReadLine() waits for whatever string you have set as SerialPort.NewLine; again I would not use DataReceived if I can avoid it, a simple loop containing ReadLine() and checking DateTime.Now could suffice.
All too often the documentation isn't clear, I assume the NewLine property is used both by WriteLine() and ReadLine(), and I haven't used it for anything other than CR LF yet.
BTW: I don't trust the default values for SerialPort properties much, in my experience it is worth setting them explicitly... This applies mostly to .NET 2.0/3.5; .NET 4.0 is said to have some improvement in the SerialPort class, I haven't seen any detailed info though.
Luc Pattyn [My Articles]
If you can't find it on YouTube try TikTok...
|
|
|
|
|
What makes you "read" in the first place? I've never worked on a "port" application where you just read all the time.
You request, then read before the timeout interval.
Then you simply keep reading until BytesToRead == 0 without timing out. That's the end of the "transmission".
Concatenating transmissions is something else again and nothing to do with the reading. (You can append to text files with one statement).
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
Gerry Schmitz wrote: What makes you "read" in the first place? I've never worked on a "port" application where you just read all the time.
You request, then read before the timeout interval. There exists an appliance that barfs out serial data whenever it does something. A request for it to barf out data needn't be made. It does a thing, it barfs out data. I am endeavoring to capture that data.
|
|
|
|
|
Then it's "broadcasting", and you're playing a slave role.
So, no retry on lost / error transmissions. Saving seems pointless.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
There is a good article and sample at Serial Port I/O[^]. The code is in C but it should be reasonably clear what the sequence of events should be.
|
|
|
|
|
For the time being, I am using a timer, storing the data until the timer is elapsed, and resetting the timer if new data comes before it has elapsed.
in DataReceived :
try
{
while (myPort.BytesToRead > 0)
logText += myPort.ReadExisting();
}
catch (Exception ex)
in elapsed timer event handler:
It seems to be operating as expected, with the acceptable artificial latency discussed in comments while it waits for the timer to expire, capturing and storing subsequent data bursts received within the timer interval in a single chunk.
|
|
|
|
|
I call COM object method, it generate exception. If call from VB project or from Excel VBA I get detail exception information (original text exception message from COM object method). But if call from C# project I receive only NullReferenceException. InnerException field is empty. What can do to get detail exception info from c#?
|
|
|
|
|
Member 12632601 wrote: I receive only NullReferenceException There is not much more that the exception handler can tell you. You need to use your debugger to find out which reference item is null, and why.
|
|
|
|
|
My code similar:
dynamic result = comobject.ExecuteQuery();
On this line debug stoped at exception NullReferenceexception.
Known what method ExcecuteQuery throw exception in message contain error description
But i no can get this message in c#
|
|
|
|
|
Use the debugger to examine all the reference items. One of them is null when it should not be.
|
|
|
|
|
ComObject is not null. What in this line can by null?
In VB same line
throw exception TargetInvocationException what have not null InnerException field. What contain detail error message.
|
|
|
|
|
I have no idea because I cannot examine your running program.
|
|
|
|
|
At a guess, some of the information on which the ExecuteQuery request depends.
Use the debugger: look at the ComObject and it's properties - are there any there that are null, that it might be using?
We can't do that for you: we have no idea what yoru object is, what it is talking to, what data it might require - and no way to find out as we can't run your code under the same conditions you do!
So break out the debugger, and start looking at exactly what is going on!
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony
"Common sense is so rare these days, it should be classified as a super power" - Random T-shirt
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
Hi all, the code below broadcasts on a lan for Logitech media servers listening on UDP port 3483 - it works perfectly unless I run it more than once in the same session which results in the error Only one usage of each socket address is normally permitted. Now I understand that you can't bind to a socket twice with the same ip/port pairing but I'm not trying to do that - I'm simply resending the broadcast. I don't have servers running on the same machine as this code is run on ( that's bitten me before ) Any ideas ?
public async Task<LMSResponseParser> ScanForServer()
{
try
{
this.listener = new UdpClient(UDPPort);
this.ReceiveIP = new IPEndPoint(IPAddress.Broadcast, UDPPort);
this.Result = await listener.ReceiveAsync();
this.RequestData = this.Result.Buffer;
this.ReceiveIP = this.Result.RemoteEndPoint;
this.BytesSent = await this.listener.SendAsync(this.RequestData, this.RequestData.Length, this.ReceiveIP);
this.Result = await this.listener.ReceiveAsync();
this.BytesReceived = this.Result.Buffer;
if (this.BytesReceived != null && this.BytesReceived.Length > 0)
this.RetVal = Encoding.UTF8.GetString(this.BytesReceived);
this.parseLMSResponse = new LMSResponseParser(BytesReceived);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (this.listener != null && this.listener.Client.Connected)
this.listener.Close();
}
return this.parseLMSResponse;
}
"I didn't mention the bats - he'd see them soon enough" - Hunter S Thompson - RIP
modified 5-Oct-20 5:30am.
|
|
|
|
|
The UspClient class implements IDisposable , so you could try wrapping it in a using block.
I'd also be inclined to use local variables for objects which aren't meant to live beyond the current method, rather than storing everything in class-level fields.
public async Task<LMSResponseParser> ScanForServer()
{
using (var listener = new UdpClient(UDPPort))
{
this.ReceiveIP = new IPEndPoint(IPAddress.Broadcast, UDPPort);
var result = await listener.ReceiveAsync();
var requestData = result.Buffer;
this.ReceiveIP = result.RemoteEndPoint;
this.BytesSent = await listener.SendAsync(requestData, requestData.Length, this.ReceiveIP);
result = await listener.ReceiveAsync();
var bytesReceived = result.Buffer;
if (bytesReceived != null && bytesReceived.Length != 0)
{
this.RetVal = Encoding.UTF8.GetString(bytesReceived);
}
return new LMSResponseParser(bytesReceived);
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks Richard I'll give it a shot
"I didn't mention the bats - he'd see them soon enough" - Hunter S Thompson - RIP
|
|
|
|
|
Hi Richard, I tried your code and it worked fine, I think it's the using statement that's key here - I modded my code to call Dispose() on the listener object and that also worked fine so I think you nailed it noticing UDPClient implements IDispose - a lesson learnt - thanks for your help
"I didn't mention the bats - he'd see them soon enough" - Hunter S Thompson - RIP
|
|
|
|
|
Visual studio 2019 16.7.5
I'm getting this warning":
Version mismatch between the analyzer package '3.3.1' and Microsoft.CodeAnalysis '2.1.0.0'
I have the latest version of Microsoft.CodeAnalysis and FxCopAnalyser installed.
I don't understand what causes the system to look at Microsoft.CodeAnalysis '2.1.0.0'.
|
|
|
|
|
|
I have installed MicroCodeAnalysis v3.70 and FxCopAnalyzers v3.3.0. I don't have version 3.3.1
|
|
|
|
|
Member 14924607 wrote: I don't have version 3.3.1
Member 14924607 wrote: Version mismatch between the analyzer package '3.3.1' ...
The error message suggests you do. Unless there's a different analyzer package installed which you haven't mentioned?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Hi,
I am using the printDocument function to print out my datagridview data. However, I am using the debugger to trace my program execution and I have found out that the "printDocument1_BeginPrint" and "printDocument1_PrintPage" never get executed during the program execution.
Can anyone tell me what I have done wrong? Why the printDocument1_PrinPage and printDoucument1_BeginPrint never got executed?
PS. I am pasting my code below of the printing logic.
Thanks,
John
#region Print Button Click Event
private void btnPrint_Click(object sender, EventArgs e)
{
PrintDialog printDialog = new PrintDialog();
PrintDocument printDocument1 = new PrintDocument();
printDialog.Document = printDocument1;
printDialog.UseEXDialog = true;
if (DialogResult.OK == printDialog.ShowDialog())
{
printDocument1.DocumentName = "Lottery Tickets Priniting";
printDocument1.Print();
}
}
#endregion
#region Begin Print Event Handler
private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
try
{
strFormat = new StringFormat();
strFormat.Alignment = StringAlignment.Near;
strFormat.LineAlignment = StringAlignment.Center;
strFormat.Trimming = StringTrimming.EllipsisCharacter;
arrColumnLefts.Clear();
arrColumnWidths.Clear();
iCellHeight = 0;
iRow = 0;
bFirstPage = true;
bNewPage = true;
iTotalWidth = 0;
foreach (DataGridViewColumn dgvGridCol in dataGridView1.Columns)
{
iTotalWidth += dgvGridCol.Width;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region Print Page Event
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
try
{
int iLeftMargin = e.MarginBounds.Left;
int iTopMargin = e.MarginBounds.Top;
bool bMorePagesToPrint = false;
int iTmpWidth = 0;
if (bFirstPage)
{
foreach (DataGridViewColumn GridCol in dataGridView1.Columns)
{
iTmpWidth = (int)(Math.Floor((double)((double)GridCol.Width /
(double)iTotalWidth * (double)iTotalWidth *
((double)e.MarginBounds.Width / (double)iTotalWidth))));
iHeaderHeight = (int)(e.Graphics.MeasureString(GridCol.HeaderText,
GridCol.InheritedStyle.Font, iTmpWidth).Height) + 11;
arrColumnLefts.Add(iLeftMargin);
arrColumnWidths.Add(iTmpWidth);
iLeftMargin += iTmpWidth;
}
}
while (iRow <= dataGridView1.Rows.Count - 1)
{
DataGridViewRow GridRow = dataGridView1.Rows[iRow];
iCellHeight = GridRow.Height + 5;
int iCount = 0;
if (iTopMargin + iCellHeight >= e.MarginBounds.Height + e.MarginBounds.Top)
{
bNewPage = true;
bFirstPage = false;
bMorePagesToPrint = true;
break;
}
else
{
if (bNewPage)
{
e.Graphics.DrawString("Lottery Tickets Listing", new Font(dataGridView1.Font, FontStyle.Bold),
Brushes.Black, e.MarginBounds.Left, e.MarginBounds.Top -
e.Graphics.MeasureString("Lottery Tickets Listing", new Font(dataGridView1.Font,
FontStyle.Bold), e.MarginBounds.Width).Height - 13);
String strDate = DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString();
e.Graphics.DrawString(strDate, new Font(dataGridView1.Font, FontStyle.Bold),
Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width -
e.Graphics.MeasureString(strDate, new Font(dataGridView1.Font,
FontStyle.Bold), e.MarginBounds.Width).Width), e.MarginBounds.Top -
e.Graphics.MeasureString("Lottery Tickets Listing", new Font(new Font(dataGridView1.Font,
FontStyle.Bold), FontStyle.Bold), e.MarginBounds.Width).Height - 13);
iTopMargin = e.MarginBounds.Top;
foreach (DataGridViewColumn GridCol in dataGridView1.Columns)
{
e.Graphics.FillRectangle(new SolidBrush(Color.LightGray),
new Rectangle((int)arrColumnLefts[iCount], iTopMargin,
(int)arrColumnWidths[iCount], iHeaderHeight));
e.Graphics.DrawRectangle(Pens.Black,
new Rectangle((int)arrColumnLefts[iCount], iTopMargin,
(int)arrColumnWidths[iCount], iHeaderHeight));
e.Graphics.DrawString(GridCol.HeaderText, GridCol.InheritedStyle.Font,
new SolidBrush(GridCol.InheritedStyle.ForeColor),
new RectangleF((int)arrColumnLefts[iCount], iTopMargin,
(int)arrColumnWidths[iCount], iHeaderHeight), strFormat);
iCount++;
}
bNewPage = false;
iTopMargin += iHeaderHeight;
}
iCount = 0;
foreach (DataGridViewCell Cel in GridRow.Cells)
{
if (Cel.Value != null)
{
e.Graphics.DrawString(Cel.Value.ToString(), Cel.InheritedStyle.Font,
new SolidBrush(Cel.InheritedStyle.ForeColor),
new RectangleF((int)arrColumnLefts[iCount], (float)iTopMargin,
(int)arrColumnWidths[iCount], (float)iCellHeight), strFormat);
}
e.Graphics.DrawRectangle(Pens.Black, new Rectangle((int)arrColumnLefts[iCount],
iTopMargin, (int)arrColumnWidths[iCount], iCellHeight));
iCount++;
}
}
iRow++;
iTopMargin += iCellHeight;
}
if (bMorePagesToPrint)
e.HasMorePages = true;
else
e.HasMorePages = false;
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
modified 2-Oct-20 1:56am.
|
|
|
|
|