//----------------------------------------------------------------------- // // 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 Protocol.OBD { using System; using System.Collections.Generic; using System.Linq; using global::SharedObjects; using global::SharedObjects.CAN.Objects; using global::SharedObjects.GUI; using global::SharedObjects.Misc; using global::SharedObjects.Protocol; using global::SharedObjects.Protocol.OBD; /// /// Class that parses PID data. /// public class PidParser : IPidParser { /* * Byte number according to J1979. * The label to number mapping will make it easier * whenever (if ever) necessary to change the offsets. */ /// /// Byte position A /// public const int BYTE_A = 0; /// /// Byte position B /// public const int BYTE_B = 1; /// /// Byte position C /// public const int BYTE_C = 2; /// /// Byte position D /// public const int BYTE_D = 3; /// /// Byte position E /// public const int BYTE_E = 4; /// /// Byte position F /// public const int BYTE_F = 5; /// /// Byte position G /// public const int BYTE_G = 6; /// /// Byte position H /// public const int BYTE_H = 7; /// /// Byte position I /// public const int BYTE_I = 8; /// /// Byte position J /// public const int BYTE_J = 9; /// /// Byte position K /// public const int BYTE_K = 10; /// /// Byte position L /// public const int BYTE_L = 11; /// /// Byte position M /// public const int BYTE_M = 12; /// /// Byte position N /// public const int BYTE_N = 13; /// /// Byte position O /// public const int BYTE_O = 14; /// /// Byte position P /// public const int BYTE_P = 15; /// /// Byte position Q /// public const int BYTE_Q = 16; /// /// Byte position R /// public const int BYTE_R = 17; /// /// Byte position S /// public const int BYTE_S = 18; /// /// Byte position T /// public const int BYTE_T = 19; /// /// Byte position U /// public const int BYTE_U = 20; /// /// Byte position V /// public const int BYTE_V = 21; /// /// Byte position W /// public const int BYTE_W = 22; /// /// Byte position X /// public const int BYTE_X = 23; /// /// Byte position Y /// public const int BYTE_Y = 24; /// /// Byte position Z /// public const int BYTE_Z = 25; /* Data formattings. */ /// /// Float with three decimals. /// public const int DT_DECIMAL_FLOAT3 = -3; /// /// Float with two decimals. /// public const int DT_DECIMAL_FLOAT2 = -2; /// /// Float with one decimal. /// public const int DT_DECIMAL_FLOAT1 = -1; /// /// String value. /// public const int DT_STRING = 0; /// /// Integer in decimal form. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "int", Justification = "Suitable in this case.")] public const int DT_DECIMAL_INT = 1; /// /// Integer in hex(2) form. /// public const int DT_HEX2 = 2; /// /// Integer in hex(4) form. /// public const int DT_HEX4 = 3; /// /// Integer in hex(8) form. /// public const int DT_HEX8 = 4; /// /// Integer in hex(16) form. /// public const int DT_HEX16 = 5; /// /// Integer in hex(2 + 2) form. /// public const int DT_HEX4_GROUP = 6; /// /// Integer in hex(2 + 2 + 2 + 2) form. /// public const int DT_HEX8_GROUP = 7; /// /// Integer in hex(2 + 2 + 2 + 2 + 2 + 2 + 2 + 2) form. /// public const int DT_HEX16_GROUP = 8; /// /// Integer in hex(8 + 8) form. /// public const int DT_HEX16_2GROUP = 9; /// /// Integer in hex(4 + 4 + 4 + 4) form. /// public const int DT_HEX16_4GROUP = 10; /// /// Logger instance. /// private ILogging iLogging; /// /// Messaging instance. /// private IMessaging iMessaging; /// /// Current PID ID. /// private uint pid; /// /// Current PID item. /// private XmlClass.pidgroup.pidlist pidItem; /// /// List of result data receivers. /// private List iDataPresentations = new List(); /// /// Current mode byte value. /// private byte mode; /// /// Frame number for freeze frame data. /// private byte frame; /// /// Lock object to avoid collission. /// private object uiManipulationLockObject = new object(); /// /// Source address for message. /// private uint srcAddress = 0; /// /// Data panel instance. /// private List obdDataPanels = new List(); /// /// Time marker. /// private long t1 = 0; /// /// Lock object for OBD data list. /// private object obdDataLockObject = new object(); /// /// Gauge presentation instance. /// private IGaugePresentation iGaugePresentation; /// /// Dictionary of next PIDs per source address. /// private List nextPidWaits = new List(); /// /// Initializes a new instance of the class. /// /// Logger instance. /// Messaging instance. public PidParser( ILogging iLogging, IMessaging iMessaging) { this.iLogging = iLogging; this.iMessaging = iMessaging; } /// /// Get the computed value for the given sensor. /// 'sensorNumeric' is in numerical form for plotting and the function returns a formatted string /// for presentation. /// /// Logger instance. /// Raw value. /// Current sensor. /// Variable useful for further computation - like plotting. /// Interpreted sensor value in string. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed.")] public static string getSensorValue( ILogging iLogging, uint data, XmlClass.pidgroup.pidlist.sensordata sensor, out double sensorNumeric) { string sensorValue = string.Empty; double value = data; if (sensor != null && iLogging != null) { bool? boolValue = null; if (sensor.startbit != null && sensor.endbit != null && sensor.startbit.Trim().Length > 0 && sensor.endbit.Trim().Length > 0) { getBitMaskedValue(iLogging, data, sensor, ref sensorValue, ref value, ref boolValue); } if (boolValue == null) { switch (sensor.unit) { case "Emission": sensorValue = getEmissionDesign((byte)value); break; case "Fuel": sensorValue = getFuelType((byte)value); break; case "OBD": sensorValue = getObdValue((byte)value); break; default: value = scaleValue(sensor, value); sensorValue = formatValue(sensor, value); break; } } } sensorNumeric = value; #if DISPLAY_RAW sensorValue += " [0x" + data.ToString("x2") + "]"; #endif return sensorValue; } /// /// Get the raw value according to sensor from the payload data. /// /// Payload data. /// Offset in payload for wanted data. /// Number of bytes for wanted data. /// Raw value. public static uint getRawValue(byte[] pidPayload, uint offset, uint count) { uint rawdata = 0; if (pidPayload != null) { for (int i = 0; i < count && (i + offset) < pidPayload.Length; i++) { rawdata = (rawdata << 8) | (uint)(pidPayload[i + offset] & 0xff); } } return rawdata; } /// /// Sets Data panel instance. /// /// Data panel instance. public void addPidReceiver(IObdDataPanel obdDataPanel) { lock (this.obdDataLockObject) { this.obdDataPanels.Add(obdDataPanel); } } /// /// Add data presentation instance. /// /// Data presentation instance. public void addDataPresentation(IDataPresentation iDataPresentation) { lock (this.uiManipulationLockObject) { if (!this.iDataPresentations.Contains(iDataPresentation)) { this.iDataPresentations.Add(iDataPresentation); } } } /// /// Set gauge presentation instance. /// /// Gauge presentation instance. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "iGaugePresentation", Justification = "Reviewed, intentional.")] public void setGaugePresentation(IGaugePresentation iGaugePresentation) { this.iGaugePresentation = iGaugePresentation; } /// /// Remove data presentation instance. /// /// Data presentation instance. public void removeUi(IDataPresentation iDataPresentation) { lock (this.uiManipulationLockObject) { if (this.iDataPresentations.Contains(iDataPresentation)) { this.iDataPresentations.Remove(iDataPresentation); } } } /// /// Parse PID. /// /// PID Item. /// Mode byte. /// Frame number for freeze frame data. /// Payload to parse. /// Source address of received data. /// 'true' if it's OK to pop the next message to send and send it. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "frame", Justification = "Reviewed, intentional.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "mode", Justification = "Reviewed, intentional.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "pidItem", Justification = "Reviewed, intentional.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "srcAddress", Justification = "Reviewed, intentional.")] public bool parsePid(XmlClass.pidgroup.pidlist pidItem, byte mode, byte frame, byte[] pidPayload, uint srcAddress) { bool popNext = true; if (pidItem != null) { this.pidItem = pidItem; this.mode = mode; this.frame = frame; this.srcAddress = srcAddress; this.t1 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND; this.pid = pidItem.pid_int; switch (this.pid) { case 0x00: // Supported PID:s 01-20 case 0x20: // Supported PID:s 21-40 case 0x40: // Supported PID:s 41-60 case 0x60: // Supported PID:s 61-80 case 0x80: // Supported PID:s 81-A0 case 0xA0: // Supported PID:s A1-E0 case 0xC0: // Supported PID:s C1-E0 case 0xE0: // Supported PID:s E1-FF popNext = this.pidParser(pidPayload); break; case 0x90: // World Wide Harmonized On-Board-Diagnostic // this.wwhVehicleObdInfo(pidPayload); break; case 0x91: // World Wide Harmonized On-Board-Diagnostic // this.wwhEcuObdInfo(pidPayload); break; case 0x93: // World Wide Harmonized On-Board-Diagnostic // this.wwhObdCountersInfo(pidPayload); break; default: popNext = this.genericHandler(pidPayload, popNext); break; } } return popNext; } /// /// The value is only some bits of the data. /// /// Logger instance. /// Raw data. /// Sensor data. /// Sensor value string. /// Numeric value. /// Boolean value. (Only set at single bit data) [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "iLogging", Justification = "Reviewed.")] private static void getBitMaskedValue( ILogging iLogging, uint data, XmlClass.pidgroup.pidlist.sensordata sensor, ref string sensorValue, ref double value, ref bool? boolValue) { int startBit = Convert.ToInt32(sensor.startbit.Trim()); int endBit = Convert.ToInt32(sensor.endbit.Trim()); value = 0; if (startBit == endBit) { bool checkFlag = ((data >> startBit) & 0x01) != 0; boolValue = checkFlag; sensorValue = checkFlag.ToString(); value = checkFlag ? 1 : 0; value = (value * 0.8) + 0.05 + (startBit / 50.0); } else { int bits = endBit - startBit + 1; if (bits > 0) { uint mask = (uint)(Math.Pow(2, bits) - 1); uint intValue = (uint)((data >> startBit) & mask); value = intValue; } else { sensorValue = "Invalid sensor declaration of 'startbit' and 'endbit'"; value = 0; boolValue = false; } } } /// /// Scale the value according to sensor. /// /// Sensor data. /// Value to scale. /// Scaled value. private static double scaleValue(XmlClass.pidgroup.pidlist.sensordata sensor, double value) { value = value + sensor.scaleoffset; value = value * sensor.scalefactor; return value; } /// /// Format value according to sensor rules. /// /// Sensor data. /// Value to format. /// Formatted value string. private static string formatValue(XmlClass.pidgroup.pidlist.sensordata sensor, double value) { string format = sensor.sensorPresentation_format; if (format == null || format.Trim().Length == 0) { format = "0.000"; } string sensorValue; try { sensorValue = value.ToString(format); } catch { sensorValue = value.ToString("0.000") + " ¹"; } return sensorValue; } /// /// Filters the sensor list by using "controlbit" sensors to decide if a sensor shall be displayed or not. /// /// Payload data. /// Raw sensor list. /// Filtered sensor list. private static IList getFilteredSensorList(byte[] pidPayload, IList sensors) { IList actualSensors = new List(); IList inactiveControlSensors = new List(); bool hasControlSensor = false; foreach (XmlClass.pidgroup.pidlist.sensordata sensor in sensors) { if (sensor.unit == "controlbit") { hasControlSensor = true; try { uint offset = sensor.offset; uint count = sensor.count; int startBit = safeGetInt(sensor.startbit); int endBit = safeGetInt(sensor.endbit); if (startBit >= 0 && endBit >= 0) { if ((offset + count) <= pidPayload.Length && count == 1 && startBit == endBit) { if ((pidPayload[offset] & (1 << startBit)) == 0) { inactiveControlSensors.Add(sensor); } } } } catch { } } else { actualSensors.Add(sensor); } } if (hasControlSensor) { XmlClass.pidgroup.pidlist.sensordata[] actualSensorArray = actualSensors.ToArray(); foreach (XmlClass.pidgroup.pidlist.sensordata sensor in actualSensorArray) { bool match = false; int startBit = safeGetInt(sensor.startbit); int endBit = safeGetInt(sensor.endbit); if (startBit >= 0 && endBit >= 0) { foreach (XmlClass.pidgroup.pidlist.sensordata controlSensor in inactiveControlSensors) { int controlStartBit = (int)controlSensor.presentation_min; int controlEndBit = (int)controlSensor.presentation_max; if (sensor.offset == controlSensor.scaleoffset && sensor.count == (uint)controlSensor.scalefactor && startBit == controlStartBit && endBit == controlEndBit) { match = true; break; } } if (match) { actualSensors.Remove(sensor); } } } } return actualSensors; } /// /// Safely get an int value from string. /// /// String to get int from. /// Converted int value. private static int safeGetInt(string strValue) { int intValue = 0; try { intValue = Convert.ToInt32(strValue); } catch { } return intValue; } /// /// Get the OBD description string. /// /// Raw data for OBD type. /// String describing the OBD variant. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Reviewed, not complex.")] private static string getObdValue(byte rawData) { string value; switch (rawData) { case 0x01: value = "OBD-II as defined by the CARB"; break; case 0x02: value = "OBD as defined by the EPA"; break; case 0x03: value = "OBD and OBD-II"; break; case 0x04: value = "OBD-I"; break; case 0x05: value = "Not meant to comply with any OBD standard"; break; case 0x06: value = "EOBD (Europe)"; break; case 0x07: value = "EOBD and OBD-II"; break; case 0x08: value = "EOBD and OBD"; break; case 0x09: value = "EOBD, OBD and OBD II"; break; case 0x0A: value = "JOBD (Japan)"; break; case 0x0B: value = "JOBD and OBD II"; break; case 0x0C: value = "JOBD and EOBD"; break; case 0x0D: value = "JOBD, EOBD, and OBD II"; break; case 0x11: value = "Engine Manufacturer Diagnostics (EMD) - Heavy-duty vehicles (>14,000) certified to EMD under title 13, CCR section 1971 (e.g., 2007-2009 model year diesel and gasoline engines)"; break; case 0x12: value = "Engine Manufacturer Diagnostics Enhanced (EMD+) - Heavy-duty engines (>14,000) certified to EMD+ under title 13, CCR section 1971.1 (e.g., 2010-2012 model year diesel and gasoline engines not certified to HD OBD, 2013-2019 model year alternate fuel engines)"; break; case 0x13: value = "Heavy Duty On-Board Diagnostics (Child/Partial) - Heavy-duty engines (>14,000) certified to HDOBD as an extrapolated/child rating under title 13, CCR section 1971.1(d)(7.1.2) or (7.2.3) (e.g., 2010-2015 model year diesel and gasoline engines that are subject to HDOBD but are not the full OBD/parent rating)"; break; case 0x14: value = "Heavy Duty On-Board Diagnostics - Heavy-duty engines (>14,000) certified to HDOBD as a full OBD/parent rating under title 13, CCR section 1971.1(d)(7.1.1) or (7.2.2) (e.g., 2010 and beyond model year diesel and gasoline engines that are subject to full HDOBD)"; break; case 0x15: value = "World Wide Harmonized OBD"; break; case 0x17: value = "Heavy Duty Euro OBD Stage I without NOx control"; break; case 0x18: value = "Heavy Duty Euro OBD Stage I with NOx control"; break; case 0x19: value = "Heavy Duty Euro OBD Stage II without NOx control"; break; case 0x1A: value = "Heavy Duty Euro OBD Stage II with NOx control"; break; case 0x1C: value = "Brazil OBD Phase 1"; break; case 0x1D: value = "Brazil OBD Phase 2"; break; case 0x1E: value = "Korean OBD"; break; case 0x1F: value = "India OBD I"; break; case 0x20: value = "India OBD II"; break; case 0x21: value = "Heavy Duty Euro OBD Stage VI"; break; default: value = "ISO/SAE reserved or Undefined OBD standard: 0x" + string.Format("{0:x2} ", (int)rawData); break; } return value; } /// /// Format data in a user friendly manner for the function. /// /// Payload data. /// String describing the fuel type. private static string getFuelType(byte rawData) { string value; switch (rawData) { case 0x01: value = "Gasoline"; break; case 0x02: value = "Methanol"; break; case 0x03: value = "Ethanol"; break; case 0x04: value = "Diesel"; break; case 0x05: value = "LPG"; break; case 0x06: value = "CNG"; break; case 0x07: value = "Propane"; break; case 0x08: value = "Electric"; break; case 0x09: value = "Bifuel running Gasoline"; break; case 0x0A: value = "Bifuel running Methanol"; break; case 0x0B: value = "Bifuel running Ethanol"; break; case 0x0C: value = "Bifuel running LPG"; break; case 0x0D: value = "Bifuel running CNG"; break; case 0x0E: value = "Bifuel running Propane"; break; case 0x0F: value = "Bifuel running Electricity"; break; case 0x10: value = "Bifuel mixed gas/electric"; break; case 0x11: value = "Hybrid gasoline"; break; case 0x12: value = "Hybrid Ethanol"; break; case 0x13: value = "Hybrid Diesel"; break; case 0x14: value = "Hybrid Electric"; break; case 0x15: value = "Hybrid Mixed fuel"; break; case 0x16: value = "Hybrid Regenerative"; break; case 0x17: value = "Bifuel running Diesel"; break; default: value = "Undefined fuel type 0x" + string.Format("{0:x2} ", (int)rawData); break; } return value; } /// /// Format data in a user friendly manner for the function. /// /// Payload data. /// String describing the emission design. private static string getEmissionDesign(byte rawData) { string value; switch (rawData) { case 0x0E: value = "Heavy Duty Vehicles (EURO IV) B1"; break; case 0x0F: value = "Heavy Duty Vehicles (EURO V) B2"; break; case 0x10: value = "Heavy Duty Vehicles (EURO EEV) C"; break; default: value = " * Unknown * 0x" + string.Format("{0:x2} ", (int)rawData); break; } return value; } /// /// A generic handler of data that uses data from definition in XML files /// to format the data to be presented to the user. /// /// Raw data. /// Indicates if it's OK to pop the next message to send and send it. /// 'true' if it's OK to pop the next message to send and send it. private bool genericHandler(byte[] pidPayload, bool popNext) { IList sensors = this.pidItem.sensors; if (sensors != null && sensors.Count > 0) { this.presentData(pidPayload, sensors); } return popNext; } /// /// Build presentation of data. /// /// Raw data. /// List of sensors to consider. private void presentData(byte[] pidPayload, IList sensors) { IList actualSensors = getFilteredSensorList(pidPayload, sensors); RawLogObject rawLogObject = new RawLogObject(this.srcAddress, this.mode, this.pidItem.pid_int, (uint)pidPayload.Length, pidPayload); this.iLogging.addRawData(rawLogObject); foreach (XmlClass.pidgroup.pidlist.sensordata sensor in actualSensors) { uint offset = sensor.offset; uint count = sensor.count; if ((offset + count) <= pidPayload.Length) { string name = this.pidItem.name; string unit = sensor.unit != null ? sensor.unit : string.Empty; uint rawValue = getRawValue(pidPayload, offset, count); bool naFlag = Utils.checkNaData(sensor, rawValue); double value; string formattedValue = getSensorValue(this.iLogging, rawValue, sensor, out value); // Don't update the GUI if we are logging but not plotting. // Maybe add some GUI feedback just to tell that it is // doing it's thing later on. this.addSimpleData(sensor, name, unit, value, formattedValue, naFlag); } else { this.iLogging.appendText( this.pidItem.name + ", " + sensor.name + " (" + sensor.unit + ")" + "; Expecting more data than we got: expected=" + (offset + count) + ", we got=" + pidPayload.Length + "\r\n", LogLevel.LOG_WARN); } } } /// /// Add a simple text row with data. /// /// Current sensor to plot. /// Name of sensor. /// Presentation unit. /// Value to plot. /// Formatted value to display. /// Value is potentially invalid. private void addSimpleData( XmlClass.pidgroup.pidlist.sensordata sensor, string name, string unit, double value, string formattedValue, bool naFlag) { lock (this.uiManipulationLockObject) { if (this.iGaugePresentation != null) { this.iGaugePresentation.setGaugeValue(this.mode, this.pidItem.pid_int, sensor, (float)value); } else { foreach (IDataPresentation iDataPresentation in this.iDataPresentations) { // Output to table. if (sensor.name != null && sensor.name.Trim().Length > 0) { name += ": " + sensor.name.Trim(); } iDataPresentation.simpleDataAdd( new PresentationData( this.srcAddress, this.mode, this.pid, this.pidItem, sensor, name, unit, formattedValue, value, this.t1, naFlag)); } } } } /// /// Parse PID bitmap. /// /// Payload data. /// 'true' if it's OK to pop the next message to send and send it. private bool pidParser(byte[] pidPayload) { bool popNext = true; if (pidPayload.Length >= 4) { byte mode1 = (byte)(this.mode & 0xbf); int bitmap = 0; for (int i = 0; i < 4; i++) { bitmap = bitmap << 8 | (int)(pidPayload[i] & 0xff); } byte nextPid = 0; switch (this.pid) { case 0x00: // Supported PID:s 01-20 if ((bitmap & 0x01) != 0) { nextPid = 0x20; } break; case 0x20: // Supported PID:s 21-40 if ((bitmap & 0x01) != 0) { nextPid = 0x40; } break; case 0x40: // Supported PID:s 41-60 if ((bitmap & 0x01) != 0) { nextPid = 0x60; } break; case 0x60: // Supported PID:s 61-80 if ((bitmap & 0x01) != 0) { nextPid = 0x80; } break; case 0x80: // Supported PID:s 81-A0 if ((bitmap & 0x01) != 0) { nextPid = 0xA0; } break; case 0xA0: // Supported PID:s A1-E0 if ((bitmap & 0x01) != 0) { nextPid = 0xC0; } break; case 0xC0: // Supported PID:s C1-E0 if ((bitmap & 0x01) != 0) { nextPid = 0xE0; } break; case 0xE0: // Supported PID:s E1-FF break; default: break; } this.updatePidWaits(nextPid); this.updateObdDataPanels(bitmap, nextPid); if (nextPid != 0) { this.requestNextPidBlock(mode1, nextPid); } } return popNext; } /// /// Perform a request for the next PID block. /// /// Request mode. /// PID to request. private void requestNextPidBlock(byte mode1, byte nextPid) { uint? destinationAddress = DataRequester.getDestinationAddress(this.iMessaging.protocolHandler, this.srcAddress); // When we got one PID set we ask for next set. // this.iMessaging.queueMsg(new TxMsg(new byte[] { 0x01, nextPid })); if (mode1 == 0x02) { this.iMessaging.queueMsg(destinationAddress, new TxMsg(new byte[] { mode1, nextPid, this.frame })); } else { this.iMessaging.queueMsg(destinationAddress, new TxMsg(new byte[] { mode1, nextPid })); } } /// /// Update the list of address where we are waiting for PID data from. /// /// Next PID block to request. private void updatePidWaits(byte nextPid) { if (nextPid != 0 && !this.nextPidWaits.Contains(this.srcAddress)) { this.nextPidWaits.Add(this.srcAddress); } else { if (nextPid == 0 && this.nextPidWaits.Contains(this.srcAddress)) { this.nextPidWaits.Remove(this.srcAddress); } } } /// /// Update the OBD data panels to display PID entries according to bitmap. /// /// Bitmap for entries to display. /// Next PID in sequence. private void updateObdDataPanels(int bitmap, byte nextPid) { IObdDataPanel[] obdDataPanelArray; lock (this.obdDataLockObject) { obdDataPanelArray = this.obdDataPanels.ToArray(); } foreach (IObdDataPanel panel in obdDataPanelArray) { panel.addDataItem(this.srcAddress, this.mode, this.pid, bitmap); } // If we don't expect any more answers... if (nextPid == 0 && this.nextPidWaits.Count == 0) { lock (this.obdDataLockObject) { obdDataPanelArray = this.obdDataPanels.ToArray(); } foreach (IObdDataPanel panel in obdDataPanelArray) { panel.displayFields(); } } } } }