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