//-----------------------------------------------------------------------
//
// 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.SimData
{
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using SharedObjects;
using SharedObjects.CAN.Objects;
using SharedObjects.GUI;
///
/// Dynamic Data
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Reviewed, intentional.")]
public class ReplayDataDictionary : DataDictionary
{
///
/// Stream for raw data.
///
private string source;
///
/// Baseline timestamp.
///
private long t0 = 0;
///
/// Thread for replay of data.
///
private Thread playDataThread = null;
///
/// Thread state.
///
private bool doRun = false;
///
/// Callback interface when end of file reached.
///
private ISimEndPlay iSimEndPlay = null;
///
/// Calculated destination address to match.
///
private uint destAddr = 0;
///
/// Replay speed scale factor.
///
private double replaySpeed = 1;
///
/// Start time (relative start of file) in ticks.
///
private long startRelativeTimeTicks;
///
/// Start time (relative start of file) in ticks.
///
private long startAbsoluteTimeTicks;
///
/// Initializes a new instance of the class.
///
/// Event log instance.
/// Source file name.
/// Source address for ECU.
public ReplayDataDictionary(ILogging iAppender, string source, uint srcAddr)
: base(iAppender)
{
this.source = source;
this.destAddr = getRespAddr(srcAddr);
this.initDictionary();
}
///
/// Finalizes an instance of the class.
///
~ReplayDataDictionary()
{
this.Dispose(false);
}
///
/// Called when start of data replay shall occur.
///
/// Callback interface to call when end of replay occurs.
/// Replay speed scale factor.
/// Time for start of replay.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "replaySpeed", Justification = "Reviewed, intentional.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "iSimEndPlay", Justification = "Reviewed, intentional.")]
public override void playData(ISimEndPlay iSimEndPlay, double replaySpeed, uint startTime)
{
this.counter = 0;
this.iSimEndPlay = iSimEndPlay;
this.replaySpeed = replaySpeed;
this.startRelativeTimeTicks = startTime * TimeSpan.TicksPerSecond;
this.doRun = true;
this.playDataThread = new Thread(this.playDataThreadImpl);
this.playDataThread.Start();
}
///
/// Called when stop of data replay shall occur.
///
public override void stopData()
{
this.iSimEndPlay = null;
this.doRun = false;
if (this.playDataThread != null)
{
this.playDataThread.Abort();
this.playDataThread.Interrupt();
this.playDataThread.Join(4000);
}
}
///
/// Dispose the instance.
///
public sealed override void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Dispose the instance.
///
/// Dispose flag.
protected virtual void Dispose(bool disposing)
{
this.doRun = false;
if (this.playDataThread != null)
{
this.playDataThread.Abort();
this.playDataThread.Interrupt();
this.playDataThread = null;
}
}
///
/// Fetch one record from file and put the data into the dictionary.
///
/// File reader.
/// Raw object.
private static void fetchOneRecord(BinaryReader rawBinaryReader, RawLogObject rawLogObject)
{
rawLogObject.timestamp = rawBinaryReader.ReadInt64();
rawLogObject.sourceAddress = rawBinaryReader.ReadUInt32();
rawLogObject.mode = rawBinaryReader.ReadByte();
rawLogObject.pid_int = rawBinaryReader.ReadUInt32();
rawLogObject.dataLen = rawBinaryReader.ReadUInt32();
rawLogObject.data = rawBinaryReader.ReadBytes((int)rawLogObject.dataLen);
}
///
/// Get (generate) response address.
///
/// Source Address
/// Response address.
private static uint getRespAddr(uint srcAddr)
{
if (srcAddr < 0x800)
{
return srcAddr | 0x08;
}
return (srcAddr & 0xFFFF0000) | ((srcAddr & 0xff) << 8) | ((srcAddr & 0xFF00) >> 8);
}
///
/// Execution thread.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Reviewed, intentional.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Reviewed, intentional.")]
private void playDataThreadImpl()
{
try
{
using (Stream rawStream = new FileStream(this.source, FileMode.Open))
{
rawStream.Seek(0, SeekOrigin.Begin);
using (BinaryReader rawBinaryReader = new BinaryReader(rawStream))
{
RawLogObject rawLogObject = new RawLogObject();
this.iAppender.appendTextLn("Play data start.");
try
{
long t1 = 0;
while (this.doRun)
{
fetchOneRecord(rawBinaryReader, rawLogObject);
if (this.t0 == 0)
{
this.t0 = rawLogObject.timestamp;
this.startAbsoluteTimeTicks = this.t0 + this.startRelativeTimeTicks;
}
if (rawLogObject.timestamp >= this.startAbsoluteTimeTicks)
{
bool matched = this.matchAddress(rawLogObject);
if (matched)
{
if (t1 != 0)
{
int diff = (int)((rawLogObject.timestamp - t1) / (TimeSpan.TicksPerMillisecond * this.replaySpeed));
Thread.Sleep(diff);
}
this.addOneRecord(rawLogObject);
this.counter++;
t1 = rawLogObject.timestamp;
}
}
this.timestamp = (double)(rawLogObject.timestamp - this.t0) / TimeSpan.TicksPerSecond;
}
}
catch (EndOfStreamException ex)
{
this.iAppender.appendTextLn(ex.GetType().ToString() + ": " + ex.Message);
}
catch (SerializationException ex)
{
this.iAppender.appendTextLn(ex.GetType().ToString() + ": " + ex.Message + ", " + ex.Data);
if (ex.InnerException != null)
{
this.iAppender.appendTextLn(ex.InnerException.GetType().ToString() + ": " + ex.InnerException.Message);
}
}
finally
{
if (this.iSimEndPlay != null)
{
this.iSimEndPlay.endPlay();
}
this.iAppender.appendText("Play data END.");
}
}
}
}
catch
{
}
}
///
/// Match address with expected destination address.
///
/// Raw log object.
/// 'true' if match.
private bool matchAddress(RawLogObject rawLogObject)
{
if (this.destAddr < 0x800)
{
return this.destAddr == rawLogObject.sourceAddress;
}
#if TRACE_REPLAY
this.iAppender.appendTextLn("rawLogObject.sourceAddress=0x" + rawLogObject.sourceAddress.ToString("X2")
+ ", this.destAddr=" + this.destAddr.ToString("X2"));
#endif
return (rawLogObject.sourceAddress & 0x18DF00FF) == (this.destAddr & 0x18DF00FF);
}
///
/// Initialize dictionary with start values.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Reviewed, intentional.")]
private void initDictionary()
{
try
{
this.iAppender.appendTextLn("Data Source: " + this.source);
using (Stream rawStream = new FileStream(this.source, FileMode.Open))
{
rawStream.Seek(0, SeekOrigin.Begin);
using (BinaryReader rawBinaryReader = new BinaryReader(rawStream))
{
RawLogObject rawLogObject = new RawLogObject();
long t1 = 0;
try
{
while (true)
{
fetchOneRecord(rawBinaryReader, rawLogObject);
if (t1 == 0 || ((rawLogObject.timestamp - t1) / TimeSpan.TicksPerSecond) < 30)
{
this.addOneRecord(rawLogObject);
if (t1 == 0)
{
t1 = rawLogObject.timestamp;
}
}
}
}
catch (EndOfStreamException ex)
{
this.iAppender.appendTextLn(ex.GetType().ToString() + ": " + ex.Message);
}
catch (SerializationException ex)
{
this.iAppender.appendTextLn(ex.GetType().ToString() + ": " + ex.Message + ", " + ex.Data);
if (ex.InnerException != null)
{
this.iAppender.appendTextLn(ex.InnerException.GetType().ToString() + ": " + ex.InnerException.Message);
}
}
finally
{
this.scope = (uint)((rawLogObject.timestamp - t1) / TimeSpan.TicksPerSecond);
this.iAppender.appendTextLn("Loaded modes: " + modeDictionary.Count + ", pids=" + this.pidCount + ", scope=" + this.scope + " seconds");
}
}
}
}
catch
{
}
finally
{
this.indexMode(0x01);
this.indexMode(0x02);
this.indexMode(0x22);
}
}
///
/// Add/update one record in the dictionary.
///
/// Object to add/update.
private void addOneRecord(RawLogObject rawLogObject)
{
uint echoPid = 1;
switch (rawLogObject.mode & 0xbf)
{
case 0xA0:
case 0xA8:
case 0xAA:
echoPid = 0;
break;
default:
break;
}
if ((rawLogObject.mode & 0xbf) == 0xA8)
{
uint pid = rawLogObject.pid_int;
foreach (byte b1 in rawLogObject.data)
{
this.addDictionaryItem((uint)(rawLogObject.mode & 0xBF), echoPid, pid, new byte[] { b1 });
pid++;
}
}
else
{
this.addDictionaryItem((uint)(rawLogObject.mode & 0xBF), echoPid, rawLogObject.pid_int, rawLogObject.data);
// Take care of the base part of extended PID (0x22) which shall be
// identical to 0x01.
if ((rawLogObject.mode & 0xBF) == 0x01)
{
this.addDictionaryItem(0x22, echoPid, rawLogObject.pid_int, rawLogObject.data);
}
}
}
}
}