|
| 1 | +import asyncio |
1 | 2 | import logging |
2 | 3 | from dataclasses import replace |
3 | 4 | from datetime import datetime, timezone |
|
9 | 10 | from homeassistant.core import HomeAssistant |
10 | 11 | from homeassistant.helpers import entity_registry as er |
11 | 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback |
12 | | -from homeassistant.helpers.event import async_call_later |
13 | 13 | from homeassistant.helpers.restore_state import RestoreEntity |
14 | 14 |
|
15 | 15 | from custom_components.evcc_intg.pyevcc_ha.const import ( |
|
23 | 23 | FORECAST_CONTENT, |
24 | 24 | SESSIONS_KEY_TOTAL, |
25 | 25 | EVCCCONF_KEY_CONFIG, |
26 | | - EVCCCONF_DEVICE_TYPES |
| 26 | + EVCCCONF_DEVICE_TYPES, |
| 27 | + EVCCCONF_KEY_DATA |
27 | 28 | ) |
28 | 29 | from custom_components.evcc_intg.pyevcc_ha.keys import Tag, camel_to_snake |
29 | 30 | from . import EvccDataUpdateCoordinator, EvccBaseEntity |
@@ -54,6 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_ |
54 | 55 | configuration_data_available = True |
55 | 56 |
|
56 | 57 | entities = [] |
| 58 | + entries_to_check = {} |
57 | 59 | the_sensors_list = SENSOR_ENTITIES |
58 | 60 |
|
59 | 61 | # we need to check if the grid data (power & currents) is available as a separate object... |
@@ -289,12 +291,73 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_ |
289 | 291 | lookup=a_stub.lookup, |
290 | 292 | ignore_zero=a_stub.ignore_zero |
291 | 293 | ) |
292 | | - |
293 | | - entity = EvccSensor(coordinator, description, do_check=True) |
| 294 | + entity = EvccSensor(coordinator, description) |
294 | 295 | entities.append(entity) |
| 296 | + entries_to_check[entity.entity_id] = { |
| 297 | + "tag": description.tag, |
| 298 | + "evcc_config_id": description.evcc_config_id, |
| 299 | + "description_key": description.key |
| 300 | + } |
295 | 301 |
|
296 | 302 | add_entity_cb(entities) |
297 | 303 |
|
| 304 | + async def _check_for_entities_to_enabled(): |
| 305 | + _LOGGER.debug("_check_for_entities_to_enabled(): SENSORS launched (will sleep now)") |
| 306 | + try: |
| 307 | + await asyncio.sleep(30) |
| 308 | + need_to_wait = True |
| 309 | + check_run_counter = 0 |
| 310 | + |
| 311 | + while check_run_counter < 11 and need_to_wait: |
| 312 | + check_run_counter += 1 |
| 313 | + meter_devices_data = coordinator.data.get(ADDITIONAL_ENDPOINTS_DATA_EVCCCONF, {}).get(EVCCCONF_KEY_DATA, {}).get(EVCCCONF_DEVICE_TYPES.METER.value) |
| 314 | + if meter_devices_data is not None: |
| 315 | + avail_data_count = 0 |
| 316 | + for a_meter_device_id, a_meter_object in meter_devices_data.items(): |
| 317 | + a_meter_device_has_error = False |
| 318 | + for a_key, a_value in a_meter_object.items(): |
| 319 | + if "error" in a_value and a_value["error"] is not None and len(a_value["error"]) > 0: |
| 320 | + a_meter_device_has_error = True |
| 321 | + break |
| 322 | + if not a_meter_device_has_error: |
| 323 | + avail_data_count +=1 |
| 324 | + |
| 325 | + if avail_data_count == len(meter_devices_data): |
| 326 | + need_to_wait = False |
| 327 | + |
| 328 | + if need_to_wait: |
| 329 | + _LOGGER.debug(f"_check_for_entities_to_enabled(): No meters data found (or they are marked with 'errors'), waiting another 30 seconds... {meter_devices_data}") |
| 330 | + await asyncio.sleep(30) |
| 331 | + |
| 332 | + _LOGGER.debug("_check_for_entities_to_enabled(): SENSORS will start now...") |
| 333 | + registry = er.async_get(hass) |
| 334 | + if registry is not None: |
| 335 | + for a_entity_id in entries_to_check: |
| 336 | + a_entity_data = entries_to_check[a_entity_id] |
| 337 | + value = coordinator.read_tag_configuration(a_entity_data["tag"], a_entity_data["evcc_config_id"]) |
| 338 | + _LOGGER.debug(f"_check_for_entities_to_enabled(): {a_entity_data["description_key"]}: {value}") |
| 339 | + entry = registry.async_get(a_entity_id) |
| 340 | + if entry is not None: |
| 341 | + if value is None: |
| 342 | + if entry.disabled_by is None: |
| 343 | + _LOGGER.debug(f"_check_for_entities_to_enabled(): Disabling entity {a_entity_id} due to missing data from evcc") |
| 344 | + registry.async_update_entity( |
| 345 | + a_entity_id, |
| 346 | + disabled_by=er.RegistryEntryDisabler.INTEGRATION |
| 347 | + ) |
| 348 | + else: |
| 349 | + if entry.disabled_by == er.RegistryEntryDisabler.INTEGRATION: |
| 350 | + _LOGGER.debug(f"_check_for_entities_to_enabled(): Enable entity {a_entity_id} due since data is available") |
| 351 | + registry.async_update_entity( |
| 352 | + a_entity_id, |
| 353 | + disabled_by=None |
| 354 | + ) |
| 355 | + _LOGGER.debug("_check_for_entities_to_enabled(): SENSOR init is COMPLETED") |
| 356 | + except BaseException as err: |
| 357 | + _LOGGER.warning(f"_check_for_entities_to_enabled(): SENSOR Error: {type(err).__name__} {err}") |
| 358 | + |
| 359 | + asyncio.create_task(_check_for_entities_to_enabled()) |
| 360 | + |
298 | 361 | def compress_data(data): |
299 | 362 | return compress_general(data, "start", "value") |
300 | 363 |
|
@@ -347,53 +410,13 @@ def compress_general(data, time_key:str, value_key:str): |
347 | 410 |
|
348 | 411 |
|
349 | 412 | class EvccSensor(EvccBaseEntity, SensorEntity, RestoreEntity): |
350 | | - def __init__(self, coordinator: EvccDataUpdateCoordinator, description: ExtSensorEntityDescription, do_check:bool=False): |
| 413 | + def __init__(self, coordinator: EvccDataUpdateCoordinator, description: ExtSensorEntityDescription): |
351 | 414 | super().__init__(entity_type=Platform.SENSOR, coordinator=coordinator, description=description) |
352 | | - self._do_check = do_check |
353 | 415 | self._previous_float_value: float | None = None |
354 | | - self._cancel_delayed_check = None |
355 | 416 | if self.tag.type == EP_TYPE.TARIFF or self.tag in [Tag.FORECAST_GRID, Tag.FORECAST_SOLAR, Tag.FORECAST_FEEDIN, Tag.FORECAST_PLANNER]: |
356 | 417 | self._last_calculated_key = None |
357 | 418 | self._last_calculated_value = None |
358 | 419 |
|
359 | | - async def async_added_to_hass(self): |
360 | | - """Run when entity about to be added to hass.""" |
361 | | - await super().async_added_to_hass() |
362 | | - # Schedule the check code for 120 seconds (2 minutes) |
363 | | - # Note: The callback is a function, not a coroutine |
364 | | - if self._do_check: |
365 | | - self._cancel_delayed_check = async_call_later(self.hass,120, self._perform_delayed_check) |
366 | | - |
367 | | - async def async_will_remove_from_hass(self): |
368 | | - """Run when the entity is removed from hass.""" |
369 | | - if self._do_check: |
370 | | - if self._cancel_delayed_check is not None: |
371 | | - self._cancel_delayed_check() |
372 | | - self._cancel_delayed_check = None |
373 | | - await super().async_will_remove_from_hass() |
374 | | - |
375 | | - async def _perform_delayed_check(self, _now): |
376 | | - value = self.coordinator.read_tag_configuration(self.tag, self.entity_description.evcc_config_id) |
377 | | - _LOGGER.debug(f"_perform_delayed_check(): {self.entity_description.key}: {value}") |
378 | | - registry = er.async_get(self.hass) |
379 | | - if registry is not None: |
380 | | - entry = registry.async_get(self.entity_id) |
381 | | - if entry is not None: |
382 | | - if value is None: |
383 | | - if entry.disabled_by is None: |
384 | | - _LOGGER.debug(f"_perform_delayed_check(): Disabling entity {self.entity_id} due to missing data from evcc") |
385 | | - registry.async_update_entity( |
386 | | - self.entity_id, |
387 | | - disabled_by=er.RegistryEntryDisabler.INTEGRATION |
388 | | - ) |
389 | | - else: |
390 | | - if entry.disabled_by == er.RegistryEntryDisabler.INTEGRATION: |
391 | | - _LOGGER.debug(f"_perform_delayed_check(): Enable entity {self.entity_id} due since data is available") |
392 | | - registry.async_update_entity( |
393 | | - self.entity_id, |
394 | | - disabled_by=None |
395 | | - ) |
396 | | - |
397 | 420 | @property |
398 | 421 | def extra_state_attributes(self): |
399 | 422 | """Return sensor attributes""" |
|
0 commit comments