1
0
mirror of https://github.com/DarthAffe/OBD.NET.git synced 2025-12-13 09:18:31 +00:00

Some improvements

This commit is contained in:
Darth Affe 2017-04-29 14:12:42 +02:00
parent 5dfac27120
commit 7e0e2447c7
6 changed files with 362 additions and 135 deletions

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,7 @@
using System; using System;
using System.IO.Ports; using System.IO.Ports;
using System.Text;
using System.Threading;
namespace OBD.NET.Communication namespace OBD.NET.Communication
{ {
@ -7,24 +9,39 @@ namespace OBD.NET.Communication
{ {
#region Properties & Fields #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<string> MessageReceived;
#endregion #endregion
#region Constructors #region Constructors
public SerialConnection(string port, int baudRate = 38400, Parity parity = Parity.None, public SerialConnection(string port, int baudRate = 38400, Parity parity = Parity.None, StopBits stopBits = StopBits.One,
StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, int timeout = 5000) Handshake handshake = Handshake.None, int timeout = 5000)
{ {
_serialPort = new SerialPort(port, baudRate, parity) this._timeout = timeout;
_serialPort = new EnhancedSerialPort(port, baudRate, parity)
{ {
StopBits = stopBits, StopBits = stopBits,
Handshake = handshake, Handshake = handshake,
ReadTimeout = timeout, ReadTimeout = timeout,
WriteTimeout = timeout WriteTimeout = timeout
}; };
_serialPort.DataReceived += SerialPortOnDataReceived;
} }
#endregion #endregion
@ -34,16 +51,51 @@ namespace OBD.NET.Communication
public void Connect() public void Connect()
{ {
_serialPort.Open(); _serialPort.Open();
Thread.Sleep(5000);
Write("\r");
} }
public void Write(string text) public void Write(string text)
{ {
if (!_hasPrompt.WaitOne(_timeout))
throw new TimeoutException("No prompt received");
_serialPort.Write(text); _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() public void Dispose()

View File

@ -23,8 +23,6 @@ namespace OBD.NET.Devices
protected Mode Mode { get; set; } = Mode.ShowCurrentData; //TODO DarthAffe 26.06.2016: Implement different modes protected Mode Mode { get; set; } = Mode.ShowCurrentData; //TODO DarthAffe 26.06.2016: Implement different modes
protected override string ExpectedNoData => @"(NO DATA)|(SEARCHING)|(STOP)|\?";
#endregion #endregion
#region Events #region Events
@ -51,62 +49,88 @@ namespace OBD.NET.Devices
base.Initialize(); base.Initialize();
Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug); Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug);
int repeats = 3;
bool repeat = true; try
while (repeat)
{ {
try Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug);
{ SendCommand(ATCommand.ResetDevice);
Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug); Thread.Sleep(1000);
SendCommand(ATCommand.ResetDevice);
Thread.Sleep(1000);
Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug); Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.EchoOff); SendCommand(ATCommand.EchoOff);
Logger?.WriteLine("Turning Linefeeds Off ...", OBDLogLevel.Debug); Logger?.WriteLine("Turning Linefeeds Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.LinefeedsOff); SendCommand(ATCommand.LinefeedsOff);
Logger?.WriteLine("Turning Headers Off ...", OBDLogLevel.Debug); Logger?.WriteLine("Turning Headers Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.HeadersOff); SendCommand(ATCommand.HeadersOff);
Logger?.WriteLine("Turning Spaced Off ...", OBDLogLevel.Debug); Logger?.WriteLine("Turning Spaced Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.PrintSpacesOff); SendCommand(ATCommand.PrintSpacesOff);
Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug); Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug);
SendCommand(ATCommand.SetProtocolAuto); SendCommand(ATCommand.SetProtocolAuto);
repeat = false; Thread.Sleep(1000);
} }
// DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry. // DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry.
catch (TimeoutException) catch
{ {
if (repeats > 0) Logger?.WriteLine("Failed to initialize the device!", OBDLogLevel.Error);
{ throw;
Logger?.WriteLine("Timout while initializing ... retry!", OBDLogLevel.Debug);
repeats--;
}
else
{
Logger?.WriteLine("Failed to initialize the device!", OBDLogLevel.Error);
throw;
}
}
} }
Thread.Sleep(1000);
} }
public virtual void SendCommand(ATCommand command) public virtual void SendCommand(ATCommand command)
{ {
SendCommand(command.Command, command.ExpectedResult); SendCommand(command.Command);
} }
public virtual T RequestData<T>() public virtual void RequestData<T>()
where T : class, IOBDData, new() where T : class, IOBDData, new()
{ {
Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug); Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug);
byte pid = ResolvePid<T>();
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<T>()
where T : class, IOBDData, new()
{
byte pid; byte pid;
if (!PidCache.TryGetValue(typeof(T), out pid)) if (!PidCache.TryGetValue(typeof(T), out pid))
{ {
@ -115,48 +139,8 @@ namespace OBD.NET.Devices
PidCache.Add(typeof(T), pid); PidCache.Add(typeof(T), pid);
DataTypeCache.Add(pid, typeof(T)); DataTypeCache.Add(pid, typeof(T));
} }
return RequestData(pid) as T;
}
public virtual IOBDData RequestData(byte pid) return 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;
} }
public override void Dispose() public override void Dispose()
@ -166,11 +150,17 @@ namespace OBD.NET.Devices
public void Dispose(bool sendCloseProtocol) public void Dispose(bool sendCloseProtocol)
{ {
if (sendCloseProtocol) try
{ {
SendCommand(ATCommand.CloseProtocol); if (sendCloseProtocol)
Thread.Sleep(500); {
SendCommand(ATCommand.CloseProtocol);
Thread.Sleep(500);
}
} }
catch { }
_dataReceivedEventHandlers = null;
base.Dispose(); base.Dispose();
} }

View File

@ -1,9 +1,4 @@
using System; 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.Communication;
using OBD.NET.Exceptions; using OBD.NET.Exceptions;
using OBD.NET.Logging; using OBD.NET.Logging;
@ -14,26 +9,22 @@ namespace OBD.NET.Devices
{ {
#region Properties & Fields #region Properties & Fields
private readonly byte[] _responseBuffer = new byte[512];
protected IOBDLogger Logger { get; } protected IOBDLogger Logger { get; }
protected SerialConnection Connection { get; } protected SerialConnection Connection { get; }
protected char Prompt { get; set; }
protected char Terminator { get; set; } protected char Terminator { get; set; }
protected abstract string ExpectedNoData { get; }
#endregion #endregion
#region Constructors #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.Connection = connection;
this.Prompt = prompt;
this.Terminator = terminator; this.Terminator = terminator;
this.Logger = logger; this.Logger = logger;
connection.MessageReceived += SerialMessageReceived;
} }
#endregion #endregion
@ -52,34 +43,16 @@ namespace OBD.NET.Devices
} }
else else
Logger?.WriteLine("Opened Serial-Connection!", OBDLogLevel.Debug); 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); command = PrepareCommand(command);
Logger?.WriteLine("Writing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose); Logger?.WriteLine("Writing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
Connection.Write(command); Connection.Write(command);
Logger?.WriteLine("Waiting for response ...", OBDLogLevel.Debug);
List<string> 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) protected virtual string PrepareCommand(string command)
@ -92,22 +65,16 @@ namespace OBD.NET.Devices
return command; return command;
} }
protected virtual List<string> ReadResponse() private void SerialMessageReceived(object sender, string message)
{ {
byte b; if (string.IsNullOrWhiteSpace(message)) return;
int count = 0;
do
{
b = Connection.ReadByte();
if (b != 0x00)
_responseBuffer[count++] = b;
} while (b != Prompt);
string response = Encoding.ASCII.GetString(_responseBuffer, 0, count - 1); // -1 to remove the prompt Logger?.WriteLine("Response: '" + message + "'", OBDLogLevel.Verbose);
Logger?.WriteLine("Response: '" + response.Replace("\r", "<\\r>").Replace("\n", "<\\n>") + "'", OBDLogLevel.Verbose); ProcessMessage(message.Trim());
return response.Split('\r').Select(x => x.Replace("\r", string.Empty).Replace("\n", string.Empty).Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
} }
protected abstract void ProcessMessage(string message);
public virtual void Dispose() public virtual void Dispose()
{ {
Connection?.Dispose(); Connection?.Dispose();

View File

@ -45,7 +45,9 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Commands\STCommand.cs" />
<Compile Include="Commands\ATCommand.cs" /> <Compile Include="Commands\ATCommand.cs" />
<Compile Include="Communication\EnhancedSerialPort.cs" />
<Compile Include="Communication\SerialConnection.cs" /> <Compile Include="Communication\SerialConnection.cs" />
<Compile Include="DataTypes\Degree.cs" /> <Compile Include="DataTypes\Degree.cs" />
<Compile Include="DataTypes\Minute.cs" /> <Compile Include="DataTypes\Minute.cs" />