//----------------------------------------------------------------------- // // 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 global::SharedObjects; using global::SharedObjects.CAN.Objects; using global::SharedObjects.DataMgmt; using global::SharedObjects.GUI.SSM; using global::SharedObjects.Misc; using global::SharedObjects.Protocol.OBD; using global::SharedObjects.Protocol.OBD.SSM; /// /// Parser for SSM data. /// public class SsmParser : IDataPanel, ISsmParser { /// /// Known SSM modes. /// public static readonly string[] modes = { "0xA0 Read Memory", "0xA8 Read Single Address", "0xAA Request Init Data (CAN)", "0xB0 Write Memory", "0xB8 Write Single Address", "0xBF ECU Init", "0x9F Alternate ECU Init", }; /// /// SSM ID 3rd byte for engine. /// public static readonly string[] engineSsmIds = { "0x01 2.5L SOHC", "0x02 2.5L SOHC", "0x03 2.2L SOHC", "0x04 2.2L SOHC", "0x05 1.5L SOHC", "0x06 1.6L SOHC", "0x07 1.8L SOHC", "0x08 2.0L SOHC", "0x09 2.0L DOHC", "0x0A 2.5L DOHC", "0x0B 2.0L DOHC Turbo", "0x0C 2.0L DOHC Turbo", "0x0D 2.0L DOHC Turbo", "0x0E 3.0L DOHC", "0x0F 2.0L DOHC Turbo", "0x10 2.5L DOHC", "0x11 2.5L DOHC Turbo", "0x12 3.0L DOHC", "0x13 1.5L DOHC", "0x14 2.0L DOHC Turbo Diesel", "0x15 3.6L DOHC", }; /// /// SSM ID 3rd byte for gearbox. /// public static readonly string[] gearboxSsmIds = { "0x01 E-4AT", "0x02 E-4AT", "0x03 E-4AT", "0x04 E-4AT", "0x05 E-4AT", "0x06 E-4AT", "0x07 E-4AT", "0x08 E-4AT", "0x09 E-4AT", "0x10 E-4AT", "0x11 E-4AT", "0x12 E-4AT", "0x13 E-4AT", "0x20 E-4AT", "0x21 E-4AT", "0x22 E-5AT", "0x23 E-4AT", "0x24 E-4AT", "0x25 E-4AT", "0x30 Center Differential", "0x40 Lineartronic (CVT)", }; /// /// Logging instance. /// private ILogging iLogging; /// /// Time marker. /// private long t1 = 0; /// /// Lock object to avoid collission. /// private object uiManipulationLockObject = new object(); /// /// List of result data receivers. /// private List iDataPresentations = new List(); /// /// List of SSM data presentation panels. /// private List ssmPanels = new List(); /// /// Gauge presentation instance. /// private IGaugePresentation iGaugePresentation; /// /// Data source instance. /// private IDataSource iDataSource; /// /// Initializes a new instance of the class. /// /// Logging instance. /// Data source instance. public SsmParser(ILogging iLogging, IDataSource iDataSource) { this.iLogging = iLogging; this.iDataSource = iDataSource; } /// /// Add SSM panel instance. /// /// SSM panel instance to add. public void addSsmPanel(ISsmPanel ssmPanel) { this.ssmPanels.Add(ssmPanel); } /// /// 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; } /// /// Add data presentation instance. /// /// Data presentation instance. public void addDataPresentation(IDataPresentation iDataPresentation) { lock (this.uiManipulationLockObject) { if (!this.iDataPresentations.Contains(iDataPresentation)) { this.iDataPresentations.Add(iDataPresentation); } } } /// /// 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) { return this.handleSsm(srcAddress, payload, requestData, ref popNext); } /// /// SSM payload parser. /// /// ECU source address for data. /// Payload to parse. /// Related request data. /// 'true' if it's OK to pop the next message to send and send it. /// 'true' if successful. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed, intentional.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#", Justification = "Reviewed.")] private bool handleSsm(uint sourceAddress, byte[] payload, IRequestData requestData, ref bool popNext) { bool success = false; #if TRACE_SSM this.iLogging.appendText("handleSSM(" + sourceAddress + "), requestData=" + (requestData != null ? requestData.ToString() : "null") + "\r\n"); #endif // Only process if the data was requested properly. if (requestData != null && requestData.message != null) { this.t1 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND; byte mode = payload[ModeParser.BYTE_MODE]; #if TRACE_SSM this.iLogging.appendText("Mode=0x" + mode.ToString("x2") + "\r\n"); #endif popNext = true; switch (mode) { case 0xea: // Handle SSM Init. this.handleSsmInit(sourceAddress, payload); success = true; break; case 0xe0: case 0xe8: // Handle SSM. this.handleSsmRead(sourceAddress, mode, payload, requestData); success = true; break; default: // Do nothing. break; } } else { this.iLogging.appendText("No matching request!\r\n"); } return success; } /// /// Handle a SSM read response. /// /// ECU source address for data. /// Mode for data. /// Payload data. /// Related request data. private void handleSsmRead(uint sourceAddress, byte mode, byte[] payload, IRequestData requestData) { byte[] ssmData = Utils.extractData(payload, 1); if (requestData == null || requestData.message == null || requestData.message.payload == null) { this.iLogging.appendText("Hmmm... Seems like we got SSM data without asking for it.\r\n"); } else { if (ssmData.Length > 0 && requestData.message.payload.Length >= 5) { uint p = 2; while (ssmData != null && ssmData.Length > 0 && (p + 3) <= requestData.message.payload.Length) { uint p1 = p; uint pid_int = requestData.message.payload[p1++]; pid_int = (pid_int << 8) | requestData.message.payload[p1++]; pid_int = (pid_int << 8) | requestData.message.payload[p1++]; XmlClass.pidgroup.pidlist ssmPid = this.getPidItem(mode, pid_int); if (ssmPid != null) { uint nextPart = this.handleIdentifiedSsmData(sourceAddress, mode, ssmData, ssmPid); #if TRACE_SSM this.iLogging.appendTextLn("nextPart=" + nextPart + ", data=" + Utils.arrayToString(ssmData, 0)); #endif this.rawLog(sourceAddress, mode, ssmData, ssmPid, nextPart); if (nextPart != 0) { p += (3 * nextPart); ssmData = Utils.extractData(ssmData, (int)nextPart); } else { // In case we got a problem. this.iLogging.appendText("Hmmm... Seems like we don't get more SSM data to process.\r\n"); ssmData = null; } } } #if TRACE_SSM this.iLogging.appendTextLn("End of SSM data, p=" + p + "."); #endif } } } /// /// Write to the raw log (if it's opened). /// /// ECU source address for data. /// Mode for data. /// Payload data. /// PID value for data. /// Position of next data part. private void rawLog(uint sourceAddress, byte mode, byte[] ssmData, XmlClass.pidgroup.pidlist ssmPid, uint nextPart) { byte[] ba; if (nextPart > 0) { ba = new byte[nextPart]; try { Array.Copy(ssmData, ba, nextPart); } catch (Exception ex) { this.iLogging.appendTextLn("ssmData=" + ssmData.Length + ", nextPart=" + nextPart + ": " + ex.Message); throw; } } else { ba = ssmData; } RawLogObject rawLogObject = new RawLogObject(sourceAddress, mode, ssmPid.pid_int, (uint)ba.Length, ba); this.iLogging.addRawData(rawLogObject); } /// /// Get PID item matching the data. /// /// Mode to get PID item for. /// PID value to get item for. /// PID item or 'null' if not found. private XmlClass.pidgroup.pidlist getPidItem(byte mode, uint pid_int) { XmlClass.pidgroup.pidlist pidItem = null; foreach (XmlClass.pidgroup pidgroup in this.iDataSource.pidgroups) { if (pidgroup.mode_int == (mode & 0xbf)) { foreach (XmlClass.pidgroup.pidlist pid in pidgroup.pids) { if (pid.pid_int == pid_int) { pidItem = pid; break; } } if (pidItem != null) { break; } } } return pidItem; } /// /// Handle arrived and identified SSM data. /// /// ECU source address for data. /// Current Mode (should always be SSM) /// Received data. /// SSM data description. /// Number of bytes to step up with. private uint handleIdentifiedSsmData(uint sourceAddress, byte mode, byte[] ssmData, XmlClass.pidgroup.pidlist ssmPid) { uint nextPart = 0; IList sensors = ssmPid.sensors; if (sensors != null && sensors.Count > 0) { nextPart = this.loopSensors(sourceAddress, mode, ssmData, ssmPid, sensors); } return nextPart; } /// /// Loop through all sensors described in the SSM data descriptor. /// /// ECU source address for data. /// Current Mode (should always be SSM) /// Received data. /// SSM data description. /// List of sensors to process. /// Number of bytes to step up with. private uint loopSensors( uint sourceAddress, byte mode, byte[] ssmData, XmlClass.pidgroup.pidlist ssmPid, IList sensors) { uint nextPart = 0; foreach (XmlClass.pidgroup.pidlist.sensordata sensor in sensors) { try { uint rawValue = PidParser.getRawValue(ssmData, sensor.offset, sensor.count); if (nextPart < sensor.count) { nextPart = sensor.count; } bool naFlag = Utils.checkNaData(sensor, rawValue); double value = 0; string formattedValue = PidParser.getSensorValue(this.iLogging, rawValue, sensor, out value); this.addSimpleData(sourceAddress, mode, ssmPid, sensor, value, formattedValue, naFlag); } catch (Exception ex) { this.iLogging.appendText(ex.GetType().ToString() + ": " + ex.Message + "\r\n" + ex.StackTrace + "\r\n"); } } return nextPart; } /// /// Add simple data to a text table. /// /// ECU source address for data. /// Current Mode (should always be SSM) /// SSM data description. /// Sensor descriptor. /// Numeric value to display. /// Value to display in text form. /// Value is potentially invalid. private void addSimpleData( uint sourceAddress, byte mode, XmlClass.pidgroup.pidlist ssmPid, XmlClass.pidgroup.pidlist.sensordata sensor, double value, string formattedValue, bool naFlag) { string name = ssmPid.name; if (sensor.name != null && sensor.name.Trim().Length > 0) { name += ": " + sensor.name.Trim(); } if (this.iGaugePresentation != null) { this.iGaugePresentation.setGaugeValue(mode, ssmPid.pid_int, sensor, (float)value); } else { if (this.iDataPresentations != null) { foreach (IDataPresentation iDataPresentation in this.iDataPresentations) { iDataPresentation.simpleDataAdd( new PresentationData( sourceAddress, mode, ssmPid.pid_int, ssmPid, sensor, name, sensor.unit, formattedValue, value, this.t1, naFlag)); } } } } /// /// Take care of a SSM init response. /// /// Source ECU address. /// Payload data. private void handleSsmInit(uint sourceAddress, byte[] payload) { if (payload.Length >= 9) { byte[] ssmData = Utils.extractData(payload, 1); uint ssmId = (uint)(ssmData[0] << 16 | ssmData[1] << 8 | ssmData[2]); string romid = string.Format("0x{0:x2} {1:x2}{2:x2} {3:x2}{4:x2}", ssmData[3], ssmData[4], ssmData[5], ssmData[6], ssmData[7]); byte[] capBytes = Utils.extractData(payload, 8); foreach (ISsmPanel ssmPanel in this.ssmPanels) { ssmPanel.handleSsmInitResponse(sourceAddress, ssmId, romid, capBytes); } } else { this.iLogging.appendText("Crippled SSM init response?\r\n"); } } } }