Shiny.Obd.Ble 1.0.0-beta-0006

Prefix Reserved
This is a prerelease version of Shiny.Obd.Ble.
dotnet add package Shiny.Obd.Ble --version 1.0.0-beta-0006
                    
NuGet\Install-Package Shiny.Obd.Ble -Version 1.0.0-beta-0006
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shiny.Obd.Ble" Version="1.0.0-beta-0006" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shiny.Obd.Ble" Version="1.0.0-beta-0006" />
                    
Directory.Packages.props
<PackageReference Include="Shiny.Obd.Ble" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shiny.Obd.Ble --version 1.0.0-beta-0006
                    
#r "nuget: Shiny.Obd.Ble, 1.0.0-beta-0006"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shiny.Obd.Ble@1.0.0-beta-0006
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shiny.Obd.Ble&version=1.0.0-beta-0006&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Shiny.Obd.Ble&version=1.0.0-beta-0006&prerelease
                    
Install as a Cake Tool

Shiny.Obd

NuGet

A .NET library for communicating with vehicles through OBD-II (On-Board Diagnostics) adapters. Supports ELM327 and OBDLink (STN) adapters over pluggable transports, starting with Bluetooth LE.

Documentation

Features

  • Command-object pattern — OBD commands are objects, not methods. Pass built-in commands or create your own for custom PIDs.
  • Generic return types — each command declares its return type (int, double, string, TimeSpan, etc.) with compile-time safety.
  • Pluggable transportsIObdTransport abstracts the communication channel. Ship with BLE; add WiFi or USB later.
  • Adapter auto-detection — detects ELM327 vs OBDLink (STN) adapters via ATI and runs the appropriate initialization sequence.
  • Adapter profilesIObdAdapterProfile lets you define custom init sequences. Built-in profiles for ELM327 and OBDLink.
  • Task-based async — fully async/await throughout, no Reactive Extensions required in consuming code.
  • 9 standard commands included — speed, RPM, coolant temp, throttle, fuel level, engine load, intake air temp, runtime, and VIN.

Projects

Package Target Description
Shiny.Obd net10.0 Core library — commands, connection, transport abstraction
Shiny.Obd.Ble net10.0 BLE transport using Shiny.BluetoothLE

Quick Start

1. Install packages


<PackageReference Include="Shiny.Obd" />


<PackageReference Include="Shiny.Obd.Ble" />

2. Connect and query

using Shiny.Obd;
using Shiny.Obd.Ble;
using Shiny.Obd.Commands;

// Create BLE transport (scans for adapter automatically)
var transport = new BleObdTransport(bleManager, new BleObdConfiguration
{
    DeviceNameFilter = "OBDLink" // optional: filter by adapter name
});

// Create connection (auto-detects adapter type)
var connection = new ObdConnection(transport);
await connection.Connect();

// Execute commands
var speed = await connection.Execute(StandardCommands.VehicleSpeed);    // int (km/h)
var rpm = await connection.Execute(StandardCommands.EngineRpm);        // int
var vin = await connection.Execute(StandardCommands.Vin);              // string

Console.WriteLine($"Speed: {speed} km/h, RPM: {rpm}, VIN: {vin}");

Architecture

┌─────────────────────────────────────────────────┐
│                 Your App                        │
│   await connection.Execute(StandardCommands.*)  │
└──────────────────────┬──────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────┐
│              ObdConnection                      │
│  • Adapter detection (ATI probe)                │
│  • Profile-based initialization                 │
│  • ELM327 response parsing (hex → bytes)        │
│  • Error handling (NO DATA, UNABLE TO CONNECT)  │
└──────────────────────┬──────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────┐
│             IObdTransport                       │
│  Pluggable transport layer                      │
│  ┌──────────────┐  ┌────────┐  ┌─────────┐     │
│  │ BleObdTransport│  │ WiFi  │  │  USB    │     │
│  │ (Shiny BLE)  │  │(future)│  │(future) │     │
│  └──────────────┘  └────────┘  └─────────┘     │
└─────────────────────────────────────────────────┘

Commands

Standard Commands

All standard commands are available as singletons via StandardCommands:

Command Mode PID Return Type Unit
VehicleSpeed 01 0D int km/h
EngineRpm 01 0C int RPM
CoolantTemperature 01 05 int °C
ThrottlePosition 01 11 double %
FuelLevel 01 2F double %
CalculatedEngineLoad 01 04 double %
IntakeAirTemperature 01 0F int °C
RuntimeSinceStart 01 1F TimeSpan
Vin 09 02 string
var speed = await connection.Execute(StandardCommands.VehicleSpeed);
var rpm = await connection.Execute(StandardCommands.EngineRpm);
var coolant = await connection.Execute(StandardCommands.CoolantTemperature);
var throttle = await connection.Execute(StandardCommands.ThrottlePosition);
var fuel = await connection.Execute(StandardCommands.FuelLevel);
var load = await connection.Execute(StandardCommands.CalculatedEngineLoad);
var intakeTemp = await connection.Execute(StandardCommands.IntakeAirTemperature);
var runtime = await connection.Execute(StandardCommands.RuntimeSinceStart);
var vin = await connection.Execute(StandardCommands.Vin);

Custom Commands

Implement IObdCommand<T> directly for full control, or extend ObdCommand<T> for standard Mode/PID commands.

Extending ObdCommand<T> (standard Mode/PID pattern)
// Barometric pressure (Mode 01, PID 0x33) — single byte, value in kPa
public class BarometricPressureCommand : ObdCommand<int>
{
    public BarometricPressureCommand() : base(0x01, 0x33) { }
    protected override int ParseData(byte[] data) => data[0];
}

// Usage
var pressure = await connection.Execute(new BarometricPressureCommand());

The ObdCommand<T> base class automatically:

  • Generates RawCommand from Mode + PID (e.g. "0133")
  • Validates the response header (mode echo + PID match)
  • Strips the 2-byte header before calling your ParseData
Implementing IObdCommand<T> (full control)
// Completely custom command with non-standard response format
public class CustomDiagnosticCommand : IObdCommand<string>
{
    public string RawCommand => "2101";  // manufacturer-specific

    public string Parse(byte[] data)
    {
        // You receive ALL response bytes — parse however you need
        return BitConverter.ToString(data);
    }
}

Adapter Profiles

Auto-Detection (default)

When you create ObdConnection(transport) without a profile, Connect() sends ATI to identify the adapter:

ATI Response Contains Detected As Profile Used
"ELM327" ObdAdapterType.Elm327 Elm327AdapterProfile
"STN" ObdAdapterType.ObdLink ObdLinkAdapterProfile
Anything else ObdAdapterType.Unknown Elm327AdapterProfile
var connection = new ObdConnection(transport);
await connection.Connect();

// Check what was detected
Console.WriteLine(connection.DetectedAdapter?.RawIdentifier); // "ELM327 v1.5"
Console.WriteLine(connection.DetectedAdapter?.Type);          // Elm327

Explicit Profile

Skip detection by providing a profile:

var connection = new ObdConnection(transport, new ObdLinkAdapterProfile());
await connection.Connect(); // uses OBDLink init, no ATI probe

Built-in Profiles

Elm327AdapterProfile — Standard initialization:

ATZ    → Reset
ATE0   → Echo off
ATL0   → Linefeed off
ATS1   → Spaces on
ATH0   → Headers off
ATSP0  → Auto protocol

ObdLinkAdapterProfile — Extends ELM327 with STN-specific optimizations:

(all ELM327 commands above)
STFAC  → Reset to factory defaults
ATCAF1 → CAN auto formatting on

Custom Profiles

public class MyAdapterProfile : IObdAdapterProfile
{
    public string Name => "MyAdapter";

    public async Task Initialize(IObdConnection connection, CancellationToken ct = default)
    {
        await connection.SendRaw("ATZ", ct);
        await Task.Delay(500, ct);
        await connection.SendRaw("ATE0", ct);
        await connection.SendRaw("ATSP6", ct);  // force CAN 11-bit 500kbaud
        // ... any adapter-specific commands
    }
}

Device Discovery

Before connecting, scan for available OBD adapters with IObdDeviceScanner:

using Shiny.Obd;
using Shiny.Obd.Ble;

var scanner = new BleObdDeviceScanner(bleManager, new BleObdConfiguration
{
    DeviceNameFilter = "OBD"
});

var cts = new CancellationTokenSource();
await scanner.Scan(device =>
{
    Console.WriteLine($"Found: {device.Name} ({device.Id})");
    // device.NativeDevice is IPeripheral for BLE
}, cts.Token);

Each discovered device is an ObdDiscoveredDevice with Name, Id, and NativeDevice. Pass it directly to BleObdTransport:

var transport = new BleObdTransport(device, new BleObdConfiguration());
var connection = new ObdConnection(transport);
await connection.Connect();

DI Registration

Register BLE OBD services in one call:

using Shiny;

builder.Services.AddBluetoothLE(); // Shiny BLE platform registration
builder.Services.AddShinyObdBluetoothLE(new BleObdConfiguration
{
    DeviceNameFilter = "OBD"
});

AddShinyObdBluetoothLE registers BleObdConfiguration and IObdDeviceScanner (BleObdDeviceScanner). You must also call AddBluetoothLE() for platform BLE support.


## BLE Transport

### Configuration

```csharp
var config = new BleObdConfiguration
{
    // GATT UUIDs — defaults work for most ELM327 BLE clones
    ServiceUuid = "FFF0",
    ReadCharacteristicUuid = "FFF1",    // notifications (RX from adapter)
    WriteCharacteristicUuid = "FFF2",   // write commands (TX to adapter)

    // Optional: filter scan results by device name
    DeviceNameFilter = "OBDLink",

    // Timeout for a single command response
    CommandTimeout = TimeSpan.FromSeconds(10)
};

Using a Discovered Device

Use BleObdDeviceScanner to find adapters, then pass the selected device directly:

ObdDiscoveredDevice device = /* from scanner */;
var transport = new BleObdTransport(device, new BleObdConfiguration());

Using a Pre-Scanned Peripheral

If you've already discovered the BLE peripheral (e.g. from a scan UI):

IPeripheral peripheral = /* from your scan */;
var transport = new BleObdTransport(peripheral, new BleObdConfiguration());

Auto-Scan

Let the transport scan for the first matching device:

IBleManager bleManager = /* from DI */;
var transport = new BleObdTransport(bleManager, new BleObdConfiguration
{
    DeviceNameFilter = "OBDII"
});

Raw Commands

Send arbitrary AT or OBD commands:

// AT commands
var version = await connection.SendRaw("ATI");      // "ELM327 v1.5"
var protocol = await connection.SendRaw("ATDPN");   // current protocol number
var voltage = await connection.SendRaw("ATRV");     // battery voltage

// Raw OBD hex commands
var response = await connection.SendRaw("0100");    // supported PIDs [01-20]

Error Handling

ObdException is thrown for adapter-level errors:

try
{
    var speed = await connection.Execute(StandardCommands.VehicleSpeed);
}
catch (ObdException ex) when (ex.Message.Contains("No data"))
{
    // Vehicle not responding to this PID (engine off, unsupported PID, etc.)
}
catch (ObdException ex) when (ex.Message.Contains("Unable to connect"))
{
    // Adapter can't reach the vehicle ECU
}

The ObdCommand<T> base class also validates response headers and throws ObdException on mode/PID mismatches.

Implementing a Custom Transport

Implement IObdTransport to add WiFi, USB, or any other communication channel:

public class WifiObdTransport : IObdTransport
{
    public bool IsConnected { get; private set; }

    public async Task Connect(CancellationToken ct = default)
    {
        // Connect to ELM327 WiFi adapter (typically 192.168.0.10:35000)
    }

    public Task Disconnect() { /* ... */ }

    public async Task<string> Send(string command, CancellationToken ct = default)
    {
        // Send command, collect response until '>' prompt, return the text
    }

    public ValueTask DisposeAsync() { /* ... */ }
}

The Send method must:

  1. Write the command string to the adapter
  2. Read the response until the > prompt character
  3. Return the response text (without the > prompt)
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta-0006 41 3/2/2026
1.0.0-beta-0005 40 3/2/2026