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

First somehow working version

This commit is contained in:
Darth Affe 2017-02-26 14:57:15 +01:00
parent b3e8e82da7
commit 5dfac27120
36 changed files with 798 additions and 14 deletions

View File

@ -0,0 +1,61 @@
namespace OBD.NET.Commands
{
public class ATCommand
{
#region Values
//TODO DarthAffe 26.06.2016: Implement all commands
public static readonly ATCommand RepeatLastCommand = new ATCommand("\r");
public static readonly ATCommand ResetDevice = new ATCommand("ATZ");
public static readonly ATCommand ReadVoltage = new ATCommand("ATRV");
public static readonly ATCommand EchoOn = new ATCommand("ATE1", "^OK$");
public static readonly ATCommand EchoOff = new ATCommand("ATE0", "^OK$");
public static readonly ATCommand HeadersOn = new ATCommand("ATH1", "^OK$");
public static readonly ATCommand HeadersOff = new ATCommand("ATH0", "^OK$");
public static readonly ATCommand PrintSpacesOn = new ATCommand("ATS1", "^OK$");
public static readonly ATCommand PrintSpacesOff = new ATCommand("ATS0", "^OK$");
public static readonly ATCommand LinefeedsOn = new ATCommand("ATL1", "^OK$");
public static readonly ATCommand LinefeedsOff = new ATCommand("ATL0", "^OK$");
public static readonly ATCommand SetProtocolAuto = new ATCommand("ATSP0", "^OK$");
public static readonly ATCommand PrintVersion = new ATCommand("ATI", "^ELM327.*");
public static readonly ATCommand CloseProtocol = new ATCommand("ATPC");
#endregion
#region Properties & Fields
public string Command { get; }
public string ExpectedResult { get; }
#endregion
#region Constructors
private ATCommand(string command, string expectedResult = null)
{
this.Command = command;
this.ExpectedResult = expectedResult;
}
#endregion
#region Methods
public override string ToString()
{
return Command;
}
#endregion
#region Operators
public static implicit operator string(ATCommand command)
{
return command.ToString();
}
#endregion
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.IO.Ports;
namespace OBD.NET.Communication
{
public class SerialConnection : IDisposable
{
#region Properties & Fields
private readonly SerialPort _serialPort;
public bool IsOpen => _serialPort.IsOpen;
#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)
{
_serialPort = new SerialPort(port, baudRate, parity)
{
StopBits = stopBits,
Handshake = handshake,
ReadTimeout = timeout,
WriteTimeout = timeout
};
}
#endregion
#region Methods
public void Connect()
{
_serialPort.Open();
}
public void Write(string text)
{
_serialPort.Write(text);
}
public byte ReadByte()
{
return (byte)_serialPort.ReadByte();
}
public void Dispose()
{
_serialPort?.Dispose();
}
#endregion
}
}

View File

@ -2,6 +2,12 @@
{
public class Count : GenericData
{
#region Properties & Fields
protected override string Unit => null;
#endregion
#region Constructors
public Count(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Degree : GenericData
{
#region Properties & Fields
protected override string Unit => "°";
#endregion
#region Constructors
public Degree(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class DegreeCelsius : GenericData
{
#region Properties & Fields
protected override string Unit => "°C";
#endregion
#region Constructors
public DegreeCelsius(double value, double minValue, double maxValue)

View File

@ -2,7 +2,7 @@
namespace OBD.NET.DataTypes
{
public class GenericData
public abstract class GenericData
{
#region Properties & Fields
@ -11,6 +11,8 @@ namespace OBD.NET.DataTypes
public double MaxValue { get; }
public bool IsFloatingPointValue { get; }
protected abstract string Unit { get; }
#endregion
#region Constructors
@ -46,5 +48,14 @@ namespace OBD.NET.DataTypes
}
#endregion
#region Methods
public override string ToString()
{
return (IsFloatingPointValue ? Value.ToString("0.00") : Value.ToString()) + (Unit == null ? string.Empty : (" " + Unit));
}
#endregion
}
}

View File

@ -2,6 +2,12 @@
{
public class GramPerSec : GenericData
{
#region Properties & Fields
protected override string Unit => "g/s";
#endregion
#region Constructors
public GramPerSec(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Kilometre : GenericData
{
#region Properties & Fields
protected override string Unit => "km";
#endregion
#region Constructors
public Kilometre(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class KilometrePerHour : GenericData
{
#region Properties & Fields
protected override string Unit => "km/h";
#endregion
#region Constructors
public KilometrePerHour(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Kilopascal : GenericData
{
#region Properties & Fields
protected override string Unit => "kPa";
#endregion
#region Constructors
public Kilopascal(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class LitresPerHour : GenericData
{
#region Properties & Fields
protected override string Unit => "l/h";
#endregion
#region Constructors
public LitresPerHour(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Milliampere : GenericData
{
#region Properties & Fields
protected override string Unit => "mA";
#endregion
#region Constructors
public Milliampere(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Minute : GenericData
{
#region Properties & Fields
protected override string Unit => "min";
#endregion
#region Constructors
public Minute(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class NewtonMetre : GenericData
{
#region Properties & Fields
protected override string Unit => "N";
#endregion
#region Constructors
public NewtonMetre(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Pascal : GenericData
{
#region Properties & Fields
protected override string Unit => "Pa";
#endregion
#region Constructors
public Pascal(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Percent : GenericData
{
#region Properties & Fields
protected override string Unit => "%";
#endregion
#region Constructors
public Percent(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Ratio : GenericData
{
#region Properties & Fields
protected override string Unit => null;
#endregion
#region Constructors
public Ratio(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class RevolutionsPerMinute : GenericData
{
#region Properties & Fields
protected override string Unit => "rpm";
#endregion
#region Constructors
public RevolutionsPerMinute(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Second : GenericData
{
#region Properties & Fields
protected override string Unit => "s";
#endregion
#region Constructors
public Second(double value, double minValue, double maxValue)

View File

@ -2,6 +2,12 @@
{
public class Volt : GenericData
{
#region Properties & Fields
protected override string Unit => "V";
#endregion
#region Constructors
public Volt(double value, double minValue, double maxValue)

View File

@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Threading;
using OBD.NET.Commands;
using OBD.NET.Communication;
using OBD.NET.Enums;
using OBD.NET.Events;
using OBD.NET.Events.EventArgs;
using OBD.NET.Extensions;
using OBD.NET.Logging;
using OBD.NET.OBDData;
namespace OBD.NET.Devices
{
public class ELM327 : SerialDevice
{
#region Properties & Fields
protected Dictionary<Type, IDataEventManager> _dataReceivedEventHandlers = new Dictionary<Type, IDataEventManager>();
protected static Dictionary<Type, byte> PidCache { get; } = new Dictionary<Type, byte>();
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
protected override string ExpectedNoData => @"(NO DATA)|(SEARCHING)|(STOP)|\?";
#endregion
#region Events
public delegate void DataReceivedEventHandler<T>(object sender, DataReceivedEventArgs<T> args) where T : IOBDData;
public delegate void RawDataReceivedEventHandler(object sender, RawDataReceivedEventArgs args);
public event RawDataReceivedEventHandler RawDataReceived;
#endregion
#region Constructors
public ELM327(SerialConnection connection, IOBDLogger logger = null)
: base(connection, logger: logger)
{ }
#endregion
#region Methods
public override void Initialize()
{
base.Initialize();
Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug);
int repeats = 3;
bool repeat = true;
while (repeat)
{
try
{
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 Linefeeds Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.LinefeedsOff);
Logger?.WriteLine("Turning Headers Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.HeadersOff);
Logger?.WriteLine("Turning Spaced Off ...", OBDLogLevel.Debug);
SendCommand(ATCommand.PrintSpacesOff);
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);
}
public virtual void SendCommand(ATCommand command)
{
SendCommand(command.Command, command.ExpectedResult);
}
public virtual T RequestData<T>()
where T : class, IOBDData, new()
{
Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug);
byte pid;
if (!PidCache.TryGetValue(typeof(T), out pid))
{
T data = Activator.CreateInstance<T>();
pid = data.PID;
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;
}
public override void Dispose()
{
Dispose(true);
}
public void Dispose(bool sendCloseProtocol)
{
if (sendCloseProtocol)
{
SendCommand(ATCommand.CloseProtocol);
Thread.Sleep(500);
}
base.Dispose();
}
public void SubscribeDataReceived<T>(DataReceivedEventHandler<T> eventHandler) where T : IOBDData
{
IDataEventManager eventManager;
if (!_dataReceivedEventHandlers.TryGetValue(typeof(T), out eventManager))
_dataReceivedEventHandlers.Add(typeof(T), (eventManager = new GenericDataEventManager<T>()));
((GenericDataEventManager<T>)eventManager).DataReceived += eventHandler;
}
public void UnsubscribeDataReceived<T>(DataReceivedEventHandler<T> eventHandler) where T : IOBDData
{
IDataEventManager eventManager;
if (_dataReceivedEventHandlers.TryGetValue(typeof(T), out eventManager))
((GenericDataEventManager<T>)eventManager).DataReceived -= eventHandler;
}
#endregion
}
}

View File

@ -0,0 +1,18 @@
using OBD.NET.Communication;
using OBD.NET.Logging;
namespace OBD.NET.Devices
{
public class STN1170 : ELM327 // Fully compatible device
{
//TODO DarthAffe 26.06.2016: Add ST-Commands and stuff
#region Constructors
public STN1170(SerialConnection connection, IOBDLogger logger = null)
: base(connection, logger)
{ }
#endregion
}
}

View File

@ -0,0 +1,118 @@
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;
namespace OBD.NET.Devices
{
public abstract class SerialDevice : IDisposable
{
#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)
{
this.Connection = connection;
this.Prompt = prompt;
this.Terminator = terminator;
this.Logger = logger;
}
#endregion
#region Methods
public virtual void Initialize()
{
Logger?.WriteLine("Opening Serial-Connection ...", OBDLogLevel.Debug);
Connection.Connect();
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);
Thread.Sleep(500);
}
protected virtual string SendCommand(string command, string expectedResult = null)
{
if (!Connection.IsOpen) return null;
command = PrepareCommand(command);
Logger?.WriteLine("Writing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
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)
{
if (command == null) throw new ArgumentNullException(nameof(command));
if (!command.EndsWith(Terminator.ToString(), StringComparison.Ordinal))
command += Terminator;
return command;
}
protected virtual List<string> ReadResponse()
{
byte b;
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: '" + 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();
}
public virtual void Dispose()
{
Connection?.Dispose();
}
#endregion
}
}

View File

@ -3,7 +3,7 @@
/// <summary>
/// https://en.wikipedia.org/wiki/OBD-II_PIDs#Modes
/// </summary>
internal enum Mode
public enum Mode
{
ShowCurrentData = 0x01,
ShowFreezeFrameData = 0x02,

View File

@ -0,0 +1,25 @@
using System;
using OBD.NET.OBDData;
namespace OBD.NET.Events.EventArgs
{
public class DataReceivedEventArgs<T> where T : IOBDData
{
#region Properties & Fields
public T Data { get; }
public DateTime Timestamp { get; }
#endregion
#region Constructors
public DataReceivedEventArgs(T data, DateTime timestamp)
{
this.Data = data;
this.Timestamp = timestamp;
}
#endregion
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace OBD.NET.Events.EventArgs
{
public class RawDataReceivedEventArgs
{
#region Properties & Fields
public string Data { get; }
public DateTime Timestamp { get; }
#endregion
#region Constructors
public RawDataReceivedEventArgs(string data, DateTime timestamp)
{
this.Data = data;
this.Timestamp = timestamp;
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
using System;
using OBD.NET.Devices;
using OBD.NET.Events.EventArgs;
using OBD.NET.OBDData;
namespace OBD.NET.Events
{
public class GenericDataEventManager<T> : IDataEventManager where T : IOBDData
{
#region Properties & Fields
#endregion
#region Events
internal event ELM327.DataReceivedEventHandler<T> DataReceived;
#endregion
#region Constructors
#endregion
#region Methods
public void RaiseEvent(object sender, IOBDData data, DateTime timestamp)
{
DataReceived?.Invoke(sender, new DataReceivedEventArgs<T>((T)data, timestamp));
}
#endregion
}
}

View File

@ -0,0 +1,10 @@
using System;
using OBD.NET.OBDData;
namespace OBD.NET.Events
{
public interface IDataEventManager
{
void RaiseEvent(object sender, IOBDData data, DateTime timestamp);
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Runtime.Serialization;
namespace OBD.NET.Exceptions
{
public class SerialException : Exception
{
#region Constructors
public SerialException()
{ }
public SerialException(string message)
: base(message)
{ }
public SerialException(string message, Exception innerException)
: base(message, innerException)
{ }
protected SerialException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endregion
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace OBD.NET.Exceptions
{
public class UnexpectedResultException : Exception
{
#region Properties & Fields
public string Result { get; }
public string ExpectedResult { get; }
#endregion
#region Constructors
public UnexpectedResultException(string result, string expectedResult)
:this($"Unexpected result '{result}'. Expected was '{expectedResult}'", result, expectedResult)
{
this.Result = result;
this.ExpectedResult = expectedResult;
}
public UnexpectedResultException(string message, string result, string expectedResult)
: base(message)
{
this.Result = result;
this.ExpectedResult = expectedResult;
}
public UnexpectedResultException(string message, Exception innerException, string result, string expectedResult)
: base(message, innerException)
{
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,24 @@
using System;
namespace OBD.NET.Extensions
{
public static class HexExtension
{
public static int GetHexVal(this char hex)
{
return hex - (hex < 58 ? 48 : (hex < 97 ? 55 : 87));
}
public static int GetHexVal(this string hex)
{
if ((hex.Length % 2) == 1)
throw new ArgumentException("The binary key cannot have an odd number of digits");
int result = 0;
foreach (char c in hex)
result = (result << 4) + (GetHexVal(c));
return result;
}
}
}

View File

@ -0,0 +1,7 @@
namespace OBD.NET.Logging
{
public interface IOBDLogger
{
void WriteLine(string text, OBDLogLevel level);
}
}

View File

@ -0,0 +1,10 @@
namespace OBD.NET.Logging
{
public enum OBDLogLevel
{
None,
Error,
Verbose,
Debug
}
}

View File

@ -45,6 +45,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\ATCommand.cs" />
<Compile Include="Communication\SerialConnection.cs" />
<Compile Include="DataTypes\Degree.cs" />
<Compile Include="DataTypes\Minute.cs" />
<Compile Include="DataTypes\LitresPerHour.cs" />
@ -63,7 +65,19 @@
<Compile Include="DataTypes\Percent.cs" />
<Compile Include="DataTypes\DegreeCelsius.cs" />
<Compile Include="DataTypes\GenericData.cs" />
<Compile Include="Devices\SerialDevice.cs" />
<Compile Include="Devices\ELM327.cs" />
<Compile Include="Devices\STN1170.cs" />
<Compile Include="Enums\Mode.cs" />
<Compile Include="Events\EventArgs\RawDataReceivedEventArgs.cs" />
<Compile Include="Events\EventArgs\DataReceivedEventArgs.cs" />
<Compile Include="Events\GenericDataEventManager.cs" />
<Compile Include="Events\IDataEventManager.cs" />
<Compile Include="Exceptions\SerialException.cs" />
<Compile Include="Exceptions\UnexpectedResultException.cs" />
<Compile Include="Extensions\HexExtension.cs" />
<Compile Include="Logging\IOBDLogger.cs" />
<Compile Include="Logging\OBDLogLevel.cs" />
<Compile Include="OBDData\20-3F\AbsoluteBarometricPressure.cs" />
<Compile Include="OBDData\40-5F\AbsoluteEvapSystemVaporPressure.cs" />
<Compile Include="OBDData\40-5F\AbsoluteLoadValue.cs" />

View File

@ -1,5 +1,5 @@
using System;
using System.IO;
using OBD.NET.Extensions;
namespace OBD.NET.OBDData
{
@ -7,7 +7,7 @@ namespace OBD.NET.OBDData
{
#region Properties & Fields
public int PID { get; }
public byte PID { get; }
private int _length;
private byte[] _rawData;
@ -34,13 +34,13 @@ namespace OBD.NET.OBDData
#region Constructors
public AbstractOBDData(int pid, int length)
public AbstractOBDData(byte pid, int length)
{
this.PID = pid;
this._length = length;
}
public AbstractOBDData(int pid, int length, byte[] rawData)
public AbstractOBDData(byte pid, int length, byte[] rawData)
: this(pid, length)
{
this.RawData = rawData;
@ -50,13 +50,16 @@ namespace OBD.NET.OBDData
#region Methods
public void Read(Stream stream)
public void Load(string data)
{
try
{
if (((data.Length % 2) == 1) || ((data.Length / 2) != _length))
throw new ArgumentException("The provided data is not valid", nameof(data));
_rawData = new byte[_length];
if (stream.Read(_rawData, 0, _length) != _length)
throw new InvalidDataException("Couldn't read enough bytes from the stream");
for (int i = 0; i < _length; ++i)
_rawData[i] = (byte)((data[i << 1].GetHexVal() << 4) + (data[(i << 1) + 1].GetHexVal()));
}
catch
{

View File

@ -1,11 +1,9 @@
using System.IO;
namespace OBD.NET.OBDData
namespace OBD.NET.OBDData
{
public interface IOBDData
{
int PID { get; }
byte PID { get; }
void Read(Stream stream);
void Load(string data);
}
}