(Bosch) Ebike charging automation

After searching ( like many others ) for a way of charging my two Bosch BES3 E-Bikes not to 100% but stopping at around 90% I ended up with the following plan.

  1. I created a sensor for my TAPO Plug showing the actual Power consumption which is during normal charging around 180 W and slowly decreases as the SOC get`s near 90%.

  2. I cretaed a second sensor substracting 100 from the actual Value, because the following blueprint I’m using can handle only Values with a max. of 100 W.

  3. This shown drop of charging current start’s at 90% SOC and when this happens the automation turn’s of the TAPO Plug and send’s a notification.

Tried this out for a while and you can easily adjust the end of charging by changing the “appliance_finishing_power_threshold:” value

This is the autimation I’m using.

The code for the Helper TemplateSensor with - 100W Power

{{ (float(states( "sensor.tapo_ebike_current_power"))) - 100 }}

And the yaml code for Fleofabri’s blueprint

alias: 90%laden
description: ""
use_blueprint:
  path: leofabri/appliance-status-monitor.yaml
  input:
    appliance_socket: switch.tapo_ebike
    appliance_power_sensor: sensor.tapo_100
    delayed_job_completion_duration: 0
    actions_job_cycle_ends:
      - type: turn_off
        device_id: 830732532d893247de93ce2364dff12b
        entity_id: 5f21b4cf06c796384dcd35758d9e8c0
        domain: switch
      - metadata: {}
        data:
          message: 90% voll
        action: notify.mobile_app_iphone
      - metadata: {}
        data:
          stop_actions: true
        target:
          entity_id: automation.90_laden
        action: automation.turn_off
    appliance_starting_power_threshold: 74
    appliance_finishing_power_threshold: 73
    appliance_state_machine: input_select.tapo_ebike_state_machine
    appliance_job_cycle: input_boolean.tapo_ebike_job_cycle
    delayed_job_completion_timer: timer.tapo_ebike_delayed_job_completion_timer
    automation_self_trigger: input_boolean.tapo_ebike_automation_self_trigger

The only thing what took me a while was setting up all the state machine helpers but therefore you just have to read LEOFABRI’s description

In my case the Automation is triggert by an automation with an IKEA Tradfri switch ( right press = charching 90% left press is charging 100%)

alias: E-Bike laden 90%
description: ""
trigger:
  - platform: device
    domain: mqtt
    device_id: 7096ed1cfa2b91da89d
    type: action
    subtype: arrow_right_click
condition: []
action:
  - type: turn_on
    device_id: 830732532d893247de93ce2364dff12b
    entity_id: 5f21b4cf06c796384dcd35758d9e8c0
    domain: switch
  - target:
      entity_id: automation.90_laden
    data: {}
    action: automation.turn_on
mode: single
4 Likes

Thank you for sharing. This is something I have intended to look into for my Haibike which has a Yamaha motor and battery combination.
This should be a good donor project.
Thanks!

I have a different approach as my Shimano STEPS system charges full speed all the way through 100% so I needed an another trigger than a power drop of the smart plug.

Background: The battery management of Shimano is a desaster, my first battery has only an 79% healty status (capacity) after just 71 charging cycles… so before I invest in a new one, I made precautions :wink:

I built a light sensor from a ESP8266 and a light resistor (<10€), programmed by ESPhome (see code below). The sensor is placed above the last one of the five charging LEDs of the battery, which starts to blink if charge level exceeds 80%. As the sensor detects that this LED starts blinking, a simple automation shuts down the smart power. My battery gets charged to exactly 82% reproducibly.

Two small but strong ring magnets hold the sensor in place over the last LED of the battery.

The sensor

  • checks light every 1,5 sec → “light detected”
  • checks if light was detected at least once in the last 10 seconds → “LED is blinking or on”
  • reports “LED is blinking or on” to Home Assistant every 5 seconds

This logic was needed as the LED blink as the charge level surpasses 80%. The LED is on exactly every other second (1 sec on, one sec off), so light gets checked every 1,5 sec just to be asynchron with my detection and the blinking. A threshold of 0.7 for light detection is used (0 is darkness, 1 is full light, the LED produces 0.5 in my setup).

esphome:
  name: esp8266-steps
  friendly_name: ESP8266-STEPS

esp8266:
  board: esp01_1m

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="

ota:
  - platform: esphome
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp8266-Steps Fallback Hotspot"
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

captive_portal:


sensor:
  - platform: adc
    pin: A0
    name: "LDR Sensor"
    id: ldr_sensor

binary_sensor:
  - platform: template
    name: "LED Blinking or On"
    id: led_blinking_or_on
    device_class: light
    lambda: |-
      // Gibt 'true' zurück, wenn in den letzten 10 Sekunden Licht erkannt wurde
      for (int i = 0; i < 10; i++) {
        if (id(light_history)[i]) {
          return true;
        }
      }
      return false;

globals:
  - id: light_detected
    type: bool
    restore_value: no
    initial_value: "false"

  - id: light_history
    type: std::vector<bool>
    restore_value: no
    initial_value: "std::vector<bool>(10, false)"

interval:
  # Aktualisiere den LDR-Sensor jede Sekunde und speichere Erkennungsergebnisse
  - interval: 1.5s
    then:
      - component.update: ldr_sensor
      - if:
          condition:
            # Wenn der LDR-Wert unter dem Schwellenwert liegt, wird Licht erkannt
            lambda: 'return id(ldr_sensor).state < 0.7;'
          then:
            - globals.set:
                id: light_detected
                value: "true"
          else:
            - globals.set:
                id: light_detected
                value: "false"

      # Speichere das aktuelle Ergebnis in der Liste und verschiebe die Einträge
      - lambda: |-
          id(light_history).erase(id(light_history).begin());
          id(light_history).push_back(id(light_detected));

  # Alle 5 Sekunden prüfen, ob innerhalb der letzten 10 Sekunden Licht erkannt wurde
  - interval: 5s
    then:
      - if:
          condition:
            lambda: |-
              // Prüfen, ob mindestens ein 'true' in den letzten 10 Sekunden gespeichert ist
              for (int i = 0; i < 10; i++) {
                if (id(light_history)[i]) {
                  return true;
                }
              }
              return false;
          then:
            - logger.log: "Light detected in the last 10 seconds."
          else:
            - logger.log: "No light detected in the last 10 seconds."
1 Like