Add support for Gree device light panels (#42979)

This commit is contained in:
Clifford Roche 2020-12-28 16:32:04 -05:00 committed by GitHub
parent a22d9e54db
commit ee97023053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 342 additions and 271 deletions

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
"""Constants for the Gree Climate integration."""
DOMAIN = "gree"
COORDINATOR = "coordinator"
FAN_MEDIUM_LOW = "medium low"
FAN_MEDIUM_HIGH = "medium high"

View File

@ -10,4 +10,4 @@
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
}
}
}
}

View 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()

View File

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

View 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"