Add prices service calls for easyEnergy (#105175)

This commit is contained in:
Klaas Schoute 2023-12-18 09:37:59 +01:00 committed by GitHub
parent d50b79ba84
commit e59e1d7f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 4116 additions and 0 deletions

View File

@ -8,6 +8,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import EasyEnergyDataUpdateCoordinator
from .services import async_setup_services
PLATFORMS = [Platform.SENSOR]
@ -25,6 +26,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await async_setup_services(hass, coordinator)
return True

View File

@ -0,0 +1,142 @@
"""Services for easyEnergy integration."""
from __future__ import annotations
from datetime import date, datetime
from enum import Enum
from functools import partial
from typing import Final
from easyenergy import Electricity, Gas, VatOption
import voluptuous as vol
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .coordinator import EasyEnergyDataUpdateCoordinator
ATTR_START: Final = "start"
ATTR_END: Final = "end"
ATTR_INCL_VAT: Final = "incl_vat"
GAS_SERVICE_NAME: Final = "get_gas_prices"
ENERGY_USAGE_SERVICE_NAME: Final = "get_energy_usage_prices"
ENERGY_RETURN_SERVICE_NAME: Final = "get_energy_return_prices"
SERVICE_SCHEMA: Final = vol.Schema(
{
vol.Optional(ATTR_START): str,
vol.Optional(ATTR_END): str,
vol.Required(ATTR_INCL_VAT, default=True): bool,
}
)
class PriceType(str, Enum):
"""Type of price."""
ENERGY_USAGE = "energy_usage"
ENERGY_RETURN = "energy_return"
GAS = "gas"
def __get_date(date_input: str | None) -> date | datetime:
"""Get date."""
if not date_input:
return dt_util.now().date()
if value := dt_util.parse_datetime(date_input):
return value
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_date",
translation_placeholders={
"date": date_input,
},
)
def __serialize_prices(prices: list[dict[str, float | datetime]]) -> ServiceResponse:
"""Serialize prices to service response."""
return {
"prices": [
{
key: str(value) if isinstance(value, datetime) else value
for key, value in timestamp_price.items()
}
for timestamp_price in prices
]
}
async def __get_prices(
call: ServiceCall,
*,
coordinator: EasyEnergyDataUpdateCoordinator,
price_type: PriceType,
) -> ServiceResponse:
"""Get prices from easyEnergy."""
start = __get_date(call.data.get(ATTR_START))
end = __get_date(call.data.get(ATTR_END))
vat = VatOption.INCLUDE
if call.data.get(ATTR_INCL_VAT) is False:
vat = VatOption.EXCLUDE
data: Electricity | Gas
if price_type == PriceType.GAS:
data = await coordinator.easyenergy.gas_prices(
start_date=start,
end_date=end,
vat=vat,
)
return __serialize_prices(data.timestamp_prices)
data = await coordinator.easyenergy.energy_prices(
start_date=start,
end_date=end,
vat=vat,
)
if price_type == PriceType.ENERGY_USAGE:
return __serialize_prices(data.timestamp_usage_prices)
return __serialize_prices(data.timestamp_return_prices)
async def async_setup_services(
hass: HomeAssistant,
coordinator: EasyEnergyDataUpdateCoordinator,
) -> None:
"""Set up services for easyEnergy integration."""
hass.services.async_register(
DOMAIN,
GAS_SERVICE_NAME,
partial(__get_prices, coordinator=coordinator, price_type=PriceType.GAS),
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
ENERGY_USAGE_SERVICE_NAME,
partial(
__get_prices, coordinator=coordinator, price_type=PriceType.ENERGY_USAGE
),
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
ENERGY_RETURN_SERVICE_NAME,
partial(
__get_prices, coordinator=coordinator, price_type=PriceType.ENERGY_RETURN
),
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)

View File

@ -0,0 +1,46 @@
get_gas_prices:
fields:
incl_vat:
required: true
default: true
selector:
boolean:
start:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:
get_energy_usage_prices:
fields:
incl_vat:
required: true
default: true
selector:
boolean:
start:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:
get_energy_return_prices:
fields:
start:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2024-01-01 00:00:00"
selector:
datetime:

View File

@ -9,6 +9,11 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"exceptions": {
"invalid_date": {
"message": "Invalid date provided. Got {date}"
}
},
"entity": {
"sensor": {
"current_hour_price": {
@ -42,5 +47,57 @@
"name": "Hours priced equal or higher than current - today"
}
}
},
"services": {
"get_gas_prices": {
"name": "Get gas prices",
"description": "Request gas prices from easyEnergy.",
"fields": {
"incl_vat": {
"name": "VAT Included",
"description": "Include or exclude VAT in the prices, default is true."
},
"start": {
"name": "Start",
"description": "Specifies the date and time from which to retrieve prices. Defaults to today if omitted."
},
"end": {
"name": "End",
"description": "Specifies the date and time until which to retrieve prices. Defaults to today if omitted."
}
}
},
"get_energy_usage_prices": {
"name": "Get energy usage prices",
"description": "Request usage energy prices from easyEnergy.",
"fields": {
"incl_vat": {
"name": "[%key:component::easyenergy::services::get_gas_prices::fields::incl_vat::name%]",
"description": "[%key:component::easyenergy::services::get_gas_prices::fields::incl_vat::description%]"
},
"start": {
"name": "[%key:component::easyenergy::services::get_gas_prices::fields::start::name%]",
"description": "[%key:component::easyenergy::services::get_gas_prices::fields::start::description%]"
},
"end": {
"name": "[%key:component::easyenergy::services::get_gas_prices::fields::end::name%]",
"description": "[%key:component::easyenergy::services::get_gas_prices::fields::end::description%]"
}
}
},
"get_energy_return_prices": {
"name": "Get energy return prices",
"description": "Request return energy prices from easyEnergy.",
"fields": {
"start": {
"name": "[%key:component::easyenergy::services::get_gas_prices::fields::start::name%]",
"description": "[%key:component::easyenergy::services::get_gas_prices::fields::start::description%]"
},
"end": {
"name": "[%key:component::easyenergy::services::get_gas_prices::fields::end::name%]",
"description": "[%key:component::easyenergy::services::get_gas_prices::fields::end::description%]"
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
"""Tests for the services provided by the easyEnergy integration."""
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.easyenergy.const import DOMAIN
from homeassistant.components.easyenergy.services import (
ENERGY_RETURN_SERVICE_NAME,
ENERGY_USAGE_SERVICE_NAME,
GAS_SERVICE_NAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
@pytest.mark.usefixtures("init_integration")
async def test_has_services(
hass: HomeAssistant,
) -> None:
"""Test the existence of the easyEnergy Service."""
assert hass.services.has_service(DOMAIN, GAS_SERVICE_NAME)
assert hass.services.has_service(DOMAIN, ENERGY_USAGE_SERVICE_NAME)
assert hass.services.has_service(DOMAIN, ENERGY_RETURN_SERVICE_NAME)
@pytest.mark.usefixtures("init_integration")
@pytest.mark.parametrize(
"service", [GAS_SERVICE_NAME, ENERGY_USAGE_SERVICE_NAME, ENERGY_RETURN_SERVICE_NAME]
)
@pytest.mark.parametrize("incl_vat", [{"incl_vat": False}, {"incl_vat": True}, {}])
@pytest.mark.parametrize(
"start", [{"start": "2023-01-01 00:00:00"}, {"start": "incorrect date"}, {}]
)
@pytest.mark.parametrize(
"end", [{"end": "2023-01-01 00:00:00"}, {"end": "incorrect date"}, {}]
)
async def test_service(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
service: str,
incl_vat: dict[str, bool],
start: dict[str, str],
end: dict[str, str],
) -> None:
"""Test the easyEnergy Service."""
data = incl_vat | start | end
try:
response = await hass.services.async_call(
DOMAIN,
service,
data,
blocking=True,
return_response=True,
)
assert response == snapshot
except ServiceValidationError as e:
assert e == snapshot