//----------------------------------------------------------------------- // // 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 Protocol { using System; using System.Collections.Generic; using System.Linq; using System.Threading; using global::DeviceApi; using global::SharedObjects; using global::SharedObjects.Api; using global::SharedObjects.GUI; using global::SharedObjects.Protocol; /// /// Protocol handler for using a serial adapter with AT commands like AGV4000 and ELM. /// /// This is an abstract class, please extend from this class to add support for the specific device /// and declare the necessary send/expect sequence. /// /// public abstract class Protocol_Serial_AT : Protocol_Common, IProtocolHandler, IMsgCallback { /// /// String containing valid characters for a hex number. /// private const string VALID_HEX = "0123456789abcdefABCDEF"; /// /// List of protocol names. /// private static readonly string[] protocolNames = { string.Empty, "SAE J1850 PWM (41.6 kbps)", "SAE J1850 VPW (10.4 kbps)", "ISO 9141-2 (5 baud init, 10.4 kbps)", "ISO 14230-4 KWP (5 baud init, 10.4 kbps)", "ISO 14230-4 KWP (fast init, 10.4 kbps)", "ISO 15765-4 CAN (11 bit ID, 500 kbps)", "ISO 15765-4 CAN (29 bit ID, 500 kbps)", "ISO 15765-4 CAN (11 bit ID, 250 kbps)", "ISO 15765-4 CAN (29 bit ID, 250 kbps)", }; /// /// Gets detected Protocol number. /// public uint detectedProtocol { get; private set; } /// /// Indicate that thread shall run or not. /// private bool doRun = false; /// /// Serial port handler. /// private SerialPortHandler serialPortHandler; /// /// Serial port receiver thread. /// private Thread serialReceiverThread; /// /// Send/Expect sequence strings. /// private string[] sendExpectSequenceArr = null; /// /// Gets or sets Send/Expect sequence strings. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Value shall only be read by this class but set in subclasses to allow for different sets of send/expect sequences.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "Value shall only be read by this class but set in subclasses to allow for different sets of send/expect sequences.")] protected string[] sendExpectSequence { private get { return this.sendExpectSequenceArr; } set { this.sendExpectSequenceArr = value; } } /// /// Position in send/expect sequence. /// private int sendExpectPosition = 0; /// /// Flag to indicate that we got data. /// private bool gotData = false; /// /// Initializes a new instance of the class. /// /// Logging interface. /// Current protocol. /// Current message handler instance. /// Source address. /// Checksum flag. /// Serial port handler. /// Callback interface for enabling the buttons when a protocol is fully initialized. protected Protocol_Serial_AT( ILogging iLogging, Protocols protocol, MessageHandler messageHandler, uint srcAddr, bool checksum, SerialPortHandler serialPortHandler, IEnableButtons iEnableButtons) : base(iLogging, protocol, messageHandler, srcAddr, checksum, iEnableButtons) { this.serialPortHandler = serialPortHandler; this.detectedProtocol = 0; } /// /// Get array of possible address bit alternatives. /// /// Array of possible address bit alternatives. public static string[] addressBitAlternatives() { return new string[] { "11" }; } /// /// Initialize the protocol if necessary. /// /// TX Flags for any messages that are created during initialization. public void init(int txFlags) { this.serialPortHandler.open(); this.doRun = true; this.iLogging.appendText("Serial Port Receiver Thread starting.\r\n"); this.serialReceiverThread = new Thread(new ThreadStart(this.receiverThread)); this.serialReceiverThread.Name = "Serial Receiver"; this.serialReceiverThread.Start(); } /// /// Start protocol services if necessary. /// public void start() { } /// /// Stop any running protocol services. /// public void stopProtocol() { this.doRun = false; /* if (this.serialPortHandler != null) { this.serialPortHandler.close(); this.serialPortHandler = null; } */ try { this.serialReceiverThread.Abort(); this.serialReceiverThread.Interrupt(); this.serialReceiverThread.Join(4000); this.serialReceiverThread = null; } catch (Exception ex) { this.iLogging.appendText(ex.Message + "\r\n"); } } /// /// Send one message where the header info already is set in the payload. /// /// Source address (address of tester). /// Tx Flags /// Message Payload which includes address data. public void sendProbeMessage(uint srcAddr, int txFlags, byte[] payload) { this.sendMessage(0xf0, 0x33, txFlags, payload); } /// /// Compose according to protocol and send one message. /// /// 'Our' address, used in some cases. /// Target address. /// Flags to add to message. /// The data to send. /// Error text if there was an error. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "3", Justification = "Reviewed.")] public string sendMessage( uint senderAddrValue, uint receiverAddrValue, int txFlags, byte[] payload) { string buf = string.Empty; for (int i = 0; i < payload.Length; i++) { if (i > 0) { buf += " "; } buf += payload[i].ToString("x2"); } buf += "\r"; #if TRACE_COMM this.iLogging.appendText(buf + "\n"); #endif this.serialPortHandler.write(buf); return string.Empty; } /// /// Set address on message according to protocol rules. /// /// Notice that not all protocols uses both from and to address. /// /// /// Message to set address on. /// From address to set. /// To address to set. public void setAddress(ref IPassThruMsg msg, uint fromAddress, uint toAddress) { } /// /// Set the address(es) of the message. /// /// This is a fully composed address, usually used by filters. /// /// /// Message to set data in. /// Combined Address. public void setAddress(ref IPassThruMsg txmsg, uint address) { } /// /// Set additional header data. /// /// Notice that not all protocols supports this. /// If the protocol doesn't support this no modifications shall /// occur of the message and the function shall return silently. /// /// /// Message to set data on. /// Size to set. /// 'true' if functional addressing is used. (depends on protocol) public void setHeader(ref IPassThruMsg msg, uint size, bool functionalTargetAddressing) { } /// /// Check if protocol supports 29 bit addressing. /// /// 'true' if protocol supports 29 bit addressing. public bool is29bit() { return false; } /// /// Get payload part of message. /// /// Message to get payload from. /// Byte array with payload. public override byte[] getPayload(IPassThruMsg msg) { byte[] payload = extractPayload(msg, 4); return payload; } /// /// Receive one message according to protocol. /// /// Message struct. public void receiveMessage(IPassThruMsg rxmsg) { byte[] payload = this.getPayload(rxmsg); this.dispatchMessage(rxmsg, payload, 0, 0); } /// /// Thread reading from serial port. /// private void receiverThread() { this.iLogging.appendText("receiverThread started.\r\n"); bool expecting = false; string buf = string.Empty; try { while (this.doRun) { if (this.sendExpectPosition < this.sendExpectSequence.Length) { this.iEnableButtons.updateProgress(this.sendExpectPosition, this.sendExpectSequence.Length); } if (!expecting && this.sendExpectPosition < this.sendExpectSequence.Length) { Thread.Sleep(1000); this.iLogging.appendText("Send '" + this.sendExpectSequence[this.sendExpectPosition] + "'.\r\n"); this.serialPortHandler.write(this.sendExpectSequence[this.sendExpectPosition++] + "\r"); this.gotData = false; this.iLogging.appendText("Expect '" + this.sendExpectSequence[this.sendExpectPosition] + "'.\r\n"); expecting = true; this.iEnableButtons.updateProgress(this.sendExpectPosition, this.sendExpectSequence.Length); } buf = this.serialPortHandler.read(this.sendExpectPosition < this.sendExpectSequence.Length); while (buf.StartsWith("\n")) { buf = buf.Substring(1); } string[] splitBuf = buf.Split(new char[] { '\r' }); if (this.sendExpectPosition >= this.sendExpectSequence.Length) { this.handleRealData(splitBuf); } else { this.initProtocolHandshake(ref expecting, ref buf, splitBuf); } } } catch (ObjectDisposedException ex) { this.iLogging.appendText("ex='" + ex.Message + "'\r\n" + ex.StackTrace + "\r\n"); } this.stopProtocol(); this.iLogging.appendText("======================= ReceiverThread Ended. ===========================\r\n"); } /// /// Handle real OBD data. /// /// Line by line split data from response. private void handleRealData(string[] splitBuf) { foreach (string buf2 in splitBuf) { string buf3 = buf2; while (buf3.StartsWith("\n")) { buf3 = buf3.Substring(1); } // Check if first two chars are hex, then we probably have data. if (buf2.Length > 1 && VALID_HEX.IndexOf(buf3.Substring(0, 1)) >= 0 && VALID_HEX.IndexOf(buf3.Substring(1, 1)) >= 0) { try { string[] sa = buf3.Split(new char[] { ' ' }); List data = new List(); foreach (string item in sa) { string item1 = item.Trim(); if (item1.Length > 0) { uint d1 = Convert.ToUInt32(item1, 16); if (d1 > 0xffffff) { data.Add((byte)((d1 >> 24) & 0xff)); } if (d1 > 0xffff) { data.Add((byte)((d1 >> 16) & 0xff)); } if (d1 > 0xff) { data.Add((byte)((d1 >> 8) & 0xff)); } data.Add((byte)(d1 & 0xff)); } } byte[] ba = data.ToArray(); if (ba.Length >= 3) { IPassThruMsg msg = PassThruMsg.getMaskedMsg(Protocols.ISO15765); for (int i = 0; i < ba.Length; i++) { msg.Data[i] = ba[i]; } msg.DataSize = ba.Length; byte[] payload = new byte[0]; uint srcAddress = 0; switch (this.detectedProtocol) { case 0: this.iLogging.appendText("Invalid protocol number\r\n"); break; case 1: case 2: case 3: payload = Protocol_Common.extractPayload(msg, 3); srcAddress = msg.Data[2]; break; case 4: case 5: if ((ba[0] & 0x3f) == 0) { payload = Protocol_Common.extractPayload(msg, 4); } else { payload = Protocol_Common.extractPayload(msg, 3); } srcAddress = msg.Data[2]; break; case 6: case 8: payload = Protocol_Common.extractPayload(msg, 3); srcAddress = (uint)(msg.Data[0] << 8) | msg.Data[1]; break; case 7: case 9: payload = Protocol_Common.extractPayload(msg, 5); srcAddress = 0; for (int i = 0; i < 4; i++) { srcAddress = (uint)(srcAddress << 8) | msg.Data[i]; } break; } this.dispatchMessage(msg, payload, srcAddress, this.detectedProtocol); } } catch (Exception ex) { // Oh Dear - not data... this.iLogging.appendText("ex='" + ex.Message + "'\r\n" + ex.StackTrace + "\r\n"); } } } } /// /// Handle protocol initialization. /// /// 'true' if expecting answer, 'false' if expected answer arrived. /// Buffer containing response from device. /// Line by line split of buffer data. private void initProtocolHandshake(ref bool expecting, ref string buf, string[] splitBuf) { if (expecting && this.sendExpectPosition < this.sendExpectSequence.Length) { switch (this.sendExpectSequence[this.sendExpectPosition]) { case "{W}": expecting = this.waitAMoment(expecting); break; case "{XN}": expecting = this.dataResponse(expecting, buf, splitBuf); break; case "{XP}": expecting = this.protocolResponse(expecting, buf, splitBuf); break; case ">": expecting = false; this.sendExpectPosition++; break; default: expecting = this.defaultResponse(expecting, ref buf); break; } if (this.sendExpectPosition >= this.sendExpectSequence.Length) { this.iLogging.appendText("Initialization Finished, protocol number=" + this.detectedProtocol + "\r\n"); this.iEnableButtons.updateProgress(0, this.sendExpectSequence.Length); if (this.detectedProtocol == 0) { this.iEnableButtons.enableButtons(); this.doRun = false; } else { this.iEnableButtons.enableButtons(); } } } } /// /// Handle a default response. /// /// Expectation Flag. /// Data buffer. /// Possibly updated expectation Flag. private bool defaultResponse(bool expecting, ref string buf) { int n = buf.IndexOf(this.sendExpectSequence[this.sendExpectPosition]); if (n >= 0) { expecting = false; buf = buf.Substring(n + this.sendExpectSequence[this.sendExpectPosition].Length); this.sendExpectPosition++; } return expecting; } /// /// Wait a moment before sending next command. /// /// Expectation Flag. /// Possibly updated expectation Flag. private bool waitAMoment(bool expecting) { Thread.Sleep(4000); expecting = false; this.sendExpectPosition++; return expecting; } /// /// Response for request for protocol command. /// /// Expectation Flag. /// Data buffer. /// Data buffer split per line. /// Possibly updated expectation Flag. private bool protocolResponse(bool expecting, string buf, string[] splitBuf) { this.iLogging.appendText("buf='" + buf + "'"); if (buf.Length > 0 && splitBuf.Length > 0) { for (int i = 0; i < splitBuf.Length; i++) { this.iLogging.appendText("splitBuf[" + i + "]='" + splitBuf[i] + "'"); string buf2 = splitBuf[i].Trim(); if (buf2.Length > 1 && VALID_HEX.IndexOf(buf2.Substring(0, 1)) >= 0 && VALID_HEX.IndexOf(buf2.Substring(1, 1)) >= 0) { this.detectedProtocol = (Convert.ToUInt32(buf2, 16) & 0x0f); this.iEnableButtons.setProtocolInfo(protocolNames[this.detectedProtocol]); expecting = false; } else { if (buf2.Length > 0 && buf2.Substring(0, 1) == "0") { this.detectedProtocol = 0; expecting = false; } } } if (!expecting) { this.sendExpectPosition++; } } return expecting; } /// /// Data response. /// /// Expectation Flag. /// Data buffer. /// Data buffer split per line. /// Possibly updated expectation Flag. private bool dataResponse(bool expecting, string buf, string[] splitBuf) { if (buf.Length > 0 && splitBuf.Length > 0) { for (int i = 0; i < splitBuf.Length; i++) { string buf2 = splitBuf[i].Trim(); // Line starting with hex before end of data. if (buf2.Length > 1 && VALID_HEX.IndexOf(buf2.Substring(0, 1)) >= 0 && VALID_HEX.IndexOf(buf2.Substring(1, 1)) >= 0) { this.gotData = true; } // Empty line indicates end of data. if (this.gotData && buf2.Length == 0) { expecting = false; } } if (!expecting) { this.sendExpectPosition++; } } return expecting; } } }