Improve data handling for Sensibo (#68419)
This commit is contained in:
parent
5fffe9b22f
commit
d23d19f9e6
10 changed files with 81 additions and 287 deletions
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pysensibo.model import MotionSensor, SensiboDevice
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
|
@ -15,8 +16,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from .coordinator import SensiboDataUpdateCoordinator
|
||||
from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity
|
||||
|
||||
|
||||
|
@ -31,7 +32,7 @@ class MotionBaseEntityDescriptionMixin:
|
|||
class DeviceBaseEntityDescriptionMixin:
|
||||
"""Mixin for required Sensibo base description keys."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], bool | None]
|
||||
value_fn: Callable[[SensiboDevice], bool | None]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -79,7 +80,7 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||
device_class=BinarySensorDeviceClass.MOTION,
|
||||
name="Room Occupied",
|
||||
icon="mdi:motion-sensor",
|
||||
value_fn=lambda data: data["room_occupied"],
|
||||
value_fn=lambda data: data.room_occupied,
|
||||
),
|
||||
SensiboDeviceBinarySensorEntityDescription(
|
||||
key="update_available",
|
||||
|
@ -87,7 +88,7 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
|
|||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
name="Update Available",
|
||||
icon="mdi:rocket-launch",
|
||||
value_fn=lambda data: data["update_available"],
|
||||
value_fn=lambda data: data.update_available,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -100,22 +101,19 @@ async def async_setup_entry(
|
|||
coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[SensiboMotionSensor | SensiboDeviceSensor] = []
|
||||
LOGGER.debug("parsed data: %s", coordinator.data.parsed)
|
||||
entities.extend(
|
||||
SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description)
|
||||
for device_id, device_data in coordinator.data.parsed.items()
|
||||
for sensor_id, sensor_data in device_data["motion_sensors"].items()
|
||||
for sensor_id, sensor_data in device_data.motion_sensors.items()
|
||||
for description in MOTION_SENSOR_TYPES
|
||||
if device_data["motion_sensors"]
|
||||
if device_data.motion_sensors
|
||||
)
|
||||
LOGGER.debug("start device %s", entities)
|
||||
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[description.key] is not None
|
||||
if getattr(device_data, description.key) is not None
|
||||
)
|
||||
LOGGER.debug("list: %s", entities)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
@ -144,7 +142,7 @@ class SensiboMotionSensor(SensiboMotionBaseEntity, BinarySensorEntity):
|
|||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{sensor_id}-{entity_description.key}"
|
||||
self._attr_name = (
|
||||
f"{self.device_data['name']} Motion Sensor {entity_description.name}"
|
||||
f"{self.device_data.name} Motion Sensor {entity_description.name}"
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -171,7 +169,7 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, BinarySensorEntity):
|
|||
)
|
||||
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}"
|
||||
self._attr_name = f"{self.device_data.name} {entity_description.name}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
|
|
|
@ -126,11 +126,9 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
"""Initiate SensiboClimate."""
|
||||
super().__init__(coordinator, device_id)
|
||||
self._attr_unique_id = device_id
|
||||
self._attr_name = coordinator.data.parsed[device_id]["name"]
|
||||
self._attr_name = self.device_data.name
|
||||
self._attr_temperature_unit = (
|
||||
TEMP_CELSIUS
|
||||
if coordinator.data.parsed[device_id]["temp_unit"] == "C"
|
||||
else TEMP_FAHRENHEIT
|
||||
TEMP_CELSIUS if self.device_data.temp_unit == "C" else TEMP_FAHRENHEIT
|
||||
)
|
||||
self._attr_supported_features = self.get_features()
|
||||
self._attr_precision = PRECISION_TENTHS
|
||||
|
@ -138,7 +136,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
def get_features(self) -> int:
|
||||
"""Get supported features."""
|
||||
features = 0
|
||||
for key in self.coordinator.data.parsed[self.unique_id]["full_features"]:
|
||||
for key in self.device_data.full_features:
|
||||
if key in FIELD_TO_FLAG:
|
||||
features |= FIELD_TO_FLAG[key]
|
||||
return features
|
||||
|
@ -146,30 +144,27 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
@property
|
||||
def current_humidity(self) -> int | None:
|
||||
"""Return the current humidity."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["humidity"]
|
||||
return self.device_data.humidity
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation."""
|
||||
return (
|
||||
SENSIBO_TO_HA[self.coordinator.data.parsed[self.unique_id]["hvac_mode"]]
|
||||
if self.coordinator.data.parsed[self.unique_id]["on"]
|
||||
SENSIBO_TO_HA[self.device_data.hvac_mode]
|
||||
if self.device_data.device_on
|
||||
else HVAC_MODE_OFF
|
||||
)
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return [
|
||||
SENSIBO_TO_HA[mode]
|
||||
for mode in self.coordinator.data.parsed[self.unique_id]["hvac_modes"]
|
||||
]
|
||||
return [SENSIBO_TO_HA[mode] for mode in self.device_data.hvac_modes]
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
return convert_temperature(
|
||||
self.coordinator.data.parsed[self.unique_id]["temp"],
|
||||
self.device_data.temp,
|
||||
TEMP_CELSIUS,
|
||||
self.temperature_unit,
|
||||
)
|
||||
|
@ -177,57 +172,51 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Return the temperature we try to reach."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["target_temp"]
|
||||
return self.device_data.target_temp
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float | None:
|
||||
"""Return the supported step of target temperature."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["temp_step"]
|
||||
return self.device_data.temp_step
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return the fan setting."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["fan_mode"]
|
||||
return self.device_data.fan_mode
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str] | None:
|
||||
"""Return the list of available fan modes."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["fan_modes"]
|
||||
return self.device_data.fan_modes
|
||||
|
||||
@property
|
||||
def swing_mode(self) -> str | None:
|
||||
"""Return the swing setting."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["swing_mode"]
|
||||
return self.device_data.swing_mode
|
||||
|
||||
@property
|
||||
def swing_modes(self) -> list[str] | None:
|
||||
"""Return the list of available swing modes."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["swing_modes"]
|
||||
return self.device_data.swing_modes
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["temp_list"][0]
|
||||
return self.device_data.temp_list[0]
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum temperature."""
|
||||
return self.coordinator.data.parsed[self.unique_id]["temp_list"][-1]
|
||||
return self.device_data.temp_list[-1]
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return (
|
||||
self.coordinator.data.parsed[self.unique_id]["available"]
|
||||
and super().available
|
||||
)
|
||||
return self.device_data.available and super().available
|
||||
|
||||
async def async_set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
if (
|
||||
"targetTemperature"
|
||||
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
|
||||
):
|
||||
if "targetTemperature" not in self.device_data.active_features:
|
||||
raise HomeAssistantError(
|
||||
"Current mode doesn't support setting Target Temperature"
|
||||
)
|
||||
|
@ -238,23 +227,13 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
if temperature == self.target_temperature:
|
||||
return
|
||||
|
||||
if temperature not in self.coordinator.data.parsed[self.unique_id]["temp_list"]:
|
||||
if temperature not in self.device_data.temp_list:
|
||||
# Requested temperature is not supported.
|
||||
if (
|
||||
temperature
|
||||
> self.coordinator.data.parsed[self.unique_id]["temp_list"][-1]
|
||||
):
|
||||
temperature = self.coordinator.data.parsed[self.unique_id]["temp_list"][
|
||||
-1
|
||||
]
|
||||
if temperature > self.device_data.temp_list[-1]:
|
||||
temperature = self.device_data.temp_list[-1]
|
||||
|
||||
elif (
|
||||
temperature
|
||||
< self.coordinator.data.parsed[self.unique_id]["temp_list"][0]
|
||||
):
|
||||
temperature = self.coordinator.data.parsed[self.unique_id]["temp_list"][
|
||||
0
|
||||
]
|
||||
elif temperature < self.device_data.temp_list[0]:
|
||||
temperature = self.device_data.temp_list[0]
|
||||
|
||||
else:
|
||||
return
|
||||
|
@ -263,10 +242,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
if (
|
||||
"fanLevel"
|
||||
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
|
||||
):
|
||||
if "fanLevel" not in self.device_data.active_features:
|
||||
raise HomeAssistantError("Current mode doesn't support setting Fanlevel")
|
||||
|
||||
await self._async_set_ac_state_property("fanLevel", fan_mode)
|
||||
|
@ -278,7 +254,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
return
|
||||
|
||||
# Turn on if not currently on.
|
||||
if not self.coordinator.data.parsed[self.unique_id]["on"]:
|
||||
if not self.device_data.device_on:
|
||||
await self._async_set_ac_state_property("on", True)
|
||||
|
||||
await self._async_set_ac_state_property("mode", HA_TO_SENSIBO[hvac_mode])
|
||||
|
@ -286,10 +262,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set new target swing operation."""
|
||||
if (
|
||||
"swing"
|
||||
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
|
||||
):
|
||||
if "swing" not in self.device_data.active_features:
|
||||
raise HomeAssistantError("Current mode doesn't support setting Swing")
|
||||
|
||||
await self._async_set_ac_state_property("swing", swing_mode)
|
||||
|
@ -309,13 +282,13 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||
params = {
|
||||
"name": name,
|
||||
"value": value,
|
||||
"ac_states": self.coordinator.data.parsed[self.unique_id]["ac_states"],
|
||||
"ac_states": self.device_data.ac_states,
|
||||
"assumed_state": assumed_state,
|
||||
}
|
||||
result = await self.async_send_command("set_ac_state", params)
|
||||
|
||||
if result["result"]["status"] == "Success":
|
||||
self.coordinator.data.parsed[self.unique_id][AC_STATE_TO_DATA[name]] = value
|
||||
setattr(self.device_data, AC_STATE_TO_DATA[name], value)
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
"""DataUpdateCoordinator for the Sensibo integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from pysensibo import SensiboClient
|
||||
from pysensibo.exceptions import AuthenticationError, SensiboError
|
||||
from pysensibo.model import SensiboData
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
|
@ -17,33 +16,6 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT
|
||||
|
||||
MAX_POSSIBLE_STEP = 1000
|
||||
|
||||
|
||||
@dataclass
|
||||
class MotionSensor:
|
||||
"""Dataclass for motionsensors."""
|
||||
|
||||
id: str
|
||||
alive: bool | None = None
|
||||
motion: bool | None = None
|
||||
fw_ver: str | None = None
|
||||
fw_type: str | None = None
|
||||
is_main_sensor: bool | None = None
|
||||
battery_voltage: int | None = None
|
||||
humidity: int | None = None
|
||||
temperature: float | None = None
|
||||
model: str | None = None
|
||||
rssi: int | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SensiboData:
|
||||
"""Dataclass for Sensibo data."""
|
||||
|
||||
raw: dict
|
||||
parsed: dict
|
||||
|
||||
|
||||
class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""A Sensibo Data Update Coordinator."""
|
||||
|
@ -67,156 +39,13 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
|
|||
async def _async_update_data(self) -> SensiboData:
|
||||
"""Fetch data from Sensibo."""
|
||||
|
||||
devices = []
|
||||
try:
|
||||
data = await self.client.async_get_devices()
|
||||
for dev in data["result"]:
|
||||
devices.append(dev)
|
||||
data = await self.client.async_get_devices_data()
|
||||
except AuthenticationError as error:
|
||||
raise ConfigEntryAuthFailed from error
|
||||
except SensiboError as error:
|
||||
raise UpdateFailed from error
|
||||
|
||||
if not devices:
|
||||
if not data.raw:
|
||||
raise UpdateFailed("No devices found")
|
||||
|
||||
device_data: dict[str, Any] = {}
|
||||
for dev in devices:
|
||||
unique_id = dev["id"]
|
||||
mac = dev["macAddress"]
|
||||
name = dev["room"]["name"]
|
||||
temperature = dev["measurements"].get("temperature")
|
||||
humidity = dev["measurements"].get("humidity")
|
||||
ac_states = dev["acState"]
|
||||
target_temperature = ac_states.get("targetTemperature")
|
||||
hvac_mode = ac_states.get("mode")
|
||||
running = ac_states.get("on")
|
||||
fan_mode = ac_states.get("fanLevel")
|
||||
swing_mode = ac_states.get("swing")
|
||||
horizontal_swing_mode = ac_states.get("horizontalSwing")
|
||||
light_mode = ac_states.get("light")
|
||||
available = dev["connectionStatus"].get("isAlive", True)
|
||||
capabilities = dev["remoteCapabilities"]
|
||||
hvac_modes = list(capabilities["modes"])
|
||||
if hvac_modes:
|
||||
hvac_modes.append("off")
|
||||
current_capabilities = capabilities["modes"][ac_states.get("mode")]
|
||||
fan_modes = current_capabilities.get("fanLevels")
|
||||
swing_modes = current_capabilities.get("swing")
|
||||
horizontal_swing_modes = current_capabilities.get("horizontalSwing")
|
||||
light_modes = current_capabilities.get("light")
|
||||
temperature_unit_key = dev.get("temperatureUnit") or ac_states.get(
|
||||
"temperatureUnit"
|
||||
)
|
||||
temperatures_list = (
|
||||
current_capabilities["temperatures"]
|
||||
.get(temperature_unit_key, {})
|
||||
.get("values", [0, 1])
|
||||
)
|
||||
if temperatures_list:
|
||||
diff = MAX_POSSIBLE_STEP
|
||||
for i in range(len(temperatures_list) - 1):
|
||||
if temperatures_list[i + 1] - temperatures_list[i] < diff:
|
||||
diff = temperatures_list[i + 1] - temperatures_list[i]
|
||||
temperature_step = diff
|
||||
|
||||
active_features = list(ac_states)
|
||||
full_features = set()
|
||||
for mode in capabilities["modes"]:
|
||||
if "temperatures" in capabilities["modes"][mode]:
|
||||
full_features.add("targetTemperature")
|
||||
if "swing" in capabilities["modes"][mode]:
|
||||
full_features.add("swing")
|
||||
if "fanLevels" in capabilities["modes"][mode]:
|
||||
full_features.add("fanLevel")
|
||||
if "horizontalSwing" in capabilities["modes"][mode]:
|
||||
full_features.add("horizontalSwing")
|
||||
if "light" in capabilities["modes"][mode]:
|
||||
full_features.add("light")
|
||||
|
||||
state = hvac_mode if hvac_mode else "off"
|
||||
|
||||
fw_ver = dev["firmwareVersion"]
|
||||
fw_type = dev["firmwareType"]
|
||||
model = dev["productModel"]
|
||||
|
||||
calibration_temp = dev["sensorsCalibration"].get("temperature")
|
||||
calibration_hum = dev["sensorsCalibration"].get("humidity")
|
||||
|
||||
# Sky plus supports functionality to use motion sensor as sensor for temp and humidity
|
||||
if main_sensor := dev["mainMeasurementsSensor"]:
|
||||
measurements = main_sensor["measurements"]
|
||||
temperature = measurements.get("temperature")
|
||||
humidity = measurements.get("humidity")
|
||||
|
||||
motion_sensors: dict[str, Any] = {}
|
||||
if dev["motionSensors"]:
|
||||
for sensor in dev["motionSensors"]:
|
||||
measurement = sensor["measurements"]
|
||||
motion_sensors[sensor["id"]] = MotionSensor(
|
||||
id=sensor["id"],
|
||||
alive=sensor["connectionStatus"].get("isAlive"),
|
||||
motion=measurement.get("motion"),
|
||||
fw_ver=sensor.get("firmwareVersion"),
|
||||
fw_type=sensor.get("firmwareType"),
|
||||
is_main_sensor=sensor.get("isMainSensor"),
|
||||
battery_voltage=measurement.get("batteryVoltage"),
|
||||
humidity=measurement.get("humidity"),
|
||||
temperature=measurement.get("temperature"),
|
||||
model=sensor.get("productModel"),
|
||||
rssi=measurement.get("rssi"),
|
||||
)
|
||||
|
||||
# Add information for pure devices
|
||||
pure_conf = dev["pureBoostConfig"]
|
||||
pure_sensitivity = pure_conf.get("sensitivity") if pure_conf else None
|
||||
pure_boost_enabled = pure_conf.get("enabled") if pure_conf else None
|
||||
pm25 = dev["measurements"].get("pm25")
|
||||
|
||||
# Binary sensors for main device
|
||||
room_occupied = dev["roomIsOccupied"]
|
||||
update_available = bool(
|
||||
dev["firmwareVersion"] != dev["currentlyAvailableFirmwareVersion"]
|
||||
)
|
||||
|
||||
device_data[unique_id] = {
|
||||
"id": unique_id,
|
||||
"mac": mac,
|
||||
"name": name,
|
||||
"ac_states": ac_states,
|
||||
"temp": temperature,
|
||||
"humidity": humidity,
|
||||
"target_temp": target_temperature,
|
||||
"hvac_mode": hvac_mode,
|
||||
"on": running,
|
||||
"fan_mode": fan_mode,
|
||||
"swing_mode": swing_mode,
|
||||
"horizontal_swing_mode": horizontal_swing_mode,
|
||||
"light_mode": light_mode,
|
||||
"available": available,
|
||||
"hvac_modes": hvac_modes,
|
||||
"fan_modes": fan_modes,
|
||||
"swing_modes": swing_modes,
|
||||
"horizontal_swing_modes": horizontal_swing_modes,
|
||||
"light_modes": light_modes,
|
||||
"temp_unit": temperature_unit_key,
|
||||
"temp_list": temperatures_list,
|
||||
"temp_step": temperature_step,
|
||||
"active_features": active_features,
|
||||
"full_features": full_features,
|
||||
"state": state,
|
||||
"fw_ver": fw_ver,
|
||||
"fw_type": fw_type,
|
||||
"model": model,
|
||||
"calibration_temp": calibration_temp,
|
||||
"calibration_hum": calibration_hum,
|
||||
"full_capabilities": capabilities,
|
||||
"motion_sensors": motion_sensors,
|
||||
"pure_sensitivity": pure_sensitivity,
|
||||
"pure_boost_enabled": pure_boost_enabled,
|
||||
"pm25": pm25,
|
||||
"room_occupied": room_occupied,
|
||||
"update_available": update_available,
|
||||
}
|
||||
|
||||
return SensiboData(raw=data, parsed=device_data)
|
||||
return data
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
import async_timeout
|
||||
from pysensibo.model import MotionSensor, SensiboDevice
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
|
@ -11,7 +12,7 @@ from homeassistant.helpers.entity import DeviceInfo
|
|||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT
|
||||
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator
|
||||
from .coordinator import SensiboDataUpdateCoordinator
|
||||
|
||||
|
||||
class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]):
|
||||
|
@ -28,7 +29,7 @@ class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]):
|
|||
self._client = coordinator.client
|
||||
|
||||
@property
|
||||
def device_data(self) -> dict[str, Any]:
|
||||
def device_data(self) -> SensiboDevice:
|
||||
"""Return data for device."""
|
||||
return self.coordinator.data.parsed[self._device_id]
|
||||
|
||||
|
@ -44,15 +45,15 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity):
|
|||
"""Initiate Sensibo Number."""
|
||||
super().__init__(coordinator, device_id)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.device_data["id"])},
|
||||
name=self.device_data["name"],
|
||||
connections={(CONNECTION_NETWORK_MAC, self.device_data["mac"])},
|
||||
identifiers={(DOMAIN, self.device_data.id)},
|
||||
name=self.device_data.name,
|
||||
connections={(CONNECTION_NETWORK_MAC, self.device_data.mac)},
|
||||
manufacturer="Sensibo",
|
||||
configuration_url="https://home.sensibo.com/",
|
||||
model=self.device_data["model"],
|
||||
sw_version=self.device_data["fw_ver"],
|
||||
hw_version=self.device_data["fw_type"],
|
||||
suggested_area=self.device_data["name"],
|
||||
model=self.device_data.model,
|
||||
sw_version=self.device_data.fw_ver,
|
||||
hw_version=self.device_data.fw_type,
|
||||
suggested_area=self.device_data.name,
|
||||
)
|
||||
|
||||
async def async_send_command(
|
||||
|
@ -108,7 +109,7 @@ class SensiboMotionBaseEntity(SensiboBaseEntity):
|
|||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, sensor_id)},
|
||||
name=f"{self.device_data['name']} Motion Sensor {name}",
|
||||
name=f"{self.device_data.name} Motion Sensor {name}",
|
||||
via_device=(DOMAIN, device_id),
|
||||
manufacturer="Sensibo",
|
||||
configuration_url="https://home.sensibo.com/",
|
||||
|
@ -120,4 +121,4 @@ class SensiboMotionBaseEntity(SensiboBaseEntity):
|
|||
@property
|
||||
def sensor_data(self) -> MotionSensor:
|
||||
"""Return data for device."""
|
||||
return self.device_data["motion_sensors"][self._sensor_id]
|
||||
return self.device_data.motion_sensors[self._sensor_id]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "sensibo",
|
||||
"name": "Sensibo",
|
||||
"documentation": "https://www.home-assistant.io/integrations/sensibo",
|
||||
"requirements": ["pysensibo==1.0.8"],
|
||||
"requirements": ["pysensibo==1.0.9"],
|
||||
"config_flow": true,
|
||||
"codeowners": ["@andrey-git", "@gjohansson-ST"],
|
||||
"iot_class": "cloud_polling",
|
||||
|
|
|
@ -84,25 +84,19 @@ class SensiboNumber(SensiboDeviceBaseEntity, NumberEntity):
|
|||
super().__init__(coordinator, device_id)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device_id}-{entity_description.key}"
|
||||
self._attr_name = (
|
||||
f"{coordinator.data.parsed[device_id]['name']} {entity_description.name}"
|
||||
)
|
||||
self._attr_name = f"{self.device_data.name} {entity_description.name}"
|
||||
|
||||
@property
|
||||
def value(self) -> float | None:
|
||||
"""Return the value from coordinator data."""
|
||||
return self.coordinator.data.parsed[self._device_id][
|
||||
self.entity_description.key
|
||||
]
|
||||
return getattr(self.device_data, self.entity_description.key)
|
||||
|
||||
async def async_set_value(self, value: float) -> None:
|
||||
"""Set value for calibration."""
|
||||
data = {self.entity_description.remote_key: value}
|
||||
result = await self.async_send_command("set_calibration", {"data": data})
|
||||
if result["status"] == "success":
|
||||
self.coordinator.data.parsed[self._device_id][
|
||||
self.entity_description.key
|
||||
] = value
|
||||
setattr(self.device_data, self.entity_description.key, value)
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
raise HomeAssistantError(f"Could not set calibration for device {self.name}")
|
||||
|
|
|
@ -58,7 +58,7 @@ async def async_setup_entry(
|
|||
SensiboSelect(coordinator, device_id, description)
|
||||
for device_id, device_data in coordinator.data.parsed.items()
|
||||
for description in SELECT_TYPES
|
||||
if description.key in device_data["full_features"]
|
||||
if description.key in device_data.full_features
|
||||
)
|
||||
|
||||
|
||||
|
@ -77,37 +77,35 @@ class SensiboSelect(SensiboDeviceBaseEntity, SelectEntity):
|
|||
super().__init__(coordinator, device_id)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device_id}-{entity_description.key}"
|
||||
self._attr_name = (
|
||||
f"{coordinator.data.parsed[device_id]['name']} {entity_description.name}"
|
||||
)
|
||||
self._attr_name = f"{self.device_data.name} {entity_description.name}"
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the current selected option."""
|
||||
return self.device_data[self.entity_description.remote_key]
|
||||
return getattr(self.device_data, self.entity_description.remote_key)
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return possible options."""
|
||||
return self.device_data[self.entity_description.remote_options] or []
|
||||
return getattr(self.device_data, self.entity_description.remote_options) or []
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set state to the selected option."""
|
||||
if self.entity_description.key not in self.device_data["active_features"]:
|
||||
if self.entity_description.key not in self.device_data.active_features:
|
||||
raise HomeAssistantError(
|
||||
f"Current mode {self.device_data['hvac_mode']} doesn't support setting {self.entity_description.name}"
|
||||
f"Current mode {self.device_data.hvac_mode} doesn't support setting {self.entity_description.name}"
|
||||
)
|
||||
|
||||
params = {
|
||||
"name": self.entity_description.key,
|
||||
"value": option,
|
||||
"ac_states": self.device_data["ac_states"],
|
||||
"ac_states": self.device_data.ac_states,
|
||||
"assumed_state": False,
|
||||
}
|
||||
result = await self.async_send_command("set_ac_state", params)
|
||||
|
||||
if result["result"]["status"] == "Success":
|
||||
self.device_data[self.entity_description.remote_key] = option
|
||||
setattr(self.device_data, self.entity_description.remote_key, option)
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pysensibo.model import MotionSensor, SensiboDevice
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
|
@ -25,7 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator
|
||||
from .coordinator import SensiboDataUpdateCoordinator
|
||||
from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity
|
||||
|
||||
|
||||
|
@ -40,7 +41,7 @@ class MotionBaseEntityDescriptionMixin:
|
|||
class DeviceBaseEntityDescriptionMixin:
|
||||
"""Mixin for required Sensibo base description keys."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
value_fn: Callable[[SensiboDevice], StateType]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -106,13 +107,13 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = (
|
|||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="PM2.5",
|
||||
icon="mdi:air-filter",
|
||||
value_fn=lambda data: data["pm25"],
|
||||
value_fn=lambda data: data.pm25,
|
||||
),
|
||||
SensiboDeviceSensorEntityDescription(
|
||||
key="pure_sensitivity",
|
||||
name="Pure Sensitivity",
|
||||
icon="mdi:air-filter",
|
||||
value_fn=lambda data: data["pure_sensitivity"],
|
||||
value_fn=lambda data: data.pure_sensitivity,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -129,15 +130,15 @@ async def async_setup_entry(
|
|||
entities.extend(
|
||||
SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description)
|
||||
for device_id, device_data in coordinator.data.parsed.items()
|
||||
for sensor_id, sensor_data in device_data["motion_sensors"].items()
|
||||
for sensor_id, sensor_data in device_data.motion_sensors.items()
|
||||
for description in MOTION_SENSOR_TYPES
|
||||
if device_data["motion_sensors"]
|
||||
if device_data.motion_sensors
|
||||
)
|
||||
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[description.key] is not None
|
||||
if getattr(device_data, description.key) is not None
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
@ -166,7 +167,7 @@ class SensiboMotionSensor(SensiboMotionBaseEntity, SensorEntity):
|
|||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{sensor_id}-{entity_description.key}"
|
||||
self._attr_name = (
|
||||
f"{self.device_data['name']} Motion Sensor {entity_description.name}"
|
||||
f"{self.device_data.name} Motion Sensor {entity_description.name}"
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -193,7 +194,7 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, SensorEntity):
|
|||
)
|
||||
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}"
|
||||
self._attr_name = f"{self.device_data.name} {entity_description.name}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
|
|
|
@ -1767,7 +1767,7 @@ pysaj==0.0.16
|
|||
pysdcp==1
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.8
|
||||
pysensibo==1.0.9
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
|
|
|
@ -1169,7 +1169,7 @@ pyrituals==0.0.6
|
|||
pyruckus==0.12
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.8
|
||||
pysensibo==1.0.9
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue