diff --git a/homeassistant/components/gree/entity.py b/homeassistant/components/gree/entity.py new file mode 100644 index 00000000000..0753a780f4b --- /dev/null +++ b/homeassistant/components/gree/entity.py @@ -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)}, + } diff --git a/homeassistant/components/gree/switch.py b/homeassistant/components/gree/switch.py index 7f659d7e64b..f8a5b4c0b3d 100644 --- a/homeassistant/components/gree/switch.py +++ b/homeassistant/components/gree/switch.py @@ -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() diff --git a/tests/components/gree/common.py b/tests/components/gree/common.py index 2c9c295da1c..40403377957 100644 --- a/tests/components/gree/common.py +++ b/tests/components/gree/common.py @@ -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, diff --git a/tests/components/gree/test_switch.py b/tests/components/gree/test_switch.py index 39ad536880c..3347fac00f5 100644 --- a/tests/components/gree/test_switch.py +++ b/tests/components/gree/test_switch.py @@ -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}"