//-----------------------------------------------------------------------
//
// 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.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using global::Protocol.OBD;
using global::SharedObjects;
using global::SharedObjects.DataMgmt;
using global::SharedObjects.Misc;
using global::SharedObjects.Protocol;
using global::SharedObjects.Protocol.OBD;
using global::UserInterface.GUI;
///
/// Class for DTC handling.
///
public partial class ObdIso14229DtcPanel : AbstractUserControl, IDataPanel
{
///
/// OBD sub function byte.
///
private const byte REPORT_NUMBER_OF_DTC_BY_STATUS_MASK = 0x01;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_BY_STATUS_MASK = 0x02;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_SNAPSHOT_IDENTIFICATION = 0x03;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER = 0x04;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER = 0x05;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x06;
///
/// OBD sub function byte.
///
private const byte REPORT_NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD = 0x07;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_BY_SEVERITY_MASK_RECORD = 0x08;
///
/// OBD sub function byte.
///
private const byte REPORT_SEVERITY_INFORMATION_OF_DTC = 0x09;
///
/// OBD sub function byte.
///
private const byte REPORT_SUPPORTED_DTC = 0x0a;
///
/// OBD sub function byte.
///
private const byte REPORT_FIRST_TEST_FAILED_DTC = 0x0b;
///
/// OBD sub function byte.
///
private const byte REPORT_FIRST_CONFIRMED_DTC = 0x0c;
///
/// OBD sub function byte.
///
private const byte REPORT_MOST_RECENT_TEST_FAILED_DTC = 0x0d;
///
/// OBD sub function byte.
///
private const byte REPORT_MOST_RECENT_CONFIRMED_DTC = 0x0e;
///
/// OBD sub function byte.
///
private const byte REPORT_MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x0f;
///
/// OBD sub function byte.
///
private const byte REPORT_MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x10;
///
/// OBD sub function byte.
///
private const byte REPORT_NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x11;
///
/// OBD sub function byte.
///
private const byte REPORT_NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x12;
///
/// OBD sub function byte.
///
private const byte REPORT_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x13;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_FAULT_DETECTION_COUNTER = 0x14;
///
/// OBD sub function byte.
///
private const byte REPORT_DTC_WITH_PERMANENT_STATUS = 0x15;
///
/// Status bits.
///
private static readonly StatusItem[] statusItems =
{
new StatusItem(0, "Test Failed"),
new StatusItem(1, "Test Failed This Operation Cycle"),
new StatusItem(2, "Pending DTC"),
new StatusItem(3, "Confirmed DTC"),
new StatusItem(4, "Test Not Completed Since Last Clear"),
new StatusItem(5, "Test Failed Since Last Clear"),
new StatusItem(6, "Test Not Completed This Operation Cycle"),
new StatusItem(7, "Warning Indicator Requested"),
};
///
/// Display the DTC format type.
///
/// This is needed when parsing the DTC data.
///
///
private static readonly string[] dtcFormats =
{
"ISO 15031-6",
"ISO 14229-1",
"SAE J1939-73",
"ISO 11992-4",
};
///
/// Messaging instance.
///
private IMessaging iMessaging;
///
/// Current protocol.
///
private Protocols protocol;
///
/// List of OBD codes for vehicle.
///
private SortedDictionary obdcodes;
///
/// DTC counter.
///
private int dtcNum = 0;
///
/// The ISO 14229 OBD code mask.
///
private uint iso14229Mask = 0xff;
///
/// Preferences instance.
///
private IPreferences iPreferences;
///
/// Initializes a new instance of the class.
///
/// Current logging instance.
/// Preferences instance.
/// Application tree instance.
/// Messaging instance.
/// Current protocol instance.
/// Dictionary of OBD codes.
public ObdIso14229DtcPanel(
ILogging iLogging,
IPreferences iPreferences,
ApplicationTree applicationTree,
IMessaging iMessaging,
Protocols protocol,
SortedDictionary obdcodes)
: base(iLogging, null, applicationTree)
{
this.iPreferences = iPreferences;
this.iMessaging = iMessaging;
this.protocol = protocol;
this.obdcodes = obdcodes;
this.InitializeComponent();
this.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.maskByteTB.Text = "0x" + this.iso14229Mask.ToString("x2");
this.init();
}
///
/// 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.
public bool parse(uint mode, byte[] payload, IRequestData requestData, ref bool popNext, uint srcAddress, uint detectedProtocol)
{
bool success = false;
switch (mode)
{
case 0x59: // ISO 14229 DTC data.
this.dispatchResponse(payload, srcAddress);
success = true;
break;
default:
break;
}
return success;
}
///
/// Do the DTC parse.
///
/// Payload to parse.
private static void parseDtc(byte[] payload)
{
int ix = 0;
while (ix <= (payload.Length - 4))
{
uint dtc = (uint)((payload[0] << 16) | (payload[1] << 8) | payload[2]);
byte status = payload[3];
ix += 4;
}
}
///
/// Dispatch the response to parser.
///
/// Payload data.
/// Source address.
private delegate void dispatchResponseFunc(byte[] payload, uint srcAddress);
///
/// Dispatch the response to parser.
///
/// Payload data.
/// Source address.
private void dispatchResponse(byte[] payload, uint srcAddress)
{
if (this.InvokeRequired)
{
try
{
this.Invoke(new dispatchResponseFunc(this.dispatchResponse), new object[] { payload, srcAddress });
}
catch (ThreadAbortException)
{
throw;
}
catch (System.Reflection.TargetParameterCountException ex)
{
MessageBox.Show(
"Exception: " + ex.Message + "\r\n",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
catch (System.ObjectDisposedException)
{
// Ignore.
}
}
else
{
this.selectReportType(payload);
}
}
///
/// Select report type.
///
/// Payload data.
private void selectReportType(byte[] payload)
{
byte reportType = payload[0];
byte[] payload1 = Utils.extractData(payload, 2); // Trim off both Mode and report type bytes.
switch (reportType)
{
case REPORT_NUMBER_OF_DTC_BY_STATUS_MASK:
this.parseNumDtc(payload1);
break;
case REPORT_DTC_BY_STATUS_MASK:
parseDtc(payload1);
break;
case REPORT_DTC_SNAPSHOT_IDENTIFICATION:
break;
case REPORT_DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER:
break;
case REPORT_DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER:
break;
case REPORT_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER:
break;
case REPORT_NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD:
break;
case REPORT_DTC_BY_SEVERITY_MASK_RECORD:
break;
case REPORT_SEVERITY_INFORMATION_OF_DTC:
break;
case REPORT_SUPPORTED_DTC:
break;
case REPORT_FIRST_TEST_FAILED_DTC:
break;
case REPORT_FIRST_CONFIRMED_DTC:
break;
case REPORT_MOST_RECENT_TEST_FAILED_DTC:
break;
case REPORT_MOST_RECENT_CONFIRMED_DTC:
break;
case REPORT_MIRROR_MEMORY_DTC_BY_STATUS_MASK:
break;
case REPORT_MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER:
break;
case REPORT_NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK:
break;
case REPORT_NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK:
break;
case REPORT_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK:
break;
case REPORT_DTC_FAULT_DETECTION_COUNTER:
break;
case REPORT_DTC_WITH_PERMANENT_STATUS:
break;
default:
break;
}
}
///
/// Parse Number of DTCs and DTC format.
///
/// Payload to parse.
private void parseNumDtc(byte[] payload)
{
byte availabilityMask = payload[0];
byte formatIdentifier = payload[1];
uint numDtcs = (uint)(payload[2] << 8 | payload[3]);
for (int i = 0; i < statusItems.Length; i++)
{
if (((1 << i) & availabilityMask) != 0)
{
this.statusMaskClb.Items.Add(statusItems[i]);
this.statusMaskClb.SetItemChecked(i, true);
}
}
if (formatIdentifier < dtcFormats.Length)
{
this.dtcFormatTextBox.Text = dtcFormats[formatIdentifier];
}
else
{
this.dtcFormatTextBox.Text = "0x" + formatIdentifier.ToString("x2");
}
this.numDtcsTextBox.Text = numDtcs.ToString();
// Go for next step - the actual DTCs.
this.getDtcs();
}
///
/// Parse the payload for DTC:s.
///
/// Payload to parse.
/// Source address of received data.
/// Detected protocol by ELM/AGV adapter.
private void displayDtcs(byte[] payload, uint srcAddress, uint detectedProtocol)
{
if (this.InvokeRequired)
{
try
{
this.Invoke(new displayDtcsFunc(this.displayDtcs), new object[] { payload, srcAddress, detectedProtocol });
}
catch (ThreadAbortException)
{
throw;
}
catch (System.Reflection.TargetParameterCountException ex)
{
MessageBox.Show(
"Exception: " + ex.Message + "\r\n",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
catch (System.ObjectDisposedException)
{
// Ignore.
}
}
else
{
this.displayDtcsImpl(payload, srcAddress, detectedProtocol);
}
}
///
/// Initialize the panel.
///
private void init()
{
byte[] ba = new byte[] { TxMsg.MODE_ISO14229_DTC_READ, REPORT_NUMBER_OF_DTC_BY_STATUS_MASK, (byte)(0xff) };
this.dtcNum = 0;
this.dtcDGV.Rows.Clear();
this.iMessaging.queueMsg(null, new TxMsg(ba));
this.iMessaging.sendMsg();
}
///
/// Size changed.
///
/// Sending object.
/// Event data.
private void dtcDGW_SizeChanged(object sender, EventArgs e)
{
DataGridView dgv = (DataGridView)sender;
if (dgv != null)
{
int sz = dgv.Width - 260;
DataGridViewColumn col = dgv.Columns["description"];
col.Width = sz;
}
}
///
/// Get list of current DTCs from vehicle.
///
/// Sending object.
/// Event data.
private void currentDtcsButton_Click(object sender, EventArgs e)
{
this.init();
}
///
/// Get list of pending DTCs from vehicle.
///
/// Sending object.
/// Event data.
private void pendingDtcsButton_Click(object sender, EventArgs e)
{
this.dtcNum = 0;
this.dtcDGV.Rows.Clear();
this.iMessaging.queueMsg(null, new TxMsg(TxMsg.GET_PENDING_DTCS));
this.iMessaging.sendMsg();
}
///
/// Get list of permanent DTCs from vehicle.
///
/// Sending object.
/// Event data.
private void permanentDtcsButton_Click(object sender, EventArgs e)
{
this.dtcNum = 0;
this.dtcDGV.Rows.Clear();
this.iMessaging.queueMsg(null, new TxMsg(TxMsg.GET_PERMANENT_DTCS));
this.iMessaging.sendMsg();
}
///
/// Clear list of DTCs from vehicle.
///
/// Sending object.
/// Event data.
private void clearDtcsButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(
"Do you want to clear all DTC:s from the ECU?\r\n"
+ "Notice that this is a permanent action and can't be reversed.\r\n"
+ "You may want to save the DTC:s first if you haven't done that already.",
"Clear DTC:s",
MessageBoxButtons.OKCancel,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2) == DialogResult.OK)
{
this.iMessaging.queueMsg(null, new TxMsg(TxMsg.CLEAR_DTCS));
this.iMessaging.sendMsg();
Thread.Sleep(600);
this.dtcDGV.Rows.Clear();
this.iMessaging.queueMsg(null, new TxMsg(TxMsg.GET_DTCS)); // Re-read the DTC list.
this.iMessaging.sendMsg();
}
}
///
/// Parse the payload for DTC:s.
///
/// Payload to parse.
/// Source address of received data.
/// Detected protocol by ELM/AGV adapter.
private delegate void displayDtcsFunc(byte[] payload, uint srcAddress, uint detectedProtocol);
///
/// Select implementation depending on protocol.
///
/// Payload to parse.
/// Source address of received data.
/// Detected protocol by ELM/AGV adapter.
private void displayDtcsImpl(byte[] payload, uint srcAddress, uint detectedProtocol)
{
this.iLogging.appendText("this.protocol.id=" + this.protocol.id + "\r\n");
switch (this.protocol.id)
{
case -1:
case -2:
switch (detectedProtocol)
{
case 6:
case 7:
case 8:
case 9:
this.displayDtcsImpl1(payload, srcAddress);
break;
default:
this.displayDtcsImpl2(payload, srcAddress);
break;
}
break;
case Protocols.ISO9141:
this.displayDtcsImpl2(payload, srcAddress);
break;
case Protocols.ISO15765:
this.displayDtcsImpl1(payload, srcAddress);
break;
default:
this.displayDtcsImpl2(payload, srcAddress);
break;
}
}
///
/// The actual implementation of displaying DTCs for ISO15765.
///
/// Payload to parse.
/// Source address of received data.
private void displayDtcsImpl1(byte[] payload, uint srcAddress)
{
int dtcs = payload[ModeParser.BYTE_PID];
this.iLogging.appendText("Expected DTCs=" + dtcs + "\r\n");
if (dtcs == 0)
{
// MessageBox.Show("No DTC:s exists on ECU with address 0x" + srcAddress.ToString("x2") + ".", "DTC Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
int n0 = 2;
while (n0 < payload.Length && dtcs-- > 0)
{
string dtcStr = Utils.getDtc(payload, ref n0);
this.addDtcRow(srcAddress, dtcStr);
}
}
}
///
/// The actual implementation of displaying DTCs for non-ISO15765.
///
/// Payload to parse.
/// Source address of received data.
private void displayDtcsImpl2(byte[] payload, uint srcAddress)
{
int n0 = 1;
while (n0 < payload.Length - 1)
{
string dtcStr = Utils.getDtc(payload, ref n0);
if (!dtcStr.Equals("P0000"))
{
this.addDtcRow(srcAddress, dtcStr);
}
}
}
///
/// Add one DTC row to table.
///
/// Address of originating ECU.
/// DTC to display.
private void addDtcRow(uint srcAddress, string dtcStr)
{
bool invalid = true;
string txt = string.Empty;
if (this.obdcodes.ContainsKey(dtcStr))
{
txt = (string)this.obdcodes[dtcStr];
invalid = (txt == null);
}
if (invalid)
{
txt = " * Unknown * ";
}
this.iLogging.appendText("dtcstr='" + dtcStr + "' " + txt + "\r\n");
DataGridViewRowCollection dgvrc = this.dtcDGV.Rows;
int n1 = dgvrc.Add(1);
DataGridViewRow dgvr = dgvrc[n1];
DataGridViewCellCollection dgvcc = dgvr.Cells;
dgvcc["count"].Value = (this.dtcNum++).ToString();
dgvcc["ecu"].Value = "0x" + srcAddress.ToString("x2");
dgvcc["dtc"].Value = dtcStr;
dgvcc["description"].Value = txt;
}
///
/// Button clicked for saving the DTC:s to file.
///
/// Sending object.
/// Event data.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Reviewed.")]
private void saveDtcsButton_Click(object sender, EventArgs e)
{
if (this.dtcDGV.Rows.Count == 0)
{
MessageBox.Show(
"Nothing to save",
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
else
{
this.saveFileDialog1.FileName = "DTCS";
this.saveFileDialog1.DefaultExt = ".csv";
this.saveFileDialog1.AddExtension = true;
this.saveFileDialog1.ValidateNames = true;
this.saveFileDialog1.Filter = "Excel CSV Format|*.csv";
if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if (!string.IsNullOrEmpty(this.saveFileDialog1.FileName))
{
string csvSep = ";";
if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ".")
{
csvSep = ",";
}
try
{
using (FileStream fs = (System.IO.FileStream)this.saveFileDialog1.OpenFile())
{
try
{
using (StreamWriter sw = new StreamWriter(fs))
{
try
{
sw.WriteLine("#" + csvSep + "\"ECU\"" + csvSep + "\"DTC\"" + csvSep + "\"Description\"");
DataGridViewRowCollection dgvrc = this.dtcDGV.Rows;
for (int i = 0; i < dgvrc.Count; i++)
{
DataGridViewRow dgvr = dgvrc[i];
DataGridViewCellCollection dgvcc = dgvr.Cells;
string ecuStr = (string)dgvcc["ecu"].Value;
string dtcStr = (string)dgvcc["dtc"].Value;
string descriptionStr = (string)dgvcc["description"].Value;
ecuStr = ecuStr.Replace("\"", "''");
dtcStr = dtcStr.Replace("\"", "''");
descriptionStr = descriptionStr.Replace("\"", "''");
sw.WriteLine(dgvcc["count"].Value + csvSep + "\"" + ecuStr + "\"" + csvSep + "\"" + dtcStr + "\"" + csvSep + "\"" + descriptionStr + "\"");
}
}
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);
}
}
}
}
}
///
/// Link clicked for request for additional info about DTC.
///
/// This opens a web browser that points to a site with extended information.
///
///
/// Sending object.
/// Event data.
private void dtcDGV_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
int col = e.ColumnIndex;
if (col == 2)
{
string str = (string)this.dtcDGV.CurrentCell.Value;
string site = this.iPreferences.obdCodeDatabase;
if (site == null || site.Trim().Length == 0)
{
site = Utils.DEFAULT_OBD_CODE_SITE;
}
System.Diagnostics.Process.Start(site + str);
}
}
///
/// Get DTCs using the ISO14229 definition.
///
/// Sending object.
/// Event data.
private void currentIso14229DtcsButton_Click(object sender, EventArgs e)
{
this.getDtcs();
}
///
/// Request the DTCs from vehicle.
///
private void getDtcs()
{
byte[] ba = new byte[] { TxMsg.MODE_ISO14229_DTC_READ, REPORT_DTC_BY_STATUS_MASK, (byte)(this.iso14229Mask & 0xff) };
this.dtcNum = 0;
this.dtcDGV.Rows.Clear();
this.iMessaging.queueMsg(null, new TxMsg(ba));
this.iMessaging.sendMsg();
}
///
/// Fetch the mask data from the mask table.
///
/// Mask value.
private uint getMaskBits()
{
uint mask = 0;
for (int i = 0; i < this.statusMaskClb.Items.Count; i++)
{
StatusItem statusItem = (StatusItem)this.statusMaskClb.Items[i];
if (this.statusMaskClb.GetItemChecked(i))
{
mask |= (uint)(1 << statusItem.bit);
}
}
return mask;
}
///
/// Clear DTCs using the ISO14229 definition.
///
/// Sending object.
/// Event data.
private void clearIso14229DtcsButton_Click(object sender, EventArgs e)
{
}
///
/// Refresh mask data.
///
/// Sending object.
/// Event data.
private void statusMaskClb_SelectedIndexChanged(object sender, EventArgs e)
{
this.iso14229Mask = this.getMaskBits();
this.maskByteTB.Text = "0x" + this.iso14229Mask.ToString("x2");
}
///
/// Refresh button clicked.
///
/// Sending object.
/// Event data.
private void refreshButton_Click(object sender, EventArgs e)
{
this.init();
}
///
/// Manual Mask button clicked.
///
/// Sending object.
/// Event data.
private void manualMaskButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < statusItems.Length; i++)
{
this.statusMaskClb.Items.Add(statusItems[i]);
this.statusMaskClb.SetItemChecked(i, true);
}
}
///
/// One status bit item.
///
private class StatusItem
{
///
/// Gets bit number.
///
public int bit { get; private set; }
///
/// Gets text describing bit.
///
public string text { get; private set; }
///
/// Initializes a new instance of the class.
///
/// Bit number.
/// Text describing bit.
public StatusItem(int bit, string text)
{
this.bit = bit;
this.text = text;
}
///
/// Get the string representation of the object.
///
/// String representation.
public override string ToString()
{
return this.text;
}
}
}
}