diff --git a/homeassistant/components/advantage_air/__init__.py b/homeassistant/components/advantage_air/__init__.py index cd676728e0d..609e9044e0d 100644 --- a/homeassistant/components/advantage_air/__init__.py +++ b/homeassistant/components/advantage_air/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers.update_coordinator import ( from .const import ADVANTAGE_AIR_RETRY, DOMAIN ADVANTAGE_AIR_SYNC_INTERVAL = 15 -ADVANTAGE_AIR_PLATFORMS = ["binary_sensor", "climate", "cover"] +ADVANTAGE_AIR_PLATFORMS = ["climate", "cover", "binary_sensor", "sensor"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py new file mode 100644 index 00000000000..e124f7b2a08 --- /dev/null +++ b/homeassistant/components/advantage_air/sensor.py @@ -0,0 +1,151 @@ +"""Sensor platform for Advantage Air integration.""" +import voluptuous as vol + +from homeassistant.components.advantage_air import AdvantageAirEntity +from homeassistant.const import PERCENTAGE +from homeassistant.helpers import config_validation as cv, entity_platform + +from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN + +ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes" +ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min" +ADVANTAGE_AIR_SERVICE_SET_TIME_TO = "set_time_to" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up AdvantageAir sensor platform.""" + + instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id] + + entities = [] + for ac_key, ac_device in instance["coordinator"].data["aircons"].items(): + entities.append(AdvantageAirTimeTo(instance, ac_key, "On")) + entities.append(AdvantageAirTimeTo(instance, ac_key, "Off")) + for zone_key, zone in ac_device["zones"].items(): + # Only show damper sensors when zone is in temperature control + if zone["type"] != 0: + entities.append(AdvantageAirZoneVent(instance, ac_key, zone_key)) + # Only show wireless signal strength sensors when using wireless sensors + if zone["rssi"] > 0: + entities.append(AdvantageAirZoneSignal(instance, ac_key, zone_key)) + async_add_entities(entities) + + platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + ADVANTAGE_AIR_SERVICE_SET_TIME_TO, + {vol.Required("minutes"): cv.positive_int}, + "set_time_to", + ) + + +class AdvantageAirTimeTo(AdvantageAirEntity): + """Representation of Advantage Air timer control.""" + + def __init__(self, instance, ac_key, time_period): + """Initialize the Advantage Air timer control.""" + super().__init__(instance, ac_key) + self.time_period = time_period + + @property + def name(self): + """Return the name.""" + return f'{self._ac["name"]} Time To {self.time_period}' + + @property + def unique_id(self): + """Return a unique id.""" + return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{self.time_period}' + + @property + def state(self): + """Return the current value.""" + return self._ac[f"countDownTo{self.time_period}"] + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return ADVANTAGE_AIR_SET_COUNTDOWN_UNIT + + @property + def icon(self): + """Return a representative icon of the timer.""" + if self._ac[f"countDownTo{self.time_period}"] > 0: + return "mdi:timer-outline" + return "mdi:timer-off-outline" + + async def set_time_to(self, **kwargs): + """Set the timer value.""" + value = min(720, max(0, int(kwargs[ADVANTAGE_AIR_SET_COUNTDOWN_VALUE]))) + await self.async_change( + {self.ac_key: {"info": {f"countDownTo{self.time_period}": value}}} + ) + + +class AdvantageAirZoneVent(AdvantageAirEntity): + """Representation of Advantage Air Zone Vent Sensor.""" + + @property + def name(self): + """Return the name.""" + return f'{self._zone["name"]} Vent' + + @property + def unique_id(self): + """Return a unique id.""" + return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-vent' + + @property + def state(self): + """Return the current value of the air vent.""" + if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: + return self._zone["value"] + return 0 + + @property + def unit_of_measurement(self): + """Return the percent sign.""" + return PERCENTAGE + + @property + def icon(self): + """Return a representative icon.""" + if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: + return "mdi:fan" + return "mdi:fan-off" + + +class AdvantageAirZoneSignal(AdvantageAirEntity): + """Representation of Advantage Air Zone wireless signal sensor.""" + + @property + def name(self): + """Return the name.""" + return f'{self._zone["name"]} Signal' + + @property + def unique_id(self): + """Return a unique id.""" + return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-signal' + + @property + def state(self): + """Return the current value of the wireless signal.""" + return self._zone["rssi"] + + @property + def unit_of_measurement(self): + """Return the percent sign.""" + return PERCENTAGE + + @property + def icon(self): + """Return a representative icon.""" + if self._zone["rssi"] >= 80: + return "mdi:wifi-strength-4" + if self._zone["rssi"] >= 60: + return "mdi:wifi-strength-3" + if self._zone["rssi"] >= 40: + return "mdi:wifi-strength-2" + if self._zone["rssi"] >= 20: + return "mdi:wifi-strength-1" + return "mdi:wifi-strength-outline" diff --git a/homeassistant/components/advantage_air/services.yaml b/homeassistant/components/advantage_air/services.yaml new file mode 100644 index 00000000000..aa222577b2f --- /dev/null +++ b/homeassistant/components/advantage_air/services.yaml @@ -0,0 +1,9 @@ +set_time_to: + description: Control timers to turn the system on or off after a set number of minutes + fields: + entity_id: + description: Time To sensor entity + example: "sensor.ac_time_to_on" + minutes: + description: Minutes until action + example: "60" diff --git a/tests/components/advantage_air/test_sensor.py b/tests/components/advantage_air/test_sensor.py new file mode 100644 index 00000000000..e420ab978fd --- /dev/null +++ b/tests/components/advantage_air/test_sensor.py @@ -0,0 +1,126 @@ +"""Test the Advantage Air Sensor Platform.""" + +from json import loads + +from homeassistant.components.advantage_air.const import DOMAIN as ADVANTAGE_AIR_DOMAIN +from homeassistant.components.advantage_air.sensor import ( + ADVANTAGE_AIR_SERVICE_SET_TIME_TO, + ADVANTAGE_AIR_SET_COUNTDOWN_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID + +from tests.components.advantage_air import ( + TEST_SET_RESPONSE, + TEST_SET_URL, + TEST_SYSTEM_DATA, + TEST_SYSTEM_URL, + add_mock_config, +) + + +async def test_sensor_platform(hass, aioclient_mock): + """Test sensor platform.""" + + aioclient_mock.get( + TEST_SYSTEM_URL, + text=TEST_SYSTEM_DATA, + ) + aioclient_mock.get( + TEST_SET_URL, + text=TEST_SET_RESPONSE, + ) + await add_mock_config(hass) + + registry = await hass.helpers.entity_registry.async_get_registry() + + assert len(aioclient_mock.mock_calls) == 1 + + # Test First TimeToOn Sensor + entity_id = "sensor.ac_one_time_to_on" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 0 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-timetoOn" + + value = 20 + await hass.services.async_call( + ADVANTAGE_AIR_DOMAIN, + ADVANTAGE_AIR_SERVICE_SET_TIME_TO, + {ATTR_ENTITY_ID: [entity_id], ADVANTAGE_AIR_SET_COUNTDOWN_VALUE: value}, + blocking=True, + ) + assert len(aioclient_mock.mock_calls) == 3 + 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"]["countDownToOn"] == value + assert aioclient_mock.mock_calls[-1][0] == "GET" + assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData" + + # Test First TimeToOff Sensor + entity_id = "sensor.ac_one_time_to_off" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 10 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-timetoOff" + + value = 0 + await hass.services.async_call( + ADVANTAGE_AIR_DOMAIN, + ADVANTAGE_AIR_SERVICE_SET_TIME_TO, + {ATTR_ENTITY_ID: [entity_id], ADVANTAGE_AIR_SET_COUNTDOWN_VALUE: value}, + blocking=True, + ) + assert len(aioclient_mock.mock_calls) == 5 + 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"]["countDownToOff"] == value + assert aioclient_mock.mock_calls[-1][0] == "GET" + assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData" + + # Test First Zone Vent Sensor + entity_id = "sensor.zone_open_with_sensor_vent" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 100 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z01-vent" + + # Test Second Zone Vent Sensor + entity_id = "sensor.zone_closed_with_sensor_vent" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 0 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z02-vent" + + # Test First Zone Signal Sensor + entity_id = "sensor.zone_open_with_sensor_signal" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 40 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z01-signal" + + # Test Second Zone Signal Sensor + entity_id = "sensor.zone_closed_with_sensor_signal" + state = hass.states.get(entity_id) + assert state + assert int(state.state) == 10 + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == "uniqueid-ac1-z02-signal" diff --git a/tests/fixtures/advantage_air/getSystemData.json b/tests/fixtures/advantage_air/getSystemData.json index fe4de0faed9..88f184b2dc0 100644 --- a/tests/fixtures/advantage_air/getSystemData.json +++ b/tests/fixtures/advantage_air/getSystemData.json @@ -3,7 +3,7 @@ "ac1": { "info": { "climateControlModeIsRunning": false, - "countDownToOff": 0, + "countDownToOff": 10, "countDownToOn": 0, "fan": "high", "filterCleanStatus": 0, @@ -24,7 +24,7 @@ "motionConfig": 2, "name": "Zone open with Sensor", "number": 1, - "rssi": -50, + "rssi": 40, "setTemp": 24, "state": "open", "type": 1, @@ -39,7 +39,52 @@ "motionConfig": 2, "name": "Zone closed with Sensor", "number": 1, - "rssi": -50, + "rssi": 10, + "setTemp": 24, + "state": "close", + "type": 1, + "value": 0 + }, + "z03": { + "error": 0, + "maxDamper": 100, + "measuredTemp": 25, + "minDamper": 0, + "motion": 1, + "motionConfig": 1, + "name": "Zone 3", + "number": 1, + "rssi": 25, + "setTemp": 24, + "state": "close", + "type": 1, + "value": 0 + }, + "z04": { + "error": 0, + "maxDamper": 100, + "measuredTemp": 25, + "minDamper": 0, + "motion": 1, + "motionConfig": 1, + "name": "Zone 4", + "number": 1, + "rssi": 75, + "setTemp": 24, + "state": "close", + "type": 1, + "value": 0 + }, + "z05": { + "error": 0, + "maxDamper": 100, + "measuredTemp": 25, + "minDamper": 0, + "motion": 1, + "motionConfig": 1, + "name": "Zone 5", + "number": 1, + "rssi": 100, "setTemp": 24, "state": "close", "type": 1, @@ -51,7 +96,7 @@ "info": { "climateControlModeIsRunning": false, "countDownToOff": 0, - "countDownToOn": 0, + "countDownToOn": 20, "fan": "low", "filterCleanStatus": 1, "freshAirStatus": "none",