//----------------------------------------------------------------------- // // 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 DeviceApi.J2534 { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using global::SharedObjects; using global::SharedObjects.Api; using global::SharedObjects.CAN; using global::SharedObjects.CAN.Objects; using global::SharedObjects.DataMgmt; using global::SharedObjects.Protocol; using global::VectorComm; /// /// TODO: Update summary. /// public class VectorPassthru : IPassThru, IPresentationLayer, IDisposable { /// /// Supported connection flags by the device. /// private static CanFlags[] connFlags = new CanFlags[] { new CanFlags(PassThruConstants.connflags.SNIFF_MODE, "SNIFF_MODE", true), new CanFlags(PassThruConstants.connflags.CAN_ID_BOTH, "CAN_ID_BOTH", true), new CanFlags(PassThruConstants.connflags.CAN_29BIT_ID, "CAN_29BIT_ID", false), }; /// /// Supported filter flags by the device. /// private static CanFlags[] messageFlags = new CanFlags[] { new CanFlags(PassThruConstants.txflags.CAN_29BIT_ID, "CAN_29BIT_ID", false), new CanFlags(PassThruConstants.txflags.ISO15765_ADDR_TYPE, "ISO15765_ADDR_TYPE", false), new CanFlags(PassThruConstants.txflags.ISO15765_FRAME_PAD, "ISO15765_FRAME_PAD", true), }; /// /// Gets the name of the interface. /// public string name { get { return "Vector CAN Passthru emulation"; } } /// /// Gets the code name of the interface. /// /// For dynamic interfaces this is the same as the DLL filename without extension. /// /// public string code { get { return "CANEmul"; } } /// /// To return an unique device ID for each opening of the device. /// private int DeviceIdCounter = 1; /// /// To return an unique connection ID for each opening of the device. /// private int ChannelIdCounter = 1; /// /// To return an unique filter ID for each opening of the device. /// private int FilterIdCounter = 1; /// /// Dictionary of open devices. /// private Dictionary> openDevices = new Dictionary>(); /// /// Dictionary of open channels. /// private Dictionary openChannels = new Dictionary(); /// /// Dictionary of active filters. /// private Dictionary activeFilters = new Dictionary(); /// /// Last error encountered. /// private string lastError = "No Error"; /// /// CAN communicator instance. /// private VectorCanCommunicator vectorCanCommunicator; /// /// Session layer part. /// private SessionLayer sessionLayer; /// /// Event logging instance. /// private ILogging iLogging; /// /// Preferences instance. /// private IPreferences iPreferences; /// /// Queue of incoming data. /// private Queue inQueue = new Queue(); /// /// Initializes a new instance of the class. /// /// Event logging instance. /// Preferences instance. public VectorPassthru(ILogging iLogging, IPreferences iPreferences) { this.iLogging = iLogging; this.iPreferences = iPreferences; this.vectorCanCommunicator = new VectorCanCommunicator("CanDataMiner", iLogging, 20); } /// /// Clean up. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Process the payload data. /// /// Target address. /// Data length. /// Payload data. public void processPayload(uint address, uint length, byte[] data) { lock (this.inQueue) { this.inQueue.Enqueue(new PayloadItem(address, length, data)); Monitor.PulseAll(this.inQueue); } } /// /// Get list of supported protocols. /// /// Array of supported protocols by the device. public Protocols[] getProtocols() { return new Protocols[] { Protocols.getProtocolById(Protocols.ISO15765), Protocols.getProtocolById(Protocols.CAN), }; } /// /// Indicate if the interface is a serial device. /// /// Notice that this also applies to Serial on USB devices. /// /// /// 'true' if serial device. public bool isSerial() { return false; } /// /// Flag indicating that the interface has a disconnect bug that needs to be considered. /// /// 'true' if bug exists. public bool disconnectBug() { return false; } /// /// Get array of connection flags available on the device. /// /// Array of CanFlags. public CanFlags[] getConnFlags() { return connFlags; } /// /// Get array of message flags available on the device. /// /// Array of CanFlags. public CanFlags[] getMessageFlags() { return messageFlags; } /// /// Implementation of PassThruOpen. /// /// Always 'null'. /// Pointer to selected device ID. (return value) /// '0' if success, non-zero if a failure occurred. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed, intentional.")] public unsafe PassThruConstants.resultCode PassThruOpen(void* pName, int* pDeviceID) { PassThruConstants.resultCode res = PassThruConstants.resultCode.ERR_DEVICE_NOT_CONNECTED; try { this.sessionLayer = new SessionLayer(this.iLogging, this.iPreferences, 0x07E8, 0x07F8, true); this.sessionLayer.addPresentationLayer(this); this.sessionLayer.setCanCommunicator(this.vectorCanCommunicator); this.vectorCanCommunicator.startCan(); this.openDevices.Add(this.DeviceIdCounter, new List()); *pDeviceID = this.DeviceIdCounter++; res = PassThruConstants.resultCode.ERR_SUCCESS; } catch (Exception ex) { this.iLogging.appendTextLn(ex.GetType().ToString() + ": " + ex.Message); } return res; } /// /// Close the given device. /// /// ID of device to close. /// '0' if success, non-zero if a failure occurred. public PassThruConstants.resultCode PassThruClose(int DeviceID) { if (this.openDevices.ContainsKey(DeviceID)) { if (this.sessionLayer != null) { this.vectorCanCommunicator.stopCan(); this.sessionLayer.setCanCommunicator(null); this.sessionLayer.removePresentationLayer(this); this.sessionLayer.Dispose(); this.sessionLayer = null; } List deviceChannels = this.openDevices[DeviceID]; foreach (SimulatedChannel channel in deviceChannels) { this.PassThruDisconnect(channel.channelId); } this.openDevices.Remove(DeviceID); // Remove remaining data from in queue. this.inQueue.Clear(); return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_DEVICE_ID; } /// /// Establish a connection on the given device. /// /// ID of current device. /// ID of protocol to use. /// Connection Flags. /// Baudrate to use for connection. /// Pointer to value for channel id (return value) /// '0' if success, non-zero if a failure occurred. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "4", Justification = "Reviewed, intentional.")] public unsafe PassThruConstants.resultCode PassThruConnect(int DeviceID, int ProtocolID, uint Flags, uint Baudrate, int* pChannelID) { if (this.openDevices.ContainsKey(DeviceID)) { // Only CAN-bus handled. if (ProtocolID != Protocols.ISO15765 && ProtocolID != Protocols.CAN) { return PassThruConstants.resultCode.ERR_INVALID_PROTOCOL_ID; } Protocols protocol = Protocols.getProtocolById(ProtocolID); foreach (SimulatedChannel channel in this.openChannels.Values) { Protocols proto1 = Protocols.getProtocolById(channel.protocol.id); if (proto1.families == protocol.families) { return PassThruConstants.resultCode.ERR_CHANNEL_IN_USE; } } List deviceChannels = this.openDevices[DeviceID]; SimulatedChannel simulatedConnection = new SimulatedChannel(DeviceID, this.ChannelIdCounter, protocol); deviceChannels.Add(simulatedConnection); *pChannelID = this.ChannelIdCounter; this.openChannels.Add(this.ChannelIdCounter++, simulatedConnection); return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_DEVICE_NOT_CONNECTED; } /// /// Disconnect the given channel. /// /// ID of channel to disconnect. /// '0' if success, non-zero if a failure occurred. public PassThruConstants.resultCode PassThruDisconnect(int ChannelID) { if (this.openChannels.ContainsKey(ChannelID)) { SimulatedChannel channel = this.openChannels[ChannelID]; List deviceChannels = this.openDevices[channel.deviceId]; if (deviceChannels.Contains(channel)) { deviceChannels.Remove(channel); } this.openChannels.Remove(ChannelID); return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Read messages from the given channel. /// /// ID of channel to read from. /// Pointer to message(s) read. /// Number of messages to read, value set at return to actual number read. /// Timeout value before returning with no message read. /// '0' if success, non-zero if a failure occurred. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "Reviewed")] public unsafe PassThruConstants.resultCode PassThruReadMsgs(int ChannelID, PASSTHRU_MSG* pMsg, int* pNumMsgs, int Timeout) { if (pMsg != null) { if (pNumMsgs != null) { if (this.openChannels.ContainsKey(ChannelID)) { lock (this.inQueue) { if (this.inQueue.Count == 0) { if (!Monitor.Wait(this.inQueue, Timeout)) { if (this.inQueue.Count > 0) { this.iLogging.appendTextLn("VectorPassthru.PassThruReadMsgs(): Timeout with data present. this.sendQueue.Count=" + this.inQueue.Count); } } } if (this.inQueue.Count > 0) { PayloadItem item = this.inQueue.Dequeue(); pMsg->DataSize = item.passthruData.Length; for (int i = 0; i < pMsg->DataSize; i++) { pMsg->Data[i] = item.passthruData[i]; } pMsg->ExtraDataIndex = 0; pMsg->RxStatus = 0; pMsg->Timestamp = (int)(DateTime.Now.Ticks / TimeSpan.TicksPerSecond); pMsg->TxFlags = 0; *pNumMsgs = 1; } else { *pNumMsgs = 0; return PassThruConstants.resultCode.ERR_TIMEOUT; } } return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } } return PassThruConstants.resultCode.ERR_NULL_PARAMETER; } /// /// Write messages to the given channel. /// /// ID of channel to write to. /// Message(s) to write. /// Number of messages to write. /// Timeout when writing message to out channel. /// '0' if success, non-zero if a failure occurred. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed, intentional.")] public unsafe PassThruConstants.resultCode PassThruWriteMsgs(int ChannelID, PASSTHRU_MSG* pMsg, int* pNumMsgs, int Timeout) { if (this.openChannels.ContainsKey(ChannelID)) { uint outAddress = (uint)(pMsg->Data[0] << 24 | pMsg->Data[1] << 16 | pMsg->Data[2] << 8 | pMsg->Data[3]); IList response = new List(); for (int i = 0; i < pMsg->DataSize - 4; i++) { response.Add(pMsg->Data[i + 4]); } if (this.sessionLayer != null) { this.sessionLayer.sendData(outAddress, response, (uint)response.Count); return PassThruConstants.resultCode.ERR_SUCCESS; } else { return PassThruConstants.resultCode.ERR_NULL_PARAMETER; } } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Start a periodic message to send from application to vehicle. /// /// ID of channel to write to. /// Message(s) to write. /// Return value: ID of message being sent. /// Interval between each message in milliseconds. /// '0' if success, non-zero if a failure occurred. public unsafe PassThruConstants.resultCode PassThruStartPeriodicMsg(int ChannelID, PASSTHRU_MSG* pMsg, int* pMsgID, int TimeInterval) { if (this.openChannels.ContainsKey(ChannelID)) { return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Stop a periodic message from being sent to the vehicle. /// /// ID of channel to stop message on. /// ID of message to stop sending. /// '0' if success, non-zero if a failure occurred. public PassThruConstants.resultCode PassThruStopPeriodicMsg(int ChannelID, int MsgID) { if (this.openChannels.ContainsKey(ChannelID)) { return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Configure a message filter. /// /// Channel to configure filter on. /// Type of filter to configure. /// Mask message in filter. /// Pattern message in filter. /// Flow control message in filter. /// ID of filter created (out value) /// '0' if success, non-zero if a failure occurred. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "5", Justification = "Reviewed, intentional.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "Reviewed, intentional.")] public unsafe PassThruConstants.resultCode PassThruStartMsgFilter( int ChannelID, int FilterType, PASSTHRU_MSG* pMaskMsg, PASSTHRU_MSG* pPatternMsg, PASSTHRU_MSG* pFlowControlMsg, int* pMsgID) { if (this.openChannels.ContainsKey(ChannelID)) { this.activeFilters.Add( this.FilterIdCounter, new FilterItem( this.FilterIdCounter, (uint)FilterType, pMaskMsg->DataSize, parseMask(pMaskMsg), parseMask(pPatternMsg), parseMask(pFlowControlMsg), (uint)pMaskMsg->TxFlags, pMaskMsg->ProtocolID)); *pMsgID = this.FilterIdCounter; this.FilterIdCounter++; return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Stop a message filter. /// /// Channel to remove filter from. /// ID of filter to remove. /// '0' if success, non-zero if a failure occurred. public PassThruConstants.resultCode PassThruStopMsgFilter(int ChannelID, int MsgID) { if (this.openChannels.ContainsKey(ChannelID)) { if (this.activeFilters.ContainsKey(MsgID)) { this.activeFilters.Remove(MsgID); return PassThruConstants.resultCode.ERR_SUCCESS; } else { return PassThruConstants.resultCode.ERR_INVALID_FILTER_ID; } } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Configure the programming voltage pin and voltage. /// /// ID of Device to configure. /// Number of pin to configure. /// Voltage to set at pin. /// '0' if success, non-zero if a failure occurred. public PassThruConstants.resultCode PassThruSetProgrammingVoltage(int DeviceID, int Pin, int Voltage) { if (this.openDevices.ContainsKey(DeviceID)) { return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_DEVICE_ID; } /// /// Read API version. /// /// ID to get version information from. /// Firmware version (return value). /// DLL version (return value). /// API version (return value). /// '0' if success, non-zero if a failure occurred. public unsafe PassThruConstants.resultCode PassThruReadVersion(int DeviceID, byte* pFirmwareVersion, byte* pDllVersion, byte* pApiVersion) { if (this.openDevices.ContainsKey(DeviceID)) { byte[] fwVer = System.Text.Encoding.ASCII.GetBytes("0.00"); byte[] dllVer = System.Text.Encoding.ASCII.GetBytes(this.vectorCanCommunicator.dllVersion); byte[] apiVer = System.Text.Encoding.ASCII.GetBytes("04.04"); strcpy(fwVer, pFirmwareVersion); strcpy(dllVer, pDllVersion); strcpy(apiVer, pApiVersion); return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_DEVICE_ID; } /// /// Get the last error encountered on the device. /// /// Pointer to string with error description. /// '0' if success, non-zero if a failure occurred. public unsafe PassThruConstants.resultCode PassThruGetLastError(sbyte* pErrorDescription) { // lastError byte[] ascii = System.Text.Encoding.ASCII.GetBytes(this.lastError); for (int i = 0; i < ascii.Length; i++) { *pErrorDescription = (sbyte)ascii[i]; pErrorDescription++; } *pErrorDescription = 0; return PassThruConstants.resultCode.ERR_SUCCESS; } /// /// Perform IOCTL operation on the device. /// /// ID of channel to perform operation on. /// IOCTL Operation. /// Input parameter for operation. /// Output parameter for operation. /// '0' if success, non-zero if a failure occurred. public unsafe PassThruConstants.resultCode PassThruIoctl( int ChannelID, int IoctlID, void* pInput, void* pOutput) { if (this.openChannels.ContainsKey(ChannelID)) { return PassThruConstants.resultCode.ERR_SUCCESS; } return PassThruConstants.resultCode.ERR_INVALID_CHANNEL_ID; } /// /// Clean up. /// /// 'true' if disposing. protected virtual void Dispose(bool disposing) { if (this.sessionLayer != null) { this.sessionLayer.Dispose(); this.sessionLayer = null; } if (this.vectorCanCommunicator != null) { this.vectorCanCommunicator.Dispose(); this.vectorCanCommunicator = null; } } /// /// Copy a 'string' in a byte array to a pre-allocated area designated by a pointer. /// /// Source array. /// Target area. private static unsafe void strcpy(byte[] ba, byte* p) { int i; for (i = 0; i < ba.Length; i++) { p[i] = ba[i]; } p[i] = 0; } /// /// Parse the msg mask. /// /// Pointer to struct. /// Parsed value private static unsafe uint parseMask(PASSTHRU_MSG* pMsg) { return parseMask(new PassThruMsg(pMsg)); } /// /// Parse the msg mask. /// /// Message instance. /// Parsed value private static uint parseMask(PassThruMsg msg) { uint mask = (uint)(msg.Data[0] << 24 | msg.Data[1] << 16 | msg.Data[2] << 8 | msg.Data[3]); return mask; } /// /// Item for a simulated channel. /// private class SimulatedChannel { /// /// Gets ID of device. /// public int deviceId { get; private set; } /// /// Gets ID of channel. /// public int channelId { get; private set; } /// /// Gets protocol instance. /// public Protocols protocol { get; private set; } /// /// Initializes a new instance of the class. /// /// ID of device. /// ID of channel. /// Protocol instance. public SimulatedChannel(int deviceId, int channelId, Protocols protocol) { this.deviceId = deviceId; this.channelId = channelId; this.protocol = protocol; } } /// /// Item for a filter. /// private class FilterItem { /// /// Gets ID of filter. /// public int msgId { get; private set; } /// /// Gets type of filter. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public uint filterType { get; private set; } /// /// Gets datasize. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public int datasize { get; private set; } /// /// Gets filter mask. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public uint mask { get; private set; } /// /// Gets filter pattern. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public uint pattern { get; private set; } /// /// Gets filter flow. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public uint flow { get; private set; } /// /// Gets filter TX flags. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public uint txFlags { get; private set; } /// /// Gets filter protocol ID. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Reviewed.")] public int protocol { get; private set; } /// /// Initializes a new instance of the class. /// /// Filter ID /// Type of filter /// Data Size /// Filter Mask /// Filter Pattern /// Filter Flow /// Filter TX flags /// Filter protocol ID public FilterItem( int msgId, uint filterType, int datasize, uint mask, uint pattern, uint flow, uint txFlags, int protocol) { this.msgId = msgId; this.filterType = filterType; this.datasize = datasize; this.protocol = protocol; this.mask = mask; this.pattern = pattern; this.flow = flow; this.txFlags = txFlags; } /// /// Get string representation of filter. /// /// String representation public override string ToString() { #if TRACE return "msgId=" + this.msgId + ", filterType=" + this.filterType + ", datasize=" + this.datasize + ", protocol=0x" + this.protocol.ToString("x2") + ", mask=0x" + this.mask.ToString("x4") + ", pattern=0x" + this.pattern.ToString("x4") + ", flow=0x" + this.flow.ToString("x4") + ", txFlags=0x" + this.txFlags.ToString("x4") + "\r\n"; #else return "msgId=" + this.msgId + "\r\n"; #endif } } /// /// Payload item. /// private class PayloadItem { /// /// Gets Data body to send through PassThru interface. /// public byte[] passthruData { get; private set; } /// /// Initializes a new instance of the class. /// /// Destination address. /// Payload length. /// Payload data. public PayloadItem(uint address, uint length, byte[] data) { this.passthruData = new byte[length + 4]; this.passthruData[0] = (byte)((address >> 24) & 0xff); this.passthruData[1] = (byte)((address >> 16) & 0xff); this.passthruData[2] = (byte)((address >> 8) & 0xff); this.passthruData[3] = (byte)(address & 0xff); Array.Copy(data, 0, this.passthruData, 4, length); } } } }