Add Sensor Platform to Advantage Air (#41870)
* WIP Add Sensor platform * Code quality improvements * Readability improvements * Fix RSSI in fixture * Sensor platform tests * Created parent sensor class * Fix DOMAIN namespace * Code Coverage fix for impossible case * Use parent class * Add to fixture for code coverage * Description Update * Use consistent name for ADVANTAGE_AIR_DOMAIN Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com> * Set ADVANTAGE_AIR_DOMAIN where required Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>pull/42010/head
parent
2443f5d108
commit
e71d851973
|
@ -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__)
|
||||
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue