mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
feat(coordinator): implement repairs system for proactive user notifications
Add repair notification system with three auto-clearing repair types: - Tomorrow data missing (after 18:00) - API rate limit exceeded (3+ consecutive errors) - Home not found in Tibber account Includes: - coordinator/repairs.py: Complete TibberPricesRepairManager implementation - Enhanced API error handling with explicit 5xx handling - Translations for 5 languages (EN, DE, NB, NL, SV) - Developer documentation in docs/developer/docs/repairs-system.md Impact: Users receive actionable notifications for important issues instead of only seeing stale data in logs.
This commit is contained in:
parent
4bd90ccdee
commit
83be54d5ad
12 changed files with 802 additions and 8 deletions
|
|
@ -886,7 +886,24 @@ class TibberPricesApiClient:
|
|||
headers: dict | None = None,
|
||||
query_type: TibberPricesQueryType = TibberPricesQueryType.USER,
|
||||
) -> Any:
|
||||
"""Get information from the API with rate limiting and retry logic."""
|
||||
"""
|
||||
Get information from the API with rate limiting and retry logic.
|
||||
|
||||
Exception Handling Strategy:
|
||||
- AuthenticationError: Immediate raise, triggers reauth flow
|
||||
- PermissionError: Immediate raise, non-retryable
|
||||
- CommunicationError: Retry with exponential backoff
|
||||
- ApiClientError (Rate Limit): Retry with Retry-After delay
|
||||
- ApiClientError (Other): Retry only if explicitly retryable
|
||||
- Network errors (aiohttp.ClientError, socket.gaierror, TimeoutError):
|
||||
Converted to CommunicationError and retried
|
||||
|
||||
Retry Logic:
|
||||
- Max retries: 5 (configurable via _max_retries)
|
||||
- Base delay: 2 seconds (exponential backoff: 2s, 4s, 8s, 16s, 32s)
|
||||
- Rate limit delay: Uses Retry-After header or falls back to exponential
|
||||
- Caps: 30s for network errors, 120s for rate limits, 300s for Retry-After
|
||||
"""
|
||||
headers = headers or prepare_headers(self._access_token, self._version)
|
||||
last_error: Exception | None = None
|
||||
|
||||
|
|
|
|||
|
|
@ -25,31 +25,79 @@ HTTP_BAD_REQUEST = 400
|
|||
HTTP_UNAUTHORIZED = 401
|
||||
HTTP_FORBIDDEN = 403
|
||||
HTTP_TOO_MANY_REQUESTS = 429
|
||||
HTTP_INTERNAL_SERVER_ERROR = 500
|
||||
HTTP_BAD_GATEWAY = 502
|
||||
HTTP_SERVICE_UNAVAILABLE = 503
|
||||
HTTP_GATEWAY_TIMEOUT = 504
|
||||
|
||||
|
||||
def verify_response_or_raise(response: aiohttp.ClientResponse) -> None:
|
||||
"""Verify that the response is valid."""
|
||||
"""
|
||||
Verify HTTP response and map to appropriate exceptions.
|
||||
|
||||
Error Mapping:
|
||||
- 401 Unauthorized → AuthenticationError (non-retryable)
|
||||
- 403 Forbidden → PermissionError (non-retryable)
|
||||
- 429 Rate Limit → ApiClientError with retry support
|
||||
- 400 Bad Request → ApiClientError (non-retryable, invalid query)
|
||||
- 5xx Server Errors → CommunicationError (retryable)
|
||||
- Other errors → Let aiohttp.raise_for_status() handle
|
||||
"""
|
||||
# Authentication failures - non-retryable
|
||||
if response.status == HTTP_UNAUTHORIZED:
|
||||
_LOGGER.error("Tibber API authentication failed - check access token")
|
||||
raise TibberPricesApiClientAuthenticationError(TibberPricesApiClientAuthenticationError.INVALID_CREDENTIALS)
|
||||
|
||||
# Permission denied - non-retryable
|
||||
if response.status == HTTP_FORBIDDEN:
|
||||
_LOGGER.error("Tibber API access forbidden - insufficient permissions")
|
||||
raise TibberPricesApiClientPermissionError(TibberPricesApiClientPermissionError.INSUFFICIENT_PERMISSIONS)
|
||||
|
||||
# Rate limiting - retryable with explicit delay
|
||||
if response.status == HTTP_TOO_MANY_REQUESTS:
|
||||
# Check for Retry-After header that Tibber might send
|
||||
retry_after = response.headers.get("Retry-After", "unknown")
|
||||
_LOGGER.warning("Tibber API rate limit exceeded - retry after %s seconds", retry_after)
|
||||
raise TibberPricesApiClientError(TibberPricesApiClientError.RATE_LIMIT_ERROR.format(retry_after=retry_after))
|
||||
|
||||
# Bad request - non-retryable (invalid query)
|
||||
if response.status == HTTP_BAD_REQUEST:
|
||||
_LOGGER.error("Tibber API rejected request - likely invalid GraphQL query")
|
||||
raise TibberPricesApiClientError(
|
||||
TibberPricesApiClientError.INVALID_QUERY_ERROR.format(message="Bad request - likely invalid GraphQL query")
|
||||
)
|
||||
|
||||
# Server errors 5xx - retryable (temporary server issues)
|
||||
if response.status in (
|
||||
HTTP_INTERNAL_SERVER_ERROR,
|
||||
HTTP_BAD_GATEWAY,
|
||||
HTTP_SERVICE_UNAVAILABLE,
|
||||
HTTP_GATEWAY_TIMEOUT,
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"Tibber API server error %d - temporary issue, will retry",
|
||||
response.status,
|
||||
)
|
||||
# Let this be caught as aiohttp.ClientResponseError in _api_wrapper
|
||||
# where it's converted to CommunicationError with retry logic
|
||||
response.raise_for_status()
|
||||
|
||||
# All other HTTP errors - let aiohttp handle
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
async def verify_graphql_response(response_json: dict, query_type: TibberPricesQueryType) -> None:
|
||||
"""Verify the GraphQL response for errors and data completeness, including empty data."""
|
||||
"""
|
||||
Verify GraphQL response and map error codes to appropriate exceptions.
|
||||
|
||||
GraphQL Error Code Mapping:
|
||||
- UNAUTHENTICATED → AuthenticationError (triggers reauth flow)
|
||||
- FORBIDDEN → PermissionError (non-retryable)
|
||||
- RATE_LIMITED/TOO_MANY_REQUESTS → ApiClientError (retryable)
|
||||
- VALIDATION_ERROR/GRAPHQL_VALIDATION_FAILED → ApiClientError (non-retryable)
|
||||
- Other codes → Generic ApiClientError (with code in message)
|
||||
- Empty data → ApiClientError (non-retryable, API has no data)
|
||||
"""
|
||||
if "errors" in response_json:
|
||||
errors = response_json["errors"]
|
||||
if not errors:
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from .data_transformation import TibberPricesDataTransformer
|
|||
from .listeners import TibberPricesListenerManager
|
||||
from .midnight_handler import TibberPricesMidnightHandler
|
||||
from .periods import TibberPricesPeriodCalculator
|
||||
from .repairs import TibberPricesRepairManager
|
||||
from .time_service import TibberPricesTimeService
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
@ -224,6 +225,11 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
config_entry=config_entry,
|
||||
log_prefix=self._log_prefix,
|
||||
)
|
||||
self._repair_manager = TibberPricesRepairManager(
|
||||
hass=hass,
|
||||
entry_id=config_entry.entry_id,
|
||||
home_name=config_entry.title,
|
||||
)
|
||||
|
||||
# Register options update listener to invalidate config caches
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(self._handle_options_update))
|
||||
|
|
@ -504,11 +510,14 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
- Timer #2: Quarter-hour entity updates
|
||||
- Timer #3: Minute timing sensor updates
|
||||
|
||||
Also saves cache to persist any unsaved changes.
|
||||
Also saves cache to persist any unsaved changes and clears all repairs.
|
||||
"""
|
||||
# Cancel all timers first
|
||||
self._listener_manager.cancel_timers()
|
||||
|
||||
# Clear all repairs when integration is removed or disabled
|
||||
await self._repair_manager.clear_all_repairs()
|
||||
|
||||
# Save cache to persist any unsaved data
|
||||
# This ensures we don't lose data if HA is shutting down
|
||||
try:
|
||||
|
|
@ -633,6 +642,10 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
# Reset lifecycle state on error
|
||||
self._is_fetching = False
|
||||
self._lifecycle_state = "error"
|
||||
|
||||
# Track rate limit errors for repair system
|
||||
await self._track_rate_limit_error(err)
|
||||
|
||||
# No separate lifecycle notification needed - error case returns data
|
||||
# which triggers normal async_update_listeners()
|
||||
return await self._data_fetcher.handle_api_error(
|
||||
|
|
@ -640,8 +653,43 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
self._transform_data,
|
||||
)
|
||||
else:
|
||||
# Check for repair conditions after successful update
|
||||
await self._check_repair_conditions(result, current_time)
|
||||
return result
|
||||
|
||||
async def _track_rate_limit_error(self, error: Exception) -> None:
|
||||
"""Track rate limit errors for repair notification system."""
|
||||
error_str = str(error).lower()
|
||||
is_rate_limit = "429" in error_str or "rate limit" in error_str or "too many requests" in error_str
|
||||
if is_rate_limit:
|
||||
await self._repair_manager.track_rate_limit_error()
|
||||
|
||||
async def _check_repair_conditions(
|
||||
self,
|
||||
result: dict[str, Any],
|
||||
current_time: datetime,
|
||||
) -> None:
|
||||
"""Check and manage repair conditions after successful data update."""
|
||||
# 1. Home not found detection (home was removed from Tibber account)
|
||||
if result and result.get("_home_not_found"):
|
||||
await self._repair_manager.create_home_not_found_repair()
|
||||
# Remove the marker before returning to entities
|
||||
result.pop("_home_not_found", None)
|
||||
else:
|
||||
# Home exists - clear any existing repair
|
||||
await self._repair_manager.clear_home_not_found_repair()
|
||||
|
||||
# 2. Tomorrow data availability (after 18:00)
|
||||
if result and "priceInfo" in result:
|
||||
has_tomorrow_data = self._data_fetcher.has_tomorrow_data(result["priceInfo"])
|
||||
await self._repair_manager.check_tomorrow_data_availability(
|
||||
has_tomorrow_data=has_tomorrow_data,
|
||||
current_time=current_time,
|
||||
)
|
||||
|
||||
# 3. Clear rate limit tracking on successful API call
|
||||
await self._repair_manager.clear_rate_limit_tracking()
|
||||
|
||||
async def load_cache(self) -> None:
|
||||
"""Load cached data from storage."""
|
||||
await self._data_fetcher.load_cache()
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import timedelta
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import timedelta
|
||||
|
||||
from custom_components.tibber_prices.api import (
|
||||
TibberPricesApiClientAuthenticationError,
|
||||
TibberPricesApiClientCommunicationError,
|
||||
|
|
@ -242,6 +240,26 @@ class TibberPricesDataFetcher:
|
|||
self._log("warning", "Home %s not found in user data, using EUR as default", home_id)
|
||||
return "EUR"
|
||||
|
||||
def _check_home_exists(self, home_id: str) -> bool:
|
||||
"""
|
||||
Check if a home ID exists in cached user data.
|
||||
|
||||
Args:
|
||||
home_id: The home ID to check.
|
||||
|
||||
Returns:
|
||||
True if home exists, False otherwise.
|
||||
|
||||
"""
|
||||
if not self._cached_user_data:
|
||||
# No user data yet - assume home exists (will be checked on next update)
|
||||
return True
|
||||
|
||||
viewer = self._cached_user_data.get("viewer", {})
|
||||
homes = viewer.get("homes", [])
|
||||
|
||||
return any(home.get("id") == home_id for home in homes)
|
||||
|
||||
async def handle_main_entry_update(
|
||||
self,
|
||||
current_time: datetime,
|
||||
|
|
@ -252,6 +270,17 @@ class TibberPricesDataFetcher:
|
|||
# Update user data if needed (daily check)
|
||||
user_data_updated = await self.update_user_data_if_needed(current_time)
|
||||
|
||||
# Check if this home still exists in user data after update
|
||||
# This detects when a home was removed from the Tibber account
|
||||
home_exists = self._check_home_exists(home_id)
|
||||
if not home_exists:
|
||||
self._log("warning", "Home ID %s not found in Tibber account", home_id)
|
||||
# Return a special marker in the result that coordinator can check
|
||||
# We still need to return valid data to avoid coordinator errors
|
||||
result = transform_fn(self._cached_price_data or {})
|
||||
result["_home_not_found"] = True # Special marker for coordinator
|
||||
return result
|
||||
|
||||
# Check if we need to update price data
|
||||
should_update = self.should_update_price_data(current_time)
|
||||
|
||||
|
|
@ -329,3 +358,37 @@ class TibberPricesDataFetcher:
|
|||
def cached_user_data(self) -> dict[str, Any] | None:
|
||||
"""Get cached user data."""
|
||||
return self._cached_user_data
|
||||
|
||||
def has_tomorrow_data(self, price_info: list[dict[str, Any]]) -> bool:
|
||||
"""
|
||||
Check if tomorrow's price data is available.
|
||||
|
||||
Args:
|
||||
price_info: List of price intervals from coordinator data.
|
||||
|
||||
Returns:
|
||||
True if at least one interval from tomorrow is present.
|
||||
|
||||
"""
|
||||
if not price_info:
|
||||
return False
|
||||
|
||||
# Get tomorrow's date
|
||||
now = self.time.now()
|
||||
tomorrow = (self.time.as_local(now) + timedelta(days=1)).date()
|
||||
|
||||
# Check if any interval is from tomorrow
|
||||
for interval in price_info:
|
||||
if "startsAt" not in interval:
|
||||
continue
|
||||
|
||||
# startsAt is already a datetime object after _transform_data()
|
||||
interval_time = interval["startsAt"]
|
||||
if isinstance(interval_time, str):
|
||||
# Fallback: parse if still string (shouldn't happen with transformed data)
|
||||
interval_time = self.time.parse_datetime(interval_time)
|
||||
|
||||
if interval_time and self.time.as_local(interval_time).date() == tomorrow:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
|||
228
custom_components/tibber_prices/coordinator/repairs.py
Normal file
228
custom_components/tibber_prices/coordinator/repairs.py
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
"""
|
||||
Repair issue management for Tibber Prices integration.
|
||||
|
||||
This module handles creation and cleanup of repair issues that notify users
|
||||
about problems requiring attention in the Home Assistant UI.
|
||||
|
||||
Repair Types:
|
||||
1. Tomorrow Data Missing - Warns when tomorrow's price data is unavailable after 18:00
|
||||
2. Persistent Rate Limits - Warns when API rate limiting persists after multiple errors
|
||||
3. Home Not Found - Warns when a home no longer exists in the Tibber account
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from custom_components.tibber_prices.const import DOMAIN
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Repair issue tracking thresholds
|
||||
TOMORROW_DATA_WARNING_HOUR = 18 # Warn after 18:00 if tomorrow data missing
|
||||
RATE_LIMIT_WARNING_THRESHOLD = 3 # Warn after 3 consecutive rate limit errors
|
||||
|
||||
|
||||
class TibberPricesRepairManager:
|
||||
"""Manage repair issues for Tibber Prices integration."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry_id: str, home_name: str) -> None:
|
||||
"""
|
||||
Initialize repair manager.
|
||||
|
||||
Args:
|
||||
hass: Home Assistant instance
|
||||
entry_id: Config entry ID for this home
|
||||
home_name: Display name of the home (for user-friendly messages)
|
||||
|
||||
"""
|
||||
self._hass = hass
|
||||
self._entry_id = entry_id
|
||||
self._home_name = home_name
|
||||
|
||||
# Track consecutive rate limit errors
|
||||
self._rate_limit_error_count = 0
|
||||
|
||||
# Track if repairs are currently active
|
||||
self._tomorrow_data_repair_active = False
|
||||
self._rate_limit_repair_active = False
|
||||
self._home_not_found_repair_active = False
|
||||
|
||||
async def check_tomorrow_data_availability(
|
||||
self,
|
||||
has_tomorrow_data: bool, # noqa: FBT001 - Clear meaning in context
|
||||
current_time: datetime,
|
||||
) -> None:
|
||||
"""
|
||||
Check if tomorrow data is available and create/clear repair as needed.
|
||||
|
||||
Creates repair if:
|
||||
- Current hour >= 18:00 (after expected data availability)
|
||||
- Tomorrow's data is missing
|
||||
|
||||
Clears repair if:
|
||||
- Tomorrow's data is now available
|
||||
|
||||
Args:
|
||||
has_tomorrow_data: Whether tomorrow's data is available
|
||||
current_time: Current local datetime for hour check
|
||||
|
||||
"""
|
||||
should_warn = current_time.hour >= TOMORROW_DATA_WARNING_HOUR and not has_tomorrow_data
|
||||
|
||||
if should_warn and not self._tomorrow_data_repair_active:
|
||||
await self._create_tomorrow_data_repair()
|
||||
elif not should_warn and self._tomorrow_data_repair_active:
|
||||
await self._clear_tomorrow_data_repair()
|
||||
|
||||
async def track_rate_limit_error(self) -> None:
|
||||
"""
|
||||
Track rate limit error and create repair if threshold exceeded.
|
||||
|
||||
Increments rate limit error counter and creates repair issue
|
||||
if threshold (3 consecutive errors) is reached.
|
||||
"""
|
||||
self._rate_limit_error_count += 1
|
||||
|
||||
if self._rate_limit_error_count >= RATE_LIMIT_WARNING_THRESHOLD and not self._rate_limit_repair_active:
|
||||
await self._create_rate_limit_repair()
|
||||
|
||||
async def clear_rate_limit_tracking(self) -> None:
|
||||
"""
|
||||
Clear rate limit error tracking after successful API call.
|
||||
|
||||
Resets counter and clears any active repair issue.
|
||||
"""
|
||||
self._rate_limit_error_count = min(self._rate_limit_error_count, 0)
|
||||
|
||||
if self._rate_limit_repair_active:
|
||||
await self._clear_rate_limit_repair()
|
||||
|
||||
async def create_home_not_found_repair(self) -> None:
|
||||
"""
|
||||
Create repair for home no longer found in Tibber account.
|
||||
|
||||
This indicates the home was deleted from the user's Tibber account
|
||||
but the config entry still exists in Home Assistant.
|
||||
"""
|
||||
if self._home_not_found_repair_active:
|
||||
return
|
||||
|
||||
_LOGGER.warning(
|
||||
"Home '%s' not found in Tibber account - creating repair issue",
|
||||
self._home_name,
|
||||
)
|
||||
|
||||
ir.async_create_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"home_not_found_{self._entry_id}",
|
||||
is_fixable=True,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="home_not_found",
|
||||
translation_placeholders={
|
||||
"home_name": self._home_name,
|
||||
"entry_id": self._entry_id,
|
||||
},
|
||||
)
|
||||
self._home_not_found_repair_active = True
|
||||
|
||||
async def clear_home_not_found_repair(self) -> None:
|
||||
"""Clear home not found repair (home is available again or entry removed)."""
|
||||
if not self._home_not_found_repair_active:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Clearing home not found repair for '%s'", self._home_name)
|
||||
|
||||
ir.async_delete_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"home_not_found_{self._entry_id}",
|
||||
)
|
||||
self._home_not_found_repair_active = False
|
||||
|
||||
async def clear_all_repairs(self) -> None:
|
||||
"""
|
||||
Clear all active repair issues.
|
||||
|
||||
Called during coordinator shutdown or entry removal.
|
||||
"""
|
||||
if self._tomorrow_data_repair_active:
|
||||
await self._clear_tomorrow_data_repair()
|
||||
if self._rate_limit_repair_active:
|
||||
await self._clear_rate_limit_repair()
|
||||
if self._home_not_found_repair_active:
|
||||
await self.clear_home_not_found_repair()
|
||||
|
||||
async def _create_tomorrow_data_repair(self) -> None:
|
||||
"""Create repair issue for missing tomorrow data."""
|
||||
_LOGGER.warning(
|
||||
"Tomorrow's price data missing after %d:00 for home '%s' - creating repair issue",
|
||||
TOMORROW_DATA_WARNING_HOUR,
|
||||
self._home_name,
|
||||
)
|
||||
|
||||
ir.async_create_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"tomorrow_data_missing_{self._entry_id}",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="tomorrow_data_missing",
|
||||
translation_placeholders={
|
||||
"home_name": self._home_name,
|
||||
"warning_hour": str(TOMORROW_DATA_WARNING_HOUR),
|
||||
},
|
||||
)
|
||||
self._tomorrow_data_repair_active = True
|
||||
|
||||
async def _clear_tomorrow_data_repair(self) -> None:
|
||||
"""Clear tomorrow data repair issue."""
|
||||
_LOGGER.debug("Tomorrow's data now available for '%s' - clearing repair issue", self._home_name)
|
||||
|
||||
ir.async_delete_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"tomorrow_data_missing_{self._entry_id}",
|
||||
)
|
||||
self._tomorrow_data_repair_active = False
|
||||
|
||||
async def _create_rate_limit_repair(self) -> None:
|
||||
"""Create repair issue for persistent rate limiting."""
|
||||
_LOGGER.warning(
|
||||
"Persistent API rate limiting detected for home '%s' (%d consecutive errors) - creating repair issue",
|
||||
self._home_name,
|
||||
self._rate_limit_error_count,
|
||||
)
|
||||
|
||||
ir.async_create_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"rate_limit_exceeded_{self._entry_id}",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="rate_limit_exceeded",
|
||||
translation_placeholders={
|
||||
"home_name": self._home_name,
|
||||
"error_count": str(self._rate_limit_error_count),
|
||||
},
|
||||
)
|
||||
self._rate_limit_repair_active = True
|
||||
|
||||
async def _clear_rate_limit_repair(self) -> None:
|
||||
"""Clear rate limit repair issue."""
|
||||
_LOGGER.debug("Rate limiting resolved for '%s' - clearing repair issue", self._home_name)
|
||||
|
||||
ir.async_delete_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"rate_limit_exceeded_{self._entry_id}",
|
||||
)
|
||||
self._rate_limit_repair_active = False
|
||||
|
|
@ -834,6 +834,18 @@
|
|||
"homes_removed": {
|
||||
"title": "Tibber-Häuser entfernt",
|
||||
"description": "Wir haben erkannt, dass {count} Zuhause aus deinem Tibber-Konto entfernt wurde(n): {homes}. Bitte überprüfe deine Tibber-Integrationskonfiguration."
|
||||
},
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Preisdaten für morgen fehlen für {home_name}",
|
||||
"description": "Die Strompreisdaten für morgen sind nach {warning_hour}:00 Uhr immer noch nicht verfügbar. Das ist ungewöhnlich, da Tibber normalerweise die Preise für morgen am Nachmittag veröffentlicht (ca. 13:00-14:00 Uhr MEZ).\n\nMögliche Ursachen:\n- Tibber hat die Preise für morgen noch nicht veröffentlicht\n- Temporäre API-Probleme\n- Dein Stromanbieter hat die Preise noch nicht an Tibber übermittelt\n\nDieses Problem löst sich automatisch, sobald die Daten für morgen verfügbar sind. Falls dies nach 20:00 Uhr weiterhin besteht, prüfe bitte die Tibber-App oder kontaktiere den Tibber-Support."
|
||||
},
|
||||
"rate_limit_exceeded": {
|
||||
"title": "API-Ratenlimit erreicht für {home_name}",
|
||||
"description": "Die Tibber-API hat diese Integration nach {error_count} aufeinanderfolgenden Fehlern ratenlimitiert. Das bedeutet, dass Anfragen zu häufig gestellt werden.\n\nDie Integration wird automatisch mit zunehmenden Verzögerungen erneut versuchen. Dieses Problem löst sich, sobald das Ratenlimit abläuft.\n\nFalls dies mehrere Stunden anhält, überprüfe:\n- Ob mehrere Home Assistant Instanzen denselben API-Token verwenden\n- Ob andere Anwendungen deinen Tibber-API-Token stark nutzen\n- Die Update-Frequenz reduzieren, falls du sie angepasst hast"
|
||||
},
|
||||
"home_not_found": {
|
||||
"title": "Zuhause '{home_name}' nicht im Tibber-Konto gefunden",
|
||||
"description": "Das in dieser Integration konfigurierte Zuhause (Eintrag-ID: {entry_id}) ist nicht mehr in deinem Tibber-Konto verfügbar. Dies passiert normalerweise, wenn:\n- Das Zuhause aus deinem Tibber-Konto gelöscht wurde\n- Das Zuhause zu einem anderen Tibber-Konto verschoben wurde\n- Der Zugriff auf dieses Zuhause widerrufen wurde\n\nBitte entferne diesen Integrationseintrag und füge ihn erneut hinzu, falls das Zuhause weiterhin überwacht werden soll. Um diesen Eintrag zu entfernen, gehe zu Einstellungen → Geräte & Dienste → Tibber Prices und lösche die Konfiguration '{home_name}'."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
|
|
|||
|
|
@ -834,6 +834,18 @@
|
|||
"homes_removed": {
|
||||
"title": "Tibber homes removed",
|
||||
"description": "We detected that {count} home(s) have been removed from your Tibber account: {homes}. Please review your Tibber integration configuration."
|
||||
},
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Tomorrow's price data missing for {home_name}",
|
||||
"description": "Tomorrow's electricity price data is still unavailable after {warning_hour}:00. This is unusual, as Tibber typically publishes tomorrow's prices in the afternoon (around 13:00-14:00 CET).\n\nPossible causes:\n- Tibber has not yet published tomorrow's prices\n- Temporary API issues\n- Your electricity provider has not submitted prices to Tibber\n\nThis issue will automatically resolve once tomorrow's data becomes available. If this persists beyond 20:00, please check the Tibber app or contact Tibber support."
|
||||
},
|
||||
"rate_limit_exceeded": {
|
||||
"title": "API rate limit exceeded for {home_name}",
|
||||
"description": "The Tibber API has rate-limited this integration after {error_count} consecutive errors. This means requests are being made too frequently.\n\nThe integration will automatically retry with increasing delays. This issue will resolve once the rate limit expires.\n\nIf this persists for several hours, consider:\n- Checking if multiple Home Assistant instances are using the same API token\n- Verifying no other applications are heavily using your Tibber API token\n- Reducing the update frequency if you've customized it"
|
||||
},
|
||||
"home_not_found": {
|
||||
"title": "Home '{home_name}' not found in Tibber account",
|
||||
"description": "The home configured in this integration (entry ID: {entry_id}) is no longer available in your Tibber account. This typically happens when:\n- The home was deleted from your Tibber account\n- The home was moved to a different Tibber account\n- Access to this home was revoked\n\nPlease remove this integration entry and re-add it if the home should still be monitored. To remove this entry, go to Settings → Devices & Services → Tibber Prices and delete the '{home_name}' configuration."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
|
|
|||
|
|
@ -834,6 +834,18 @@
|
|||
"homes_removed": {
|
||||
"title": "Tibber-hjem fjernet",
|
||||
"description": "Vi oppdaget at {count} hjem har blitt fjernet fra din Tibber-konto: {homes}. Vennligst gjennomgå din Tibber-integrasjonskonfigurasjon."
|
||||
},
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Prisdata for i morgen mangler for {home_name}",
|
||||
"description": "Strømprisdata for i morgen er fortsatt utilgjengelig etter {warning_hour}:00. Dette er uvanlig, da Tibber vanligvis publiserer morgendagens priser på ettermiddagen (rundt 13:00-14:00 CET).\n\nMulige årsaker:\n- Tibber har ikke publisert morgendagens priser ennå\n- Midlertidige API-problemer\n- Strømleverandøren din har ikke sendt inn priser til Tibber\n\nDette problemet vil løse seg automatisk når morgendagens data blir tilgjengelig. Hvis dette vedvarer etter 20:00, vennligst sjekk Tibber-appen eller kontakt Tibber-support."
|
||||
},
|
||||
"rate_limit_exceeded": {
|
||||
"title": "API-hastighetsbegrensning overskredet for {home_name}",
|
||||
"description": "Tibber-APIet har hastighetsbegrenset denne integrasjonen etter {error_count} påfølgende feil. Dette betyr at forespørsler blir gjort for hyppig.\n\nIntegrasjonen vil automatisk prøve på nytt med økende forsinkelser. Dette problemet vil løse seg når hastighetsbegrensningen utløper.\n\nHvis dette vedvarer i flere timer, vurder:\n- Å sjekke om flere Home Assistant-instanser bruker samme API-token\n- Å verifisere at ingen andre applikasjoner bruker Tibber-API-tokenet ditt mye\n- Å redusere oppdateringsfrekvensen hvis du har tilpasset den"
|
||||
},
|
||||
"home_not_found": {
|
||||
"title": "Hjemmet '{home_name}' ble ikke funnet i Tibber-kontoen",
|
||||
"description": "Hjemmet konfigurert i denne integrasjonen (oppførings-ID: {entry_id}) er ikke lenger tilgjengelig i Tibber-kontoen din. Dette skjer vanligvis når:\n- Hjemmet ble slettet fra Tibber-kontoen din\n- Hjemmet ble flyttet til en annen Tibber-konto\n- Tilgang til dette hjemmet ble tilbakekalt\n\nVennligst fjern denne integrasjonsoppføringen og legg den til på nytt hvis hjemmet fortsatt skal overvåkes. For å fjerne denne oppføringen, gå til Innstillinger → Enheter og tjenester → Tibber Prices og slett '{home_name}'-konfigurasjonen."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
|
|
|||
|
|
@ -834,6 +834,18 @@
|
|||
"homes_removed": {
|
||||
"title": "Tibber-huizen verwijderd",
|
||||
"description": "We hebben gedetecteerd dat {count} huis/huizen zijn verwijderd van je Tibber-account: {homes}. Controleer je Tibber-integratieconfiguratie."
|
||||
},
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Prijsgegevens voor morgen ontbreken voor {home_name}",
|
||||
"description": "De elektriciteitsprijsgegevens voor morgen zijn nog steeds niet beschikbaar na {warning_hour}:00 uur. Dit is ongebruikelijk, aangezien Tibber doorgaans de prijzen voor morgen in de middag publiceert (rond 13:00-14:00 CET).\n\nMogelijke oorzaken:\n- Tibber heeft de prijzen voor morgen nog niet gepubliceerd\n- Tijdelijke API-problemen\n- Je elektriciteitsleverancier heeft nog geen prijzen aan Tibber ingediend\n\nDit probleem wordt automatisch opgelost zodra de gegevens voor morgen beschikbaar zijn. Als dit na 20:00 uur aanhoudt, controleer dan de Tibber-app of neem contact op met Tibber-ondersteuning."
|
||||
},
|
||||
"rate_limit_exceeded": {
|
||||
"title": "API-snelheidslimiet overschreden voor {home_name}",
|
||||
"description": "De Tibber-API heeft deze integratie beperkt na {error_count} opeenvolgende fouten. Dit betekent dat verzoeken te vaak worden gedaan.\n\nDe integratie zal automatisch opnieuw proberen met toenemende vertragingen. Dit probleem wordt opgelost zodra de snelheidslimiet verloopt.\n\nAls dit meerdere uren aanhoudt, overweeg dan:\n- Controleren of meerdere Home Assistant-instanties hetzelfde API-token gebruiken\n- Verifiëren dat geen andere applicaties je Tibber-API-token intensief gebruiken\n- De updatefrequentie verminderen als je deze hebt aangepast"
|
||||
},
|
||||
"home_not_found": {
|
||||
"title": "Huis '{home_name}' niet gevonden in Tibber-account",
|
||||
"description": "Het huis geconfigureerd in deze integratie (entry ID: {entry_id}) is niet langer beschikbaar in je Tibber-account. Dit gebeurt meestal wanneer:\n- Het huis is verwijderd uit je Tibber-account\n- Het huis is verplaatst naar een ander Tibber-account\n- Toegang tot dit huis is ingetrokken\n\nVerwijder deze integratie-entry en voeg deze opnieuw toe als het huis nog steeds moet worden gemonitord. Om deze entry te verwijderen, ga naar Instellingen → Apparaten & Diensten → Tibber Prices en verwijder de '{home_name}'-configuratie."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
|
|
|||
|
|
@ -834,6 +834,18 @@
|
|||
"homes_removed": {
|
||||
"title": "Tibber-hem borttagna",
|
||||
"description": "Vi upptäckte att {count} hem har tagits bort från ditt Tibber-konto: {homes}. Vänligen granska din Tibber-integrationskonfiguration."
|
||||
},
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Prisdata för imorgon saknas för {home_name}",
|
||||
"description": "Elprisdata för imorgon är fortfarande otillgänglig efter {warning_hour}:00. Detta är ovanligt, eftersom Tibber vanligtvis publicerar morgondagens priser på eftermiddagen (runt 13:00-14:00 CET).\n\nMöjliga orsaker:\n- Tibber har inte publicerat morgondagens priser ännu\n- Tillfälliga API-problem\n- Din elleverantör har inte skickat in priser till Tibber\n\nDetta problem kommer att lösas automatiskt när morgondagens data blir tillgänglig. Om detta fortsätter efter 20:00, vänligen kontrollera Tibber-appen eller kontakta Tibber-support."
|
||||
},
|
||||
"rate_limit_exceeded": {
|
||||
"title": "API-hastighetsgräns överskriden för {home_name}",
|
||||
"description": "Tibber-API:et har hastighetsbegränsat denna integration efter {error_count} på varandra följande fel. Detta betyder att förfrågningar görs för ofta.\n\nIntegrationen kommer automatiskt att försöka igen med ökande fördröjningar. Detta problem kommer att lösas när hastighetsgränsen löper ut.\n\nOm detta fortsätter i flera timmar, överväg:\n- Att kontrollera om flera Home Assistant-instanser använder samma API-token\n- Att verifiera att inga andra applikationer använder din Tibber-API-token mycket\n- Att minska uppdateringsfrekvensen om du har anpassat den"
|
||||
},
|
||||
"home_not_found": {
|
||||
"title": "Hemmet '{home_name}' hittades inte i Tibber-kontot",
|
||||
"description": "Hemmet som konfigurerats i denna integration (post-ID: {entry_id}) är inte längre tillgängligt i ditt Tibber-konto. Detta händer vanligtvis när:\n- Hemmet togs bort från ditt Tibber-konto\n- Hemmet flyttades till ett annat Tibber-konto\n- Åtkomst till detta hem återkallades\n\nVänligen ta bort denna integrationspost och lägg till den igen om hemmet fortfarande ska övervakas. För att ta bort denna post, gå till Inställningar → Enheter och tjänster → Tibber Prices och ta bort '{home_name}'-konfigurationen."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
|
|
|||
330
docs/developer/docs/repairs-system.md
Normal file
330
docs/developer/docs/repairs-system.md
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
# Repairs System
|
||||
|
||||
The Tibber Prices integration includes a proactive repair notification system that alerts users to important issues requiring attention. This system leverages Home Assistant's built-in `issue_registry` to create user-facing notifications in the UI.
|
||||
|
||||
## Overview
|
||||
|
||||
The repairs system is implemented in `coordinator/repairs.py` via the `TibberPricesRepairManager` class, which is instantiated in the coordinator and integrated into the update cycle.
|
||||
|
||||
**Design Principles:**
|
||||
- **Proactive**: Detect issues before they become critical
|
||||
- **User-friendly**: Clear explanations with actionable guidance
|
||||
- **Auto-clearing**: Repairs automatically disappear when conditions resolve
|
||||
- **Non-blocking**: Integration continues to work even with active repairs
|
||||
|
||||
## Implemented Repair Types
|
||||
|
||||
### 1. Tomorrow Data Missing
|
||||
|
||||
**Issue ID:** `tomorrow_data_missing_{entry_id}`
|
||||
|
||||
**When triggered:**
|
||||
- Current time is after 18:00 (configurable via `TOMORROW_DATA_WARNING_HOUR`)
|
||||
- Tomorrow's electricity price data is still not available
|
||||
|
||||
**When cleared:**
|
||||
- Tomorrow's data becomes available
|
||||
- Automatically checks on every successful API update
|
||||
|
||||
**User impact:**
|
||||
Users cannot plan ahead for tomorrow's electricity usage optimization. Automations relying on tomorrow's prices will not work.
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# In coordinator update cycle
|
||||
has_tomorrow_data = self._data_fetcher.has_tomorrow_data(result["priceInfo"])
|
||||
await self._repair_manager.check_tomorrow_data_availability(
|
||||
has_tomorrow_data=has_tomorrow_data,
|
||||
current_time=current_time,
|
||||
)
|
||||
```
|
||||
|
||||
**Translation placeholders:**
|
||||
- `home_name`: Name of the affected home
|
||||
- `warning_hour`: Hour after which warning appears (default: 18)
|
||||
|
||||
### 2. Rate Limit Exceeded
|
||||
|
||||
**Issue ID:** `rate_limit_exceeded_{entry_id}`
|
||||
|
||||
**When triggered:**
|
||||
- Integration encounters 3 or more consecutive rate limit errors (HTTP 429)
|
||||
- Threshold configurable via `RATE_LIMIT_WARNING_THRESHOLD`
|
||||
|
||||
**When cleared:**
|
||||
- Successful API call completes (no rate limit error)
|
||||
- Error counter resets to 0
|
||||
|
||||
**User impact:**
|
||||
API requests are being throttled, causing stale data. Updates may be delayed until rate limit expires.
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# In error handler
|
||||
is_rate_limit = (
|
||||
"429" in error_str
|
||||
or "rate limit" in error_str
|
||||
or "too many requests" in error_str
|
||||
)
|
||||
if is_rate_limit:
|
||||
await self._repair_manager.track_rate_limit_error()
|
||||
|
||||
# On successful update
|
||||
await self._repair_manager.clear_rate_limit_tracking()
|
||||
```
|
||||
|
||||
**Translation placeholders:**
|
||||
- `home_name`: Name of the affected home
|
||||
- `error_count`: Number of consecutive rate limit errors
|
||||
|
||||
### 3. Home Not Found
|
||||
|
||||
**Issue ID:** `home_not_found_{entry_id}`
|
||||
|
||||
**When triggered:**
|
||||
- Home configured in this integration is no longer present in Tibber account
|
||||
- Detected during user data refresh (daily check)
|
||||
|
||||
**When cleared:**
|
||||
- Home reappears in Tibber account (unlikely - manual cleanup expected)
|
||||
- Integration entry is removed (shutdown cleanup)
|
||||
|
||||
**User impact:**
|
||||
Integration cannot fetch data for a non-existent home. User must remove the config entry and re-add if needed.
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# After user data update
|
||||
home_exists = self._data_fetcher._check_home_exists(home_id)
|
||||
if not home_exists:
|
||||
await self._repair_manager.create_home_not_found_repair()
|
||||
else:
|
||||
await self._repair_manager.clear_home_not_found_repair()
|
||||
```
|
||||
|
||||
**Translation placeholders:**
|
||||
- `home_name`: Name of the missing home
|
||||
- `entry_id`: Config entry ID for reference
|
||||
|
||||
## Configuration Constants
|
||||
|
||||
Defined in `coordinator/constants.py`:
|
||||
|
||||
```python
|
||||
TOMORROW_DATA_WARNING_HOUR = 18 # Hour after which to warn about missing tomorrow data
|
||||
RATE_LIMIT_WARNING_THRESHOLD = 3 # Number of consecutive errors before creating repair
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Class Structure
|
||||
|
||||
```python
|
||||
class TibberPricesRepairManager:
|
||||
"""Manages repair issues for a single Tibber home."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry_id: str,
|
||||
home_name: str,
|
||||
) -> None:
|
||||
"""Initialize repair manager."""
|
||||
self._hass = hass
|
||||
self._entry_id = entry_id
|
||||
self._home_name = home_name
|
||||
|
||||
# State tracking
|
||||
self._tomorrow_data_repair_active = False
|
||||
self._rate_limit_error_count = 0
|
||||
self._rate_limit_repair_active = False
|
||||
self._home_not_found_repair_active = False
|
||||
```
|
||||
|
||||
### State Tracking
|
||||
|
||||
Each repair type maintains internal state to avoid redundant operations:
|
||||
|
||||
- **`_tomorrow_data_repair_active`**: Boolean flag, prevents creating duplicate repairs
|
||||
- **`_rate_limit_error_count`**: Integer counter, tracks consecutive errors
|
||||
- **`_rate_limit_repair_active`**: Boolean flag, tracks repair status
|
||||
- **`_home_not_found_repair_active`**: Boolean flag, one-time repair (manual cleanup)
|
||||
|
||||
### Lifecycle Integration
|
||||
|
||||
**Coordinator Initialization:**
|
||||
```python
|
||||
self._repair_manager = TibberPricesRepairManager(
|
||||
hass=hass,
|
||||
entry_id=self.config_entry.entry_id,
|
||||
home_name=self._home_name,
|
||||
)
|
||||
```
|
||||
|
||||
**Update Cycle Integration:**
|
||||
```python
|
||||
# Success path - check conditions
|
||||
if result and "priceInfo" in result:
|
||||
has_tomorrow_data = self._data_fetcher.has_tomorrow_data(result["priceInfo"])
|
||||
await self._repair_manager.check_tomorrow_data_availability(
|
||||
has_tomorrow_data=has_tomorrow_data,
|
||||
current_time=current_time,
|
||||
)
|
||||
await self._repair_manager.clear_rate_limit_tracking()
|
||||
|
||||
# Error path - track rate limits
|
||||
if is_rate_limit:
|
||||
await self._repair_manager.track_rate_limit_error()
|
||||
```
|
||||
|
||||
**Shutdown Cleanup:**
|
||||
```python
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Shut down coordinator and clean up."""
|
||||
await self._repair_manager.clear_all_repairs()
|
||||
# ... other cleanup ...
|
||||
```
|
||||
|
||||
## Translation System
|
||||
|
||||
Repairs use Home Assistant's standard translation system. Translations are defined in:
|
||||
|
||||
- `/translations/en.json`
|
||||
- `/translations/de.json`
|
||||
- `/translations/nb.json`
|
||||
- `/translations/nl.json`
|
||||
- `/translations/sv.json`
|
||||
|
||||
**Structure:**
|
||||
```json
|
||||
{
|
||||
"issues": {
|
||||
"tomorrow_data_missing": {
|
||||
"title": "Tomorrow's price data missing for {home_name}",
|
||||
"description": "Detailed explanation with multiple paragraphs...\n\nPossible causes:\n- Cause 1\n- Cause 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Home Assistant Integration
|
||||
|
||||
Repairs appear in:
|
||||
- **Settings → System → Repairs** (main repairs panel)
|
||||
- **Notifications** (bell icon in UI shows repair count)
|
||||
|
||||
Repair properties:
|
||||
- **`is_fixable=False`**: No automated fix available (user action required)
|
||||
- **`severity=IssueSeverity.WARNING`**: Yellow warning level (not critical)
|
||||
- **`translation_key`**: References `issues.{key}` in translation files
|
||||
|
||||
## Testing Repairs
|
||||
|
||||
### Tomorrow Data Missing
|
||||
|
||||
1. Wait until after 18:00 local time
|
||||
2. Ensure integration has no tomorrow price data
|
||||
3. Repair should appear in UI
|
||||
4. When tomorrow data arrives (next API fetch), repair clears
|
||||
|
||||
**Manual trigger:**
|
||||
```python
|
||||
# Temporarily set warning hour to current hour for testing
|
||||
TOMORROW_DATA_WARNING_HOUR = datetime.now().hour
|
||||
```
|
||||
|
||||
### Rate Limit Exceeded
|
||||
|
||||
1. Simulate 3+ consecutive rate limit errors
|
||||
2. Repair should appear after 3rd error
|
||||
3. Successful API call clears the repair
|
||||
|
||||
**Manual test:**
|
||||
- Reduce API polling interval to trigger rate limiting
|
||||
- Or temporarily return HTTP 429 in API client
|
||||
|
||||
### Home Not Found
|
||||
|
||||
1. Remove home from Tibber account via app/web
|
||||
2. Wait for user data refresh (daily check)
|
||||
3. Repair appears indicating home is missing
|
||||
4. Remove integration entry to clear repair
|
||||
|
||||
## Adding New Repair Types
|
||||
|
||||
To add a new repair type:
|
||||
|
||||
1. **Add constants** (if needed) in `coordinator/constants.py`
|
||||
2. **Add state tracking** in `TibberPricesRepairManager.__init__`
|
||||
3. **Implement check method** with create/clear logic
|
||||
4. **Add translations** to all 5 language files
|
||||
5. **Integrate into coordinator** update cycle or error handlers
|
||||
6. **Add cleanup** to `clear_all_repairs()` method
|
||||
7. **Document** in this file
|
||||
|
||||
**Example template:**
|
||||
```python
|
||||
async def check_new_condition(self, *, param: bool) -> None:
|
||||
"""Check new condition and create/clear repair."""
|
||||
should_warn = param # Your condition logic
|
||||
|
||||
if should_warn and not self._new_repair_active:
|
||||
await self._create_new_repair()
|
||||
elif not should_warn and self._new_repair_active:
|
||||
await self._clear_new_repair()
|
||||
|
||||
async def _create_new_repair(self) -> None:
|
||||
"""Create new repair issue."""
|
||||
_LOGGER.warning("New issue detected - creating repair")
|
||||
|
||||
ir.async_create_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"new_issue_{self._entry_id}",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="new_issue",
|
||||
translation_placeholders={
|
||||
"home_name": self._home_name,
|
||||
},
|
||||
)
|
||||
self._new_repair_active = True
|
||||
|
||||
async def _clear_new_repair(self) -> None:
|
||||
"""Clear new repair issue."""
|
||||
_LOGGER.debug("New issue resolved - clearing repair")
|
||||
|
||||
ir.async_delete_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
f"new_issue_{self._entry_id}",
|
||||
)
|
||||
self._new_repair_active = False
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use state tracking** - Prevents duplicate repair creation
|
||||
2. **Auto-clear when resolved** - Improves user experience
|
||||
3. **Clear on shutdown** - Prevents orphaned repairs
|
||||
4. **Use descriptive issue IDs** - Include entry_id for multi-home setups
|
||||
5. **Provide actionable guidance** - Tell users what they can do
|
||||
6. **Use appropriate severity** - WARNING for most cases, ERROR only for critical
|
||||
7. **Test all language translations** - Ensure placeholders work correctly
|
||||
8. **Document expected behavior** - What triggers, what clears, what user should do
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential additions to the repairs system:
|
||||
|
||||
- **Stale data warning**: Alert when cache is >24 hours old with no API updates
|
||||
- **Missing permissions**: Detect insufficient API token scopes
|
||||
- **Config migration needed**: Notify users of breaking changes requiring reconfiguration
|
||||
- **Extreme price alert**: Warn when prices exceed historical thresholds (optional, user-configurable)
|
||||
|
||||
## References
|
||||
|
||||
- Home Assistant Repairs Documentation: https://developers.home-assistant.io/docs/core/platform/repairs
|
||||
- Issue Registry API: `homeassistant.helpers.issue_registry`
|
||||
- Integration Constants: `custom_components/tibber_prices/const.py`
|
||||
- Repair Manager Implementation: `custom_components/tibber_prices/coordinator/repairs.py`
|
||||
|
|
@ -25,7 +25,7 @@ const sidebars: SidebarsConfig = {
|
|||
{
|
||||
type: 'category',
|
||||
label: '💻 Development',
|
||||
items: ['setup', 'coding-guidelines', 'critical-patterns', 'debugging'],
|
||||
items: ['setup', 'coding-guidelines', 'critical-patterns', 'repairs-system', 'debugging'],
|
||||
collapsible: true,
|
||||
collapsed: false,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue