Skip to content
Snippets Groups Projects
Unverified Commit d5c7ae5b authored by Robert Groot's avatar Robert Groot Committed by GitHub
Browse files

Add Energyzero get_gas_prices and get_energy_price services (#101374)

parent 0534b0de
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import EnergyZeroDataUpdateCoordinator
from .services import async_register_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)
async_register_services(hass, coordinator)
return True
......
"""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,
)
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:
......@@ -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": {
......@@ -39,5 +44,43 @@
"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%]"
}
}
}
}
}
This diff is collapsed.
"""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,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment