Add Tado add meter readings service (#111552)

pull/111565/head
Niels Perfors 2024-02-26 23:47:01 +01:00 committed by GitHub
parent 4de56c1751
commit 951743551a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 256 additions and 1 deletions

View File

@ -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

View File

@ -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"

View File

@ -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
)

View File

@ -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

View File

@ -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": {

View File

@ -0,0 +1,4 @@
{
"code": "duplicated_meter_reading",
"message": "reading already exists for date [2024-01-01]"
}

View File

@ -0,0 +1 @@
{ "code": "invalid_meter_reading", "message": "invalid new reading" }

View File

@ -0,0 +1,6 @@
{
"id": "12345a6b-7c8d-9e01-2fa3-4b5c67890def",
"homeId": 123456,
"date": "2024-01-01",
"reading": 1234
}

View File

@ -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)