//----------------------------------------------------------------------- // // 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 System.Linq; using System.Threading; using SharedObjects; using SharedObjects.Protocol; /// /// Session layer for CAN communication. /// /// This class manages communication with a single peer (the test tool), /// and is not able to handle multiple multi-frame communications. /// /// public class CanSessionLayer : AbstractSessionLayer { /// /// Number of cycles we may get a timeout waiting for CTS when doing multi-frame before sending anyway. /// private const int TIMEOUT_CYCLES = 10; /// /// The block size to use when doing multi-frame communication. /// /// Block size 1 means that every frame needs to be acked. /// /// private const byte BLOCK_SIZE = 0x01; /// /// The frame spacing to use in milliseconds between frames sent. /// /// 20ms by default, change if needed. /// /// private const byte FRAME_SPACING = 0x14; /// /// Event logging instance. /// private ILogging iLogging = null; /// /// CAN bus communication engine. /// private IObdCommunicator iObdCommunicator; /// /// Instance of multi-frame data. /// private MultiFrameData multiFrameData; /// /// Default functional address. /// private uint funcAddr; /// /// Default physical address. /// private uint physAddr; /// /// Presentation layer instance. /// private IList presentationLayerList = new List(); /// /// Simulation engine instance. /// private ISimEngine iSimEngine; /// /// Queue for sending of data. /// private Queue sendQueue = new Queue(); /// /// Flag for send thread. /// private bool doRun = false; /// /// Number of frames before next CTS. /// /// Only applicable for multi-frame. /// /// private uint blockSize = 1; /// /// Separation time between data frames. /// private uint separationTime = 4; /// /// Object to lock with when sending. /// private object lockObject = new object(); /// /// Thread for transmission of data. /// private Thread sendThread = null; /// /// Common Simulator preferences. /// private IObdCanSimPrefs iObdCanSimPrefs; /// /// Number of frames received before sending next CTS. /// /// Only applicable for multi-frame. /// /// private uint rxBlockSize = 1; /// /// Initializes a new instance of the class. /// /// Event log appender instance. /// Common Simulator preferences. /// Functional address. /// Physical address. public CanSessionLayer(ILogging iLogging, IObdCanSimPrefs iObdCanSimPrefs, uint funcAddr, uint physAddr) { this.iLogging = iLogging; this.iObdCanSimPrefs = iObdCanSimPrefs; this.funcAddr = funcAddr; this.physAddr = physAddr; this.doRun = true; this.sendThread = new Thread(this.sendThreadImpl); this.sendThread.Name = "0x" + physAddr.ToString("X2"); this.sendThread.Start(); } /// /// Set the simulation engine. /// /// Simulation engine instance. public override void setSimEngine(ISimEngine iSimEngine) { this.iSimEngine = iSimEngine; } /// /// Clean up. /// public override void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Add presentation layer instance. /// /// Presentation layer to add public override void addPresentationLayer(IPresentationLayerToSession iPresentationLayer) { if (!this.presentationLayerList.Contains(iPresentationLayer)) { this.presentationLayerList.Add(iPresentationLayer); } } /// /// Remove presentation layer instance. /// /// Presentation layer to remove. public override void removePresentationLayer(IPresentationLayerToSession iPresentationLayer) { if (this.presentationLayerList.Contains(iPresentationLayer)) { this.presentationLayerList.Remove(iPresentationLayer); } } /// /// Set communicator instance. /// /// Communicator instance. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "iObdCommunicator", Justification = "Reviewed, intentional")] public override void setObdCommunicator(IObdCommunicator iObdCommunicator) { if (iObdCommunicator == null && this.iObdCommunicator != null) { this.iObdCommunicator.removeDataReceiver(this); } this.iObdCommunicator = iObdCommunicator; if (iObdCommunicator != null) { this.iObdCommunicator.addDataReceiver(this); } } /// /// Enqueue data to send on CAN. /// /// Target address. /// Payload data. /// Payload data length. public override void sendData(uint outAddress, IList response, uint dataLen) { if (response != null) { if (this.iSimEngine != null) { this.iSimEngine.addRow(rowMode.session_out, outAddress, 0, 0, 0, Utils.listToHexString(response)); } if (response.Count <= 7) { response.Insert(0, (byte)dataLen); // Insert length for single frame data. lock (this.sendQueue) { this.queueData(outAddress, 0x00, response); Monitor.PulseAll(this.sendQueue); } } else { uint maskedDataLen = dataLen & 0x0fff; this.iLogging.appendTextLn("Datalen=" + maskedDataLen); if (dataLen == maskedDataLen) { byte firstByte = (byte)((maskedDataLen >> 8) & 0x0f); byte secondByte = (byte)(maskedDataLen & 0xff); byte frameType = 0x01; firstByte |= (byte)((frameType << 4) & 0xf0); // First Frame. response.Insert(0, firstByte); // Insert length for multi frame data. response.Insert(1, secondByte); // Insert length for multi frame data. int responseLength = response.Count; int partNumber = 0; // Split up into parts and queue them. IList nextPart = response; lock (this.sendQueue) { while (nextPart.Count > 0) { int partSize = nextPart.Count < 8 ? nextPart.Count : 8; List currentPart = new List(nextPart.Take(partSize)); this.queueData(outAddress, frameType, currentPart); partNumber++; frameType = 0x02; nextPart = new List(nextPart.Skip(partSize)); if (nextPart.Count > 0) { byte cfData = (byte)((byte)((frameType << 4) & 0xf0) | (partNumber & 0x0f)); nextPart.Insert(0, cfData); } } Monitor.PulseAll(this.sendQueue); } } else { this.iLogging.appendTextLn("Trying to send more data than protocol supports."); } } } } /// /// Receive one data packet. /// /// Address value. /// Payload data. /// Flags value. /// RES1 Value. /// RES2 Value. public override void receiveData(uint id, byte[] data0, ushort flags, ulong res1, ulong res2) { if (data0 != null) { uint testerAddr1; bool physicalAddress; if (this.matchAddress(id, out testerAddr1, out physicalAddress)) { uint outAddress = this.getRespAddr(testerAddr1); if (this.iSimEngine != null) { this.iSimEngine.addRow(rowMode.transport_in, id, 0x00, 0, 0, Utils.arrayToString(data0, 0) + ", flags=0x" + flags.ToString("X2") + ", res1=0x" + res1.ToString("X2") + ", res2=0x" + res2.ToString("X2")); } if (data0.Length >= 3) { uint p0 = 0; uint zLen = (uint)data0[p0++]; // Only length in some cases, but works for now. uint frameType = (zLen >> 4) & 0x0f; if (physicalAddress) { switch (frameType) { case 0: this.multiFrameData = null; // Discard any old data. this.singleFrameHandler(id, outAddress, zLen, data0); break; case 1: this.multiFrameData = new MultiFrameData(zLen, data0, p0); this.sendFlowCTS(outAddress); break; case 2: this.multiFrameHandler(outAddress, id, zLen, data0, p0); break; case 3: // We got a flow control frame, let's see what we shall do about it. this.handleFcFrame(zLen, data0, p0); break; } } else { switch (frameType) { case 0: this.multiFrameData = null; // Discard any old data. this.singleFrameHandler(id, outAddress, zLen, data0); break; default: this.iLogging.appendText("Got incorrect frame type [" + frameType + "] for functional address."); break; } } } else { if (this.iLogging != null) { this.iLogging.appendTextLn("Received less than 2 bytes of data, ignoring."); } } } } } /// /// Clean up. /// /// 'true' if disposing. protected virtual void Dispose(bool disposing) { this.doRun = false; this.sendThread.Abort(); } /// /// Put data into queue. /// /// Destination address. /// Frame type value. /// Payload data. private void queueData(uint outAddress, byte frameType, IList response) { byte[] rspBa = this.prepareResponse(response); QueueItem queueItem = new QueueItem(outAddress, frameType, rspBa); this.sendQueue.Enqueue(queueItem); } /// /// Thread for sending on CAN. /// private void sendThreadImpl() { long t0 = DateTime.Now.Ticks; // Used to detect "stuck" situations. int timeoutCounter = 0; while (this.doRun) { QueueItem queueItem = null; lock (this.sendQueue) { if (this.sendQueue.Count == 0) { if (!Monitor.Wait(this.sendQueue, 4000) && this.sendQueue.Count > 0) { this.iLogging.appendTextLn("SessionLayer.sendThreadImpl(): Timeout with data present."); } } if (this.sendQueue.Count > 0) { queueItem = this.sendQueue.First(); if (timeoutCounter > TIMEOUT_CYCLES) { this.iLogging.appendTextLn(TIMEOUT_CYCLES + " cycles of " + this.separationTime + "ms waiting for CTS, sending anyway."); } if ((queueItem.frameType == 0x00 || queueItem.frameType == 0x01 || queueItem.frameType == 0x03) || ((queueItem.frameType == 0x02) && this.blockSize > 0) || (timeoutCounter > TIMEOUT_CYCLES)) { if (queueItem.frameType == 0x01) { this.blockSize = 1; } queueItem = this.sendQueue.Dequeue(); timeoutCounter = 0; if (this.blockSize > 0 && (queueItem.frameType == 0x01 || queueItem.frameType == 0x02)) { this.blockSize--; } } else { queueItem = null; } } } if (queueItem != null) { long t1 = DateTime.Now.Ticks; long n = this.separationTime - ((t1 - t0) / TimeSpan.TicksPerMillisecond); if (n > 0) { Thread.Sleep((int)n); } t0 = t1; uint outAddress = queueItem.outAddress; byte[] payload = queueItem.payload; this.sendDataCan(outAddress, payload); } else { if (this.sendQueue.Count > 0) { timeoutCounter++; } else { timeoutCounter = 0; } if (this.separationTime == 0) { Thread.Sleep(30); } else { Thread.Sleep((int)this.separationTime); } } } } /// /// Do a direct send on CAN with the given data. /// /// Destination address. /// Payload data. private void sendDataCan(uint outAddress, byte[] payload) { lock (this.lockObject) { if (this.iSimEngine != null) { this.iSimEngine.addRow(rowMode.transport_out, outAddress, 0x00, 0, 0, Utils.arrayToString(payload, 0)); } uint a2 = outAddress; if (this.iObdCommunicator != null) { this.iObdCommunicator.send(outAddress, payload); } } } /// /// Send CTS to caller, tell it that we can take one frame at a time. /// /// Response address. private void sendFlowCTS(uint outAddress) { this.rxBlockSize = this.iObdCanSimPrefs.blockSize; List response = new List(); response.Add(0x30); // CTS. response.Add(this.iObdCanSimPrefs.blockSize); response.Add(this.iObdCanSimPrefs.waitTime); // Send flow control messages right away instead of queueing them. this.sendDataCan(outAddress, this.prepareResponse(response)); } /// /// Send Abort to caller. /// /// Response address. private void sendFlowAbort(uint outAddress) { this.rxBlockSize = this.iObdCanSimPrefs.blockSize; List response = new List(); response.Add(0x32); // Abort. response.Add(this.iObdCanSimPrefs.blockSize); response.Add(this.iObdCanSimPrefs.waitTime); // Send flow control messages right away instead of queueing them. this.sendDataCan(outAddress, this.prepareResponse(response)); } /// /// Prepare the response to a byte array from a list of bytes. /// /// List of bytes. /// Byte array. private byte[] prepareResponse(IList response) { if (this.iSimEngine != null && this.iSimEngine.padData) { while (response.Count < 8) { response.Add(0x00); } } byte[] rspBa = response.ToArray(); return rspBa; } /// /// Get (generate) response address. /// /// Address part of test tool. /// Response address. private uint getRespAddr(uint testerAddr) { if (testerAddr == 0) { return this.physAddr | 0x08; } return (this.physAddr & 0xFFFF0000) | ((testerAddr & 0xff) << 8) | ((this.physAddr & 0xFF00) >> 8); } /// /// Check if received address matches the addresses of this engine. /// /// Incoming address to test. /// Out: Tester part of the address. /// Out: If physical address did match. /// 'true' if matching address. private bool matchAddress(uint id, out uint testerAddr, out bool physicalAddress) { physicalAddress = id == this.physAddr; testerAddr = 0; if (this.funcAddr <= 0x7FF) { return id == this.funcAddr || physicalAddress; } testerAddr = id & 0xFF; physicalAddress = (id & 0x18DFFF00) == (this.physAddr & 0x18DFFF00); return ((id & 0x18DFFF00) == (this.funcAddr & 0x18DFFF00)) || physicalAddress; } /// /// Handle one Flow Control frame. /// /// Byte containing flag state. /// Payload data. /// Payload data position. private void handleFcFrame(uint zLen, byte[] data0, uint p0) { uint fcFlag = zLen & 0x0f; switch (fcFlag) { case 0: // Clear to send. lock (this.sendQueue) { this.blockSize = data0[p0++]; this.separationTime = data0[p0++]; if (this.blockSize == 0) { this.sendQueue.Clear(); this.iLogging.appendTextLn("Peer indicated BlockSize of zero - cancelling transfer. (See parameter ISO15765_BS on peer)"); } else { Monitor.PulseAll(this.sendQueue); } } break; case 1: // Wait. this.blockSize = 0; break; case 2: // Overflow/Abort. lock (this.sendQueue) { this.sendQueue.Clear(); this.blockSize = 1; } break; default: break; } } /// /// Handle continuation of a multi-frame message. /// /// Response address (for handshake). /// Target address. /// Data length. /// Payload data /// Position of data in frame. private void multiFrameHandler(uint outAddress, uint id, uint zLen, byte[] data0, uint p0) { if (this.multiFrameData != null) { this.multiFrameData.addData(zLen & 0x0f, data0, p0); if (this.multiFrameData.error) { this.iLogging.appendTextLn("Multiframe data error, discarding rest."); this.multiFrameData = null; this.sendFlowAbort(outAddress); } else { if (this.multiFrameData.complete) { this.invokePresentation(id, outAddress, this.multiFrameData.length, this.multiFrameData.data); } else { this.rxBlockSize--; if (this.rxBlockSize <= 0) { this.sendFlowCTS(outAddress); } } } } } /// /// Handle arrival of a single frame. /// /// Functional addressing only permits single frames. /// /// /// Target address. /// Tester address part. /// Data length. /// Payload data private void singleFrameHandler(uint id, uint outAddress, uint zLen, byte[] data0) { byte[] data = new byte[zLen]; Array.Copy(data0, 1, data, 0, zLen); this.invokePresentation(id, outAddress, zLen, data); } /// /// Invoke the presentation layer. /// /// Target address. /// Tester address part. /// Data length. /// Payload data. private void invokePresentation(uint id, uint outAddress, uint zLen, byte[] data) { string inData = Utils.arrayToString(data, 0); if (this.iSimEngine != null) { this.iSimEngine.addRow(rowMode.session_in, id, 0, 0, zLen, inData); } foreach (IPresentationLayerToSession presentationLayer in this.presentationLayerList) { presentationLayer.processPayload(id, outAddress, zLen, data); } } /// /// One item in send queue. /// private class QueueItem { /// /// Gets destination address. /// public uint outAddress { get; private set; } /// /// Gets frame type, 0=Single, 1=First, 2=Consecutive, 3=Flow. /// /// Flow frames shall usually not be present in queue. /// /// public byte frameType { get; private set; } /// /// Gets payload data. /// public byte[] payload { get; private set; } /// /// Initializes a new instance of the class. /// /// Destination address. /// Frame type. /// Payload data. public QueueItem(uint outAddress, byte frameType, byte[] payload) { this.outAddress = outAddress; this.frameType = frameType; this.payload = payload; } } } }