Skip to content

Commit f91c871

Browse files
author
marq24
committed
- update sessions @ xx:55 and every xx:00
- additional settings in Integration to enable/disable additional vehicle and meter sensors
1 parent 25aa2cc commit f91c871

8 files changed

Lines changed: 102 additions & 26 deletions

File tree

custom_components/evcc_intg/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
SERVICE_DEL_VEHICLE_PLAN,
6767
CONF_INCLUDE_EVCC,
6868
CONF_USE_WS,
69+
CONF_EXTENDED_VEHICLE_DATA,
70+
CONF_EXTENDED_METER_DATA,
6971
CONF_PURGE_ALL,
7072
CONFIG_VERSION,
7173
CONFIG_MINOR_VERSION,
@@ -319,13 +321,17 @@ def __init__(self, hass: HomeAssistant, http_session: aiohttp.ClientSession, con
319321

320322
# we need to set the 'config_update_interval_in_seconds' before we init the bridge, cause we need
321323
# the interval from the coordinator in the bridge (for the config updates)...
322-
self.update_interval_in_seconds_from_config_entry = config_entry.data.get(CONF_SCAN_INTERVAL, 30)
324+
self._update_interval_in_seconds_from_config_entry = config_entry.data.get(CONF_SCAN_INTERVAL, 30)
325+
self._request_ext_vehicle_data = config_entry.data.get(CONF_EXTENDED_VEHICLE_DATA, False)
326+
self._request_ext_meter_data = config_entry.data.get(CONF_EXTENDED_METER_DATA, False)
323327

324328
self.bridge = EvccApiBridge(host=config_entry.data.get(CONF_HOST, "NOT-CONFIGURED"),
325329
web_session=http_session,
326330
coordinator=self,
327331
lang=lang,
328-
opt_password=config_entry.data.get(CONF_PASSWORD, None))
332+
opt_password=config_entry.data.get(CONF_PASSWORD, None),
333+
ext_vehicle_data=self._request_ext_vehicle_data,
334+
ext_meter_data=self._request_ext_meter_data)
329335

330336

331337
self.include_evcc_prefix = config_entry.data.get(CONF_INCLUDE_EVCC, False)
@@ -371,7 +377,7 @@ def __init__(self, hass: HomeAssistant, http_session: aiohttp.ClientSession, con
371377
super().__init__(hass, _LOGGER, name=DOMAIN)
372378
else:
373379
super().__init__(hass, _LOGGER, name=DOMAIN,
374-
update_interval=timedelta(seconds=self.update_interval_in_seconds_from_config_entry))
380+
update_interval=timedelta(seconds=self._update_interval_in_seconds_from_config_entry))
375381

376382
# Callable[[Event], Any]
377383
def __call__(self, evt: Event) -> bool:

custom_components/evcc_intg/config_flow.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
CONF_INCLUDE_EVCC,
1616
CONF_USE_WS,
1717
CONF_PURGE_ALL,
18+
CONF_EXTENDED_VEHICLE_DATA,
19+
CONF_EXTENDED_METER_DATA,
1820
CONFIG_VERSION, CONFIG_MINOR_VERSION
1921
)
2022

@@ -25,6 +27,8 @@
2527
DEFAULT_SCAN_INTERVAL = 15
2628
DEFAULT_USE_WS = True
2729
DEFAULT_INCLUDE_EVCC = False
30+
DEFAULT_EXTENDED_VEHICLE_DATA = True
31+
DEFAULT_EXTENDED_METER_DATA = True
2832

2933
class EvccFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
3034
"""Config flow for evcc_intg."""
@@ -42,6 +46,8 @@ def __init__(self):
4246
self._default_scan_interval = DEFAULT_SCAN_INTERVAL
4347
self._default_use_ws = DEFAULT_USE_WS
4448
self._default_include_evcc = DEFAULT_INCLUDE_EVCC
49+
self._default_extended_vehicle_data = DEFAULT_EXTENDED_VEHICLE_DATA
50+
self._default_extended_meter_data = DEFAULT_EXTENDED_METER_DATA
4551

4652
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
4753
entry_data = self._get_reconfigure_entry().data
@@ -51,6 +57,8 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None)
5157
self._default_scan_interval = entry_data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
5258
self._default_use_ws = entry_data.get(CONF_USE_WS, DEFAULT_USE_WS)
5359
self._default_include_evcc = entry_data.get(CONF_INCLUDE_EVCC, DEFAULT_INCLUDE_EVCC)
60+
self._default_extended_vehicle_data = entry_data.get(CONF_EXTENDED_VEHICLE_DATA, DEFAULT_EXTENDED_VEHICLE_DATA)
61+
self._default_extended_meter_data = entry_data.get(CONF_EXTENDED_METER_DATA, DEFAULT_EXTENDED_METER_DATA)
5462

5563
return await self.async_step_user()
5664

@@ -107,6 +115,8 @@ async def async_step_user(self, user_input=None):
107115
user_input[CONF_SCAN_INTERVAL] = self._default_scan_interval
108116
user_input[CONF_USE_WS] = self._default_use_ws
109117
user_input[CONF_INCLUDE_EVCC] = self._default_include_evcc
118+
user_input[CONF_EXTENDED_VEHICLE_DATA] = self._default_extended_vehicle_data
119+
user_input[CONF_EXTENDED_METER_DATA] = self._default_extended_meter_data
110120
user_input[CONF_PURGE_ALL] = False
111121

112122
return self.async_show_form(
@@ -118,6 +128,8 @@ async def async_step_user(self, user_input=None):
118128
vol.Required(CONF_USE_WS, default=user_input.get(CONF_USE_WS)): bool,
119129
vol.Required(CONF_SCAN_INTERVAL, default=user_input.get(CONF_SCAN_INTERVAL)): int,
120130
vol.Required(CONF_INCLUDE_EVCC, default=user_input.get(CONF_INCLUDE_EVCC)): bool,
131+
vol.Optional(CONF_EXTENDED_VEHICLE_DATA, default=user_input.get(CONF_EXTENDED_VEHICLE_DATA, DEFAULT_EXTENDED_VEHICLE_DATA)): bool,
132+
vol.Optional(CONF_EXTENDED_METER_DATA, default=user_input.get(CONF_EXTENDED_METER_DATA, DEFAULT_EXTENDED_METER_DATA)): bool,
121133
vol.Optional(CONF_PURGE_ALL, default=user_input.get(CONF_PURGE_ALL)): bool,
122134
}),
123135
description_placeholders={"repo": "https://github.com/marq24/ha-evcc"},

custom_components/evcc_intg/const.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
CONF_INCLUDE_EVCC: Final = "include_evcc"
5757
CONF_PURGE_ALL: Final = "purge_all_devices"
5858
CONF_USE_WS= "use_websocket"
59+
CONF_EXTENDED_VEHICLE_DATA: Final = "extended_vehicle_data"
60+
CONF_EXTENDED_METER_DATA: Final = "extended_meter_data"
5961

6062
EVCC_JSON_KEY_NAME: Final = "evccName"
6163
EVCC_JSON_ORIGIN_OBJECT = "originObject"
@@ -1899,7 +1901,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
18991901
state_class=SensorStateClass.MEASUREMENT,
19001902
native_unit_of_measurement=UnitOfLength.KILOMETERS,
19011903
suggested_display_precision=0,
1902-
entity_registry_enabled_default=False
1904+
entity_registry_enabled_default=False,
19031905
),
19041906
ExtSensorEntityDescriptionStub(
19051907
tag=Tag.EVCCCONF_VEHICLEODOMETER,
@@ -1909,7 +1911,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
19091911
device_class=None,
19101912
suggested_display_precision=0,
19111913
ignore_zero=True,
1912-
entity_registry_enabled_default=False
1914+
entity_registry_enabled_default=False,
19131915
),
19141916
ExtSensorEntityDescriptionStub(
19151917
tag=Tag.EVCCCONF_VEHICLESOC,
@@ -1918,7 +1920,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
19181920
native_unit_of_measurement=PERCENTAGE,
19191921
device_class=None,
19201922
suggested_display_precision=0,
1921-
entity_registry_enabled_default=False
1923+
entity_registry_enabled_default=False,
19221924
),
19231925
ExtSensorEntityDescriptionStub(
19241926
tag=Tag.EVCCCONF_VEHICLELIMITSOC,
@@ -1927,7 +1929,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
19271929
native_unit_of_measurement=PERCENTAGE,
19281930
device_class=None,
19291931
suggested_display_precision=0,
1930-
entity_registry_enabled_default=False
1932+
entity_registry_enabled_default=False,
19311933
)
19321934
]
19331935
# the meters are coming from the evcc configuration endpoints (and require the evcc-admin pwd)
@@ -1940,15 +1942,15 @@ class ExtSwitchEntityDescription(SwitchEntityDescription):
19401942
state_class=SensorStateClass.MEASUREMENT,
19411943
native_unit_of_measurement=UnitOfPower.WATT,
19421944
suggested_display_precision=2,
1943-
device_class=SensorDeviceClass.POWER
1945+
device_class=SensorDeviceClass.POWER,
19441946
),
19451947
ExtSensorEntityDescriptionStub(
19461948
tag=Tag.EVCCCONF_METERENERGY,
19471949
icon="mdi:transmission-tower",
19481950
state_class=SensorStateClass.TOTAL,
19491951
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
19501952
device_class=SensorDeviceClass.ENERGY,
1951-
suggested_display_precision=2
1953+
suggested_display_precision=2,
19521954
),
19531955
ExtSensorEntityDescriptionStub(
19541956
tag=Tag.EVCCCONF_METERSOC,

custom_components/evcc_intg/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
"iot_class": "local_push",
1212
"issue_tracker": "https://github.com/marq24/ha-evcc/issues",
1313
"requirements": ["packaging>=21.0"],
14-
"version": "2026.5.3"
14+
"version": "2026.5.4"
1515
}

custom_components/evcc_intg/pyevcc_ha/__init__.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ def _add_to_sums(a_sums_dict: dict, key: str, val_charge_duration, val_charged_e
189189
a_sums_dict[key]["cost"] += val_cost
190190

191191
class EvccApiBridge:
192-
def __init__(self, host: str, web_session, coordinator: DataUpdateCoordinator = None, lang: str = "en", opt_password: str = None) -> None:
192+
def __init__(self, host: str, web_session, coordinator: DataUpdateCoordinator = None, lang: str = "en",
193+
opt_password: str = None, ext_vehicle_data: bool = False, ext_meter_data: bool = False) -> None:
193194
# make sure we are compliant with old configurations (that does not include the schema in the host variable)
194195
if not host.startswith(("http://", "https://")):
195196
host = f"http://{host}"
@@ -210,6 +211,8 @@ def __init__(self, host: str, web_session, coordinator: DataUpdateCoordinator =
210211
self.host = host
211212
self._admin_password = opt_password
212213
self._admin_cookie_expire_datetime = None
214+
self._request_ext_vehicle_data = ext_vehicle_data
215+
self._request_ext_meter_data = ext_meter_data
213216

214217
self.web_session = web_session
215218
self.lang_map = None
@@ -430,41 +433,67 @@ async def read_all_data(self, request_all:bool=True,
430433
self._data = {}
431434
json_resp = self._data
432435

436+
433437
self._data_coordinator_update_needed = False
434-
current_hour = datetime.now(timezone.utc).hour
435-
current_quarter_hour = datetime.now(timezone.utc).minute//15
438+
now_utc = datetime.now(timezone.utc)
439+
current_hour = now_utc.hour
440+
current_minute = now_utc.minute
441+
current_quarter_hour = current_minute // 15
442+
443+
444+
# additional tariffs endpoint data
436445
if request_all or request_tariffs:
437446
if self.request_tariff_endpoints:
438447
# we only update the tariff data once per hour...
439448
if self._TARIFF_LAST_UPDATE_QUARTER_HOUR != current_quarter_hour:
440449
_LOGGER.debug(f"going to request 'tariff' data from evcc@{self.host}")
441-
json_resp = await self.read_tariff_data(json_resp)
442-
self._data_coordinator_update_needed = True
450+
json_resp, data_was_fetched = await self.read_tariff_data(json_resp)
451+
if data_was_fetched:
452+
self._data_coordinator_update_needed = True
443453
self._TARIFF_LAST_UPDATE_QUARTER_HOUR = current_quarter_hour
444454
else:
445455
# we must copy the previous existing data to the new json_resp!
446456
if self._data is not None and ADDITIONAL_ENDPOINTS_DATA_TARIFF in self._data:
447457
json_resp[ADDITIONAL_ENDPOINTS_DATA_TARIFF] = self._data[ADDITIONAL_ENDPOINTS_DATA_TARIFF]
448458

459+
# additional sessions endpoint data
449460
if request_all or request_sessions:
450-
# we only update the session's data once per hour...
451-
if self._SESSIONS_LAST_UPDATE_HOUR != current_hour:
461+
# update session data twice per hour:
462+
# window A → minutes 55–59 (5 minutes before the full hour)
463+
# window B → minutes 00–54 (start of the new hour)
464+
if current_minute >= 55:
465+
_sessions_slot = current_hour * 100 + 55 # e.g. 1455 at 14:55–14:59
466+
else:
467+
_sessions_slot = current_hour * 100 # e.g. 1400 at 14:00–14:54
468+
469+
_sessions_should_fetch = (
470+
self._SESSIONS_LAST_UPDATE_HOUR == -1 # first run: fetch immediately
471+
or (self._SESSIONS_LAST_UPDATE_HOUR != _sessions_slot)
472+
)
473+
if _sessions_should_fetch:
452474
_LOGGER.debug(f"going to request 'sessions' data from evcc@{self.host}")
453-
json_resp = await self.read_sessions_data(json_resp)
454-
self._data_coordinator_update_needed = True
455-
self._SESSIONS_LAST_UPDATE_HOUR = current_hour
475+
json_resp, data_was_fetched = await self.read_sessions_data(json_resp)
476+
if data_was_fetched:
477+
self._data_coordinator_update_needed = True
478+
self._SESSIONS_LAST_UPDATE_HOUR = _sessions_slot
456479
else:
457480
# we must copy the previous existing data to the new json_resp!
458481
if self._data is not None and ADDITIONAL_ENDPOINTS_DATA_SESSIONS in self._data:
459482
json_resp[ADDITIONAL_ENDPOINTS_DATA_SESSIONS] = self._data[ADDITIONAL_ENDPOINTS_DATA_SESSIONS]
460483

484+
# additional configuration endpoint data
461485
if request_all or request_config:
462486
now_time = time()
463487
if self._CONFIG_LAST_UPDATE + self._CONFIG_UPDATE_INTERVAL_IN_SECONDS <= time():
464488
_LOGGER.debug(f"going to request 'configuration' data from evcc@{self.host}")
465-
json_resp = await self.read_config_data(json_resp, log_requests=log_config_requests)
466-
self._data_coordinator_update_needed = True
489+
json_resp, data_was_fetched = await self.read_config_data(json_resp, log_requests=log_config_requests)
490+
if data_was_fetched:
491+
self._data_coordinator_update_needed = True
467492
self._CONFIG_LAST_UPDATE = now_time
493+
else:
494+
# we must copy the previous existing data to the new json_resp!
495+
if self._data is not None and ADDITIONAL_ENDPOINTS_DATA_EVCCCONF in self._data:
496+
json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF] = self._data[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF]
468497

469498
self._data = json_resp
470499
return json_resp
@@ -482,6 +511,7 @@ async def read_state_data(self) -> dict:
482511

483512
async def read_tariff_data(self, json_resp: dict) -> dict:
484513
# _LOGGER.info(f"going to request additional tariff data from evcc@{self.host}")
514+
tariff_data_was_fetched = False
485515
if ADDITIONAL_ENDPOINTS_DATA_TARIFF not in json_resp:
486516
json_resp[ADDITIONAL_ENDPOINTS_DATA_TARIFF] = {}
487517

@@ -492,14 +522,16 @@ async def read_tariff_data(self, json_resp: dict) -> dict:
492522
tariff_resp = await _do_request(method=self.web_session.get(url=req, ssl=False, timeout=static_5sec_timeout))
493523
if tariff_resp is not None and len(tariff_resp) > 0:
494524
json_resp[ADDITIONAL_ENDPOINTS_DATA_TARIFF][a_key] = tariff_resp
525+
tariff_data_was_fetched = True
495526

496527
except Exception as err:
497528
_LOGGER.info(f"could not read tariff data for '{a_key}' -> '{err}'")
498529

499-
return json_resp
530+
return json_resp, tariff_data_was_fetched
500531

501532
async def read_sessions_data(self, json_resp: dict) -> dict:
502533
# _LOGGER.info(f"going to request additional sessions data from evcc@{self.host}")
534+
session_data_was_fetched = False
503535
if ADDITIONAL_ENDPOINTS_DATA_SESSIONS not in json_resp:
504536
json_resp[ADDITIONAL_ENDPOINTS_DATA_SESSIONS] = {}
505537

@@ -514,13 +546,15 @@ async def read_sessions_data(self, json_resp: dict) -> dict:
514546

515547
# do the math stuff...
516548
calculate_session_sums(sessions_resp, json_resp)
549+
session_data_was_fetched = True
517550

518551
except BaseException as err:
519552
_LOGGER.info(f"could not read sessions data '{type(err).__name__}' -> {err}")
520553

521-
return json_resp
554+
return json_resp, session_data_was_fetched
522555

523556
async def read_config_data(self, json_resp: dict, log_requests:bool=False):
557+
config_data_was_fetched = False
524558
if await self.ensure_session_is_authorized():
525559
if ADDITIONAL_ENDPOINTS_DATA_EVCCCONF not in json_resp:
526560
# creating our core data container object...
@@ -532,6 +566,14 @@ async def read_config_data(self, json_resp: dict, log_requests:bool=False):
532566
a_config = json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF][EVCCCONF_KEY_CONFIG]
533567

534568
for a_device_type, value_list in a_config.items():
569+
if a_device_type == EVCCCONF_DEVICE_TYPES.VEHICLE.value and not self._request_ext_vehicle_data:
570+
_LOGGER.debug(f"skipping vehicle data since 'request_ext_vehicle_data' is set to False")
571+
continue
572+
573+
if a_device_type == EVCCCONF_DEVICE_TYPES.METER.value and not self._request_ext_meter_data:
574+
_LOGGER.debug(f"skipping meter data since 'request_ext_meter_data' is set to False")
575+
continue
576+
535577
for a_device_id in value_list:
536578
if a_device_type not in json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF][EVCCCONF_KEY_DATA]:
537579
json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF][EVCCCONF_KEY_DATA][a_device_type] = {}
@@ -544,12 +586,14 @@ async def read_config_data(self, json_resp: dict, log_requests:bool=False):
544586
# make sure that we always use lower case device-ids
545587
# compare also with the reading code in 'read_tag_configuration'
546588
json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF][EVCCCONF_KEY_DATA][a_device_type][a_device_id.lower()] = a_status_resp
589+
if not config_data_was_fetched:
590+
config_data_was_fetched = True
547591
if log_requests:
548592
_LOGGER.debug(f"Response received for {req}: {a_status_resp}")
549593

550594
_LOGGER.debug(f"read_config_data(): configuration data read {list(json_resp[ADDITIONAL_ENDPOINTS_DATA_EVCCCONF][EVCCCONF_KEY_DATA].keys())}")
551595

552-
return json_resp
596+
return json_resp, config_data_was_fetched
553597

554598
async def _read_config_setup(self, log_requests:bool=False):
555599
the_configuration = {}

custom_components/evcc_intg/sensor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_
169169
veh_name_addon = a_vehicle_obj["name"]
170170

171171
for a_stub in SENSOR_ENTITIES_PER_VEHICLE:
172+
if configuration_data_available and a_stub.tag.type == EP_TYPE.EVCCCONF and not coordinator._request_ext_vehicle_data:
173+
_LOGGER.debug(f"Skipping EVCCCONF sensor {a_stub.tag} for vehicle {veh_name_addon} due to _request_ext_vehicle_data = FALSE")
174+
continue
175+
172176
force_enable_by_default = configuration_data_available and a_stub.tag.type == EP_TYPE.EVCCCONF
173177
# only when the json_idx has a length of 1, we must patch our key & translation_key
174178
patch_keys = a_stub.json_idx is not None and len(a_stub.json_idx) == 1
@@ -249,7 +253,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_
249253
entities.append(entity)
250254

251255
# the additional meter entities (from the configuration)
252-
if configuration_data_available:
256+
if configuration_data_available and coordinator._request_ext_meter_data:
253257
meter_data = coordinator.data.get(ADDITIONAL_ENDPOINTS_DATA_EVCCCONF, {}).get(EVCCCONF_KEY_CONFIG, {}).get(EVCCCONF_DEVICE_TYPES.METER.value, {})
254258
for a_meter_key in meter_data:
255259
# we MUST ensure that the meter_id_addon is a valid HA entity-id

0 commit comments

Comments
 (0)