Custom component Nissan Leaf via LeLink 2 (ELM327) BLE

I have a gen2 leaf, and it will only respond to requests via the dongle when the car is switched on. If you turn the car on when it’s charging, it stays on at least, but not a tremendously reliable strategy.

I also use a Wallbox charger which reports charging power and energy added (but not SoC) over WiFi: much more reliable.

1 Like

I just put a deposit on a second gen leaf (2019 SL) and was planning on ordering a wallbox “charger” and hoping to find a way to get the SOC from the leaf so that I can use home assistant to control how full I charge the leaf. This looks like it might do it if I order a Bluetooth odb adapter?

Hi @peter.vk, I do exactly this.
I have a 2021 62kWh Lean, and a Wallbox Pulsar, which reliably reports the amount of energy added.

Since the OBD dongle can only fetch state of charge (SoC) while the car is switched on, I use an input_number helper to ‘save’ the last known SoC.

I then have an automation that calculates the estimated amount of energy to add to get to 80% SoC. This result is stored in another input_number helper.


(excuse the decimal places)

Another automation monitors the wallbox energy_added sensor and pauses charging once the target amount has been added.

One last little detail is that the wallbox energy_added sensor resets to zero when each time the car is plugged in. So I account for that.

Here’s the automations:

Save the SoC

alias: Update SoC
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.nissan_leaf_state_of_charge
condition:
  - condition: template
    value_template: >
      {% set state = states('sensor.nissan_leaf_state_of_charge') %}
      {{state.isdigit() or state.replace('.', '', 1).isdigit() }}
    alias: Check that the SoC is a valid number
action:
  - metadata: {}
    data:
      value: "{{ states('sensor.nissan_leaf_state_of_charge') | float }}"
    target:
      entity_id: input_number.car_state_of_charge
    action: input_number.set_value
mode: single

Calculate charge limit:

alias: Car SoC to kWh charge limit
description: ""
trigger:
  - platform: state
    entity_id:
      - input_number.car_state_of_charge
condition:
  - condition: template
    value_template: >
      {% set state = states('sensor.car_charger_added_energy') %}
      {% if not state.isnumeric() and not state|float(false) %}
        {{ false }}
      {% endif %}
      {{ true }}
action:
  - if:
      - condition: or
        conditions:
          - condition: state
            entity_id: sensor.car_charger_status_description
            state: Paused
          - condition: state
            entity_id: sensor.car_charger_status_description
            state: Scheduled
          - condition: state
            entity_id: sensor.car_charger_status_description
            state: Charging
        alias: If the car is plugged in
    then:
      - metadata: {}
        data:
          value: >-
            {% set SoC = (states('input_number.car_state_of_charge') | float) %}
            {% set added_energy = (states('sensor.car_charger_added_energy') | float) %}
            {% set target_percent = 80 %} {% set scale_factor = 68.7 %}
            {% set remaining_energy = (target_percent - SoC) / 100 * scale_factor %}
            {% set energy_limit = remaining_energy + added_energy %}
            {{ [0, [scale_factor, energy_limit] | min] | max }}
        target:
          entity_id: input_number.car_charger_kwh_limit
        alias: >-
          Estimate the kWh limit based on current SoC and the amount already added
        action: input_number.set_value
    else:
      - metadata: {}
        data:
          value: >-
            {% set SoC = (states('input_number.car_state_of_charge') | float) %}
            {% set target_percent = 80 %} {% set scale_factor = 68.7 %}
            {% set target_energy = (target_percent - SoC) / 100 * scale_factor %}
            {{ [0, [scale_factor, target_energy] | min] | max }}
        target:
          entity_id: input_number.car_charger_kwh_limit
        alias: Estimate the kWh limit based on current SoC
        action: input_number.set_value
mode: single

Cut off the charging:

alias: "Car charger: Charging cutoff"
description: ""
trigger:
  - platform: numeric_state
    entity_id:
      - sensor.car_charger_added_energy
    above: input_number.car_charger_kwh_limit
condition:
  - condition: state
    entity_id: sensor.car_charger_status_description
    state: Charging
action:
  - type: turn_off
    device_id: 1643276e5d516342711e6a4ca97e725a
    entity_id: fedb6af0f6fd631045aa53c65bd92cc4
    domain: switch
mode: single
2 Likes

This is awesome, thanks for sharing! What ODB dongle did you buy?

Love the progress you’ve made and promise of this integration.

Think I’m missing something very basic about the setup to get info flowing from OBD to HA. Appreciate if anyone can give me a steer.

Progress

  • I have LeLink OBD connected via LeafSpy for last 2 years.
  • I just received M5Stack Atom Lite and flashed it as a Bluetooth Proxy
  • This seems to be detected in HA automatically (pic).
    image

But…

  • I’m getting this error with the integration.

I assume I prob need to configure the Bluetooth Proxy to talk to my specific OBD? But where would I do that?

Appreciate this is probably a dumb question! I’ve been using HA ~9 months now but this will be the first BLE device I’m trying to integrate.

Thanks for all your efforts.

First of all - thank you very much for your work.

I have a 2016 Leaf AZE0 with 24kWh battery and I ordered Vgate iCar Pro BT4.0 dongle. It has separate UUID for read and write, but that was quite an easy fix in the code.

What is not so easy is that CAN messages are a bit different for ZE0/AZE0 (which was already prompted here). What works for LBC decode for my Leaf is:

        "state_of_charge": int.from_bytes(d[30:34]) / 10000,
        "hv_battery_health": int.from_bytes(d[28:30]) / 102.4,
        "hv_battery_Ah": int.from_bytes(d[34:38]) / 10000,
        "hv_battery_current_1": hv_battery_current_1 / 1024,
        "hv_battery_current_2": hv_battery_current_2 / 1024,
        "hv_battery_voltage": int.from_bytes(d[20:22]) / 100,

The odometer, remaining range and tire pressures sensor seem not to work, but I was testing with car plugged in and charging. I’ll check tommorow with just the igniton on

2024-09-19 23:04:54.815 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<6). Padded message: bytearray(b'\x7f"\x11\x00\x00\x00')
2024-09-19 23:04:54.815 INFO (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Sending command: b'74303220e25': Tyre pressure front right
2024-09-19 23:04:55.009 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Received frame: 763037F2211FFFFFFFF
2024-09-19 23:04:55.009 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<4). Padded message: bytearray(b'\x7f"\x11\x00')
2024-09-19 23:04:55.009 INFO (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Sending command: b'74303220e26': Tyre pressure front left
2024-09-19 23:04:55.223 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Received frame: 763037F2211FFFFFFFF
2024-09-19 23:04:55.223 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<4). Padded message: bytearray(b'\x7f"\x11\x00')
2024-09-19 23:04:55.223 INFO (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Sending command: b'74303220e27': Tyre pressure rear right
2024-09-19 23:04:55.435 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Received frame: 763037F2211FFFFFFFF
2024-09-19 23:04:55.436 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<4). Padded message: bytearray(b'\x7f"\x11\x00')
2024-09-19 23:04:55.436 INFO (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Sending command: b'74303220e28': Tyre pressure rear left
2024-09-19 23:04:55.635 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Received frame: 763037F2211FFFFFFFF
2024-09-19 23:04:55.635 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<4). Padded message: bytearray(b'\x7f"\x11\x00')
2024-09-19 23:04:55.635 INFO (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Sending command: b'74303220e24': Remaining range (km)
2024-09-19 23:04:55.839 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.obd] Received frame: 763037F2211FFFFFFFF
2024-09-19 23:04:55.839 DEBUG (MainThread) [custom_components.nissan_leaf_obd_ble.OBDCommand] Message was shorter than expected (3<13). Padded message: bytearray(b'\x7f"\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

Unfortunately the SoC parameter only updates when the car is on and does not update while charging. I did some investigation hoping I could add support of CAN monitoring via ELM327 for older Leafs that do not require CAN-polling, but it seems that the pyOBD library does not support that and I’m not sure I’m up to the task of doing it on my own. But who knows…

Dear Paul! Please share the configuration ( .yaml) for Bluetooth Proxy. Thank you!

This is where the bt proxy yaml lives:

1 Like

why not using a carwings integration? I have to admit it doesn’t provide as much data as I see the dongle does. But for charging purposes you can poll every n-minutes and retrieve the battery state.

No carwings here in New Zealand

check, I didn’t check your info, would have seen than you’re in New Zealand. A shame Nissan hasn’t made it possible to use this in New Zealand. Nice to see there are possibilities. Better than nothing.

Just wanted to say I really appreciate @pbutterworth 's work on this integration.

After a bit of playing around with the BLE proxy location and configuring some helpers to help convert and persist some of the data attributes I have this up and running in my dashboard.

A few limitations but really not a big deal for my use case:

  • The data is only updated when car is powered on, usually briefly pulling in and out of the drive. Practically it means charging status and SOC is not updated when charging.
  • I’m using helpers to persist data and when HA reboots, they loose their values. Will report ‘unavailable’ until the car again talks to the proxy.

Fun bonus:
I’ve setup a helper to remind me to regularly rotate my tires based on the car’s odometer reading and a “last rotated” helper.

Sharing YAML card config below if interested

- type: grid
  cards:
    - type: conditional
      conditions:
        - condition: numeric_state
          entity: sensor.leaf_miles_to_tire_rotation
          below: -499
      card:
        type: markdown
        content: >-
          <ha-alert alert-type="error"><ha-icon icon="mdi:tire"
          style="width:3vw;height:3vw;"></ha-icon>&nbsp; **Nissan Leaf** Tire
          Rotation is **OVERDUE**!</ha-alert>
    - type: conditional
      conditions:
        - condition: numeric_state
          entity: sensor.leaf_miles_to_tire_rotation
          below: 0
          above: -500
      card:
        type: markdown
        content: >-
          <ha-alert alert-type="warning"><ha-icon icon="mdi:tire"
          style="width:3vw;height:3vw;"></ha-icon>&nbsp; **Nissan Leaf** Tire
          Rotation is Due!</ha-alert>
    - type: picture-elements
      elements:
        - type: state-icon
          style:
            bottom: "-25%"
            left: 34%
          entity: sensor.leaf_calculated_range
          tap_action:
            action: more-info
        - type: state-label
          style:
            left: 34%
            bottom: "-35%"
          entity: sensor.leaf_calculated_range
          tap_action:
            action: more-info
        - type: state-icon
          style:
            bottom: "-25%"
            left: 50%
          entity: sensor.nissan_leaf_obd_ble_state_of_charge
        - type: state-label
          style:
            bottom: "-35%"
            left: 50%
          entity: sensor.nissan_leaf_obd_ble_state_of_charge
        - type: state-icon
          style:
            left: 66%
            bottom: "-25%"
          entity: sensor.nissan_leaf_obd_ble_charging_mode
          icon: mdi:ev-plug-type1
        - type: state-label
          style:
            left: 66%
            bottom: "-35%"
          entity: sensor.nissan_leaf_obd_ble_charging_mode
      layout_options:
        grid_columns: full
        grid_rows: 6
      title: Leafy
      image: /api/image/serve/852a9ce7c8a9080d38ac7dff3ba94e7f/512x512
      dark_mode_image: /api/image/serve/be73511477a0cc17fac05546566c5d41/512x512
    - type: tile
      tap_action:
        action: none
      icon_tap_action:
        action: none
      name: Last Updated
      hide_state: false
      show_entity_picture: false
      state_content: last_updated
      vertical: false
      color: blue-grey
      icon: mdi:update
      entity: sensor.nissan_leaf_obd_ble_state_of_charge
    - type: tile
      entity: sensor.leaf_calculated_odometer
      name: Odometer
      icon: mdi:speedometer
      color: blue-grey
    - type: custom:vertical-stack-in-card
      cards:
        - square: false
          type: grid
          columns: 4
          cards:
            - type: gauge
              entity: sensor.leaf_fl_tire
              name: Front Left
              needle: false
              min: 26
              max: 50
              segments:
                - from: 0
                  color: "#db4437"
                - from: 34
                  color: "#ffa600"
                - from: 36
                  color: "#43a047"
                - from: 40
                  color: "#ffa600"
                - from: 42
                  color: "#db4437"
              card_mod:
                style: |
                  ha-card {
                    border: none;
                    box-shadow: 0px 0px;          
                  }
            - type: gauge
              entity: sensor.leaf_fr_tire
              name: Front Right
              needle: false
              min: 26
              max: 50
              segments:
                - from: 0
                  color: "#db4437"
                - from: 34
                  color: "#ffa600"
                - from: 36
                  color: "#43a047"
                - from: 40
                  color: "#ffa600"
                - from: 42
                  color: "#db4437"
              card_mod:
                style: |
                  ha-card {
                    border: none;
                    box-shadow: 0px 0px;          
                  }
            - type: gauge
              entity: sensor.leaf_bl_tire
              name: Rear Left
              needle: false
              min: 26
              max: 50
              segments:
                - from: 0
                  color: "#db4437"
                - from: 34
                  color: "#ffa600"
                - from: 36
                  color: "#43a047"
                - from: 40
                  color: "#ffa600"
                - from: 42
                  color: "#db4437"
              card_mod:
                style: |
                  ha-card {
                    border: none;
                    box-shadow: 0px 0px;          
                  }
            - type: gauge
              entity: sensor.leaf_br_tire
              name: Rear Right
              needle: false
              min: 26
              max: 50
              segments:
                - from: 0
                  color: "#db4437"
                - from: 34
                  color: "#ffa600"
                - from: 36
                  color: "#43a047"
                - from: 40
                  color: "#ffa600"
                - from: 42
                  color: "#db4437"
              card_mod:
                style: |
                  ha-card {
                    border: none;
                    box-shadow: 0px 0px;          
                  }

I’ve been keeping an eye on the work here as I am NZ based and I’m thinking it would bug me too much that I cannot read the SOC while the car is charging :confused:
Although getting odometer reading would allow for a reminder to renew road user charges which would be very useful!

One idea I’ve seen (possibly in this thread) is to setup a helper that estimates SOC while charging.

If your charger is integrated with HA you could take the SOC at start, kW rate, time, and battery size to get a pretty good estimate.

Yep, here: Custom component Nissan Leaf via LeLink 2 (ELM327) BLE - #43 by pbutterworth

Also worth noting that if the car is charging, and you turn it on, it stays on the whole time while charging (rather than switching off after 15 mins). So if you want to monitor SoC through a whole charge, there is a (not very satisfactory) way.

I also have a recharge reminder:

alias: Car low battery warning
description: ""
mode: single
triggers:
  - entity_id:
      - sensor.nissan_leaf_state_of_charge
    below: 30
    for:
      hours: 0
      minutes: 5
      seconds: 0
    trigger: numeric_state
conditions:
  - condition: state
    entity_id: sensor.car_charger_status_description
    state: Ready
    alias: Check that the car isn't plugged in already
actions:
  - metadata: {}
    data:
      title: Nissan Leaf
      message: My battery is quite low, did you mean to plug me in?
    action: notify.notify

And can also plot a nice statistics graph using the odometer reading:

RUC reminder:

alias: RUC warning
description: Post a persistent notification when there is less than 500 km left on the RUC
mode: single
triggers:
  - entity_id:
      - sensor.nissan_leaf_odometer
    value_template: "{{ states('input_number.leaf_ruc') | float - state.state | float  }}"
    below: 500
    trigger: numeric_state
conditions: []
actions:
  - metadata: {}
    data:
      title: Nissan Leaf
      message: Road User Charges will expire in less than 500 km.
    action: notify.persistent_notification

My charge rate varies depending on the amount of available solar sometimes so I’m sure the calcs would be possible but could become quite hairy :slight_smile:

Does your charger report the amount of energy added in the current charging session? This is much more reliable if you have a varying charge rate.
This is what I’m using to decide when to cut off charging.
So you know what the car SoC was when it was last turned on.
And (if) you know how much energy has been added in the current charging session,
Then you can estimate the current SoC

Hey @pbutterworth just got a bluetooth OBD today… Have the “install instructions” changed at all?
I’ve copied folder to custom components rebooted etc but dongle is never detected…

Hmm, what obd dongle did you get?
Also, where is your Bluetooth adapter for HA located? Signal range can be an issue. For example, I have HA yellow with on board Bluetooth adapter. This is in a cupboard in a room right next to the driveway, and I found it to be hit and miss. So now I have a esphome proxy in the garage.

Hey Paul, I grabbed this one.

https://www.aliexpress.com/item/1005005775562398.html

I don’t have a Bluetooth adapter plugged into my HA instance but I do have 3 BT proxies dotted around the house, one is in the garage about 3m away from the car.

I’ve left it for a couple of days but no luck yet in picking up the car…