Custom ESPHome component for BLE ELM327 (OBD2)

Hi everyone,

I've put together a custom ESPHome component that allows you to read car data using a BLE ELM327 (OBD2) scanner.

You can find the code and the README with configuration details here:

Here is the YAML configuration from my actual setup:

Any shared extended PIDs would be greatly appreciated!

3 Likes

Anyway, since it communicates via OBD scanner commands, all PIDs supported by the vehicle should be available.

First, you need to connect to the OBD scanner, so you will need its MAC address and the operating UUID. You can find them using an app called nRF Connect. This is my setup. For the full configuration, please check the URL.

ble_client:
  - mac_address: ${mac_vlinker}
    id: obd_client

ble_elm327:
  id: obd_elm
  ble_client_id: obd_client
  service_uuid: "18F0"
  rx_char_uuid: "2AF0"
  tx_char_uuid: "2AF1"
  init_commands:
    - "ATZ"
    - "ATE0"
    - "ATL0"
    - "ATS0"
    - "ATH0"
    - "ATSP6"
  tx_delay: 100

The basic standard PIDs can be configured as follows:

sensor:
  - platform: ble_elm327
    id: "engine_run_time"
    name: "Engine Run Time"
    preset: run_time
    update_interval: 10s

To configure the PIDs manually, you can define the mode, pid, and formula yourself as follows:

sensor:
  - platform: ble_elm327
    name: "Intake Air Temperature"
    pid: "0F"
    mode: "01"
    update_interval: 10s
    formula: "return a - 40.0f;"
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement

It might need some testing, but I think you can set it up like this."

- platform: ble_elm327
    name: "Peugeot e-208 SOH"
    pid: "D860"            
    mode: "22"
    update_interval: 10s  
    formula: "return ((int16_t)((a << 8) | b)) / 16.0f;" # or ((int16_t)((b << 8) | c)) / 16.0f;"
    unit_of_measurement: "%"
    accuracy_decimals: 1
    state_class: measurement

You can add it as shown in the init_commands below. You need to add all of the init_commands. Otherwise, parsing might fail.

ble_elm327:
  id: obd_elm
  ble_client_id: obd_client
  service_uuid: "18F0"
  rx_char_uuid: "2AF0"
  tx_char_uuid: "2AF1"
  init_commands:
    - "ATZ"
    - "ATE0"
    - "ATL0"
    - "ATS0"
    - "ATH0"
    - "ATSP6"
    - "ATSH6B4"
  tx_delay: 50

I'll look into enabling separate commands for each PID.

By referring to this link, you can send a separate pre-command for each PID.

Please post in English.

I tested it and it's working. Thank you.

1 Like

Could you share a working PID YAML configuration? I'd like to add it to example.

  • platform: ble_elm327
    name: "SOH"
    ble_elm327_id: obd_elm
    mode: "22"
    pid: "D860"
    pre_commands:

    • "ATSH 6B4"
      update_interval: 30s
      formula: |-
      return ((b * 256.0f) + c) / 16.0f;
      unit_of_measurement: "%"
      accuracy_decimals: 1
      state_class: measurement
  • platform: ble_elm327
    ble_elm327_id: obd_elm
    name: "SOC"
    pre_commands:

    • "ATSH 6B4"
      mode: "22"
      pid: "D410"
      update_interval: 30s
      formula: |-
      return ((a * 256.0f) + b) / 512.0f;
      unit_of_measurement: "%"
      accuracy_decimals: 1
      state_class: measurement
  • platform: ble_elm327
    name: "HV Voltage"
    mode: "22"
    pid: "D815"
    pre_commands:

    • "ATSH 6B4"
      update_interval: 30s
      formula: |-
      return ((a * 256.0f) + b) / 16.0f;
      unit_of_measurement: "V"
      accuracy_decimals: 1
      state_class: measurement
      device_class: voltage
  • platform: ble_elm327
    name: "Odometro"
    ble_elm327_id: obd_elm
    mode: "22"
    pid: "D49C"
    pre_commands:

    • "ATSH6A6"
      update_interval: 35s
      formula: |-
      return ((b * 65536.0f) + (c * 256.0f) + d);
      unit_of_measurement: "km"
      accuracy_decimals: 0
      state_class: total_increasing
      It's working on a 2021 Citroen E_Berlingo, which is the same as a Peugeot E208.

I have more PIDs that I'm going to test.

Thank you. I'll add this to my GitHub README.

  • platform: ble_elm327
    name: "Batt HV Temp"
    mode: "22"
    pid: "D8EF"
    pre_commands:
    • "ATSH 6A2"
      update_interval: 30s
      formula: |-
      return a;
      unit_of_measurement: "°C"
      accuracy_decimals: 1
      state_class: measurement
      device_class: Temperature
1 Like