Add support to Dyson 360 Eye robot vacuum using new vacuum platform (#8852)
* Add support to Dyson 360 Eye robot vacuum using new vacuum platform * Fix tests with Python 3.5 * Code review * Code review - v2 * Code review - v3pull/8866/head
parent
82a7dffc03
commit
83afd12807
|
@ -13,7 +13,7 @@ from homeassistant.helpers import discovery
|
|||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
|
||||
CONF_DEVICES
|
||||
|
||||
REQUIREMENTS = ['libpurecoollink==0.4.1']
|
||||
REQUIREMENTS = ['libpurecoollink==0.4.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -69,14 +69,17 @@ def setup(hass, config):
|
|||
dyson_device = next((d for d in dyson_devices if
|
||||
d.serial == device["device_id"]), None)
|
||||
if dyson_device:
|
||||
connected = dyson_device.connect(None, device["device_ip"],
|
||||
timeout, retry)
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", dyson_device)
|
||||
hass.data[DYSON_DEVICES].append(dyson_device)
|
||||
else:
|
||||
_LOGGER.warning("Unable to connect to device %s",
|
||||
dyson_device)
|
||||
try:
|
||||
connected = dyson_device.connect(device["device_ip"])
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", dyson_device)
|
||||
hass.data[DYSON_DEVICES].append(dyson_device)
|
||||
else:
|
||||
_LOGGER.warning("Unable to connect to device %s",
|
||||
dyson_device)
|
||||
except OSError as ose:
|
||||
_LOGGER.error("Unable to connect to device %s: %s",
|
||||
str(dyson_device.network_device), str(ose))
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Unable to find device %s in Dyson account",
|
||||
|
@ -86,7 +89,7 @@ def setup(hass, config):
|
|||
for device in dyson_devices:
|
||||
_LOGGER.info("Trying to connect to device %s with timeout=%i "
|
||||
"and retry=%i", device, timeout, retry)
|
||||
connected = device.connect(None, None, timeout, retry)
|
||||
connected = device.auto_connect(timeout, retry)
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", device)
|
||||
hass.data[DYSON_DEVICES].append(device)
|
||||
|
@ -98,5 +101,6 @@ def setup(hass, config):
|
|||
_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)
|
||||
|
||||
return True
|
||||
|
|
|
@ -36,7 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
hass.data[DYSON_FAN_DEVICES] = []
|
||||
|
||||
# Get Dyson Devices from parent component
|
||||
for device in hass.data[DYSON_DEVICES]:
|
||||
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
|
||||
for device in [d for d in hass.data[DYSON_DEVICES] if
|
||||
isinstance(d, DysonPureCoolLink)]:
|
||||
dyson_entity = DysonPureCoolLinkDevice(hass, device)
|
||||
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
devices = []
|
||||
unit = hass.config.units.temperature_unit
|
||||
# Get Dyson Devices from parent component
|
||||
for device in hass.data[DYSON_DEVICES]:
|
||||
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
|
||||
for device in [d for d in hass.data[DYSON_DEVICES] if
|
||||
isinstance(d, DysonPureCoolLink)]:
|
||||
devices.append(DysonFilterLifeSensor(hass, device))
|
||||
devices.append(DysonDustSensor(hass, device))
|
||||
devices.append(DysonHumiditySensor(hass, device))
|
||||
|
|
|
@ -168,9 +168,6 @@ def send_command(hass, command, params=None, entity_id=None):
|
|||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up the vacuum component."""
|
||||
if not config[DOMAIN]:
|
||||
return False
|
||||
|
||||
component = EntityComponent(
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_VACUUMS)
|
||||
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
"""
|
||||
Support for the Dyson 360 eye vacuum cleaner robot.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/vacuum.dyson/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.dyson import DYSON_DEVICES
|
||||
from homeassistant.components.vacuum import (SUPPORT_BATTERY,
|
||||
SUPPORT_FAN_SPEED, SUPPORT_PAUSE,
|
||||
SUPPORT_RETURN_HOME,
|
||||
SUPPORT_STATUS, SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||
VacuumDevice)
|
||||
from homeassistant.util.icon import icon_for_battery_level
|
||||
|
||||
ATTR_FULL_CLEAN_TYPE = "full_clean_type"
|
||||
ATTR_CLEAN_ID = "clean_id"
|
||||
ATTR_POSITION = "position"
|
||||
|
||||
DEPENDENCIES = ['dyson']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DYSON_360_EYE_DEVICES = "dyson_360_eye_devices"
|
||||
|
||||
ICON = "mdi:roomba"
|
||||
|
||||
SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \
|
||||
SUPPORT_RETURN_HOME | SUPPORT_FAN_SPEED | SUPPORT_STATUS | \
|
||||
SUPPORT_BATTERY | SUPPORT_STOP
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Dyson 360 Eye robot vacuum platform."""
|
||||
_LOGGER.info("Creating new Dyson 360 Eye robot vacuum")
|
||||
if DYSON_360_EYE_DEVICES not in hass.data:
|
||||
hass.data[DYSON_360_EYE_DEVICES] = []
|
||||
|
||||
# Get Dyson Devices from parent component
|
||||
from libpurecoollink.dyson_360_eye import Dyson360Eye
|
||||
for device in [d for d in hass.data[DYSON_DEVICES] if
|
||||
isinstance(d, Dyson360Eye)]:
|
||||
dyson_entity = Dyson360EyeDevice(device)
|
||||
hass.data[DYSON_360_EYE_DEVICES].append(dyson_entity)
|
||||
|
||||
add_devices(hass.data[DYSON_360_EYE_DEVICES])
|
||||
return True
|
||||
|
||||
|
||||
class Dyson360EyeDevice(VacuumDevice):
|
||||
"""Dyson 360 Eye robot vacuum device."""
|
||||
|
||||
def __init__(self, device):
|
||||
"""Dyson 360 Eye robot vacuum device."""
|
||||
_LOGGER.info("Creating device %s", device.name)
|
||||
self._device = device
|
||||
self._icon = ICON
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Callback when entity is added to hass."""
|
||||
self.hass.async_add_job(
|
||||
self._device.add_message_listener, self.on_message)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Called when new messages 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
|
||||
def icon(self):
|
||||
"""Return the icon to use for device."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Return the status of the vacuum cleaner."""
|
||||
from libpurecoollink.const import Dyson360EyeMode
|
||||
dyson_labels = {
|
||||
Dyson360EyeMode.INACTIVE_CHARGING: "Stopped - Charging",
|
||||
Dyson360EyeMode.INACTIVE_CHARGED: "Stopped - Charged",
|
||||
Dyson360EyeMode.FULL_CLEAN_PAUSED: "Paused",
|
||||
Dyson360EyeMode.FULL_CLEAN_RUNNING: "Cleaning",
|
||||
Dyson360EyeMode.FULL_CLEAN_ABORTED: "Returning home",
|
||||
Dyson360EyeMode.FULL_CLEAN_INITIATED: "Start cleaning",
|
||||
Dyson360EyeMode.FAULT_USER_RECOVERABLE: "Error - device blocked",
|
||||
Dyson360EyeMode.FAULT_REPLACE_ON_DOCK:
|
||||
"Error - Replace device on dock",
|
||||
Dyson360EyeMode.FULL_CLEAN_FINISHED: "Finished",
|
||||
Dyson360EyeMode.FULL_CLEAN_NEEDS_CHARGE: "Need charging"
|
||||
}
|
||||
return dyson_labels.get(self._device.state.state,
|
||||
self._device.state.state)
|
||||
|
||||
@property
|
||||
def battery_level(self):
|
||||
"""Return the battery level of the vacuum cleaner."""
|
||||
return self._device.state.battery_level
|
||||
|
||||
@property
|
||||
def fan_speed(self):
|
||||
"""Return the fan speed of the vacuum cleaner."""
|
||||
from libpurecoollink.const import PowerMode
|
||||
speed_labels = {
|
||||
PowerMode.MAX: "Max",
|
||||
PowerMode.QUIET: "Quiet"
|
||||
}
|
||||
return speed_labels[self._device.state.power_mode]
|
||||
|
||||
@property
|
||||
def fan_speed_list(self):
|
||||
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||
return ["Quiet", "Max"]
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the specific state attributes of this vacuum cleaner."""
|
||||
return {
|
||||
ATTR_POSITION: str(self._device.state.position)
|
||||
}
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
from libpurecoollink.const import Dyson360EyeMode
|
||||
return self._device.state.state in [
|
||||
Dyson360EyeMode.FULL_CLEAN_INITIATED,
|
||||
Dyson360EyeMode.FULL_CLEAN_ABORTED,
|
||||
Dyson360EyeMode.FULL_CLEAN_RUNNING
|
||||
]
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag vacuum cleaner robot features that are supported."""
|
||||
return SUPPORT_DYSON
|
||||
|
||||
@property
|
||||
def battery_icon(self):
|
||||
"""Return the battery icon for the vacuum cleaner."""
|
||||
from libpurecoollink.const import Dyson360EyeMode
|
||||
charging = self._device.state.state in [
|
||||
Dyson360EyeMode.INACTIVE_CHARGING]
|
||||
return icon_for_battery_level(
|
||||
battery_level=self.battery_level, charging=charging)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the vacuum on."""
|
||||
_LOGGER.debug("Turn on device %s", self.name)
|
||||
from libpurecoollink.const import Dyson360EyeMode
|
||||
if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]:
|
||||
self._device.resume()
|
||||
else:
|
||||
self._device.start()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the vacuum off and return to home."""
|
||||
_LOGGER.debug("Turn off device %s", self.name)
|
||||
self._device.pause()
|
||||
|
||||
def stop(self, **kwargs):
|
||||
"""Stop the vacuum cleaner."""
|
||||
_LOGGER.debug("Stop device %s", self.name)
|
||||
self._device.pause()
|
||||
|
||||
def set_fan_speed(self, fan_speed, **kwargs):
|
||||
"""Set fan speed."""
|
||||
_LOGGER.debug("Set fan speed %s on device %s", fan_speed, self.name)
|
||||
from libpurecoollink.const import PowerMode
|
||||
power_modes = {
|
||||
"Quiet": PowerMode.QUIET,
|
||||
"Max": PowerMode.MAX
|
||||
}
|
||||
self._device.set_power_mode(power_modes[fan_speed])
|
||||
|
||||
def start_pause(self, **kwargs):
|
||||
"""Start, pause or resume the cleaning task."""
|
||||
from libpurecoollink.const import Dyson360EyeMode
|
||||
if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]:
|
||||
_LOGGER.debug("Resume device %s", self.name)
|
||||
self._device.resume()
|
||||
elif self._device.state.state in [Dyson360EyeMode.INACTIVE_CHARGED,
|
||||
Dyson360EyeMode.INACTIVE_CHARGING]:
|
||||
_LOGGER.debug("Start device %s", self.name)
|
||||
self._device.start()
|
||||
else:
|
||||
_LOGGER.debug("Pause device %s", self.name)
|
||||
self._device.pause()
|
||||
|
||||
def return_to_base(self, **kwargs):
|
||||
"""Set the vacuum cleaner to return to the dock."""
|
||||
_LOGGER.debug("Return to base device %s", self.name)
|
||||
self._device.abort()
|
|
@ -354,7 +354,7 @@ knxip==0.5
|
|||
libnacl==1.5.2
|
||||
|
||||
# homeassistant.components.dyson
|
||||
libpurecoollink==0.4.1
|
||||
libpurecoollink==0.4.2
|
||||
|
||||
# homeassistant.components.device_tracker.mikrotik
|
||||
librouteros==1.0.2
|
||||
|
|
|
@ -62,7 +62,7 @@ holidays==0.8.1
|
|||
influxdb==3.0.0
|
||||
|
||||
# homeassistant.components.dyson
|
||||
libpurecoollink==0.4.1
|
||||
libpurecoollink==0.4.2
|
||||
|
||||
# homeassistant.components.media_player.soundtouch
|
||||
libsoundtouch==0.7.2
|
||||
|
|
|
@ -7,6 +7,7 @@ from homeassistant.components.fan import dyson
|
|||
from tests.common import get_test_home_assistant
|
||||
from libpurecoollink.const import FanSpeed, FanMode, NightMode, Oscillation
|
||||
from libpurecoollink.dyson_pure_state import DysonPureCoolState
|
||||
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
|
||||
|
||||
|
||||
class MockDysonState(DysonPureCoolState):
|
||||
|
@ -49,7 +50,7 @@ def _get_device_auto():
|
|||
|
||||
def _get_device_on():
|
||||
"""Return a valid state on."""
|
||||
device = mock.Mock()
|
||||
device = mock.Mock(spec=DysonPureCoolLink)
|
||||
device.name = "Device_name"
|
||||
device.state = mock.Mock()
|
||||
device.state.fan_mode = "FAN"
|
||||
|
@ -84,8 +85,10 @@ class DysonTest(unittest.TestCase):
|
|||
assert len(devices) == 1
|
||||
assert devices[0].name == "Device_name"
|
||||
|
||||
device = _get_device_on()
|
||||
self.hass.data[dyson.DYSON_DEVICES] = [device]
|
||||
device_fan = _get_device_on()
|
||||
device_non_fan = _get_device_off()
|
||||
|
||||
self.hass.data[dyson.DYSON_DEVICES] = [device_fan, device_non_fan]
|
||||
dyson.setup_platform(self.hass, None, _add_device)
|
||||
|
||||
def test_dyson_set_speed(self):
|
||||
|
|
|
@ -6,11 +6,12 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, \
|
|||
STATE_OFF
|
||||
from homeassistant.components.sensor import dyson
|
||||
from tests.common import get_test_home_assistant
|
||||
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
|
||||
|
||||
|
||||
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
|
||||
|
@ -75,8 +76,9 @@ class DysonTest(unittest.TestCase):
|
|||
assert devices[3].name == "Device_name temperature"
|
||||
assert devices[4].name == "Device_name air quality"
|
||||
|
||||
device = _get_device_without_state()
|
||||
self.hass.data[dyson.DYSON_DEVICES] = [device]
|
||||
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)
|
||||
|
||||
def test_dyson_filter_life_sensor(self):
|
||||
|
|
|
@ -11,6 +11,7 @@ def _get_dyson_account_device_available():
|
|||
device = mock.Mock()
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.connect = mock.Mock(return_value=True)
|
||||
device.auto_connect = mock.Mock(return_value=True)
|
||||
return device
|
||||
|
||||
|
||||
|
@ -19,6 +20,15 @@ def _get_dyson_account_device_not_available():
|
|||
device = mock.Mock()
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.connect = mock.Mock(return_value=False)
|
||||
device.auto_connect = mock.Mock(return_value=False)
|
||||
return device
|
||||
|
||||
|
||||
def _get_dyson_account_device_error():
|
||||
"""Return an invalid device raising OSError while connecting."""
|
||||
device = mock.Mock()
|
||||
device.serial = "XX-XXXXX-XX"
|
||||
device.connect = mock.Mock(side_effect=OSError("Network error"))
|
||||
return device
|
||||
|
||||
|
||||
|
@ -77,7 +87,7 @@ class DysonTest(unittest.TestCase):
|
|||
self.assertEqual(mocked_login.call_count, 1)
|
||||
self.assertEqual(mocked_devices.call_count, 1)
|
||||
self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 1)
|
||||
self.assertEqual(mocked_discovery.call_count, 2)
|
||||
self.assertEqual(mocked_discovery.call_count, 3)
|
||||
|
||||
@mock.patch('libpurecoollink.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_not_available()])
|
||||
|
@ -100,6 +110,27 @@ class DysonTest(unittest.TestCase):
|
|||
self.assertEqual(mocked_devices.call_count, 1)
|
||||
self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0)
|
||||
|
||||
@mock.patch('libpurecoollink.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_error()])
|
||||
@mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
|
||||
def test_dyson_custom_conf_device_error(self, mocked_login,
|
||||
mocked_devices):
|
||||
"""Test device connection with device raising an exception."""
|
||||
dyson.setup(self.hass, {dyson.DOMAIN: {
|
||||
dyson.CONF_USERNAME: "email",
|
||||
dyson.CONF_PASSWORD: "password",
|
||||
dyson.CONF_LANGUAGE: "FR",
|
||||
dyson.CONF_DEVICES: [
|
||||
{
|
||||
"device_id": "XX-XXXXX-XX",
|
||||
"device_ip": "192.168.0.1"
|
||||
}
|
||||
]
|
||||
}})
|
||||
self.assertEqual(mocked_login.call_count, 1)
|
||||
self.assertEqual(mocked_devices.call_count, 1)
|
||||
self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0)
|
||||
|
||||
@mock.patch('homeassistant.helpers.discovery.load_platform')
|
||||
@mock.patch('libpurecoollink.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_available()])
|
||||
|
@ -141,7 +172,7 @@ class DysonTest(unittest.TestCase):
|
|||
self.assertEqual(mocked_login.call_count, 1)
|
||||
self.assertEqual(mocked_devices.call_count, 1)
|
||||
self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 1)
|
||||
self.assertEqual(mocked_discovery.call_count, 2)
|
||||
self.assertEqual(mocked_discovery.call_count, 3)
|
||||
|
||||
@mock.patch('libpurecoollink.dyson.DysonAccount.devices',
|
||||
return_value=[_get_dyson_account_device_not_available()])
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
"""Test the Dyson 360 eye robot vacuum component."""
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from libpurecoollink.dyson_360_eye import Dyson360Eye
|
||||
from libpurecoollink.const import Dyson360EyeMode, PowerMode
|
||||
|
||||
from homeassistant.components.vacuum import dyson
|
||||
from homeassistant.components.vacuum.dyson import Dyson360EyeDevice
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def _get_non_vacuum_device():
|
||||
"""Return a non vacuum device."""
|
||||
device = mock.Mock()
|
||||
device.name = "Device_Fan"
|
||||
device.state = None
|
||||
return device
|
||||
|
||||
|
||||
def _get_vacuum_device_cleaning():
|
||||
"""Return a vacuum device running."""
|
||||
device = mock.Mock(spec=Dyson360Eye)
|
||||
device.name = "Device_Vacuum"
|
||||
device.state = mock.MagicMock()
|
||||
device.state.state = Dyson360EyeMode.FULL_CLEAN_RUNNING
|
||||
device.state.battery_level = 85
|
||||
device.state.power_mode = PowerMode.QUIET
|
||||
device.state.position = (0, 0)
|
||||
return device
|
||||
|
||||
|
||||
def _get_vacuum_device_charging():
|
||||
"""Return a vacuum device charging."""
|
||||
device = mock.Mock(spec=Dyson360Eye)
|
||||
device.name = "Device_Vacuum"
|
||||
device.state = mock.MagicMock()
|
||||
device.state.state = Dyson360EyeMode.INACTIVE_CHARGING
|
||||
device.state.battery_level = 40
|
||||
device.state.power_mode = PowerMode.QUIET
|
||||
device.state.position = (0, 0)
|
||||
return device
|
||||
|
||||
|
||||
def _get_vacuum_device_pause():
|
||||
"""Return a vacuum device in pause."""
|
||||
device = mock.MagicMock(spec=Dyson360Eye)
|
||||
device.name = "Device_Vacuum"
|
||||
device.state = mock.MagicMock()
|
||||
device.state.state = Dyson360EyeMode.FULL_CLEAN_PAUSED
|
||||
device.state.battery_level = 40
|
||||
device.state.power_mode = PowerMode.QUIET
|
||||
device.state.position = (0, 0)
|
||||
return device
|
||||
|
||||
|
||||
def _get_vacuum_device_unknown_state():
|
||||
"""Return a vacuum device with unknown state."""
|
||||
device = mock.Mock(spec=Dyson360Eye)
|
||||
device.name = "Device_Vacuum"
|
||||
device.state = mock.MagicMock()
|
||||
device.state.state = "Unknown"
|
||||
return device
|
||||
|
||||
|
||||
class DysonTest(unittest.TestCase):
|
||||
"""Dyson 360 eye robot vacuum component test class."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_setup_component_with_no_devices(self):
|
||||
"""Test setup component with no devices."""
|
||||
self.hass.data[dyson.DYSON_DEVICES] = []
|
||||
add_devices = mock.MagicMock()
|
||||
dyson.setup_platform(self.hass, {}, add_devices)
|
||||
add_devices.assert_called_with([])
|
||||
|
||||
def test_setup_component(self):
|
||||
"""Test setup component with devices."""
|
||||
def _add_device(devices):
|
||||
assert len(devices) == 1
|
||||
assert devices[0].name == "Device_Vacuum"
|
||||
|
||||
device_vacuum = _get_vacuum_device_cleaning()
|
||||
device_non_vacuum = _get_non_vacuum_device()
|
||||
self.hass.data[dyson.DYSON_DEVICES] = [device_vacuum,
|
||||
device_non_vacuum]
|
||||
dyson.setup_platform(self.hass, {}, _add_device)
|
||||
|
||||
def test_on_message(self):
|
||||
"""Test when message is received."""
|
||||
device = _get_vacuum_device_cleaning()
|
||||
component = Dyson360EyeDevice(device)
|
||||
component.entity_id = "entity_id"
|
||||
component.schedule_update_ha_state = mock.Mock()
|
||||
component.on_message(mock.Mock())
|
||||
self.assertTrue(component.schedule_update_ha_state.called)
|
||||
|
||||
def test_should_poll(self):
|
||||
"""Test polling is disable."""
|
||||
device = _get_vacuum_device_cleaning()
|
||||
component = Dyson360EyeDevice(device)
|
||||
self.assertFalse(component.should_poll)
|
||||
|
||||
def test_properties(self):
|
||||
"""Test component properties."""
|
||||
device1 = _get_vacuum_device_cleaning()
|
||||
device2 = _get_vacuum_device_unknown_state()
|
||||
device3 = _get_vacuum_device_charging()
|
||||
component = Dyson360EyeDevice(device1)
|
||||
component2 = Dyson360EyeDevice(device2)
|
||||
component3 = Dyson360EyeDevice(device3)
|
||||
self.assertEqual(component.name, "Device_Vacuum")
|
||||
self.assertTrue(component.is_on)
|
||||
self.assertEqual(component.icon, "mdi:roomba")
|
||||
self.assertEqual(component.status, "Cleaning")
|
||||
self.assertEqual(component2.status, "Unknown")
|
||||
self.assertEqual(component.battery_level, 85)
|
||||
self.assertEqual(component.fan_speed, "Quiet")
|
||||
self.assertEqual(component.fan_speed_list, ["Quiet", "Max"])
|
||||
self.assertEqual(component.device_state_attributes['position'],
|
||||
'(0, 0)')
|
||||
self.assertTrue(component.available)
|
||||
self.assertEqual(component.supported_features, 255)
|
||||
self.assertEqual(component.battery_icon, "mdi:battery-80")
|
||||
self.assertEqual(component3.battery_icon, "mdi:battery-charging-40")
|
||||
|
||||
def test_turn_on(self):
|
||||
"""Test turn on vacuum."""
|
||||
device1 = _get_vacuum_device_charging()
|
||||
component1 = Dyson360EyeDevice(device1)
|
||||
component1.turn_on()
|
||||
self.assertTrue(device1.start.called)
|
||||
|
||||
device2 = _get_vacuum_device_pause()
|
||||
component2 = Dyson360EyeDevice(device2)
|
||||
component2.turn_on()
|
||||
self.assertTrue(device2.resume.called)
|
||||
|
||||
def test_turn_off(self):
|
||||
"""Test turn off vacuum."""
|
||||
device1 = _get_vacuum_device_cleaning()
|
||||
component1 = Dyson360EyeDevice(device1)
|
||||
component1.turn_off()
|
||||
self.assertTrue(device1.pause.called)
|
||||
|
||||
def test_stop(self):
|
||||
"""Test stop vacuum."""
|
||||
device1 = _get_vacuum_device_cleaning()
|
||||
component1 = Dyson360EyeDevice(device1)
|
||||
component1.stop()
|
||||
self.assertTrue(device1.pause.called)
|
||||
|
||||
def test_set_fan_speed(self):
|
||||
"""Test set fan speed vacuum."""
|
||||
device1 = _get_vacuum_device_cleaning()
|
||||
component1 = Dyson360EyeDevice(device1)
|
||||
component1.set_fan_speed("Max")
|
||||
device1.set_power_mode.assert_called_with(PowerMode.MAX)
|
||||
|
||||
def test_start_pause(self):
|
||||
"""Test start/pause."""
|
||||
device1 = _get_vacuum_device_charging()
|
||||
component1 = Dyson360EyeDevice(device1)
|
||||
component1.start_pause()
|
||||
self.assertTrue(device1.start.called)
|
||||
|
||||
device2 = _get_vacuum_device_pause()
|
||||
component2 = Dyson360EyeDevice(device2)
|
||||
component2.start_pause()
|
||||
self.assertTrue(device2.resume.called)
|
||||
|
||||
device3 = _get_vacuum_device_cleaning()
|
||||
component3 = Dyson360EyeDevice(device3)
|
||||
component3.start_pause()
|
||||
self.assertTrue(device3.pause.called)
|
||||
|
||||
def test_return_to_base(self):
|
||||
"""Test return to base."""
|
||||
device = _get_vacuum_device_pause()
|
||||
component = Dyson360EyeDevice(device)
|
||||
component.return_to_base()
|
||||
self.assertTrue(device.abort.called)
|
Loading…
Reference in New Issue