hass.tibber_prices/docs/developer/versioned_docs/version-v0.25.0b0/repairs-system.md

10 KiB

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:

# 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:

# 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:

# 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:

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

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:

self._repair_manager = TibberPricesRepairManager(
    hass=hass,
    entry_id=self.config_entry.entry_id,
    home_name=self._home_name,
)

Update Cycle Integration:

# 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:

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:

{
  "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:

# 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:

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