Last active
April 28, 2026 08:09
-
-
Save mikezs/ce72828069ce0acd2be1d5222b1187ab to your computer and use it in GitHub Desktop.
ebike-smart-charge.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: π² E-Bike Smart Charge (v0.0.1a6) | |
| description: > | |
| π Charges an e-bike battery during off-peak hours via a smart plug, | |
| stopping automatically when the target charge level is reached. | |
| ## β‘ How it works | |
| The blueprint measures energy delivered via the smart plug's kWh sensor | |
| and accounts for charger efficiency to calculate how much wall energy is | |
| needed to reach your target battery level. Every 5 minutes during charging | |
| it updates an input_number helper with the estimated current battery level, | |
| so you can pause mid-charge and the state stays accurate. | |
| ## π§ Required helpers (create once in Settings β Helpers) | |
| Create two **Number** helpers before setting up this automation: | |
| 1. **Current Battery Level** β range 0β100, step 1, unit %. Update this | |
| from your bike display before each charge. The automation keeps it | |
| current during charging, so after a completed charge you only need to | |
| update it if you've ridden since. | |
| 2. **Charge Target** β range 0β100, step 5, unit %. Change this each week | |
| depending on your planned ride. Use 80% for battery longevity on regular | |
| commutes, 100% for long rides. | |
| Add both helpers to a dashboard card so they're easy to update. | |
| ## β° Hard cutoff time | |
| The cutoff is optional. Enable it to ensure charging always stops within | |
| your cheap-rate electricity window. Disable it to let charging run as long | |
| as needed to reach the target (useful if you start charging earlier in the | |
| evening at normal rate and just want it to finish automatically). | |
| ## π Storage charge | |
| When enabled, the automation will also charge the battery up to a lower | |
| storage level (default 50%) on nights that are not ride nights. This keeps | |
| the battery healthy during periods of low use. The storage charge has its | |
| own start and end times so it can run in a different off-peak window from | |
| the ride charge. It always stops at its configured end time. On any night | |
| where a ride charge would run, the storage charge is automatically skipped. | |
| ## π Restart survival | |
| If Home Assistant restarts mid-charge, the automation automatically resumes | |
| when HA starts up again. It detects that the smart plug is still on and that | |
| the current time is within the charge window, then picks up from the last | |
| saved battery level. No energy is double-counted. | |
| ## π Smart stop detection | |
| The automation watches the smart plug's live power draw and reacts to | |
| sudden drops. When power falls below 5 W for 3 continuous minutes: | |
| - **If you're near the target** (β₯90% of estimated need delivered), the | |
| charge is treated as **complete**. | |
| - **If you're not near the target**, the charge is treated as | |
| **interrupted** (most likely the battery was unplugged from the | |
| charger). The notification reports how much was delivered before the | |
| disconnection. | |
| ## π― 100% target β special handling | |
| When the charge target is set to **100%**, energy-based completion is | |
| disabled entirely. E-bike chargers enter a CC/CV trickle phase near full | |
| where they add real battery percentage while drawing little wall energy, | |
| so our kWh estimate stops the charge a few percent short. At 100% the | |
| only natural-completion signal is the charger's own power drop (handled | |
| by Smart stop detection above). The cutoff time still applies as a | |
| backstop. For any target below 100%, energy-based completion is used | |
| as normal. | |
| ## π― Tuning charger efficiency | |
| If the battery level shown on your bike display after a completed charge | |
| doesn't match your target, adjust the efficiency setting: increase it if | |
| HA stopped too early (charger is more efficient than assumed), decrease it | |
| if it ran too long. The smart stop detection above acts as a backstop | |
| when efficiency is slightly off, especially at high charge targets. | |
| ## π Auto-reset target after ride charge | |
| Lithium batteries are happiest when not sitting at high charge levels for | |
| long periods. If you bump the Charge Target to 100% for a long ride and | |
| forget to reset it, the next charge will fill to 100% again even if you | |
| only need 80%. When enabled, this option automatically resets the target | |
| helper back to a default (80% recommended) after each successful ride | |
| charge completes. Storage charges and interrupted/cutoff ride charges | |
| don't trigger the reset β only successful natural completions do. | |
| ## π Smart plug accuracy | |
| Many smart plugs under-report (or over-report) energy by 10β25%, which | |
| is independent from charger efficiency. Use **Plug Accuracy Factor** to | |
| correct for this. Calibrate by running a known load through the plug β | |
| e.g. a 100 W incandescent bulb for 1 hour should deliver 100 Wh, or a | |
| kettle whose label wattage and boil time give an expected Wh. The factor | |
| is `actual_wh / reported_wh`: set above 1.00 if your plug under-reads, | |
| below 1.00 if it over-reads. Leave at 1.00 if you trust the plug. | |
| domain: automation | |
| input: | |
| charger_switch: | |
| name: Charger Switch | |
| description: The smart plug switch entity controlling the charger. | |
| selector: | |
| entity: | |
| domain: switch | |
| energy_sensor: | |
| name: Energy Sensor (kWh) | |
| description: > | |
| The cumulative energy sensor on the smart plug (device class: energy, | |
| unit kWh). Most smart plugs list this as "Energy" in the device page. | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: energy | |
| power_sensor: | |
| name: Power Sensor (W) | |
| description: > | |
| The live power sensor on the smart plug (device class: power, unit W). | |
| Used to verify the battery is actually connected and drawing power | |
| shortly after the charger turns on. | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: power | |
| current_level_helper: | |
| name: Current Battery Level (%) | |
| description: > | |
| An input_number helper showing the estimated current charge level of | |
| the battery. Update this from your bike display before each charge. | |
| The automation will keep it updated every 5 minutes while charging. | |
| selector: | |
| entity: | |
| domain: input_number | |
| charge_target_helper: | |
| name: Charge Target (%) | |
| description: > | |
| An input_number helper showing the desired charge level to reach. | |
| Change this each week β use 80% to be kind to the battery on normal | |
| weeks, 100% before a long ride. | |
| selector: | |
| entity: | |
| domain: input_number | |
| enable_target_reset: | |
| name: Reset Target After Ride Charge | |
| description: > | |
| When enabled, the Charge Target helper resets to a default value | |
| (below) after each successful ride charge. Useful so a one-off bump | |
| to 100% for a long ride doesn't leave the target at 100% for next | |
| week's regular ride. Storage charges and interrupted/cutoff/manual | |
| ride charges don't trigger the reset. | |
| default: true | |
| selector: | |
| boolean: {} | |
| target_reset_pct: | |
| name: Default Ride Target (%) | |
| description: > | |
| The value to reset the Charge Target helper to after a successful | |
| ride charge. 80% is the standard recommendation β kind to the | |
| battery while leaving plenty of range. Only used when "Reset Target | |
| After Ride Charge" is enabled. | |
| default: 80 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| step: 5 | |
| unit_of_measurement: "%" | |
| battery_capacity_wh: | |
| name: Battery Capacity (Wh) | |
| description: > | |
| The total energy capacity of your e-bike battery in watt-hours. | |
| Check your battery label or manual β common sizes are 250β750 Wh. | |
| Set this once and leave it. | |
| default: 500 | |
| selector: | |
| number: | |
| min: 100 | |
| max: 3000 | |
| step: 10 | |
| unit_of_measurement: Wh | |
| charger_efficiency: | |
| name: Charger Efficiency (%) | |
| description: > | |
| How efficiently the charger converts wall power to battery power. | |
| A 95% efficient charger draws 100 Wh from the wall to deliver 95 Wh | |
| to the battery. Most quality e-bike chargers are 90β98%. Start with | |
| 95 and adjust over time: if your bike display shows less than the | |
| target after charging, lower this value (charger is less efficient | |
| than assumed); if HA stopped early and the battery isn't full enough, | |
| raise it. | |
| default: 95 | |
| selector: | |
| number: | |
| min: 70 | |
| max: 100 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| plug_accuracy_factor: | |
| name: Smart Plug Accuracy Factor | |
| description: > | |
| Calibration multiplier applied to the smart plug's energy readings. | |
| Many cheap smart plugs under-report by 10β25%. Set this to | |
| `actual_wh / reported_wh` from a known-load test (e.g. a 100 W bulb | |
| run for 1 hour should report 100 Wh). Values above 1.00 mean the plug | |
| under-reads (corrects upward); below 1.00 means it over-reads. Leave | |
| at 1.00 if you trust the plug or haven't calibrated yet. | |
| default: 1.00 | |
| selector: | |
| number: | |
| min: 0.7 | |
| max: 1.3 | |
| step: 0.01 | |
| charge_days: | |
| name: Ride Days | |
| description: > | |
| The day(s) you want the bike ready to ride. If your start time is in | |
| the evening (6pm or later), charging begins the night before β so | |
| select Sunday if you want to charge Saturday night ready for a Sunday | |
| ride. If your start time is in the morning or afternoon, select the | |
| same day you want to ride. | |
| selector: | |
| select: | |
| multiple: true | |
| options: | |
| - Monday | |
| - Tuesday | |
| - Wednesday | |
| - Thursday | |
| - Friday | |
| - Saturday | |
| - Sunday | |
| charge_start_time: | |
| name: Charge Start Time | |
| description: When to start charging. Defaults to the start of the cheap-rate window. | |
| default: "23:30:00" | |
| selector: | |
| time: {} | |
| use_cutoff_time: | |
| name: Use Hard Cutoff Time | |
| description: > | |
| When enabled, charging always stops at the cutoff time below regardless | |
| of whether the target has been reached. Enable this if you want to stay | |
| within a cheap-rate electricity window. When disabled, charging runs | |
| until the target is reached (or the plug is turned off manually). | |
| default: true | |
| selector: | |
| boolean: {} | |
| charge_end_time: | |
| name: Charge End Time (Hard Cutoff) | |
| description: > | |
| The time to stop charging regardless of progress. Only used when | |
| "Use Hard Cutoff Time" is enabled above. Set to the end of your | |
| cheap electricity window. | |
| default: "05:30:00" | |
| selector: | |
| time: {} | |
| enable_storage_charge: | |
| name: Enable Storage Charge | |
| description: > | |
| When enabled, the battery will be charged to the storage level on | |
| non-ride nights. Useful for keeping the battery healthy during weeks | |
| when you're not riding. | |
| default: false | |
| selector: | |
| boolean: {} | |
| storage_target_pct: | |
| name: Storage Charge Target (%) | |
| description: > | |
| The battery level to charge to on non-ride nights. 50% is the | |
| recommended storage level for most lithium batteries. | |
| default: 50 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 90 | |
| step: 5 | |
| unit_of_measurement: "%" | |
| storage_start_time: | |
| name: Storage Charge Start Time | |
| description: When to start the storage charge. Can be the same as or different from the ride charge start time. | |
| default: "23:30:00" | |
| selector: | |
| time: {} | |
| storage_end_time: | |
| name: Storage Charge End Time | |
| description: > | |
| The storage charge always stops by this time. Set to the end of your | |
| cheap electricity window for storage charging. | |
| default: "05:30:00" | |
| selector: | |
| time: {} | |
| price_per_kwh: | |
| name: Electricity Price (per kWh) | |
| description: > | |
| The cost of electricity per kWh in your local currency. Used to estimate | |
| the cost of each charge session in the completion notification. Set this | |
| to your cheap-rate tariff price β e.g. 0.075 for 7.5p/kWh. | |
| default: 0.075 | |
| selector: | |
| number: | |
| min: 0.01 | |
| max: 1.00 | |
| step: 0.001 | |
| unit_of_measurement: "/kWh" | |
| enable_soc_reminder: | |
| name: Enable Battery Level Reminder | |
| description: > | |
| When enabled, a notification is sent on each ride day evening reminding | |
| you to update the current battery level helper after your ride. This | |
| ensures the storage charge (if enabled) uses an accurate starting level. | |
| default: true | |
| selector: | |
| boolean: {} | |
| soc_reminder_time: | |
| name: Battery Level Reminder Time | |
| description: > | |
| The time on ride days to send the reminder. Set to sometime after you | |
| typically return from your ride, e.g. 20:00. | |
| default: "20:00:00" | |
| selector: | |
| time: {} | |
| trigger: | |
| - platform: time | |
| at: !input charge_start_time | |
| id: ride | |
| - platform: time | |
| at: !input storage_start_time | |
| id: storage | |
| - platform: time | |
| at: !input soc_reminder_time | |
| id: soc_reminder | |
| - platform: homeassistant | |
| event: start | |
| id: restart | |
| variables: | |
| charger_switch: !input charger_switch | |
| energy_sensor: !input energy_sensor | |
| power_sensor: !input power_sensor | |
| current_level_helper: !input current_level_helper | |
| charge_target_helper: !input charge_target_helper | |
| enable_target_reset: !input enable_target_reset | |
| target_reset_pct: !input target_reset_pct | |
| battery_capacity_wh: !input battery_capacity_wh | |
| charger_efficiency: !input charger_efficiency | |
| plug_accuracy_factor: !input plug_accuracy_factor | |
| charge_days: !input charge_days | |
| charge_start_time: !input charge_start_time | |
| use_cutoff_time: !input use_cutoff_time | |
| charge_end_time: !input charge_end_time | |
| enable_storage_charge: !input enable_storage_charge | |
| storage_target_pct: !input storage_target_pct | |
| storage_start_time: !input storage_start_time | |
| storage_end_time: !input storage_end_time | |
| price_per_kwh: !input price_per_kwh | |
| enable_soc_reminder: !input enable_soc_reminder | |
| soc_reminder_time: !input soc_reminder_time | |
| condition: | |
| - condition: template | |
| value_template: > | |
| {% set start_hour = charge_start_time.split(':')[0] | int %} | |
| {% set check_day = (now() + timedelta(days=1)).strftime('%A') if start_hour >= 18 else now().strftime('%A') %} | |
| {% set is_ride_night = check_day in charge_days %} | |
| {% if trigger.id == 'soc_reminder' %} | |
| {{ enable_soc_reminder and now().strftime('%A') in charge_days }} | |
| {% elif trigger.id == 'ride' %} | |
| {{ is_ride_night and | |
| states(charge_target_helper) | float(0) > states(current_level_helper) | float(0) }} | |
| {% elif trigger.id == 'storage' %} | |
| {{ enable_storage_charge and | |
| not is_ride_night and | |
| states(current_level_helper) | float(0) < storage_target_pct | float }} | |
| {% else %} | |
| {# restart: plug must be on, within ride or storage window, and something left to charge #} | |
| {% if use_cutoff_time %} | |
| {% set r_end = today_at(charge_end_time) %} | |
| {% if r_end <= today_at(charge_start_time) %}{% set r_end = r_end + timedelta(days=1) %}{% endif %} | |
| {% set in_ride_window = now() >= today_at(charge_start_time) and now() < r_end %} | |
| {% else %} | |
| {% set s_min = charge_start_time.split(':')[0] | int * 60 + charge_start_time.split(':')[1] | int %} | |
| {% set in_ride_window = now().hour * 60 + now().minute >= s_min or now().hour < 6 %} | |
| {% endif %} | |
| {% if enable_storage_charge %} | |
| {% set st_end = today_at(storage_end_time) %} | |
| {% if st_end <= today_at(storage_start_time) %}{% set st_end = st_end + timedelta(days=1) %}{% endif %} | |
| {% set in_storage_window = now() >= today_at(storage_start_time) and now() < st_end %} | |
| {% else %} | |
| {% set in_storage_window = false %} | |
| {% endif %} | |
| {% set needs_charge = states(charge_target_helper) | float(0) > states(current_level_helper) | float(0) or | |
| (enable_storage_charge and states(current_level_helper) | float(0) < storage_target_pct | float) %} | |
| {{ is_state(charger_switch, 'on') and (in_ride_window or in_storage_window) and needs_charge }} | |
| {% endif %} | |
| action: | |
| # SOC reminder: notify and stop β nothing else to do. | |
| - if: | |
| - condition: template | |
| value_template: "{{ trigger.id == 'soc_reminder' }}" | |
| then: | |
| - service: notify.persistent_notification | |
| data: | |
| title: "π² Update Your Battery Level" | |
| message: > | |
| π Don't forget to update your current battery level after today's | |
| ride. It's currently set to {{ states(current_level_helper) | round(0) }}%. | |
| Update it in the dashboard before tonight's charge window. | |
| - stop: "SOC reminder sent" | |
| - variables: | |
| # Single {{ }} expression so HA stores a clean Python string, not a | |
| # rendered block with surrounding whitespace that would always be truthy. | |
| charge_type: > | |
| {{ 'storage' if (trigger.id == 'storage' or | |
| (trigger.id == 'restart' and enable_storage_charge and | |
| ((now() + timedelta(days=1)).strftime('%A') if charge_start_time.split(':')[0] | int >= 18 | |
| else now().strftime('%A')) not in charge_days)) else 'ride' }} | |
| current_pct: "{{ states(current_level_helper) | float(0) }}" | |
| target_pct: > | |
| {{ storage_target_pct | float if charge_type == 'storage' | |
| else states(charge_target_helper) | float(80) }} | |
| # Wall energy needed = battery energy needed Γ· efficiency. | |
| # Then divide by plug_accuracy_factor: we compare against the plug's | |
| # reported reading, and if the plug under-reads (factor > 1), each | |
| # reported Wh represents more actual Wh so we stop sooner. | |
| needed_kwh: > | |
| {{ ((target_pct - current_pct) / 100 * battery_capacity_wh | float) | |
| / (charger_efficiency | float / 100) | |
| / (plug_accuracy_factor | float) / 1000 }} | |
| start_energy: "{{ states(energy_sensor) | float(0) }}" | |
| cutoff_dt: > | |
| {% if charge_type == 'storage' %} | |
| {% set end = today_at(storage_end_time) %} | |
| {% if end <= now() %}{% set end = end + timedelta(days=1) %}{% endif %} | |
| {{ end.isoformat() }} | |
| {% elif use_cutoff_time %} | |
| {% set end = today_at(charge_end_time) %} | |
| {% if end <= now() %}{% set end = end + timedelta(days=1) %}{% endif %} | |
| {{ end.isoformat() }} | |
| {% else %} | |
| none | |
| {% endif %} | |
| # Turn on the plug only if it's currently off. | |
| # On a restart-recovery run the plug is already on, so we skip this. | |
| - if: | |
| - condition: template | |
| value_template: "{{ is_state(charger_switch, 'off') }}" | |
| then: | |
| - service: switch.turn_on | |
| target: | |
| entity_id: !input charger_switch | |
| # Wait for the plug to confirm it's on (state updates are async). | |
| - wait_template: "{{ is_state(charger_switch, 'on') }}" | |
| timeout: | |
| seconds: 10 | |
| # Give the charger a moment to start drawing power, then check the battery | |
| # is actually connected. A real charger will draw well above 5 W within | |
| # 20 seconds; near-zero means nothing is plugged in. | |
| - delay: | |
| seconds: 20 | |
| - if: | |
| - condition: template | |
| value_template: "{{ states(power_sensor) | float(0) < 5 }}" | |
| then: | |
| - service: switch.turn_off | |
| target: | |
| entity_id: !input charger_switch | |
| - service: notify.persistent_notification | |
| data: | |
| title: "π² E-Bike Charger Warning" | |
| message: > | |
| β οΈ Charger turned on but no power draw detected after 20 seconds. | |
| Is the battery plugged into the charger? Charging has been cancelled. | |
| - stop: "No power draw detected β battery may not be connected" | |
| # Wait for any exit condition to fire. The wait times out every 5 minutes | |
| # so we can update the current-level helper, which keeps mid-charge pauses | |
| # and HA-restart recoveries accurate. Each exit trigger has an id: we can | |
| # read back from wait.trigger to determine why we stopped. | |
| # | |
| # condition: numeric_state doesn't support for:, but triggers do β that's | |
| # why this is structured as wait_for_trigger rather than repeat: while. | |
| - repeat: | |
| sequence: | |
| - wait_for_trigger: | |
| # Template trigger (not numeric_state) because numeric_state | |
| # above:/below: don't reliably accept action-scoped variables β | |
| # they evaluate to None at trigger setup time. | |
| # | |
| # When target is 100% the energy estimate is unreliable: e-bike | |
| # chargers enter a CC/CV trickle phase near full that adds a few | |
| # percent of real charge while delivering little wall energy. So | |
| # at 100%, we suppress this trigger entirely and let power_dropped | |
| # be the only natural-completion signal (cutoff is still a backstop). | |
| - platform: template | |
| value_template: > | |
| {{ target_pct < 100 | |
| and (states(energy_sensor) | float(0) - start_energy) >= needed_kwh }} | |
| id: target_reached | |
| - platform: state | |
| entity_id: !input charger_switch | |
| to: "off" | |
| id: manual | |
| # Smart stop: power below 5W for 3 continuous minutes means the | |
| # charger has finished (if near target) or the battery has been | |
| # disconnected (if not). The 3-minute window absorbs brief CC/CV | |
| # transition dips without false-tripping. Differentiated post-loop. | |
| - platform: numeric_state | |
| entity_id: !input power_sensor | |
| below: 5 | |
| for: | |
| minutes: 3 | |
| id: power_dropped | |
| timeout: | |
| minutes: 5 | |
| continue_on_timeout: true | |
| # Wait timed out (no exit trigger fired) β update the helper and loop. | |
| - if: | |
| - condition: template | |
| value_template: "{{ wait.trigger is none }}" | |
| then: | |
| - variables: | |
| delivered_kwh: "{{ states(energy_sensor) | float(0) - start_energy }}" | |
| - service: input_number.set_value | |
| target: | |
| entity_id: !input current_level_helper | |
| data: | |
| # Convert reported kWh β actual wall Wh (Γ plug_accuracy_factor) | |
| # β battery Wh (Γ efficiency) β battery %. | |
| value: > | |
| {{ [current_pct + (delivered_kwh * (plug_accuracy_factor | float) | |
| * (charger_efficiency | float / 100) | |
| * 1000 / battery_capacity_wh | float * 100), | |
| target_pct] | min | round(1) }} | |
| until: | |
| - condition: template | |
| value_template: > | |
| {% set past_cutoff = cutoff_dt != 'none' and now() >= cutoff_dt | as_datetime %} | |
| {{ wait.trigger is not none or past_cutoff }} | |
| # Determine why we stopped. wait.trigger.id tells us which exit fired | |
| # (target_reached / manual / power_dropped); if it's None we hit the cutoff. | |
| # power_dropped splits into charger_finished vs interrupted based on how | |
| # close we got to the target. | |
| - variables: | |
| final_delivered_kwh: "{{ states(energy_sensor) | float(0) - start_energy }}" | |
| exit_reason: >- | |
| {{ (('charger_finished' if final_delivered_kwh >= needed_kwh * 0.9 | |
| else 'interrupted') | |
| if (wait.trigger is not none and wait.trigger.id == 'power_dropped') | |
| else (wait.trigger.id if wait.trigger is not none else 'cutoff')) }} | |
| - service: switch.turn_off | |
| target: | |
| entity_id: !input charger_switch | |
| # Final accurate helper update and notification. final_wall_wh is the | |
| # corrected actual energy (reported Γ plug_accuracy_factor), so the cost | |
| # line and battery estimate reflect reality, not the plug's biased reading. | |
| # final_pct is computed here once and reused by both the helper-update | |
| # service call and the notification. | |
| - variables: | |
| final_wall_wh: > | |
| {{ (final_delivered_kwh * 1000 * (plug_accuracy_factor | float)) | round(0) }} | |
| final_battery_wh: > | |
| {{ (final_wall_wh | float * (charger_efficiency | float / 100)) | round(0) }} | |
| charge_cost: > | |
| {{ (final_wall_wh | float / 1000 * price_per_kwh | float) | round(3) }} | |
| # When the charger says it's done (target reached or power dropped near | |
| # target), trust that and pin to target_pct. Otherwise use the | |
| # calculated estimate β best info we have for partial charges. | |
| final_pct: >- | |
| {{ target_pct | float | round(1) | |
| if exit_reason in ['target_reached', 'charger_finished'] | |
| else ([current_pct + (final_battery_wh | float | |
| / battery_capacity_wh | float * 100), | |
| target_pct] | min | round(1)) }} | |
| - service: input_number.set_value | |
| target: | |
| entity_id: !input current_level_helper | |
| data: | |
| value: "{{ final_pct }}" | |
| # Auto-reset the ride target after a successful ride charge so a one-off | |
| # bump to 100% doesn't carry over. Skipped on storage charges (different | |
| # target entirely), and on interrupted/cutoff/manual exits (likely want | |
| # to retry tomorrow with the same target). Also skipped if the target is | |
| # already at the reset value, to avoid a noise state-change event. | |
| - variables: | |
| target_was_reset: > | |
| {{ enable_target_reset | |
| and charge_type == 'ride' | |
| and exit_reason in ['target_reached', 'charger_finished'] | |
| and (target_reset_pct | float) != (target_pct | float) }} | |
| - if: | |
| - condition: template | |
| value_template: "{{ target_was_reset }}" | |
| then: | |
| - service: input_number.set_value | |
| target: | |
| entity_id: !input charge_target_helper | |
| data: | |
| value: "{{ target_reset_pct }}" | |
| - service: notify.persistent_notification | |
| data: | |
| title: "π² E-Bike {{ 'π Storage' if charge_type == 'storage' else 'π΄ Ride' }} Charge {{ 'Interrupted' if exit_reason == 'interrupted' else 'Complete' }}" | |
| message: > | |
| π Charged from {{ current_pct | round(1) }}% β {{ final_pct }}% | |
| (target {{ target_pct | round(1) }}%). | |
| β‘ Drew {{ final_wall_wh }} Wh from the wall, delivered ~{{ final_battery_wh }} Wh | |
| to the battery ({{ charger_efficiency }}% efficiency). | |
| Needed {{ (needed_kwh * 1000) | round(0) }} Wh from the wall. | |
| π° Estimated cost: {{ charge_cost }} (@ {{ price_per_kwh }}/kWh). | |
| {% if exit_reason == 'target_reached' %}β Target reached. | |
| {% elif exit_reason == 'charger_finished' %}π Charger finished β power dropped near target. | |
| {% elif exit_reason == 'interrupted' %}β οΈ Charge interrupted β battery may have been disconnected (power dropped well before reaching target). | |
| {% elif exit_reason == 'cutoff' %}β° Stopped at cutoff time. | |
| {% else %}π Stopped early β plug turned off manually.{% endif %} | |
| {% if target_was_reset %} | |
| π Target reset to {{ target_reset_pct }}% for next charge.{% endif %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment