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