hass.tibber_prices/custom_components/tibber_prices/services/__init__.py
Julian Pawlowski 6e0613c055 feat(services): add 5 scheduling services for price-optimized time windows
New services for finding optimal electricity price windows:
- find_cheapest_block: Cheapest contiguous time block (e.g., dishwasher)
- find_cheapest_hours: Cheapest N hours, non-contiguous (e.g., EV charging)
- find_cheapest_schedule: Multi-task scheduling with no-overlap (e.g., shared circuit)
- find_most_expensive_block: Most expensive contiguous block (peak avoidance)
- find_most_expensive_hours: Most expensive N hours (consumption shifting)

Key features:
- Flexible search range (today, tomorrow, today+tomorrow, rolling window)
- Power profile support for variable consumption patterns
- Price level filtering (e.g., only CHEAP/VERY_CHEAP intervals)
- Comparison details showing savings vs. alternatives
- Sliding window algorithm (O(n)) for block search, greedy scheduling
  for multi-task optimization

Also includes:
- Shared validation utilities (search range, price level, power profile)
- entry_id now optional on all services (auto-selects single home)
- Input validation for existing services (time range, filter conflicts)
- Service icons for all new and existing services
- Translations for all 5 languages (en, de, nb, nl, sv)
- Removed 10 unused config.error translation keys (replaced by exceptions)
- Tests for price window algorithms and search range resolution

Impact: Users can find optimal time windows for appliances, EV charging,
and multi-device scheduling via HA service calls. Existing services
improved with optional entry_id and better input validation.
2026-04-11 18:58:27 +00:00

158 lines
5 KiB
Python

"""
Service handlers for Tibber Prices integration.
This package provides service endpoints for external integrations and data export:
- Chart data export (get_chartdata)
- ApexCharts YAML generation (get_apexcharts_yaml)
- User data refresh (refresh_user_data)
- Debug: Clear tomorrow data (debug_clear_tomorrow) - DevContainer only
Architecture:
- helpers.py: Common utilities (get_entry_and_data)
- formatters.py: Data transformation and formatting functions
- chartdata.py: Main data export service handler
- apexcharts.py: ApexCharts card YAML generator
- refresh_user_data.py: User data refresh handler
- debug_clear_tomorrow.py: Debug tool for testing tomorrow refresh (dev only)
"""
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from custom_components.tibber_prices.const import DOMAIN
from homeassistant.core import SupportsResponse, callback
from .find_cheapest_block import (
FIND_CHEAPEST_BLOCK_SERVICE_NAME,
FIND_CHEAPEST_BLOCK_SERVICE_SCHEMA,
handle_find_cheapest_block,
)
from .find_cheapest_hours import (
FIND_CHEAPEST_HOURS_SERVICE_NAME,
FIND_CHEAPEST_HOURS_SERVICE_SCHEMA,
handle_find_cheapest_hours,
)
from .find_cheapest_schedule import (
FIND_CHEAPEST_SCHEDULE_SERVICE_NAME,
FIND_CHEAPEST_SCHEDULE_SERVICE_SCHEMA,
handle_find_cheapest_schedule,
)
from .find_most_expensive_block import (
FIND_MOST_EXPENSIVE_BLOCK_SERVICE_NAME,
FIND_MOST_EXPENSIVE_BLOCK_SERVICE_SCHEMA,
handle_find_most_expensive_block,
)
from .find_most_expensive_hours import (
FIND_MOST_EXPENSIVE_HOURS_SERVICE_NAME,
FIND_MOST_EXPENSIVE_HOURS_SERVICE_SCHEMA,
handle_find_most_expensive_hours,
)
from .get_apexcharts_yaml import (
APEXCHARTS_SERVICE_SCHEMA,
APEXCHARTS_YAML_SERVICE_NAME,
handle_apexcharts_yaml,
)
from .get_chartdata import CHARTDATA_SERVICE_NAME, CHARTDATA_SERVICE_SCHEMA, handle_chartdata
from .get_price import GET_PRICE_SERVICE_NAME, GET_PRICE_SERVICE_SCHEMA, handle_get_price
from .refresh_user_data import (
REFRESH_USER_DATA_SERVICE_NAME,
REFRESH_USER_DATA_SERVICE_SCHEMA,
handle_refresh_user_data,
)
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
__all__ = [
"async_setup_services",
]
# Check if running in development mode (DevContainer)
_IS_DEV_MODE = os.environ.get("TIBBER_PRICES_DEV") == "1"
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services for Tibber Prices integration."""
hass.services.async_register(
DOMAIN,
APEXCHARTS_YAML_SERVICE_NAME,
handle_apexcharts_yaml,
schema=APEXCHARTS_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
CHARTDATA_SERVICE_NAME,
handle_chartdata,
schema=CHARTDATA_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
GET_PRICE_SERVICE_NAME,
handle_get_price,
schema=GET_PRICE_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
FIND_CHEAPEST_BLOCK_SERVICE_NAME,
handle_find_cheapest_block,
schema=FIND_CHEAPEST_BLOCK_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
FIND_CHEAPEST_HOURS_SERVICE_NAME,
handle_find_cheapest_hours,
schema=FIND_CHEAPEST_HOURS_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
FIND_CHEAPEST_SCHEDULE_SERVICE_NAME,
handle_find_cheapest_schedule,
schema=FIND_CHEAPEST_SCHEDULE_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
FIND_MOST_EXPENSIVE_BLOCK_SERVICE_NAME,
handle_find_most_expensive_block,
schema=FIND_MOST_EXPENSIVE_BLOCK_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
FIND_MOST_EXPENSIVE_HOURS_SERVICE_NAME,
handle_find_most_expensive_hours,
schema=FIND_MOST_EXPENSIVE_HOURS_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
REFRESH_USER_DATA_SERVICE_NAME,
handle_refresh_user_data,
schema=REFRESH_USER_DATA_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
# Debug services - only available in DevContainer (TIBBER_PRICES_DEV=1)
if _IS_DEV_MODE:
from .debug_clear_tomorrow import ( # noqa: PLC0415 - Conditional import for dev-only service
DEBUG_CLEAR_TOMORROW_SERVICE_NAME,
DEBUG_CLEAR_TOMORROW_SERVICE_SCHEMA,
handle_debug_clear_tomorrow,
)
hass.services.async_register(
DOMAIN,
DEBUG_CLEAR_TOMORROW_SERVICE_NAME,
handle_debug_clear_tomorrow,
schema=DEBUG_CLEAR_TOMORROW_SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)