# 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

```bash
npm install elm327
```

or

```bash
yarn add elm327
```

## Quick Start

### Serial Connection (USB)

```javascript
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

```typescript
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

```javascript
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)

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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

```typescript
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

```typescript
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
```bash
npm run build

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

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

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

### WiFi Connection
```bash
npm run example:wifi
```

### New Examples (Added!)

#### Flow Control
```bash
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

```bash
npx ts-node examples/can-monitor.ts /dev/ttyUSB0
```
Captures raw CAN traffic using AT MA (Monitor All) command.

#### CAN Bus Monitor with Filter

```bash
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
```bash
npx ts-node examples/pid-scanner.ts /dev/ttyUSB0
```
Dynamically scans all supported PIDs with progress events.

#### Clone Compatibility
```bash
npx ts-node examples/clone-compat.ts /dev/ttyUSB0 lenient
```
Demonstrates clone compatibility modes for old ELM327 v1.5/v2.1 adapters.

#### Reset Adapter
```bash
npx ts-node examples/reset-adapter.ts /dev/ttyUSB0
```
Shows how to reset the adapter independently using ATZ without reconnecting.

#### Bluetooth BLE
```bash
npx ts-node examples/bluetooth-ble.ts
```
Demonstrates BLE smart discovery with multiple UUID patterns.

#### Freeze Frame
```bash
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

```javascript
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

```javascript
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:

```typescript
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

```typescript
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

```typescript
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)
```bash
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:

```typescript
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](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.

See [CHANGELOG.md](CHANGELOG.md) for version history and changes.

### Development Setup

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

### Running Examples

```bash
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)

```bash
# 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](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

- [python-OBD](https://github.com/python-obd/python-OBD) — Python OBD2 library
- [node-obd](https://github.com/andilabs/node-obd) — Another Node.js OBD library
- [elm327-emulator](https://github.com/Ircama/elt) — ELM327 emulator for testing

## Support

For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/edu-amr/elm327).
