//-----------------------------------------------------------------------
//
// 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.");
}
}
}
}