|
1 | 1 | """Solar Surplus Charging Profile automation.""" |
2 | 2 | from __future__ import annotations |
3 | 3 | import logging |
4 | | -from datetime import timedelta |
| 4 | +from datetime import timedelta, datetime |
5 | 5 |
|
6 | 6 | from homeassistant.core import HomeAssistant, callback |
7 | 7 | from homeassistant.helpers.event import async_track_time_interval |
@@ -81,6 +81,7 @@ def __init__( |
81 | 81 | self._home_min_soc_saturday_entity = None |
82 | 82 | self._home_min_soc_sunday_entity = None |
83 | 83 | self._priority_state_sensor_entity = None |
| 84 | + self._solar_surplus_diagnostic_sensor_entity = None |
84 | 85 |
|
85 | 86 | # Timer for periodic checks |
86 | 87 | self._timer_unsub = None |
@@ -141,6 +142,7 @@ async def async_setup(self) -> None: |
141 | 142 | self._home_min_soc_saturday_entity = self._find_entity_by_suffix("evsc_home_min_soc_saturday") |
142 | 143 | self._home_min_soc_sunday_entity = self._find_entity_by_suffix("evsc_home_min_soc_sunday") |
143 | 144 | self._priority_state_sensor_entity = self._find_entity_by_suffix("evsc_priority_daily_state") |
| 145 | + self._solar_surplus_diagnostic_sensor_entity = self._find_entity_by_suffix("evsc_solar_surplus_diagnostic") |
144 | 146 |
|
145 | 147 | if not all([ |
146 | 148 | self._forza_ricarica_entity, |
@@ -530,30 +532,97 @@ async def _async_periodic_check(self, now=None) -> None: |
530 | 532 | _LOGGER.info("=" * 80) |
531 | 533 | return |
532 | 534 |
|
533 | | - # Parse values |
| 535 | + # Parse values with detailed error reporting |
| 536 | + sensor_values = {} |
| 537 | + sensor_errors = [] |
| 538 | + |
534 | 539 | try: |
535 | | - fv_production = float(fv_state.state) |
536 | | - home_consumption = float(consumption_state.state) |
537 | | - grid_import = float(grid_import_state.state) |
538 | | - grid_threshold = float(grid_threshold_state.state) |
539 | | - grid_import_delay = float(grid_delay_state.state) |
540 | | - surplus_drop_delay = float(surplus_delay_state.state) |
541 | | - use_home_battery = use_battery_state.state == "on" |
542 | | - battery_min_soc = float(battery_min_soc_state.state) |
543 | | - |
544 | | - # Home battery SOC (optional) |
545 | | - home_battery_soc = None |
546 | | - if soc_home_state and use_home_battery: |
547 | | - try: |
548 | | - home_battery_soc = float(soc_home_state.state) |
549 | | - except (ValueError, TypeError): |
550 | | - _LOGGER.warning("⚠️ Home Battery SOC value invalid, treating as unavailable") |
| 540 | + sensor_values["fv_production"] = float(fv_state.state) |
| 541 | + except (ValueError, TypeError) as e: |
| 542 | + sensor_errors.append(f"Solar Production ({self._fv_production}): '{fv_state.state}' - {e}") |
| 543 | + |
| 544 | + try: |
| 545 | + sensor_values["home_consumption"] = float(consumption_state.state) |
| 546 | + except (ValueError, TypeError) as e: |
| 547 | + sensor_errors.append(f"Home Consumption ({self._home_consumption}): '{consumption_state.state}' - {e}") |
| 548 | + |
| 549 | + try: |
| 550 | + sensor_values["grid_import"] = float(grid_import_state.state) |
| 551 | + except (ValueError, TypeError) as e: |
| 552 | + sensor_errors.append(f"Grid Import ({self._grid_import}): '{grid_import_state.state}' - {e}") |
| 553 | + |
| 554 | + try: |
| 555 | + sensor_values["grid_threshold"] = float(grid_threshold_state.state) |
| 556 | + except (ValueError, TypeError) as e: |
| 557 | + sensor_errors.append(f"Grid Threshold: '{grid_threshold_state.state}' - {e}") |
| 558 | + |
| 559 | + try: |
| 560 | + sensor_values["grid_import_delay"] = float(grid_delay_state.state) |
| 561 | + except (ValueError, TypeError) as e: |
| 562 | + sensor_errors.append(f"Grid Import Delay: '{grid_delay_state.state}' - {e}") |
551 | 563 |
|
| 564 | + try: |
| 565 | + sensor_values["surplus_drop_delay"] = float(surplus_delay_state.state) |
| 566 | + except (ValueError, TypeError) as e: |
| 567 | + sensor_errors.append(f"Surplus Drop Delay: '{surplus_delay_state.state}' - {e}") |
| 568 | + |
| 569 | + try: |
| 570 | + sensor_values["battery_min_soc"] = float(battery_min_soc_state.state) |
552 | 571 | except (ValueError, TypeError) as e: |
553 | | - _LOGGER.warning(f"⚠️ Decision: Invalid sensor values: {e}") |
| 572 | + sensor_errors.append(f"Home Battery Min SOC: '{battery_min_soc_state.state}' - {e}") |
| 573 | + |
| 574 | + sensor_values["use_home_battery"] = use_battery_state.state == "on" |
| 575 | + |
| 576 | + # Home battery SOC (optional) |
| 577 | + home_battery_soc = None |
| 578 | + if soc_home_state and sensor_values.get("use_home_battery"): |
| 579 | + try: |
| 580 | + home_battery_soc = float(soc_home_state.state) |
| 581 | + except (ValueError, TypeError): |
| 582 | + _LOGGER.warning(f"⚠️ Home Battery SOC ({self._soc_home}) value invalid: '{soc_home_state.state}', treating as unavailable") |
| 583 | + |
| 584 | + # If there are any errors, log them and update diagnostic sensor |
| 585 | + if sensor_errors: |
| 586 | + _LOGGER.error("=" * 80) |
| 587 | + _LOGGER.error("❌ SENSOR ERROR DETAILS:") |
| 588 | + for error in sensor_errors: |
| 589 | + _LOGGER.error(f" {error}") |
| 590 | + _LOGGER.error("=" * 80) |
| 591 | + _LOGGER.error("⚠️ Decision: Cannot proceed with solar surplus check due to invalid sensor values") |
| 592 | + _LOGGER.error(" Please check that all configured sensors are providing valid numeric values") |
| 593 | + _LOGGER.error(" Entity IDs to check:") |
| 594 | + _LOGGER.error(f" - Solar Production: {self._fv_production}") |
| 595 | + _LOGGER.error(f" - Home Consumption: {self._home_consumption}") |
| 596 | + _LOGGER.error(f" - Grid Import: {self._grid_import}") |
554 | 597 | _LOGGER.info("=" * 80) |
| 598 | + |
| 599 | + # Update diagnostic sensor with error details |
| 600 | + if self._solar_surplus_diagnostic_sensor_entity: |
| 601 | + await self._update_diagnostic_sensor( |
| 602 | + state="ERROR: Invalid sensor values", |
| 603 | + attributes={ |
| 604 | + "last_check": datetime.now().isoformat(), |
| 605 | + "errors": sensor_errors, |
| 606 | + "solar_production_entity": self._fv_production, |
| 607 | + "home_consumption_entity": self._home_consumption, |
| 608 | + "grid_import_entity": self._grid_import, |
| 609 | + "solar_production_value": fv_state.state, |
| 610 | + "home_consumption_value": consumption_state.state, |
| 611 | + "grid_import_value": grid_import_state.state, |
| 612 | + } |
| 613 | + ) |
555 | 614 | return |
556 | 615 |
|
| 616 | + # Extract parsed values |
| 617 | + fv_production = sensor_values["fv_production"] |
| 618 | + home_consumption = sensor_values["home_consumption"] |
| 619 | + grid_import = sensor_values["grid_import"] |
| 620 | + grid_threshold = sensor_values["grid_threshold"] |
| 621 | + grid_import_delay = sensor_values["grid_import_delay"] |
| 622 | + surplus_drop_delay = sensor_values["surplus_drop_delay"] |
| 623 | + use_home_battery = sensor_values["use_home_battery"] |
| 624 | + battery_min_soc = sensor_values["battery_min_soc"] |
| 625 | + |
557 | 626 | # Get current amperage - treat as 0 if charger is OFF |
558 | 627 | charger_state = self.hass.states.get(self._charger_switch) |
559 | 628 | charger_is_on = charger_state and charger_state.state == "on" |
@@ -829,6 +898,17 @@ async def _adjust_amperage_down_one_step(self, target_amps: int) -> None: |
829 | 898 | _LOGGER.info(" 3/5: Target is 0A, keeping charger off") |
830 | 899 | _LOGGER.info("✅ Charger stopped") |
831 | 900 |
|
| 901 | + async def _update_diagnostic_sensor(self, state: str, attributes: dict) -> None: |
| 902 | + """Update the solar surplus diagnostic sensor with current check information.""" |
| 903 | + if not self._solar_surplus_diagnostic_sensor_entity: |
| 904 | + return |
| 905 | + |
| 906 | + self.hass.states.async_set( |
| 907 | + self._solar_surplus_diagnostic_sensor_entity, |
| 908 | + state, |
| 909 | + attributes, |
| 910 | + ) |
| 911 | + |
832 | 912 | async def async_remove(self) -> None: |
833 | 913 | """Remove the automation.""" |
834 | 914 | if self._timer_unsub: |
|
0 commit comments