Add Tado add meter readings service (#111552)
parent
4de56c1751
commit
951743551a
|
@ -10,10 +10,11 @@ from homeassistant.components.climate import PRESET_AWAY, PRESET_HOME
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import (
|
||||
|
@ -33,6 +34,7 @@ from .const import (
|
|||
UPDATE_MOBILE_DEVICE_TRACK,
|
||||
UPDATE_TRACK,
|
||||
)
|
||||
from .services import setup_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -52,6 +54,14 @@ SCAN_MOBILE_DEVICE_INTERVAL = timedelta(seconds=30)
|
|||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Tado."""
|
||||
|
||||
setup_services(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Tado from a config entry."""
|
||||
|
||||
|
@ -425,3 +435,10 @@ class TadoConnector:
|
|||
self.tado.set_temp_offset(device_id, offset)
|
||||
except RequestException as exc:
|
||||
_LOGGER.error("Could not set temperature offset: %s", exc)
|
||||
|
||||
def set_meter_reading(self, reading: int) -> dict[str, str]:
|
||||
"""Send meter reading to Tado."""
|
||||
try:
|
||||
return self.tado.set_eiq_meter_readings(reading=reading)
|
||||
except RequestException as exc:
|
||||
raise HomeAssistantError("Could not set meter reading") from exc
|
||||
|
|
|
@ -204,3 +204,9 @@ TADO_TO_HA_OFFSET_MAP = {
|
|||
# Constants for Overlay Default settings
|
||||
HA_TERMINATION_TYPE = "default_overlay_type"
|
||||
HA_TERMINATION_DURATION = "default_overlay_seconds"
|
||||
|
||||
# Constants for service calls
|
||||
SERVICE_ADD_METER_READING = "add_meter_reading"
|
||||
CONF_CONFIG_ENTRY = "config_entry"
|
||||
CONF_READING = "reading"
|
||||
ATTR_MESSAGE = "message"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
"""Services for the Tado integration."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import selector
|
||||
|
||||
from .const import (
|
||||
ATTR_MESSAGE,
|
||||
CONF_CONFIG_ENTRY,
|
||||
CONF_READING,
|
||||
DATA,
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SCHEMA_ADD_METER_READING = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CONFIG_ENTRY): selector.ConfigEntrySelector(
|
||||
{
|
||||
"integration": DOMAIN,
|
||||
}
|
||||
),
|
||||
vol.Required(CONF_READING): vol.Coerce(int),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up the services for the Tado integration."""
|
||||
|
||||
async def add_meter_reading(call: ServiceCall) -> None:
|
||||
"""Send meter reading to Tado."""
|
||||
entry_id: str = call.data[CONF_CONFIG_ENTRY]
|
||||
reading: int = call.data[CONF_READING]
|
||||
_LOGGER.debug("Add meter reading %s", reading)
|
||||
|
||||
tadoconnector = hass.data[DOMAIN][entry_id][DATA]
|
||||
response: dict = await hass.async_add_executor_job(
|
||||
tadoconnector.set_meter_reading, call.data[CONF_READING]
|
||||
)
|
||||
|
||||
if ATTR_MESSAGE in response:
|
||||
raise HomeAssistantError(response[ATTR_MESSAGE])
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_ADD_METER_READING, add_meter_reading, SCHEMA_ADD_METER_READING
|
||||
)
|
|
@ -61,3 +61,18 @@ set_climate_temperature_offset:
|
|||
max: 10
|
||||
step: 0.01
|
||||
unit_of_measurement: "°"
|
||||
|
||||
add_meter_reading:
|
||||
fields:
|
||||
config_entry:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: tado
|
||||
reading:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
mode: box
|
||||
min: 0
|
||||
step: 1
|
||||
|
|
|
@ -122,6 +122,20 @@
|
|||
"description": "Offset you would like (depending on your device)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"add_meter_reading": {
|
||||
"name": "Add meter readings",
|
||||
"description": "Add meter readings to Tado Energy IQ.",
|
||||
"fields": {
|
||||
"config_entry": {
|
||||
"name": "Config Entry",
|
||||
"description": "Config entry to add meter readings to."
|
||||
},
|
||||
"reading": {
|
||||
"name": "Reading",
|
||||
"description": "Reading in m³ or kWh without decimals."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"code": "duplicated_meter_reading",
|
||||
"message": "reading already exists for date [2024-01-01]"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{ "code": "invalid_meter_reading", "message": "invalid new reading" }
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "12345a6b-7c8d-9e01-2fa3-4b5c67890def",
|
||||
"homeId": 123456,
|
||||
"date": "2024-01-01",
|
||||
"reading": 1234
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
"""The serive tests for the tado platform."""
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from homeassistant.components.tado.const import (
|
||||
CONF_CONFIG_ENTRY,
|
||||
CONF_READING,
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
||||
async def test_has_services(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the existence of the Tado Service."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
assert hass.services.has_service(DOMAIN, SERVICE_ADD_METER_READING)
|
||||
|
||||
|
||||
async def test_add_meter_readings(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the add_meter_readings service."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
config_entry: MockConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
fixture: str = load_fixture("tado/add_readings_success.json")
|
||||
with patch(
|
||||
"PyTado.interface.Tado.set_eiq_meter_readings",
|
||||
return_value=json.loads(fixture),
|
||||
):
|
||||
response: None = await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
service_data={
|
||||
CONF_CONFIG_ENTRY: config_entry.entry_id,
|
||||
CONF_READING: 1234,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert response is None
|
||||
|
||||
|
||||
async def test_add_meter_readings_exception(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the add_meter_readings service with a RequestException."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
config_entry: MockConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
with (
|
||||
patch(
|
||||
"PyTado.interface.Tado.set_eiq_meter_readings",
|
||||
side_effect=RequestException("Error"),
|
||||
),
|
||||
pytest.raises(HomeAssistantError) as exc,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
service_data={
|
||||
CONF_CONFIG_ENTRY: config_entry.entry_id,
|
||||
CONF_READING: 1234,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert "Could not set meter reading" in str(exc)
|
||||
|
||||
|
||||
async def test_add_meter_readings_invalid(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the add_meter_readings service with an invalid_meter_reading response."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
config_entry: MockConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
fixture: str = load_fixture("tado/add_readings_invalid_meter_reading.json")
|
||||
with (
|
||||
patch(
|
||||
"PyTado.interface.Tado.set_eiq_meter_readings",
|
||||
return_value=json.loads(fixture),
|
||||
),
|
||||
pytest.raises(HomeAssistantError) as exc,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
service_data={
|
||||
CONF_CONFIG_ENTRY: config_entry.entry_id,
|
||||
CONF_READING: 1234,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert "invalid new reading" in str(exc)
|
||||
|
||||
|
||||
async def test_add_meter_readings_duplicate(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the add_meter_readings service with a duplicated_meter_reading response."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
config_entry: MockConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
fixture: str = load_fixture("tado/add_readings_duplicated_meter_reading.json")
|
||||
with (
|
||||
patch(
|
||||
"PyTado.interface.Tado.set_eiq_meter_readings",
|
||||
return_value=json.loads(fixture),
|
||||
),
|
||||
pytest.raises(HomeAssistantError) as exc,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_METER_READING,
|
||||
service_data={
|
||||
CONF_CONFIG_ENTRY: config_entry.entry_id,
|
||||
CONF_READING: 1234,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert "reading already exists for date" in str(exc)
|
Loading…
Reference in New Issue