elm327-obd2

Node.js/TypeScript library for ELM327 OBD2 adapters over USB, Bluetooth and WiFi


Keywords
obd2, obd, obdii, elm327, bluetooth, serial, wifi, vehicle, diagnostics, automotive, car, js-obd2, node-elm327, node-obd, node-obd2, nodejs-elm327, obd-ii, obd2-node
License
MIT
Install
npm install elm327-obd2@1.0.2

Documentation

elm327

A comprehensive Node.js library for communicating with OBD2 (On-Board Diagnostics) systems in vehicles. Supports serial (USB), Bluetooth (BLE), and WiFi (TCP) connections to ELM327 adapters.

Features

  • Universal OBD2 Support: Compatible with all OBD2-compliant vehicles (1996+)
  • Multiple Connection Types: Serial (USB/RS232), Bluetooth (Web BLE), and WiFi (TCP)
  • Comprehensive Parameter Set: 23+ predefined OBD2 parameters with proper decoders
  • Real-time Monitoring: Event-driven data streaming capabilities
  • Adapter Management: Automatic adapter initialization and configuration
  • Cross-platform: Works on Windows, macOS, and Linux
  • TypeScript Support: Full TypeScript definitions included
  • Well Tested: Comprehensive test suite with 23+ tests passing
  • Clone Compatibility: Supports old ELM327 v1.5/v2.1 clones with compatibility modes
  • CAN Bus Monitoring: Capture raw CAN traffic with AT MA/AT MP commands
  • Flow Control: Full support for CAN flow control (AT FC SH/SD/SM/CFC)
  • Freeze Frame: Read snapshot data at moment of fault (Mode 02)
  • Dynamic PID Scanning: Automatically discover supported PIDs (00, 20, 40, 60...)
  • Exponential Backoff: Smart reconnection with increasing delays
  • NRC Parsing: Human-readable Negative Response Code messages
  • BLE Smart Discovery: Multiple UUID patterns for clone adapter support
  • File Logging: Persistent logging to file with RAW, PRETTY, or JSON formats

Installation

npm install elm327

or

yarn add elm327

Quick Start

Serial Connection (USB)

const { OBD2Client, listSerialPorts } = require('elm327');

async function main() {
  // List available serial ports
  const ports = await listSerialPorts();
  console.log('Available ports:', ports);

  // Create client
  const client = new OBD2Client({
    type: 'serial',
    port: '/dev/ttyUSB0', // or 'COM3' on Windows
    baudRate: 38400,
  });

  // Connect and initialize
  await client.connect();

  // Read some basic parameters
  const rpm = await client.getRPM();
  const speed = await client.getSpeed();
  const temp = await client.getCoolantTemperature();

  console.log(`RPM: ${rpm}`);
  console.log(`Speed: ${speed} km/h`);
  console.log(`Coolant: ${temp}°C`);

  await client.disconnect();
}

main().catch(console.error);

TypeScript Example

import { OBD2Client, ConnectionConfig, OBD2Response } from 'elm327';

const config: ConnectionConfig = {
  type: 'serial',
  port: '/dev/ttyUSB0',
  baudRate: 38400,
  timeout: 5000,
};

const client = new OBD2Client(config);

client.on('connected', () => console.log('Connected!'));
client.on('response', (response: OBD2Response) => {
  console.log(`${response.command}: ${response.value} ${response.unit}`);
});

await client.connect();
const engineLoad = await client.getEngineLoad();

WiFi Connection

const { OBD2Client } = require('elm327');

const client = new OBD2Client({
  type: 'wifi',
  host: '192.168.0.10', // Default ELM327 WiFi adapter IP
  port: 35000, // Default ELM327 WiFi port
});

await client.connect();
const rpm = await client.getRPM();
console.log(`RPM: ${rpm}`);
await client.disconnect();

Bluetooth Connection (BLE)

import { OBD2Client, ConnectionConfig } from 'elm327';

const config: ConnectionConfig = {
  type: 'bluetooth',
  // Smart discovery will try multiple known UUIDs for clone support
  flowControl: {
    enabled: true,
    header: '0x7E0', // CAN ID for flow control
  },
};

const client = new OBD2Client(config);
await client.connect();

API Documentation

OBD2Client

Constructor

const client = new OBD2Client(config);

Config Options:

Option Type Required Default Description
type 'serial' | 'bluetooth' | 'wifi' Yes - Connection type
port string For serial - Serial port path (e.g., /dev/ttyUSB0, COM3)
address string For bluetooth - Bluetooth device address (optional for Web Bluetooth API)
host string For wifi 192.168.0.10 WiFi adapter IP address
port string | number For wifi 35000 WiFi adapter port
baudRate number No 38400 Serial baud rate
timeout number No 5000 Command timeout in milliseconds
lineEnding string No '\r' Line ending character
maxLines number No 0 Max log file lines (0 = unlimited). Oldest lines trimmed automatically
cloneCompatibility 'auto' | 'strict' | 'lenient' | 'minimal' No 'auto' Clone compatibility mode
flowControl object No - Flow control configuration

Clone Compatibility Modes

  • 'auto': Detect and adjust automatically (default)
  • 'strict': Full feature set, may fail on old clones
  • 'lenient': Skip unsupported commands, longer delays
  • 'minimal': Only essential commands (ATZ, ATE0, ATSP0)

Flow Control Configuration

flowControl: {
  enabled: true,
  header: '0x7E0', // CAN ID for flow control
  data: '0x30',    // Flow control data byte
  mode: 1,          // Flow control mode (optional)
}

Methods

Connection Management
await client.connect();                    // Connect to adapter and initialize
await client.disconnect();                 // Disconnect from adapter
await client.reset();                      // Reset adapter using ATZ (independent from reconnect)
client.isConnected();                      // Check connection status
client.getAdapterInfo();                   // Get adapter information (version, protocol, device)
Data Query Methods
// Generic query methods
await client.query(commandName);           // Query by command name (e.g., 'ENGINE_RPM')
await client.queryPid(pid);                // Query by PID string (e.g., '010C')
await client.queryMultiple([commands]);    // Query multiple parameters sequentially
await client.queryCommand(command);        // Query with a custom OBD2Command object

// Convenience methods
await client.getRPM();                    // Engine RPM
await client.getSpeed();                  // Vehicle speed (km/h)
await client.getCoolantTemperature();      // Coolant temperature (°C)
await client.getEngineLoad();              // Engine load (%)
await client.getFuelLevel();              // Fuel level (%)
await client.getThrottlePosition();       // Throttle position (%)

// Oxygen sensor methods (NEW!)
await client.query('O2S1_WR');            // O2 Sensor 1 Wide Range
await client.query('O2S2_WR');            // O2 Sensor 2 Wide Range
await client.query('O2S3_WR');            // O2 Sensor 3 Wide Range
await client.query('O2S4_WR');            // O2 Sensor 4 Wide Range
await client.query('O2S1_V');             // O2 Sensor 1 Voltage
await client.query('O2S2_V');             // O2 Sensor 2 Voltage
await client.query('O2S3_V');             // O2 Sensor 3 Voltage
await client.query('O2S4_V');             // O2 Sensor 4 Voltage
await client.query('O2S1_ST');            // O2 Sensor 1 Short Term Trim
Diagnostic Methods
await client.getDTCs();                   // Get Diagnostic Trouble Codes (Mode 03)
await client.clearDTCs();                 // Clear DTCs (Mode 04)
await client.getFreezeFrame(pid);          // Get freeze frame data for specific PID (Mode 02)
await client.getAllFreezeFrames();         // Get all available freeze frame data
await client.getSupportedPids();           // Dynamically scan all supported PIDs
await client.scanPids(mode, start, end);  // Scan PIDs in range with progress events

// Vehicle Information
await client.getVIN();                    // Get Vehicle Identification Number (Mode 09)
await client.getCalibrationID();           // Get calibration ID
await client.getVehicleInfo();             // Get all vehicle info
await client.getProtocolInfo();            // Get protocol information

// Diagnostic Requests (OpenXC-inspired)
await client.sendDiagnosticRequest(config); // Send custom diagnostic request
CAN Bus Monitoring
await client.startCANMonitor();            // Start monitoring all CAN traffic (AT MA)
await client.stopCANMonitor();             // Stop CAN monitoring
await client.startCANMonitorWithFilter('7E8'); // Monitor only frames matching CAN ID (AT CF + AT CM)
// Note: Use Flow Control configuration for controlled CAN communication
Polling Methods
client.setPollInterval(ms);                // Set global poll interval (default: 1000ms)
client.addPoller(commandName);             // Add command to polling list
client.startPolling(intervalMs);           // Start polling all added commands
client.stopPolling();                      // Stop polling
client.setAutoReconnect(enabled);         // Enable/disable auto-reconnect with exponential backoff
Logging Methods

The logger is disabled by default. Enable it to write logs to a file:

import { OBD2Client, LogFormat, LogLevel } from 'elm327';

const client = new OBD2Client(config);

// Enable logging with PRETTY format (NestJS-style)
client.enableLogger({
  filePath: './obd2.log',
  format: LogFormat.PRETTY,
});

// Or use RAW format (only the raw ELM327 response)
client.enableLogger({
  filePath: './obd2-raw.log',
  format: LogFormat.RAW,
});

// Or use JSON format (one JSON object per line)
client.enableLogger({
  filePath: './obd2.json',
  format: LogFormat.JSON,
});

// Filter by specific log levels
client.enableLogger({
  filePath: './obd2-errors.log',
  levels: [LogLevel.ERROR, LogLevel.WARN],
});

// Limit file size (keeps only the newest 2000 lines, trims oldest)
client.enableLogger({
  filePath: './obd2.log',
  format: LogFormat.JSON,
  maxLines: 2000,
});

// Change maxLines at runtime
client.setLoggerMaxLines(5000);

// Change format at runtime
client.setLoggerFormat(LogFormat.JSON);

// Change levels at runtime
client.setLoggerLevels([LogLevel.INFO, LogLevel.ERROR]);

// Disable logging
client.disableLogger();
Log Formats
Format Description Example
LogFormat.RAW Raw ELM327 response only 41 0C 1A F8
LogFormat.PRETTY NestJS-style formatted log [2026-05-07 10:30:45] CMD [OBD2Client] 010C {"name":"ENGINE_RPM"}
LogFormat.JSON One JSON object per line {"timestamp":"2026-05-07T10:30:45.000Z","level":"CMD","context":"OBD2Client","message":"010C","name":"ENGINE_RPM"}
Log Levels
Level Description
LogLevel.INFO General information (connect, disconnect, etc.)
LogLevel.DEBUG Debug information
LogLevel.WARN Warning messages
LogLevel.ERROR Error messages
LogLevel.RAW_DATA Raw data from adapter
LogLevel.COMMAND Commands sent to adapter
LogLevel.RESPONSE Responses received from adapter

Events

client.on('connected', () => {});           // Connection established
client.on('disconnected', () => {});       // Connection lost
client.on('ready', (adapterInfo) => {});   // Adapter initialized successfully
client.on('response', (response) => {});   // Decoded data received
client.on('error', (error) => {});         // Error occurred
client.on('rawData', (data) => {});        // Raw data from adapter
client.on('pollData', (data) => {});       // Polling data received
client.on('pollError', (command, error) => {}); // Polling error
client.on('pollComplete', (results) => {}); // Polling complete
client.on('scanProgress', (data) => {});   // PID scan progress updates
client.on('scanComplete', (data) => {});  // PID scan completed
client.on('reconnecting', () => {});       // Reconnection in progress
client.on('reconnected', () => {});      // Reconnection successful
client.on('canData', (data) => {});        // CAN frame received (monitor mode)
client.on('adapterReset', () => {});      // Adapter reset completed
client.on('debug', (data) => {});          // Debug information

Available Commands

Command Description Unit
ENGINE_LOAD Calculated engine load %
COOLANT_TEMP Engine coolant temperature °C
FUEL_PRESSURE Fuel pressure kPa
INTAKE_PRESSURE Intake manifold absolute pressure kPa
ENGINE_RPM Engine speed rpm
VEHICLE_SPEED Vehicle speed km/h
TIMING_ADVANCE Timing advance °
INTAKE_TEMP Intake air temperature °C
MAF_RATE Mass air flow sensor air flow rate g/s
THROTTLE_POS Absolute throttle position %
OBD_STANDARDS OBD standards compliance -
RUNTIME Run time since engine start seconds
FUEL_LEVEL Fuel tank level input %
BAROMETRIC_PRESSURE Absolute barometric pressure kPa
AMBIENT_TEMP Ambient air temperature °C
VIN Vehicle Identification Number -
O2S1_WR O2 Sensor 1 Wide Range -
O2S2_WR O2 Sensor 2 Wide Range -
O2S3_WR O2 Sensor 3 Wide Range -
O2S4_WR O2 Sensor 4 Wide Range -
O2S1_V O2 Sensor 1 Voltage V
O2S2_V O2 Sensor 2 Voltage V
O2S3_V O2 Sensor 3 Voltage V
O2S4_V O2 Sensor 4 Voltage V
O2S1_ST O2 Sensor 1 Short Term Trim %

Utility Functions

import { listSerialPorts, isBluetoothAvailable, getAllCommands, createOBD2Client } from 'elm327';

// List available serial ports
const ports = await listSerialPorts();

// Check if Bluetooth is available (browser only)
const btAvailable = await isBluetoothAvailable();

// Get all predefined OBD2 commands
const commands = getAllCommands();

// Create client with convenience function
const client = createOBD2Client(config);

Hardware Compatibility

Supported OBD2 Adapters

  • ELM327-based adapters (USB, Bluetooth, WiFi)
  • OBDLink adapters
  • UniCarScan adapters
  • Generic OBD2 interfaces

Tested Adapters

  • ELM327 USB
  • ELM327 Bluetooth
  • Vgate iCar Pro Bluetooth
  • BAFX Products Bluetooth OBD2
  • Generic ELM327 WiFi adapters
  • Old clones (v1.5/v2.1) with cloneCompatibility mode

Connection Types

Serial (USB/RS232)

  • Most reliable connection method
  • Typically uses /dev/ttyUSB0 on Linux, COM3 on Windows
  • Standard baud rates: 9600, 38400, 115200
  • For old clones: use cloneCompatibility: 'lenient' or 'minimal'

Bluetooth

  • In browsers: uses Web Bluetooth API (BLE only) - no address needed (uses UUID scan)
  • In Node.js: use SerialConnection with a paired device
  • Linux: rfcomm connect /dev/rfcomm0 <MAC> then use SerialConnection
  • macOS: use /dev/tty.* device after pairing
  • Smart Discovery: Automatically tries multiple known UUIDs for clone support
  • Note: For Web Bluetooth, address is optional (scans for devices automatically)

WiFi (TCP)

  • Connects over TCP/IP to WiFi ELM327 adapters
  • Default: 192.168.0.10:35000
  • Requires connecting to the adapter's WiFi network first

Examples

The library includes several examples in the examples/ directory:

Basic Usage

npm run build

# Auto-detect serial port:
npm run example:basic

# Or specify a port:
npm run example:basic -- /dev/ttyUSB0

Real-time Monitoring

# Real-time monitoring (port required):
npm run example:monitoring -- /dev/ttyUSB0

WiFi Connection

npm run example:wifi

New Examples (Added!)

Flow Control

npx ts-node examples/flow-control.ts /dev/ttyUSB0

Demonstrates CAN flow control (AT FC SH/SD/SM/CFC) for controlled communication.

CAN Bus Monitor

npx ts-node examples/can-monitor.ts /dev/ttyUSB0

Captures raw CAN traffic using AT MA (Monitor All) command.

CAN Bus Monitor with Filter

npx ts-node examples/can-monitor-with-filter.ts 7E8

Monitors only frames matching the specified CAN ID (uses AT CF + AT CM commands).

PID Scanner

npx ts-node examples/pid-scanner.ts /dev/ttyUSB0

Dynamically scans all supported PIDs with progress events.

Clone Compatibility

npx ts-node examples/clone-compat.ts /dev/ttyUSB0 lenient

Demonstrates clone compatibility modes for old ELM327 v1.5/v2.1 adapters.

Reset Adapter

npx ts-node examples/reset-adapter.ts /dev/ttyUSB0

Shows how to reset the adapter independently using ATZ without reconnecting.

Bluetooth BLE

npx ts-node examples/bluetooth-ble.ts

Demonstrates BLE smart discovery with multiple UUID patterns.

Freeze Frame

npx ts-node examples/freeze-frame.ts /dev/ttyUSB0

Reads freeze frame data (Mode 02) captured at the moment of fault.

Real-time Monitoring Example

const { OBD2Client } = require('elm327');

const client = new OBD2Client({
  type: 'serial',
  port: '/dev/ttyUSB0',
});

await client.connect();

// Monitor key parameters every 2 seconds
setInterval(async () => {
  try {
    const data = await client.queryMultiple([
      'ENGINE_RPM',
      'VEHICLE_SPEED',
      'COOLANT_TEMP',
      'ENGINE_LOAD',
    ]);

    console.log('Vehicle Data:', data);
  } catch (error) {
    console.error('Monitoring error:', error.message);
  }
}, 2000);

Error Handling

const { OBD2Client, ConnectionError, TimeoutError, ProtocolError } = require('elm327');

const client = new OBD2Client(config);

client.on('error', (error) => {
  if (error.code === 'CONNECTION_ERROR') {
    console.log('Connection lost, attempting to reconnect...');
  } else if (error.code === 'TIMEOUT_ERROR') {
    console.log('Command timed out');
  } else if (error.code === 'PROTOCOL_ERROR') {
    console.log('Protocol error:', error.message);
  }
});

try {
  await client.connect();
} catch (error) {
  console.error('Failed to connect:', error.message);
}

Negative Response Codes (NRC)

The library now parses NRC (Negative Response Codes) from diagnostic requests and provides human-readable messages:

const response = await client.sendDiagnosticRequest({
  mode: DiagnosticMode.CURRENT_DATA,
  pid: 0x0d,
});

if (!response.success && response.negativeResponseCode) {
  console.log(`NRC: ${response.negativeResponseMessage}`);
  // Example: "Request Out of Range (0x31)"
}

Custom Command Decoder

import { OBD2Client, OBD2Command } from 'elm327';

// Define a custom command
const customCommand: OBD2Command = {
  name: 'CUSTOM_PARAM',
  pid: '0150',
  description: 'Custom parameter',
  decoder: (data: string) => {
    const value = parseInt(data.substring(4, 6), 16);
    return value * 0.5;
  },
  unit: 'custom_unit',
};

const client = new OBD2Client(config);
await client.connect();

const response = await client.queryCommand(customCommand);
console.log(`Custom param: ${response.value} ${response.unit}`);

Logging to File

import { OBD2Client, LogFormat, LogLevel } from 'elm327';

const client = new OBD2Client({
  type: 'serial',
  port: '/dev/ttyUSB0',
});

// Enable JSON logging
client.enableLogger({
  filePath: './logs/obd2-session.json',
  format: LogFormat.JSON,
});

await client.connect();

// All commands and responses will be logged
const rpm = await client.getRPM();
const dtcs = await client.getDTCs();

await client.disconnect();
// Logger is automatically disabled on disconnect

Troubleshooting

Common Issues

Permission Denied (Linux/macOS)

sudo chmod 666 /dev/ttyUSB0
# or add user to dialout group
sudo usermod -a -G dialout $USER

Port Not Found

  • Check if adapter is properly connected
  • Use listSerialPorts() to find available ports
  • Try different USB ports

Adapter Not Responding

  • Verify adapter compatibility (ELM327 recommended)
  • Check baud rate settings
  • Ensure vehicle is running or ignition is on
  • For old clones, try cloneCompatibility: 'lenient' with longer timeout (10000ms+)

Bluetooth Connection Issues

  • Pair adapter with system first
  • Check if adapter is already connected to another device
  • Verify Bluetooth permissions
  • Try BLE smart discovery (multiple UUIDs supported)

WiFi Connection Issues

  • Ensure you are connected to the adapter's WiFi network
  • Verify IP address (default: 192.168.0.10) and port (default: 35000)
  • Check firewall settings

BUFFER FULL on Cheap Clones

  • Use sequential queries instead of parallel (queryMultiple is sequential by design)
  • Increase timeout values
  • Use cloneCompatibility: 'minimal' mode

Debug Mode

Enable debug logging using the events:

const client = new OBD2Client(config);

client.on('rawData', (data) => {
  console.log('Raw data:', data);
});

client.on('debug', (data) => {
  console.log('Debug:', data.message);
});

client.on('error', (error) => {
  console.error('Debug error:', error);
});

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

See CHANGELOG.md for version history and changes.

Development Setup

git clone https://github.com/edu-amr/elm327.git
cd elm327
npm install
npm run build
npm test

Running Examples

npm run build

# Auto-detect serial port:
npm run example:basic

# Or specify a port:
npm run example:basic -- /dev/ttyUSB0

# Real-time monitoring (port required):
npm run example:monitoring -- /dev/ttyUSB0

# New examples:
npx ts-node examples/flow-control.ts /dev/ttyUSB0
npx ts-node examples/can-monitor.ts /dev/ttyUSB0
npx ts-node examples/pid-scanner.ts /dev/ttyUSB0
npx ts-node examples/clone-compat.ts /dev/ttyUSB0 lenient
npx ts-node examples/reset-adapter.ts /dev/ttyUSB0
npx ts-node examples/freeze-frame.ts /dev/ttyUSB0

Git Hooks

This project uses Husky for git hooks:

  • pre-commit: Runs typecheck (tsc --noEmit) on staged .ts files
  • commit-msg: Validates commit messages using commitlint (conventional commits)
# Hooks are automatically installed after npm install
# To skip hooks (not recommended): git commit --no-verify

License

This project is licensed under the MIT License — see the LICENSE file for details.

Acknowledgments

  • Based on the ELM327 command set
  • Inspired by the OBD2 protocol specifications
  • Thanks to the automotive diagnostics community
  • OpenXC project for diagnostic request inspiration

Related Projects

Support

For issues, questions, or contributions, please visit the GitHub repository.