From 7e0e2447c7b78074305ba1a67f2596bb1722c05b Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 29 Apr 2017 14:12:42 +0200 Subject: [PATCH] Some improvements --- OBD.NET/OBD.NET/Commands/STCommand.cs | 53 ++++++ .../Communication/EnhancedSerialPort.cs | 163 ++++++++++++++++++ .../OBD.NET/Communication/SerialConnection.cs | 66 ++++++- OBD.NET/OBD.NET/Devices/ELM327.cs | 158 ++++++++--------- OBD.NET/OBD.NET/Devices/SerialDevice.cs | 55 ++---- OBD.NET/OBD.NET/OBD.NET.csproj | 2 + 6 files changed, 362 insertions(+), 135 deletions(-) create mode 100644 OBD.NET/OBD.NET/Commands/STCommand.cs create mode 100644 OBD.NET/OBD.NET/Communication/EnhancedSerialPort.cs diff --git a/OBD.NET/OBD.NET/Commands/STCommand.cs b/OBD.NET/OBD.NET/Commands/STCommand.cs new file mode 100644 index 0000000..e0e1af8 --- /dev/null +++ b/OBD.NET/OBD.NET/Commands/STCommand.cs @@ -0,0 +1,53 @@ +namespace OBD.NET.Commands +{ + public class STCommand + { + #region Values + + //TODO DarthAffe 19.03.2017: Implement all commands + + internal static readonly STCommand AddPassFilter = new STCommand("STFAP"); + internal static readonly STCommand AddBlockFilter = new STCommand("STFAB"); + internal static readonly STCommand AddFlowControlFilter = new STCommand("STFAFC"); + internal static readonly STCommand ClearPassFilters = new STCommand("STFCP"); + internal static readonly STCommand ClearBlockFilters = new STCommand("STFCB"); + internal static readonly STCommand ClearFlowControlFilters = new STCommand("STFCFC"); + internal static readonly STCommand Monitor = new STCommand("STM"); + internal static readonly STCommand MonitorAll = new STCommand("STMA"); + + #endregion + + #region Properties & Fields + + public string Command { get; } + + #endregion + + #region Constructors + + protected STCommand(string command) + { + this.Command = command; + } + + #endregion + + #region Methods + + public override string ToString() + { + return Command; + } + + #endregion + + #region Operators + + public static implicit operator string(STCommand command) + { + return command.ToString(); + } + + #endregion + } +} diff --git a/OBD.NET/OBD.NET/Communication/EnhancedSerialPort.cs b/OBD.NET/OBD.NET/Communication/EnhancedSerialPort.cs new file mode 100644 index 0000000..f92267b --- /dev/null +++ b/OBD.NET/OBD.NET/Communication/EnhancedSerialPort.cs @@ -0,0 +1,163 @@ +// Copyright 2013 Antanas Veiverys www.veiverys.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; +using System.IO; +using System.IO.Ports; +using System.Reflection; +using System.Runtime.InteropServices; + +// Source: http://antanas.veiverys.com/mono-serialport-datareceived-event-workaround-using-a-derived-class/ +namespace OBD.NET.Communication +{ + [DesignerCategory("Code")] + public class EnhancedSerialPort : SerialPort + { + #region Properties & Fields + + // private member access via reflection + private int _fd; + private FieldInfo _disposedFieldInfo; + private object _dataReceived; + + #endregion + + #region DLLImports + + [DllImport("MonoPosixHelper", SetLastError = true)] + private static extern bool poll_serial(int fd, out int error, int timeout); + + [DllImport("libc")] + private static extern IntPtr strerror(int errnum); + + #endregion + + #region Constructors + + public EnhancedSerialPort() + : base() + { } + + public EnhancedSerialPort(IContainer container) + : base(container) + { } + + public EnhancedSerialPort(string portName) + : base(portName) + { } + + public EnhancedSerialPort(string portName, int baudRate) + : base(portName, baudRate) + { } + + public EnhancedSerialPort(string portName, int baudRate, Parity parity) + : base(portName, baudRate, parity) + { } + + public EnhancedSerialPort(string portName, int baudRate, Parity parity, int dataBits) + : base(portName, baudRate, parity, dataBits) + { } + + public EnhancedSerialPort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) + : base(portName, baudRate, parity, dataBits, stopBits) + { } + + #endregion + + #region Methods + + public new void Open() + { + base.Open(); + + if (!IsWindows) + { + FieldInfo fieldInfo = BaseStream.GetType().GetField("fd", BindingFlags.Instance | BindingFlags.NonPublic); + _fd = (int)fieldInfo.GetValue(BaseStream); + _disposedFieldInfo = BaseStream.GetType().GetField("disposed", BindingFlags.Instance | BindingFlags.NonPublic); + fieldInfo = typeof(SerialPort).GetField("data_received", BindingFlags.Instance | BindingFlags.NonPublic); + _dataReceived = fieldInfo.GetValue(this); + + new System.Threading.Thread(EventThreadFunction).Start(); + } + } + + private static bool IsWindows + { + get + { + PlatformID id = Environment.OSVersion.Platform; + return (id == PlatformID.Win32Windows) || (id == PlatformID.Win32NT); // WinCE not supported + } + } + + private void EventThreadFunction() + { + do + { + try + { + Stream stream = BaseStream; + if (stream == null) + return; + + if (Poll(stream, ReadTimeout)) + OnDataReceived(null); + } + catch + { + return; + } + } while (IsOpen); + } + + private void OnDataReceived(SerialDataReceivedEventArgs args) + { + SerialDataReceivedEventHandler handler = Events[_dataReceived] as SerialDataReceivedEventHandler; + handler?.Invoke(this, args); + } + + private bool Poll(Stream stream, int timeout) + { + CheckDisposed(stream); + if (IsOpen == false) + throw new Exception("port is closed"); + int error; + + bool pollResult = poll_serial(_fd, out error, ReadTimeout); + if (error == -1) + ThrowIOException(); + + return pollResult; + } + + private static void ThrowIOException() + { + int errnum = Marshal.GetLastWin32Error(); + string errorMessage = Marshal.PtrToStringAnsi(strerror(errnum)); + + throw new IOException(errorMessage); + } + + private void CheckDisposed(Stream stream) + { + bool disposed = (bool)_disposedFieldInfo.GetValue(stream); + if (disposed) + throw new ObjectDisposedException(stream.GetType().FullName); + } + + #endregion + } +} diff --git a/OBD.NET/OBD.NET/Communication/SerialConnection.cs b/OBD.NET/OBD.NET/Communication/SerialConnection.cs index 0894ecb..3c8aed7 100644 --- a/OBD.NET/OBD.NET/Communication/SerialConnection.cs +++ b/OBD.NET/OBD.NET/Communication/SerialConnection.cs @@ -1,5 +1,7 @@ using System; using System.IO.Ports; +using System.Text; +using System.Threading; namespace OBD.NET.Communication { @@ -7,24 +9,39 @@ namespace OBD.NET.Communication { #region Properties & Fields - private readonly SerialPort _serialPort; + private readonly EnhancedSerialPort _serialPort; + private readonly int _timeout; - public bool IsOpen => _serialPort.IsOpen; + public bool IsOpen => _serialPort?.IsOpen ?? false; + + private readonly byte[] _readBuffer = new byte[1024]; + private readonly StringBuilder _lineBuffer = new StringBuilder(); + + private readonly AutoResetEvent _hasPrompt = new AutoResetEvent(true); + + #endregion + + #region Events + + public event EventHandler MessageReceived; #endregion #region Constructors - public SerialConnection(string port, int baudRate = 38400, Parity parity = Parity.None, - StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, int timeout = 5000) + public SerialConnection(string port, int baudRate = 38400, Parity parity = Parity.None, StopBits stopBits = StopBits.One, + Handshake handshake = Handshake.None, int timeout = 5000) { - _serialPort = new SerialPort(port, baudRate, parity) + this._timeout = timeout; + _serialPort = new EnhancedSerialPort(port, baudRate, parity) { StopBits = stopBits, Handshake = handshake, ReadTimeout = timeout, WriteTimeout = timeout }; + + _serialPort.DataReceived += SerialPortOnDataReceived; } #endregion @@ -34,16 +51,51 @@ namespace OBD.NET.Communication public void Connect() { _serialPort.Open(); + Thread.Sleep(5000); + Write("\r"); } public void Write(string text) { + if (!_hasPrompt.WaitOne(_timeout)) + throw new TimeoutException("No prompt received"); + _serialPort.Write(text); } - public byte ReadByte() + private void SerialPortOnDataReceived(object sender, SerialDataReceivedEventArgs serialDataReceivedEventArgs) { - return (byte)_serialPort.ReadByte(); + int count = _serialPort.Read(_readBuffer, 0, _serialPort.BytesToRead); + for (int i = 0; i < count; i++) + { + char c = (char)_readBuffer[i]; + switch (c) + { + case '\r': + FinishLine(); + break; + + case '>': + _hasPrompt.Set(); + break; + + case '\n': + case (char)0x00: + break; // ignore + + default: + _lineBuffer.Append(c); + break; + } + } + } + + private void FinishLine() + { + string line = _lineBuffer.ToString(); + _lineBuffer.Clear(); + + MessageReceived?.Invoke(this, line); } public void Dispose() diff --git a/OBD.NET/OBD.NET/Devices/ELM327.cs b/OBD.NET/OBD.NET/Devices/ELM327.cs index de9bd75..ca7e445 100644 --- a/OBD.NET/OBD.NET/Devices/ELM327.cs +++ b/OBD.NET/OBD.NET/Devices/ELM327.cs @@ -23,8 +23,6 @@ namespace OBD.NET.Devices protected Mode Mode { get; set; } = Mode.ShowCurrentData; //TODO DarthAffe 26.06.2016: Implement different modes - protected override string ExpectedNoData => @"(NO DATA)|(SEARCHING)|(STOP)|\?"; - #endregion #region Events @@ -51,62 +49,88 @@ namespace OBD.NET.Devices base.Initialize(); Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug); - int repeats = 3; - bool repeat = true; - while (repeat) + + try { - try - { - Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug); - SendCommand(ATCommand.ResetDevice); - Thread.Sleep(1000); + Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug); + SendCommand(ATCommand.ResetDevice); + Thread.Sleep(1000); - Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug); - SendCommand(ATCommand.EchoOff); + Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug); + SendCommand(ATCommand.EchoOff); - Logger?.WriteLine("Turning Linefeeds Off ...", OBDLogLevel.Debug); - SendCommand(ATCommand.LinefeedsOff); + Logger?.WriteLine("Turning Linefeeds Off ...", OBDLogLevel.Debug); + SendCommand(ATCommand.LinefeedsOff); - Logger?.WriteLine("Turning Headers Off ...", OBDLogLevel.Debug); - SendCommand(ATCommand.HeadersOff); + Logger?.WriteLine("Turning Headers Off ...", OBDLogLevel.Debug); + SendCommand(ATCommand.HeadersOff); - Logger?.WriteLine("Turning Spaced Off ...", OBDLogLevel.Debug); - SendCommand(ATCommand.PrintSpacesOff); + Logger?.WriteLine("Turning Spaced Off ...", OBDLogLevel.Debug); + SendCommand(ATCommand.PrintSpacesOff); - Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug); - SendCommand(ATCommand.SetProtocolAuto); + Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug); + SendCommand(ATCommand.SetProtocolAuto); - repeat = false; - } - // DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry. - catch (TimeoutException) - { - if (repeats > 0) - { - Logger?.WriteLine("Timout while initializing ... retry!", OBDLogLevel.Debug); - repeats--; - } - else - { - Logger?.WriteLine("Failed to initialize the device!", OBDLogLevel.Error); - throw; - } - } + Thread.Sleep(1000); + } + // DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry. + catch + { + Logger?.WriteLine("Failed to initialize the device!", OBDLogLevel.Error); + throw; } - - Thread.Sleep(1000); } public virtual void SendCommand(ATCommand command) { - SendCommand(command.Command, command.ExpectedResult); + SendCommand(command.Command); } - public virtual T RequestData() + public virtual void RequestData() where T : class, IOBDData, new() { Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug); + byte pid = ResolvePid(); + RequestData(pid); + } + + protected virtual void RequestData(byte pid) + { + Logger?.WriteLine("Requesting PID " + pid.ToString("X2") + " ...", OBDLogLevel.Debug); + SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2")); + } + + protected override void 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(); + if (mode == (byte)Mode) + { + byte pid = (byte)message.Substring(2, 2).GetHexVal(); + Type dataType; + if (DataTypeCache.TryGetValue(pid, out dataType)) + { + 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); + } + } + } + } + + protected virtual byte ResolvePid() + where T : class, IOBDData, new() + { byte pid; if (!PidCache.TryGetValue(typeof(T), out pid)) { @@ -115,48 +139,8 @@ namespace OBD.NET.Devices PidCache.Add(typeof(T), pid); DataTypeCache.Add(pid, typeof(T)); } - return RequestData(pid) as T; - } - public virtual IOBDData RequestData(byte pid) - { - Logger?.WriteLine("Requesting PID " + pid.ToString("X2") + " ...", OBDLogLevel.Debug); - string result = SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2"), "4.*"); - Logger?.WriteLine("Result for PID " + pid.ToString("X2") + ": " + result, OBDLogLevel.Debug); - return ProcessData(result); - } - - protected virtual IOBDData ProcessData(string data) - { - if (data == null) return null; - - DateTime timestamp = DateTime.Now; - - RawDataReceived?.Invoke(this, new RawDataReceivedEventArgs(data, timestamp)); - - if (data.Length > 4) - if (data[0] == '4') - { - byte mode = (byte)data[1].GetHexVal(); - if (mode == (byte)Mode) - { - byte pid = (byte)data.Substring(2, 2).GetHexVal(); - Type dataType; - if (DataTypeCache.TryGetValue(pid, out dataType)) - { - IOBDData obdData = (IOBDData)Activator.CreateInstance(dataType); - obdData.Load(data.Substring(4, data.Length - 4)); - - IDataEventManager dataEventManager; - if (_dataReceivedEventHandlers.TryGetValue(dataType, out dataEventManager)) - dataEventManager.RaiseEvent(this, obdData, timestamp); - - return obdData; - } - } - } - - return null; + return pid; } public override void Dispose() @@ -166,11 +150,17 @@ namespace OBD.NET.Devices public void Dispose(bool sendCloseProtocol) { - if (sendCloseProtocol) + try { - SendCommand(ATCommand.CloseProtocol); - Thread.Sleep(500); + if (sendCloseProtocol) + { + SendCommand(ATCommand.CloseProtocol); + Thread.Sleep(500); + } } + catch { } + + _dataReceivedEventHandlers = null; base.Dispose(); } diff --git a/OBD.NET/OBD.NET/Devices/SerialDevice.cs b/OBD.NET/OBD.NET/Devices/SerialDevice.cs index 29566e1..67fb3dc 100644 --- a/OBD.NET/OBD.NET/Devices/SerialDevice.cs +++ b/OBD.NET/OBD.NET/Devices/SerialDevice.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; using OBD.NET.Communication; using OBD.NET.Exceptions; using OBD.NET.Logging; @@ -14,26 +9,22 @@ namespace OBD.NET.Devices { #region Properties & Fields - private readonly byte[] _responseBuffer = new byte[512]; - protected IOBDLogger Logger { get; } protected SerialConnection Connection { get; } - protected char Prompt { get; set; } protected char Terminator { get; set; } - protected abstract string ExpectedNoData { get; } - #endregion #region Constructors - protected SerialDevice(SerialConnection connection, char prompt = '>', char terminator = '\r', IOBDLogger logger = null) + protected SerialDevice(SerialConnection connection, char terminator = '\r', IOBDLogger logger = null) { this.Connection = connection; - this.Prompt = prompt; this.Terminator = terminator; this.Logger = logger; + + connection.MessageReceived += SerialMessageReceived; } #endregion @@ -52,34 +43,16 @@ namespace OBD.NET.Devices } else Logger?.WriteLine("Opened Serial-Connection!", OBDLogLevel.Debug); - - Thread.Sleep(500); } - protected virtual string SendCommand(string command, string expectedResult = null) + protected virtual void SendCommand(string command) { - if (!Connection.IsOpen) return null; + if (!Connection.IsOpen) return; command = PrepareCommand(command); Logger?.WriteLine("Writing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose); Connection.Write(command); - - Logger?.WriteLine("Waiting for response ...", OBDLogLevel.Debug); - List results = ReadResponse(); - if (expectedResult == null) - return results.LastOrDefault(); - - // DarthAffe 22.02.2017: We expect the lats result to be the "best". - for (int i = results.Count - 1; i >= 0; i--) - if (Regex.Match(results[i], expectedResult, RegexOptions.IgnoreCase).Success) - return results[i]; - - if (results.Any(x => Regex.Match(x, ExpectedNoData, RegexOptions.IgnoreCase).Success)) - return null; - - Logger?.WriteLine("Unexpected Result: '" + string.Join("<\\r>", results) + "' expected was '" + expectedResult + "'", OBDLogLevel.Error); - throw new UnexpectedResultException(string.Join("<\\r>", results), expectedResult); } protected virtual string PrepareCommand(string command) @@ -92,22 +65,16 @@ namespace OBD.NET.Devices return command; } - protected virtual List ReadResponse() + private void SerialMessageReceived(object sender, string message) { - byte b; - int count = 0; - do - { - b = Connection.ReadByte(); - if (b != 0x00) - _responseBuffer[count++] = b; - } while (b != Prompt); + if (string.IsNullOrWhiteSpace(message)) return; - string response = Encoding.ASCII.GetString(_responseBuffer, 0, count - 1); // -1 to remove the prompt - Logger?.WriteLine("Response: '" + response.Replace("\r", "<\\r>").Replace("\n", "<\\n>") + "'", OBDLogLevel.Verbose); - return response.Split('\r').Select(x => x.Replace("\r", string.Empty).Replace("\n", string.Empty).Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + Logger?.WriteLine("Response: '" + message + "'", OBDLogLevel.Verbose); + ProcessMessage(message.Trim()); } + protected abstract void ProcessMessage(string message); + public virtual void Dispose() { Connection?.Dispose(); diff --git a/OBD.NET/OBD.NET/OBD.NET.csproj b/OBD.NET/OBD.NET/OBD.NET.csproj index 8851d00..0c0204a 100644 --- a/OBD.NET/OBD.NET/OBD.NET.csproj +++ b/OBD.NET/OBD.NET/OBD.NET.csproj @@ -45,7 +45,9 @@ + +