Skip to content

Commit

Permalink
Add service for setting preset temps
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyroberts committed Oct 1, 2024
1 parent 6a2bf9d commit f1b80c3
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 4 deletions.
40 changes: 37 additions & 3 deletions custom_components/wundasmart/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
import aiohttp
from typing import Any
import voluptuous as vol

from homeassistant.components.climate import (
ClimateEntity,
Expand All @@ -21,17 +22,20 @@
CONF_PASSWORD,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback, async_get_current_platform
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers import config_validation as cv

from . import WundasmartDataUpdateCoordinator
from .pywundasmart import send_command, get_room_id_from_device
from .pywundasmart import send_command, set_register, get_room_id_from_device
from .session import get_session
from .const import *

_LOGGER = logging.getLogger(__name__)

SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"

SUPPORTED_HVAC_MODES = [
HVACMode.OFF,
HVACMode.AUTO,
Expand Down Expand Up @@ -84,6 +88,17 @@ async def async_setup_entry(
)
for wunda_id, device in rooms))

platform = async_get_current_platform()

platform.async_register_entity_service(
SERVICE_SET_PRESET_TEMPERATURE,
{
vol.Required('preset'): vol.In(SUPPORTED_PRESET_MODES, msg="invalid preset"),
vol.Required('temperature'): cv.Number
},
Device.async_set_preset_temperature,
)


class Device(CoordinatorEntity[WundasmartDataUpdateCoordinator], ClimateEntity):
"""Representation of an Wundasmart climate."""
Expand Down Expand Up @@ -393,3 +408,22 @@ async def async_turn_on(self) -> None:
async def async_turn_off(self) -> None:
"""Turn the entity off."""
await self.async_set_hvac_mode(HVACMode.OFF)

async def async_set_preset_temperature(self, service_data: ServiceCall) -> None:
"""Change one of the preset temperatures."""
preset = service_data.data["preset"]
temperature = service_data.data["temperature"]

async with get_session(self._wunda_ip) as session:
await set_register(
session,
self._wunda_ip,
self._wunda_user,
self._wunda_pass,
timeout=self._timeout,
device_id=self._wunda_id,
register_id=PRESET_MODE_STATE_KEYS[preset],
value=temperature)

# Fetch the updated state
await self.coordinator.async_request_refresh()
5 changes: 4 additions & 1 deletion custom_components/wundasmart/const.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Constants for the wundasmart integration."""
from dataclasses import dataclass
from homeassistant.components.climate import (
PRESET_ECO,
PRESET_COMFORT
)

DOMAIN = "wundasmart"

Expand All @@ -10,7 +14,6 @@
DEFAULT_CONNECT_TIMEOUT = 5
DEFAULT_READ_TIMEOUT = 5


@dataclass
class DeviceIdRanges:
MIN_SENSOR_ID: int
Expand Down
40 changes: 40 additions & 0 deletions custom_components/wundasmart/pywundasmart.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import xml.etree.ElementTree as ET
from .const import *
import urllib.parse
import asyncio
Expand Down Expand Up @@ -177,3 +178,42 @@ async def send_command(session: aiohttp.ClientSession,

_LOGGER.warning(f"Failed to send command to Wundasmart : {status=}")
raise RuntimeError(f"Failed to send command: {params=}; {status=}")


async def set_register(session: aiohttp.ClientSession,
wunda_ip: str,
wunda_user: str,
wunda_pass: str,
device_id: str,
register_id: str,
value: str,
timeout: int = 3,
retries: int = 5,
retry_delay: float = 0.5):
"""Send a setregister command to the wunda smart hub controller"""
wunda_url = f"http://{wunda_ip}/setregister.cgi?{device_id}@{register_id}={value}"

attempts = 0
while attempts < retries:
attempts += 1
async with session.get(wunda_url,
auth=aiohttp.BasicAuth(wunda_user, wunda_pass),
timeout=timeout) as resp:
text = None
status = resp.status
if status == 200:
text = await resp.text()
root = ET.fromstring(text)
status = root.attrib.get('status')
if status == "ok":
return status

if attempts < retries:
_LOGGER.warning("Call to setregister.cgi failed with status '%(status)s'%(text)s", {
"status": status,
"text": f"\n{text}" if text is not None else ""
})
await asyncio.sleep(retry_delay)

_LOGGER.warning(f"Failed to set register : {status=}")
raise RuntimeError(f"Failed to set register: {device_id=}; {register_id=}; {value=}")
29 changes: 29 additions & 0 deletions custom_components/wundasmart/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,32 @@ hw_off:
default: '00:30:00'
selector:
time:
set_preset_temperature:
name: Set Preset Temperature
description: Sets one of the temperature presets.
target:
entity:
integration: wundasmart
domain: climate
fields:
preset:
name: Preset
description: Named preset to set temperature of.
required: true
selector:
select:
translation_key: "preset"
options:
- label: "Reduced"
value: "reduced"
- label: "Eco"
value: "eco"
- label: "Comfort"
value: "comfort"
temperature:
name: "Temperature"
description: "Temperature to set for the preset."
required: true
selector:
number:
min: 0
24 changes: 24 additions & 0 deletions tests/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,30 @@ async def test_set_presets(hass: HomeAssistant, config):
assert mock.call_args.kwargs["params"]["temp"] == 21.0


async def test_set_preset_temps(hass: HomeAssistant, config):
entry = MockConfigEntry(domain=DOMAIN, data=config)
entry.add_to_hass(hass)

data = deserialize_get_devices_fixture(load_fixture("test_set_presets.json"))
with patch("custom_components.wundasmart.get_devices", return_value=data), \
patch("custom_components.wundasmart.climate.send_command", return_value=None) as mock:
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

with patch("custom_components.wundasmart.climate.set_register", return_value=None) as mock:
await hass.services.async_call("wundasmart", "set_preset_temperature", {
"entity_id": "climate.test_room",
"preset": "eco",
"temperature": 10
})
await hass.async_block_till_done()

# Check send_command was called correctly
assert mock.call_count == 1
assert mock.call_args.kwargs["device_id"] == 121
assert mock.call_args.kwargs["register_id"] == "t_norm"
assert mock.call_args.kwargs["value"] == 10

async def test_turn_on_off(hass: HomeAssistant, config):
entry = MockConfigEntry(domain=DOMAIN, data=config)
entry.add_to_hass(hass)
Expand Down

0 comments on commit f1b80c3

Please sign in to comment.