Skip to content

Commit d8df032

Browse files
Antonio ...claude
andcommitted
feat: Add Today's Target Sensors for EV and Home Battery (v1.3.26)
New Features: - Added two new sensor entities for easy dashboard integration: - sensor.evsc_today_ev_target: Shows today's EV SOC target (pure numeric value) - sensor.evsc_today_home_target: Shows today's Home battery SOC target (pure numeric value) Technical Details: - Both sensors return numeric values with % unit of measurement - Include 'day' attribute (current weekday name) - Auto-update when Priority Balancer calculates priority - Persist state across HA restarts (RestoreEntity) - Grouped under "EV Smart Charger" device Benefits: - ✅ Simple numeric values perfect for gauge cards, graphs, automations - ✅ No template parsing needed in dashboard - ✅ Automatic daily updates at midnight - ✅ Easy integration with conditional cards and scripts User Questions Answered: 1. Battery Support Amperage check frequency: Maximum 1 minute (periodic check only, no real-time listener) 2. EV_FREE mode behavior: Confirmed - uses ONLY solar surplus, NO home battery support (as designed) 3. Today's targets: Implemented as two separate sensors for maximum dashboard flexibility Files Modified: - const.py: Added HELPER_TODAY_EV_TARGET_SUFFIX, HELPER_TODAY_HOME_TARGET_SUFFIX, VERSION = "1.3.26" - sensor.py: Created EVSCTodayEVTargetSensor and EVSCTodayHomeTargetSensor classes (~106 new lines) - priority_balancer.py: Added sensor discovery and update logic (~15 new lines) - manifest.json: Updated version to 1.3.26 Total Sensors: 6 (was 4, now 6) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2e6f7d4 commit d8df032

4 files changed

Lines changed: 172 additions & 4 deletions

File tree

custom_components/ev_smart_charger/const.py

Lines changed: 3 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.25"
5+
VERSION = "1.3.26"
66
DEFAULT_NAME = "EV Smart Charger"
77

88
# ========== PLATFORMS ==========
@@ -131,6 +131,8 @@
131131
HELPER_PRIORITY_STATE_SUFFIX = "evsc_priority_daily_state"
132132
HELPER_SOLAR_SURPLUS_DIAGNOSTIC_SUFFIX = "evsc_solar_surplus_diagnostic"
133133
HELPER_LOG_FILE_PATH_SUFFIX = "evsc_log_file_path" # v1.3.25
134+
HELPER_TODAY_EV_TARGET_SUFFIX = "evsc_today_ev_target" # v1.3.26
135+
HELPER_TODAY_HOME_TARGET_SUFFIX = "evsc_today_home_target" # v1.3.26
134136

135137
# ========== DEFAULT VALUES - SOLAR SURPLUS ==========
136138
DEFAULT_CHECK_INTERVAL = 1 # minutes

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.25",
4+
"version": "1.3.26",
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/priority_balancer.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
DEFAULT_EV_MIN_SOC_WEEKEND,
1212
DEFAULT_HOME_MIN_SOC,
1313
HELPER_PRIORITY_BALANCER_ENABLED_SUFFIX,
14+
HELPER_TODAY_EV_TARGET_SUFFIX,
15+
HELPER_TODAY_HOME_TARGET_SUFFIX,
1416
PRIORITY_EV,
1517
PRIORITY_HOME,
1618
PRIORITY_EV_FREE,
@@ -42,6 +44,8 @@ def __init__(self, hass: HomeAssistant, entry_id: str, config: dict):
4244
self._enabled_entity = None
4345
self._ev_min_soc_entities = {}
4446
self._home_min_soc_entities = {}
47+
self._today_ev_target_sensor = None # v1.3.26
48+
self._today_home_target_sensor = None # v1.3.26
4549

4650
# Mobile notification service
4751
self._mobile_notifier = MobileNotificationService(
@@ -84,6 +88,19 @@ async def async_setup(self):
8488
self.hass, home_suffix
8589
)
8690

91+
# Discover today's target sensors (v1.3.26)
92+
self._today_ev_target_sensor = entity_helper.find_by_suffix(
93+
self.hass, HELPER_TODAY_EV_TARGET_SUFFIX
94+
)
95+
self._today_home_target_sensor = entity_helper.find_by_suffix(
96+
self.hass, HELPER_TODAY_HOME_TARGET_SUFFIX
97+
)
98+
99+
if self._today_ev_target_sensor:
100+
self.logger.info(f"Discovered Today EV Target sensor: {self._today_ev_target_sensor}")
101+
if self._today_home_target_sensor:
102+
self.logger.info(f"Discovered Today Home Target sensor: {self._today_home_target_sensor}")
103+
87104
self.logger.success("Priority Balancer setup complete")
88105

89106
def is_enabled(self) -> bool:
@@ -305,7 +322,7 @@ async def _update_priority_sensor(
305322
home_target: int,
306323
today: str,
307324
):
308-
"""Update priority state sensor."""
325+
"""Update priority state sensor and today's target sensors (v1.3.26)."""
309326
sensor_entity = entity_helper.find_by_suffix(
310327
self.hass, "evsc_priority_daily_state"
311328
)
@@ -315,7 +332,7 @@ async def _update_priority_sensor(
315332
return
316333

317334
try:
318-
# Update sensor state and attributes
335+
# Update priority state sensor
319336
self.hass.states.async_set(
320337
sensor_entity,
321338
priority,
@@ -330,6 +347,29 @@ async def _update_priority_sensor(
330347
"last_update": datetime.now().isoformat(),
331348
},
332349
)
350+
351+
# Update today's EV target sensor (v1.3.26)
352+
if self._today_ev_target_sensor:
353+
self.hass.states.async_set(
354+
self._today_ev_target_sensor,
355+
ev_target,
356+
{
357+
"day": today.capitalize(),
358+
"unit_of_measurement": "%",
359+
},
360+
)
361+
362+
# Update today's Home target sensor (v1.3.26)
363+
if self._today_home_target_sensor:
364+
self.hass.states.async_set(
365+
self._today_home_target_sensor,
366+
home_target,
367+
{
368+
"day": today.capitalize(),
369+
"unit_of_measurement": "%",
370+
},
371+
)
372+
333373
except Exception as e:
334374
self.logger.error(f"Failed to update priority sensor: {e}")
335375

custom_components/ev_smart_charger/sensor.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ async def async_setup_entry(
6363
)
6464
)
6565

66+
# Create Today EV Target Sensor (v1.3.26)
67+
entities.append(
68+
EVSCTodayEVTargetSensor(
69+
entry.entry_id,
70+
"evsc_today_ev_target",
71+
"EVSC Today EV Target",
72+
"mdi:battery-charging-80",
73+
)
74+
)
75+
76+
# Create Today Home Target Sensor (v1.3.26)
77+
entities.append(
78+
EVSCTodayHomeTargetSensor(
79+
entry.entry_id,
80+
"evsc_today_home_target",
81+
"EVSC Today Home Target",
82+
"mdi:home-battery",
83+
)
84+
)
85+
6686
async_add_entities(entities)
6787
_LOGGER.info(f"✅ Created {len(entities)} EVSC sensors")
6888

@@ -277,3 +297,109 @@ async def async_added_to_hass(self) -> None:
277297
await super().async_added_to_hass()
278298
_LOGGER.info(f"✅ Log File Path sensor registered: {self.entity_id} (unique_id: {self.unique_id})")
279299
_LOGGER.info(f" 📄 Log file path: {self._attr_native_value}")
300+
301+
302+
class EVSCTodayEVTargetSensor(SensorEntity, RestoreEntity):
303+
"""EVSC Today EV Target Sensor - shows today's EV SOC target (v1.3.26)."""
304+
305+
_attr_should_poll = False
306+
307+
def __init__(
308+
self,
309+
entry_id: str,
310+
suffix: str,
311+
name: str,
312+
icon: str,
313+
) -> None:
314+
"""Initialize the sensor."""
315+
self._entry_id = entry_id
316+
self._attr_unique_id = f"{DOMAIN}_{entry_id}_{suffix}"
317+
self._attr_name = name
318+
self._attr_icon = icon
319+
self._attr_native_value = None
320+
self._attr_native_unit_of_measurement = "%"
321+
self._attr_extra_state_attributes = {}
322+
# Set explicit entity_id to match pattern
323+
self.entity_id = f"sensor.{DOMAIN}_{entry_id}_{suffix}"
324+
325+
@property
326+
def device_info(self):
327+
"""Return device info to group all entities under one device."""
328+
return {
329+
"identifiers": {(DOMAIN, self._entry_id)},
330+
"name": "EV Smart Charger",
331+
"manufacturer": "antbald",
332+
"model": "EV Smart Charger",
333+
"sw_version": VERSION,
334+
}
335+
336+
@property
337+
def extra_state_attributes(self) -> dict:
338+
"""Return the state attributes."""
339+
return self._attr_extra_state_attributes
340+
341+
async def async_added_to_hass(self) -> None:
342+
"""Restore last state."""
343+
await super().async_added_to_hass()
344+
_LOGGER.info(f"✅ Today EV Target sensor registered: {self.entity_id} (unique_id: {self.unique_id})")
345+
346+
if (last_state := await self.async_get_last_state()) is not None:
347+
try:
348+
self._attr_native_value = float(last_state.state) if last_state.state not in [None, "unknown", "unavailable"] else None
349+
except (ValueError, TypeError):
350+
self._attr_native_value = None
351+
if last_state.attributes:
352+
self._attr_extra_state_attributes = dict(last_state.attributes)
353+
354+
355+
class EVSCTodayHomeTargetSensor(SensorEntity, RestoreEntity):
356+
"""EVSC Today Home Target Sensor - shows today's Home battery SOC target (v1.3.26)."""
357+
358+
_attr_should_poll = False
359+
360+
def __init__(
361+
self,
362+
entry_id: str,
363+
suffix: str,
364+
name: str,
365+
icon: str,
366+
) -> None:
367+
"""Initialize the sensor."""
368+
self._entry_id = entry_id
369+
self._attr_unique_id = f"{DOMAIN}_{entry_id}_{suffix}"
370+
self._attr_name = name
371+
self._attr_icon = icon
372+
self._attr_native_value = None
373+
self._attr_native_unit_of_measurement = "%"
374+
self._attr_extra_state_attributes = {}
375+
# Set explicit entity_id to match pattern
376+
self.entity_id = f"sensor.{DOMAIN}_{entry_id}_{suffix}"
377+
378+
@property
379+
def device_info(self):
380+
"""Return device info to group all entities under one device."""
381+
return {
382+
"identifiers": {(DOMAIN, self._entry_id)},
383+
"name": "EV Smart Charger",
384+
"manufacturer": "antbald",
385+
"model": "EV Smart Charger",
386+
"sw_version": VERSION,
387+
}
388+
389+
@property
390+
def extra_state_attributes(self) -> dict:
391+
"""Return the state attributes."""
392+
return self._attr_extra_state_attributes
393+
394+
async def async_added_to_hass(self) -> None:
395+
"""Restore last state."""
396+
await super().async_added_to_hass()
397+
_LOGGER.info(f"✅ Today Home Target sensor registered: {self.entity_id} (unique_id: {self.unique_id})")
398+
399+
if (last_state := await self.async_get_last_state()) is not None:
400+
try:
401+
self._attr_native_value = float(last_state.state) if last_state.state not in [None, "unknown", "unavailable"] else None
402+
except (ValueError, TypeError):
403+
self._attr_native_value = None
404+
if last_state.attributes:
405+
self._attr_extra_state_attributes = dict(last_state.attributes)

0 commit comments

Comments
 (0)