Sensibo use switch for Pure boost (#73833)

* Initial commit

* Finalize pure boost switch

* Fix service required
This commit is contained in:
G Johansson 2022-06-22 18:26:25 +02:00 committed by GitHub
parent 837957d89e
commit 532e25d087
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 97 deletions

View file

@ -99,13 +99,6 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
)
PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
SensiboDeviceBinarySensorEntityDescription(
key="pure_boost_enabled",
device_class=BinarySensorDeviceClass.RUNNING,
name="Pure Boost Enabled",
icon="mdi:wind-power-outline",
value_fn=lambda data: data.pure_boost_enabled,
),
SensiboDeviceBinarySensorEntityDescription(
key="pure_ac_integration",
entity_category=EntityCategory.DIAGNOSTIC,

View file

@ -107,21 +107,14 @@ async def async_setup_entry(
platform.async_register_entity_service(
SERVICE_ENABLE_PURE_BOOST,
{
vol.Inclusive(ATTR_AC_INTEGRATION, "settings"): bool,
vol.Inclusive(ATTR_GEO_INTEGRATION, "settings"): bool,
vol.Inclusive(ATTR_INDOOR_INTEGRATION, "settings"): bool,
vol.Inclusive(ATTR_OUTDOOR_INTEGRATION, "settings"): bool,
vol.Inclusive(ATTR_SENSITIVITY, "settings"): vol.In(
["Normal", "Sensitive"]
),
vol.Required(ATTR_AC_INTEGRATION): bool,
vol.Required(ATTR_GEO_INTEGRATION): bool,
vol.Required(ATTR_INDOOR_INTEGRATION): bool,
vol.Required(ATTR_OUTDOOR_INTEGRATION): bool,
vol.Required(ATTR_SENSITIVITY): vol.In(["Normal", "Sensitive"]),
},
"async_enable_pure_boost",
)
platform.async_register_entity_service(
SERVICE_DISABLE_PURE_BOOST,
{},
"async_disable_pure_boost",
)
class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
@ -353,9 +346,3 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
await self.async_send_command("set_pure_boost", params)
await self.coordinator.async_refresh()
async def async_disable_pure_boost(self) -> None:
"""Disable Pure Boost Configuration."""
await self.async_send_command("set_pure_boost", {"enabled": False})
await self.coordinator.async_refresh()

View file

@ -45,45 +45,38 @@ enable_pure_boost:
ac_integration:
name: AC Integration
description: Integrate with Air Conditioner.
required: false
required: true
example: true
selector:
boolean:
geo_integration:
name: Geo Integration
description: Integrate with Presence.
required: false
required: true
example: true
selector:
boolean:
indoor_integration:
name: Indoor Air Quality
description: Integrate with checking indoor air quality.
required: false
required: true
example: true
selector:
boolean:
outdoor_integration:
name: Outdoor Air Quality
description: Integrate with checking outdoor air quality.
required: false
required: true
example: true
selector:
boolean:
sensitivity:
name: Sensitivity
description: Set the sensitivity for Pure Boost.
required: false
required: true
example: "Normal"
selector:
select:
options:
- "Normal"
- "Sensitive"
disable_pure_boost:
name: Disable Pure Boost
description: Disable Pure Boost.
target:
entity:
integration: sensibo
domain: climate

View file

@ -29,7 +29,7 @@ class DeviceBaseEntityDescriptionMixin:
"""Mixin for required Sensibo base description keys."""
value_fn: Callable[[SensiboDevice], bool | None]
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None]]
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None]] | None
command_on: str
command_off: str
remote_key: str
@ -56,6 +56,19 @@ DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = (
),
)
PURE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = (
SensiboDeviceSwitchEntityDescription(
key="pure_boost_switch",
device_class=SwitchDeviceClass.SWITCH,
name="Pure Boost",
value_fn=lambda data: data.pure_boost_enabled,
extra_fn=None,
command_on="set_pure_boost",
command_off="set_pure_boost",
remote_key="pure_boost_enabled",
),
)
def build_params(command: str, device_data: SensiboDevice) -> dict[str, Any] | None:
"""Build params for turning on switch."""
@ -66,6 +79,16 @@ def build_params(command: str, device_data: SensiboDevice) -> dict[str, Any] | N
"acState": {**device_data.ac_states, "on": new_state},
}
return params
if command == "set_pure_boost":
new_state = bool(device_data.pure_boost_enabled is False)
params = {"enabled": new_state}
if device_data.pure_measure_integration is None:
params["sensitivity"] = "N"
params["measurementsIntegration"] = True
params["acIntegration"] = False
params["geoIntegration"] = False
params["primeIntegration"] = False
return params
return None
@ -84,6 +107,12 @@ async def async_setup_entry(
for device_id, device_data in coordinator.data.parsed.items()
if device_data.model != "pure"
)
entities.extend(
SensiboDeviceSwitch(coordinator, device_id, description)
for description in PURE_SWITCH_TYPES
for device_id, device_data in coordinator.data.parsed.items()
if device_data.model == "pure"
)
async_add_entities(entities)
@ -130,7 +159,10 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
result = await self.async_send_command(self.entity_description.command_off)
params = build_params(self.entity_description.command_on, self.device_data)
result = await self.async_send_command(
self.entity_description.command_off, params
)
if result["status"] == "success":
setattr(self.device_data, self.entity_description.remote_key, False)
@ -141,6 +173,8 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity):
)
@property
def extra_state_attributes(self) -> Mapping[str, Any]:
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return additional attributes."""
return self.entity_description.extra_fn(self.device_data)
if self.entity_description.extra_fn:
return self.entity_description.extra_fn(self.device_data)
return None

View file

@ -27,20 +27,18 @@ async def test_binary_sensor(
state2 = hass.states.get("binary_sensor.hallway_motion_sensor_main_sensor")
state3 = hass.states.get("binary_sensor.hallway_motion_sensor_motion")
state4 = hass.states.get("binary_sensor.hallway_room_occupied")
state5 = hass.states.get("binary_sensor.kitchen_pure_boost_enabled")
state6 = hass.states.get(
state5 = hass.states.get(
"binary_sensor.kitchen_pure_boost_linked_with_indoor_air_quality"
)
state7 = hass.states.get(
state6 = hass.states.get(
"binary_sensor.kitchen_pure_boost_linked_with_outdoor_air_quality"
)
assert state1.state == "on"
assert state2.state == "on"
assert state3.state == "on"
assert state4.state == "on"
assert state5.state == "off"
assert state6.state == "on"
assert state7.state == "off"
assert state5.state == "on"
assert state6.state == "off"
monkeypatch.setattr(
get_data.parsed["ABC999111"].motion_sensors["AABBCC"], "alive", False

View file

@ -28,7 +28,6 @@ from homeassistant.components.sensibo.climate import (
ATTR_OUTDOOR_INTEGRATION,
ATTR_SENSITIVITY,
SERVICE_ASSUME_STATE,
SERVICE_DISABLE_PURE_BOOST,
SERVICE_ENABLE_PURE_BOOST,
SERVICE_ENABLE_TIMER,
_find_valid_target_temp,
@ -809,7 +808,7 @@ async def test_climate_pure_boost(
await hass.async_block_till_done()
state_climate = hass.states.get("climate.kitchen")
state2 = hass.states.get("binary_sensor.kitchen_pure_boost_enabled")
state2 = hass.states.get("switch.kitchen_pure_boost")
assert state2.state == "off"
with patch(
@ -878,7 +877,7 @@ async def test_climate_pure_boost(
)
await hass.async_block_till_done()
state1 = hass.states.get("binary_sensor.kitchen_pure_boost_enabled")
state1 = hass.states.get("switch.kitchen_pure_boost")
state2 = hass.states.get(
"binary_sensor.kitchen_pure_boost_linked_with_indoor_air_quality"
)
@ -890,49 +889,3 @@ async def test_climate_pure_boost(
assert state2.state == "on"
assert state3.state == "on"
assert state4.state == "s"
with patch(
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
return_value=get_data,
), patch(
"homeassistant.components.sensibo.coordinator.SensiboClient.async_set_pureboost",
return_value={
"status": "success",
"result": {
"enabled": False,
"sensitivity": "S",
"measurements_integration": True,
"ac_integration": False,
"geo_integration": False,
"prime_integration": True,
},
},
) as mock_set_pureboost:
await hass.services.async_call(
DOMAIN,
SERVICE_DISABLE_PURE_BOOST,
{
ATTR_ENTITY_ID: state_climate.entity_id,
},
blocking=True,
)
await hass.async_block_till_done()
mock_set_pureboost.assert_called_once()
monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_boost_enabled", False)
monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_sensitivity", "s")
with patch(
"homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data",
return_value=get_data,
):
async_fire_time_changed(
hass,
dt.utcnow() + timedelta(minutes=5),
)
await hass.async_block_till_done()
state1 = hass.states.get("binary_sensor.kitchen_pure_boost_enabled")
state4 = hass.states.get("sensor.kitchen_pure_sensitivity")
assert state1.state == "off"
assert state4.state == "s"

View file

@ -25,7 +25,7 @@ from homeassistant.util import dt
from tests.common import async_fire_time_changed
async def test_switch(
async def test_switch_timer(
hass: HomeAssistant,
load_int: ConfigEntry,
monkeypatch: MonkeyPatch,
@ -105,6 +105,81 @@ async def test_switch(
assert state1.state == STATE_OFF
async def test_switch_pure_boost(
hass: HomeAssistant,
load_int: ConfigEntry,
monkeypatch: MonkeyPatch,
get_data: SensiboData,
) -> None:
"""Test the Sensibo switch."""
state1 = hass.states.get("switch.kitchen_pure_boost")
assert state1.state == STATE_OFF
with patch(
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
return_value=get_data,
), patch(
"homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost",
return_value={"status": "success"},
):
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: state1.entity_id,
},
blocking=True,
)
await hass.async_block_till_done()
monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_boost_enabled", True)
with patch(
"homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data",
return_value=get_data,
):
async_fire_time_changed(
hass,
dt.utcnow() + timedelta(minutes=5),
)
await hass.async_block_till_done()
state1 = hass.states.get("switch.kitchen_pure_boost")
assert state1.state == STATE_ON
with patch(
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
return_value=get_data,
), patch(
"homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost",
return_value={"status": "success"},
):
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: state1.entity_id,
},
blocking=True,
)
await hass.async_block_till_done()
monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_boost_enabled", False)
with patch(
"homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data",
return_value=get_data,
):
async_fire_time_changed(
hass,
dt.utcnow() + timedelta(minutes=5),
)
await hass.async_block_till_done()
state1 = hass.states.get("switch.kitchen_pure_boost")
assert state1.state == STATE_OFF
async def test_switch_command_failure(
hass: HomeAssistant,
load_int: ConfigEntry,
@ -162,4 +237,14 @@ async def test_build_params(
"minutesFromNow": 60,
"acState": {**get_data.parsed["ABC999111"].ac_states, "on": False},
}
monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_measure_integration", None)
assert build_params("set_pure_boost", get_data.parsed["AAZZAAZZ"]) == {
"enabled": True,
"sensitivity": "N",
"measurementsIntegration": True,
"acIntegration": False,
"geoIntegration": False,
"primeIntegration": False,
}
assert build_params("incorrect_command", get_data.parsed["ABC999111"]) is None