Sensibo Add timer (#73072)

This commit is contained in:
G Johansson
2022-06-13 09:17:08 -10:00
committed by GitHub
parent dca4d3cd61
commit c660fae8d8
6 changed files with 373 additions and 15 deletions
@@ -1,9 +1,9 @@
"""Binary Sensor platform for Sensibo integration."""
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from pysensibo.model import MotionSensor, SensiboDevice
@@ -36,6 +36,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] | None] | None
@dataclass
@@ -77,13 +78,25 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionBinarySensorEntityDescription, ...] = (
),
)
DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
MOTION_DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
SensiboDeviceBinarySensorEntityDescription(
key="room_occupied",
device_class=BinarySensorDeviceClass.MOTION,
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},
),
)
@@ -94,6 +107,7 @@ PURE_SENSOR_TYPES: tuple[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",
@@ -102,6 +116,7 @@ PURE_SENSOR_TYPES: tuple[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",
@@ -110,6 +125,7 @@ PURE_SENSOR_TYPES: tuple[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",
@@ -118,6 +134,7 @@ PURE_SENSOR_TYPES: tuple[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",
@@ -126,6 +143,7 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
name="Pure Boost linked with Outdoor Air Quality",
icon="mdi:connection",
value_fn=lambda data: data.pure_prime_integration,
extra_fn=None,
),
)
@@ -148,11 +166,17 @@ async def async_setup_entry(
for sensor_id, sensor_data in device_data.motion_sensors.items()
for description in MOTION_SENSOR_TYPES
)
entities.extend(
SensiboDeviceSensor(coordinator, device_id, description)
for description in MOTION_DEVICE_SENSOR_TYPES
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 getattr(device_data, description.key) is not None
if device_data.model != "pure"
)
entities.extend(
SensiboDeviceSensor(coordinator, device_id, description)
@@ -223,3 +247,10 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, BinarySensorEntity):
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
+33 -1
View File
@@ -18,7 +18,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.temperature import convert as convert_temperature
@@ -27,6 +27,8 @@ from .coordinator import SensiboDataUpdateCoordinator
from .entity import SensiboDeviceBaseEntity
SERVICE_ASSUME_STATE = "assume_state"
SERVICE_TIMER = "timer"
ATTR_MINUTES = "minutes"
PARALLEL_UPDATES = 0
FIELD_TO_FLAG = {
@@ -85,6 +87,14 @@ async def async_setup_entry(
},
"async_assume_state",
)
platform.async_register_entity_service(
SERVICE_TIMER,
{
vol.Required(ATTR_STATE): vol.In(["on", "off"]),
vol.Optional(ATTR_MINUTES): cv.positive_int,
},
"async_set_timer",
)
class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
@@ -276,3 +286,25 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
"""Sync state with api."""
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)
if result["status"] == "success":
return await self.coordinator.async_request_refresh()
raise HomeAssistantError(f"Could not set timer for device {self.name}")
+12 -2
View File
@@ -57,7 +57,7 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity):
)
async def async_send_command(
self, command: str, params: dict[str, Any]
self, command: str, params: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Send command to Sensibo api."""
try:
@@ -72,16 +72,20 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity):
return result
async def async_send_api_call(
self, command: str, params: dict[str, Any]
self, command: str, params: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Send api call."""
result: dict[str, Any] = {"status": None}
if command == "set_calibration":
if TYPE_CHECKING:
assert params is not None
result = await self._client.async_set_calibration(
self._device_id,
params["data"],
)
if command == "set_ac_state":
if TYPE_CHECKING:
assert params is not None
result = await self._client.async_set_ac_state_property(
self._device_id,
params["name"],
@@ -89,6 +93,12 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity):
params["ac_states"],
params["assumed_state"],
)
if command == "set_timer":
if TYPE_CHECKING:
assert params is not None
result = await self._client.async_set_timer(self._device_id, params)
if command == "del_timer":
result = await self._client.async_del_timer(self._device_id)
return result
+33 -6
View File
@@ -1,9 +1,10 @@
"""Sensor platform for Sensibo integration."""
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import TYPE_CHECKING
from datetime import datetime
from typing import TYPE_CHECKING, Any
from pysensibo.model import MotionSensor, SensiboDevice
@@ -44,7 +45,8 @@ class MotionBaseEntityDescriptionMixin:
class DeviceBaseEntityDescriptionMixin:
"""Mixin for required Sensibo base description keys."""
value_fn: Callable[[SensiboDevice], StateType]
value_fn: Callable[[SensiboDevice], StateType | datetime]
extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None] | None] | None
@dataclass
@@ -111,13 +113,25 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = (
name="PM2.5",
icon="mdi:air-filter",
value_fn=lambda data: data.pm25,
extra_fn=None,
),
SensiboDeviceSensorEntityDescription(
key="pure_sensitivity",
name="Pure Sensitivity",
icon="mdi:air-filter",
value_fn=lambda data: str(data.pure_sensitivity).lower(),
device_class="sensibo__sensitivity",
value_fn=lambda data: data.pure_sensitivity,
extra_fn=None,
),
)
DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = (
SensiboDeviceSensorEntityDescription(
key="timer_time",
device_class=SensorDeviceClass.TIMESTAMP,
name="Timer End Time",
icon="mdi:timer",
value_fn=lambda data: data.timer_time,
extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on},
),
)
@@ -146,6 +160,12 @@ async def async_setup_entry(
for description in PURE_SENSOR_TYPES
if device_data.model == "pure"
)
entities.extend(
SensiboDeviceSensor(coordinator, device_id, description)
for device_id, device_data in coordinator.data.parsed.items()
for description in DEVICE_SENSOR_TYPES
if device_data.model != "pure"
)
async_add_entities(entities)
@@ -205,6 +225,13 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, SensorEntity):
self._attr_name = f"{self.device_data.name} {entity_description.name}"
@property
def native_value(self) -> StateType:
def native_value(self) -> StateType | datetime:
"""Return value of sensor."""
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
@@ -16,3 +16,31 @@ assume_state:
options:
- "on"
- "off"
timer:
name: Timer
description: Set or delete timer for device.
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)
required: false
example: 30
selector:
number:
min: 0
step: 1
mode: box