//----------------------------------------------------------------------- // // 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 UserInterface.GUI.OBD { using System; using System.ComponentModel; using System.Drawing; using System.IO; using System.Threading; using System.Windows.Forms; using global::Protocol.OBD; using global::SharedObjects; using global::SharedObjects.DataMgmt; using global::SharedObjects.GUI; using global::SharedObjects.Misc; using global::SharedObjects.Misc.Objects; using global::SharedObjects.Protocol; using global::SharedObjects.Protocol.OBD; /// /// Panel for probing of possible PID values in a Mode. /// public partial class ProbePidsPanel : AbstractUserControl, IDataPanel { /// /// Color for items that was detected with a decent response. /// private static readonly Color OK_COLOR = Color.LightGreen; /// /// Color for items that did an error response. /// private static readonly Color ERR_COLOR = Color.LightPink; /// /// Color for items that timed out. /// private static readonly Color TIMEOUT_COLOR = Color.LightYellow; /// /// Thread for probing. /// private Thread probeThread = null; /// /// Flag to indicate if thread shall be executing. /// private bool doRun = true; /// /// Lock object for synchronizing. /// private object lockObject = new object(); /// /// Timeout flag. /// private bool timeout = true; /// /// Error flag. /// private bool error = false; /// /// Messaging interface instance. /// private IMessaging iMessaging; /// /// Data source instance. /// private IDataSource iDataSource; /// /// Mode parser instance. /// private ModeParser modeParser; /// /// Current mode. /// private byte mode = 0x00; /// /// First PID to scan. /// private uint startPid = 0x00; /// /// Last PID to scan. /// private uint endPid = 0x00; /// /// Mode value valid. /// private bool modeValid = false; /// /// Start PID valid. /// private bool startValid = false; /// /// End PID valid. /// private bool endValid = false; /// /// Flag indicating that all items shall be displayed regardless of they are found or not. /// private bool showAll = false; /// /// Initializes a new instance of the class. /// /// Event logging instance. /// Current Vehicle. /// Current Application Tree instance. /// Messaging interface instance. /// Data source instance. /// Mode parser instance. public ProbePidsPanel( ILogging iLogging, XmlClass.vehicle vehicle, IApplicationTree applicationTree, IMessaging iMessaging, IDataSource iDataSource, ModeParser modeParser) : base(iLogging, vehicle, applicationTree) { this.iMessaging = iMessaging; this.iDataSource = iDataSource; this.modeParser = modeParser; this.InitializeComponent(); this.showAll = this.showCheckbox.Checked; foreach (XmlClass.modeitem modeitem in this.iDataSource.modes) { this.modeComboBox.Items.Add("0x" + modeitem.mode_int.ToString("x2") + " " + modeitem.name); } } /// /// Perform parsing of data. /// /// Message mode. /// Actual data payload. /// Related transmitted message to help with parsing. /// Pop next request and send. /// Source address of received data. /// Detected protocol by ELM/AGV adapter. /// 'true' if successfully parsed by panel. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "payload", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "mode", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "error", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#", Justification = "Reviewed.")] public bool parse(uint mode, byte[] payload, IRequestData requestData, ref bool popNext, uint srcAddress, uint detectedProtocol) { #if TRACE this.iLogging.appendText("mode=0x" + mode.ToString("x2") + "\r\n"); #endif bool error = false; if (mode == 0x7f) { error = true; } else { } lock (this.lockObject) { this.error = error; this.timeout = false; Monitor.PulseAll(this.lockObject); } return true; } /// /// Start button clicked. /// /// Sending object. /// Event data. private void startButton_Click(object sender, EventArgs e) { this.startButtonEnabled(false); this.stopButtonEnabled(true); this.modeParser.probePids = true; this.listView1.Items.Clear(); this.probeThread = new Thread(this.probeThreadImpl); this.doRun = true; this.probeThread.Start(); } /// /// Stop button clicked. /// /// Sending object. /// Event data. private void stopButton_Click(object sender, EventArgs e) { this.stopButtonEnabled(false); this.doRun = false; this.probeThread.Abort(); this.probeThread.Interrupt(); this.probeThread.Join(4000); this.startButtonEnabled(true); this.modeParser.probePids = false; } /// /// Implementation of Thread for probing. /// private void probeThreadImpl() { foreach (XmlClass.modeitem modeitem in this.iDataSource.modes) { if (modeitem.mode_int == this.mode) { for (uint i = this.startPid; i <= this.endPid && this.doRun; i++) { XmlClass.pidgroup.pidlist pid = new XmlClass.pidgroup.pidlist(); pid.name = "PID 0x" + i.ToString("x2"); pid.pidIsMap = false; pid.plotHeight = 150; pid.refresh = "1:1"; pid.pid_int = i; RequestData msg; switch (this.mode) { case 0x02: msg = new RequestData(null, (byte)this.mode, pid, new TxMsg(new byte[] { (byte)this.mode, (byte)(pid.pid_int & 0xff), 0x00 })); break; case 0x22: msg = new RequestData(null, (byte)this.mode, pid, new TxMsg(new byte[] { (byte)this.mode, (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff) })); break; case 0xa0: msg = new RequestData(null, (byte)this.mode, pid, new TxMsg(new byte[] { (byte)this.mode, 0x00, (byte)((pid.pid_int >> 16) & 0xff), (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff), (byte)(pid.pid_bytes) })); break; case 0xa8: msg = new RequestData(null, (byte)this.mode, pid, new TxMsg(new byte[] { (byte)this.mode, 0x00, (byte)((pid.pid_int >> 16) & 0xff), (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff) })); break; default: msg = new RequestData(null, (byte)this.mode, pid, new TxMsg(new byte[] { (byte)this.mode, (byte)(pid.pid_int & 0xff) })); break; } this.iMessaging.queueMsg(msg); this.iMessaging.sendMsg(); lock (this.lockObject) { this.timeout = true; Monitor.Wait(this.lockObject, 200); ListViewItem listViewItem = new ListViewItem(); if (!this.timeout) { if (!this.error) { listViewItem.BackColor = OK_COLOR; listViewItem.Text = "A 0x" + i.ToString("x2"); } else { listViewItem.BackColor = ERR_COLOR; listViewItem.Text = "E 0x" + i.ToString("x2"); } } else { listViewItem.BackColor = TIMEOUT_COLOR; listViewItem.Text = "N 0x" + i.ToString("x2"); } if (this.showAll || (!this.error && !this.timeout)) { this.addItem(listViewItem); } } } } if (!this.doRun) { break; } } Thread.Sleep(1000); this.stopButtonEnabled(false); this.startButtonEnabled(true); this.modeParser.probePids = false; } /// /// Set Stop Button enable state. /// /// 'true' if set to enabled. private delegate void stopButtonEnabledFunc(bool enabled); /// /// Set Stop Button enable state. /// /// 'true' if set to enabled. private void stopButtonEnabled(bool enabled) { if (this.InvokeRequired) { try { this.Invoke(new stopButtonEnabledFunc(this.stopButtonEnabled), new object[] { enabled }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { this.stopButton.Enabled = enabled; } } /// /// Add one item to the List View. /// /// Item to add. private delegate void addItemFunc(ListViewItem listViewItem); /// /// Add one item to the List View. /// /// Item to add. private void addItem(ListViewItem listViewItem) { if (this.InvokeRequired) { try { this.Invoke(new addItemFunc(this.addItem), new object[] { listViewItem }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { this.listView1.Items.Add(listViewItem); } } /// /// Selected index in Mode combo box updated. /// /// Sending object. /// Event data. private void modeComboBox_SelectedIndexChanged(object sender, EventArgs e) { ComboBox tb = (ComboBox)sender; if (tb.Text.Trim().Length > 0) { try { this.mode = (byte)Utils.hexParse(tb.Text); this.modeValid = true; this.startButtonEnabled(true); } catch { } } } /// /// Validate range start value. /// /// Sending object. /// Event data. private void rangeStartTB_Validating(object sender, CancelEventArgs e) { TextBox tb = (TextBox)sender; if (tb.Text.Trim().Length > 0) { try { this.startPid = Utils.hexParse(tb.Text); this.startValid = true; this.startButtonEnabled(true); } catch (Exception ex) { if (MessageBox.Show( "Invalid data '" + tb.Text + "'\r\n" + ex.Message, "Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Hand) == DialogResult.OK) { e.Cancel = true; tb.BackColor = Color.LightPink; } else { tb.Text = string.Empty; } } } } /// /// Validate range end value. /// /// Sending object. /// Event data. private void rangeEndTB_Validating(object sender, CancelEventArgs e) { TextBox tb = (TextBox)sender; if (tb.Text.Trim().Length > 0) { try { this.endPid = Utils.hexParse(tb.Text); this.endValid = true; this.startButtonEnabled(true); } catch (Exception ex) { if (MessageBox.Show( "Invalid data '" + tb.Text + "'\r\n" + ex.Message, "Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Hand) == DialogResult.OK) { e.Cancel = true; tb.BackColor = Color.LightPink; } else { tb.Text = string.Empty; } } } } /// /// Set Start button enabled. /// /// 'true' if enabled. private delegate void startButtonEnabledFunc(bool enabled); /// /// Set Start button enabled. /// /// 'true' if enabled. private void startButtonEnabled(bool enabled) { if (this.InvokeRequired) { try { this.Invoke(new startButtonEnabledFunc(this.startButtonEnabled), new object[] { enabled }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { this.startButton.Enabled = enabled && this.endValid && this.startValid && this.modeValid; this.saveButton.Enabled = this.startButton.Enabled && this.listView1.Items.Count > 0; } } /// /// Save button clicked. /// /// Sending object. /// Event data. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Reviewed.")] private void saveButton_Click(object sender, EventArgs e) { this.saveFileDialog1.FileName = string.Empty; this.saveFileDialog1.DefaultExt = ".txt"; this.saveFileDialog1.AddExtension = true; this.saveFileDialog1.ValidateNames = true; this.saveFileDialog1.Filter = "Text File|*.txt"; if (this.saveFileDialog1.ShowDialog() == DialogResult.OK) { if (!string.IsNullOrEmpty(this.saveFileDialog1.FileName)) { System.IO.FileStream fileStream = null; StreamWriter streamWriter = null; try { fileStream = (System.IO.FileStream)this.saveFileDialog1.OpenFile(); streamWriter = new StreamWriter(fileStream, Utils.iso_8859_1); foreach (ListViewItem listViewItem in this.listView1.Items) { streamWriter.WriteLine(listViewItem.Text); } } catch (Exception ex) { MessageBox.Show( ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } finally { if (streamWriter != null) { try { streamWriter.Close(); } catch { } } if (fileStream != null) { try { fileStream.Close(); } catch { } } } } } } /// /// Checkbox for "Show All" changed state. /// /// Sending object. /// Event data. private void showCheckbox_CheckedChanged(object sender, EventArgs e) { CheckBox cb = (CheckBox)sender; this.showAll = cb.Checked; } } }