Skip to content

Commit 789bb30

Browse files
author
Antonio ...
committed
feat: v2.6.0 — mass bug fixing & improvement (issues #36–#42)
Grouped release closing seven reports. Fully backward compatible — the two new features are opt-in; the bug fixes are transparent. - #37 (bug): daily SOC-target numbers used non-existent mdi:calendar-<weekday> icons → empty icon in HA. Map to valid mdi:calendar-week / -weekend. - #39 (bug): Hybrid Mode PRIORITY_EV_FREE guard compared soc_home < 100 instead of the configured evsc_hybrid_battery_full_threshold (inconsistent with the IDLE-entry / keep-alive guards). Now uses threshold (default 95% → no-op or more permissive). - #41 (bug, cosmetic): telemetry logged the GAS 302 redirect HTML as a raw body before the status check → looked like an error. Skip the body on the 302 happy path; keep it for 2xx/errors. - #40 (improvement, detection-only): idle INFO log flood (~48k lines/day). Solar Surplus / Priority Balancer / Night Smart Charge now log the verbose block at INFO only on actionable / approaching ticks, DEBUG otherwise. Diagnostic sensors still update every tick. No behaviour change. - #38 (enhancement): self-tuning RIDING_EDGE step-up for Generic chargers — jump 2 levels after 2 consecutive stable single-level step-ups (Tuya unaffected). - #36 (feature/safety): optional grid_available binary_sensor. When OFF (debounced) Night Smart Charge grid mode stops terminally so it doesn't drain the home battery during a grid outage on hybrid Battery First/UPS inverters. Fail-safe reader (is_grid_available): unmapped or unavailable/unknown → None → never a spurious stop. New STOP_REASON_GRID_LOSS. - #42 (feature): customizable nighttime window via two opt-in numbers (evsc_nighttime_sunset_offset / _sunrise_offset, 0–120 min, default 0). Positive values extend the night window that gates Solar Surplus. Entity counts: +2 always-created numbers (#42) → 69 / 55. #36 is a mapped sensor (counts unchanged). Tests: new tests/test_v260_fixes.py (#36 fail-safe reader, #42 offsets); test_config_flow counts 69/55. Full suite green except the pre-existing environment-only baseline failures (identical on clean master). VERSION = "2.6.0".
1 parent df2bda3 commit 789bb30

23 files changed

Lines changed: 658 additions & 81 deletions

‎CLAUDE.md‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,109 @@ async def _set_amperage(self, target_amperage: int):
757757

758758
## Version History
759759

760+
### v2.6.0 (2026-06-06)
761+
**Mass bug-fixing & improvement release — 7 issues (#36–#42)**
762+
763+
A grouped release closing seven reports (six from DJm00n, one from xion2000):
764+
three confirmed bugs, one log-noise cleanup, one self-tuning enhancement and two
765+
opt-in features. Fully backward compatible — existing installs see no behaviour
766+
change unless they map the new optional sensor or set the new numbers.
767+
768+
**#37 — Invalid MDI day icons (BUG).** The daily SOC-target numbers built
769+
`mdi:calendar-{weekday}` icons (`mdi:calendar-monday`…`-sunday`) that do not
770+
exist in Material Design Icons, so HA rendered an empty icon. [number.py](custom_components/ev_smart_charger/number.py)
771+
now maps each day to a valid icon (`mdi:calendar-week` weekdays / `mdi:calendar-weekend`
772+
weekend). No entity/behaviour change.
773+
774+
**#39 — Hybrid Mode EV_FREE guard hardcoded to 100% (BUG).** In
775+
[hybrid_inverter_mode.py](custom_components/ev_smart_charger/hybrid_inverter_mode.py)
776+
the `PRIORITY_EV_FREE` override guard compared `soc_home < 100` while the
777+
IDLE-entry and keep-alive guards used `evsc_hybrid_battery_full_threshold`. On
778+
large BMS-managed batteries that sit at 98–99% before briefly touching 100%, a
779+
user who set the threshold to 98 saw the probe refused. Both sites now use the
780+
configured `threshold` (default 95% → more permissive than 100, fully
781+
backward-compatible).
782+
783+
**#41 — Telemetry 302 raw body looked like an error (BUG, cosmetic).**
784+
[telemetry.py](custom_components/ev_smart_charger/telemetry.py) logged the raw
785+
response body *before* the status check, so the standard GAS redirect HTML
786+
(`<!-- GSE Default Error --> Moved Temporarily`) printed on every successful
787+
302 and read as a failure. The raw-body log now runs only for non-302 (2xx /
788+
error) responses; the 302 happy path logs a single success line. Delivery/retry
789+
logic unchanged.
790+
791+
**#40 — INFO log flood at idle (~48k lines/day) (IMPROVEMENT, detection-only).**
792+
Three components logged verbose INFO blocks on every periodic tick even when
793+
nothing changed. Now they emit the full block at INFO only when something
794+
actionable happens, and a single DEBUG line otherwise — no behaviour change, and
795+
the diagnostic sensors still update every tick.
796+
- [solar_surplus.py](custom_components/ev_smart_charger/solar_surplus.py): the
797+
sensor/decision readout is consolidated after `target_amps` is finalized and
798+
gated on `target_amps != current_amps`; the per-tick header, "Priority
799+
Balancer:" and "Both targets met" lines and the "Amperage optimal at 0A"
800+
confirmation are DEBUG on no-op ticks.
801+
- [priority_balancer.py](custom_components/ev_smart_charger/priority_balancer.py):
802+
the decision block logs at INFO only on the first call and on a state
803+
transition (reusing `_last_priority`); stable ticks emit one DEBUG line. The
804+
diagnostic-telemetry and change-notification paths are untouched.
805+
- [night_smart_charge.py](custom_components/ev_smart_charger/night_smart_charge.py):
806+
the "WINDOW CHECK DIAGNOSTIC" block (a v1.4.2 debug relic) is now INFO only
807+
when the window is active or within 30 minutes of opening; otherwise DEBUG.
808+
809+
**#38 — Slow RIDING_EDGE convergence on Generic chargers (ENHANCEMENT,
810+
self-tuning).** Generic chargers step in 1 A increments, so RIDING_EDGE took
811+
~11 ticks (6→17 A) vs ~5 for Tuya. [hybrid_inverter_mode.py](custom_components/ev_smart_charger/hybrid_inverter_mode.py)
812+
now tracks consecutive stable step-ups (`_consecutive_stepup_count`) and, on a
813+
Generic charger only, jumps 2 levels per tick after 2 consecutive single-level
814+
step-ups (capped at `solar_max_amperage`). The counter resets on any step-down
815+
and on every state transition. Tuya is unaffected (its levels are already
816+
coarse). No new entity — counts unchanged.
817+
818+
**#36 — Grid loss drains the home battery (FEATURE / SAFETY).** On hybrid
819+
"Battery First"/UPS inverters a grid outage is invisible to the integration
820+
(all power sensors keep reporting), so Night Smart Charge grid mode keeps
821+
drawing — now from the home battery — during the outage. New **optional
822+
`grid_available` binary_sensor** (mapped in the Sensors step): when it reads OFF
823+
(debounced for the grid-import delay, default 30 s) the grid-mode session stops
824+
terminally. **Fail-safe** ([power_model.py](custom_components/ev_smart_charger/power_model.py)
825+
`is_grid_available`): an unmapped sensor OR an `unavailable`/`unknown` state
826+
returns `None` and never triggers a stop (so a boot-time / inverter-restart
827+
`unavailable` cannot spuriously kill the session). Solar Surplus and Hybrid
828+
Mode need no change (they key off PV surplus). New `STOP_REASON_GRID_LOSS`;
829+
unmapped → byte-for-byte legacy behaviour. No new helper entity (counts
830+
unchanged).
831+
832+
**#42 — Customizable nighttime period (FEATURE).** Two opt-in numbers,
833+
`evsc_nighttime_sunset_offset` and `evsc_nighttime_sunrise_offset` (minutes,
834+
0–120, default 0). Positive values **extend** the nighttime window that gates
835+
Solar Surplus (the "SKIPPED: Nighttime" decision): night starts this many
836+
minutes before sunset and ends this many minutes after sunrise.
837+
`AstralTimeService.is_nighttime` takes the offsets (default 0 = astronomical,
838+
byte-for-byte legacy); only the Solar Surplus call passes them — the Smart
839+
Blocker window and Hybrid Mode daytime gate are intentionally unchanged.
840+
841+
**Entity counts**: +2 always-created numbers (issue #42) →
842+
`TOTAL_INTEGRATION_ENTITIES` 67→**69**, `TOTAL_INTEGRATION_ENTITIES_NO_BATTERY`
843+
53→**55**. #36 is a mapped sensor, not a helper (counts unchanged).
844+
845+
**Files**: `number.py` (#37, #42), `hybrid_inverter_mode.py` (#39, #38),
846+
`telemetry.py` (#41), `solar_surplus.py` (#40, #42), `priority_balancer.py`
847+
(#40), `night_smart_charge.py` (#40, #36), `power_model.py` (#36),
848+
`config_flow.py` (#36), `dashboard_manager.py` (#36), `utils/astral_time_service.py`
849+
(#42), `const.py`, `strings.json` + `translations/{en,it,nl}.json`,
850+
`frontend/ev-smart-charger-dashboard.js` (#42 steppers + #36 whitelist),
851+
`manifest.json`; tests: new `tests/test_v260_fixes.py` (#36 fail-safe reader,
852+
#42 offsets), `tests/test_config_flow.py` (counts 69/55). `VERSION = "2.6.0"`.
853+
Full suite green except the pre-existing environment-only baseline failures
854+
(identical on clean master).
855+
856+
**Upgrade priority**: 🟢 RECOMMENDED for hybrid-inverter installs (map
857+
`grid_available` for outage protection #36) and anyone bothered by the INFO log
858+
flood (#40). ⚪ Otherwise low-impact — the bug fixes are transparent and the two
859+
features are opt-in.
860+
861+
---
862+
760863
### v2.5.1 (2026-06-04)
761864
**FIX: Stale "SKIPPED: Nighttime" on the Solar Surplus diagnostic + self-diagnosable astral times ([issue #34](https://github.com/antbald/ha-ev-smart-charger/issues/34), xion2000)**
762865

‎README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Most projects target one charger brand or assume a single-phase home. EV Smart C
5454
5555
> **🔋 New in v2.4.0** — the same **battery-power sensor** now also protects **Night Smart Charge grid mode**. On hybrid "Battery First" inverters, grid-mode charging can silently drain the home battery (grid stays ≈ 0 W) instead of pulling from the grid; v2.4.0 stops the session once the battery is being drained *and* has reached `home_battery_min_soc`, making that floor effective in grid mode just like it already is in battery mode. **No-op without the sensor.** Solves [issue #33](https://github.com/antbald/ha-ev-smart-charger/issues/33).
5656
57+
> **🆕 New in v2.6.0** — mass bug-fix & improvement release (issues #36–#42). Highlights: an optional **`grid_available` binary sensor** ([issue #36](https://github.com/antbald/ha-ev-smart-charger/issues/36)) stops Night Smart Charge grid mode during a **grid outage** so it doesn't drain the home battery on hybrid Battery First / UPS inverters (fail-safe: an unavailable sensor never triggers a false stop); a **customizable nighttime window** ([issue #42](https://github.com/antbald/ha-ev-smart-charger/issues/42)) via two opt-in offset numbers (start before sunset / end after sunrise); and a big **INFO log-noise reduction** ([issue #40](https://github.com/antbald/ha-ev-smart-charger/issues/40)) — idle ticks no longer flood the HA log. Also: valid day icons (#37), faster Generic-charger ramp-up (#38), a consistent Hybrid full-battery threshold (#39), and a cleaner telemetry log (#41). **All backward compatible** — the two features are opt-in.
58+
5759
---
5860

5961
## 📸 Preview
@@ -416,9 +418,12 @@ The charger switch entity is used as the unique ID for the config entry. Adding
416418
| Home consumption | Yes | `W` |
417419
| Grid import | Yes | `W` |
418420
| EV charging power (v2.2.0) | No | `W` |
421+
| Grid available (v2.6.0) | No | `binary_sensor` |
419422

420423
**EV charging power (v2.2.0):** the most reliable signal for whether the car is actually drawing current — when mapped it becomes the single source of truth for charging detection (and the dashboard's green "EV charging" banner), overriding the status string. In three-phase mode map all three per-phase sensors (they are summed) or leave all blank. The sensor must report positive watts while charging; if yours reports negative, wrap it in a template sensor (`{{ states('sensor.your_power') | float(0) | abs }}`) — the diagnostic sensor's `charging_power_w` will read a flat 0 W if the sign is reversed.
421424

425+
**Grid available (v2.6.0 — [issue #36](https://github.com/antbald/ha-ev-smart-charger/issues/36)):** optional `binary_sensor` (on = grid present, off = grid lost), typically exposed by hybrid inverter integrations (e.g. `binary_sensor.inverter_grid`). On hybrid "Battery First"/UPS inverters a grid outage is invisible to the integration (every power sensor keeps reporting), so Night Smart Charge **grid mode** would keep drawing — now from the home battery — during the outage. When mapped and OFF (sustained for the grid-import delay, default 30 s), the grid-mode session stops. **Fail-safe:** an `unavailable`/`unknown` state (e.g. at HA boot or an inverter-integration restart) is treated as "unknown" and never triggers a stop. Leave empty to keep the previous behaviour.
426+
422427
### Step 4 — PV Forecast
423428

424429
| Field | Required | Unit |
@@ -554,6 +559,8 @@ When a day's flag is **ON** and the EV target is not yet reached at sunrise, Nig
554559
| `evsc_night_charge_amperage` | `16` | `6–32` | A | Amperage for overnight charging sessions. |
555560
| `evsc_min_solar_forecast_threshold` | `20` | `0–100` | kWh | If tomorrow's forecast ≥ this value, Night Smart Charge uses home battery mode instead of grid mode. |
556561
| `evsc_night_pv_handoff_threshold` | `0` | `0–5000` | W | **v2.3.0 (opt-in).** `0` = disabled (stop at astronomical sunrise). When > 0, on `car_ready` OFF days Night Smart Charge continues past sunrise and hands off to Solar Surplus once measured `fv_production` stays ≥ this value for 5 min (hard-capped at `evsc_car_ready_time`). Recommended ~200 W, above your inverter's idle reading. See the high-latitude note in [Night Smart Charge](#night-smart-charge). |
562+
| `evsc_nighttime_sunset_offset` | `0` | `0–120` | min | **v2.6.0 (opt-in — [issue #42](https://github.com/antbald/ha-ev-smart-charger/issues/42)).** Minutes **before** sunset at which the nighttime window starts. `0` = astronomical sunset. Extends the window where Solar Surplus is skipped ("SKIPPED: Nighttime"). |
563+
| `evsc_nighttime_sunrise_offset` | `0` | `0–120` | min | **v2.6.0 (opt-in — [issue #42](https://github.com/antbald/ha-ev-smart-charger/issues/42)).** Minutes **after** sunrise at which the nighttime window ends. `0` = astronomical sunrise. Delays when Solar Surplus resumes in the morning. |
557564

558565
---
559566

‎custom_components/ev_smart_charger/config_flow.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
CONF_FV_PRODUCTION,
2525
CONF_FV_PRODUCTION_L2,
2626
CONF_FV_PRODUCTION_L3,
27+
CONF_GRID_AVAILABLE,
2728
CONF_GRID_IMPORT,
2829
CONF_GRID_IMPORT_L2,
2930
CONF_GRID_IMPORT_L3,
@@ -259,6 +260,14 @@ def _sensor_schema(
259260
vol.Optional(CONF_CHARGING_POWER_L3, **_field_config(current_data.get(CONF_CHARGING_POWER_L3)))
260261
] = _entity_selector("sensor")
261262

263+
# v2.6.0 (issue #36): optional grid-availability binary_sensor. When mapped
264+
# and OFF, Night Smart Charge grid mode stops (avoids draining the home
265+
# battery during a grid outage on hybrid Battery First/UPS inverters).
266+
# Unmapped → byte-for-byte legacy behaviour.
267+
fields[
268+
vol.Optional(CONF_GRID_AVAILABLE, **_field_config(current_data.get(CONF_GRID_AVAILABLE)))
269+
] = _entity_selector("binary_sensor")
270+
262271
return vol.Schema(fields)
263272

264273

‎custom_components/ev_smart_charger/const.py‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# ========== INTEGRATION METADATA ==========
44
DOMAIN = "ev_smart_charger"
5-
VERSION = "2.5.1"
5+
VERSION = "2.6.0"
66
DEFAULT_NAME = "EV Smart Charger"
77
FRONTEND_URL_BASE = "/api/ev_smart_charger/frontend"
88
FRONTEND_CARD_FILENAME = "ev-smart-charger-dashboard.js"
@@ -111,6 +111,13 @@
111111
CONF_HOME_CONSUMPTION = "home_consumption"
112112
CONF_GRID_IMPORT = "grid_import"
113113
CONF_PV_FORECAST = "pv_forecast"
114+
# v2.6.0 (issue #36): optional binary_sensor reporting grid availability
115+
# (on = grid present, off = grid lost). On hybrid "Battery First"/UPS inverters
116+
# a grid outage is invisible to the integration (all power sensors keep
117+
# reporting), so Night Smart Charge grid mode would keep draining the home
118+
# battery. When mapped and OFF (sustained), the night session stops. Unmapped =
119+
# byte-for-byte legacy behaviour.
120+
CONF_GRID_AVAILABLE = "grid_available"
114121

115122
# Phase mode + per-phase sensors (v2.0.0). The existing keys above act as L1
116123
# (so single-phase installs are byte-for-byte unchanged); L2/L3 are only mapped
@@ -206,6 +213,14 @@
206213
# Battery-only helper (meaningless without a home battery).
207214
HELPER_MAX_BATTERY_DISCHARGE_FOR_EV_SUFFIX = "evsc_max_battery_discharge_for_ev"
208215

216+
# Numbers - Nighttime window (v2.6.0, issue #42). Customise the "nighttime"
217+
# period that gates Solar Surplus (the "SKIPPED: Nighttime" decision). Both
218+
# default to 0 = astronomical sunset/sunrise (legacy behaviour). Positive values
219+
# EXTEND the night window: it starts <sunset_offset> minutes before sunset and
220+
# ends <sunrise_offset> minutes after sunrise.
221+
HELPER_NIGHTTIME_SUNSET_OFFSET_SUFFIX = "evsc_nighttime_sunset_offset"
222+
HELPER_NIGHTTIME_SUNRISE_OFFSET_SUFFIX = "evsc_nighttime_sunrise_offset"
223+
209224
# Numbers - Night Smart Charge
210225
HELPER_NIGHT_CHARGE_AMPERAGE_SUFFIX = "evsc_night_charge_amperage"
211226
HELPER_MIN_SOLAR_FORECAST_THRESHOLD_SUFFIX = "evsc_min_solar_forecast_threshold"
@@ -313,6 +328,12 @@
313328
# Sustained seconds PV must stay >= threshold before handing off (internal, not a helper).
314329
NIGHT_PV_HANDOFF_SUSTAIN_SECONDS = 300 # 5 minutes
315330

331+
# v2.6.0 (issue #42): nighttime window offsets (minutes). 0 = astronomical
332+
# sunset/sunrise (legacy). Positive extends the night: starts this many minutes
333+
# before sunset / ends this many minutes after sunrise.
334+
DEFAULT_NIGHTTIME_SUNSET_OFFSET = 0 # minutes before sunset
335+
DEFAULT_NIGHTTIME_SUNRISE_OFFSET = 0 # minutes after sunrise
336+
316337
# ========== NIGHT SMART CHARGE RETRY SETTINGS (v1.6.1) ==========
317338
NIGHT_CHARGE_START_MAX_RETRIES = 3 # Maximum attempts to start charger
318339
NIGHT_CHARGE_START_RETRY_DELAYS = [5, 15, 30] # Seconds between retry attempts (backoff)
@@ -391,21 +412,23 @@
391412
# v1.8.0 set the baseline to 64 (added 6 Hybrid Mode entities). v1.11.9 added
392413
# 1 sensor (evsc_night_session_state) → 65. v2.1.0 (issue #29) adds 1 battery-only
393414
# number (evsc_max_battery_discharge_for_ev) → 66. v2.3.0 (issue #32) adds 1
394-
# always-created number (evsc_night_pv_handoff_threshold) → 67.
415+
# always-created number (evsc_night_pv_handoff_threshold) → 67. v2.6.0 (issue #42)
416+
# adds 2 always-created numbers (nighttime sunset/sunrise offsets) → 69.
395417
# COUPLING (issue #22): the disabled-helper tolerance in
396418
# __init__._async_wait_for_helper_registration assumes this equals the number
397419
# of entities actually created when nothing is disabled. If it drifts above
398420
# reality (cf. v1.6.20), a single user-disabled entity turns the tolerant
399421
# startup path back into a hard ConfigEntryNotReady. Keep this in sync.
400-
TOTAL_INTEGRATION_ENTITIES = 67
422+
TOTAL_INTEGRATION_ENTITIES = 69
401423
# Verified count (v2.3.0): 53 entities when running in PV-only mode.
402424
# Unchanged in v2.1.0: the discharge number is battery-only (skipped in PV-only mode).
403425
# v2.3.0 (issue #32): evsc_night_pv_handoff_threshold is NOT battery-only → +1 → 53.
426+
# v2.6.0 (issue #42): 2 nighttime offset numbers are NOT battery-only → +2 → 55.
404427
# Skipped helpers (13): 2 switches (use_home_battery, preserve_home_battery),
405428
# 3 numbers (home_battery_min_soc, battery_support_amperage, battery_support_sunset_buffer),
406429
# 7 daily home min SOC numbers (Monday–Sunday), 1 sensor (today_home_target).
407430
# Hybrid Mode entities are still created in PV-only mode but stay IDLE (requires soc_home).
408-
TOTAL_INTEGRATION_ENTITIES_NO_BATTERY = 53
431+
TOTAL_INTEGRATION_ENTITIES_NO_BATTERY = 55
409432

410433

411434
def has_home_battery(config: dict) -> bool:

‎custom_components/ev_smart_charger/dashboard_manager.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
CONF_FV_PRODUCTION,
6464
CONF_FV_PRODUCTION_L2,
6565
CONF_FV_PRODUCTION_L3,
66+
CONF_GRID_AVAILABLE,
6667
CONF_GRID_IMPORT,
6768
CONF_GRID_IMPORT_L2,
6869
CONF_GRID_IMPORT_L3,
@@ -237,6 +238,8 @@ def _build_card_config(entry: ConfigEntry) -> dict[str, Any]:
237238
"pv_forecast_tomorrow_entity": CONF_PV_FORECAST_TOMORROW,
238239
# v2.1.0 (issue #29): optional signed battery-power sensor.
239240
"battery_power_entity": CONF_BATTERY_POWER,
241+
# v2.6.0 (issue #36): optional grid-availability binary_sensor.
242+
"grid_available_entity": CONF_GRID_AVAILABLE,
240243
}
241244
for card_key, conf_key in mapping.items():
242245
value = data.get(conf_key)

0 commit comments

Comments
 (0)