shiny-obd

star 2

Generate code using Shiny.Obd, an OBD-II vehicle communication library for .NET with command-object pattern, adapter auto-detection, and BLE transport

shinyorg By shinyorg schedule Updated 6/6/2026

name: shiny-obd description: Generate code using Shiny.Obd, an OBD-II vehicle communication library for .NET with command-object pattern, adapter auto-detection, and BLE transport auto_invoke: true triggers:

  • "obd"
  • "obd-ii"
  • "obd2"
  • "vehicle diagnostics"
  • "elm327"
  • "obdlink"
  • "IObdCommand"
  • "IObdConnection"
  • "IObdTransport"
  • "IObdDeviceScanner"
  • "ObdDiscoveredDevice"
  • "BleObdDeviceScanner"
  • "ObdCommand"
  • "ObdConnection"
  • "StandardCommands"
  • "vehicle speed"
  • "engine rpm"
  • "coolant temperature"
  • "throttle position"
  • "vin command"
  • "obd ble"
  • "BleObdTransport"
  • "obd adapter"
  • "obd scan"
  • "device scanner"
  • "adapter profile"
  • "Shiny.Obd"
  • "AddShinyObdBluetoothLE"

Shiny.Obd Skill

Triggers

  • obd
  • obd-ii
  • obd2
  • vehicle diagnostics
  • elm327
  • obdlink
  • IObdCommand
  • IObdConnection
  • IObdTransport
  • IObdDeviceScanner
  • ObdDiscoveredDevice
  • BleObdDeviceScanner
  • ObdCommand
  • ObdConnection
  • StandardCommands
  • vehicle speed
  • engine rpm
  • coolant temperature
  • throttle position
  • vin command
  • obd ble
  • BleObdTransport
  • obd adapter
  • obd scan
  • device scanner
  • adapter profile
  • Shiny.Obd
  • AddShinyObdBluetoothLE

You are an expert in Shiny.Obd, a .NET library for communicating with vehicles through OBD-II adapters. It uses a command-object pattern with generic return types, pluggable transports (BLE first), and adapter auto-detection for ELM327 and OBDLink (STN) adapters.

When to Use This Skill

Invoke this skill when the user wants to:

  • Read vehicle data (speed, RPM, coolant temp, VIN, etc.) through OBD-II
  • Create custom OBD commands with typed return values
  • Connect to an OBD-II adapter over Bluetooth LE
  • Scan for / discover available OBD adapters
  • Configure ELM327 or OBDLink adapter initialization
  • Implement a custom transport (WiFi, USB) for OBD communication
  • Send raw AT commands to an OBD adapter
  • Handle OBD response parsing and error handling
  • Build a MAUI app with OBD integration

Library Overview

  • Repository: https://github.com/shinyorg/obd
  • Namespaces: Shiny.Obd, Shiny.Obd.Ble, Shiny.Obd.Commands
  • NuGet: Shiny.Obd (core), Shiny.Obd.Ble (BLE transport)
  • Targets: net10.0 (core), net10.0 (BLE)

Core Types

IObdCommand — Command interface

Every OBD command implements this. T is the parsed result type.

public interface IObdCommand<T>
{
    string RawCommand { get; }
    T Parse(byte[] data);
}

ObdCommand — Base class for standard Mode/PID commands

Validates mode+PID response header, strips it, and delegates to ParseData.

public abstract class ObdCommand<T> : IObdCommand<T>
{
    protected ObdCommand(byte mode, byte pid);
    public byte Mode { get; }
    public byte Pid { get; }
    public virtual string RawCommand { get; } // "{Mode:X2}{Pid:X2}"
    protected abstract T ParseData(byte[] data); // data after header
}

IObdConnection — Connection interface

public interface IObdConnection : IAsyncDisposable
{
    bool IsConnected { get; }
    Task Connect(CancellationToken ct = default);
    Task Disconnect();
    Task<T> Execute<T>(IObdCommand<T> command, CancellationToken ct = default);
    Task<string> SendRaw(string command, CancellationToken ct = default);
}

IObdTransport — Transport abstraction

public interface IObdTransport : IAsyncDisposable
{
    bool IsConnected { get; }
    Task Connect(CancellationToken ct = default);
    Task Disconnect();
    Task<string> Send(string command, CancellationToken ct = default);
}

IObdDeviceScanner — Device discovery

public interface IObdDeviceScanner
{
    Task Scan(Action<ObdDiscoveredDevice> onDeviceFound, CancellationToken ct = default);
}

Cancel the token to stop scanning. Each discovered device invokes the callback.

ObdDiscoveredDevice — Discovered adapter

public class ObdDiscoveredDevice
{
    public string Name { get; }        // e.g. "OBDLink MX+"
    public string Id { get; }          // unique identifier (BLE UUID, IP, etc.)
    public object NativeDevice { get; } // IPeripheral for BLE, IPEndPoint for WiFi, etc.
}

ObdConnection — ELM327 protocol handler

Two constructors:

  • ObdConnection(IObdTransport transport) — auto-detects adapter via ATI
  • ObdConnection(IObdTransport transport, IObdAdapterProfile profile) — uses explicit profile, skips detection

Properties:

  • DetectedAdapterObdAdapterInfo? with RawIdentifier (string) and Type (ObdAdapterType enum: Unknown, Elm327, ObdLink). Null when explicit profile used.

Handles:

  • ELM327 hex response parsing (single-line and multi-frame CAN)
  • Error detection: "NO DATA", "UNABLE TO CONNECT", "BUS INIT: ...ERROR", "?"
  • Strips "SEARCHING..." and "BUS INIT" prefixes

IObdAdapterProfile — Adapter initialization

public interface IObdAdapterProfile
{
    string Name { get; }
    Task Initialize(IObdConnection connection, CancellationToken ct = default);
}

Built-in profiles:

  • Elm327AdapterProfile — ATZ, ATE0, ATL0, ATS1, ATH0, ATSP0
  • ObdLinkAdapterProfile — extends Elm327 with STFAC, ATCAF1

Standard Commands (StandardCommands static class)

Property Type Command Return Parse Formula
VehicleSpeed VehicleSpeedCommand 010D int (km/h) A
EngineRpm EngineRpmCommand 010C int (RPM) ((A*256)+B)/4
CoolantTemperature CoolantTemperatureCommand 0105 int (°C) A-40
ThrottlePosition ThrottlePositionCommand 0111 double (%) (A*100)/255
FuelLevel FuelLevelCommand 012F double (%) (A*100)/255
CalculatedEngineLoad CalculatedEngineLoadCommand 0104 double (%) (A*100)/255
IntakeAirTemperature IntakeAirTemperatureCommand 010F int (°C) A-40
RuntimeSinceStart RuntimeSinceStartCommand 011F TimeSpan (A*256)+B seconds
Vin VinCommand 0902 string skip count byte, ASCII decode

BLE Transport (Shiny.Obd.Ble)

BleObdConfiguration

public class BleObdConfiguration
{
    public string ServiceUuid { get; set; } = "FFF0";
    public string ReadCharacteristicUuid { get; set; } = "FFF1";
    public string WriteCharacteristicUuid { get; set; } = "FFF2";
    public string? DeviceNameFilter { get; set; }
    public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(10);
}

BleObdTransport

Three constructors:

  • BleObdTransport(IBleManager bleManager, BleObdConfiguration config) — scans for adapter
  • BleObdTransport(IPeripheral peripheral, BleObdConfiguration config) — uses pre-discovered peripheral
  • BleObdTransport(ObdDiscoveredDevice device, BleObdConfiguration config) — uses device from scanner

Uses Shiny.BluetoothLE v4 APIs:

  • ConnectAsync for task-based connection
  • NotifyCharacteristic for RX notifications
  • WriteCharacteristicAsync for TX writes
  • Collects notification bytes until > prompt, returns complete response

Code Generation Patterns

Creating a custom OBD command (standard PID)

public class BarometricPressureCommand : ObdCommand<int>
{
    public BarometricPressureCommand() : base(0x01, 0x33) { }
    protected override int ParseData(byte[] data) => data[0];
}

Creating a custom OBD command (non-standard)

public class ManufacturerCommand : IObdCommand<string>
{
    public string RawCommand => "2101";
    public string Parse(byte[] data) => BitConverter.ToString(data);
}

Full connection setup with BLE

var transport = new BleObdTransport(bleManager, new BleObdConfiguration
{
    DeviceNameFilter = "OBDLink"
});
var connection = new ObdConnection(transport);
await connection.Connect();

var speed = await connection.Execute(StandardCommands.VehicleSpeed);
var rpm = await connection.Execute(StandardCommands.EngineRpm);
var vin = await connection.Execute(StandardCommands.Vin);

Scanning for devices then connecting

var scanner = new BleObdDeviceScanner(bleManager);
var cts = new CancellationTokenSource();
ObdDiscoveredDevice? selected = null;

await scanner.Scan(device =>
{
    selected = device;
    cts.Cancel(); // stop after first device
}, cts.Token);

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

MAUI DI registration

// In MauiProgram.cs
builder.Services.AddBluetoothLE();  // Shiny BLE v4 — namespace: Shiny
builder.Services.AddShinyObdBluetoothLE(new BleObdConfiguration
{
    DeviceNameFilter = "OBD"
});

AddShinyObdBluetoothLE registers BleObdConfiguration and IObdDeviceScanner (BleObdDeviceScanner). Call AddBluetoothLE() separately for platform BLE support.

Explicit adapter profile

var connection = new ObdConnection(transport, new ObdLinkAdapterProfile());
await connection.Connect();

Custom adapter profile

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

Custom transport implementation

public class WifiObdTransport : IObdTransport
{
    public bool IsConnected { get; private set; }
    public async Task Connect(CancellationToken ct = default) { /* TCP connect */ }
    public Task Disconnect() { /* close socket */ }
    public async Task<string> Send(string command, CancellationToken ct = default)
    {
        // Write command, read until '>' prompt, return response without '>'
    }
    public ValueTask DisposeAsync() { /* cleanup */ }
}

Important Notes

  • All async methods do NOT use the Async suffix (e.g. Connect, Execute, SendRaw).
  • ObdCommand<T>.ParseData receives bytes AFTER the 2-byte mode+PID header is stripped.
  • IObdCommand<T>.Parse receives ALL response bytes including mode+PID header.
  • ObdConnection appends \r to all commands sent via SendRaw before passing to transport.
  • BLE transport uses SemaphoreSlim to serialize commands (one at a time).
  • ELM327 response parser handles both single-line ("41 0D 50") and multi-frame CAN ("0: 49 02 01 57 42\r1: 41 30...") formats.
  • BleObdDeviceScanner deduplicates by peripheral UUID — each device is reported once.
  • For MAUI, register BLE with builder.Services.AddBluetoothLE() (namespace Shiny, no UseShiny needed in v4), then call builder.Services.AddShinyObdBluetoothLE() to register OBD BLE services.
  • AddShinyObdBluetoothLE registers BleObdConfiguration and IObdDeviceScanner (BleObdDeviceScanner).
  • A full MAUI sample app exists in samples/Sample.Maui/ with scan → select → dashboard flow.
Install via CLI
npx skills add https://github.com/shinyorg/skills --skill shiny-obd
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator