UNPKG

elm327

Version:

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

763 lines (588 loc) 22.9 kB
# 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).