Add deconz current hvac operation to thermostate based on "state" (#59989)

* deconz - add current hvac operation to thermostate based on "state"

* deconz - extend current hvac operation to thermostate based on "state" and "mode"

* Add tests for current hvac action

* Add boost mode as special case

* format using Black

* sort imports

* Add test for device with mode none and state none

* Update homeassistant/components/deconz/climate.py

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

* Fix test_climate.py test_no_mode_no_state

* Add test for boost mode

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
This commit is contained in:
Michael Jäger 2022-09-21 08:20:44 +02:00 committed by GitHub
parent d7eb277bc8
commit 7a6897c757
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 216 additions and 1 deletions

View File

@ -24,6 +24,7 @@ from homeassistant.components.climate import (
PRESET_ECO, PRESET_ECO,
ClimateEntity, ClimateEntity,
ClimateEntityFeature, ClimateEntityFeature,
HVACAction,
HVACMode, HVACMode,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -172,6 +173,21 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
mode=HVAC_MODE_TO_DECONZ[hvac_mode], mode=HVAC_MODE_TO_DECONZ[hvac_mode],
) )
@property
def hvac_action(self) -> str | None:
"""Return current hvac operation ie. heat, cool.
Preset 'BOOST' is interpreted as 'state_on'.
"""
if self._device.mode == ThermostatMode.OFF:
return HVACAction.OFF
if self._device.state_on or self._device.preset == ThermostatPreset.BOOST:
if self._device.mode == ThermostatMode.COOL:
return HVACAction.COOLING
return HVACAction.HEATING
return HVACAction.IDLE
# Preset control # Preset control
@property @property

View File

@ -25,6 +25,7 @@ from homeassistant.components.climate.const import (
PRESET_BOOST, PRESET_BOOST,
PRESET_COMFORT, PRESET_COMFORT,
PRESET_ECO, PRESET_ECO,
HVACAction,
HVACMode, HVACMode,
) )
from homeassistant.components.deconz.climate import ( from homeassistant.components.deconz.climate import (
@ -108,6 +109,7 @@ async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket
assert climate_thermostat.attributes["temperature"] == 21.0 assert climate_thermostat.attributes["temperature"] == 21.0
assert climate_thermostat.attributes["locked"] is True assert climate_thermostat.attributes["locked"] is True
assert hass.states.get("sensor.thermostat_battery").state == "59" assert hass.states.get("sensor.thermostat_battery").state == "59"
assert climate_thermostat.attributes["hvac_action"] == HVACAction.HEATING
# Event signals thermostat configured off # Event signals thermostat configured off
@ -122,6 +124,10 @@ async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == STATE_OFF assert hass.states.get("climate.thermostat").state == STATE_OFF
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.IDLE
)
# Event signals thermostat state on # Event signals thermostat state on
@ -136,6 +142,10 @@ async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == HVACMode.HEAT assert hass.states.get("climate.thermostat").state == HVACMode.HEAT
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
# Verify service calls # Verify service calls
@ -210,6 +220,10 @@ async def test_climate_device_without_cooling_support(
assert hass.states.get("sensor.thermostat_battery").state == "100" assert hass.states.get("sensor.thermostat_battery").state == "100"
assert hass.states.get("climate.presence_sensor") is None assert hass.states.get("climate.presence_sensor") is None
assert hass.states.get("climate.clip_thermostat") is None assert hass.states.get("climate.clip_thermostat") is None
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
# Event signals thermostat configured off # Event signals thermostat configured off
@ -224,6 +238,10 @@ async def test_climate_device_without_cooling_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == STATE_OFF assert hass.states.get("climate.thermostat").state == STATE_OFF
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.OFF
)
# Event signals thermostat state on # Event signals thermostat state on
@ -239,6 +257,10 @@ async def test_climate_device_without_cooling_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == HVACMode.HEAT assert hass.states.get("climate.thermostat").state == HVACMode.HEAT
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
# Event signals thermostat state off # Event signals thermostat state off
@ -253,6 +275,10 @@ async def test_climate_device_without_cooling_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == STATE_OFF assert hass.states.get("climate.thermostat").state == STATE_OFF
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.IDLE
)
# Verify service calls # Verify service calls
@ -382,8 +408,11 @@ async def test_climate_device_with_cooling_support(
assert climate_thermostat.attributes["current_temperature"] == 23.2 assert climate_thermostat.attributes["current_temperature"] == 23.2
assert climate_thermostat.attributes["temperature"] == 22.2 assert climate_thermostat.attributes["temperature"] == 22.2
assert hass.states.get("sensor.zen_01_battery").state == "25" assert hass.states.get("sensor.zen_01_battery").state == "25"
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"] == HVACAction.IDLE
)
# Event signals thermostat state cool # Event signals thermostat mode cool
event_changed_sensor = { event_changed_sensor = {
"t": "event", "t": "event",
@ -398,6 +427,27 @@ async def test_climate_device_with_cooling_support(
assert hass.states.get("climate.zen_01").state == HVACMode.COOL assert hass.states.get("climate.zen_01").state == HVACMode.COOL
assert hass.states.get("climate.zen_01").attributes["temperature"] == 11.1 assert hass.states.get("climate.zen_01").attributes["temperature"] == 11.1
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"] == HVACAction.IDLE
)
# Event signals thermostat state on
event_changed_sensor = {
"t": "event",
"e": "changed",
"r": "sensors",
"id": "0",
"state": {"on": True},
}
await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done()
assert hass.states.get("climate.zen_01").state == HVACMode.COOL
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"]
== HVACAction.COOLING
)
# Verify service calls # Verify service calls
@ -463,6 +513,9 @@ async def test_climate_device_with_fan_support(
FAN_ON, FAN_ON,
FAN_OFF, FAN_OFF,
] ]
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"] == HVACAction.IDLE
)
# Event signals fan mode defaults to off # Event signals fan mode defaults to off
@ -477,6 +530,9 @@ async def test_climate_device_with_fan_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_OFF assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_OFF
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"] == HVACAction.IDLE
)
# Event signals unsupported fan mode # Event signals unsupported fan mode
@ -492,6 +548,10 @@ async def test_climate_device_with_fan_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"]
== HVACAction.HEATING
)
# Event signals unsupported fan mode # Event signals unsupported fan mode
@ -506,6 +566,10 @@ async def test_climate_device_with_fan_support(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"]
== HVACAction.HEATING
)
# Verify service calls # Verify service calls
@ -593,6 +657,9 @@ async def test_climate_device_with_preset(hass, aioclient_mock, mock_deconz_webs
DECONZ_PRESET_HOLIDAY, DECONZ_PRESET_HOLIDAY,
DECONZ_PRESET_MANUAL, DECONZ_PRESET_MANUAL,
] ]
assert (
hass.states.get("climate.zen_01").attributes["hvac_action"] == HVACAction.IDLE
)
# Event signals deCONZ preset # Event signals deCONZ preset
@ -693,6 +760,10 @@ async def test_clip_climate_device(hass, aioclient_mock):
assert len(hass.states.async_all()) == 3 assert len(hass.states.async_all()) == 3
assert hass.states.get("climate.clip_thermostat").state == HVACMode.HEAT assert hass.states.get("climate.clip_thermostat").state == HVACMode.HEAT
assert (
hass.states.get("climate.clip_thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
# Disallow clip sensors # Disallow clip sensors
@ -713,6 +784,10 @@ async def test_clip_climate_device(hass, aioclient_mock):
assert len(hass.states.async_all()) == 3 assert len(hass.states.async_all()) == 3
assert hass.states.get("climate.clip_thermostat").state == HVACMode.HEAT assert hass.states.get("climate.clip_thermostat").state == HVACMode.HEAT
assert (
hass.states.get("climate.clip_thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket): async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket):
@ -738,6 +813,10 @@ async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket):
await setup_deconz_integration(hass, aioclient_mock) await setup_deconz_integration(hass, aioclient_mock)
assert hass.states.get("climate.thermostat").state == HVACMode.AUTO assert hass.states.get("climate.thermostat").state == HVACMode.AUTO
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
event_changed_sensor = { event_changed_sensor = {
"t": "event", "t": "event",
@ -750,6 +829,10 @@ async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket):
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("climate.thermostat").state == HVACMode.AUTO assert hass.states.get("climate.thermostat").state == HVACMode.AUTO
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.IDLE
)
async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocket): async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocket):
@ -784,6 +867,10 @@ async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocke
assert len(hass.states.async_all()) == 2 assert len(hass.states.async_all()) == 2
assert hass.states.get("climate.thermostat").state == HVACMode.AUTO assert hass.states.get("climate.thermostat").state == HVACMode.AUTO
assert hass.states.get("sensor.thermostat_battery").state == "100" assert hass.states.get("sensor.thermostat_battery").state == "100"
assert (
hass.states.get("climate.thermostat").attributes["hvac_action"]
== HVACAction.HEATING
)
async def test_not_allow_clip_thermostat(hass, aioclient_mock): async def test_not_allow_clip_thermostat(hass, aioclient_mock):
@ -806,3 +893,115 @@ async def test_not_allow_clip_thermostat(hass, aioclient_mock):
) )
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
async def test_no_mode_no_state(hass, aioclient_mock, mock_deconz_websocket):
"""Test that a climate device without mode and state works."""
data = {
"sensors": {
"0": {
"config": {
"battery": 25,
"heatsetpoint": 2222,
"mode": None,
"preset": "auto",
"offset": 0,
"on": True,
"reachable": True,
},
"ep": 1,
"etag": "074549903686a77a12ef0f06c499b1ef",
"lastseen": "2020-11-27T13:45Z",
"manufacturername": "Zen Within",
"modelid": "Zen-01",
"name": "Zen-01",
"state": {"lastupdated": "none", "on": None, "temperature": 2290},
"type": "ZHAThermostat",
"uniqueid": "00:24:46:00:00:11:6f:56-01-0201",
}
}
}
with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock)
assert len(hass.states.async_all()) == 2
climate_thermostat = hass.states.get("climate.zen_01")
assert climate_thermostat.state is STATE_OFF
assert climate_thermostat.attributes["preset_mode"] is DECONZ_PRESET_AUTO
assert climate_thermostat.attributes["hvac_action"] is HVACAction.IDLE
# Verify service calls
mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config")
async def test_boost_mode(hass, aioclient_mock, mock_deconz_websocket):
"""Test that a climate device with boost mode and different state works."""
data = {
"sensors": {
"0": {
"config": {
"battery": 58,
"heatsetpoint": 2200,
"locked": False,
"mode": "heat",
"offset": -200,
"on": True,
"preset": "manual",
"reachable": True,
"schedule": {},
"schedule_on": False,
"setvalve": False,
"windowopen_set": False,
},
"ep": 1,
"etag": "404c15db68c318ebe7832ce5aa3d1e30",
"lastannounced": "2022-08-31T03:00:59Z",
"lastseen": "2022-09-19T11:58Z",
"manufacturername": "_TZE200_b6wax7g0",
"modelid": "TS0601",
"name": "Thermostat",
"state": {
"lastupdated": "2022-09-19T11:58:24.204",
"lowbattery": False,
"on": False,
"temperature": 2200,
"valve": 0,
},
"type": "ZHAThermostat",
"uniqueid": "84:fd:27:ff:fe:8a:eb:89-01-0201",
}
}
}
with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock)
assert len(hass.states.async_all()) == 3
climate_thermostat = hass.states.get("climate.thermostat")
assert climate_thermostat.state == HVACMode.HEAT
assert climate_thermostat.attributes["preset_mode"] is DECONZ_PRESET_MANUAL
assert climate_thermostat.attributes["hvac_action"] is HVACAction.IDLE
# Event signals thermostat preset boost and valve 100 (real data)
event_changed_sensor = {
"t": "event",
"e": "changed",
"r": "sensors",
"id": "0",
"config": {"preset": "boost"},
"state": {"valve": 100},
}
await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done()
climate_thermostat = hass.states.get("climate.thermostat")
assert climate_thermostat.attributes["preset_mode"] is PRESET_BOOST
assert climate_thermostat.attributes["hvac_action"] is HVACAction.HEATING
# Verify service calls
mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config")