diff --git a/OBD.NET/OBD.NET.Common/Devices/Command.cs b/OBD.NET/OBD.NET.Common/Devices/Command.cs new file mode 100644 index 0000000..c3b306c --- /dev/null +++ b/OBD.NET/OBD.NET.Common/Devices/Command.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OBD.NET.Common.Devices +{ + /// + /// Class used for queued command + /// + public class QueuedCommand + { + + /// + /// Initializes a new instance + /// + /// + public QueuedCommand(string commandText) + { + CommandResult = new CommandResult(); + CommandText = commandText; + } + + public string CommandText { get; set; } + + public CommandResult CommandResult { get; } + } +} diff --git a/OBD.NET/OBD.NET.Common/Devices/CommandResult.cs b/OBD.NET/OBD.NET.Common/Devices/CommandResult.cs new file mode 100644 index 0000000..7352301 --- /dev/null +++ b/OBD.NET/OBD.NET.Common/Devices/CommandResult.cs @@ -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; } + } +} diff --git a/OBD.NET/OBD.NET.Common/Devices/ELM327.cs b/OBD.NET/OBD.NET.Common/Devices/ELM327.cs index 6b07d36..170f561 100644 --- a/OBD.NET/OBD.NET.Common/Devices/ELM327.cs +++ b/OBD.NET/OBD.NET.Common/Devices/ELM327.cs @@ -89,13 +89,21 @@ namespace OBD.NET.Devices throw; } } - + + /// + /// Sends the AT command. + /// + /// The command. public virtual void SendCommand(ATCommand command) { SendCommand(command.Command); } - + + /// + /// Requests the data and calls the handler + /// + /// public virtual void RequestData() where T : class, IOBDData, new() { @@ -111,13 +119,33 @@ namespace OBD.NET.Devices SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2")); } - protected override void ProcessMessage(string message) + /// + /// Requests the data asynchronous and return the data when available + /// + /// + /// + public virtual async Task RequestDataAsync() + where T : class, IOBDData, new() + { + Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug); + byte pid = ResolvePid(); + 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(); @@ -129,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() diff --git a/OBD.NET/OBD.NET.Common/Devices/SerialDevice.cs b/OBD.NET/OBD.NET.Common/Devices/SerialDevice.cs index 2ca4954..d9c8dcc 100644 --- a/OBD.NET/OBD.NET.Common/Devices/SerialDevice.cs +++ b/OBD.NET/OBD.NET.Common/Devices/SerialDevice.cs @@ -7,6 +7,7 @@ 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 { @@ -15,7 +16,7 @@ namespace OBD.NET.Devices /// public abstract class SerialDevice : IDisposable { - private BlockingCollection commandQueue; + private BlockingCollection commandQueue; private readonly StringBuilder _lineBuffer = new StringBuilder(); @@ -24,6 +25,8 @@ namespace OBD.NET.Devices private Task commandWorkerTask; private CancellationTokenSource commandCancellationToken; + + protected QueuedCommand currentCommand; /// /// Logger instance @@ -48,7 +51,7 @@ namespace OBD.NET.Devices /// private SerialDevice() { - commandQueue = new BlockingCollection(); + commandQueue = new BlockingCollection(); } /// @@ -117,16 +120,20 @@ namespace OBD.NET.Devices /// /// command string /// Not connected - protected virtual void SendCommand(string command) + 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); - commandQueue.Add(command); + + var cmd = new QueuedCommand(command); + commandQueue.Add(cmd); + return cmd.CommandResult; } /// @@ -161,6 +168,7 @@ namespace OBD.NET.Devices break; case '>': + currentCommand.CommandResult.WaitHandle.Set(); commandFinishedEvent.Set(); break; @@ -186,14 +194,25 @@ namespace OBD.NET.Devices if (string.IsNullOrWhiteSpace(line)) return; Logger?.WriteLine("Response: '" + line + "'", OBDLogLevel.Verbose); - ProcessMessage(line); + InternalProcessMessage(line); + } + + /// + /// Process message and sets the result + /// + /// The message. + private void InternalProcessMessage(string message) + { + var data = ProcessMessage(message); + currentCommand.CommandResult.Result = data; } /// /// Processes the message. /// /// message received - protected abstract void ProcessMessage(string message); + /// result data + protected abstract object ProcessMessage(string message); /// /// Worker method for sending commands @@ -202,18 +221,18 @@ namespace OBD.NET.Devices { while (!commandCancellationToken.IsCancellationRequested) { - string command = null; - if(commandQueue.TryTake(out command, Timeout.Infinite, commandCancellationToken.Token)) + currentCommand = null; + if(commandQueue.TryTake(out currentCommand, Timeout.Infinite, commandCancellationToken.Token)) { - Logger?.WriteLine("Writing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose); + Logger?.WriteLine("Writing Command: '" + currentCommand.CommandText.Replace('\r', '\'') + "'", OBDLogLevel.Verbose); if (Connection.IsAsync) { - await Connection.WriteAsync(Encoding.ASCII.GetBytes(command)); + await Connection.WriteAsync(Encoding.ASCII.GetBytes(currentCommand.CommandText)); } else { - Connection.Write(Encoding.ASCII.GetBytes(command)); + Connection.Write(Encoding.ASCII.GetBytes(currentCommand.CommandText)); } //wait for command to finish diff --git a/OBD.NET/OBD.NET.Common/Util/AsyncManulResetEvent.cs b/OBD.NET/OBD.NET.Common/Util/AsyncManulResetEvent.cs new file mode 100644 index 0000000..4f8f34f --- /dev/null +++ b/OBD.NET/OBD.NET.Common/Util/AsyncManulResetEvent.cs @@ -0,0 +1,48 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace OBD.NET.Util +{ + /// + /// Notifies one or more waiting awaiters that an event has occurred + /// + public class AsyncManualResetEvent + { + private volatile TaskCompletionSource _tcs = new TaskCompletionSource(); + + /// + /// Waits the async. + /// + /// + public Task WaitAsync() + { + return _tcs.Task; + } + + //public void Set() { m_tcs.TrySetResult(true); } + /// + /// Sets the state of the event to signaled, allowing one or more waiting awaiters to proceed. + /// + public void Set() + { + var tcs = _tcs; + Task.Factory.StartNew(s => ((TaskCompletionSource)s).TrySetResult(true), + tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default); + tcs.Task.Wait(); + } + + /// + /// Sets the state of the event to nonsignaled, causing awaiters to block. + /// + public void Reset() + { + while (true) + { + var tcs = _tcs; + if (!tcs.Task.IsCompleted || + Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource(), tcs) == tcs) + return; + } + } + } +} \ No newline at end of file