Add Energyzero get_gas_prices and get_energy_price services (#101374)

Co-authored-by: Klaas Schoute <klaas_schoute@hotmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Robert Groot 2023-12-21 15:39:51 +01:00 committed by GitHub
parent 0534b0dee4
commit d5c7ae5b34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 2701 additions and 0 deletions

View File

@ -8,6 +8,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN from .const import DOMAIN
from .coordinator import EnergyZeroDataUpdateCoordinator from .coordinator import EnergyZeroDataUpdateCoordinator
from .services import async_register_services
PLATFORMS = [Platform.SENSOR] 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 hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async_register_services(hass, coordinator)
return True return True

View File

@ -0,0 +1,129 @@
"""The EnergyZero services."""
from __future__ import annotations
from datetime import date, datetime
from enum import Enum
from functools import partial
from typing import Final
from energyzero import Electricity, Gas, VatOption
import voluptuous as vol
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .coordinator import EnergyZeroDataUpdateCoordinator
ATTR_START: Final = "start"
ATTR_END: Final = "end"
ATTR_INCL_VAT: Final = "incl_vat"
GAS_SERVICE_NAME: Final = "get_gas_prices"
ENERGY_SERVICE_NAME: Final = "get_energy_prices"
SERVICE_SCHEMA: Final = vol.Schema(
{
vol.Required(ATTR_INCL_VAT): bool,
vol.Optional(ATTR_START): str,
vol.Optional(ATTR_END): str,
}
)
class PriceType(Enum):
"""Type of price."""
ENERGY = "energy"
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(
"Invalid datetime provided.",
translation_domain=DOMAIN,
translation_key="invalid_date",
translation_placeholders={
"date": date_input,
},
)
def __serialize_prices(prices: Electricity | Gas) -> ServiceResponse:
"""Serialize prices."""
return {
"prices": [
{
key: str(value) if isinstance(value, datetime) else value
for key, value in timestamp_price.items()
}
for timestamp_price in prices.timestamp_prices
]
}
async def __get_prices(
call: ServiceCall,
*,
coordinator: EnergyZeroDataUpdateCoordinator,
price_type: PriceType,
) -> ServiceResponse:
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.energyzero.gas_prices(
start_date=start,
end_date=end,
vat=vat,
)
else:
data = await coordinator.energyzero.energy_prices(
start_date=start,
end_date=end,
vat=vat,
)
return __serialize_prices(data)
@callback
def async_register_services(
hass: HomeAssistant, coordinator: EnergyZeroDataUpdateCoordinator
):
"""Set up EnergyZero services."""
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_SERVICE_NAME,
partial(__get_prices, coordinator=coordinator, price_type=PriceType.ENERGY),
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)

View File

@ -0,0 +1,34 @@
get_gas_prices:
fields:
incl_vat:
required: true
default: true
selector:
boolean:
start:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:
get_energy_prices:
fields:
incl_vat:
required: true
default: true
selector:
boolean:
start:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:
end:
required: false
example: "2023-01-01 00:00:00"
selector:
datetime:

View File

@ -9,6 +9,11 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
}, },
"exceptions": {
"invalid_date": {
"message": "Invalid date provided. Got {date}"
}
},
"entity": { "entity": {
"sensor": { "sensor": {
"current_hour_price": { "current_hour_price": {
@ -39,5 +44,43 @@
"name": "Hours priced equal or lower than current - today" "name": "Hours priced equal or lower than current - today"
} }
} }
},
"services": {
"get_gas_prices": {
"name": "Get gas prices",
"description": "Request gas prices from EnergyZero.",
"fields": {
"incl_vat": {
"name": "Including VAT",
"description": "Include VAT in the prices."
},
"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_prices": {
"name": "Get energy prices",
"description": "Request energy prices from EnergyZero.",
"fields": {
"incl_vat": {
"name": "[%key:component::energyzero::services::get_gas_prices::fields::incl_vat::name%]",
"description": "[%key:component::energyzero::services::get_gas_prices::fields::incl_vat::description%]"
},
"start": {
"name": "[%key:component::energyzero::services::get_gas_prices::fields::start::name%]",
"description": "[%key:component::energyzero::services::get_gas_prices::fields::start::description%]"
},
"end": {
"name": "[%key:component::energyzero::services::get_gas_prices::fields::end::name%]",
"description": "[%key:component::energyzero::services::get_gas_prices::fields::end::description%]"
}
}
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
"""Tests for the services provided by the EnergyZero integration."""
import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol
from homeassistant.components.energyzero.const import DOMAIN
from homeassistant.components.energyzero.services import (
ENERGY_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 EnergyZero Service."""
assert hass.services.has_service(DOMAIN, GAS_SERVICE_NAME)
assert hass.services.has_service(DOMAIN, ENERGY_SERVICE_NAME)
@pytest.mark.usefixtures("init_integration")
@pytest.mark.parametrize("service", [GAS_SERVICE_NAME, ENERGY_SERVICE_NAME])
@pytest.mark.parametrize("incl_vat", [{"incl_vat": False}, {"incl_vat": True}])
@pytest.mark.parametrize("start", [{"start": "2023-01-01 00:00:00"}, {}])
@pytest.mark.parametrize("end", [{"end": "2023-01-01 00:00:00"}, {}])
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 EnergyZero Service."""
data = incl_vat | start | end
assert snapshot == await hass.services.async_call(
DOMAIN,
service,
data,
blocking=True,
return_response=True,
)
@pytest.mark.usefixtures("init_integration")
@pytest.mark.parametrize("service", [GAS_SERVICE_NAME, ENERGY_SERVICE_NAME])
@pytest.mark.parametrize(
("service_data", "error", "error_message"),
[
({}, vol.er.Error, "required key not provided .+"),
(
{"incl_vat": "incorrect vat"},
vol.er.Error,
"expected bool for dictionary value .+",
),
(
{"incl_vat": True, "start": "incorrect date"},
ServiceValidationError,
"Invalid datetime provided.",
),
(
{"incl_vat": True, "end": "incorrect date"},
ServiceValidationError,
"Invalid datetime provided.",
),
],
)
async def test_service_validation(
hass: HomeAssistant,
service: str,
service_data: dict[str, str],
error: type[Exception],
error_message: str,
) -> None:
"""Test the EnergyZero Service validation."""
with pytest.raises(error, match=error_message):
await hass.services.async_call(
DOMAIN,
service,
service_data,
blocking=True,
return_response=True,
)