Improve dyson code (#45172)

* Improve air_quality

* Improve climate

* Improve sensor

* Improve vacuum

* Improve fan

* Fix pylint

* Improve on_message

* Change unique ID back

* Remove unused attribute

* Remove redundant current_humidity

* Merge current_temperature

* Rename fan device to fan entity

* Fix filter life sensors

* Remove unneeded context switch

* Remove entity_type

* Fix pylint

* Add comment on humidity check
This commit is contained in:
Xiaonan Shen 2021-01-17 17:24:26 +08:00 committed by GitHub
parent 28a611f3da
commit a42d43d054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 179 additions and 310 deletions

View file

@ -7,6 +7,7 @@ import voluptuous as vol
from homeassistant.const import CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.const import CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -108,3 +109,45 @@ def setup(hass, config):
discovery.load_platform(hass, platform, DOMAIN, {}, config) discovery.load_platform(hass, platform, DOMAIN, {}, config)
return True return True
class DysonEntity(Entity):
"""Representation of a Dyson entity."""
def __init__(self, device, state_type):
"""Initialize the entity."""
self._device = device
self._state_type = state_type
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message_filter)
def on_message_filter(self, message):
"""Filter new messages received."""
if self._state_type is None or isinstance(message, self._state_type):
_LOGGER.debug(
"Message received for device %s : %s",
self.name,
message,
)
self.on_message(message)
def on_message(self, message):
"""Handle new messages received."""
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the Dyson sensor."""
return self._device.name
@property
def unique_id(self):
"""Return the sensor's unique id."""
return self._device.serial

View file

@ -4,9 +4,9 @@ import logging
from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool import DysonPureCool
from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State
from homeassistant.components.air_quality import DOMAIN, AirQualityEntity from homeassistant.components.air_quality import AirQualityEntity
from . import DYSON_DEVICES from . import DYSON_DEVICES, DysonEntity
ATTRIBUTION = "Dyson purifier air quality sensor" ATTRIBUTION = "Dyson purifier air quality sensor"
@ -39,41 +39,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(hass.data[DYSON_AIQ_DEVICES]) add_entities(hass.data[DYSON_AIQ_DEVICES])
class DysonAirSensor(AirQualityEntity): class DysonAirSensor(DysonEntity, AirQualityEntity):
"""Representation of a generic Dyson air quality sensor.""" """Representation of a generic Dyson air quality sensor."""
def __init__(self, device): def __init__(self, device):
"""Create a new generic air quality Dyson sensor.""" """Create a new generic air quality Dyson sensor."""
self._device = device super().__init__(device, DysonEnvironmentalSensorV2State)
self._old_value = None self._old_value = None
self._name = device.name
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message): def on_message(self, message):
"""Handle new messages which are received from the fan.""" """Handle new messages which are received from the fan."""
_LOGGER.debug(
"%s: Message received for %s device: %s", DOMAIN, self.name, message
)
if ( if (
self._old_value is None self._old_value is None
or self._old_value != self._device.environmental_state or self._old_value != self._device.environmental_state
) and isinstance(message, DysonEnvironmentalSensorV2State): ):
self._old_value = self._device.environmental_state self._old_value = self._device.environmental_state
self.schedule_update_ha_state() self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the Dyson sensor."""
return self._name
@property @property
def attribution(self): def attribution(self):
"""Return the attribution.""" """Return the attribution."""
@ -117,11 +99,6 @@ class DysonAirSensor(AirQualityEntity):
return int(self._device.environmental_state.volatile_organic_compounds) return int(self._device.environmental_state.volatile_organic_compounds)
return None return None
@property
def unique_id(self):
"""Return the sensor's unique id."""
return self._device.serial
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device state attributes.""" """Return the device state attributes."""

View file

@ -37,13 +37,13 @@ from homeassistant.components.climate.const import (
) )
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DYSON_DEVICES from . import DYSON_DEVICES, DysonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE] SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE]
SUPPORT_FAN_PCOOL = [FAN_OFF, FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH] SUPPORT_FAN_PCOOL = [FAN_OFF, FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
SUPPORT_HVAG = [HVAC_MODE_COOL, HVAC_MODE_HEAT] SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT]
SUPPORT_HVAC_PCOOL = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] SUPPORT_HVAC_PCOOL = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
@ -88,41 +88,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(new_entities) add_entities(new_entities)
class DysonPureHotCoolLinkEntity(ClimateEntity): class DysonClimateEntity(DysonEntity, ClimateEntity):
"""Representation of a Dyson climate fan.""" """Representation of a Dyson climate fan."""
def __init__(self, device):
"""Initialize the fan."""
self._device = device
self._current_temp = None
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message):
"""Call when new messages received from the climate."""
if isinstance(message, DysonPureHotCoolState):
_LOGGER.debug(
"Message received for climate device %s : %s", self.name, message
)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS return SUPPORT_FLAGS
@property
def name(self):
"""Return the display name of this climate."""
return self._device.name
@property @property
def temperature_unit(self): def temperature_unit(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
@ -131,11 +104,13 @@ class DysonPureHotCoolLinkEntity(ClimateEntity):
@property @property
def current_temperature(self): def current_temperature(self):
"""Return the current temperature.""" """Return the current temperature."""
if self._device.environmental_state: if (
self._device.environmental_state
and self._device.environmental_state.temperature
):
temperature_kelvin = self._device.environmental_state.temperature temperature_kelvin = self._device.environmental_state.temperature
if temperature_kelvin != 0: return float("{:.1f}".format(temperature_kelvin - 273))
self._current_temp = float(f"{(temperature_kelvin - 273):.1f}") return None
return self._current_temp
@property @property
def target_temperature(self): def target_temperature(self):
@ -146,12 +121,49 @@ class DysonPureHotCoolLinkEntity(ClimateEntity):
@property @property
def current_humidity(self): def current_humidity(self):
"""Return the current humidity.""" """Return the current humidity."""
if self._device.environmental_state: # Humidity equaling to 0 means invalid value so we don't check for None here
if self._device.environmental_state.humidity == 0: # https://github.com/home-assistant/core/pull/45172#discussion_r559069756
return None if (
self._device.environmental_state
and self._device.environmental_state.humidity
):
return self._device.environmental_state.humidity return self._device.environmental_state.humidity
return None return None
@property
def min_temp(self):
"""Return the minimum temperature."""
return 1
@property
def max_temp(self):
"""Return the maximum temperature."""
return 37
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
if target_temp is None:
_LOGGER.error("Missing target temperature %s", kwargs)
return
target_temp = int(target_temp)
_LOGGER.debug("Set %s temperature %s", self.name, target_temp)
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
self.set_heat_target(HeatTarget.celsius(target_temp))
def set_heat_target(self, heat_target):
"""Set heating target temperature."""
class DysonPureHotCoolLinkEntity(DysonClimateEntity):
"""Representation of a Dyson climate fan."""
def __init__(self, device):
"""Initialize the fan."""
super().__init__(device, DysonPureHotCoolState)
@property @property
def hvac_mode(self): def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode. """Return hvac operation ie. heat, cool mode.
@ -168,7 +180,7 @@ class DysonPureHotCoolLinkEntity(ClimateEntity):
Need to be a subset of HVAC_MODES. Need to be a subset of HVAC_MODES.
""" """
return SUPPORT_HVAG return SUPPORT_HVAC
@property @property
def hvac_action(self): def hvac_action(self):
@ -194,18 +206,10 @@ class DysonPureHotCoolLinkEntity(ClimateEntity):
"""Return the list of available fan modes.""" """Return the list of available fan modes."""
return SUPPORT_FAN return SUPPORT_FAN
def set_temperature(self, **kwargs): def set_heat_target(self, heat_target):
"""Set new target temperature.""" """Set heating target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
if target_temp is None:
return
target_temp = int(target_temp)
_LOGGER.debug("Set %s temperature %s", self.name, target_temp)
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
self._device.set_configuration( self._device.set_configuration(
heat_target=HeatTarget.celsius(target_temp), heat_mode=HeatMode.HEAT_ON heat_target=heat_target, heat_mode=HeatMode.HEAT_ON
) )
def set_fan_mode(self, fan_mode): def set_fan_mode(self, fan_mode):
@ -224,78 +228,13 @@ class DysonPureHotCoolLinkEntity(ClimateEntity):
elif hvac_mode == HVAC_MODE_COOL: elif hvac_mode == HVAC_MODE_COOL:
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF) self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
@property
def min_temp(self):
"""Return the minimum temperature."""
return 1
@property class DysonPureHotCoolEntity(DysonClimateEntity):
def max_temp(self):
"""Return the maximum temperature."""
return 37
class DysonPureHotCoolEntity(ClimateEntity):
"""Representation of a Dyson climate hot+cool fan.""" """Representation of a Dyson climate hot+cool fan."""
def __init__(self, device): def __init__(self, device):
"""Initialize the fan.""" """Initialize the fan."""
self._device = device super().__init__(device, DysonPureHotCoolV2State)
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message):
"""Call when new messages received from the climate device."""
if isinstance(message, DysonPureHotCoolV2State):
_LOGGER.debug(
"Message received for climate device %s : %s", self.name, message
)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the display name of this climate."""
return self._device.name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self._device.environmental_state.temperature is not None:
temperature_kelvin = self._device.environmental_state.temperature
if temperature_kelvin != 0:
return float("{:.1f}".format(temperature_kelvin - 273))
return None
@property
def target_temperature(self):
"""Return the target temperature."""
heat_target = int(self._device.state.heat_target) / 10
return int(heat_target - 273)
@property
def current_humidity(self):
"""Return the current humidity."""
if self._device.environmental_state.humidity is not None:
if self._device.environmental_state.humidity != 0:
return self._device.environmental_state.humidity
return None
@property @property
def hvac_mode(self): def hvac_mode(self):
@ -347,18 +286,9 @@ class DysonPureHotCoolEntity(ClimateEntity):
"""Return the list of available fan modes.""" """Return the list of available fan modes."""
return SUPPORT_FAN_PCOOL return SUPPORT_FAN_PCOOL
def set_temperature(self, **kwargs): def set_heat_target(self, heat_target):
"""Set new target temperature.""" """Set heating target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE) self._device.set_heat_target(heat_target)
if target_temp is None:
_LOGGER.error("Missing target temperature %s", kwargs)
return
target_temp = int(target_temp)
_LOGGER.debug("Set %s temperature %s", self.name, target_temp)
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
self._device.set_heat_target(HeatTarget.celsius(target_temp))
def set_fan_mode(self, fan_mode): def set_fan_mode(self, fan_mode):
"""Set new fan mode.""" """Set new fan mode."""
@ -385,13 +315,3 @@ class DysonPureHotCoolEntity(ClimateEntity):
self._device.enable_heat_mode() self._device.enable_heat_mode()
elif hvac_mode == HVAC_MODE_COOL: elif hvac_mode == HVAC_MODE_COOL:
self._device.disable_heat_mode() self._device.disable_heat_mode()
@property
def min_temp(self):
"""Return the minimum temperature."""
return 1
@property
def max_temp(self):
"""Return the maximum temperature."""
return 37

View file

@ -19,7 +19,7 @@ from homeassistant.components.fan import (
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DYSON_DEVICES from . import DYSON_DEVICES, DysonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -105,10 +105,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if device.serial not in device_serials: if device.serial not in device_serials:
if isinstance(device, DysonPureCool): if isinstance(device, DysonPureCool):
has_purecool_devices = True has_purecool_devices = True
dyson_entity = DysonPureCoolDevice(device) dyson_entity = DysonPureCoolEntity(device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity) hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
elif isinstance(device, DysonPureCoolLink): elif isinstance(device, DysonPureCoolLink):
dyson_entity = DysonPureCoolLinkDevice(hass, device) dyson_entity = DysonPureCoolLinkEntity(device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity) hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
add_entities(hass.data[DYSON_FAN_DEVICES]) add_entities(hass.data[DYSON_FAN_DEVICES])
@ -179,35 +179,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
) )
class DysonPureCoolLinkDevice(FanEntity): class DysonFanEntity(DysonEntity, FanEntity):
"""Representation of a Dyson fan.""" """Representation of a Dyson fan."""
def __init__(self, hass, device): @property
def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
class DysonPureCoolLinkEntity(DysonFanEntity):
"""Representation of a Dyson fan."""
def __init__(self, device):
"""Initialize the fan.""" """Initialize the fan."""
_LOGGER.debug("Creating device %s", device.name) super().__init__(device, DysonPureCoolState)
self.hass = hass
self._device = device
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message):
"""Call when new messages received from the fan."""
if isinstance(message, DysonPureCoolState):
_LOGGER.debug("Message received for fan device %s: %s", self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this fan."""
return self._device.name
def set_speed(self, speed: str) -> None: def set_speed(self, speed: str) -> None:
"""Set the speed of the fan. Never called ??.""" """Set the speed of the fan. Never called ??."""
@ -274,11 +260,6 @@ class DysonPureCoolLinkDevice(FanEntity):
"""Return direction of the fan [forward, reverse].""" """Return direction of the fan [forward, reverse]."""
return None return None
@property
def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
def set_night_mode(self, night_mode: bool) -> None: def set_night_mode(self, night_mode: bool) -> None:
"""Turn fan in night mode.""" """Turn fan in night mode."""
_LOGGER.debug("Set %s night mode %s", self.name, night_mode) _LOGGER.debug("Set %s night mode %s", self.name, night_mode)
@ -330,32 +311,12 @@ class DysonPureCoolLinkDevice(FanEntity):
return {ATTR_NIGHT_MODE: self.night_mode, ATTR_AUTO_MODE: self.auto_mode} return {ATTR_NIGHT_MODE: self.night_mode, ATTR_AUTO_MODE: self.auto_mode}
class DysonPureCoolDevice(FanEntity): class DysonPureCoolEntity(DysonFanEntity):
"""Representation of a Dyson Purecool (TP04/DP04) fan.""" """Representation of a Dyson Purecool (TP04/DP04) fan."""
def __init__(self, device): def __init__(self, device):
"""Initialize the fan.""" """Initialize the fan."""
self._device = device super().__init__(device, DysonPureCoolV2State)
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message):
"""Call when new messages received from the fan."""
if isinstance(message, DysonPureCoolV2State):
_LOGGER.debug("Message received for fan device %s: %s", self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this fan."""
return self._device.name
def turn_on(self, speed: str = None, **kwargs) -> None: def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan.""" """Turn on the fan."""
@ -455,6 +416,7 @@ class DysonPureCoolDevice(FanEntity):
"""Return true if the entity is on.""" """Return true if the entity is on."""
if self._device.state: if self._device.state:
return self._device.state.fan_power == "ON" return self._device.state.fan_power == "ON"
return False
@property @property
def speed(self): def speed(self):
@ -483,11 +445,6 @@ class DysonPureCoolDevice(FanEntity):
return self._device.state.speed return self._device.state.speed
return int(self._device.state.speed) return int(self._device.state.speed)
@property
def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
@property @property
def auto_mode(self): def auto_mode(self):
"""Return Auto mode.""" """Return Auto mode."""

View file

@ -7,7 +7,7 @@ from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
from homeassistant.const import PERCENTAGE, STATE_OFF, TEMP_CELSIUS, TIME_HOURS from homeassistant.const import PERCENTAGE, STATE_OFF, TEMP_CELSIUS, TIME_HOURS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from . import DYSON_DEVICES from . import DYSON_DEVICES, DysonEntity
SENSOR_UNITS = { SENSOR_UNITS = {
"air_quality": None, "air_quality": None,
@ -15,6 +15,7 @@ SENSOR_UNITS = {
"filter_life": TIME_HOURS, "filter_life": TIME_HOURS,
"carbon_filter_state": PERCENTAGE, "carbon_filter_state": PERCENTAGE,
"hepa_filter_state": PERCENTAGE, "hepa_filter_state": PERCENTAGE,
"combi_filter_state": PERCENTAGE,
"humidity": PERCENTAGE, "humidity": PERCENTAGE,
} }
@ -24,10 +25,22 @@ SENSOR_ICONS = {
"filter_life": "mdi:filter-outline", "filter_life": "mdi:filter-outline",
"carbon_filter_state": "mdi:filter-outline", "carbon_filter_state": "mdi:filter-outline",
"hepa_filter_state": "mdi:filter-outline", "hepa_filter_state": "mdi:filter-outline",
"combi_filter_state": "mdi:filter-outline",
"humidity": "mdi:water-percent", "humidity": "mdi:water-percent",
"temperature": "mdi:thermometer", "temperature": "mdi:thermometer",
} }
SENSOR_NAMES = {
"air_quality": "AQI",
"dust": "Dust",
"filter_life": "Filter Life",
"humidity": "Humidity",
"carbon_filter_state": "Carbon Filter Remaining Life",
"hepa_filter_state": "HEPA Filter Remaining Life",
"combi_filter_state": "Combi Filter Remaining Life",
"temperature": "Temperature",
}
DYSON_SENSOR_DEVICES = "dyson_sensor_devices" DYSON_SENSOR_DEVICES = "dyson_sensor_devices"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -57,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
# It's reported with the HEPA state, while the Carbon state is set to INValid. # It's reported with the HEPA state, while the Carbon state is set to INValid.
if device.state and device.state.carbon_filter_state == "INV": if device.state and device.state.carbon_filter_state == "INV":
if f"{device.serial}-hepa_filter_state" not in device_ids: if f"{device.serial}-hepa_filter_state" not in device_ids:
new_entities.append(DysonHepaFilterLifeSensor(device, "Combi")) new_entities.append(DysonHepaFilterLifeSensor(device, "combi"))
else: else:
if f"{device.serial}-hepa_filter_state" not in device_ids: if f"{device.serial}-hepa_filter_state" not in device_ids:
new_entities.append(DysonHepaFilterLifeSensor(device)) new_entities.append(DysonHepaFilterLifeSensor(device))
@ -77,37 +90,31 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(devices) add_entities(devices)
class DysonSensor(Entity): class DysonSensor(DysonEntity, Entity):
"""Representation of a generic Dyson sensor.""" """Representation of a generic Dyson sensor."""
def __init__(self, device, sensor_type): def __init__(self, device, sensor_type):
"""Create a new generic Dyson sensor.""" """Create a new generic Dyson sensor."""
self._device = device super().__init__(device, None)
self._old_value = None self._old_value = None
self._name = None
self._sensor_type = sensor_type self._sensor_type = sensor_type
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message): def on_message(self, message):
"""Handle new messages which are received from the fan.""" """Handle new messages which are received from the fan."""
# Prevent refreshing if not needed # Prevent refreshing if not needed
if self._old_value is None or self._old_value != self.state: if self._old_value is None or self._old_value != self.state:
_LOGGER.debug("Message received for %s device: %s", self.name, message)
self._old_value = self.state self._old_value = self.state
self.schedule_update_ha_state() self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property @property
def name(self): def name(self):
"""Return the name of the Dyson sensor name.""" """Return the name of the Dyson sensor name."""
return self._name return f"{super().name} {SENSOR_NAMES[self._sensor_type]}"
@property
def unique_id(self):
"""Return the sensor's unique id."""
return f"{self._device.serial}-{self._sensor_type}"
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
@ -119,11 +126,6 @@ class DysonSensor(Entity):
"""Return the icon for this sensor.""" """Return the icon for this sensor."""
return SENSOR_ICONS[self._sensor_type] return SENSOR_ICONS[self._sensor_type]
@property
def unique_id(self):
"""Return the sensor's unique id."""
return f"{self._device.serial}-{self._sensor_type}"
class DysonFilterLifeSensor(DysonSensor): class DysonFilterLifeSensor(DysonSensor):
"""Representation of Dyson Filter Life sensor (in hours).""" """Representation of Dyson Filter Life sensor (in hours)."""
@ -131,7 +133,6 @@ class DysonFilterLifeSensor(DysonSensor):
def __init__(self, device): def __init__(self, device):
"""Create a new Dyson Filter Life sensor.""" """Create a new Dyson Filter Life sensor."""
super().__init__(device, "filter_life") super().__init__(device, "filter_life")
self._name = f"{self._device.name} Filter Life"
@property @property
def state(self): def state(self):
@ -147,7 +148,6 @@ class DysonCarbonFilterLifeSensor(DysonSensor):
def __init__(self, device): def __init__(self, device):
"""Create a new Dyson Carbon Filter Life sensor.""" """Create a new Dyson Carbon Filter Life sensor."""
super().__init__(device, "carbon_filter_state") super().__init__(device, "carbon_filter_state")
self._name = f"{self._device.name} Carbon Filter Remaining Life"
@property @property
def state(self): def state(self):
@ -160,10 +160,9 @@ class DysonCarbonFilterLifeSensor(DysonSensor):
class DysonHepaFilterLifeSensor(DysonSensor): class DysonHepaFilterLifeSensor(DysonSensor):
"""Representation of Dyson HEPA (or Combi) Filter Life sensor (in percent).""" """Representation of Dyson HEPA (or Combi) Filter Life sensor (in percent)."""
def __init__(self, device, filter_type="HEPA"): def __init__(self, device, filter_type="hepa"):
"""Create a new Dyson Filter Life sensor.""" """Create a new Dyson Filter Life sensor."""
super().__init__(device, "hepa_filter_state") super().__init__(device, f"{filter_type}_filter_state")
self._name = f"{self._device.name} {filter_type} Filter Remaining Life"
@property @property
def state(self): def state(self):
@ -179,7 +178,6 @@ class DysonDustSensor(DysonSensor):
def __init__(self, device): def __init__(self, device):
"""Create a new Dyson Dust sensor.""" """Create a new Dyson Dust sensor."""
super().__init__(device, "dust") super().__init__(device, "dust")
self._name = f"{self._device.name} Dust"
@property @property
def state(self): def state(self):
@ -195,7 +193,6 @@ class DysonHumiditySensor(DysonSensor):
def __init__(self, device): def __init__(self, device):
"""Create a new Dyson Humidity sensor.""" """Create a new Dyson Humidity sensor."""
super().__init__(device, "humidity") super().__init__(device, "humidity")
self._name = f"{self._device.name} Humidity"
@property @property
def state(self): def state(self):
@ -213,7 +210,6 @@ class DysonTemperatureSensor(DysonSensor):
def __init__(self, device, unit): def __init__(self, device, unit):
"""Create a new Dyson Temperature sensor.""" """Create a new Dyson Temperature sensor."""
super().__init__(device, "temperature") super().__init__(device, "temperature")
self._name = f"{self._device.name} Temperature"
self._unit = unit self._unit = unit
@property @property
@ -240,7 +236,6 @@ class DysonAirQualitySensor(DysonSensor):
def __init__(self, device): def __init__(self, device):
"""Create a new Dyson Air Quality sensor.""" """Create a new Dyson Air Quality sensor."""
super().__init__(device, "air_quality") super().__init__(device, "air_quality")
self._name = f"{self._device.name} AQI"
@property @property
def state(self): def state(self):

View file

@ -17,7 +17,7 @@ from homeassistant.components.vacuum import (
) )
from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.icon import icon_for_battery_level
from . import DYSON_DEVICES from . import DYSON_DEVICES, DysonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,35 +54,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return True return True
class Dyson360EyeDevice(VacuumEntity): class Dyson360EyeDevice(DysonEntity, VacuumEntity):
"""Dyson 360 Eye robot vacuum device.""" """Dyson 360 Eye robot vacuum device."""
def __init__(self, device): def __init__(self, device):
"""Dyson 360 Eye robot vacuum device.""" """Dyson 360 Eye robot vacuum device."""
_LOGGER.debug("Creating device %s", device.name) super().__init__(device, None)
self._device = device
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self._device.add_message_listener(self.on_message)
def on_message(self, message):
"""Handle a new messages that was received from the vacuum."""
_LOGGER.debug("Message received for %s device: %s", self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
False if entity pushes its state to HA.
"""
return False
@property
def name(self):
"""Return the name of the device."""
return self._device.name
@property @property
def status(self): def status(self):

View file

@ -187,7 +187,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_set_speed(self): def test_dyson_set_speed(self):
"""Test set fan speed.""" """Test set fan speed."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.set_speed("1") component.set_speed("1")
set_config = device.set_configuration set_config = device.set_configuration
@ -202,7 +202,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_turn_on(self): def test_dyson_turn_on(self):
"""Test turn on fan.""" """Test turn on fan."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.turn_on() component.turn_on()
set_config = device.set_configuration set_config = device.set_configuration
@ -211,7 +211,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_turn_night_mode(self): def test_dyson_turn_night_mode(self):
"""Test turn on fan with night mode.""" """Test turn on fan with night mode."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.set_night_mode(True) component.set_night_mode(True)
set_config = device.set_configuration set_config = device.set_configuration
@ -224,17 +224,17 @@ class DysonTest(unittest.TestCase):
def test_is_night_mode(self): def test_is_night_mode(self):
"""Test night mode.""" """Test night mode."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.night_mode assert not component.night_mode
device = _get_device_off() device = _get_device_off()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.night_mode assert component.night_mode
def test_dyson_turn_auto_mode(self): def test_dyson_turn_auto_mode(self):
"""Test turn on/off fan with auto mode.""" """Test turn on/off fan with auto mode."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.set_auto_mode(True) component.set_auto_mode(True)
set_config = device.set_configuration set_config = device.set_configuration
@ -247,17 +247,17 @@ class DysonTest(unittest.TestCase):
def test_is_auto_mode(self): def test_is_auto_mode(self):
"""Test auto mode.""" """Test auto mode."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.auto_mode assert not component.auto_mode
device = _get_device_auto() device = _get_device_auto()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.auto_mode assert component.auto_mode
def test_dyson_turn_on_speed(self): def test_dyson_turn_on_speed(self):
"""Test turn on fan with specified speed.""" """Test turn on fan with specified speed."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.turn_on("1") component.turn_on("1")
set_config = device.set_configuration set_config = device.set_configuration
@ -272,7 +272,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_turn_off(self): def test_dyson_turn_off(self):
"""Test turn off fan.""" """Test turn off fan."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.should_poll assert not component.should_poll
component.turn_off() component.turn_off()
set_config = device.set_configuration set_config = device.set_configuration
@ -281,7 +281,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_oscillate_off(self): def test_dyson_oscillate_off(self):
"""Test turn off oscillation.""" """Test turn off oscillation."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
component.oscillate(False) component.oscillate(False)
set_config = device.set_configuration set_config = device.set_configuration
set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_OFF) set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_OFF)
@ -289,7 +289,7 @@ class DysonTest(unittest.TestCase):
def test_dyson_oscillate_on(self): def test_dyson_oscillate_on(self):
"""Test turn on oscillation.""" """Test turn on oscillation."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
component.oscillate(True) component.oscillate(True)
set_config = device.set_configuration set_config = device.set_configuration
set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_ON) set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_ON)
@ -297,71 +297,71 @@ class DysonTest(unittest.TestCase):
def test_dyson_oscillate_value_on(self): def test_dyson_oscillate_value_on(self):
"""Test get oscillation value on.""" """Test get oscillation value on."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.oscillating assert component.oscillating
def test_dyson_oscillate_value_off(self): def test_dyson_oscillate_value_off(self):
"""Test get oscillation value off.""" """Test get oscillation value off."""
device = _get_device_off() device = _get_device_off()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.oscillating assert not component.oscillating
def test_dyson_on(self): def test_dyson_on(self):
"""Test device is on.""" """Test device is on."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.is_on assert component.is_on
def test_dyson_off(self): def test_dyson_off(self):
"""Test device is off.""" """Test device is off."""
device = _get_device_off() device = _get_device_off()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.is_on assert not component.is_on
device = _get_device_with_no_state() device = _get_device_with_no_state()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert not component.is_on assert not component.is_on
def test_dyson_get_speed(self): def test_dyson_get_speed(self):
"""Test get device speed.""" """Test get device speed."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.speed == 1 assert component.speed == 1
device = _get_device_off() device = _get_device_off()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.speed == 4 assert component.speed == 4
device = _get_device_with_no_state() device = _get_device_with_no_state()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.speed is None assert component.speed is None
device = _get_device_auto() device = _get_device_auto()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.speed == "AUTO" assert component.speed == "AUTO"
def test_dyson_get_direction(self): def test_dyson_get_direction(self):
"""Test get device direction.""" """Test get device direction."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.current_direction is None assert component.current_direction is None
def test_dyson_get_speed_list(self): def test_dyson_get_speed_list(self):
"""Test get speeds list.""" """Test get speeds list."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert len(component.speed_list) == 11 assert len(component.speed_list) == 11
def test_dyson_supported_features(self): def test_dyson_supported_features(self):
"""Test supported features.""" """Test supported features."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
assert component.supported_features == 3 assert component.supported_features == 3
def test_on_message(self): def test_on_message(self):
"""Test when message is received.""" """Test when message is received."""
device = _get_device_on() device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device) component = dyson.DysonPureCoolLinkEntity(device)
component.entity_id = "entity_id" component.entity_id = "entity_id"
component.schedule_update_ha_state = mock.Mock() component.schedule_update_ha_state = mock.Mock()
component.on_message(MockDysonState()) component.on_message(MockDysonState())
@ -788,7 +788,7 @@ async def test_purecool_update_state(devices, login, hass):
for call in device.add_message_listener.call_args_list: for call in device.add_message_listener.call_args_list:
callback = call[0][0] callback = call[0][0]
if type(callback.__self__) == dyson.DysonPureCoolDevice: if type(callback.__self__) == dyson.DysonPureCoolEntity:
callback(device.state) callback(device.state)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -849,7 +849,7 @@ async def test_purecool_update_state_filter_inv(devices, login, hass):
for call in device.add_message_listener.call_args_list: for call in device.add_message_listener.call_args_list:
callback = call[0][0] callback = call[0][0]
if type(callback.__self__) == dyson.DysonPureCoolDevice: if type(callback.__self__) == dyson.DysonPureCoolEntity:
callback(device.state) callback(device.state)
await hass.async_block_till_done() await hass.async_block_till_done()