//----------------------------------------------------------------------- // // Copyright © 2012 Nils Hammar. All rights reserved. // //----------------------------------------------------------------------- /* * Software to access vehicle information via the OBD-II connector. * * Copyright © 2012 Nils Hammar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Alternative licensing is possible, see the licensing document. * * The above text may not be removed or modified. */ namespace LoggingLibrary { using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Windows.Forms; using global::SharedObjects; using global::SharedObjects.Api; using global::SharedObjects.CAN.Objects; using global::SharedObjects.Misc; /// /// Class for logging of events. /// public partial class LoggingForm : Form, ILogging { /// /// Indicator of no file. /// public const string NOFILE = " * None * "; /// /// Light red color. /// private static readonly Color color1 = Color.FromArgb(0xff, 0xA0, 0xA0); /// /// Light yellow color. /// private static readonly Color color2 = Color.FromArgb(0xff, 0xff, 0xA0); /// /// Light green color. /// private static readonly Color color3 = Color.FromArgb(0xA0, 0xff, 0xA0); /// /// Light cyan color. /// private static readonly Color color4 = Color.FromArgb(0xA0, 0xff, 0xff); /// /// Light blue color. /// private static readonly Color color5 = Color.FromArgb(0xA0, 0xA0, 0xff); /// /// Light magenta color. /// private static readonly Color color6 = Color.FromArgb(0xff, 0xA0, 0xff); /// /// Light gray color. /// private static readonly Color color7 = Color.LightGray; /// /// Current RAW data log stream. /// private FileStream rawDataLogStream = null; /// /// Current RAW writer. /// private BinaryWriter rawBinaryWriter = null; /// /// Current CAN data log stream. /// private FileStream canDataLogStream = null; /// /// Current event log stream. /// private FileStream eventLogStream = null; /// /// Current CSV data log stream. /// private FileStream valueDataLogStream = null; /// /// Current CAN data log writer instance. /// private TextWriter canDataLogWriter; /// /// Current event log writer. /// private TextWriter eventLogWriter = null; /// /// Current CSV data log writer. /// private TextWriter valueDataLogWriter; /// /// Baseline timestamp for CAN log file. /// private long t0 = 0; /// /// To capture uncontrolled closing of the window. /// private bool controlledClose = false; /// /// Queue for displaying message items. /// private Queue dispQueue = new Queue(); /// /// Object to synchronize threads with. /// private object lockObject = new object(); /// /// Thread handling receiving of messages. /// private Thread queueThread = null; /// /// Flag indicate running thread. /// private bool doRun = true; /// /// Start time. /// private long tBase = 0L; /// /// Flag indicating that EOL (CR+LF) was received and that next line shall start with a timestamp. /// private bool gotEol = true; /// /// Rows in traffic data grid view. /// private DataGridViewRowCollection trafficRows = null; /// /// Initializes a new instance of the class. /// public LoggingForm() { this.tBase = DateTime.Now.Ticks; this.canDataLogWriter = null; this.valueDataLogWriter = null; this.InitializeComponent(); this.trafficRows = this.trafficDataGridView.Rows; this.logLevelCB.SelectedIndex = 2; this.queueThread = new Thread(new ThreadStart(this.displayQueueItems)); this.queueThread.Name = "Logging Queue Thread"; this.queueThread.Start(); } /// /// Closing of the log form. /// /// To capture uncontrolled closing of the window. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "controlledClose", Justification = "Reviewed, intentional.")] public void Close(bool controlledClose) { this.doRun = false; this.queueThread.Abort(); this.queueThread.Interrupt(); this.queueThread.Join(4000); this.controlledClose = controlledClose; this.Close(); } /// /// Add one row to the traffic table. /// /// Traffic direction and layer level. /// Address field. /// Mode field. /// PID field. /// Payload length. /// Payload data. public void addRow(rowMode direction, uint address, byte mode, uint pid, uint length, string rspStr) { if (this.trafficDataGridView.InvokeRequired) { try { this.trafficDataGridView.Invoke(new addRowFunc(this.addRow), new object[] { direction, address, mode, pid, length, rspStr }); } catch (ThreadAbortException) { throw; } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + Utils.CRLF, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { // Only keep the 1000 last rows. while (this.trafficRows.Count > 1000) { this.trafficRows.RemoveAt(0); } int n = this.trafficRows.Add(); DataGridViewRow row = this.trafficRows[n]; DataGridViewCellCollection cells = row.Cells; string directionStr = string.Empty; switch (direction) { case rowMode.transport_in: directionStr = "In1"; row.DefaultCellStyle.BackColor = color1; break; case rowMode.session_in: directionStr = "In2"; row.DefaultCellStyle.BackColor = color2; break; case rowMode.presentation_in: directionStr = "In3"; row.DefaultCellStyle.BackColor = color3; break; case rowMode.presentation_out: directionStr = "Out3"; row.DefaultCellStyle.BackColor = color4; break; case rowMode.session_out: directionStr = "Out2"; row.DefaultCellStyle.BackColor = color5; break; case rowMode.transport_out: directionStr = "Out1"; row.DefaultCellStyle.BackColor = color6; break; case rowMode.data_discard: directionStr = "-"; row.DefaultCellStyle.BackColor = color7; break; } cells["col_timestamp"].Value = ((DateTime.Now.Ticks - this.tBase) / (double)TimeSpan.TicksPerSecond).ToString("0.0000"); cells["col_direction"].Value = directionStr; cells["col_address"].Value = "0x" + address.ToString("X4"); if (direction == rowMode.presentation_in || direction == rowMode.presentation_out) { cells["col_mode"].Value = "0x" + mode.ToString("X2"); cells["col_pid"].Value = "0x" + pid.ToString("X2"); cells["col_length"].Value = length.ToString(); } cells["col_payload"].Value = rspStr; this.trafficDataGridView.FirstDisplayedScrollingRowIndex = n > 10 ? n - 10 : n; } } /// /// Open CAN data log. /// /// 'true' if successful. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Reviewed, intentional.")] public bool openCanDataLog() { bool success = false; this.saveFileDialog2.FileName = string.Empty; this.saveFileDialog2.DefaultExt = ".asc"; this.saveFileDialog2.AddExtension = true; this.saveFileDialog2.ValidateNames = true; this.saveFileDialog2.Filter = "Vector Canalyzer Asc Format|*.asc"; this.saveFileDialog2.ShowDialog(); if (!string.IsNullOrEmpty(this.saveFileDialog2.FileName)) { try { this.canDataLogStream = (System.IO.FileStream)this.saveFileDialog2.OpenFile(); this.canDataLogWriter = new StreamWriter(this.canDataLogStream, Utils.iso_8859_1); DateTime thisDate1 = DateTime.Now; string dateTimeString = thisDate1.ToString("ddd MMM dd hh:mm:ss ", CultureInfo.CreateSpecificCulture("en-US")) + thisDate1.ToString("tt", CultureInfo.CreateSpecificCulture("en-US")).ToLower(CultureInfo.InvariantCulture) + thisDate1.ToString(" yyyy", CultureInfo.CreateSpecificCulture("en-US")); this.canDataLogWriter.WriteLine("date " + dateTimeString); this.canDataLogWriter.WriteLine("base hex timestamps absolute"); this.canDataLogWriter.WriteLine("internal events logged"); this.canDataLogWriter.WriteLine("// version 7.6.0"); this.canDataLogWriter.WriteLine("Begin Triggerblock " + dateTimeString); this.t0 = thisDate1.Ticks; long t1 = this.t0; string secStr = getSecondsStr(this.t0, t1); this.canDataLogWriter.WriteLine(secStr + " Start of measurement"); this.canDataLogFileName.Text = this.saveFileDialog2.FileName; success = true; } catch (Exception ex) { MessageBox.Show( ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } return success; } /// /// Get seconds string. /// /// The number of seconds is relative to a saved timestamp. /// /// /// Timestamp to relate to. /// Seconds string. public string getSecondsStr(long t1) { return getSecondsStr(this.t0, t1); } /// /// Add raw log data to binary log file. /// /// Binary object to write to file. public void addRawData(RawLogObject rawLogObject) { if (rawLogObject != null && this.rawDataLogStream != null && this.rawBinaryWriter != null) { this.rawBinaryWriter.Write(rawLogObject.timestamp); this.rawBinaryWriter.Write(rawLogObject.sourceAddress); this.rawBinaryWriter.Write(rawLogObject.mode); this.rawBinaryWriter.Write(rawLogObject.pid_int); this.rawBinaryWriter.Write(rawLogObject.dataLen); this.rawBinaryWriter.Write(rawLogObject.data); this.rawBinaryWriter.Flush(); this.rawDataLogStream.Flush(); } } /// /// Open event log. /// /// 'true' if successful. public bool openRawLog() { this.closeRawLog(); bool success = false; this.saveFileDialog4.FileName = string.Empty; this.saveFileDialog4.DefaultExt = ".raw"; this.saveFileDialog4.AddExtension = true; this.saveFileDialog4.ValidateNames = true; this.saveFileDialog4.Filter = "Raw File|*.raw"; if (this.saveFileDialog4.ShowDialog() == System.Windows.Forms.DialogResult.OK) { if (!string.IsNullOrEmpty(this.saveFileDialog4.FileName)) { try { this.rawDataLogStream = (System.IO.FileStream)this.saveFileDialog4.OpenFile(); this.rawBinaryWriter = new BinaryWriter(this.rawDataLogStream); success = true; } catch (Exception ex) { MessageBox.Show( ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } } return success; } /// /// Close event log. /// public void closeRawLog() { if (this.rawBinaryWriter != null) { try { this.rawBinaryWriter.Flush(); } catch { } } if (this.rawDataLogStream != null) { try { this.rawDataLogStream.Flush(); } catch { } } if (this.rawBinaryWriter != null) { try { this.rawBinaryWriter.Close(); } catch { } this.rawBinaryWriter = null; } if (this.rawDataLogStream != null) { try { this.rawDataLogStream.Close(); } catch { } this.rawDataLogStream = null; } } /// /// Open event log. /// /// 'true' if successful. public bool openEventLog() { bool success = false; this.saveFileDialog1.FileName = string.Empty; this.saveFileDialog1.DefaultExt = ".txt"; this.saveFileDialog1.AddExtension = true; this.saveFileDialog1.ValidateNames = true; this.saveFileDialog1.Filter = "Text File|*.txt"; this.saveFileDialog1.ShowDialog(); if (!string.IsNullOrEmpty(this.saveFileDialog1.FileName)) { try { this.eventLogStream = (System.IO.FileStream)this.saveFileDialog1.OpenFile(); this.eventLogWriter = new StreamWriter(this.eventLogStream); this.eventLogFileName.Text = this.saveFileDialog1.FileName; success = true; } catch (Exception ex) { MessageBox.Show( ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } return success; } /// /// Close event log. /// public void closeEventLog() { if (this.eventLogStream != null) { try { this.eventLogStream.Flush(); } catch { } } if (this.eventLogWriter != null) { try { this.eventLogWriter.Close(); } catch { } this.eventLogWriter = null; } if (this.eventLogStream != null) { try { this.eventLogStream.Close(); } catch { } this.eventLogStream = null; } this.eventLogFileName.Text = NOFILE; } /// /// Save event window. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Reviewed.")] public void saveEventWindow() { this.saveFileDialog1.FileName = string.Empty; this.saveFileDialog1.DefaultExt = ".txt"; this.saveFileDialog1.AddExtension = true; this.saveFileDialog1.ValidateNames = true; this.saveFileDialog1.Filter = "Text File|*.txt"; this.saveFileDialog1.ShowDialog(); if (!string.IsNullOrEmpty(this.saveFileDialog1.FileName)) { try { using (FileStream fs = (System.IO.FileStream)this.saveFileDialog1.OpenFile()) { try { using (StreamWriter sw = new StreamWriter(fs)) { try { sw.WriteLine(this.eventLogWindow.Text); } finally { try { sw.Close(); } catch { } } } } finally { try { fs.Close(); } catch { } } } } catch (Exception ex) { MessageBox.Show( ex.Message + "\r\n" + ex.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } } /// /// Only append to CAN data log file, not window. /// /// Text string to append. public void appendLog(string txt) { if (this.eventLogWriter != null) { this.eventLogWriter.Write(txt); if (this.eventLogWriter != null) { try { this.eventLogWriter.Flush(); } catch { } } } } /// /// Open measurements data log file. /// /// 'true' if successful. public bool openDataLog() { bool success = false; this.saveFileDialog3.FileName = string.Empty; this.saveFileDialog3.DefaultExt = ".csv"; this.saveFileDialog3.AddExtension = true; this.saveFileDialog3.ValidateNames = true; this.saveFileDialog3.Filter = "Excel CSV Format|*.csv"; DialogResult res = this.saveFileDialog3.ShowDialog(); if (res == DialogResult.OK) { if (!string.IsNullOrEmpty(this.saveFileDialog3.FileName)) { try { this.valueDataLogStream = (System.IO.FileStream)this.saveFileDialog3.OpenFile(); this.valueDataLogWriter = new StreamWriter(this.valueDataLogStream, Utils.iso_8859_1); this.measurementDataLogFileName.Text = this.saveFileDialog3.FileName; success = true; } catch (Exception ex) { MessageBox.Show( ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } } } return success; } /// /// Close measurements data log file. /// public void closeDataLog() { if (this.valueDataLogWriter != null) { try { this.valueDataLogWriter.Flush(); } catch { } } if (this.valueDataLogStream != null) { try { this.valueDataLogStream.Flush(); } catch { } } if (this.valueDataLogWriter != null) { try { this.valueDataLogWriter.Close(); } catch { } this.valueDataLogWriter = null; } if (this.valueDataLogStream != null) { try { this.valueDataLogStream.Close(); } catch { } this.valueDataLogStream = null; } this.measurementDataLogFileName.Text = NOFILE; } /// /// Close CAN data log. /// public void closeCanDataLog() { if (this.canDataLogWriter != null) { try { this.canDataLogWriter.WriteLine("End TriggerBlock"); } catch { } try { this.canDataLogWriter.Flush(); } catch { } } if (this.canDataLogStream != null) { try { this.canDataLogStream.Flush(); } catch { } } if (this.canDataLogWriter != null) { try { this.canDataLogWriter.Close(); } catch { } this.canDataLogWriter = null; } if (this.canDataLogStream != null) { try { this.canDataLogStream.Close(); } catch { } this.canDataLogStream = null; } this.canDataLogFileName.Text = NOFILE; } /// /// Append text to the text box. /// /// Text to append, caller supplies line breaks. public void appendText(string txt) { this.appendText(txt, LogLevel.LOG_INFO); } /// /// Append text to the text box. /// public void appendTextLn() { this.appendText("\r\n", LogLevel.LOG_INFO); } /// /// Append text to the text box. /// /// Text to append, caller supplies line breaks. public void appendTextLn(string txt) { this.appendText(txt + "\r\n", LogLevel.LOG_INFO); } /// /// Append text to the text box. /// The return value can be used to control a group of log messages /// where the first only needs to be checked if no logging is needed. /// This will improve performance. /// /// Text to append, caller supplies line breaks. /// Level of message, LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR /// 'true' if message was logged at the given log level. public bool appendText(string txt, int logLevel) { bool logged = false; if (this.InvokeRequired) { try { logged = (bool)this.Invoke(new appendTextFunc(this.appendText), new object[] { txt, logLevel }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\ntxt='" + txt + "'", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { if (this.logLevelCB == null || logLevel > this.logLevelCB.SelectedIndex) { logged = true; try { if (this.eventLogWindow.Text.Length > 10240) { this.eventLogWindow.Text = this.eventLogWindow.Text.Substring(this.eventLogWindow.Text.Length - 10240, 10240); } if (this.gotEol) { double tDiff = (DateTime.Now.Ticks - this.tBase) / (double)TimeSpan.TicksPerSecond; txt = tDiff.ToString("000,000.000") + ": " + txt; } this.eventLogWindow.AppendText(txt); this.gotEol = txt.EndsWith("\r\n"); if (this.eventLogWriter != null) { this.eventLogWriter.Write(txt); if (this.eventLogWriter != null) { try { this.eventLogWriter.Flush(); } catch { } } } } catch { } } } return logged; } /// /// Test Device for error. /// /// Involved device. /// Status code to test. /// 'true' if OK to continue. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Reviewed.")] public bool errTestDevice(IPassThruDevice passThruDevice, PassThruConstants.resultCode res) { return this.errTest(res, passThruDevice.lastError); } /// /// Display message content. /// /// Prefix to display with message, e.g. 'Tx'. /// Message to display. public void dispMsg(string prefix, IPassThruMsg msg) { string addr = string.Empty; string data = string.Empty; if (msg != null) { for (int i = 0; i < msg.DataSize; i++) { if (i < 4) { addr += msg.Data[i].ToString("x2") + " "; } else { data += msg.Data[i].ToString("x2") + " "; } } this.dispMsg(prefix, msg, addr, data); } } /// /// Display one raw message with header info. /// /// Prefix to display with entry, e.g. 'Tx' /// Message to display. /// Address string. /// Payload string. public void dispMsg(string prefix, IPassThruMsg msg, string addressStr, string data) { lock (this.lockObject) { this.dispQueue.Enqueue(new DispQueueItem(prefix, msg, addressStr, data)); Monitor.PulseAll(this.lockObject); } } /// /// Test Device for error. /// /// Involved device. /// Status code to test. /// Error text. /// 'true' if OK to continue. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Reviewed.")] public bool errTestDevice(IPassThruDevice passThruDevice, PassThruConstants.resultCode res, out string errtext) { return this.errTest(res, passThruDevice.lastError, out errtext); } /// /// Test Connection for error. /// /// Current connection. /// Status code to test. /// 'true' if OK to continue. public bool errTestConnection(IPassThruConnection passThruConnection, PassThruConstants.resultCode res) { if (passThruConnection != null) { return this.errTest(res, passThruConnection.lastError); } return false; } /// /// Test Connection for error. /// /// Current connection. /// Status code to test. /// Error text. /// 'true' if OK to continue. public bool errTestConnection(IPassThruConnection passThruConnection, PassThruConstants.resultCode res, out string errtext) { if (passThruConnection != null) { return this.errTest(res, passThruConnection.lastError, out errtext); } errtext = "passThruConnection is 'null'"; return false; } /// /// Test the result code for error and log. /// /// Result code to test. /// Last error string. /// 'true' if OK to continue. public bool errTest(PassThruConstants.resultCode res, string lastErrorStr) { string errtext; return this.errTest(res, lastErrorStr, out errtext); } /// /// Test the result code for error and log. /// /// Result code to test. /// Last error string. /// Error text returned to be used in a pop-up or similar. /// 'true' if execution can continue. public bool errTest(PassThruConstants.resultCode res, string lastErrorStr, out string errtext) { bool cont = true; errtext = string.Empty; if (res != 0) { cont = false; switch (res) { case PassThruConstants.resultCode.ERR_SUCCESS: break; case PassThruConstants.resultCode.ERR_TIMEOUT: errtext = "ERR_TIMEOUT"; break; case PassThruConstants.resultCode.ERR_BUFFER_EMPTY: errtext = "ERR_BUFFER_EMPTY"; break; case PassThruConstants.resultCode.ERR_NOT_SUPPORTED: case PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID: case PassThruConstants.resultCode.ERR_INVALID_PROTOCOL_ID: case PassThruConstants.resultCode.ERR_NULL_PARAMETER: case PassThruConstants.resultCode.ERR_INVALID_IOCTL_VALUE: case PassThruConstants.resultCode.ERR_INVALID_FLAGS: case PassThruConstants.resultCode.ERR_FAILED: case PassThruConstants.resultCode.ERR_DEVICE_NOT_CONNECTED: case PassThruConstants.resultCode.ERR_INVALID_MSG: case PassThruConstants.resultCode.ERR_INVALID_TIME_INTERVAL: case PassThruConstants.resultCode.ERR_EXCEEDED_LIMIT: case PassThruConstants.resultCode.ERR_INVALID_MSG_ID: case PassThruConstants.resultCode.ERR_DEVICE_IN_USE: case PassThruConstants.resultCode.ERR_INVALID_IOCTL_ID: case PassThruConstants.resultCode.ERR_BUFFER_FULL: case PassThruConstants.resultCode.ERR_BUFFER_OVERFLOW: case PassThruConstants.resultCode.ERR_PIN_INVALID: case PassThruConstants.resultCode.ERR_CHANNEL_IN_USE: case PassThruConstants.resultCode.ERR_MSG_PROTOCOL_ID: case PassThruConstants.resultCode.ERR_INVALID_FILTER_ID: case PassThruConstants.resultCode.ERR_NO_FLOW_CONTROL: case PassThruConstants.resultCode.ERR_NOT_UNIQUE: case PassThruConstants.resultCode.ERR_INVALID_BAUDRATE: case PassThruConstants.resultCode.ERR_INVALID_DEVICE_ID: errtext = "[" + string.Format("0x{0:x}", res) + "] " + J2534_Error.errors[res].name + ": " + J2534_Error.errors[res].text + "\r\n" + " LastError: " + lastErrorStr; this.appendText("E1: " + errtext + "\r\n"); break; case PassThruConstants.resultCode.READ_FAIL: errtext = "CanApp: Read Failure."; this.appendText("E2: " + errtext + "\r\n"); break; case PassThruConstants.resultCode.NO_CHANNEL: errtext = "CanApp: Not Connected - please connect first."; this.appendText("E3: " + errtext + "\r\n"); break; default: this.appendText("E4: [" + res + "] " + lastErrorStr + "\r\n"); break; } } return cont; } /// /// This is where CSV file format data shall be written. /// The contents depends on what the user selects. /// /// Text writer instance. public TextWriter getValueDataLogWriter() { return this.valueDataLogWriter; } /// /// Get the data log writer. /// This is where ASC file format data shall be written. /// The content is the raw CAN bus data. /// /// Text writer instance. public TextWriter getCanDataLogWriter() { return this.canDataLogWriter; } /// /// Get seconds string. /// /// The number of seconds is the difference between the timestamps; t1 - t0. /// /// /// First timestamp. /// Second timestamp. /// Seconds string. private static string getSecondsStr(long t0, long t1) { double seconds = (t1 - t0) / (double)TimeSpan.TicksPerSecond; string secStr = seconds.ToString("F6", CultureInfo.CreateSpecificCulture("en-US")); if (secStr.Length < 11) { secStr = secStr.PadLeft(11, ' '); } return secStr; } /// /// Thread used to avoid deadlocks. /// private void displayQueueItems() { while (this.doRun) { lock (this.lockObject) { if (this.dispQueue.Count == 0) { // Wait for data to enter or timeout (in case we god out of sync) if (!Monitor.Wait(this.lockObject, 10000)) { if (this.dispQueue.Count > 0) { this.appendTextLn("LoggingForm.displayQueueItems(): Timeout with data present. this.sendQueue.Count=" + this.dispQueue.Count); } } } } DispQueueItem dispQueueItem = null; lock (this.lockObject) { if (this.dispQueue.Count > 0) { dispQueueItem = this.dispQueue.Dequeue(); } } if (dispQueueItem != null) { this.dispMsgInt(dispQueueItem.prefix, dispQueueItem.msg, dispQueueItem.addressStr, dispQueueItem.data); } } } /// /// Display one raw message with header info. /// /// Prefix to display with entry, e.g. 'Tx' /// Message to display. /// Address string. /// Payload string. private void dispMsgInt(string prefix, IPassThruMsg msg, string addressStr, string data) { if (this.InvokeRequired) { try { this.Invoke(new dispMsgIntFunc(this.dispMsgInt), new object[] { prefix, msg, addressStr, data }); } catch (System.Threading.ThreadAbortException) { throw; } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { if (msg != null) { this.appendText(prefix + " [" + msg.DataSize + " bytes] " + " " + addressStr + ":" + data); this.appendText(", ProtocolID=" + msg.ProtocolID); this.appendText(", RxStatus=" + msg.RxStatus); this.appendText(", TxFlags=" + msg.TxFlags); this.appendText(", Timestamp=" + msg.Timestamp); this.appendText(", ExtraDataIndex=" + msg.ExtraDataIndex + "\r\n"); if (msg.DataSize > 0) { if (prefix == "Rx" && msg.ExtraDataIndex == 0) { // this.parent.appendText("WARNING: ExtraDataIndex is zero, which it only shall be for pure status messages.\r\n"); } } } } } /// /// Workaround for insufficient native thread handling in C# /// /// Text string to append. /// Level of message, LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR /// 'true' if message was logged at the given log level. private delegate bool appendTextFunc(string txt, int logLevel); /// /// Display one raw message with header info. /// /// Prefix to display with entry, e.g. 'Tx' /// Message to display. /// Address string. /// Payload string. private delegate void dispMsgIntFunc(string prefix, IPassThruMsg msg, string addressStr, string data); /// /// Handle Form Closing event. /// /// Sending object. /// Event data. private void LoggingForm_FormClosing(object sender, FormClosingEventArgs e) { if (!this.controlledClose) { e.Cancel = true; } } /// /// Clear the log window from old events. /// /// Sending object. /// Event data. private void clearButton_Click(object sender, EventArgs e) { this.eventLogWindow.Text = string.Empty; } /// /// Clear the traffic table. /// /// Sending object. /// Event data. private void clearButton2_Click(object sender, EventArgs e) { this.trafficDataGridView.Rows.Clear(); } /// /// Add one row to the traffic table. /// /// Traffic direction and layer level. /// Address field. /// Mode field. /// PID field. /// Payload length. /// Payload data. private delegate void addRowFunc(rowMode direction, uint address, byte mode, uint pid, uint length, string rspStr); /// /// One item in the display queue. /// private class DispQueueItem { /// /// Gets logging prefix string, e.g. "Tx". /// public string prefix { get; private set; } /// /// Gets message to display content of. /// public IPassThruMsg msg { get; private set; } /// /// Gets address string related to log event. /// public string addressStr { get; private set; } /// /// Gets formatted data. /// public string data { get; private set; } /// /// Initializes a new instance of the class. /// /// Prefix to display with entry, e.g. 'Tx' /// Message to display. /// Address string. /// Payload string. public DispQueueItem(string prefix, IPassThruMsg msg, string addressStr, string data) { this.prefix = prefix; this.msg = msg; this.addressStr = addressStr; this.data = data; } } } }