1
0
mirror of https://github.com/DarthAffe/OBD.NET.git synced 2025-12-12 08:48:30 +00:00

Merge pull request #2 from romanlum/master

Refactored command handling and added uwp implementation
This commit is contained in:
DarthAffe 2017-07-03 20:29:52 +02:00 committed by GitHub
commit ea2ac35046
164 changed files with 1397 additions and 371 deletions

View File

@ -0,0 +1,29 @@
namespace OBD.NET.Common.Communication.EventArgs
{
/// <summary>
/// Event args for receiving serial data
/// </summary>
public class DataReceivedEventArgs:System.EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="DataReceivedEventArgs"/> class.
/// </summary>
/// <param name="count">The count.</param>
/// <param name="data">The data.</param>
public DataReceivedEventArgs(int count, byte[] data)
{
Count = count;
Data = data;
}
/// <summary>
/// Count of valid data bytes in the buffer
/// </summary>
public int Count { get; private set; }
/// <summary>
/// Data buffer holding the bytes
/// </summary>
public byte[] Data { get; private set; }
}
}

View File

@ -0,0 +1,59 @@
using OBD.NET.Common.Communication.EventArgs;
using System;
using System.Threading.Tasks;
namespace OBD.NET.Communication
{
/// <summary>
/// Serial connection interface
/// </summary>
/// <seealso cref="System.IDisposable" />
public interface ISerialConnection : IDisposable
{
/// <summary>
/// Gets a value indicating whether this instance is open.
/// </summary>
/// <value>
/// <c>true</c> if this instance is open; otherwise, <c>false</c>.
/// </value>
bool IsOpen { get; }
/// <summary>
/// Gets a value indicating whether this instance uses asynchronous IO
/// </summary>
/// <remarks>
/// Has to be set to true if asynchronous IO is supported.
/// If true async methods have to be implemented
/// </remarks>
bool IsAsync { get; }
/// <summary>
/// Occurs when a full line was received
/// </summary>
event EventHandler<DataReceivedEventArgs> DataReceived;
/// <summary>
/// Connects the serial port.
/// </summary>
void Connect();
/// <summary>
/// Connects the serial port asynchronous
/// </summary>
/// <returns></returns>
Task ConnectAsync();
/// <summary>
/// Writes the specified data to the serial connection
/// </summary>
/// <param name="text">The text.</param>
void Write(byte[] data);
/// <summary>
/// Writes the specified data to the serial connection asynchronous
/// </summary>
/// <param name="text">The text.</param>
Task WriteAsync(byte[] data);
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OBD.NET.Common.Devices
{
/// <summary>
/// Class used for queued command
/// </summary>
public class QueuedCommand
{
/// <summary>
/// Initializes a new instance
/// </summary>
/// <param name="commandText"></param>
public QueuedCommand(string commandText)
{
CommandResult = new CommandResult();
CommandText = commandText;
}
public string CommandText { get; set; }
public CommandResult CommandResult { get; }
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using OBD.NET.Util;
namespace OBD.NET.Common.Devices
{
public class CommandResult
{
public CommandResult()
{
WaitHandle = new AsyncManualResetEvent();
}
public object Result { get; set; }
public AsyncManualResetEvent WaitHandle { get; }
}
}

View File

@ -9,6 +9,7 @@ using OBD.NET.Events.EventArgs;
using OBD.NET.Extensions;
using OBD.NET.Logging;
using OBD.NET.OBDData;
using System.Threading.Tasks;
namespace OBD.NET.Devices
{
@ -22,7 +23,7 @@ namespace OBD.NET.Devices
protected static Dictionary<byte, Type> DataTypeCache { get; } = new Dictionary<byte, Type>();
protected Mode Mode { get; set; } = Mode.ShowCurrentData; //TODO DarthAffe 26.06.2016: Implement different modes
#endregion
#region Events
@ -36,7 +37,7 @@ namespace OBD.NET.Devices
#region Constructors
public ELM327(SerialConnection connection, IOBDLogger logger = null)
public ELM327(ISerialConnection connection, IOBDLogger logger = null)
: base(connection, logger: logger)
{ }
@ -44,17 +45,26 @@ namespace OBD.NET.Devices
#region Methods
public override async Task InitializeAsync()
{
await base.InitializeAsync();
InternalInitialize();
}
public override void Initialize()
{
base.Initialize();
InternalInitialize();
}
private void InternalInitialize()
{
Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug);
try
{
Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug);
SendCommand(ATCommand.ResetDevice);
Thread.Sleep(1000);
Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.EchoOff);
@ -71,7 +81,6 @@ namespace OBD.NET.Devices
Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug);
SendCommand(ATCommand.SetProtocolAuto);
Thread.Sleep(1000);
}
// DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry.
catch
@ -81,11 +90,20 @@ namespace OBD.NET.Devices
}
}
/// <summary>
/// Sends the AT command.
/// </summary>
/// <param name="command">The command.</param>
public virtual void SendCommand(ATCommand command)
{
SendCommand(command.Command);
}
/// <summary>
/// Requests the data and calls the handler
/// </summary>
/// <typeparam name="T"></typeparam>
public virtual void RequestData<T>()
where T : class, IOBDData, new()
{
@ -101,13 +119,33 @@ namespace OBD.NET.Devices
SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2"));
}
protected override void ProcessMessage(string message)
/// <summary>
/// Requests the data asynchronous and return the data when available
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public virtual async Task<T> RequestDataAsync<T>()
where T : class, IOBDData, new()
{
Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug);
byte pid = ResolvePid<T>();
Logger?.WriteLine("Requesting PID " + pid.ToString("X2") + " ...", OBDLogLevel.Debug);
var result = SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2"));
await result.WaitHandle.WaitAsync();
return result.Result as T;
}
protected override object ProcessMessage(string message)
{
DateTime timestamp = DateTime.Now;
RawDataReceived?.Invoke(this, new RawDataReceivedEventArgs(message, timestamp));
if (message.Length > 4)
{
if (message[0] == '4')
{
byte mode = (byte)message[1].GetHexVal();
@ -119,13 +157,17 @@ namespace OBD.NET.Devices
{
IOBDData obdData = (IOBDData)Activator.CreateInstance(dataType);
obdData.Load(message.Substring(4, message.Length - 4));
IDataEventManager dataEventManager;
if (_dataReceivedEventHandlers.TryGetValue(dataType, out dataEventManager))
dataEventManager.RaiseEvent(this, obdData, timestamp);
return obdData;
}
}
}
}
return null;
}
protected virtual byte ResolvePid<T>()
@ -155,12 +197,11 @@ namespace OBD.NET.Devices
if (sendCloseProtocol)
{
SendCommand(ATCommand.CloseProtocol);
Thread.Sleep(500);
}
}
catch { }
_dataReceivedEventHandlers = null;
_dataReceivedEventHandlers.Clear();
base.Dispose();
}

View File

@ -9,7 +9,7 @@ namespace OBD.NET.Devices
#region Constructors
public STN1170(SerialConnection connection, IOBDLogger logger = null)
public STN1170(ISerialConnection connection, IOBDLogger logger = null)
: base(connection, logger)
{ }

View File

@ -0,0 +1,256 @@
using System;
using OBD.NET.Communication;
using OBD.NET.Exceptions;
using OBD.NET.Logging;
using System.Threading.Tasks;
using OBD.NET.Common.Communication.EventArgs;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
using OBD.NET.Common.Devices;
namespace OBD.NET.Devices
{
/// <summary>
/// Base class used for communicating with the device
/// </summary>
public abstract class SerialDevice : IDisposable
{
private BlockingCollection<QueuedCommand> commandQueue;
private readonly StringBuilder _lineBuffer = new StringBuilder();
private readonly AutoResetEvent commandFinishedEvent = new AutoResetEvent(false);
private Task commandWorkerTask;
private CancellationTokenSource commandCancellationToken;
protected QueuedCommand currentCommand;
/// <summary>
/// Logger instance
/// </summary>
protected IOBDLogger Logger { get; }
/// <summary>
/// Low level connection
/// </summary>
protected ISerialConnection Connection { get; }
/// <summary>
/// Terminator of the protocol message
/// </summary>
protected char Terminator { get; set; }
#region Constructors
/// <summary>
/// Prevents a default instance of the <see cref="SerialDevice"/> class from being created.
/// </summary>
private SerialDevice()
{
commandQueue = new BlockingCollection<QueuedCommand>();
}
/// <summary>
/// Initializes a new instance of the <see cref="SerialDevice"/> class.
/// </summary>
/// <param name="connection">connection.</param>
/// <param name="terminator">terminator used for terminating the command message</param>
/// <param name="logger">logger instance</param>
protected SerialDevice(ISerialConnection connection, char terminator = '\r', IOBDLogger logger = null)
:this()
{
Connection = connection;
Terminator = terminator;
Logger = logger;
connection.DataReceived += OnDataReceived;
}
#endregion
#region Methods
/// <summary>
/// Initializes the device
/// </summary>
public virtual void Initialize()
{
Connection.Connect();
CheckConnectionAndStartWorker();
}
/// <summary>
/// Initializes the device
/// </summary>
public virtual async Task InitializeAsync()
{
await Connection.ConnectAsync();
CheckConnectionAndStartWorker();
}
/// <summary>
/// Checks the connection and starts background worker which is sending the commands
/// </summary>
/// <exception cref="SerialException">Failed to open Serial-Connection.</exception>
private void CheckConnectionAndStartWorker()
{
if (!Connection.IsOpen)
{
Logger?.WriteLine("Failed to open Serial-Connection.", OBDLogLevel.Error);
throw new SerialException("Failed to open Serial-Connection.");
}
else
{
Logger?.WriteLine("Opened Serial-Connection!", OBDLogLevel.Debug);
}
commandCancellationToken = new CancellationTokenSource();
commandWorkerTask = Task.Factory.StartNew(CommandWorker);
}
/// <summary>
/// Sends the command.
/// </summary>
/// <param name="command">command string</param>
/// <exception cref="System.InvalidOperationException">Not connected</exception>
protected virtual CommandResult SendCommand(string command)
{
if (!Connection.IsOpen)
{
throw new InvalidOperationException("Not connected");
}
command = PrepareCommand(command);
Logger?.WriteLine("Queuing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
var cmd = new QueuedCommand(command);
commandQueue.Add(cmd);
return cmd.CommandResult;
}
/// <summary>
/// Prepares the command
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
protected virtual string PrepareCommand(string command)
{
if (command == null) throw new ArgumentNullException(nameof(command));
if (!command.EndsWith(Terminator.ToString(), StringComparison.Ordinal))
command += Terminator;
return command;
}
/// <summary>
/// Called when data is received from the serial device
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="DataReceivedEventArgs"/> instance containing the event data.</param>
private void OnDataReceived(object sender, DataReceivedEventArgs e)
{
for (int i = 0; i < e.Count; i++)
{
char c = (char)e.Data[i];
switch (c)
{
case '\r':
FinishLine();
break;
case '>':
currentCommand.CommandResult.WaitHandle.Set();
commandFinishedEvent.Set();
break;
case '\n':
case (char)0x00:
break; // ignore
default:
_lineBuffer.Append(c);
break;
}
}
}
/// <summary>
/// Signals a final message
/// </summary>
private void FinishLine()
{
string line = _lineBuffer.ToString().Trim();
_lineBuffer.Clear();
if (string.IsNullOrWhiteSpace(line)) return;
Logger?.WriteLine("Response: '" + line + "'", OBDLogLevel.Verbose);
InternalProcessMessage(line);
}
/// <summary>
/// Process message and sets the result
/// </summary>
/// <param name="message">The message.</param>
private void InternalProcessMessage(string message)
{
var data = ProcessMessage(message);
currentCommand.CommandResult.Result = data;
}
/// <summary>
/// Processes the message.
/// </summary>
/// <param name="message">message received</param>
/// <returns>result data</returns>
protected abstract object ProcessMessage(string message);
/// <summary>
/// Worker method for sending commands
/// </summary>
private async void CommandWorker()
{
while (!commandCancellationToken.IsCancellationRequested)
{
currentCommand = null;
if(commandQueue.TryTake(out currentCommand, Timeout.Infinite, commandCancellationToken.Token))
{
Logger?.WriteLine("Writing Command: '" + currentCommand.CommandText.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
if (Connection.IsAsync)
{
await Connection.WriteAsync(Encoding.ASCII.GetBytes(currentCommand.CommandText));
}
else
{
Connection.Write(Encoding.ASCII.GetBytes(currentCommand.CommandText));
}
//wait for command to finish
commandFinishedEvent.WaitOne();
}
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public virtual void Dispose()
{
commandCancellationToken?.Cancel();
commandWorkerTask?.Wait();
Connection?.Dispose();
}
#endregion
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
namespace OBD.NET.Exceptions
{
@ -17,10 +16,7 @@ namespace OBD.NET.Exceptions
public SerialException(string message, Exception innerException)
: base(message, innerException)
{ }
protected SerialException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endregion
}

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
namespace OBD.NET.Exceptions
{
@ -34,13 +33,7 @@ namespace OBD.NET.Exceptions
this.Result = result;
this.ExpectedResult = expectedResult;
}
protected UnexpectedResultException(SerializationInfo info, StreamingContext context, string result, string expectedResult)
: base(info, context)
{
this.Result = result;
this.ExpectedResult = expectedResult;
}
#endregion
}

View File

@ -0,0 +1,17 @@
using OBD.NET.Logging;
using System.Diagnostics;
namespace OBD.NET.Common.Logging
{
/// <summary>
/// Simple debug logger
/// </summary>
/// <seealso cref="OBD.NET.Logging.IOBDLogger" />
public class OBDDebugLogger : IOBDLogger
{
public void WriteLine(string text, OBDLogLevel level)
{
Debug.WriteLine($"{level}: {text}");
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<Authors>Wyrez / Roman Lumetsberger</Authors>
<Company>-</Company>
<Product>OBD.NET</Product>
<Description>C#-Library to read/write data from/to a car through an ELM327-/STN1170-Adapter</Description>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.1.0</Version>
</PropertyGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More