From afd79a675cfbccdc7983ee6afd0518556c1ae848 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 18 Apr 2021 18:36:34 +1000 Subject: [PATCH] Add set_myzone service to Advantage Air (#46934) * Add set_myzone service requested on forums * Add MyZone binary sensor for climate zones * Fixed Black on binary_sensor.py * Add the new entity * Fix spelling * Test myZone value * MyZone Binary Sensor test * Fixed new binary sensor tests * Fix removed dependancy * Correct fixture * Update homeassistant/components/advantage_air/binary_sensor.py Co-authored-by: Philip Allgaier * Updated services.yaml to use target Co-authored-by: Philip Allgaier --- .../components/advantage_air/binary_sensor.py | 27 +++++++++++ .../components/advantage_air/climate.py | 15 ++++++ .../components/advantage_air/services.yaml | 15 ++++-- .../advantage_air/test_binary_sensor.py | 48 +++++++++++++++++++ .../components/advantage_air/test_climate.py | 17 +++++++ .../fixtures/advantage_air/getSystemData.json | 12 ++--- 6 files changed, 125 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/advantage_air/binary_sensor.py b/homeassistant/components/advantage_air/binary_sensor.py index 50a7ef83895..f7b295c9634 100644 --- a/homeassistant/components/advantage_air/binary_sensor.py +++ b/homeassistant/components/advantage_air/binary_sensor.py @@ -24,6 +24,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Only add motion sensor when motion is enabled if zone["motionConfig"] >= 2: entities.append(AdvantageAirZoneMotion(instance, ac_key, zone_key)) + # Only add MyZone if it is available + if zone["type"] != 0: + entities.append(AdvantageAirZoneMyZone(instance, ac_key, zone_key)) async_add_entities(entities) @@ -73,3 +76,27 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity): def is_on(self): """Return if motion is detect.""" return self._zone["motion"] + + +class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity): + """Advantage Air Zone MyZone.""" + + @property + def name(self): + """Return the name.""" + return f'{self._zone["name"]} MyZone' + + @property + def unique_id(self): + """Return a unique id.""" + return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-myzone' + + @property + def is_on(self): + """Return if this zone is the myZone.""" + return self._zone["number"] == self._ac["myZone"] + + @property + def entity_registry_enabled_default(self): + """Return false to disable this entity by default.""" + return False diff --git a/homeassistant/components/advantage_air/climate.py b/homeassistant/components/advantage_air/climate.py index d3c4e897819..ca25edbda4f 100644 --- a/homeassistant/components/advantage_air/climate.py +++ b/homeassistant/components/advantage_air/climate.py @@ -15,6 +15,7 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS +from homeassistant.helpers import entity_platform from .const import ( ADVANTAGE_AIR_STATE_CLOSE, @@ -49,6 +50,7 @@ AC_HVAC_MODES = [ HVAC_MODE_FAN_ONLY, HVAC_MODE_DRY, ] +ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone" ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] PARALLEL_UPDATES = 0 @@ -68,6 +70,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(AdvantageAirZone(instance, ac_key, zone_key)) async_add_entities(entities) + platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + ADVANTAGE_AIR_SERVICE_SET_MYZONE, + {}, + "set_myzone", + ) + class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity): """AdvantageAir Climate class.""" @@ -233,3 +242,9 @@ class AdvantageAirZone(AdvantageAirClimateEntity): await self.async_change( {self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}} ) + + async def set_myzone(self, **kwargs): + """Set this zone as the 'MyZone'.""" + await self.async_change( + {self.ac_key: {"info": {"myZone": self._zone["number"]}}} + ) diff --git a/homeassistant/components/advantage_air/services.yaml b/homeassistant/components/advantage_air/services.yaml index aa222577b2f..e70208c4ac1 100644 --- a/homeassistant/components/advantage_air/services.yaml +++ b/homeassistant/components/advantage_air/services.yaml @@ -1,9 +1,18 @@ set_time_to: + name: Set Time To description: Control timers to turn the system on or off after a set number of minutes + target: + entity: + integration: advantage_air + domain: sensor fields: - entity_id: - description: Time To sensor entity - example: "sensor.ac_time_to_on" minutes: description: Minutes until action example: "60" +set_myzone: + name: Set MyZone + description: Change which zone is set as the reference for temperature control + target: + entity: + integration: advantage_air + domain: climate diff --git a/tests/components/advantage_air/test_binary_sensor.py b/tests/components/advantage_air/test_binary_sensor.py index dee4b9fd99a..275b5fc4e52 100644 --- a/tests/components/advantage_air/test_binary_sensor.py +++ b/tests/components/advantage_air/test_binary_sensor.py @@ -1,8 +1,12 @@ """Test the Advantage Air Binary Sensor Platform.""" +from datetime import timedelta +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt +from tests.common import async_fire_time_changed from tests.components.advantage_air import ( TEST_SET_RESPONSE, TEST_SET_URL, @@ -68,3 +72,47 @@ async def test_binary_sensor_async_setup_entry(hass, aioclient_mock): entry = registry.async_get(entity_id) assert entry assert entry.unique_id == "uniqueid-ac1-z02-motion" + + # Test First MyZone Sensor (disabled by default) + entity_id = "binary_sensor.zone_open_with_sensor_myzone" + + assert not hass.states.get(entity_id) + + registry.async_update_entity(entity_id=entity_id, disabled_by=None) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z01-myzone" + + # Test Second Motion Sensor (disabled by default) + entity_id = "binary_sensor.zone_closed_with_sensor_myzone" + + assert not hass.states.get(entity_id) + + registry.async_update_entity(entity_id=entity_id, disabled_by=None) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z02-myzone" diff --git a/tests/components/advantage_air/test_climate.py b/tests/components/advantage_air/test_climate.py index ea0cf025462..4374057bbb2 100644 --- a/tests/components/advantage_air/test_climate.py +++ b/tests/components/advantage_air/test_climate.py @@ -3,12 +3,14 @@ from json import loads from homeassistant.components.advantage_air.climate import ( + ADVANTAGE_AIR_SERVICE_SET_MYZONE, HASS_FAN_MODES, HASS_HVAC_MODES, ) from homeassistant.components.advantage_air.const import ( ADVANTAGE_AIR_STATE_OFF, ADVANTAGE_AIR_STATE_ON, + DOMAIN as ADVANTAGE_AIR_DOMAIN, ) from homeassistant.components.climate.const import ( ATTR_FAN_MODE, @@ -170,6 +172,21 @@ async def test_climate_async_setup_entry(hass, aioclient_mock): assert aioclient_mock.mock_calls[-1][0] == "GET" assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData" + # Test set_myair service + await hass.services.async_call( + ADVANTAGE_AIR_DOMAIN, + ADVANTAGE_AIR_SERVICE_SET_MYZONE, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + assert len(aioclient_mock.mock_calls) == 17 + assert aioclient_mock.mock_calls[-2][0] == "GET" + assert aioclient_mock.mock_calls[-2][1].path == "/setAircon" + data = loads(aioclient_mock.mock_calls[-2][1].query["json"]) + assert data["ac1"]["info"]["myZone"] == 1 + assert aioclient_mock.mock_calls[-1][0] == "GET" + assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData" + async def test_climate_async_failed_update(hass, aioclient_mock): """Test climate change failure.""" diff --git a/tests/fixtures/advantage_air/getSystemData.json b/tests/fixtures/advantage_air/getSystemData.json index 65dbf8d672b..19dda28fec1 100644 --- a/tests/fixtures/advantage_air/getSystemData.json +++ b/tests/fixtures/advantage_air/getSystemData.json @@ -9,7 +9,7 @@ "filterCleanStatus": 0, "freshAirStatus": "off", "mode": "vent", - "myZone": 0, + "myZone": 1, "name": "AC One", "setTemp": 24, "state": "on" @@ -38,7 +38,7 @@ "motion": 0, "motionConfig": 2, "name": "Zone closed with Sensor", - "number": 1, + "number": 2, "rssi": 10, "setTemp": 24, "state": "close", @@ -53,7 +53,7 @@ "motion": 1, "motionConfig": 1, "name": "Zone 3", - "number": 1, + "number": 3, "rssi": 25, "setTemp": 24, "state": "close", @@ -68,7 +68,7 @@ "motion": 1, "motionConfig": 1, "name": "Zone 4", - "number": 1, + "number": 4, "rssi": 75, "setTemp": 24, "state": "close", @@ -80,7 +80,7 @@ "maxDamper": 100, "measuredTemp": 25, "minDamper": 0, - "motion": 1, + "motion": 5, "motionConfig": 1, "name": "Zone 5", "number": 1, @@ -130,7 +130,7 @@ "motion": 0, "motionConfig": 0, "name": "Zone closed without sensor", - "number": 1, + "number": 2, "rssi": 0, "setTemp": 24, "state": "close",