//-----------------------------------------------------------------------
//
// 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.Threading;
using System.Windows.Forms;
using global::SharedObjects;
using global::SharedObjects.Api;
using global::SharedObjects.Protocol;
///
/// A layer that acts as an intermediate between the API and the protocol handler.
///
public class MessageHandler : IMessageHandler
{
///
/// Size of a temporary buffer when reading/writing to the raw log.
///
public const int BUFFER_SIZE = 65536;
///
/// Locking object to handle concurrency when writing to raw log file.
///
private object messageLockObjectInstance = new object();
///
/// Gets locking object to handle concurrency when writing to raw log file.
///
public object messageLockObject
{
get
{
return this.messageLockObjectInstance;
}
}
///
/// Logger interface.
///
private ILogging iLogging;
///
/// Current timeout for an operation.
///
private int timeout;
///
/// Current connection instance.
///
private IPassThruConnection passThruConnection;
///
/// Current protocol handler instance.
///
private IMsgCallback protocolHandler = null;
///
/// Current raw data receiver.
///
private IRawReceiver rawReceiver = null;
///
/// Message counter.
///
private uint msgNum = 0;
///
/// Current protocol.
///
private Protocols protocol;
///
/// Thread handling receiving of messages.
///
private Thread msgReceiverThread = null;
///
/// Flag to control receiver thread.
///
private bool doRun = true;
///
/// Locking object to handle thread management.
///
private object threadMgmtLockObject = new object();
///
/// Initializes a new instance of the class.
///
/// Logger instance.
/// Initial timeout value.
/// Current device connection instance.
/// Current protocol instance.
public MessageHandler(ILogging iLogging, int timeout, IPassThruConnection passThruConnection, Protocols protocol)
{
this.iLogging = iLogging;
this.timeout = timeout;
this.passThruConnection = passThruConnection;
this.protocol = protocol;
}
///
/// Configure the protocol handler for this message handler.
///
/// Protocol handler instance.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "protocolHandler", Justification = "Intentional.")]
public void setProtocolCallback(IMsgCallback protocolHandler)
{
this.protocolHandler = protocolHandler;
}
///
/// Change the timeout value.
///
/// New timeout value.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "timeout", Justification = "Intentional.")]
public void setTimeout(int timeout)
{
this.timeout = timeout;
}
///
/// Perform a five baud initialization. (Only for ISO9141, KWP2000)
///
/// Target address.
/// Number of received bytes.
/// Keyword 1.
/// Keyword 2.
/// Result status, 0=success.
public PassThruConstants.resultCode IoctlFiveBaudInit(uint target, out uint outBytes, out uint keyword1, out uint keyword2)
{
return this.passThruConnection.IoctlFiveBaudInit(target, out outBytes, out keyword1, out keyword2);
}
///
/// Perform a fast initialization. (Only KWP2000)
///
/// Protocol ID
/// Source address to use.
/// Result message from init.
/// Result status, 0=success.
public PassThruConstants.resultCode IoctlFastInit(int protocolId, uint srcaddress, out IPassThruMsg result)
{
return this.passThruConnection.IoctlFastInit(protocolId, srcaddress, out result);
}
///
/// Set the receiver of raw messages for this protocol handler.
/// Notice that the value may be 'null'.
///
/// Instance of raw data receiver.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "rawReceiver", Justification = "Intentional.")]
public void setRawReceiver(IRawReceiver rawReceiver)
{
this.rawReceiver = rawReceiver;
}
///
/// Send the given message.
///
/// Message to send.
/// Status code from operation, 0=Success.
public PassThruConstants.resultCode sendMessage(IPassThruMsg txmsg)
{
#if TRACE_COMM
this.iLogging.dispMsg("Tx", txmsg);
#endif
// Transmit message.
int n1 = 1;
PassThruConstants.resultCode status = this.passThruConnection.Gl_PassThruWriteMsgs(txmsg, ref n1, this.timeout);
string errtext;
if (!this.iLogging.errTestConnection(this.passThruConnection, status, out errtext))
{
this.iLogging.appendText("WriteMsgs res=" + status + ", n=" + n1 + ", errtxt=" + errtext + "\r\n");
this.iLogging.dispMsg("TxErr", txmsg);
}
return status;
}
///
/// Start Message Listener thread.
///
public void startThread()
{
lock (this.threadMgmtLockObject)
{
this.iLogging.appendText("CAN Msg Receiver Thread starting.\r\n");
this.msgReceiverThread = new Thread(new ThreadStart(this.receiverThread));
this.msgReceiverThread.Name = "Msg Receiver";
this.msgReceiverThread.Start();
}
}
///
/// Stop the Message Listener thread.
///
public void stopThread()
{
lock (this.threadMgmtLockObject)
{
this.doRun = false;
this.iLogging.appendText("CAN Msg Receiver Thread stopping.\r\n");
// Check if already dead.
if (this.msgReceiverThread != null && !this.msgReceiverThread.IsAlive)
{
this.msgReceiverThread = null;
}
// Not dead, abort it!
if (this.msgReceiverThread != null)
{
this.iLogging.appendText("CAN Msg Receiver Thread aborting...\r\n");
this.msgReceiverThread.Abort();
this.iLogging.appendText("CAN Msg Receiver Thread interrupted...\r\n");
this.msgReceiverThread.Interrupt();
this.iLogging.appendText("CAN Msg Receiver Thread joining...\r\n");
this.msgReceiverThread.Join(4000);
this.msgReceiverThread = null;
this.iLogging.appendText("CAN Msg Receiver Thread stopped.\r\n");
}
}
}
///
/// Get the payload part of the message with the header stripped off.
///
/// Message to get payload from.
/// Byte array with payload data.
public byte[] getPayload(IPassThruMsg msg)
{
return this.protocolHandler.getPayload(msg);
}
///
/// Starts a periodically sent message, e.g. for keep alive of a connection.
///
/// Message to send.
/// Message ID.
/// Status of action.
public PassThruConstants.resultCode startPeriodicMessage(IPassThruMsg msg, out int msgId)
{
return this.passThruConnection.Gl_PassThruStartPeriodicMsg(msg, out msgId, 3000);
}
///
/// Stops a periodically sent message.
///
/// ID of message to stop as provided by startPeriodicMessage()
public void stopPeriodicMessage(int msgId)
{
this.passThruConnection.Gl_PassThruStopPeriodicMsg(msgId);
}
///
/// Glue method for error handler.
///
/// Result code.
/// Error text.
/// 'true' if execution can continue.
public bool errTestConnection(PassThruConstants.resultCode res, out string errtext)
{
return this.iLogging.errTestConnection(this.passThruConnection, res, out errtext);
}
///
/// Handle execution of background thread that performs the communication
/// with the USB device.
/// The received message is then dispatched to one or two handlers
/// depending on conditions.
///
[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]
private void receiverThread()
{
this.iLogging.appendText("Receiver Thread started.\r\n");
// Outside loop to avoid performance penalty when allocating/deallocating.
IPassThruMsg rxmsg;
this.doRun = true;
try
{
while (this.doRun)
{
int numRxMsg = 1;
this.msgNum++;
PassThruConstants.resultCode res = this.passThruConnection.Gl_PassThruReadMsgs((int)this.protocol.id, out rxmsg, ref numRxMsg, this.timeout);
if (this.doRun)
{
if (res != 0)
{
switch (res)
{
case PassThruConstants.resultCode.READ_FAIL:
this.iLogging.appendText("[" + this.msgNum + "]: Read Fail - Terminating.\r\n", LogLevel.LOG_WARN);
this.iLogging.appendText("Last Error: " + this.passThruConnection.lastError + "\r\n");
this.doRun = false;
break;
case PassThruConstants.resultCode.NO_CHANNEL:
this.iLogging.appendText("[" + this.msgNum + "]: Not Connected - please connect first.\r\n", LogLevel.LOG_WARN);
this.doRun = false;
break;
case PassThruConstants.resultCode.ERR_TIMEOUT: // Timeout - no action necessary.
case PassThruConstants.resultCode.ERR_BUFFER_EMPTY: // Timeout - no action necessary.
break;
default:
this.iLogging.appendText("[" + this.msgNum + "]: {" + numRxMsg + "} [" + res + "] " + this.passThruConnection.lastError + "\r\n", LogLevel.LOG_WARN);
break;
}
Thread.Sleep(200); // To avoid flooding.
}
else
{
try
{
// Check which receivers that may want the message.
// Notice that the rawReceiver can decide that it
// shall be the only one that sees this message.
bool interpretResponse = true;
if ((rxmsg.RxStatus & PassThruConstants.rxflags.START_OF_MESSAGE) != 0)
{
#if TRACE_COMM
this.iLogging.dispMsg("START_OF_MESSAGE: Rx", rxmsg);
#endif
}
else
{
if ((rxmsg.RxStatus & PassThruConstants.rxflags.START_OF_MESSAGE) == 0
&& (rxmsg.RxStatus & PassThruConstants.rxflags.TX_MSG_TYPE) == 0)
{
#if TRACE_COMM
this.iLogging.dispMsg("Rx", rxmsg);
#endif
if (this.rawReceiver != null)
{
interpretResponse = this.rawReceiver.rawHandler(rxmsg);
}
if (interpretResponse && this.protocolHandler != null)
{
this.protocolHandler.receiveMessage(rxmsg);
}
}
}
}
catch (ThreadAbortException)
{
throw;
}
catch (IndexOutOfRangeException ex)
{
this.iLogging.appendText("Index out of range, corrupt message?\r\n" + ex.Message + "\r\n\r\n" + ex.StackTrace + "\r\n", LogLevel.LOG_WARN);
this.iLogging.dispMsg("Ex", rxmsg);
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message + "\r\n\r\n" + ex.StackTrace,
"Exception Encountered!",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
this.iLogging.dispMsg("Ex", rxmsg);
throw;
}
}
}
}
}
catch (ThreadAbortException)
{
throw;
}
catch (Exception ex)
{
this.iLogging.appendText(ex.GetType().ToString() + "\r\n" + ex.Message + "\r\n" + ex.StackTrace + "\r\n");
}
finally
{
this.iLogging.appendText("CAN Msg Receiver Thread ended.\r\n");
}
}
}
}