//----------------------------------------------------------------------- // // 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 SimProtocol { using System; using System.Collections.Generic; using SharedObjects; using SharedObjects.GUI; using SharedObjects.Protocol; using SharedObjects.SimData; using SimProtocol.SimData; /// /// Presentation layer in the OSI stack for OBD data. /// /// This handles roughly the presentation layer, not strictly. /// /// public class PresentationLayer : IPresentationLayerToSession, IDisposable, IPresentationLayer { /// /// Gets Record number counter. /// public int counter { get { if (this.dataDictionary != null) { return this.dataDictionary.counter; } return 0; } } /// /// Gets Record number counter. /// public uint scope { get { if (this.dataDictionary != null) { return this.dataDictionary.scope; } return 0; } } /// /// Gets last encountered timestamp. /// public double timestamp { get { if (this.dataDictionary != null) { return this.dataDictionary.timestamp; } return 0; } } /// /// Gets Dictionary of all loaded modes from file. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Reviewed, intentional")] public IDictionary> modeDictionary { get { if (this.dataDictionary != null) { return this.dataDictionary.modeDictionary; } return null; } } /// /// Event logging instance. /// private ILogging iAppender = null; /// /// Dictionary of static response data. /// private IDataDictionary dataDictionary; /// /// Simulation engine instance. /// private ISimEngine iSimEngine; /// /// Interface for presentation layer to call on session layer. /// private ISessionLayerToPresentation iSessionLayerToPresentation; /// /// Initializes a new instance of the class. /// /// Event log appender instance. /// Simulation engine instance. /// Session layer instance. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "Reviewed, intentional")] public PresentationLayer(ILogging iAppender, ISimEngine iSimEngine, ISessionLayerToPresentation iSessionLayerToPresentation) { this.iAppender = iAppender; this.iSimEngine = iSimEngine; this.iSessionLayerToPresentation = iSessionLayerToPresentation; this.iSessionLayerToPresentation.addPresentationLayer(this); } /// /// Start replay of data from captured file. /// /// Callback interface to call when end of replay occurs. /// Replay speed scale factor. /// Time for start of replay. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "EndPlay", Justification = "Reviewed")] public void playData(ISimEndPlay iSimEndPlay, double replaySpeed, uint startTime) { this.dataDictionary.playData(iSimEndPlay, replaySpeed, startTime); } /// /// Start replay of data from captured file. /// public void stopData() { this.dataDictionary.stopData(); } /// /// Load data dictionary from file with responses. /// /// Name of file to load. public void loadStaticData(string dataFile) { this.dataDictionary = new StaticDataDictionary(this.iAppender, dataFile); } /// /// Load data dictionary from file with responses. /// /// File with data to replay. /// Source address for ECU. public void loadReplayData(string dataFile, uint srcAddr) { this.dataDictionary = new ReplayDataDictionary(this.iAppender, dataFile, srcAddr); } /// /// Process the payload data. /// /// Target address. /// Return address for data. /// Data length. /// Payload data. public void processPayload(uint address, uint outAddress, uint zLen, byte[] data) { try { this.processPayloadImpl(address, outAddress, zLen, data); } catch (Exception ex) { this.iAppender.appendTextLn(ex.GetType() + ": " + ex.Message + "\r\n" + ex.StackTrace); } } /// /// Dispose the instance. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Dispose the instance. /// /// Dispose flag. protected virtual void Dispose(bool disposing) { this.dataDictionary.Dispose(); } /// /// Fetch response data from database. /// /// Database dictionary item. /// Response byte list. private static void getResponseData(IDictionaryItem dictionaryItem, List response) { byte[] ba = dictionaryItem.data; foreach (byte b1 in ba) { response.Add(b1); } } /// /// Get the PID/Address value from received data. /// /// PID dictionary to look up matching item in. /// Payload data. /// Data offset pointer. /// Data format engine instance to help with parsing. /// Response byte list. /// Dictionary item fro PID. /// PID/Address value. private static uint getPidValue( IDictionary pidDictionary, byte[] data, ref uint p, IDataFormatEngine dataFormatEngine, List response, out IDictionaryItem dictionaryItem) { uint pidValue = 0; uint p0 = p; if (dataFormatEngine.pidLen == 0) { pidValue = 0xFFFFFF; p++; } else { for (int i = 0; i < dataFormatEngine.pidLen; i++) { pidValue = pidValue << 8; pidValue |= data[p++]; } } dictionaryItem = null; if (pidDictionary.ContainsKey(pidValue)) { dictionaryItem = pidDictionary[pidValue]; } if (dictionaryItem == null || dictionaryItem.echoPid == 1) { for (int i = 0; i < dataFormatEngine.pidLen; i++) { response.Add(data[p0++]); } } return pidValue; } /// /// Process the payload data. /// /// Address field. /// Return address for data. /// Payload data length. /// Payload data. private void processPayloadImpl(uint id, uint outAddress, uint zLen, byte[] data) { uint p = 0; byte mode = data[p++]; if (this.dataDictionary.modeDictionary.ContainsKey(mode)) { IDictionary pidDictionary = this.dataDictionary.modeDictionary[mode]; IDataFormatEngine dataFormatEngine = new DataFormatEngine(mode); p += dataFormatEngine.padBytes; List response = new List(); response.Add((byte)(mode | 0x40)); IDictionaryItem dictionaryItem; uint pidValue = getPidValue(pidDictionary, data, ref p, dataFormatEngine, response, out dictionaryItem); this.addIndataRow(id, data, 0, mode, pidValue, zLen); if (mode == 0x02) { response.Add(data[p++]); // Frame number. } bool success = dictionaryItem != null; int n1 = 0; while (dictionaryItem != null) { n1++; getResponseData(dictionaryItem, response); dictionaryItem = null; // this.iAppender.appendTextLn(" zLen=" + zLen + ", p=" + p + ", dataFormatEngine.pidLen=" + dataFormatEngine.pidLen); if (((int)zLen) - ((int)p) >= dataFormatEngine.pidLen && dataFormatEngine.pidLen > 0) { pidValue = getPidValue(pidDictionary, data, ref p, dataFormatEngine, response, out dictionaryItem); } } if (!success) { response.Clear(); response.Add(0x7f); response.Add(mode); response.Add(0x11); } if (success || this.iSimEngine.pidErrorCodeFlag) { uint n = (uint)response.Count; string rspStr = Utils.listToHexString(response); this.iSimEngine.addRow(rowMode.presentation_out, outAddress, (byte)(mode | 0x40), pidValue, n, rspStr); this.sendData(outAddress, response, n); } else { this.iSimEngine.addRow(rowMode.data_discard, 0, 0, 0, 0, "PID: No Response sent."); } } else { this.modeErrorResponse(outAddress, mode); } } /// /// Send data on CAN. /// /// Target address. /// Payload data. /// Payload data length. private void sendData(uint outAddress, List response, uint dataLen) { this.iSessionLayerToPresentation.sendData(outAddress, response, dataLen); } /// /// Add one row for incoming data. /// /// Address field. /// Raw data array. /// Offset in data array to start at. /// Mode byte value. /// PID/Address value. /// Length value. private void addIndataRow(uint id, byte[] data, uint p, byte mode, uint pidValue, uint zLen) { string inData = Utils.arrayToString(data, p); this.iSimEngine.addRow(rowMode.presentation_in, id, mode, pidValue, zLen, inData); } /// /// Prepare and send response for Mode errors. /// /// Return address for data. /// Mode byte value. private void modeErrorResponse(uint outAddress, byte mode) { if (this.iSimEngine.modeErrorCodeFlag) { List response = new List(); response.Add(0x7f); response.Add(mode); response.Add(0x10); string rspStr = Utils.listToHexString(response); uint n = (uint)response.Count; this.iSimEngine.addRow(rowMode.presentation_out, outAddress, (byte)(mode | 0x40), 0, n, rspStr); this.sendData(outAddress, response, n); } else { this.iSimEngine.addRow(rowMode.data_discard, 0, 0, 0, 0, "Mode: No Response sent."); } } } }