Add support for Gree device light panels (#42979)
This commit is contained in:
parent
a22d9e54db
commit
ee97023053
8 changed files with 342 additions and 271 deletions
|
@ -1,12 +1,14 @@
|
|||
"""The Gree Climate integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .bridge import CannotConnect, DeviceHelper
|
||||
from .const import DOMAIN
|
||||
from .bridge import CannotConnect, DeviceDataUpdateCoordinator, DeviceHelper
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -40,23 +42,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
)
|
||||
devices.append(device)
|
||||
|
||||
hass.data[DOMAIN]["devices"] = devices
|
||||
hass.data[DOMAIN]["pending"] = devices
|
||||
coordinators = [DeviceDataUpdateCoordinator(hass, d) for d in devices]
|
||||
await asyncio.gather(*[x.async_refresh() for x in coordinators])
|
||||
|
||||
hass.data[DOMAIN][COORDINATOR] = coordinators
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, CLIMATE_DOMAIN)
|
||||
)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, SWITCH_DOMAIN)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_forward_entry_unload(
|
||||
entry, CLIMATE_DOMAIN
|
||||
results = asyncio.gather(
|
||||
hass.config_entries.async_forward_entry_unload(entry, CLIMATE_DOMAIN),
|
||||
hass.config_entries.async_forward_entry_unload(entry, SWITCH_DOMAIN),
|
||||
)
|
||||
|
||||
unload_ok = all(await results)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop("devices", None)
|
||||
hass.data[DOMAIN].pop("pending", None)
|
||||
hass.data[DOMAIN].pop(CLIMATE_DOMAIN, None)
|
||||
hass.data[DOMAIN].pop(SWITCH_DOMAIN, None)
|
||||
|
||||
return unload_ok
|
||||
|
|
|
@ -1,11 +1,71 @@
|
|||
"""Helper and wrapper classes for Gree module."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from greeclimate.device import Device, DeviceInfo
|
||||
from greeclimate.discovery import Discovery
|
||||
from greeclimate.exceptions import DeviceNotBoundError
|
||||
from greeclimate.exceptions import DeviceNotBoundError, DeviceTimeoutError
|
||||
|
||||
from homeassistant import exceptions
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, MAX_ERRORS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Manages polling for state changes from the device."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, device: Device):
|
||||
"""Initialize the data update coordinator."""
|
||||
DataUpdateCoordinator.__init__(
|
||||
self,
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{DOMAIN}-{device.device_info.name}",
|
||||
update_interval=timedelta(seconds=60),
|
||||
)
|
||||
self.device = device
|
||||
self._error_count = 0
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Update the state of the device."""
|
||||
try:
|
||||
await self.device.update_state()
|
||||
except DeviceTimeoutError as error:
|
||||
self._error_count += 1
|
||||
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self.last_update_success and self._error_count >= MAX_ERRORS:
|
||||
_LOGGER.warning(
|
||||
"Device is unavailable: %s (%s)",
|
||||
self.name,
|
||||
self.device.device_info,
|
||||
)
|
||||
raise UpdateFailed(error) from error
|
||||
else:
|
||||
if not self.last_update_success and self._error_count:
|
||||
_LOGGER.warning(
|
||||
"Device is available: %s (%s)",
|
||||
self.name,
|
||||
str(self.device.device_info),
|
||||
)
|
||||
|
||||
self._error_count = 0
|
||||
|
||||
async def push_state_update(self):
|
||||
"""Send state updates to the physical device."""
|
||||
try:
|
||||
return await self.device.push_state_update()
|
||||
except DeviceTimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Timeout send state update to: %s (%s)",
|
||||
self.name,
|
||||
self.device.device_info,
|
||||
)
|
||||
|
||||
|
||||
class DeviceHelper:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Support for interface with a Gree climate systems."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
|
@ -10,7 +9,6 @@ from greeclimate.device import (
|
|||
TemperatureUnits,
|
||||
VerticalSwing,
|
||||
)
|
||||
from greeclimate.exceptions import DeviceTimeoutError
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
|
@ -45,12 +43,13 @@ from homeassistant.const import (
|
|||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
FAN_MEDIUM_HIGH,
|
||||
FAN_MEDIUM_LOW,
|
||||
MAX_ERRORS,
|
||||
MAX_TEMP,
|
||||
MIN_TEMP,
|
||||
TARGET_TEMPERATURE_STEP,
|
||||
|
@ -58,9 +57,6 @@ from .const import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=60)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
HVAC_MODES = {
|
||||
Mode.Auto: HVAC_MODE_AUTO,
|
||||
Mode.Cool: HVAC_MODE_COOL,
|
||||
|
@ -101,85 +97,21 @@ SUPPORTED_FEATURES = (
|
|||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Gree HVAC device from a config entry."""
|
||||
async_add_entities(
|
||||
GreeClimateEntity(device) for device in hass.data[DOMAIN].pop("pending")
|
||||
[
|
||||
GreeClimateEntity(coordinator)
|
||||
for coordinator in hass.data[DOMAIN][COORDINATOR]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class GreeClimateEntity(ClimateEntity):
|
||||
class GreeClimateEntity(CoordinatorEntity, ClimateEntity):
|
||||
"""Representation of a Gree HVAC device."""
|
||||
|
||||
def __init__(self, device):
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize the Gree device."""
|
||||
self._device = device
|
||||
self._name = device.device_info.name
|
||||
self._mac = device.device_info.mac
|
||||
self._available = False
|
||||
self._error_count = 0
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state of the device."""
|
||||
try:
|
||||
await self._device.update_state()
|
||||
|
||||
if not self._available and self._error_count:
|
||||
_LOGGER.warning(
|
||||
"Device is available: %s (%s)",
|
||||
self._name,
|
||||
str(self._device.device_info),
|
||||
)
|
||||
|
||||
self._available = True
|
||||
self._error_count = 0
|
||||
except DeviceTimeoutError:
|
||||
self._error_count += 1
|
||||
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self._available and self._error_count >= MAX_ERRORS:
|
||||
self._available = False
|
||||
_LOGGER.warning(
|
||||
"Device is unavailable: %s (%s)",
|
||||
self._name,
|
||||
self._device.device_info,
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self._available:
|
||||
self._available = False
|
||||
_LOGGER.exception(
|
||||
"Unknown exception caught during update by gree device: %s (%s)",
|
||||
self._name,
|
||||
self._device.device_info,
|
||||
)
|
||||
|
||||
async def _push_state_update(self):
|
||||
"""Send state updates to the physical device."""
|
||||
try:
|
||||
return await self._device.push_state_update()
|
||||
except DeviceTimeoutError:
|
||||
self._error_count += 1
|
||||
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self._available and self._error_count >= MAX_ERRORS:
|
||||
self._available = False
|
||||
_LOGGER.warning(
|
||||
"Device timedout while sending state update: %s (%s)",
|
||||
self._name,
|
||||
self._device.device_info,
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Under normal conditions GREE units timeout every once in a while
|
||||
if self._available:
|
||||
self._available = False
|
||||
_LOGGER.exception(
|
||||
"Unknown exception caught while sending state update to: %s (%s)",
|
||||
self._name,
|
||||
self._device.device_info,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the device is available."""
|
||||
return self._available
|
||||
super().__init__(coordinator)
|
||||
self._name = coordinator.device.device_info.name
|
||||
self._mac = coordinator.device.device_info.mac
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@ -204,7 +136,7 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the temperature units for the device."""
|
||||
units = self._device.temperature_units
|
||||
units = self.coordinator.device.temperature_units
|
||||
return TEMP_CELSIUS if units == TemperatureUnits.C else TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
|
@ -220,7 +152,7 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def target_temperature(self) -> float:
|
||||
"""Return the target temperature for the device."""
|
||||
return self._device.target_temperature
|
||||
return self.coordinator.device.target_temperature
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -234,8 +166,9 @@ class GreeClimateEntity(ClimateEntity):
|
|||
self._name,
|
||||
)
|
||||
|
||||
self._device.target_temperature = round(temperature)
|
||||
await self._push_state_update()
|
||||
self.coordinator.device.target_temperature = round(temperature)
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
|
@ -255,10 +188,10 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current HVAC mode for the device."""
|
||||
if not self._device.power:
|
||||
if not self.coordinator.device.power:
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
return HVAC_MODES.get(self._device.mode)
|
||||
return HVAC_MODES.get(self.coordinator.device.mode)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
|
@ -272,15 +205,17 @@ class GreeClimateEntity(ClimateEntity):
|
|||
)
|
||||
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self._device.power = False
|
||||
await self._push_state_update()
|
||||
self.coordinator.device.power = False
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
if not self._device.power:
|
||||
self._device.power = True
|
||||
if not self.coordinator.device.power:
|
||||
self.coordinator.device.power = True
|
||||
|
||||
self._device.mode = HVAC_MODES_REVERSE.get(hvac_mode)
|
||||
await self._push_state_update()
|
||||
self.coordinator.device.mode = HVAC_MODES_REVERSE.get(hvac_mode)
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
|
@ -292,13 +227,13 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def preset_mode(self) -> str:
|
||||
"""Return the current preset mode for the device."""
|
||||
if self._device.steady_heat:
|
||||
if self.coordinator.device.steady_heat:
|
||||
return PRESET_AWAY
|
||||
if self._device.power_save:
|
||||
if self.coordinator.device.power_save:
|
||||
return PRESET_ECO
|
||||
if self._device.sleep:
|
||||
if self.coordinator.device.sleep:
|
||||
return PRESET_SLEEP
|
||||
if self._device.turbo:
|
||||
if self.coordinator.device.turbo:
|
||||
return PRESET_BOOST
|
||||
return PRESET_NONE
|
||||
|
||||
|
@ -313,21 +248,22 @@ class GreeClimateEntity(ClimateEntity):
|
|||
self._name,
|
||||
)
|
||||
|
||||
self._device.steady_heat = False
|
||||
self._device.power_save = False
|
||||
self._device.turbo = False
|
||||
self._device.sleep = False
|
||||
self.coordinator.device.steady_heat = False
|
||||
self.coordinator.device.power_save = False
|
||||
self.coordinator.device.turbo = False
|
||||
self.coordinator.device.sleep = False
|
||||
|
||||
if preset_mode == PRESET_AWAY:
|
||||
self._device.steady_heat = True
|
||||
self.coordinator.device.steady_heat = True
|
||||
elif preset_mode == PRESET_ECO:
|
||||
self._device.power_save = True
|
||||
self.coordinator.device.power_save = True
|
||||
elif preset_mode == PRESET_BOOST:
|
||||
self._device.turbo = True
|
||||
self.coordinator.device.turbo = True
|
||||
elif preset_mode == PRESET_SLEEP:
|
||||
self._device.sleep = True
|
||||
self.coordinator.device.sleep = True
|
||||
|
||||
await self._push_state_update()
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> List[str]:
|
||||
|
@ -337,7 +273,7 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def fan_mode(self) -> str:
|
||||
"""Return the current fan mode for the device."""
|
||||
speed = self._device.fan_speed
|
||||
speed = self.coordinator.device.fan_speed
|
||||
return FAN_MODES.get(speed)
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
|
@ -345,8 +281,9 @@ class GreeClimateEntity(ClimateEntity):
|
|||
if fan_mode not in FAN_MODES_REVERSE:
|
||||
raise ValueError(f"Invalid fan mode: {fan_mode}")
|
||||
|
||||
self._device.fan_speed = FAN_MODES_REVERSE.get(fan_mode)
|
||||
await self._push_state_update()
|
||||
self.coordinator.device.fan_speed = FAN_MODES_REVERSE.get(fan_mode)
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> List[str]:
|
||||
|
@ -356,8 +293,8 @@ class GreeClimateEntity(ClimateEntity):
|
|||
@property
|
||||
def swing_mode(self) -> str:
|
||||
"""Return the current swing mode for the device."""
|
||||
h_swing = self._device.horizontal_swing == HorizontalSwing.FullSwing
|
||||
v_swing = self._device.vertical_swing == VerticalSwing.FullSwing
|
||||
h_swing = self.coordinator.device.horizontal_swing == HorizontalSwing.FullSwing
|
||||
v_swing = self.coordinator.device.vertical_swing == VerticalSwing.FullSwing
|
||||
|
||||
if h_swing and v_swing:
|
||||
return SWING_BOTH
|
||||
|
@ -378,14 +315,15 @@ class GreeClimateEntity(ClimateEntity):
|
|||
self._name,
|
||||
)
|
||||
|
||||
self._device.horizontal_swing = HorizontalSwing.Center
|
||||
self._device.vertical_swing = VerticalSwing.FixedMiddle
|
||||
self.coordinator.device.horizontal_swing = HorizontalSwing.Center
|
||||
self.coordinator.device.vertical_swing = VerticalSwing.FixedMiddle
|
||||
if swing_mode in (SWING_BOTH, SWING_HORIZONTAL):
|
||||
self._device.horizontal_swing = HorizontalSwing.FullSwing
|
||||
self.coordinator.device.horizontal_swing = HorizontalSwing.FullSwing
|
||||
if swing_mode in (SWING_BOTH, SWING_VERTICAL):
|
||||
self._device.vertical_swing = VerticalSwing.FullSwing
|
||||
self.coordinator.device.vertical_swing = VerticalSwing.FullSwing
|
||||
|
||||
await self._push_state_update()
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def swing_modes(self) -> List[str]:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Constants for the Gree Climate integration."""
|
||||
|
||||
DOMAIN = "gree"
|
||||
COORDINATOR = "coordinator"
|
||||
|
||||
FAN_MEDIUM_LOW = "medium low"
|
||||
FAN_MEDIUM_HIGH = "medium high"
|
||||
|
|
|
@ -10,4 +10,4 @@
|
|||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
homeassistant/components/gree/switch.py
Normal file
78
homeassistant/components/gree/switch.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
"""Support for interface with a Gree climate systems."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Gree HVAC device from a config entry."""
|
||||
async_add_entities(
|
||||
[
|
||||
GreeSwitchEntity(coordinator)
|
||||
for coordinator in hass.data[DOMAIN][COORDINATOR]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class GreeSwitchEntity(CoordinatorEntity, SwitchEntity):
|
||||
"""Representation of a Gree HVAC device."""
|
||||
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize the Gree device."""
|
||||
super().__init__(coordinator)
|
||||
self._name = coordinator.device.device_info.name + " Panel Light"
|
||||
self._mac = coordinator.device.device_info.mac
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique id for the device."""
|
||||
return f"{self._mac}-panel-light"
|
||||
|
||||
@property
|
||||
def icon(self) -> Optional[str]:
|
||||
"""Return the icon for the device."""
|
||||
return "mdi:lightbulb"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device specific attributes."""
|
||||
return {
|
||||
"name": self._name,
|
||||
"identifiers": {(DOMAIN, self._mac)},
|
||||
"manufacturer": "Gree",
|
||||
"connections": {(CONNECTION_NETWORK_MAC, self._mac)},
|
||||
}
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return DEVICE_CLASS_SWITCH
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the light is turned on."""
|
||||
return self.coordinator.device.light
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the entity on."""
|
||||
self.coordinator.device.light = True
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the entity off."""
|
||||
self.coordinator.device.light = False
|
||||
await self.coordinator.push_state_update()
|
||||
self.async_write_ha_state()
|
|
@ -159,10 +159,15 @@ async def test_update_connection_failure(hass, discovery, device, mock_now):
|
|||
|
||||
async def test_update_connection_failure_recovery(hass, discovery, device, mock_now):
|
||||
"""Testing update hvac connection failure recovery."""
|
||||
device().update_state.side_effect = [DeviceTimeoutError, DEFAULT_MOCK]
|
||||
device().update_state.side_effect = [
|
||||
DeviceTimeoutError,
|
||||
DeviceTimeoutError,
|
||||
DEFAULT_MOCK,
|
||||
]
|
||||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
# First update becomes unavailable
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
|
@ -172,6 +177,7 @@ async def test_update_connection_failure_recovery(hass, discovery, device, mock_
|
|||
assert state.name == "fake-device-1"
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Second update restores the connection
|
||||
next_update = mock_now + timedelta(minutes=10)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
|
@ -188,11 +194,6 @@ async def test_update_unhandled_exception(hass, discovery, device, mock_now):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
@ -221,21 +222,9 @@ async def test_send_command_device_timeout(hass, discovery, device, mock_now):
|
|||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
device().update_state.side_effect = DeviceTimeoutError
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
|
||||
# Second update to make an initial error (device is still available)
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
# Second attempt should make the device unavailable
|
||||
# Send failure should not raise exceptions or change device state
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
|
@ -246,47 +235,13 @@ async def test_send_command_device_timeout(hass, discovery, device, mock_now):
|
|||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_send_command_device_unknown_error(hass, discovery, device, mock_now):
|
||||
"""Test for sending power on command to the device with a device timeout."""
|
||||
device().update_state.side_effect = [DEFAULT_MOCK, Exception]
|
||||
device().push_state_update.side_effect = Exception
|
||||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# First update to make the device available
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == "fake-device-1"
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_AUTO},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_send_power_on(hass, discovery, device, mock_now):
|
||||
"""Test for sending power on command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
|
@ -305,11 +260,6 @@ async def test_send_power_on_device_timeout(hass, discovery, device, mock_now):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
|
@ -326,11 +276,6 @@ async def test_send_target_temperature(hass, discovery, device, mock_now):
|
|||
"""Test for sending target temperature command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
|
@ -351,11 +296,6 @@ async def test_send_target_temperature_device_timeout(
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
|
@ -374,11 +314,6 @@ async def test_update_target_temperature(hass, discovery, device, mock_now):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 32
|
||||
|
@ -391,11 +326,6 @@ async def test_send_preset_mode(hass, discovery, device, mock_now, preset):
|
|||
"""Test for sending preset mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
|
@ -412,11 +342,6 @@ async def test_send_invalid_preset_mode(hass, discovery, device, mock_now):
|
|||
"""Test for sending preset mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
@ -441,11 +366,6 @@ async def test_send_preset_mode_device_timeout(
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
|
@ -470,11 +390,6 @@ async def test_update_preset_mode(hass, discovery, device, mock_now, preset):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_PRESET_MODE) == preset
|
||||
|
@ -495,11 +410,6 @@ async def test_send_hvac_mode(hass, discovery, device, mock_now, hvac_mode):
|
|||
"""Test for sending hvac mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
|
@ -524,11 +434,6 @@ async def test_send_hvac_mode_device_timeout(
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
|
@ -559,11 +464,6 @@ async def test_update_hvac_mode(hass, discovery, device, mock_now, hvac_mode):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == hvac_mode
|
||||
|
@ -577,11 +477,6 @@ async def test_send_fan_mode(hass, discovery, device, mock_now, fan_mode):
|
|||
"""Test for sending fan mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
|
@ -598,11 +493,6 @@ async def test_send_invalid_fan_mode(hass, discovery, device, mock_now):
|
|||
"""Test for sending fan mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
@ -628,11 +518,6 @@ async def test_send_fan_mode_device_timeout(
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
|
@ -655,11 +540,6 @@ async def test_update_fan_mode(hass, discovery, device, mock_now, fan_mode):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_FAN_MODE) == fan_mode
|
||||
|
@ -672,11 +552,6 @@ async def test_send_swing_mode(hass, discovery, device, mock_now, swing_mode):
|
|||
"""Test for sending swing mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
|
@ -693,11 +568,6 @@ async def test_send_invalid_swing_mode(hass, discovery, device, mock_now):
|
|||
"""Test for sending swing mode command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
@ -722,11 +592,6 @@ async def test_send_swing_mode_device_timeout(
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
|
@ -757,11 +622,6 @@ async def test_update_swing_mode(hass, discovery, device, mock_now, swing_mode):
|
|||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
next_update = mock_now + timedelta(minutes=5)
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_SWING_MODE) == swing_mode
|
||||
|
|
124
tests/components/gree/test_switch.py
Normal file
124
tests/components/gree/test_switch.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
"""Tests for gree component."""
|
||||
from greeclimate.exceptions import DeviceTimeoutError
|
||||
|
||||
from homeassistant.components.gree.const import DOMAIN as GREE_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
ENTITY_ID = f"{DOMAIN}.fake_device_1_panel_light"
|
||||
|
||||
|
||||
async def async_setup_gree(hass):
|
||||
"""Set up the gree switch platform."""
|
||||
MockConfigEntry(domain=GREE_DOMAIN).add_to_hass(hass)
|
||||
await async_setup_component(hass, GREE_DOMAIN, {GREE_DOMAIN: {DOMAIN: {}}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_send_panel_light_on(hass, discovery, device):
|
||||
"""Test for sending power on command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
async def test_send_panel_light_on_device_timeout(hass, discovery, device):
|
||||
"""Test for sending power on command to the device with a device timeout."""
|
||||
device().push_state_update.side_effect = DeviceTimeoutError
|
||||
|
||||
await async_setup_gree(hass)
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
async def test_send_panel_light_off(hass, discovery, device):
|
||||
"""Test for sending power on command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_send_panel_light_toggle(hass, discovery, device):
|
||||
"""Test for sending power on command to the device."""
|
||||
await async_setup_gree(hass)
|
||||
|
||||
# Turn the service on first
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Toggle it off
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TOGGLE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Toggle is back on
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TOGGLE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
async def test_panel_light_name(hass, discovery, device):
|
||||
"""Test for name property."""
|
||||
await async_setup_gree(hass)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake-device-1 Panel Light"
|
Loading…
Add table
Reference in a new issue