Skip to content

Commit

Permalink
Implement setting preset mode (#5)
Browse files Browse the repository at this point in the history
Added ability to set preset modes

Fixes #4
  • Loading branch information
tonyroberts authored Nov 14, 2023
1 parent 2545a01 commit f763d04
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 3 deletions.
68 changes: 67 additions & 1 deletion custom_components/wundasmart/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
ClimateEntityFeature,
HVACAction,
HVACMode,
PRESET_ECO,
PRESET_COMFORT
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -37,6 +39,20 @@
HVACMode.HEAT,
]

PRESET_REDUCED = "reduced"

SUPPORTED_PRESET_MODES = [
PRESET_REDUCED,
PRESET_ECO,
PRESET_COMFORT
]

PRESET_MODE_STATE_KEYS = {
PRESET_REDUCED: "t_lo",
PRESET_ECO: "t_norm",
PRESET_COMFORT: "t_hi"
}

PARALLEL_UPDATES = 1


Expand Down Expand Up @@ -71,6 +87,8 @@ class Device(CoordinatorEntity[WundasmartDataUpdateCoordinator], ClimateEntity):

_attr_hvac_modes = SUPPORTED_HVAC_MODES
_attr_temperature_unit = TEMP_CELSIUS
_attr_preset_modes = SUPPORTED_PRESET_MODES
_attr_translation_key = DOMAIN

def __init__(
self,
Expand All @@ -93,11 +111,14 @@ def __init__(
self._attr_unique_id = device["id"]
self._attr_type = device["device_type"]
self._attr_device_info = coordinator.device_info
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
self._attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
)
self._attr_current_temperature = None
self._attr_target_temperature = None
self._attr_current_humidity = None
self._attr_hvac_mode = HVACMode.AUTO
self._attr_preset_mode = None

# Update with initial state
self.__update_state()
Expand Down Expand Up @@ -166,6 +187,26 @@ def __set_target_temperature(self):
except (ValueError, TypeError):
_LOGGER.warning(f"Unexpected set temp value '{state['temp']}' for {self._attr_name}")

def __set_preset_mode(self):
state = self.__state
try:
set_temp = float(state.get("temp", 0.0))
except (ValueError, TypeError):
_LOGGER.warning(f"Unexpected set temp value '{state['temp']}' for {self._attr_name}")
return

for preset_mode, state_key in PRESET_MODE_STATE_KEYS.items():
if state.get(state_key) is not None:
try:
t_preset = float(self.__state[state_key])
if t_preset == set_temp:
self._attr_preset_mode = preset_mode
break
except (ValueError, TypeError):
_LOGGER.warning(f"Unexpected {state_key} value '{state[state_key]}' for {self._attr_name}")
else:
self._attr_preset_mode = None

def __set_hvac_state(self):
"""Set the hvac action and hvac mode from the coordinator data."""
state = self.__state
Expand Down Expand Up @@ -195,6 +236,7 @@ def __update_state(self):
self.__set_current_temperature()
self.__set_current_humidity()
self.__set_target_temperature()
self.__set_preset_mode()
self.__set_hvac_state()

@callback
Expand Down Expand Up @@ -254,3 +296,27 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode):

# Fetch the updated state
await self.coordinator.async_request_refresh()

async def async_set_preset_mode(self, preset_mode) -> None:
state_key = PRESET_MODE_STATE_KEYS.get(preset_mode)
if state_key is None:
raise NotImplementedError(f"Unsupported Preset mode {preset_mode}")

t_preset = float(self.__state[state_key])

await send_command(
self._session,
self._wunda_ip,
self._wunda_user,
self._wunda_pass,
params={
"cmd": 1,
"roomid": self._wunda_id,
"temp": t_preset,
"locktt": 0,
"time": 0,
},
)

# Fetch the updated state
await self.coordinator.async_request_refresh()
2 changes: 2 additions & 0 deletions custom_components/wundasmart/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ async def async_setup_entry(
class Sensor(CoordinatorEntity[WundasmartDataUpdateCoordinator], SensorEntity):
"""Sensor entity for WundaSmart sensor values."""

_attr_translation_key = DOMAIN

def __init__(
self,
wunda_id: str,
Expand Down
13 changes: 13 additions & 0 deletions custom_components/wundasmart/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,18 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"entity": {
"climate": {
"wundasmart": {
"state_attributes": {
"preset_mode": {
"state": {
"reduced": "Reduced"
}
}
}
}
}
}
}
13 changes: 13 additions & 0 deletions custom_components/wundasmart/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,18 @@
"title": "Wundasmart configuration"
}
}
},
"entity": {
"climate": {
"wundasmart": {
"state_attributes": {
"preset_mode": {
"state": {
"reduced": "Reduced"
}
}
}
}
}
}
}
3 changes: 1 addition & 2 deletions custom_components/wundasmart/water_heater.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@ class Device(CoordinatorEntity[WundasmartDataUpdateCoordinator], WaterHeaterEnti
OPERATION_BOOST_ON,
OPERATION_BOOST_OFF
]

_attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE

_attr_temperature_unit = TEMP_CELSIUS
_attr_translation_key = DOMAIN

def __init__(
self,
Expand Down
95 changes: 95 additions & 0 deletions tests/fixtures/test_set_presets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"state": true,
"devices": {
"0": {
"device_type": "wunda",
"state": {
"hw_mode": "2",
"hw_mode_state": "1",
"hw_boost_state": "0",
"hw_sp": "45",
"hw_hist": "5",
"hw_ext_temp": "0"
},
"device_name": "Smart%20HubSwitch",
"id": "wunda.0"
},
"1": {
"device_type": "SENSOR",
"id": "SENSOR.1",
"state": {
"s": "1",
"t": "BT6",
"v": "1.8",
"temp": "17.8",
"rh": "66.57",
"temp_ext": "",
"ext": "0",
"bat": "100",
"sig": "88",
"alarm": "0"
}
},
"31": {
"device_type": "TRV",
"id": "TRV.31",
"state": {
"s": "1",
"t": "TH3K",
"v": "1.2",
"vtemp": "15.20",
"bat": "100",
"sig": "88",
"room_id": "0",
"vpos_min": "5",
"vpos_range": "40",
"downforce": "0",
"alarm": "0",
"trv_range": "716"
}
},
"121": {
"device_type": "ROOM",
"state": {
"t_lo": "14.00",
"t_norm": "19.00",
"t_hi": "21.00",
"heat": "4",
"temp_pre": "0",
"prg": "1",
"lock": "0",
"temp": "21.00",
"ntemp": "-1.00",
"ntime": "0",
"es_avgtime": "0",
"zone": "1",
"relays": "0",
"hb0": "0",
"hb1": "0",
"enable": "9",
"tmax": "27",
"tmaxh": "5",
"settime": "7200",
"pic": "3",
"loc_in": "1",
"loc_out": "3",
"alarm": "0",
"tbl": "555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"
},
"name": "Test%20Room",
"id": "ROOM.121",
"sensor_state": {
"s": "1",
"t": "BT6",
"v": "1.8",
"temp": "17.8",
"rh": "66.57",
"temp_ext": "",
"ext": "0",
"bat": "100",
"sig": "88",
"alarm": "0"
}
}
}
}
56 changes: 56 additions & 0 deletions tests/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,59 @@ async def test_hvac_mode_when_manually_turned_off(hass: HomeAssistant, config):
assert state
assert state.state == HVACAction.OFF
assert state.attributes["hvac_action"] == HVACAction.OFF


async def test_set_presets(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()

state = hass.states.get("climate.test_room")

assert state
assert state.attributes["temperature"] == 21.0
assert state.attributes["preset_mode"] == "comfort"

# set the preset 'reduced'
await hass.services.async_call("climate", "set_preset_mode", {
"entity_id": "climate.test_room",
"preset_mode": "reduced"
})
await hass.async_block_till_done()

# Check send_command was called correctly
assert mock.call_count == 1
assert mock.call_args.kwargs["params"]
assert mock.call_args.kwargs["params"]["roomid"] == 121
assert mock.call_args.kwargs["params"]["temp"] == 14.0

# set the preset 'eco'
await hass.services.async_call("climate", "set_preset_mode", {
"entity_id": "climate.test_room",
"preset_mode": "eco"
})
await hass.async_block_till_done()

# Check send_command was called correctly
assert mock.call_count == 2
assert mock.call_args.kwargs["params"]
assert mock.call_args.kwargs["params"]["roomid"] == 121
assert mock.call_args.kwargs["params"]["temp"] == 19.0

# set the preset 'comfort'
await hass.services.async_call("climate", "set_preset_mode", {
"entity_id": "climate.test_room",
"preset_mode": "comfort"
})
await hass.async_block_till_done()

# Check send_command was called correctly
assert mock.call_count == 3
assert mock.call_args.kwargs["params"]
assert mock.call_args.kwargs["params"]["roomid"] == 121
assert mock.call_args.kwargs["params"]["temp"] == 21.0

0 comments on commit f763d04

Please sign in to comment.