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 line
pull/23553/head
etheralm 2019-04-30 02:24:05 +02:00 committed by Martin Hjelmare
parent b4e2a0ef84
commit 1d70005b01
9 changed files with 430 additions and 112 deletions

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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)."""

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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()])

View File

@ -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