Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split timer service for Sensibo #73684

Merged
merged 4 commits into from Jun 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 2 additions & 33 deletions homeassistant/components/sensibo/binary_sensor.py
@@ -1,9 +1,9 @@
"""Binary Sensor platform for Sensibo integration."""
from __future__ import annotations

from collections.abc import Callable, Mapping
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

from pysensibo.model import MotionSensor, SensiboDevice

Expand Down Expand Up @@ -36,7 +36,6 @@ class DeviceBaseEntityDescriptionMixin:
"""Mixin for required Sensibo base description keys."""

value_fn: Callable[[SensiboDevice], bool | None]
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None] | None] | None


@dataclass
Expand Down Expand Up @@ -85,18 +84,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Room Occupied",
icon="mdi:motion-sensor",
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},
),
)

Expand All @@ -107,7 +94,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Pure Boost Enabled",
icon="mdi:wind-power-outline",
value_fn=lambda data: data.pure_boost_enabled,
extra_fn=None,
),
SensiboDeviceBinarySensorEntityDescription(
key="pure_ac_integration",
Expand All @@ -116,7 +102,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Pure Boost linked with AC",
icon="mdi:connection",
value_fn=lambda data: data.pure_ac_integration,
extra_fn=None,
),
SensiboDeviceBinarySensorEntityDescription(
key="pure_geo_integration",
Expand All @@ -125,7 +110,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Pure Boost linked with Presence",
icon="mdi:connection",
value_fn=lambda data: data.pure_geo_integration,
extra_fn=None,
),
SensiboDeviceBinarySensorEntityDescription(
key="pure_measure_integration",
Expand All @@ -134,7 +118,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Pure Boost linked with Indoor Air Quality",
icon="mdi:connection",
value_fn=lambda data: data.pure_measure_integration,
extra_fn=None,
),
SensiboDeviceBinarySensorEntityDescription(
key="pure_prime_integration",
Expand All @@ -143,7 +126,6 @@ class SensiboDeviceBinarySensorEntityDescription(
name="Pure Boost linked with Outdoor Air Quality",
icon="mdi:connection",
value_fn=lambda data: data.pure_prime_integration,
extra_fn=None,
),
)

Expand Down Expand Up @@ -172,12 +154,6 @@ async def async_setup_entry(
for device_id, device_data in coordinator.data.parsed.items()
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(
SensiboDeviceSensor(coordinator, device_id, description)
for description in PURE_SENSOR_TYPES
Expand Down Expand Up @@ -247,10 +223,3 @@ def __init__(
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
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
36 changes: 13 additions & 23 deletions homeassistant/components/sensibo/climate.py
Expand Up @@ -27,7 +27,7 @@
from .entity import SensiboDeviceBaseEntity

SERVICE_ASSUME_STATE = "assume_state"
SERVICE_TIMER = "timer"
SERVICE_ENABLE_TIMER = "enable_timer"
ATTR_MINUTES = "minutes"
SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost"
SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost"
Expand Down Expand Up @@ -98,12 +98,11 @@ async def async_setup_entry(
"async_assume_state",
)
platform.async_register_entity_service(
SERVICE_TIMER,
SERVICE_ENABLE_TIMER,
{
vol.Required(ATTR_STATE): vol.In(["on", "off"]),
vol.Optional(ATTR_MINUTES): cv.positive_int,
vol.Required(ATTR_MINUTES): cv.positive_int,
},
"async_set_timer",
"async_enable_timer",
)
platform.async_register_entity_service(
SERVICE_ENABLE_PURE_BOOST,
Expand Down Expand Up @@ -315,27 +314,18 @@ async def async_assume_state(self, state: str) -> None:
await self._async_set_ac_state_property("on", state != HVACMode.OFF, True)
await self.coordinator.async_refresh()

async def async_set_timer(self, state: str, minutes: int | None = None) -> None:
"""Set or delete timer."""
if state == "off" and self.device_data.timer_id is None:
raise HomeAssistantError("No timer to delete")

if state == "on" and minutes is None:
raise ValueError("No value provided for timer")

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)
async def async_enable_timer(self, minutes: int) -> None:
"""Enable the timer."""
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":
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(
self,
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/sensibo/const.py
Expand Up @@ -18,6 +18,7 @@
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,
]
DEFAULT_NAME = "Sensibo"
Expand Down
16 changes: 3 additions & 13 deletions homeassistant/components/sensibo/services.yaml
Expand Up @@ -16,24 +16,14 @@ assume_state:
options:
- "on"
- "off"
timer:
name: Timer
description: Set or delete timer for device.
enable_timer:
name: Enable Timer
description: Enable the timer with custom time.
target:
entity:
integration: sensibo
domain: climate
fields:
state:
name: State
description: Timer on or off.
required: true
example: "on"
selector:
select:
options:
- "on"
- "off"
minutes:
name: Minutes
description: Countdown for timer (for timer state on)
Expand Down
146 changes: 146 additions & 0 deletions homeassistant/components/sensibo/switch.py
@@ -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)
bdraco marked this conversation as resolved.
Show resolved Hide resolved


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":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd invert the check and raise if true. Then we can outdent below.

setattr(self.device_data, self.entity_description.remote_key, True)
self.async_write_ha_state()
return await self.coordinator.async_request_refresh()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method shouldn't return anything.

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)