Add sensor support for dyson 2018 models (#22578)
fix check for already created entities remove hepa and carbon filter add AQI attribute initial commit fix check for already created entities remove hepa and carbon filter add AQI attribute add air quality component tests fix method call tests fix line lengths fix pylint issues fix docstrings revert fan related changes remove whitespace change fix fan update state test add for loop for platform initialization add requested changes to aiq platform change string concatenation to new style string formatting update air quality tests update air quality tests refactor sensor component changes fix pylint issues fix debug string in the air quality component replace failing tests for older devices fix line length fan tests remove dependencies const and move imports move back imports to methods remove whitespace from blank linepull/23553/head
parent
b4e2a0ef84
commit
1d70005b01
|
@ -16,6 +16,7 @@ CONF_RETRY = 'retry'
|
|||
DEFAULT_TIMEOUT = 5
|
||||
DEFAULT_RETRY = 10
|
||||
DYSON_DEVICES = 'dyson_devices'
|
||||
DYSON_PLATFORMS = ['sensor', 'fan', 'vacuum', 'climate', 'air_quality']
|
||||
|
||||
DOMAIN = 'dyson'
|
||||
|
||||
|
@ -91,9 +92,7 @@ def setup(hass, config):
|
|||
# Start fan/sensors components
|
||||
if hass.data[DYSON_DEVICES]:
|
||||
_LOGGER.debug("Starting sensor/fan components")
|
||||
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, "fan", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, "climate", DOMAIN, {}, config)
|
||||
for platform in DYSON_PLATFORMS:
|
||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
"""Support for Dyson Pure Cool Air Quality Sensors."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.air_quality import AirQualityEntity, DOMAIN
|
||||
from . import DYSON_DEVICES
|
||||
|
||||
ATTRIBUTION = 'Dyson purifier air quality sensor'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DYSON_AIQ_DEVICES = 'dyson_aiq_devices'
|
||||
|
||||
ATTR_VOC = 'volatile_organic_compounds'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Dyson Sensors."""
|
||||
from libpurecool.dyson_pure_cool import DysonPureCool
|
||||
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
hass.data.setdefault(DYSON_AIQ_DEVICES, [])
|
||||
|
||||
# Get Dyson Devices from parent component
|
||||
device_ids = [device.unique_id for device in hass.data[DYSON_AIQ_DEVICES]]
|
||||
for device in hass.data[DYSON_DEVICES]:
|
||||
if isinstance(device, DysonPureCool) and \
|
||||
device.serial not in device_ids:
|
||||
hass.data[DYSON_AIQ_DEVICES].append(DysonAirSensor(device))
|
||||
add_entities(hass.data[DYSON_AIQ_DEVICES])
|
||||
|
||||
|
||||
class DysonAirSensor(AirQualityEntity):
|
||||
"""Representation of a generic Dyson air quality sensor."""
|
||||
|
||||
def __init__(self, device):
|
||||
"""Create a new generic air quality Dyson sensor."""
|
||||
self._device = device
|
||||
self._old_value = None
|
||||
self._name = device.name
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.async_add_executor_job(
|
||||
self._device.add_message_listener, self.on_message)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Handle new messages which are received from the fan."""
|
||||
from libpurecool.dyson_pure_state_v2 import \
|
||||
DysonEnvironmentalSensorV2State
|
||||
|
||||
_LOGGER.debug('%s: Message received for %s device: %s',
|
||||
DOMAIN, self.name, message)
|
||||
if (self._old_value is None or
|
||||
self._old_value != self._device.environmental_state) and \
|
||||
isinstance(message, DysonEnvironmentalSensorV2State):
|
||||
self._old_value = self._device.environmental_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
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return ATTRIBUTION
|
||||
|
||||
@property
|
||||
def air_quality_index(self):
|
||||
"""Return the Air Quality Index (AQI)."""
|
||||
return max(self.particulate_matter_2_5,
|
||||
self.particulate_matter_10,
|
||||
self.nitrogen_dioxide,
|
||||
self.volatile_organic_compounds)
|
||||
|
||||
@property
|
||||
def particulate_matter_2_5(self):
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
if self._device.environmental_state:
|
||||
return int(self._device.environmental_state.particulate_matter_25)
|
||||
return None
|
||||
|
||||
@property
|
||||
def particulate_matter_10(self):
|
||||
"""Return the particulate matter 10 level."""
|
||||
if self._device.environmental_state:
|
||||
return int(self._device.environmental_state.particulate_matter_10)
|
||||
return None
|
||||
|
||||
@property
|
||||
def nitrogen_dioxide(self):
|
||||
"""Return the NO2 (nitrogen dioxide) level."""
|
||||
if self._device.environmental_state:
|
||||
return int(self._device.environmental_state.nitrogen_dioxide)
|
||||
return None
|
||||
|
||||
@property
|
||||
def volatile_organic_compounds(self):
|
||||
"""Return the VOC (Volatile Organic Compounds) level."""
|
||||
if self._device.environmental_state:
|
||||
return int(self._device.
|
||||
environmental_state.volatile_organic_compounds)
|
||||
return None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the sensor's unique id."""
|
||||
return self._device.serial
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
data = {}
|
||||
|
||||
voc = self.volatile_organic_compounds
|
||||
if voc is not None:
|
||||
data[ATTR_VOC] = voc
|
||||
return data
|
|
@ -474,7 +474,8 @@ class DysonPureCoolDevice(FanEntity):
|
|||
FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM,
|
||||
FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM,
|
||||
FanSpeed.FAN_SPEED_8.value: SPEED_HIGH,
|
||||
FanSpeed.FAN_SPEED_9.value: SPEED_HIGH}
|
||||
FanSpeed.FAN_SPEED_9.value: SPEED_HIGH,
|
||||
FanSpeed.FAN_SPEED_10.value: SPEED_HIGH}
|
||||
|
||||
return speed_map[self._device.state.speed]
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import logging
|
|||
|
||||
from homeassistant.const import STATE_OFF, TEMP_CELSIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import DYSON_DEVICES
|
||||
|
||||
SENSOR_UNITS = {
|
||||
|
@ -21,26 +20,38 @@ SENSOR_ICONS = {
|
|||
'temperature': 'mdi:thermometer',
|
||||
}
|
||||
|
||||
DYSON_SENSOR_DEVICES = 'dyson_sensor_devices'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Dyson Sensors."""
|
||||
_LOGGER.debug("Creating new Dyson fans")
|
||||
devices = []
|
||||
unit = hass.config.units.temperature_unit
|
||||
# Get Dyson Devices from parent component
|
||||
from libpurecool.dyson_pure_cool import DysonPureCool
|
||||
from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
|
||||
from libpurecool.dyson_pure_cool import DysonPureCool
|
||||
|
||||
for device in [d for d in hass.data[DYSON_DEVICES]
|
||||
if isinstance(d, DysonPureCoolLink) and
|
||||
not isinstance(d, DysonPureCool)]:
|
||||
devices.append(DysonFilterLifeSensor(device))
|
||||
devices.append(DysonDustSensor(device))
|
||||
devices.append(DysonHumiditySensor(device))
|
||||
devices.append(DysonTemperatureSensor(device, unit))
|
||||
devices.append(DysonAirQualitySensor(device))
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
hass.data.setdefault(DYSON_SENSOR_DEVICES, [])
|
||||
unit = hass.config.units.temperature_unit
|
||||
devices = hass.data[DYSON_SENSOR_DEVICES]
|
||||
|
||||
# Get Dyson Devices from parent component
|
||||
device_ids = [device.unique_id for device in
|
||||
hass.data[DYSON_SENSOR_DEVICES]]
|
||||
for device in hass.data[DYSON_DEVICES]:
|
||||
if isinstance(device, DysonPureCool):
|
||||
if '{}-{}'.format(device.serial, 'temperature') not in device_ids:
|
||||
devices.append(DysonTemperatureSensor(device, unit))
|
||||
if '{}-{}'.format(device.serial, 'humidity') not in device_ids:
|
||||
devices.append(DysonHumiditySensor(device))
|
||||
elif isinstance(device, DysonPureCoolLink):
|
||||
devices.append(DysonFilterLifeSensor(device))
|
||||
devices.append(DysonDustSensor(device))
|
||||
devices.append(DysonHumiditySensor(device))
|
||||
devices.append(DysonTemperatureSensor(device, unit))
|
||||
devices.append(DysonAirQualitySensor(device))
|
||||
add_entities(devices)
|
||||
|
||||
|
||||
|
@ -56,7 +67,7 @@ class DysonSensor(Entity):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.async_add_job(
|
||||
self.hass.async_add_executor_job(
|
||||
self._device.add_message_listener, self.on_message)
|
||||
|
||||
def on_message(self, message):
|
||||
|
@ -88,6 +99,11 @@ class DysonSensor(Entity):
|
|||
"""Return the icon for this sensor."""
|
||||
return SENSOR_ICONS[self._sensor_type]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the sensor's unique id."""
|
||||
return '{}-{}'.format(self._device.serial, self._sensor_type)
|
||||
|
||||
|
||||
class DysonFilterLifeSensor(DysonSensor):
|
||||
"""Representation of Dyson Filter Life sensor (in hours)."""
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
"""Test the Dyson air quality component."""
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
import asynctest
|
||||
from libpurecool.dyson_pure_cool import DysonPureCool
|
||||
from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State
|
||||
|
||||
import homeassistant.components.dyson.air_quality as dyson
|
||||
from homeassistant.components import dyson as dyson_parent
|
||||
from homeassistant.components.air_quality import DOMAIN as AIQ_DOMAIN, \
|
||||
ATTR_PM_2_5, ATTR_PM_10, ATTR_NO2
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
def _get_dyson_purecool_device():
|
||||
"""Return a valid device as provided by the Dyson web services."""
|
||||
device = mock.Mock(spec=DysonPureCool)
|
||||
device.serial = 'XX-XXXXX-XX'
|
||||
device.name = 'Living room'
|
||||
device.connect = mock.Mock(return_value=True)
|
||||
device.auto_connect = mock.Mock(return_value=True)
|
||||
device.environmental_state.particulate_matter_25 = '0014'
|
||||
device.environmental_state.particulate_matter_10 = '0025'
|
||||
device.environmental_state.nitrogen_dioxide = '0042'
|
||||
device.environmental_state.volatile_organic_compounds = '0035'
|
||||
return device
|
||||
|
||||
|
||||
def _get_config():
|
||||
"""Return a config dictionary."""
|
||||
return {dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: 'email',
|
||||
dyson_parent.CONF_PASSWORD: 'password',
|
||||
dyson_parent.CONF_LANGUAGE: 'GB',
|
||||
dyson_parent.CONF_DEVICES: [
|
||||
{
|
||||
'device_id': 'XX-XXXXX-XX',
|
||||
'device_ip': '192.168.0.1'
|
||||
}
|
||||
]
|
||||
}}
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_aiq_attributes(devices, login, hass):
|
||||
"""Test state attributes."""
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
fan_state = hass.states.get("air_quality.living_room")
|
||||
attributes = fan_state.attributes
|
||||
|
||||
assert fan_state.state == '14'
|
||||
assert attributes[ATTR_PM_2_5] == 14
|
||||
assert attributes[ATTR_PM_10] == 25
|
||||
assert attributes[ATTR_NO2] == 42
|
||||
assert attributes[dyson.ATTR_VOC] == 35
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_aiq_update_state(devices, login, hass):
|
||||
"""Test state update."""
|
||||
device = devices.return_value[0]
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
event = {
|
||||
"msg": "ENVIRONMENTAL-CURRENT-SENSOR-DATA",
|
||||
"time": "2019-03-29T10:00:01.000Z",
|
||||
"data": {
|
||||
"pm10": "0080",
|
||||
"p10r": "0151",
|
||||
"hact": "0040",
|
||||
"va10": "0055",
|
||||
"p25r": "0161",
|
||||
"noxl": "0069",
|
||||
"pm25": "0035",
|
||||
"sltm": "OFF",
|
||||
"tact": "2960"
|
||||
}
|
||||
}
|
||||
device.environmental_state = \
|
||||
DysonEnvironmentalSensorV2State(json.dumps(event))
|
||||
|
||||
callback = device.add_message_listener.call_args_list[2][0][0]
|
||||
callback(device.environmental_state)
|
||||
await hass.async_block_till_done()
|
||||
fan_state = hass.states.get("air_quality.living_room")
|
||||
attributes = fan_state.attributes
|
||||
|
||||
assert fan_state.state == '35'
|
||||
assert attributes[ATTR_PM_2_5] == 35
|
||||
assert attributes[ATTR_PM_10] == 80
|
||||
assert attributes[ATTR_NO2] == 69
|
||||
assert attributes[dyson.ATTR_VOC] == 55
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_component_setup_only_once(devices, login, hass):
|
||||
"""Test if entities are created only once."""
|
||||
config = _get_config()
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
discovery.load_platform(hass, AIQ_DOMAIN,
|
||||
dyson_parent.DOMAIN, {}, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.data[dyson.DYSON_AIQ_DEVICES]) == 1
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_aiq_without_discovery(devices, login, hass):
|
||||
"""Test if component correctly returns if discovery not set."""
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
add_entities_mock = mock.MagicMock()
|
||||
|
||||
dyson.setup_platform(hass, None, add_entities_mock, None)
|
||||
|
||||
assert add_entities_mock.call_count == 0
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_aiq_empty_environment_state(devices, login, hass):
|
||||
"""Test device with empty environmental state."""
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
device = hass.data[dyson.DYSON_AIQ_DEVICES][0]
|
||||
device._device.environmental_state = None
|
||||
|
||||
assert device.state is None
|
||||
assert device.particulate_matter_2_5 is None
|
||||
assert device.particulate_matter_10 is None
|
||||
assert device.nitrogen_dioxide is None
|
||||
assert device.volatile_organic_compounds is None
|
|
@ -2,6 +2,7 @@
|
|||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import asynctest
|
||||
from libpurecool.const import (FocusMode, HeatMode,
|
||||
HeatState, HeatTarget, TiltState)
|
||||
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
|
||||
|
@ -10,7 +11,7 @@ from libpurecool.dyson_pure_state import DysonPureHotCoolState
|
|||
from homeassistant.components import dyson as dyson_parent
|
||||
from homeassistant.components.dyson import climate as dyson
|
||||
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.setup import async_setup_component
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
|
@ -22,6 +23,25 @@ class MockDysonState(DysonPureHotCoolState):
|
|||
pass
|
||||
|
||||
|
||||
def _get_config():
|
||||
"""Return a config dictionary."""
|
||||
return {dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: "email",
|
||||
dyson_parent.CONF_PASSWORD: "password",
|
||||
dyson_parent.CONF_LANGUAGE: "GB",
|
||||
dyson_parent.CONF_DEVICES: [
|
||||
{
|
||||
"device_id": "XX-XXXXX-XX",
|
||||
"device_ip": "192.168.0.1"
|
||||
},
|
||||
{
|
||||
"device_id": "YY-YYYYY-YY",
|
||||
"device_ip": "192.168.0.2"
|
||||
}
|
||||
]
|
||||
}}
|
||||
|
||||
|
||||
def _get_device_with_no_state():
|
||||
"""Return a device with no state."""
|
||||
device = mock.Mock(spec=DysonPureHotCoolLink)
|
||||
|
@ -60,6 +80,7 @@ def _get_device_cool():
|
|||
"""Return a device with state of cooling."""
|
||||
device = mock.Mock(spec=DysonPureHotCoolLink)
|
||||
device.name = "Device_name"
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.state.tilt = TiltState.TILT_FALSE.value
|
||||
device.state.focus_mode = FocusMode.FOCUS_OFF.value
|
||||
device.state.heat_target = HeatTarget.celsius(12)
|
||||
|
@ -89,6 +110,7 @@ def _get_device_heat_on():
|
|||
"""Return a device with state of heating."""
|
||||
device = mock.Mock(spec=DysonPureHotCoolLink)
|
||||
device.name = "Device_name"
|
||||
device.serial = "YY-YYYYY-YY"
|
||||
device.state = mock.Mock()
|
||||
device.state.tilt = TiltState.TILT_FALSE.value
|
||||
device.state.focus_mode = FocusMode.FOCUS_ON.value
|
||||
|
@ -111,24 +133,6 @@ class DysonTest(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_device_heat_on(), _get_device_cool()])
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
def test_setup_component_with_parent_discovery(self, mocked_login,
|
||||
mocked_devices):
|
||||
"""Test setup_component using discovery."""
|
||||
setup_component(self.hass, dyson_parent.DOMAIN, {
|
||||
dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: "email",
|
||||
dyson_parent.CONF_PASSWORD: "password",
|
||||
dyson_parent.CONF_LANGUAGE: "US",
|
||||
}
|
||||
})
|
||||
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 2
|
||||
self.hass.block_till_done()
|
||||
for m in mocked_devices.return_value:
|
||||
assert m.add_message_listener.called
|
||||
|
||||
def test_setup_component_without_devices(self):
|
||||
"""Test setup component with no devices."""
|
||||
self.hass.data[dyson.DYSON_DEVICES] = []
|
||||
|
@ -357,3 +361,15 @@ class DysonTest(unittest.TestCase):
|
|||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.target_temperature == 23
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_device_heat_on(), _get_device_cool()])
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
async def test_setup_component_with_parent_discovery(mocked_login,
|
||||
mocked_devices, hass):
|
||||
"""Test setup_component using discovery."""
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.data[dyson.DYSON_DEVICES]) == 2
|
||||
|
|
|
@ -13,7 +13,7 @@ from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State
|
|||
import homeassistant.components.dyson.fan as dyson
|
||||
from homeassistant.components import dyson as dyson_parent
|
||||
from homeassistant.components.dyson import DYSON_DEVICES
|
||||
from homeassistant.components.fan import (DOMAIN, ATTR_SPEED, ATTR_SPEED_LIST,
|
||||
from homeassistant.components.fan import (DOMAIN, ATTR_SPEED,
|
||||
ATTR_OSCILLATING, SPEED_LOW,
|
||||
SPEED_MEDIUM, SPEED_HIGH,
|
||||
SERVICE_OSCILLATE)
|
||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (SERVICE_TURN_ON,
|
|||
SERVICE_TURN_OFF,
|
||||
ATTR_ENTITY_ID)
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.setup import setup_component, async_setup_component
|
||||
from homeassistant.setup import async_setup_component
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
|
@ -55,6 +55,21 @@ def _get_dyson_purecool_device():
|
|||
return device
|
||||
|
||||
|
||||
def _get_dyson_purecoollink_device():
|
||||
"""Return a valid device as provided by the Dyson web services."""
|
||||
device = mock.Mock(spec=DysonPureCoolLink)
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.name = "Living room"
|
||||
device.connect = mock.Mock(return_value=True)
|
||||
device.auto_connect = mock.Mock(return_value=True)
|
||||
device.state = mock.Mock()
|
||||
device.state.oscillation = "ON"
|
||||
device.state.fan_mode = "FAN"
|
||||
device.state.speed = FanSpeed.FAN_SPEED_AUTO.value
|
||||
device.state.night_mode = "OFF"
|
||||
return device
|
||||
|
||||
|
||||
def _get_supported_speeds():
|
||||
return [
|
||||
int(FanSpeed.FAN_SPEED_1.value),
|
||||
|
@ -173,45 +188,6 @@ class DysonTest(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_device_on()])
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
def test_get_state_attributes(self, mocked_login, mocked_devices):
|
||||
"""Test async added to hass."""
|
||||
setup_component(self.hass, dyson_parent.DOMAIN, {
|
||||
dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: "email",
|
||||
dyson_parent.CONF_PASSWORD: "password",
|
||||
dyson_parent.CONF_LANGUAGE: "US",
|
||||
}
|
||||
})
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get("{}.{}".format(
|
||||
DOMAIN,
|
||||
mocked_devices.return_value[0].name))
|
||||
|
||||
assert dyson.ATTR_NIGHT_MODE in state.attributes
|
||||
assert dyson.ATTR_AUTO_MODE in state.attributes
|
||||
assert ATTR_SPEED in state.attributes
|
||||
assert ATTR_SPEED_LIST in state.attributes
|
||||
assert ATTR_OSCILLATING in state.attributes
|
||||
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_device_on()])
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
def test_async_added_to_hass(self, mocked_login, mocked_devices):
|
||||
"""Test async added to hass."""
|
||||
setup_component(self.hass, dyson_parent.DOMAIN, {
|
||||
dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: "email",
|
||||
dyson_parent.CONF_PASSWORD: "password",
|
||||
dyson_parent.CONF_LANGUAGE: "US",
|
||||
}
|
||||
})
|
||||
self.hass.block_till_done()
|
||||
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1
|
||||
assert mocked_devices.return_value[0].add_message_listener.called
|
||||
|
||||
def test_dyson_set_speed(self):
|
||||
"""Test set fan speed."""
|
||||
device = _get_device_on()
|
||||
|
@ -415,6 +391,22 @@ class DysonTest(unittest.TestCase):
|
|||
dyson_device.set_night_mode.assert_called_with(True)
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecoollink_device()])
|
||||
async def test_purecoollink_attributes(devices, login, hass):
|
||||
"""Test state attributes."""
|
||||
await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
fan_state = hass.states.get("fan.living_room")
|
||||
attributes = fan_state.attributes
|
||||
|
||||
assert fan_state.state == "on"
|
||||
assert attributes[dyson.ATTR_NIGHT_MODE] is False
|
||||
assert attributes[ATTR_SPEED] == FanSpeed.FAN_SPEED_AUTO.value
|
||||
assert attributes[ATTR_OSCILLATING] is True
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
|
@ -670,31 +662,6 @@ async def test_purecool_set_timer(devices, login, hass):
|
|||
assert device.disable_sleep_timer.call_count == 1
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_attributes(devices, login, hass):
|
||||
"""Test state attributes."""
|
||||
await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
|
||||
await hass.async_block_till_done()
|
||||
fan_state = hass.states.get("fan.living_room")
|
||||
attributes = fan_state.attributes
|
||||
|
||||
assert fan_state.state == "on"
|
||||
assert attributes[dyson.ATTR_NIGHT_MODE] is False
|
||||
assert attributes[dyson.ATTR_AUTO_MODE] is True
|
||||
assert attributes[dyson.ATTR_ANGLE_LOW] == 90
|
||||
assert attributes[dyson.ATTR_ANGLE_HIGH] == 180
|
||||
assert attributes[dyson.ATTR_FLOW_DIRECTION_FRONT] is True
|
||||
assert attributes[dyson.ATTR_TIMER] == 60
|
||||
assert attributes[dyson.ATTR_HEPA_FILTER] == 90
|
||||
assert attributes[dyson.ATTR_CARBON_FILTER] == 80
|
||||
assert attributes[dyson.ATTR_DYSON_SPEED] == FanSpeed.FAN_SPEED_AUTO.value
|
||||
assert attributes[ATTR_SPEED] == SPEED_MEDIUM
|
||||
assert attributes[ATTR_OSCILLATING] is True
|
||||
assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds()
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
|
@ -713,7 +680,7 @@ async def test_purecool_update_state(devices, login, hass):
|
|||
"osau": "0095", "ancp": "CUST"}}
|
||||
device.state = DysonPureCoolV2State(json.dumps(event))
|
||||
|
||||
callback = device.add_message_listener.call_args_list[0][0][0]
|
||||
callback = device.add_message_listener.call_args_list[3][0][0]
|
||||
callback(device.state)
|
||||
await hass.async_block_till_done()
|
||||
fan_state = hass.states.get("fan.living_room")
|
||||
|
|
|
@ -87,7 +87,7 @@ class DysonTest(unittest.TestCase):
|
|||
assert mocked_login.call_count == 1
|
||||
assert mocked_devices.call_count == 1
|
||||
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1
|
||||
assert mocked_discovery.call_count == 4
|
||||
assert mocked_discovery.call_count == 5
|
||||
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_not_available()])
|
||||
|
@ -172,7 +172,7 @@ class DysonTest(unittest.TestCase):
|
|||
assert mocked_login.call_count == 1
|
||||
assert mocked_devices.call_count == 1
|
||||
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1
|
||||
assert mocked_discovery.call_count == 4
|
||||
assert mocked_discovery.call_count == 5
|
||||
|
||||
@mock.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_not_available()])
|
||||
|
|
|
@ -2,17 +2,51 @@
|
|||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import asynctest
|
||||
from libpurecool.dyson_pure_cool import DysonPureCool
|
||||
from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
|
||||
|
||||
from homeassistant.components import dyson as dyson_parent
|
||||
from homeassistant.components.dyson import sensor as dyson
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, \
|
||||
STATE_OFF
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.setup import async_setup_component
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def _get_dyson_purecool_device():
|
||||
"""Return a valid device provide by Dyson web services."""
|
||||
device = mock.Mock(spec=DysonPureCool)
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.name = "Living room"
|
||||
device.connect = mock.Mock(return_value=True)
|
||||
device.auto_connect = mock.Mock(return_value=True)
|
||||
device.environmental_state.humidity = 42
|
||||
device.environmental_state.temperature = 280
|
||||
device.state.hepa_filter_state = 90
|
||||
device.state.carbon_filter_state = 80
|
||||
return device
|
||||
|
||||
|
||||
def _get_config():
|
||||
"""Return a config dictionary."""
|
||||
return {dyson_parent.DOMAIN: {
|
||||
dyson_parent.CONF_USERNAME: "email",
|
||||
dyson_parent.CONF_PASSWORD: "password",
|
||||
dyson_parent.CONF_LANGUAGE: "GB",
|
||||
dyson_parent.CONF_DEVICES: [
|
||||
{
|
||||
"device_id": "XX-XXXXX-XX",
|
||||
"device_ip": "192.168.0.1"
|
||||
}
|
||||
]
|
||||
}}
|
||||
|
||||
|
||||
def _get_device_without_state():
|
||||
"""Return a valid device provide by Dyson web services."""
|
||||
device = mock.Mock()
|
||||
device = mock.Mock(spec=DysonPureCoolLink)
|
||||
device.name = "Device_name"
|
||||
device.state = None
|
||||
device.environmental_state = None
|
||||
|
@ -21,7 +55,7 @@ def _get_device_without_state():
|
|||
|
||||
def _get_with_state():
|
||||
"""Return a valid device with state values."""
|
||||
device = mock.Mock(spec=DysonPureCoolLink)
|
||||
device = mock.Mock()
|
||||
device.name = "Device_name"
|
||||
device.state = mock.Mock()
|
||||
device.state.filter_life = 100
|
||||
|
@ -65,7 +99,7 @@ class DysonTest(unittest.TestCase):
|
|||
self.hass.data[dyson.DYSON_DEVICES] = []
|
||||
add_entities = mock.MagicMock()
|
||||
dyson.setup_platform(self.hass, None, add_entities)
|
||||
add_entities.assert_called_with([])
|
||||
add_entities.assert_not_called()
|
||||
|
||||
def test_setup_component(self):
|
||||
"""Test setup component with devices."""
|
||||
|
@ -80,7 +114,7 @@ class DysonTest(unittest.TestCase):
|
|||
device_fan = _get_device_without_state()
|
||||
device_non_fan = _get_with_state()
|
||||
self.hass.data[dyson.DYSON_DEVICES] = [device_fan, device_non_fan]
|
||||
dyson.setup_platform(self.hass, None, _add_device)
|
||||
dyson.setup_platform(self.hass, None, _add_device, mock.MagicMock())
|
||||
|
||||
def test_dyson_filter_life_sensor(self):
|
||||
"""Test filter life sensor with no value."""
|
||||
|
@ -228,3 +262,17 @@ class DysonTest(unittest.TestCase):
|
|||
assert sensor.unit_of_measurement is None
|
||||
assert sensor.name == "Device_name AQI"
|
||||
assert sensor.entity_id == "sensor.dyson_1"
|
||||
|
||||
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
|
||||
@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_purecool_device()])
|
||||
async def test_purecool_component_setup_only_once(devices, login, hass):
|
||||
"""Test if entities are created only once."""
|
||||
config = _get_config()
|
||||
await async_setup_component(hass, dyson_parent.DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
discovery.load_platform(hass, "sensor", dyson_parent.DOMAIN, {}, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.data[dyson.DYSON_SENSOR_DEVICES]) == 2
|
||||
|
|
Loading…
Reference in New Issue