ELMduino - Library for Car Hacking

If you want to start car hacking through your OBD-II port using a bluetooth scanner - ELMduino is for you.

ELMduino download and GitHub Link

This library is also installable via the Arduino IDE’s Libraries Manager.

You can use this library to interface with OBD-II scanners such as this common one and will work with any car that has an OBD-II port:

And you can use it to query any pieces of data as specified by the OBD-II public PIDs. Here is a list of PIDs supported. Some notables include speed, rpm, MAF pressure, etc.

Below is an example code that will print rpm data from a (running) car.

#include <SoftwareSerial.h>
#include "ELMduino.h"


SoftwareSerial mySerial(2, 3); // RX, TX
#define ELM_PORT mySerial


ELM327 myELM327;


uint32_t rpm = 0;


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);
  ELM_PORT.begin(115200);

  Serial.println("Attempting to connect to ELM327...");

  if (!myELM327.begin(ELM_PORT))
  {
    Serial.println("Couldn't connect to OBD scanner");
    while (1);
  }

  Serial.println("Connected to ELM327");
}


void loop()
{
  float tempRPM = myELM327.rpm();

  if (myELM327.status == ELM_SUCCESS)
  {
    rpm = (uint32_t)tempRPM;
    Serial.print("RPM: "); Serial.println(rpm);
  }
  else
  {
    Serial.print(F("\tERROR: "));
    Serial.println(myELM327.status);
    delay(100);
  }
}

Note that you will need to connect to the bluetooth scanner using an HC-05 or other bluetooth to UART converter.

Hi, can do PID2102 support? PID response example 2102 (A2 F1 11 61 2 A5 80 0 6F 0 0 0 D0 E8 D0 E8 0 0 0 0 0 0 0 0 0 0 0 0 0 1 7F 0 0 0 0 0 0 8B)

You can query any PID supported by the ELM327. For custom queries, take a look at this example that prints soot level:

// modified test + my custom PIDs
// based purely on library

#include "BluetoothSerial.h"
#include "ELMduino.h"


#define ELM_PORT SerialBT
#define ESP_BLUETOOTH_NAME "ESP32"


BluetoothSerial SerialBT;
ELM327 myELM327;


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);
  ELM_PORT.begin(ESP_BLUETOOTH_NAME, true);

  Serial.println("Attempting to connect to ELM327...");

  if (!ELM_PORT.connect("V-LINK"))
  {
    Serial.println("Couldn't connect to OBD scanner - Phase 1");
    while (1);
  }

  if (!myELM327.begin(ELM_PORT))
  {
    Serial.println("Couldn't connect to OBD scanner - Phase 2");
    while (1);
  }
  
  Serial.println("Connected to ELM327");
  
  for (int a = 0; a < 10; ++a)
  {
    digitalWrite(LED_BUILTIN,HIGH); delay(100);
    digitalWrite(LED_BUILTIN,LOW); delay(100); 
  }
}


void loop()
{
  int32_t rpm = -1;
  int32_t soot = -1;
  int32_t burn = -1;
  float litersLeft = -1;

  /////////////////////////////////////////////////////// RPM
  float tempRPM = myELM327.rpm();
  
  Serial.print("Payload received for rpm: ");
  for (byte i = 0; i < PAYLOAD_LEN; i++)
    Serial.write(myELM327.payload[i]);
  Serial.println();
  
  if (myELM327.status == ELM_SUCCESS)
  {
    rpm = (int32_t)tempRPM;
    Serial.print("RPM: "); Serial.println(rpm);
  }
  else
      printError();

  /////////////////////////////////////////////////////// Soot
  if (myELM327.queryPID(34, 13162))
  {
    int32_t tempSoot = myELM327.findResponse();

    Serial.print("Payload received for soot: ");
    for (byte i = 0; i < PAYLOAD_LEN; i++)
      Serial.write(myELM327.payload[i]);
    Serial.println();
    
    if (myELM327.status == ELM_SUCCESS)
    {
      soot = tempSoot;
      Serial.print("Soot: "); Serial.println(soot);
    }
    else
      printError();
  }

  /////////////////////////////////////////////////////// Liters
  if (myELM327.queryPID(34, 4906))
  {
    int32_t tempLitersLeft = myELM327.findResponse() / 64.0;

    Serial.print("Payload received for liters: ");
    for (byte i = 0; i < PAYLOAD_LEN; i++)
      Serial.write(myELM327.payload[i]);
    Serial.println();

    if (myELM327.status == ELM_SUCCESS)
    {
      litersLeft = tempLitersLeft;
      Serial.print("Liters: "); Serial.println(litersLeft);
    }
    else
      printError();
  }

  /////////////////////////////////////////////////////// Burns
  if (myELM327.queryPID(34, 8434))
  {
    int32_t tempBurn = myELM327.findResponse();

    Serial.print("Payload received for DPF burns: ");
    for (byte i = 0; i < PAYLOAD_LEN; i++)
      Serial.write(myELM327.payload[i]);
    Serial.println();

    if (myELM327.status == ELM_SUCCESS)
    {
      burn = tempBurn;
      Serial.print("DPF burns: "); Serial.println(burn);
    }
    else
      printError();
  }

  for (int a = 0; a < 5; ++a)
  {
    digitalWrite(LED_BUILTIN,HIGH);
    delay(200);
    digitalWrite(LED_BUILTIN,LOW);
    delay(200); 
  }
}


void printError()
{
  if (myELM327.status == ELM_SUCCESS)
    Serial.println(F("\tELM_SUCCESS"));
  else if (myELM327.status == ELM_NO_RESPONSE)
    Serial.println(F("\tERROR: ELM_NO_RESPONSE"));
  else if (myELM327.status == ELM_BUFFER_OVERFLOW)
    Serial.println(F("\tERROR: ELM_BUFFER_OVERFLOW"));
  else if (myELM327.status == ELM_GARBAGE)
    Serial.println(F("\tERROR: ELM_GARBAGE"));
  else if (myELM327.status == ELM_UNABLE_TO_CONNECT)
    Serial.println(F("\tERROR: ELM_UNABLE_TO_CONNECT"));
  else if (myELM327.status == ELM_NO_DATA)
    Serial.println(F("\tERROR: ELM_NO_DATA"));
  else if (myELM327.status == ELM_STOPPED)
    Serial.println(F("\tERROR: ELM_STOPPED"));
  else if (myELM327.status == ELM_TIMEOUT)
    Serial.println(F("\tERROR: ELM_TIMEOUT"));
  else if (myELM327.status == ELM_GENERAL_ERROR)
    Serial.println(F("\tERROR: ELM_GENERAL_ERROR"));
  }

  delay(500);
}

For more info on custom queries, you can look at this GitHub issue.

However, that is an absolutely ridiculously long PID response. Are you sure the response is supposed to be that long? Can you provide a link to more info on this PID?

Name,ShortName,ModeAndPID,Equation,Min Value,Max Value,Units,Header

Температура охлаждающей жидкости,ЕСТ,2102,A/256*200-50,-40,150,C,
Температура на впуске,IAT,2102,B/256*200-50,-40,150,C,
Температура двигателя,EngTemp,210c,O/256*200-50,-40,150,C,
Положение ДЗ,TPS,2102,C/256*100,0,100,%,
Напряжение БС,Volt BS,2102,(D+1.5)/9.854,0,16,V,
Скорость,Speed,2102,E,0,256,km/h,
Обороты двигателя,RPM,2102,F*256+G,0,9000,rpm,
Требуемый ХХ,Need idle,210d,M*256+N,500,1500,rpm,
Барометрическое давление,BARO,2102,H*0.456,0,119,kPa,
Абсолютное давление,МАР,2102,J*0.456,0,119,kPa,
Массовый расход воздуха,MAF,2102,(L*256+M)/47,0,1400,mg/t,
Длительность впрыска пусковая,InjDurSt,2102,(N*256+O)/64,0,1050,ms,
Длительность впрыска текущая,InjDur,2102,(P*256+Q)/250,0,0,ms,
Коррекция длительности впрыска,Corr Inj,2102,T*256+U,0,65535,ms,
Передача МКПП,Gear,2102,V,0,6,,
Режим АКПП,AKPP,2102,W,0,256,,
Скважность продувки адсорбера,AdsFlow,2102,X/256*100,0,100,%,
Рециркуляция отработаных газов,EGR,2102,Y/256*100,0,100,%,

,,, 45 заменить на объем бака
,,,Fuel Tank,Fuel exist,2102,Z/256*45,0,45,l,
Fuel Tank%,Fuel exist,2102,Z/256*100,0,100,%,
Fuel Tank avg%,Fuel exist,210b,X*0.6,0,100,%,
Давление в системе кондиционирования,CondPres,2102,AB*12,0,3100,kPa,

Реальное положение ДЗ,RealTPS,2102,AC,0,256,steps,
Желаемое положение ДЗ,MeanTPS,2102,AE,0,256,steps,

Режим работы двигателя,Engine st,210c,AF,0,255,,
Бензонасос,FuelPump,210B,{A:0},0,1,0ff/On,
ДЗ закрыта,PSClose,210B,{I:0},0,1,0ff/On,
Кондиционер,Condit,210B,{R:0},0,1,0ff/On,
Реле низкой скор.вент.охл-я,L airfan,210B,{S:0},0,1,0ff/On,
Реле высокой скор.вент.охл-я,H airfan,210B,{T:0},0,1,0ff/On,

O2Sensor1,O2S1,2104,(C*256+B)*4.88,0,319810,mV,
O2Sensor2,O2S2,2104,(G*256+F)*4.88,0,319810,mV,
O2Sensor Heater,Heat O2S,210B,{B:0},0,1,0ff/On,
Датчик детонации,Detonate,2101,G*256+H,0,65535,mV,

УОЗ1,УОЗ1,2103,(180-A)/10,-15,15,deg,
УОЗ2,УОЗ2,2103,(180-B)/10,-15,15,deg,
УОЗ3,УОЗ3,2103,(180-C)/10,-15,15,deg,
УОЗ4,УОЗ4,2103,(180-D)/10,-15,15,deg,
Требуемый УОЗ,треб УОЗ,2103,(128-G)/10,-15,15,deg,

Краткосрочная коррекция топливоподачи,Inst Fuel Corr,2104,((S - (178*{S:7}))*256 + R)/655,-50,50,%,
Накопленная коррекция топливоподачи,Add Fuel Corr,2105,((G - (178*{G:7}))*256 + F)/655,-50,50,%,
Средняя коррекция топливоподачи,Abs Fuel Corr,2104,((W - (178*{W:7}))*256 + V)/655,-50,50,%,

Впускной коллектор переменной длины,Twin Port,210B,{Q:0},0,1,0ff/On,
полная нагрузка,FullLoad,210B,{H:0},0,1,0ff/On,
муфта гидротрансформатора АКПП,Clutch,210B,{U:0},0,1,0ff/On,
drive,drive,210c,{A:0},0,1,off/on,

Режим ХХ,EngIdle,210b,{C:0},0,1,Off/on,
Отключение подачи топлива,Inj off,210b,{G:0},0,1,Off/on,
Время с запуска двигателя,EngRunTime,2117,(I*256+J)/10,0,6553.5,sec
Положение ДЗ2,TPS2,2115,T,0,100,%

Pid 2102 contains all the parameters. Car Chevrolet Lacetti SIRIUS D42

I don't speak Russian - can you provide an English internet link to a description of that PID?

pid2102 in my machine transmits all the data, each response byte is the data of each parameter.

Power_Broker:
I don't speak Russian - can you provide an English internet link to a description of that PID?

Coolant temperature, EST, 2102,A/256*200-50,-40,150, C,
The temperature at the inlet,IAT,2102,B/256*200-50,-40,150,C,
Position DZ, TPS,2102,C/256*100,0,100,%,
Voltage BS, Volt BS, 2102,(D+1.5)/9.854,0,16,V,
Speed, Speed,2102,E, 0,256, km/h,
Engine speed, RPM, 2102,F*256+G, 0, 9000, rpm,
Barometric pressure, BARO,2102,H*0.456, 0, 119,kPa,
Absolute pressure, MAR, 2102, J*0.456, 0, 119, kPa,
Mass air consumption, MAF, 2102,(L*256+M)/47,0,1400, mg/t,
The duration of the injection launcher,InjDurSt,2102,(N*256+O)/64,0,1050,ms,
The duration of the injection current,InjDur,2102,(P*256+Q)/250,0,0,ms,
Correction of injection duration, Corr Inj,2102, T*256+U, 0,65535, ms,
Transmission of manual transmission, Gear, 2102, V, 0,6,,
Automatic transmission mode, AKPP,2102, W, 0,256,,
The duty cycle EVAP purge,AdsFlow,2102,X/256*100,0,100,%,
Exhaust gas recirculation, EGR,2102, Y/256*100,0,100,%,
Fuel Tank%, Fuel exist,2102,Z/256*100,0,100,%,
The pressure in the air conditioning system,CondPres,2102,AB*12,0,3100,kPa,
The real situation of the DMZ,RealTPS,2102,AC,0,256,steps,
Desired position DZ, MeanTPS,2102,AE, 0,256,steps,

links unfortunately only in Russian. this is the pid of torque

Chrome can translate it for me, I want to see the link. I’ve never heard of a PID that returns more than one value.

http://arduino.ru/forum/proekty/bortovoi-kompyuter-dlya-opel-zafira?page=38

As the library stands right now, the length of that response wouldn't fit inside the response buffer of the ELMduino library (max 40 bytes). Even if the entire response could fit, there's no way parse each of the individual pieces of data out of the response within the library, so you'd have to write your own parser anyway.

You can fork the library (or edit a local copy on your machine) to increase the response buffer and then write your own parser.

Basically, the standard library supports querying custom PIDs if the PID response is 40 chars long or less.

Hello to everybody.

First of all, thank you Power_Broker for creating such library.

I think I might use it but I need some help from more experienced users :slight_smile:

I have a china ELM 327 BT device but because of the paring process I prefer to hook it up via wires.
In the device I found out the NXP TJA1040 transceiver:

I would like to wire it with wemos d1 mini and now, I am not sure it this is right but I think I have to use this pins on the NXP chip. Is that right?

nieprzem:
First of all, thank you Power_Broker for creating such library.

:slight_smile: :slight_smile: :slight_smile:

nieprzem:
I would like to wire it with wemos d1 mini and now, I am not sure it this is right but I think I have to use this pins on the NXP chip. Is that right?

I've never used the Wemos D1 Mini, but if it has standard Bluetooth functionality built-in (like the ESP32s), then it would actually be a lot simpler just to use bluetooth. That being said, if you still want to hardwire your project and still use ELMduino.h, you will have to wire things up differently than you you suggest.

The chip pointed to in your pic is a CAN transceiver and not the ELM327 itself. In fact, the board in the pic doesn't have an ELM327 chip at all. ELM327s only come in dual-inline packages and the OBD interpreter on the board in your pic is a quad (bottom left hand corner with the surface removed). However, I think the OBD interpreter being used is the STN2120, which (I believe) is compatible with the ELM327 command set - meaning ELMduino.h should still work for the STN2120.

The datasheet I linked has a chip pinout on page 6. Looks like the pins you're interested in are 4 and 5.

NOTE: THE STN2120 IS A 3V3 DEVICE!!

Thank you for the quick response.

The chip which is on the board has 8 pins on every edge.
STN2120 from datasheet has 11 pins.

Not sure if these are the same.
Never mind, I’ll have to search for the proper ELM327 device then.

Oops, I should've paid more attention to the number of pins...

Good luck!

P_B thanks for this library.

I had problems connecting my ESP32 to my ELM327, and found out today that it didn’t like to connect using the name “OBDII” (which is the correct name btw),
I had to use the MAC address.
My correct working ESP32_Bluetooth_Serial.ino is as follows:

#include "BluetoothSerial.h"
#include "ELMduino.h"


BluetoothSerial SerialBT;
#define ELM_PORT   SerialBT
#define DEBUG_PORT Serial

//String MACadd = "DC:0D:30:48:8D:60";                         //enter the ELM327 MAC address
uint8_t address[6]  = {0xDC, 0x0D, 0x30, 0x48, 0x8D, 0x60};  //enter the ELM327 MAC address after the 0x

ELM327 myELM327;


uint32_t rpm = 0;


void setup()
{
#if LED_BUILTIN
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
#endif

  DEBUG_PORT.begin(115200);
  //SerialBT.setPin("1234");
  ELM_PORT.begin("ArduHUD", true);

  if (!ELM_PORT.connect(address))            //"OBDII" replaced by "address"
  {
    DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1");
    while (1);
  }

  if (!myELM327.begin(ELM_PORT))
  {
    Serial.println("Couldn't connect to OBD scanner - Phase 2");
    while (1);
  }

  Serial.println("Connected to ELM327");
}


void loop()
{
  float tempRPM = myELM327.rpm();

  if (myELM327.status == ELM_SUCCESS)
  {
    rpm = (uint32_t)tempRPM;
    Serial.print("RPM: "); Serial.println(rpm);
  }
  else
  {
    printError();
  }
}


void printError()
{
  Serial.print("Received: ");
  for (byte i = 0; i < myELM327.recBytes; i++)
    Serial.write(myELM327.payload[i]);
  Serial.println();

  if (myELM327.status == ELM_SUCCESS)
    Serial.println(F("\tELM_SUCCESS"));
  else if (myELM327.status == ELM_NO_RESPONSE)
    Serial.println(F("\tERROR: ELM_NO_RESPONSE"));
  else if (myELM327.status == ELM_BUFFER_OVERFLOW)
    Serial.println(F("\tERROR: ELM_BUFFER_OVERFLOW"));
  else if (myELM327.status == ELM_UNABLE_TO_CONNECT)
    Serial.println(F("\tERROR: ELM_UNABLE_TO_CONNECT"));
  else if (myELM327.status == ELM_NO_DATA)
    Serial.println(F("\tERROR: ELM_NO_DATA"));
  else if (myELM327.status == ELM_STOPPED)
    Serial.println(F("\tERROR: ELM_STOPPED"));
  else if (myELM327.status == ELM_TIMEOUT)
    Serial.println(F("\tERROR: ELM_TIMEOUT"));
  else if (myELM327.status == ELM_TIMEOUT)
    Serial.println(F("\tERROR: ELM_GENERAL_ERROR"));

  delay(100);
}

Power_Broker, thank you for sharing this library!

I also tried the example sketch above, but I always receive the ELM_Timeout message.
In my case the connection is stable, using just "Android-Vlink" name (Vgate iCar Pro BL 4.0 https://amzn.to/3jB7NSZ ).
With the ESP32_test sketch, I'm able to send AT commands to this OBDII adapter, successfully.

Thanks for your help!

how do i use the pid code to read vehicle speed...Pid that in your github

There are "kph()" and "mph()" member functions available for querying vehicle speed. If you want to know what a library can and can't do, check the header file for a quick reference.

Found out something new today regarding the ESP32,
once you've connected your ESP32 to some ELM327, and want to connect to a different ELM,
you'll have to remove the bonded ELM from the ESP32 using this code:

github link, setting the "#define REMOVE_BONDED_DEVICES 0" from 0 to 1

While having done that, I could connect to a different ELM327 either by name or MAC address.