Add more Gree switches (#49629)

* Support additional switch for gree devices

* Undo some changes not related to review

* Retry build

* Back to Gree 0.11.7
This commit is contained in:
Clifford Roche 2021-07-18 02:24:09 -04:00 committed by GitHub
parent 8f3166a955
commit b63e38f538
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 209 additions and 48 deletions

View file

@ -0,0 +1,37 @@
"""Entity object for shared properties of Gree entities."""
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .bridge import DeviceDataUpdateCoordinator
from .const import DOMAIN
class GreeEntity(CoordinatorEntity):
"""Generic Gree entity (base class)."""
def __init__(self, coordinator: DeviceDataUpdateCoordinator, desc: str) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._desc = desc
self._name = f"{coordinator.device.device_info.name}"
self._mac = coordinator.device.device_info.mac
@property
def name(self):
"""Return the name of the node."""
return f"{self._name} {self._desc}"
@property
def unique_id(self):
"""Return the unique id based for the node."""
return f"{self._mac}_{self._desc}"
@property
def device_info(self):
"""Return info about the device."""
return {
"identifiers": {(DOMAIN, self._mac)},
"name": self._name,
"manufacturer": "Gree",
"connections": {(CONNECTION_NETWORK_MAC, self._mac)},
}

View file

@ -3,11 +3,10 @@ from __future__ import annotations
from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import COORDINATORS, DISPATCH_DEVICE_DISCOVERED, DISPATCHERS, DOMAIN
from .entity import GreeEntity
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -16,7 +15,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
@callback
def init_device(coordinator):
"""Register the device."""
async_add_entities([GreeSwitchEntity(coordinator)])
async_add_entities(
[
GreePanelLightSwitchEntity(coordinator),
GreeQuietModeSwitchEntity(coordinator),
GreeFreshAirSwitchEntity(coordinator),
GreeXFanSwitchEntity(coordinator),
]
)
for coordinator in hass.data[DOMAIN][COORDINATORS]:
init_device(coordinator)
@ -26,40 +32,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
)
class GreeSwitchEntity(CoordinatorEntity, SwitchEntity):
"""Representation of a Gree HVAC device."""
class GreePanelLightSwitchEntity(GreeEntity, SwitchEntity):
"""Representation of the front panel light on the 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"
super().__init__(coordinator, "Panel Light")
@property
def icon(self) -> str | None:
"""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."""
@ -81,3 +65,93 @@ class GreeSwitchEntity(CoordinatorEntity, SwitchEntity):
self.coordinator.device.light = False
await self.coordinator.push_state_update()
self.async_write_ha_state()
class GreeQuietModeSwitchEntity(GreeEntity, SwitchEntity):
"""Representation of the quiet mode state of the device."""
def __init__(self, coordinator):
"""Initialize the Gree device."""
super().__init__(coordinator, "Quiet")
@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 state is turned on."""
return self.coordinator.device.quiet
async def async_turn_on(self, **kwargs):
"""Turn the entity on."""
self.coordinator.device.quiet = 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.quiet = False
await self.coordinator.push_state_update()
self.async_write_ha_state()
class GreeFreshAirSwitchEntity(GreeEntity, SwitchEntity):
"""Representation of the fresh air mode state of the device."""
def __init__(self, coordinator):
"""Initialize the Gree device."""
super().__init__(coordinator, "Fresh Air")
@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 state is turned on."""
return self.coordinator.device.fresh_air
async def async_turn_on(self, **kwargs):
"""Turn the entity on."""
self.coordinator.device.fresh_air = 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.fresh_air = False
await self.coordinator.push_state_update()
self.async_write_ha_state()
class GreeXFanSwitchEntity(GreeEntity, SwitchEntity):
"""Representation of the extra fan mode state of the device."""
def __init__(self, coordinator):
"""Initialize the Gree device."""
super().__init__(coordinator, "XFan")
@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 state is turned on."""
return self.coordinator.device.xfan
async def async_turn_on(self, **kwargs):
"""Turn the entity on."""
self.coordinator.device.xfan = 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.xfan = False
await self.coordinator.push_state_update()
self.async_write_ha_state()

View file

@ -62,6 +62,7 @@ def build_device_mock(name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc1122
horizontal_swing=0,
vertical_swing=0,
target_temperature=25,
current_temperature=25,
power=False,
sleep=False,
quiet=False,

View file

@ -1,5 +1,6 @@
"""Tests for gree component."""
from greeclimate.exceptions import DeviceTimeoutError
import pytest
from homeassistant.components.gree.const import DOMAIN as GREE_DOMAIN
from homeassistant.components.switch import DOMAIN
@ -16,7 +17,10 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
ENTITY_ID = f"{DOMAIN}.fake_device_1_panel_light"
ENTITY_ID_LIGHT_PANEL = f"{DOMAIN}.fake_device_1_panel_light"
ENTITY_ID_QUIET = f"{DOMAIN}.fake_device_1_quiet"
ENTITY_ID_FRESH_AIR = f"{DOMAIN}.fake_device_1_fresh_air"
ENTITY_ID_XFAN = f"{DOMAIN}.fake_device_1_xfan"
async def async_setup_gree(hass):
@ -26,23 +30,41 @@ async def async_setup_gree(hass):
await hass.async_block_till_done()
async def test_send_panel_light_on(hass):
@pytest.mark.parametrize(
"entity",
[
ENTITY_ID_LIGHT_PANEL,
ENTITY_ID_QUIET,
ENTITY_ID_FRESH_AIR,
ENTITY_ID_XFAN,
],
)
async def test_send_switch_on(hass, entity):
"""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},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_ON
async def test_send_panel_light_on_device_timeout(hass, device):
@pytest.mark.parametrize(
"entity",
[
ENTITY_ID_LIGHT_PANEL,
ENTITY_ID_QUIET,
ENTITY_ID_FRESH_AIR,
ENTITY_ID_XFAN,
],
)
async def test_send_switch_on_device_timeout(hass, device, entity):
"""Test for sending power on command to the device with a device timeout."""
device().push_state_update.side_effect = DeviceTimeoutError
@ -51,32 +73,50 @@ async def test_send_panel_light_on_device_timeout(hass, device):
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_ON
async def test_send_panel_light_off(hass):
@pytest.mark.parametrize(
"entity",
[
ENTITY_ID_LIGHT_PANEL,
ENTITY_ID_QUIET,
ENTITY_ID_FRESH_AIR,
ENTITY_ID_XFAN,
],
)
async def test_send_switch_off(hass, entity):
"""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},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_OFF
async def test_send_panel_light_toggle(hass):
@pytest.mark.parametrize(
"entity",
[
ENTITY_ID_LIGHT_PANEL,
ENTITY_ID_QUIET,
ENTITY_ID_FRESH_AIR,
ENTITY_ID_XFAN,
],
)
async def test_send_switch_toggle(hass, entity):
"""Test for sending power on command to the device."""
await async_setup_gree(hass)
@ -84,11 +124,11 @@ async def test_send_panel_light_toggle(hass):
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_ON
@ -96,11 +136,11 @@ async def test_send_panel_light_toggle(hass):
assert await hass.services.async_call(
DOMAIN,
SERVICE_TOGGLE,
{ATTR_ENTITY_ID: ENTITY_ID},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_OFF
@ -108,17 +148,26 @@ async def test_send_panel_light_toggle(hass):
assert await hass.services.async_call(
DOMAIN,
SERVICE_TOGGLE,
{ATTR_ENTITY_ID: ENTITY_ID},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
state = hass.states.get(entity)
assert state is not None
assert state.state == STATE_ON
async def test_panel_light_name(hass):
@pytest.mark.parametrize(
"entity,name",
[
(ENTITY_ID_LIGHT_PANEL, "Panel Light"),
(ENTITY_ID_QUIET, "Quiet"),
(ENTITY_ID_FRESH_AIR, "Fresh Air"),
(ENTITY_ID_XFAN, "XFan"),
],
)
async def test_entity_name(hass, entity, name):
"""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"
state = hass.states.get(entity)
assert state.attributes[ATTR_FRIENDLY_NAME] == f"fake-device-1 {name}"