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
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."