Add HVACMode.OFF to Plugwise Adam (#103360)

pull/104033/head^2
Bouwe Westerdijk 2023-11-24 20:07:17 +01:00 committed by GitHub
parent e161bb9e41
commit 4700ad7817
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 54 deletions

View File

@ -46,6 +46,8 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = DOMAIN
_previous_mode: str = "heating"
def __init__(
self,
coordinator: PlugwiseDataUpdateCoordinator,
@ -55,10 +57,15 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
super().__init__(coordinator, device_id)
self._attr_extra_state_attributes = {}
self._attr_unique_id = f"{device_id}-climate"
self.cdr_gateway = coordinator.data.gateway
gateway_id: str = coordinator.data.gateway["gateway_id"]
self.gateway_data = coordinator.data.devices[gateway_id]
# Determine supported features
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
if self.coordinator.data.gateway["cooling_present"]:
if (
self.cdr_gateway["cooling_present"]
and self.cdr_gateway["smile_name"] != "Adam"
):
self._attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)
@ -73,6 +80,20 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
self.device["thermostat"]["resolution"], 0.1
)
def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> None:
"""Return the previous action-mode when the regulation-mode is not heating or cooling.
Helper for set_hvac_mode().
"""
# When no cooling available, _previous_mode is always heating
if (
"regulation_modes" in self.gateway_data
and "cooling" in self.gateway_data["regulation_modes"]
):
mode = self.gateway_data["select_regulation_mode"]
if mode in ("cooling", "heating"):
self._previous_mode = mode
@property
def current_temperature(self) -> float:
"""Return the current temperature."""
@ -105,33 +126,46 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
@property
def hvac_mode(self) -> HVACMode:
"""Return HVAC operation ie. auto, heat, or heat_cool mode."""
"""Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
if (mode := self.device.get("mode")) is None or mode not in self.hvac_modes:
return HVACMode.HEAT
return HVACMode(mode)
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of available HVACModes."""
hvac_modes = [HVACMode.HEAT]
if self.coordinator.data.gateway["cooling_present"]:
hvac_modes = [HVACMode.HEAT_COOL]
"""Return a list of available HVACModes."""
hvac_modes: list[HVACMode] = []
if "regulation_modes" in self.gateway_data:
hvac_modes.append(HVACMode.OFF)
if self.device["available_schedules"] != ["None"]:
hvac_modes.append(HVACMode.AUTO)
if self.cdr_gateway["cooling_present"]:
if "regulation_modes" in self.gateway_data:
if self.gateway_data["select_regulation_mode"] == "cooling":
hvac_modes.append(HVACMode.COOL)
if self.gateway_data["select_regulation_mode"] == "heating":
hvac_modes.append(HVACMode.HEAT)
else:
hvac_modes.append(HVACMode.HEAT_COOL)
else:
hvac_modes.append(HVACMode.HEAT)
return hvac_modes
@property
def hvac_action(self) -> HVACAction | None:
def hvac_action(self) -> HVACAction:
"""Return the current running hvac operation if supported."""
heater: str | None = self.coordinator.data.gateway["heater_id"]
if heater:
heater_data = self.coordinator.data.devices[heater]
if heater_data["binary_sensors"]["heating_state"]:
return HVACAction.HEATING
if heater_data["binary_sensors"].get("cooling_state"):
return HVACAction.COOLING
# Keep track of the previous action-mode
self._previous_action_mode(self.coordinator)
heater: str = self.coordinator.data.gateway["heater_id"]
heater_data = self.coordinator.data.devices[heater]
if heater_data["binary_sensors"]["heating_state"]:
return HVACAction.HEATING
if heater_data["binary_sensors"].get("cooling_state", False):
return HVACAction.COOLING
return HVACAction.IDLE
@ -168,9 +202,18 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
if hvac_mode not in self.hvac_modes:
raise HomeAssistantError("Unsupported hvac_mode")
await self.coordinator.api.set_schedule_state(
self.device["location"], "on" if hvac_mode == HVACMode.AUTO else "off"
)
if hvac_mode == self.hvac_mode:
return
if hvac_mode == HVACMode.OFF:
await self.coordinator.api.set_regulation_mode(hvac_mode)
else:
await self.coordinator.api.set_schedule_state(
self.device["location"],
"on" if hvac_mode == HVACMode.AUTO else "off",
)
if self.hvac_mode == HVACMode.OFF:
await self.coordinator.api.set_regulation_mode(self._previous_mode)
@plugwise_command
async def async_set_preset_mode(self, preset_mode: str) -> None:

View File

@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["crcmod", "plugwise"],
"requirements": ["plugwise==0.33.2"],
"requirements": ["plugwise==0.34.0"],
"zeroconf": ["_plugwise._tcp.local."]
}

View File

@ -1477,7 +1477,7 @@ plexauth==0.0.6
plexwebsocket==0.0.14
# homeassistant.components.plugwise
plugwise==0.33.2
plugwise==0.34.0
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11

View File

@ -1135,7 +1135,7 @@ plexauth==0.0.6
plexwebsocket==0.0.14
# homeassistant.components.plugwise
plugwise==0.33.2
plugwise==0.34.0
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11

View File

@ -8,7 +8,7 @@
"firmware": "2016-10-27T02:00:00+02:00",
"hardware": "255",
"location": "06aecb3d00354375924f50c47af36bd2",
"mode": "heat",
"mode": "off",
"model": "Lisa",
"name": "Slaapkamer",
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],

View File

@ -55,22 +55,20 @@
"available_schedules": ["Weekschema", "Badkamer", "Test"],
"dev_class": "thermostat",
"location": "f2bf9048bef64cc5b6d5110154e33c81",
"mode": "heat_cool",
"mode": "cool",
"model": "ThermoTouch",
"name": "Anna",
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
"select_schedule": "Weekschema",
"selected_schedule": "None",
"sensors": {
"setpoint_high": 23.5,
"setpoint_low": 4.0,
"setpoint": 23.5,
"temperature": 25.8
},
"thermostat": {
"lower_bound": 1.0,
"resolution": 0.01,
"setpoint_high": 23.5,
"setpoint_low": 4.0,
"setpoint": 23.5,
"upper_bound": 35.0
},
"vendor": "Plugwise"
@ -115,9 +113,8 @@
"select_schedule": "Badkamer",
"sensors": {
"battery": 56,
"setpoint_high": 23.5,
"setpoint_low": 20.0,
"temperature": 239
"setpoint": 23.5,
"temperature": 23.9
},
"temperature_offset": {
"lower_bound": -2.0,
@ -128,8 +125,7 @@
"thermostat": {
"lower_bound": 0.0,
"resolution": 0.01,
"setpoint_high": 25.0,
"setpoint_low": 19.0,
"setpoint": 25.0,
"upper_bound": 99.9
},
"vendor": "Plugwise",

View File

@ -13,6 +13,10 @@ from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
HA_PLUGWISE_SMILE_ASYNC_UPDATE = (
"homeassistant.components.plugwise.coordinator.Smile.async_update"
)
async def test_adam_climate_entity_attributes(
hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry
@ -21,7 +25,7 @@ async def test_adam_climate_entity_attributes(
state = hass.states.get("climate.zone_lisa_wk")
assert state
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_modes"] == [HVACMode.HEAT, HVACMode.AUTO]
assert state.attributes["hvac_modes"] == [HVACMode.AUTO, HVACMode.HEAT]
# hvac_action is not asserted as the fixture is not in line with recent firmware functionality
assert "preset_modes" in state.attributes
@ -39,7 +43,7 @@ async def test_adam_climate_entity_attributes(
state = hass.states.get("climate.zone_thermostat_jessie")
assert state
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_modes"] == [HVACMode.HEAT, HVACMode.AUTO]
assert state.attributes["hvac_modes"] == [HVACMode.AUTO, HVACMode.HEAT]
# hvac_action is not asserted as the fixture is not in line with recent firmware functionality
assert "preset_modes" in state.attributes
@ -62,13 +66,21 @@ async def test_adam_2_climate_entity_attributes(
assert state
assert state.state == HVACMode.HEAT
assert state.attributes["hvac_action"] == "heating"
assert state.attributes["hvac_modes"] == [HVACMode.HEAT, HVACMode.AUTO]
assert state.attributes["hvac_modes"] == [
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.HEAT,
]
state = hass.states.get("climate.lisa_badkamer")
assert state
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_action"] == "heating"
assert state.attributes["hvac_modes"] == [HVACMode.HEAT, HVACMode.AUTO]
assert state.attributes["hvac_modes"] == [
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.HEAT,
]
async def test_adam_3_climate_entity_attributes(
@ -78,11 +90,58 @@ async def test_adam_3_climate_entity_attributes(
state = hass.states.get("climate.anna")
assert state
assert state.state == HVACMode.HEAT_COOL
assert state.state == HVACMode.COOL
assert state.attributes["hvac_action"] == "cooling"
assert state.attributes["hvac_modes"] == [
HVACMode.HEAT_COOL,
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.COOL,
]
data = mock_smile_adam_3.async_update.return_value
data.devices["da224107914542988a88561b4452b0f6"][
"select_regulation_mode"
] = "heating"
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["mode"] = "heat"
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
"cooling_state"
] = False
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
"heating_state"
] = True
with patch(HA_PLUGWISE_SMILE_ASYNC_UPDATE, return_value=data):
async_fire_time_changed(hass, utcnow() + timedelta(minutes=1))
await hass.async_block_till_done()
state = hass.states.get("climate.anna")
assert state
assert state.state == HVACMode.HEAT
assert state.attributes["hvac_action"] == "heating"
assert state.attributes["hvac_modes"] == [
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.HEAT,
]
data = mock_smile_adam_3.async_update.return_value
data.devices["da224107914542988a88561b4452b0f6"][
"select_regulation_mode"
] = "cooling"
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["mode"] = "cool"
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
"cooling_state"
] = True
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
"heating_state"
] = False
with patch(HA_PLUGWISE_SMILE_ASYNC_UPDATE, return_value=data):
async_fire_time_changed(hass, utcnow() + timedelta(minutes=1))
await hass.async_block_till_done()
state = hass.states.get("climate.anna")
assert state
assert state.state == HVACMode.COOL
assert state.attributes["hvac_action"] == "cooling"
assert state.attributes["hvac_modes"] == [
HVACMode.OFF,
HVACMode.AUTO,
HVACMode.COOL,
]
@ -173,6 +232,60 @@ async def test_adam_climate_entity_climate_changes(
)
async def test_adam_climate_off_mode_change(
hass: HomeAssistant,
mock_smile_adam_4: MagicMock,
init_integration: MockConfigEntry,
) -> None:
"""Test handling of user requests in adam climate device environment."""
state = hass.states.get("climate.slaapkamer")
assert state
assert state.state == HVACMode.OFF
await hass.services.async_call(
"climate",
"set_hvac_mode",
{
"entity_id": "climate.slaapkamer",
"hvac_mode": "heat",
},
blocking=True,
)
assert mock_smile_adam_4.set_schedule_state.call_count == 1
assert mock_smile_adam_4.set_regulation_mode.call_count == 1
mock_smile_adam_4.set_regulation_mode.assert_called_with("heating")
state = hass.states.get("climate.kinderkamer")
assert state
assert state.state == HVACMode.HEAT
await hass.services.async_call(
"climate",
"set_hvac_mode",
{
"entity_id": "climate.kinderkamer",
"hvac_mode": "off",
},
blocking=True,
)
assert mock_smile_adam_4.set_schedule_state.call_count == 1
assert mock_smile_adam_4.set_regulation_mode.call_count == 2
mock_smile_adam_4.set_regulation_mode.assert_called_with("off")
state = hass.states.get("climate.logeerkamer")
assert state
assert state.state == HVACMode.HEAT
await hass.services.async_call(
"climate",
"set_hvac_mode",
{
"entity_id": "climate.logeerkamer",
"hvac_mode": "heat",
},
blocking=True,
)
assert mock_smile_adam_4.set_schedule_state.call_count == 1
assert mock_smile_adam_4.set_regulation_mode.call_count == 2
async def test_anna_climate_entity_attributes(
hass: HomeAssistant,
mock_smile_anna: MagicMock,
@ -183,10 +296,7 @@ async def test_anna_climate_entity_attributes(
assert state
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_action"] == "heating"
assert state.attributes["hvac_modes"] == [
HVACMode.HEAT,
HVACMode.AUTO,
]
assert state.attributes["hvac_modes"] == [HVACMode.AUTO, HVACMode.HEAT]
assert "no_frost" in state.attributes["preset_modes"]
assert "home" in state.attributes["preset_modes"]
@ -211,8 +321,8 @@ async def test_anna_2_climate_entity_attributes(
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_action"] == "cooling"
assert state.attributes["hvac_modes"] == [
HVACMode.HEAT_COOL,
HVACMode.AUTO,
HVACMode.HEAT_COOL,
]
assert state.attributes["supported_features"] == 18
assert state.attributes["target_temp_high"] == 24.0
@ -230,8 +340,8 @@ async def test_anna_3_climate_entity_attributes(
assert state.state == HVACMode.AUTO
assert state.attributes["hvac_action"] == "idle"
assert state.attributes["hvac_modes"] == [
HVACMode.HEAT_COOL,
HVACMode.AUTO,
HVACMode.HEAT_COOL,
]
@ -270,10 +380,8 @@ async def test_anna_climate_entity_climate_changes(
{"entity_id": "climate.anna", "hvac_mode": "auto"},
blocking=True,
)
assert mock_smile_anna.set_schedule_state.call_count == 1
mock_smile_anna.set_schedule_state.assert_called_with(
"c784ee9fdab44e1395b8dee7d7a497d5", "on"
)
# hvac_mode is already auto so not called.
assert mock_smile_anna.set_schedule_state.call_count == 0
await hass.services.async_call(
"climate",
@ -281,16 +389,13 @@ async def test_anna_climate_entity_climate_changes(
{"entity_id": "climate.anna", "hvac_mode": "heat"},
blocking=True,
)
assert mock_smile_anna.set_schedule_state.call_count == 2
assert mock_smile_anna.set_schedule_state.call_count == 1
mock_smile_anna.set_schedule_state.assert_called_with(
"c784ee9fdab44e1395b8dee7d7a497d5", "off"
)
data = mock_smile_anna.async_update.return_value
data.devices["3cb70739631c4d17a86b8b12e8a5161b"]["available_schedules"] = ["None"]
with patch(
"homeassistant.components.plugwise.coordinator.Smile.async_update",
return_value=data,
):
with patch(HA_PLUGWISE_SMILE_ASYNC_UPDATE, return_value=data):
async_fire_time_changed(hass, utcnow() + timedelta(minutes=1))
await hass.async_block_till_done()
state = hass.states.get("climate.anna")

View File

@ -16,7 +16,7 @@ from tests.common import MockConfigEntry
async def test_adam_select_entities(
hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry
) -> None:
"""Test a select."""
"""Test a thermostat Select."""
state = hass.states.get("select.zone_lisa_wk_thermostat_schedule")
assert state
@ -44,3 +44,27 @@ async def test_adam_change_select_entity(
"on",
"Badkamer Schema",
)
async def test_adam_select_regulation_mode(
hass: HomeAssistant, mock_smile_adam_3: MagicMock, init_integration: MockConfigEntry
) -> None:
"""Test a regulation_mode select.
Also tests a change in climate _previous mode.
"""
state = hass.states.get("select.adam_regulation_mode")
assert state
assert state.state == "cooling"
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
"entity_id": "select.adam_regulation_mode",
"option": "heating",
},
blocking=True,
)
assert mock_smile_adam_3.set_regulation_mode.call_count == 1
mock_smile_adam_3.set_regulation_mode.assert_called_with("heating")