//----------------------------------------------------------------------- // // 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 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 accessing on-board monitoring test results. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnBoard", Justification = "Reviewed.")] public partial class OnBoardDiagTestPanel : AbstractUserControl, IDataPanel { /// /// Messaging instance. /// private IMessaging iMessaging; /// /// Mode Parser instance. /// private ModeParser modeParser; /// /// Initializes a new instance of the class. /// public OnBoardDiagTestPanel() { this.iMessaging = null; this.modeParser = null; 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))); } /// /// Initializes a new instance of the class. /// /// Logging instance. /// Application tree instance. /// Messaging instance. /// Data source interface instance. /// Current vehicle instance. /// Mode parser instance. /// Data file directory path. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "CanApp.CustomToolTip", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed.")] public OnBoardDiagTestPanel( ILogging iLogging, IApplicationTree applicationTree, IMessaging iMessaging, IDataSource iDataSource, XmlClass.vehicle vehicle, ModeParser modeParser, string dataFileDir) : base(iLogging, vehicle, applicationTree) { this.iMessaging = iMessaging; this.modeParser = modeParser; this.InitializeComponent(); this.obdDataPanel1.init( iLogging, iMessaging, applicationTree, 0x06, iDataSource, vehicle, dataFileDir); 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))); } /// /// 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; if (payload != null) { switch (mode) { case 0x46: // On-board monitoring test results uint pid = (uint)(payload[1] & 0xff); byte[] pidPayload = Utils.extractData(payload, 1); this.parsePid(srcAddress, mode, pid, pidPayload); success = true; break; default: break; } } return success; } /// /// Initialize and refresh data. /// public void init() { IRequestData rd = new RequestData(null, TxMsg.MODE_CURRENT, null, new TxMsg(TxMsg.GET_SUPPORTED_OBDMIDS)); this.iMessaging.queueMsg(rd); this.iMessaging.sendMsg(); } /// /// Scale the value and get the unit for the value. /// /// Scaling and Unit ID /// Raw Value /// Out: Unit string. /// Scaled Value. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "Reviewed.")] private static double scaleValue(byte unitScalingId, int value, out string unit) { double result = 0; unit = string.Empty; switch (unitScalingId) { case 0x01: result = value; break; case 0x02: result = value / 10.0; break; case 0x03: result = value / 100.0; break; case 0x04: result = value / 1000.0; break; case 0x05: result = value / 32768.0; break; case 0x06: result = value / 3276.8; break; case 0x07: result = value / 4.0; unit = "rpm"; break; case 0x08: result = value / 100.0; unit = "km/h"; break; case 0x09: result = value; unit = "km/h"; break; case 0x0a: result = value / 8192.0; unit = "mV"; break; case 0x0b: result = value / 1000.0; unit = "V"; break; case 0x0c: result = value / 100.0; unit = "V"; break; case 0x0d: result = value / 256.0; unit = "mA"; break; case 0x0e: result = value / 1000.0; unit = "A"; break; case 0x0f: result = value / 100.0; unit = "A"; break; case 0x10: result = value; unit = "ms"; break; case 0x11: result = value / 10.0; unit = "ms"; break; case 0x12: result = value; unit = "s"; break; case 0x13: result = value; unit = "mOhm"; break; case 0x14: result = value; unit = "Ohm"; break; case 0x15: result = value; unit = "kOhm"; break; case 0x16: result = value / 10.0; unit = "°C"; break; case 0x17: result = value / 100.0; unit = "kPa"; break; case 0x18: result = value / 85.47; unit = "kPa"; break; case 0x19: result = value * 0.079; unit = "kPa"; break; case 0x1a: result = value; unit = "kPa"; break; case 0x1b: result = value * 10.0; unit = "kPa"; break; case 0x1c: result = value / 100.0; unit = "°"; break; case 0x1d: result = value / 2.0; unit = "°"; break; case 0x1e: result = value / 32768.0; unit = "lambda"; break; case 0x1f: result = value / 20.0; unit = "Air/Fuel ratio."; break; case 0x20: result = value / 256.0; unit = string.Empty; break; case 0x21: result = value / 1000.0; unit = "Hz"; break; case 0x22: result = value; unit = "Hz"; break; case 0x23: result = value; unit = "kHz"; break; case 0x24: result = value; unit = "counts"; break; case 0x25: result = value; unit = "km"; break; case 0x26: result = value / 10000.0; unit = "V/ms"; break; case 0x27: result = value / 100.0; unit = "g/s"; break; case 0x28: result = value; unit = "g/s"; break; case 0x29: result = value / 4.0; unit = "Pa/s"; break; case 0x2a: result = value / 1000.0; unit = "kg/h"; break; case 0x2b: result = value; unit = string.Empty; break; case 0x2c: result = value / 100.0; unit = "g/cyl"; break; case 0x2d: result = value / 100.0; unit = "mg/stroke"; break; case 0x2e: result = value; unit = string.Empty; break; case 0x2f: result = value / 100.0; unit = "%"; break; case 0x30: result = value / 655.36; unit = "%"; break; case 0x31: result = value / 1000.0; unit = "l"; break; case 0x32: result = value / 32768.0; unit = "inch/bit"; break; case 0x33: result = value / 4096.0; unit = "Air/Fuel ratio"; break; case 0x34: result = value; unit = "min"; break; case 0x35: result = value / 100.0; unit = "%"; break; case 0x36: result = value / 100.0; unit = "g"; break; case 0x37: result = value / 10; unit = "g"; break; case 0x38: result = value; unit = "g"; break; case 0x39: result = (value - 32768) / 100.0; unit = "%"; break; case 0x81: result = (short)value; unit = string.Empty; break; case 0x82: result = ((short)value) / 10.0; unit = string.Empty; break; case 0x83: result = ((short)value) / 100.0; unit = string.Empty; break; case 0x84: result = ((short)value) / 1000.0; unit = string.Empty; break; case 0x85: result = ((short)value) / 32768.0; unit = string.Empty; break; case 0x86: result = ((short)value) / 3276.8; unit = string.Empty; break; case 0x8a: result = ((short)value) / 8192.0; unit = "V"; break; case 0x8b: result = ((short)value) / 1000.0; unit = "V"; break; case 0x8c: result = ((short)value) / 100.0; unit = "V"; break; case 0x8d: result = ((short)value) / 256.0; unit = "mA"; break; case 0x8e: result = ((short)value) / 1000.0; unit = "A"; break; case 0x90: result = ((short)value) / 1000.0; unit = "s"; break; case 0x96: result = ((short)value) / 10.0; unit = "°C"; break; case 0x9c: result = ((short)value) / 100.0; unit = "°"; break; case 0x9d: result = ((short)value) / 2.0; unit = "°"; break; case 0xa8: result = ((short)value); unit = "g/s"; break; case 0xa9: result = ((short)value) / 4.0; unit = "Pa/s"; break; case 0xaf: result = ((short)value) / 100.0; unit = "%"; break; case 0xb0: result = ((short)value) / 327.68; unit = "%"; break; case 0xb1: result = ((short)value) * 2.0; unit = "mV"; break; case 0xfd: result = ((short)value) / 1000.0; unit = "kPa"; break; case 0xfe: result = ((short)value) / 4.0; unit = "Pa"; break; default: result = value; unit = "N/A"; break; } return result; } /// /// Parse PID data. /// /// Source address of received data. /// Mode byte value. /// PID value. /// PID data payload. private void parsePid(uint srcAddress, uint mode_int, uint pid, byte[] pidPayload) { XmlClass.pidgroup.pidlist pidItem = this.modeParser.findPidItem(mode_int, pid); if (pidItem != null) { this.iLogging.appendText("OnBoardDiagTestPanel: pidItem=" + pidItem.name + "\r\n"); switch (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 byte[] subPidPayload = Utils.extractData(pidPayload, 1); this.pidParser(srcAddress, mode_int, pid, subPidPayload); break; default: this.genericHandler(srcAddress, pidPayload); break; } } } /// /// A generic handler of data that uses data from definition in XML files /// to format the data to be presented to the user. /// /// Source ECU address. /// Payload data. private void genericHandler(uint srcAddress, byte[] pidPayload) { byte[] data = pidPayload; EcuDataPanel ecuDataPanel = getDataPanelByAddress(srcAddress, -1); if (data.Length >= 9) { byte mid = data[0]; byte tid = data[1]; byte unitScalingId = data[2]; int value = data[3] << 8 | data[4]; int minLimit = data[5] << 8 | data[6]; int maxLimit = data[7] << 8 | data[8]; string valueUnitStr; string minLimitUnitStr; string maxLimitUnitStr; double scaledValue = scaleValue(unitScalingId, value, out valueUnitStr); double scaledMinLimit = scaleValue(unitScalingId, minLimit, out minLimitUnitStr); double scaledMaxLimit = scaleValue(unitScalingId, maxLimit, out maxLimitUnitStr); data = Utils.extractData(data, 9); } } /// /// Parse PID bitmap. /// /// Source ECU address. /// Mode byte value. /// Current PID. /// Payload data. /// 'true' if it's OK to pop the next message to send and send it. private bool pidParser(uint srcAddress, uint mode_int, uint pid, byte[] pidPayload) { bool popNext = true; if (pidPayload.Length >= 4) { int bitmap = 0; for (int i = 0; i < 4; i++) { bitmap = bitmap << 8 | (int)(pidPayload[i] & 0xff); } byte nextPid = 0; switch (pid) { case 0x00: // Supported PID:s 01-20 nextPid = 0x20; break; case 0x20: // Supported PID:s 21-40 nextPid = 0x40; break; case 0x40: // Supported PID:s 41-60 nextPid = 0x60; break; case 0x60: // Supported PID:s 61-80 nextPid = 0x80; break; case 0x80: // Supported PID:s 81-A0 nextPid = 0xA0; break; case 0xA0: // Supported PID:s A1-E0 nextPid = 0xC0; break; case 0xC0: // Supported PID:s C1-E0 nextPid = 0xE0; break; default: break; } this.updateObdDataPanels(srcAddress, mode_int, pid, bitmap, nextPid); if (nextPid != 0) { // When we got one PID set we ask for next set. this.iMessaging.queueMsg(null, new TxMsg(new byte[] { TxMsg.MODE_OBD_MID, nextPid })); this.iMessaging.sendMsg(); popNext = false; } } return popNext; } /// /// Update the OBD data panels to display PID entries according to bitmap. /// /// Source ECU address. /// Mode byte value. /// Current PID. /// Bitmap for entries to display. /// Next PID in sequence. private void updateObdDataPanels(uint srcAddress, uint mode_int, uint pid, int bitmap, byte nextPid) { this.obdDataPanel1.addDataItem(srcAddress, (byte)mode_int, pid, bitmap); if (nextPid == 0) { this.obdDataPanel1.displayFields(); } } /// /// Manually refresh the data. /// /// Sending object. /// Event data. private void refreshButton_Click(object sender, EventArgs e) { this.init(); } } }