Skip to content

Commit 94e6a17

Browse files
author
Antonio ...
committed
feat: Add car ready flags and battery pre-check (v1.3.13)
Add daily "Car Ready" flags for intelligent night charge fallback/skip behavior and pre-check home battery SOC before starting battery mode. Problem Fixed: - When PV forecast good but home battery already below 20% threshold, system would start charging for 15 seconds before monitor detected the issue and stopped charging - No user control over fallback behavior (always used grid) Enhancements: - Pre-check home battery SOC BEFORE starting charger in battery mode - 7 new switches for daily car ready flags (Monday-Sunday) - User can now choose per-day behavior when battery insufficient: * Car ready = TRUE: Fallback to GRID MODE (ensures car ready in morning) * Car ready = FALSE: SKIP charging (wait for solar surplus) New Entities (7 switches): - switch.evsc_car_ready_monday through evsc_car_ready_sunday - Icon: mdi:car-clock - Defaults: Mon-Fri = TRUE (work days), Sat-Sun = FALSE (weekends) Pre-Check Logic (_start_battery_charge): 1. Validate home battery SOC before starting charger 2. If SOC <= threshold AND car_ready = TRUE → Fallback to GRID MODE 3. If SOC <= threshold AND car_ready = FALSE → SKIP (wait for solar) 4. If SOC > threshold → Proceed with BATTERY MODE normally Technical Changes: - const.py: Added HELPER_CAR_READY_*_SUFFIX constants and defaults - switch.py: Created 7 new car ready switch entities - night_smart_charge.py: Added pre-check logic in _start_battery_charge() - night_smart_charge.py: Added _get_car_ready_for_today() helper method - night_smart_charge.py: Added car_ready entity discovery in async_setup() - logging_helper.py: Added CAR emoji alias for EV - manifest.json: Version bump to 1.3.13 - CLAUDE.md: Documented v1.3.13 changes and new behavior Behavior Table: | PV Forecast | Home SOC | Car Ready | Action | |-------------|----------|-----------|---------| | >= 20 kWh | > 20% | any | BATTERY MODE | | >= 20 kWh | <= 20% | TRUE | GRID MODE (fallback) | | >= 20 kWh | <= 20% | FALSE | SKIP (wait solar) | | < 20 kWh | any | any | GRID MODE (forecast low) | Benefits: - Prevents unnecessary 15-second battery discharge - User control over fallback strategy (grid vs skip) - Flexible per-day configuration - Better battery protection 🤖 Generated with Claude Code
1 parent 5aeffea commit 94e6a17

6 files changed

Lines changed: 225 additions & 6 deletions

File tree

‎CLAUDE.md‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ The integration creates helper entities automatically via platform files:
100100
- `evsc_use_home_battery` - Enable/disable home battery support in Solar Surplus
101101
- `evsc_priority_balancer_enabled` - Enable/disable Priority Balancer
102102
- `evsc_night_smart_charge_enabled` - Enable/disable Night Smart Charge
103+
- `evsc_car_ready_monday` through `evsc_car_ready_sunday` - Daily flags for night charge fallback/skip behavior (v1.3.13+)
103104
104105
**Numbers (`number.py`):**
105106
- `evsc_check_interval` - Solar Surplus recalculation interval (1-60 min, default 1)
@@ -240,17 +241,27 @@ Updates `evsc_priority_daily_state` sensor with:
240241
**Logic:**
241242
1. Check if enabled and current time >= `evsc_night_charge_time`
242243
2. Check PV forecast vs threshold
243-
3. If forecast sufficient AND `evsc_use_home_battery` ON → battery mode
244+
3. If forecast sufficient AND `evsc_use_home_battery` ON → battery mode (with pre-check)
244245
4. If forecast insufficient OR battery support OFF → grid mode
245246
5. Monitor SOC targets via Priority Balancer
246247
6. Stop when `priority_balancer.is_ev_target_reached()` returns true
247248

249+
**Battery Mode Pre-Check (v1.3.13+):**
250+
- Before starting charger, validates home battery SOC
251+
- If home SOC <= threshold:
252+
- Check car_ready flag for current day (Mon-Sun)
253+
- If car_ready = TRUE → Fallback to GRID MODE (ensures car ready in morning)
254+
- If car_ready = FALSE → SKIP charging (wait for solar surplus)
255+
- Prevents 15-second discharge before monitor detects low battery
256+
248257
**Key Methods:**
249-
- `async_setup()` - Sets up time-based trigger
258+
- `async_setup()` - Sets up time-based trigger, discovers car_ready switches
250259
- `_async_time_trigger()` - Triggered at configured night charge time
260+
- `_start_battery_charge()` - Start battery mode with pre-check logic
251261
- `_battery_mode_monitor()` - Monitor battery-based charging, check targets
252262
- `_grid_mode_monitor()` - Monitor grid-based charging, check targets
253263
- `_should_activate()` - Check activation conditions
264+
- `_get_car_ready_for_today()` - Get car_ready flag for current weekday (v1.3.13+)
254265
- `is_active()` - Public method to check if Night Charge is currently active
255266
- **All charger operations delegated to ChargerController**
256267

@@ -377,7 +388,7 @@ This allows automations to find helper entities regardless of entry_id.
377388

378389
**Integration Metadata:**
379390
- `DOMAIN = "ev_smart_charger"`
380-
- `VERSION = "1.0.3"`
391+
- `VERSION = "1.3.13"`
381392
- `DEFAULT_NAME = "EV Smart Charger"`
382393

383394
**Platforms:**
@@ -441,6 +452,10 @@ This allows automations to find helper entities regardless of entry_id.
441452
- `SERVICE_CALL_TIMEOUT = 10` (seconds for service calls)
442453
- `SMART_BLOCKER_ENFORCEMENT_TIMEOUT = 1800` (30 minutes for retry enforcement)
443454

455+
**Car Ready Defaults (v1.3.13+):**
456+
- `DEFAULT_CAR_READY_WEEKDAY = True` (Monday-Friday: car needed for work)
457+
- `DEFAULT_CAR_READY_WEEKEND = False` (Saturday-Sunday: car not urgently needed)
458+
444459
## Common Development Patterns
445460

446461
### Adding a New Helper Entity
@@ -738,6 +753,27 @@ async def _set_amperage(self, target_amperage: int):
738753

739754
## Version History
740755

756+
### v1.3.13 (2025-11-05)
757+
**Car Ready Flag & Battery Pre-Check**
758+
- Added: Daily "Car Ready" flags for intelligent night charge fallback/skip behavior
759+
- Added: Pre-check of home battery SOC before starting BATTERY MODE to prevent 15-second discharge
760+
- **Problem Fixed**: When PV forecast good but home battery already below 20% threshold, system would start charging for 15 seconds before monitor detected issue
761+
- **Enhancement**: User can now choose per-day behavior when battery insufficient:
762+
- Car ready = TRUE (Mon-Fri default): Use GRID MODE as fallback (ensures car ready in morning)
763+
- Car ready = FALSE (Sat-Sun default): SKIP charging (wait for solar surplus)
764+
- **New Entities** (7 switches created automatically):
765+
- `switch.evsc_car_ready_monday` through `sunday`
766+
- Icon: `mdi:car-clock`
767+
- Defaults: Mon-Fri = TRUE (work days), Sat-Sun = FALSE (weekends)
768+
- **Pre-Check Logic** in `_start_battery_charge()`:
769+
1. Check home battery SOC before starting charger
770+
2. If SOC <= threshold AND car_ready = TRUE → Fallback to GRID MODE
771+
3. If SOC <= threshold AND car_ready = FALSE → SKIP (wait for solar)
772+
4. If SOC > threshold → Proceed with BATTERY MODE normally
773+
- **Helper Method**: `_get_car_ready_for_today()` returns boolean based on current weekday
774+
- **Technical**: Modified `const.py` (constants), `switch.py` (7 switches), `night_smart_charge.py` (pre-check + helper), `logging_helper.py` (CAR emoji), `manifest.json` (version)
775+
- **Upgrade priority**: 🟡 RECOMMENDED for users wanting flexible night charge behavior
776+
741777
### v1.3.12 (2025-11-05)
742778
**CRITICAL FIX: Night Smart Charge Restart Loop & Battery Protection**
743779
- Fixed FIVE critical bugs causing charger restart loops, inadequate battery protection, and excessive logging

‎custom_components/ev_smart_charger/const.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# ========== INTEGRATION METADATA ==========
44
DOMAIN = "ev_smart_charger"
5-
VERSION = "1.3.12"
5+
VERSION = "1.3.13"
66
DEFAULT_NAME = "EV Smart Charger"
77

88
# ========== PLATFORMS ==========
@@ -106,6 +106,15 @@
106106
HELPER_HOME_MIN_SOC_SATURDAY_SUFFIX = "evsc_home_min_soc_saturday"
107107
HELPER_HOME_MIN_SOC_SUNDAY_SUFFIX = "evsc_home_min_soc_sunday"
108108

109+
# Switches - Car Ready Flags (daily)
110+
HELPER_CAR_READY_MONDAY_SUFFIX = "evsc_car_ready_monday"
111+
HELPER_CAR_READY_TUESDAY_SUFFIX = "evsc_car_ready_tuesday"
112+
HELPER_CAR_READY_WEDNESDAY_SUFFIX = "evsc_car_ready_wednesday"
113+
HELPER_CAR_READY_THURSDAY_SUFFIX = "evsc_car_ready_thursday"
114+
HELPER_CAR_READY_FRIDAY_SUFFIX = "evsc_car_ready_friday"
115+
HELPER_CAR_READY_SATURDAY_SUFFIX = "evsc_car_ready_saturday"
116+
HELPER_CAR_READY_SUNDAY_SUFFIX = "evsc_car_ready_sunday"
117+
109118
# Selects
110119
HELPER_CHARGING_PROFILE_SUFFIX = "evsc_charging_profile"
111120

@@ -144,6 +153,10 @@
144153
DEFAULT_NIGHT_CHARGE_AMPERAGE = 16 # amps
145154
NIGHT_CHARGE_COOLDOWN_SECONDS = 3600 # 1 hour - prevent re-evaluation after completion
146155

156+
# ========== DEFAULT VALUES - CAR READY FLAGS ==========
157+
DEFAULT_CAR_READY_WEEKDAY = True # Monday-Friday (car needed for work)
158+
DEFAULT_CAR_READY_WEEKEND = False # Saturday-Sunday (car not urgently needed)
159+
147160
# ========== SMART BLOCKER SETTINGS ==========
148161
SMART_BLOCKER_ENFORCEMENT_TIMEOUT = 1800 # 30 minutes in seconds
149162
SMART_BLOCKER_RETRY_ATTEMPTS = 3

‎custom_components/ev_smart_charger/manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "ev_smart_charger",
33
"name": "EV Smart Charger",
4-
"version": "1.3.12",
4+
"version": "1.3.13",
55
"documentation": "https://github.com/antbald/ha-ev-smart-charger",
66
"issue_tracker": "https://github.com/antbald/ha-ev-smart-charger/issues",
77
"codeowners": ["@antbald"],

‎custom_components/ev_smart_charger/night_smart_charge.py‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
HELPER_MIN_SOLAR_FORECAST_THRESHOLD_SUFFIX,
2222
HELPER_NIGHT_CHARGE_AMPERAGE_SUFFIX,
2323
HELPER_HOME_BATTERY_MIN_SOC_SUFFIX,
24+
HELPER_CAR_READY_MONDAY_SUFFIX,
25+
HELPER_CAR_READY_TUESDAY_SUFFIX,
26+
HELPER_CAR_READY_WEDNESDAY_SUFFIX,
27+
HELPER_CAR_READY_THURSDAY_SUFFIX,
28+
HELPER_CAR_READY_FRIDAY_SUFFIX,
29+
HELPER_CAR_READY_SATURDAY_SUFFIX,
30+
HELPER_CAR_READY_SUNDAY_SUFFIX,
2431
)
2532
from .utils.logging_helper import EVSCLogger
2633
from .utils import entity_helper, state_helper
@@ -72,6 +79,7 @@ def __init__(
7279
self._solar_forecast_threshold_entity = None
7380
self._night_charge_amperage_entity = None
7481
self._home_battery_min_soc_entity = None
82+
self._car_ready_entities = {} # Dict: {0: monday_entity, ..., 6: sunday_entity}
7583

7684
# Timer and state tracking
7785
self._timer_unsub = None
@@ -105,6 +113,25 @@ async def async_setup(self) -> None:
105113
self.hass, HELPER_HOME_BATTERY_MIN_SOC_SUFFIX
106114
)
107115

116+
# Discover car_ready switches for each day (v1.3.13+)
117+
days_suffixes = [
118+
HELPER_CAR_READY_MONDAY_SUFFIX,
119+
HELPER_CAR_READY_TUESDAY_SUFFIX,
120+
HELPER_CAR_READY_WEDNESDAY_SUFFIX,
121+
HELPER_CAR_READY_THURSDAY_SUFFIX,
122+
HELPER_CAR_READY_FRIDAY_SUFFIX,
123+
HELPER_CAR_READY_SATURDAY_SUFFIX,
124+
HELPER_CAR_READY_SUNDAY_SUFFIX,
125+
]
126+
127+
for idx, suffix in enumerate(days_suffixes):
128+
entity = entity_helper.find_by_suffix(self.hass, suffix)
129+
if entity:
130+
self._car_ready_entities[idx] = entity
131+
self.logger.info(f"Found car_ready entity for day {idx}: {entity}")
132+
else:
133+
self.logger.warning(f"Car ready entity not found for day {idx} (suffix: {suffix})")
134+
108135
# Warn about missing entities (backward compatibility)
109136
missing_entities = []
110137
if not self._night_charge_enabled_entity:
@@ -391,6 +418,41 @@ async def _start_battery_charge(self, pv_forecast: float) -> None:
391418
ev_target = self.priority_balancer.get_ev_target_for_today()
392419
threshold = self._get_solar_threshold()
393420

421+
# ========== PRE-CHECK: Home Battery SOC (v1.3.13+) ==========
422+
self.logger.info(f"{self.logger.BATTERY} Pre-check: Validating home battery SOC...")
423+
home_soc = await self.priority_balancer.get_home_current_soc()
424+
425+
if home_soc <= home_min_soc:
426+
# Battery below threshold!
427+
self.logger.warning(
428+
f"{self.logger.ALERT} Home battery below threshold: "
429+
f"{home_soc}% <= {home_min_soc}%"
430+
)
431+
432+
# Check car_ready flag for today
433+
car_ready_today = self._get_car_ready_for_today()
434+
435+
if car_ready_today:
436+
# Car needed → Fallback to GRID
437+
self.logger.warning(
438+
f"{self.logger.CAR} Car ready flag is ON for today → "
439+
f"Fallback to GRID MODE to ensure EV is ready in the morning"
440+
)
441+
await self._start_grid_charge(pv_forecast)
442+
return
443+
else:
444+
# Car not needed → SKIP
445+
self.logger.info(
446+
f"{self.logger.CAR} Car ready flag is OFF for today → "
447+
f"Skipping night charge, will rely on tomorrow's solar surplus"
448+
)
449+
self.logger.info("Night charge session cancelled (waiting for solar)")
450+
self.logger.separator()
451+
return
452+
453+
self.logger.success(f"Home battery SOC check passed: {home_soc}% > {home_min_soc}%")
454+
# ========== END PRE-CHECK ==========
455+
394456
self.logger.info(f" Charger amperage: {amperage}A")
395457
self.logger.info(f" EV target SOC: {ev_target}%")
396458
self.logger.info(f" Home battery minimum SOC: {home_min_soc}%")
@@ -585,6 +647,37 @@ def _get_home_battery_min_soc(self) -> float:
585647
20.0
586648
)
587649

650+
def _get_car_ready_for_today(self) -> bool:
651+
"""
652+
Get car_ready flag for current day (v1.3.13+).
653+
654+
Returns:
655+
True if car needs to be ready in the morning (use grid as fallback)
656+
False if car not needed (skip charging, wait for solar)
657+
"""
658+
# Get current day (0=Monday, 6=Sunday)
659+
current_day = datetime.now().weekday()
660+
661+
# Get entity for today
662+
entity_id = self._car_ready_entities.get(current_day)
663+
664+
if not entity_id:
665+
# Fallback: weekday=True, weekend=False
666+
self.logger.warning(
667+
f"Car ready entity not found for day {current_day}, "
668+
f"using default (weekday=True, weekend=False)"
669+
)
670+
return current_day < 5 # Monday-Friday = True
671+
672+
# Get switch state
673+
car_ready = state_helper.get_bool(self.hass, entity_id, default=True)
674+
675+
day_names = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
676+
day_name = day_names[current_day]
677+
self.logger.info(f"{self.logger.CALENDAR} Car ready flag for {day_name}: {car_ready}")
678+
679+
return car_ready
680+
588681
def _log_configuration(self) -> None:
589682
"""Log current configuration."""
590683
enabled = state_helper.get_bool(self.hass, self._night_charge_enabled_entity) if self._night_charge_enabled_entity else False

‎custom_components/ev_smart_charger/switch.py‎

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
from homeassistant.helpers.restore_state import RestoreEntity
1010
from homeassistant.const import STATE_ON
1111

12-
from .const import DOMAIN, VERSION
12+
from .const import (
13+
DOMAIN,
14+
VERSION,
15+
DEFAULT_CAR_READY_WEEKDAY,
16+
DEFAULT_CAR_READY_WEEKEND,
17+
)
1318

1419
_LOGGER = logging.getLogger(__name__)
1520

@@ -105,6 +110,77 @@ async def async_setup_entry(
105110
)
106111
)
107112

113+
# Car Ready switches (v1.3.13+)
114+
entities.append(
115+
EVSCSwitch(
116+
entry.entry_id,
117+
"evsc_car_ready_monday",
118+
"EVSC Car Ready Monday",
119+
"mdi:car-clock",
120+
default_state=DEFAULT_CAR_READY_WEEKDAY,
121+
)
122+
)
123+
124+
entities.append(
125+
EVSCSwitch(
126+
entry.entry_id,
127+
"evsc_car_ready_tuesday",
128+
"EVSC Car Ready Tuesday",
129+
"mdi:car-clock",
130+
default_state=DEFAULT_CAR_READY_WEEKDAY,
131+
)
132+
)
133+
134+
entities.append(
135+
EVSCSwitch(
136+
entry.entry_id,
137+
"evsc_car_ready_wednesday",
138+
"EVSC Car Ready Wednesday",
139+
"mdi:car-clock",
140+
default_state=DEFAULT_CAR_READY_WEEKDAY,
141+
)
142+
)
143+
144+
entities.append(
145+
EVSCSwitch(
146+
entry.entry_id,
147+
"evsc_car_ready_thursday",
148+
"EVSC Car Ready Thursday",
149+
"mdi:car-clock",
150+
default_state=DEFAULT_CAR_READY_WEEKDAY,
151+
)
152+
)
153+
154+
entities.append(
155+
EVSCSwitch(
156+
entry.entry_id,
157+
"evsc_car_ready_friday",
158+
"EVSC Car Ready Friday",
159+
"mdi:car-clock",
160+
default_state=DEFAULT_CAR_READY_WEEKDAY,
161+
)
162+
)
163+
164+
entities.append(
165+
EVSCSwitch(
166+
entry.entry_id,
167+
"evsc_car_ready_saturday",
168+
"EVSC Car Ready Saturday",
169+
"mdi:car-clock",
170+
default_state=DEFAULT_CAR_READY_WEEKEND,
171+
)
172+
)
173+
174+
entities.append(
175+
EVSCSwitch(
176+
entry.entry_id,
177+
"evsc_car_ready_sunday",
178+
"EVSC Car Ready Sunday",
179+
"mdi:car-clock",
180+
default_state=DEFAULT_CAR_READY_WEEKEND,
181+
)
182+
)
183+
108184
async_add_entities(entities)
109185
_LOGGER.info(f"✅ Created {len(entities)} EVSC switch entities")
110186

‎custom_components/ev_smart_charger/utils/logging_helper.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class EVSCLogger:
2222
DAY = "☀️"
2323
BATTERY = "🔋"
2424
EV = "🚗"
25+
CAR = "🚗" # Alias for EV (v1.3.13+)
2526
HOME = "🏠"
2627
SOLAR = "🌞"
2728
GRID = "⚡"

0 commit comments

Comments
 (0)