Improve data handling for Sensibo (#68419)

This commit is contained in:
G Johansson 2022-03-24 22:15:08 +01:00 committed by GitHub
parent 5fffe9b22f
commit d23d19f9e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 287 deletions

View file

@ -3,7 +3,8 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from pysensibo.model import MotionSensor, SensiboDevice
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -15,8 +16,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, LOGGER from .const import DOMAIN
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator from .coordinator import SensiboDataUpdateCoordinator
from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity
@ -31,7 +32,7 @@ class MotionBaseEntityDescriptionMixin:
class DeviceBaseEntityDescriptionMixin: class DeviceBaseEntityDescriptionMixin:
"""Mixin for required Sensibo base description keys.""" """Mixin for required Sensibo base description keys."""
value_fn: Callable[[dict[str, Any]], bool | None] value_fn: Callable[[SensiboDevice], bool | None]
@dataclass @dataclass
@ -79,7 +80,7 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
device_class=BinarySensorDeviceClass.MOTION, device_class=BinarySensorDeviceClass.MOTION,
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,
), ),
SensiboDeviceBinarySensorEntityDescription( SensiboDeviceBinarySensorEntityDescription(
key="update_available", key="update_available",
@ -87,7 +88,7 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
name="Update Available", name="Update Available",
icon="mdi:rocket-launch", 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] coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
entities: list[SensiboMotionSensor | SensiboDeviceSensor] = [] entities: list[SensiboMotionSensor | SensiboDeviceSensor] = []
LOGGER.debug("parsed data: %s", coordinator.data.parsed)
entities.extend( entities.extend(
SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description) SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description)
for device_id, device_data in coordinator.data.parsed.items() 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 for description in MOTION_SENSOR_TYPES
if device_data["motion_sensors"] if device_data.motion_sensors
) )
LOGGER.debug("start device %s", entities)
entities.extend( entities.extend(
SensiboDeviceSensor(coordinator, device_id, description) SensiboDeviceSensor(coordinator, device_id, description)
for description in DEVICE_SENSOR_TYPES for description in DEVICE_SENSOR_TYPES
for device_id, device_data in coordinator.data.parsed.items() 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) async_add_entities(entities)
@ -144,7 +142,7 @@ class SensiboMotionSensor(SensiboMotionBaseEntity, BinarySensorEntity):
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{sensor_id}-{entity_description.key}" self._attr_unique_id = f"{sensor_id}-{entity_description.key}"
self._attr_name = ( self._attr_name = (
f"{self.device_data['name']} Motion Sensor {entity_description.name}" f"{self.device_data.name} Motion Sensor {entity_description.name}"
) )
@property @property
@ -171,7 +169,7 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, BinarySensorEntity):
) )
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{device_id}-{entity_description.key}" 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 @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:

View file

@ -126,11 +126,9 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
"""Initiate SensiboClimate.""" """Initiate SensiboClimate."""
super().__init__(coordinator, device_id) super().__init__(coordinator, device_id)
self._attr_unique_id = 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 = ( self._attr_temperature_unit = (
TEMP_CELSIUS TEMP_CELSIUS if self.device_data.temp_unit == "C" else TEMP_FAHRENHEIT
if coordinator.data.parsed[device_id]["temp_unit"] == "C"
else TEMP_FAHRENHEIT
) )
self._attr_supported_features = self.get_features() self._attr_supported_features = self.get_features()
self._attr_precision = PRECISION_TENTHS self._attr_precision = PRECISION_TENTHS
@ -138,7 +136,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
def get_features(self) -> int: def get_features(self) -> int:
"""Get supported features.""" """Get supported features."""
features = 0 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: if key in FIELD_TO_FLAG:
features |= FIELD_TO_FLAG[key] features |= FIELD_TO_FLAG[key]
return features return features
@ -146,30 +144,27 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
@property @property
def current_humidity(self) -> int | None: def current_humidity(self) -> int | None:
"""Return the current humidity.""" """Return the current humidity."""
return self.coordinator.data.parsed[self.unique_id]["humidity"] return self.device_data.humidity
@property @property
def hvac_mode(self) -> str: def hvac_mode(self) -> str:
"""Return hvac operation.""" """Return hvac operation."""
return ( return (
SENSIBO_TO_HA[self.coordinator.data.parsed[self.unique_id]["hvac_mode"]] SENSIBO_TO_HA[self.device_data.hvac_mode]
if self.coordinator.data.parsed[self.unique_id]["on"] if self.device_data.device_on
else HVAC_MODE_OFF else HVAC_MODE_OFF
) )
@property @property
def hvac_modes(self) -> list[str]: def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes.""" """Return the list of available hvac operation modes."""
return [ return [SENSIBO_TO_HA[mode] for mode in self.device_data.hvac_modes]
SENSIBO_TO_HA[mode]
for mode in self.coordinator.data.parsed[self.unique_id]["hvac_modes"]
]
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
return convert_temperature( return convert_temperature(
self.coordinator.data.parsed[self.unique_id]["temp"], self.device_data.temp,
TEMP_CELSIUS, TEMP_CELSIUS,
self.temperature_unit, self.temperature_unit,
) )
@ -177,57 +172,51 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
@property @property
def target_temperature(self) -> float | None: def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self.coordinator.data.parsed[self.unique_id]["target_temp"] return self.device_data.target_temp
@property @property
def target_temperature_step(self) -> float | None: def target_temperature_step(self) -> float | None:
"""Return the supported step of target temperature.""" """Return the supported step of target temperature."""
return self.coordinator.data.parsed[self.unique_id]["temp_step"] return self.device_data.temp_step
@property @property
def fan_mode(self) -> str | None: def fan_mode(self) -> str | None:
"""Return the fan setting.""" """Return the fan setting."""
return self.coordinator.data.parsed[self.unique_id]["fan_mode"] return self.device_data.fan_mode
@property @property
def fan_modes(self) -> list[str] | None: def fan_modes(self) -> list[str] | None:
"""Return the list of available fan modes.""" """Return the list of available fan modes."""
return self.coordinator.data.parsed[self.unique_id]["fan_modes"] return self.device_data.fan_modes
@property @property
def swing_mode(self) -> str | None: def swing_mode(self) -> str | None:
"""Return the swing setting.""" """Return the swing setting."""
return self.coordinator.data.parsed[self.unique_id]["swing_mode"] return self.device_data.swing_mode
@property @property
def swing_modes(self) -> list[str] | None: def swing_modes(self) -> list[str] | None:
"""Return the list of available swing modes.""" """Return the list of available swing modes."""
return self.coordinator.data.parsed[self.unique_id]["swing_modes"] return self.device_data.swing_modes
@property @property
def min_temp(self) -> float: def min_temp(self) -> float:
"""Return the minimum temperature.""" """Return the minimum temperature."""
return self.coordinator.data.parsed[self.unique_id]["temp_list"][0] return self.device_data.temp_list[0]
@property @property
def max_temp(self) -> float: def max_temp(self) -> float:
"""Return the maximum temperature.""" """Return the maximum temperature."""
return self.coordinator.data.parsed[self.unique_id]["temp_list"][-1] return self.device_data.temp_list[-1]
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
return ( return self.device_data.available and super().available
self.coordinator.data.parsed[self.unique_id]["available"]
and super().available
)
async def async_set_temperature(self, **kwargs) -> None: async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature.""" """Set new target temperature."""
if ( if "targetTemperature" not in self.device_data.active_features:
"targetTemperature"
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
):
raise HomeAssistantError( raise HomeAssistantError(
"Current mode doesn't support setting Target Temperature" "Current mode doesn't support setting Target Temperature"
) )
@ -238,23 +227,13 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
if temperature == self.target_temperature: if temperature == self.target_temperature:
return 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. # Requested temperature is not supported.
if ( if temperature > self.device_data.temp_list[-1]:
temperature temperature = self.device_data.temp_list[-1]
> self.coordinator.data.parsed[self.unique_id]["temp_list"][-1]
):
temperature = self.coordinator.data.parsed[self.unique_id]["temp_list"][
-1
]
elif ( elif temperature < self.device_data.temp_list[0]:
temperature temperature = self.device_data.temp_list[0]
< self.coordinator.data.parsed[self.unique_id]["temp_list"][0]
):
temperature = self.coordinator.data.parsed[self.unique_id]["temp_list"][
0
]
else: else:
return return
@ -263,10 +242,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
async def async_set_fan_mode(self, fan_mode: str) -> None: async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode.""" """Set new target fan mode."""
if ( if "fanLevel" not in self.device_data.active_features:
"fanLevel"
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
):
raise HomeAssistantError("Current mode doesn't support setting Fanlevel") raise HomeAssistantError("Current mode doesn't support setting Fanlevel")
await self._async_set_ac_state_property("fanLevel", fan_mode) await self._async_set_ac_state_property("fanLevel", fan_mode)
@ -278,7 +254,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
return return
# Turn on if not currently on. # 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("on", True)
await self._async_set_ac_state_property("mode", HA_TO_SENSIBO[hvac_mode]) 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: async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation.""" """Set new target swing operation."""
if ( if "swing" not in self.device_data.active_features:
"swing"
not in self.coordinator.data.parsed[self.unique_id]["active_features"]
):
raise HomeAssistantError("Current mode doesn't support setting Swing") raise HomeAssistantError("Current mode doesn't support setting Swing")
await self._async_set_ac_state_property("swing", swing_mode) await self._async_set_ac_state_property("swing", swing_mode)
@ -309,13 +282,13 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
params = { params = {
"name": name, "name": name,
"value": value, "value": value,
"ac_states": self.coordinator.data.parsed[self.unique_id]["ac_states"], "ac_states": self.device_data.ac_states,
"assumed_state": assumed_state, "assumed_state": assumed_state,
} }
result = await self.async_send_command("set_ac_state", params) result = await self.async_send_command("set_ac_state", params)
if result["result"]["status"] == "Success": 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() self.async_write_ha_state()
return return

View file

@ -1,12 +1,11 @@
"""DataUpdateCoordinator for the Sensibo integration.""" """DataUpdateCoordinator for the Sensibo integration."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import Any
from pysensibo import SensiboClient from pysensibo import SensiboClient
from pysensibo.exceptions import AuthenticationError, SensiboError from pysensibo.exceptions import AuthenticationError, SensiboError
from pysensibo.model import SensiboData
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY 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 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): class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
"""A Sensibo Data Update Coordinator.""" """A Sensibo Data Update Coordinator."""
@ -67,156 +39,13 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
async def _async_update_data(self) -> SensiboData: async def _async_update_data(self) -> SensiboData:
"""Fetch data from Sensibo.""" """Fetch data from Sensibo."""
devices = []
try: try:
data = await self.client.async_get_devices() data = await self.client.async_get_devices_data()
for dev in data["result"]:
devices.append(dev)
except AuthenticationError as error: except AuthenticationError as error:
raise ConfigEntryAuthFailed from error raise ConfigEntryAuthFailed from error
except SensiboError as error: except SensiboError as error:
raise UpdateFailed from error raise UpdateFailed from error
if not devices: if not data.raw:
raise UpdateFailed("No devices found") raise UpdateFailed("No devices found")
return data
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)

View file

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any from typing import Any
import async_timeout import async_timeout
from pysensibo.model import MotionSensor, SensiboDevice
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC 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 homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator from .coordinator import SensiboDataUpdateCoordinator
class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]): class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]):
@ -28,7 +29,7 @@ class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]):
self._client = coordinator.client self._client = coordinator.client
@property @property
def device_data(self) -> dict[str, Any]: def device_data(self) -> SensiboDevice:
"""Return data for device.""" """Return data for device."""
return self.coordinator.data.parsed[self._device_id] return self.coordinator.data.parsed[self._device_id]
@ -44,15 +45,15 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity):
"""Initiate Sensibo Number.""" """Initiate Sensibo Number."""
super().__init__(coordinator, device_id) super().__init__(coordinator, device_id)
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.device_data["id"])}, identifiers={(DOMAIN, self.device_data.id)},
name=self.device_data["name"], name=self.device_data.name,
connections={(CONNECTION_NETWORK_MAC, self.device_data["mac"])}, connections={(CONNECTION_NETWORK_MAC, self.device_data.mac)},
manufacturer="Sensibo", manufacturer="Sensibo",
configuration_url="https://home.sensibo.com/", configuration_url="https://home.sensibo.com/",
model=self.device_data["model"], model=self.device_data.model,
sw_version=self.device_data["fw_ver"], sw_version=self.device_data.fw_ver,
hw_version=self.device_data["fw_type"], hw_version=self.device_data.fw_type,
suggested_area=self.device_data["name"], suggested_area=self.device_data.name,
) )
async def async_send_command( async def async_send_command(
@ -108,7 +109,7 @@ class SensiboMotionBaseEntity(SensiboBaseEntity):
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, sensor_id)}, 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), via_device=(DOMAIN, device_id),
manufacturer="Sensibo", manufacturer="Sensibo",
configuration_url="https://home.sensibo.com/", configuration_url="https://home.sensibo.com/",
@ -120,4 +121,4 @@ class SensiboMotionBaseEntity(SensiboBaseEntity):
@property @property
def sensor_data(self) -> MotionSensor: def sensor_data(self) -> MotionSensor:
"""Return data for device.""" """Return data for device."""
return self.device_data["motion_sensors"][self._sensor_id] return self.device_data.motion_sensors[self._sensor_id]

View file

@ -2,7 +2,7 @@
"domain": "sensibo", "domain": "sensibo",
"name": "Sensibo", "name": "Sensibo",
"documentation": "https://www.home-assistant.io/integrations/sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo",
"requirements": ["pysensibo==1.0.8"], "requirements": ["pysensibo==1.0.9"],
"config_flow": true, "config_flow": true,
"codeowners": ["@andrey-git", "@gjohansson-ST"], "codeowners": ["@andrey-git", "@gjohansson-ST"],
"iot_class": "cloud_polling", "iot_class": "cloud_polling",

View file

@ -84,25 +84,19 @@ class SensiboNumber(SensiboDeviceBaseEntity, NumberEntity):
super().__init__(coordinator, device_id) super().__init__(coordinator, device_id)
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{device_id}-{entity_description.key}" self._attr_unique_id = f"{device_id}-{entity_description.key}"
self._attr_name = ( self._attr_name = f"{self.device_data.name} {entity_description.name}"
f"{coordinator.data.parsed[device_id]['name']} {entity_description.name}"
)
@property @property
def value(self) -> float | None: def value(self) -> float | None:
"""Return the value from coordinator data.""" """Return the value from coordinator data."""
return self.coordinator.data.parsed[self._device_id][ return getattr(self.device_data, self.entity_description.key)
self.entity_description.key
]
async def async_set_value(self, value: float) -> None: async def async_set_value(self, value: float) -> None:
"""Set value for calibration.""" """Set value for calibration."""
data = {self.entity_description.remote_key: value} data = {self.entity_description.remote_key: value}
result = await self.async_send_command("set_calibration", {"data": data}) result = await self.async_send_command("set_calibration", {"data": data})
if result["status"] == "success": if result["status"] == "success":
self.coordinator.data.parsed[self._device_id][ setattr(self.device_data, self.entity_description.key, value)
self.entity_description.key
] = value
self.async_write_ha_state() self.async_write_ha_state()
return return
raise HomeAssistantError(f"Could not set calibration for device {self.name}") raise HomeAssistantError(f"Could not set calibration for device {self.name}")

View file

@ -58,7 +58,7 @@ async def async_setup_entry(
SensiboSelect(coordinator, device_id, description) SensiboSelect(coordinator, device_id, description)
for device_id, device_data in coordinator.data.parsed.items() for device_id, device_data in coordinator.data.parsed.items()
for description in SELECT_TYPES 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) super().__init__(coordinator, device_id)
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{device_id}-{entity_description.key}" self._attr_unique_id = f"{device_id}-{entity_description.key}"
self._attr_name = ( self._attr_name = f"{self.device_data.name} {entity_description.name}"
f"{coordinator.data.parsed[device_id]['name']} {entity_description.name}"
)
@property @property
def current_option(self) -> str | None: def current_option(self) -> str | None:
"""Return the current selected option.""" """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 @property
def options(self) -> list[str]: def options(self) -> list[str]:
"""Return possible options.""" """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: async def async_select_option(self, option: str) -> None:
"""Set state to the selected option.""" """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( 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 = { params = {
"name": self.entity_description.key, "name": self.entity_description.key,
"value": option, "value": option,
"ac_states": self.device_data["ac_states"], "ac_states": self.device_data.ac_states,
"assumed_state": False, "assumed_state": False,
} }
result = await self.async_send_command("set_ac_state", params) result = await self.async_send_command("set_ac_state", params)
if result["result"]["status"] == "Success": 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() self.async_write_ha_state()
return return

View file

@ -3,7 +3,8 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from pysensibo.model import MotionSensor, SensiboDevice
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -25,7 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from .const import DOMAIN from .const import DOMAIN
from .coordinator import MotionSensor, SensiboDataUpdateCoordinator from .coordinator import SensiboDataUpdateCoordinator
from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity
@ -40,7 +41,7 @@ class MotionBaseEntityDescriptionMixin:
class DeviceBaseEntityDescriptionMixin: class DeviceBaseEntityDescriptionMixin:
"""Mixin for required Sensibo base description keys.""" """Mixin for required Sensibo base description keys."""
value_fn: Callable[[dict[str, Any]], StateType] value_fn: Callable[[SensiboDevice], StateType]
@dataclass @dataclass
@ -106,13 +107,13 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="PM2.5", name="PM2.5",
icon="mdi:air-filter", icon="mdi:air-filter",
value_fn=lambda data: data["pm25"], value_fn=lambda data: data.pm25,
), ),
SensiboDeviceSensorEntityDescription( SensiboDeviceSensorEntityDescription(
key="pure_sensitivity", key="pure_sensitivity",
name="Pure Sensitivity", name="Pure Sensitivity",
icon="mdi:air-filter", 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( entities.extend(
SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description) SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description)
for device_id, device_data in coordinator.data.parsed.items() 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 for description in MOTION_SENSOR_TYPES
if device_data["motion_sensors"] if device_data.motion_sensors
) )
entities.extend( entities.extend(
SensiboDeviceSensor(coordinator, device_id, description) SensiboDeviceSensor(coordinator, device_id, description)
for device_id, device_data in coordinator.data.parsed.items() for device_id, device_data in coordinator.data.parsed.items()
for description in DEVICE_SENSOR_TYPES 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) async_add_entities(entities)
@ -166,7 +167,7 @@ class SensiboMotionSensor(SensiboMotionBaseEntity, SensorEntity):
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{sensor_id}-{entity_description.key}" self._attr_unique_id = f"{sensor_id}-{entity_description.key}"
self._attr_name = ( self._attr_name = (
f"{self.device_data['name']} Motion Sensor {entity_description.name}" f"{self.device_data.name} Motion Sensor {entity_description.name}"
) )
@property @property
@ -193,7 +194,7 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, SensorEntity):
) )
self.entity_description = entity_description self.entity_description = entity_description
self._attr_unique_id = f"{device_id}-{entity_description.key}" 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 @property
def native_value(self) -> StateType: def native_value(self) -> StateType:

View file

@ -1767,7 +1767,7 @@ pysaj==0.0.16
pysdcp==1 pysdcp==1
# homeassistant.components.sensibo # homeassistant.components.sensibo
pysensibo==1.0.8 pysensibo==1.0.9
# homeassistant.components.serial # homeassistant.components.serial
# homeassistant.components.zha # homeassistant.components.zha

View file

@ -1169,7 +1169,7 @@ pyrituals==0.0.6
pyruckus==0.12 pyruckus==0.12
# homeassistant.components.sensibo # homeassistant.components.sensibo
pysensibo==1.0.8 pysensibo==1.0.9
# homeassistant.components.serial # homeassistant.components.serial
# homeassistant.components.zha # homeassistant.components.zha