mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Split timer service for Sensibo (#73684)
This commit is contained in:
parent
b19b6ec6ea
commit
68135e57af
@ -1,9 +1,9 @@
|
|||||||
"""Binary Sensor platform for Sensibo integration."""
|
"""Binary Sensor platform for Sensibo integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from pysensibo.model import MotionSensor, SensiboDevice
|
from pysensibo.model import MotionSensor, SensiboDevice
|
||||||
|
|
||||||
@ -36,7 +36,6 @@ class DeviceBaseEntityDescriptionMixin:
|
|||||||
"""Mixin for required Sensibo base description keys."""
|
"""Mixin for required Sensibo base description keys."""
|
||||||
|
|
||||||
value_fn: Callable[[SensiboDevice], bool | None]
|
value_fn: Callable[[SensiboDevice], bool | None]
|
||||||
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None] | None] | None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -85,18 +84,6 @@ MOTION_DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ..
|
|||||||
name="Room Occupied",
|
name="Room Occupied",
|
||||||
icon="mdi:motion-sensor",
|
icon="mdi:motion-sensor",
|
||||||
value_fn=lambda data: data.room_occupied,
|
value_fn=lambda data: data.room_occupied,
|
||||||
extra_fn=None,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|
||||||
SensiboDeviceBinarySensorEntityDescription(
|
|
||||||
key="timer_on",
|
|
||||||
device_class=BinarySensorDeviceClass.RUNNING,
|
|
||||||
name="Timer Running",
|
|
||||||
icon="mdi:timer",
|
|
||||||
value_fn=lambda data: data.timer_on,
|
|
||||||
extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -107,7 +94,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||||||
name="Pure Boost Enabled",
|
name="Pure Boost Enabled",
|
||||||
icon="mdi:wind-power-outline",
|
icon="mdi:wind-power-outline",
|
||||||
value_fn=lambda data: data.pure_boost_enabled,
|
value_fn=lambda data: data.pure_boost_enabled,
|
||||||
extra_fn=None,
|
|
||||||
),
|
),
|
||||||
SensiboDeviceBinarySensorEntityDescription(
|
SensiboDeviceBinarySensorEntityDescription(
|
||||||
key="pure_ac_integration",
|
key="pure_ac_integration",
|
||||||
@ -116,7 +102,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||||||
name="Pure Boost linked with AC",
|
name="Pure Boost linked with AC",
|
||||||
icon="mdi:connection",
|
icon="mdi:connection",
|
||||||
value_fn=lambda data: data.pure_ac_integration,
|
value_fn=lambda data: data.pure_ac_integration,
|
||||||
extra_fn=None,
|
|
||||||
),
|
),
|
||||||
SensiboDeviceBinarySensorEntityDescription(
|
SensiboDeviceBinarySensorEntityDescription(
|
||||||
key="pure_geo_integration",
|
key="pure_geo_integration",
|
||||||
@ -125,7 +110,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||||||
name="Pure Boost linked with Presence",
|
name="Pure Boost linked with Presence",
|
||||||
icon="mdi:connection",
|
icon="mdi:connection",
|
||||||
value_fn=lambda data: data.pure_geo_integration,
|
value_fn=lambda data: data.pure_geo_integration,
|
||||||
extra_fn=None,
|
|
||||||
),
|
),
|
||||||
SensiboDeviceBinarySensorEntityDescription(
|
SensiboDeviceBinarySensorEntityDescription(
|
||||||
key="pure_measure_integration",
|
key="pure_measure_integration",
|
||||||
@ -134,7 +118,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||||||
name="Pure Boost linked with Indoor Air Quality",
|
name="Pure Boost linked with Indoor Air Quality",
|
||||||
icon="mdi:connection",
|
icon="mdi:connection",
|
||||||
value_fn=lambda data: data.pure_measure_integration,
|
value_fn=lambda data: data.pure_measure_integration,
|
||||||
extra_fn=None,
|
|
||||||
),
|
),
|
||||||
SensiboDeviceBinarySensorEntityDescription(
|
SensiboDeviceBinarySensorEntityDescription(
|
||||||
key="pure_prime_integration",
|
key="pure_prime_integration",
|
||||||
@ -143,7 +126,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||||||
name="Pure Boost linked with Outdoor Air Quality",
|
name="Pure Boost linked with Outdoor Air Quality",
|
||||||
icon="mdi:connection",
|
icon="mdi:connection",
|
||||||
value_fn=lambda data: data.pure_prime_integration,
|
value_fn=lambda data: data.pure_prime_integration,
|
||||||
extra_fn=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -172,12 +154,6 @@ async def async_setup_entry(
|
|||||||
for device_id, device_data in coordinator.data.parsed.items()
|
for device_id, device_data in coordinator.data.parsed.items()
|
||||||
if device_data.motion_sensors is not None
|
if device_data.motion_sensors is not None
|
||||||
)
|
)
|
||||||
entities.extend(
|
|
||||||
SensiboDeviceSensor(coordinator, device_id, description)
|
|
||||||
for description in DEVICE_SENSOR_TYPES
|
|
||||||
for device_id, device_data in coordinator.data.parsed.items()
|
|
||||||
if device_data.model != "pure"
|
|
||||||
)
|
|
||||||
entities.extend(
|
entities.extend(
|
||||||
SensiboDeviceSensor(coordinator, device_id, description)
|
SensiboDeviceSensor(coordinator, device_id, description)
|
||||||
for description in PURE_SENSOR_TYPES
|
for description in PURE_SENSOR_TYPES
|
||||||
@ -247,10 +223,3 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, BinarySensorEntity):
|
|||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
return self.entity_description.value_fn(self.device_data)
|
return self.entity_description.value_fn(self.device_data)
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
|
||||||
"""Return additional attributes."""
|
|
||||||
if self.entity_description.extra_fn is not None:
|
|
||||||
return self.entity_description.extra_fn(self.device_data)
|
|
||||||
return None
|
|
||||||
|
@ -27,7 +27,7 @@ from .coordinator import SensiboDataUpdateCoordinator
|
|||||||
from .entity import SensiboDeviceBaseEntity
|
from .entity import SensiboDeviceBaseEntity
|
||||||
|
|
||||||
SERVICE_ASSUME_STATE = "assume_state"
|
SERVICE_ASSUME_STATE = "assume_state"
|
||||||
SERVICE_TIMER = "timer"
|
SERVICE_ENABLE_TIMER = "enable_timer"
|
||||||
ATTR_MINUTES = "minutes"
|
ATTR_MINUTES = "minutes"
|
||||||
SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost"
|
SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost"
|
||||||
SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost"
|
SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost"
|
||||||
@ -98,12 +98,11 @@ async def async_setup_entry(
|
|||||||
"async_assume_state",
|
"async_assume_state",
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_TIMER,
|
SERVICE_ENABLE_TIMER,
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_STATE): vol.In(["on", "off"]),
|
vol.Required(ATTR_MINUTES): cv.positive_int,
|
||||||
vol.Optional(ATTR_MINUTES): cv.positive_int,
|
|
||||||
},
|
},
|
||||||
"async_set_timer",
|
"async_enable_timer",
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_ENABLE_PURE_BOOST,
|
SERVICE_ENABLE_PURE_BOOST,
|
||||||
@ -315,27 +314,18 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||||||
await self._async_set_ac_state_property("on", state != HVACMode.OFF, True)
|
await self._async_set_ac_state_property("on", state != HVACMode.OFF, True)
|
||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_set_timer(self, state: str, minutes: int | None = None) -> None:
|
async def async_enable_timer(self, minutes: int) -> None:
|
||||||
"""Set or delete timer."""
|
"""Enable the timer."""
|
||||||
if state == "off" and self.device_data.timer_id is None:
|
new_state = bool(self.device_data.ac_states["on"] is False)
|
||||||
raise HomeAssistantError("No timer to delete")
|
params = {
|
||||||
|
"minutesFromNow": minutes,
|
||||||
if state == "on" and minutes is None:
|
"acState": {**self.device_data.ac_states, "on": new_state},
|
||||||
raise ValueError("No value provided for timer")
|
}
|
||||||
|
result = await self.async_send_command("set_timer", params)
|
||||||
if state == "off":
|
|
||||||
result = await self.async_send_command("del_timer")
|
|
||||||
else:
|
|
||||||
new_state = bool(self.device_data.ac_states["on"] is False)
|
|
||||||
params = {
|
|
||||||
"minutesFromNow": minutes,
|
|
||||||
"acState": {**self.device_data.ac_states, "on": new_state},
|
|
||||||
}
|
|
||||||
result = await self.async_send_command("set_timer", params)
|
|
||||||
|
|
||||||
if result["status"] == "success":
|
if result["status"] == "success":
|
||||||
return await self.coordinator.async_request_refresh()
|
return await self.coordinator.async_request_refresh()
|
||||||
raise HomeAssistantError(f"Could not set timer for device {self.name}")
|
raise HomeAssistantError(f"Could not enable timer for device {self.name}")
|
||||||
|
|
||||||
async def async_enable_pure_boost(
|
async def async_enable_pure_boost(
|
||||||
self,
|
self,
|
||||||
|
@ -18,6 +18,7 @@ PLATFORMS = [
|
|||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
Platform.UPDATE,
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
DEFAULT_NAME = "Sensibo"
|
DEFAULT_NAME = "Sensibo"
|
||||||
|
@ -16,24 +16,14 @@ assume_state:
|
|||||||
options:
|
options:
|
||||||
- "on"
|
- "on"
|
||||||
- "off"
|
- "off"
|
||||||
timer:
|
enable_timer:
|
||||||
name: Timer
|
name: Enable Timer
|
||||||
description: Set or delete timer for device.
|
description: Enable the timer with custom time.
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
integration: sensibo
|
integration: sensibo
|
||||||
domain: climate
|
domain: climate
|
||||||
fields:
|
fields:
|
||||||
state:
|
|
||||||
name: State
|
|
||||||
description: Timer on or off.
|
|
||||||
required: true
|
|
||||||
example: "on"
|
|
||||||
selector:
|
|
||||||
select:
|
|
||||||
options:
|
|
||||||
- "on"
|
|
||||||
- "off"
|
|
||||||
minutes:
|
minutes:
|
||||||
name: Minutes
|
name: Minutes
|
||||||
description: Countdown for timer (for timer state on)
|
description: Countdown for timer (for timer state on)
|
||||||
|
146
homeassistant/components/sensibo/switch.py
Normal file
146
homeassistant/components/sensibo/switch.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
"""Switch platform for Sensibo integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pysensibo.model import SensiboDevice
|
||||||
|
|
||||||
|
from homeassistant.components.switch import (
|
||||||
|
SwitchDeviceClass,
|
||||||
|
SwitchEntity,
|
||||||
|
SwitchEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import SensiboDataUpdateCoordinator
|
||||||
|
from .entity import SensiboDeviceBaseEntity
|
||||||
|
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeviceBaseEntityDescriptionMixin:
|
||||||
|
"""Mixin for required Sensibo base description keys."""
|
||||||
|
|
||||||
|
value_fn: Callable[[SensiboDevice], bool | None]
|
||||||
|
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None]]
|
||||||
|
command_on: str
|
||||||
|
command_off: str
|
||||||
|
remote_key: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SensiboDeviceSwitchEntityDescription(
|
||||||
|
SwitchEntityDescription, DeviceBaseEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Describes Sensibo Switch entity."""
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = (
|
||||||
|
SensiboDeviceSwitchEntityDescription(
|
||||||
|
key="timer_on_switch",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
name="Timer",
|
||||||
|
icon="mdi:timer",
|
||||||
|
value_fn=lambda data: data.timer_on,
|
||||||
|
extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on},
|
||||||
|
command_on="set_timer",
|
||||||
|
command_off="del_timer",
|
||||||
|
remote_key="timer_on",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_params(command: str, device_data: SensiboDevice) -> dict[str, Any] | None:
|
||||||
|
"""Build params for turning on switch."""
|
||||||
|
if command == "set_timer":
|
||||||
|
new_state = bool(device_data.ac_states["on"] is False)
|
||||||
|
params = {
|
||||||
|
"minutesFromNow": 60,
|
||||||
|
"acState": {**device_data.ac_states, "on": new_state},
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up Sensibo binary sensor platform."""
|
||||||
|
|
||||||
|
coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities: list[SensiboDeviceSwitch] = []
|
||||||
|
|
||||||
|
entities.extend(
|
||||||
|
SensiboDeviceSwitch(coordinator, device_id, description)
|
||||||
|
for description in DEVICE_SWITCH_TYPES
|
||||||
|
for device_id, device_data in coordinator.data.parsed.items()
|
||||||
|
if device_data.model != "pure"
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity):
|
||||||
|
"""Representation of a Sensibo Device Switch."""
|
||||||
|
|
||||||
|
entity_description: SensiboDeviceSwitchEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: SensiboDataUpdateCoordinator,
|
||||||
|
device_id: str,
|
||||||
|
entity_description: SensiboDeviceSwitchEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initiate Sensibo Device Switch."""
|
||||||
|
super().__init__(
|
||||||
|
coordinator,
|
||||||
|
device_id,
|
||||||
|
)
|
||||||
|
self.entity_description = entity_description
|
||||||
|
self._attr_unique_id = f"{device_id}-{entity_description.key}"
|
||||||
|
self._attr_name = f"{self.device_data.name} {entity_description.name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return True if entity is on."""
|
||||||
|
return self.entity_description.value_fn(self.device_data)
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
params = build_params(self.entity_description.command_on, self.device_data)
|
||||||
|
result = await self.async_send_command(
|
||||||
|
self.entity_description.command_on, params
|
||||||
|
)
|
||||||
|
|
||||||
|
if result["status"] == "success":
|
||||||
|
setattr(self.device_data, self.entity_description.remote_key, True)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
return await self.coordinator.async_request_refresh()
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Could not execute {self.entity_description.command_on} for device {self.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
result = await self.async_send_command(self.entity_description.command_off)
|
||||||
|
|
||||||
|
if result["status"] == "success":
|
||||||
|
setattr(self.device_data, self.entity_description.remote_key, False)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
return await self.coordinator.async_request_refresh()
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Could not execute {self.entity_description.command_off} for device {self.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> Mapping[str, Any]:
|
||||||
|
"""Return additional attributes."""
|
||||||
|
return self.entity_description.extra_fn(self.device_data)
|
@ -30,7 +30,7 @@ from homeassistant.components.sensibo.climate import (
|
|||||||
SERVICE_ASSUME_STATE,
|
SERVICE_ASSUME_STATE,
|
||||||
SERVICE_DISABLE_PURE_BOOST,
|
SERVICE_DISABLE_PURE_BOOST,
|
||||||
SERVICE_ENABLE_PURE_BOOST,
|
SERVICE_ENABLE_PURE_BOOST,
|
||||||
SERVICE_TIMER,
|
SERVICE_ENABLE_TIMER,
|
||||||
_find_valid_target_temp,
|
_find_valid_target_temp,
|
||||||
)
|
)
|
||||||
from homeassistant.components.sensibo.const import DOMAIN
|
from homeassistant.components.sensibo.const import DOMAIN
|
||||||
@ -706,9 +706,45 @@ async def test_climate_set_timer(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state1 = hass.states.get("climate.hallway")
|
state_climate = hass.states.get("climate.hallway")
|
||||||
assert hass.states.get("sensor.hallway_timer_end_time").state == STATE_UNKNOWN
|
assert hass.states.get("sensor.hallway_timer_end_time").state == STATE_UNKNOWN
|
||||||
assert hass.states.get("binary_sensor.hallway_timer_running").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_timer",
|
||||||
|
return_value={"status": "failure"},
|
||||||
|
):
|
||||||
|
with pytest.raises(MultipleInvalid):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_ENABLE_TIMER,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state_climate.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
|
return_value=get_data,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_set_timer",
|
||||||
|
return_value={"status": "failure"},
|
||||||
|
):
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_ENABLE_TIMER,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state_climate.entity_id,
|
||||||
|
ATTR_MINUTES: 30,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
@ -719,10 +755,9 @@ async def test_climate_set_timer(
|
|||||||
):
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_TIMER,
|
SERVICE_ENABLE_TIMER,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
ATTR_ENTITY_ID: state_climate.entity_id,
|
||||||
ATTR_STATE: "on",
|
|
||||||
ATTR_MINUTES: 30,
|
ATTR_MINUTES: 30,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
@ -752,166 +787,6 @@ async def test_climate_set_timer(
|
|||||||
hass.states.get("sensor.hallway_timer_end_time").state
|
hass.states.get("sensor.hallway_timer_end_time").state
|
||||||
== "2022-06-06T12:00:00+00:00"
|
== "2022-06-06T12:00:00+00:00"
|
||||||
)
|
)
|
||||||
assert hass.states.get("binary_sensor.hallway_timer_running").state == "on"
|
|
||||||
assert hass.states.get("binary_sensor.hallway_timer_running").attributes == {
|
|
||||||
"device_class": "running",
|
|
||||||
"friendly_name": "Hallway Timer Running",
|
|
||||||
"icon": "mdi:timer",
|
|
||||||
"id": "SzTGE4oZ4D",
|
|
||||||
"turn_on": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
|
||||||
return_value=get_data,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_del_timer",
|
|
||||||
return_value={"status": "success"},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TIMER,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
|
||||||
ATTR_STATE: "off",
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_on", False)
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_id", None)
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_state_on", None)
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_time", None)
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
assert hass.states.get("sensor.hallway_timer_end_time").state == STATE_UNKNOWN
|
|
||||||
assert hass.states.get("binary_sensor.hallway_timer_running").state == "off"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_climate_set_timer_failures(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entity_registry_enabled_by_default: AsyncMock,
|
|
||||||
load_int: ConfigEntry,
|
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
|
||||||
get_data: SensiboData,
|
|
||||||
) -> None:
|
|
||||||
"""Test the Sensibo climate Set Timer service failures."""
|
|
||||||
|
|
||||||
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("climate.hallway")
|
|
||||||
assert hass.states.get("sensor.hallway_timer_end_time").state == STATE_UNKNOWN
|
|
||||||
assert hass.states.get("binary_sensor.hallway_timer_running").state == "off"
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TIMER,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
|
||||||
ATTR_STATE: "on",
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
|
||||||
return_value=get_data,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_set_timer",
|
|
||||||
return_value={"status": "success", "result": {"id": ""}},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TIMER,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
|
||||||
ATTR_STATE: "on",
|
|
||||||
ATTR_MINUTES: 30,
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_on", True)
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_id", None)
|
|
||||||
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_state_on", False)
|
|
||||||
monkeypatch.setattr(
|
|
||||||
get_data.parsed["ABC999111"],
|
|
||||||
"timer_time",
|
|
||||||
datetime(2022, 6, 6, 12, 00, 00, tzinfo=dt.UTC),
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TIMER,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
|
||||||
ATTR_STATE: "off",
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
|
||||||
return_value=get_data,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.sensibo.util.SensiboClient.async_set_timer",
|
|
||||||
return_value={"status": "failure"},
|
|
||||||
):
|
|
||||||
with pytest.raises(HomeAssistantError):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TIMER,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: state1.entity_id,
|
|
||||||
ATTR_STATE: "on",
|
|
||||||
ATTR_MINUTES: 30,
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_climate_pure_boost(
|
async def test_climate_pure_boost(
|
||||||
|
165
tests/components/sensibo/test_switch.py
Normal file
165
tests/components/sensibo/test_switch.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
"""The test for the sensibo switch platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pysensibo.model import SensiboData
|
||||||
|
import pytest
|
||||||
|
from pytest import MonkeyPatch
|
||||||
|
|
||||||
|
from homeassistant.components.sensibo.switch import build_params
|
||||||
|
from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.util import dt
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_switch(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
load_int: ConfigEntry,
|
||||||
|
monkeypatch: MonkeyPatch,
|
||||||
|
get_data: SensiboData,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Sensibo switch."""
|
||||||
|
|
||||||
|
state1 = hass.states.get("switch.hallway_timer")
|
||||||
|
assert state1.state == STATE_OFF
|
||||||
|
assert state1.attributes["id"] is None
|
||||||
|
assert state1.attributes["turn_on"] is None
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
|
return_value=get_data,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_set_timer",
|
||||||
|
return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}},
|
||||||
|
):
|
||||||
|
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["ABC999111"], "timer_on", True)
|
||||||
|
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_id", "SzTGE4oZ4D")
|
||||||
|
monkeypatch.setattr(get_data.parsed["ABC999111"], "timer_state_on", 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.hallway_timer")
|
||||||
|
assert state1.state == STATE_ON
|
||||||
|
assert state1.attributes["id"] == "SzTGE4oZ4D"
|
||||||
|
assert state1.attributes["turn_on"] is False
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
|
return_value=get_data,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_del_timer",
|
||||||
|
return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}},
|
||||||
|
):
|
||||||
|
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["ABC999111"], "timer_on", 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.hallway_timer")
|
||||||
|
assert state1.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_switch_command_failure(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
load_int: ConfigEntry,
|
||||||
|
monkeypatch: MonkeyPatch,
|
||||||
|
get_data: SensiboData,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Sensibo switch fails commands."""
|
||||||
|
|
||||||
|
state1 = hass.states.get("switch.hallway_timer")
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
|
return_value=get_data,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_set_timer",
|
||||||
|
return_value={"status": "failure"},
|
||||||
|
):
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state1.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data",
|
||||||
|
return_value=get_data,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.sensibo.util.SensiboClient.async_del_timer",
|
||||||
|
return_value={"status": "failure"},
|
||||||
|
):
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state1.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_build_params(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
load_int: ConfigEntry,
|
||||||
|
monkeypatch: MonkeyPatch,
|
||||||
|
get_data: SensiboData,
|
||||||
|
) -> None:
|
||||||
|
"""Test the build params method."""
|
||||||
|
|
||||||
|
assert build_params("set_timer", get_data.parsed["ABC999111"]) == {
|
||||||
|
"minutesFromNow": 60,
|
||||||
|
"acState": {**get_data.parsed["ABC999111"].ac_states, "on": False},
|
||||||
|
}
|
||||||
|
assert build_params("incorrect_command", get_data.parsed["ABC999111"]) is None
|
Loading…
x
Reference in New Issue
Block a user