mirror of https://github.com/home-assistant/core
Add Advantage Air Integration (#40159)
Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
cd21786536
commit
3e41c682f4
|
@ -17,6 +17,7 @@ homeassistant/components/abode/* @shred86
|
|||
homeassistant/components/accuweather/* @bieniu
|
||||
homeassistant/components/acmeda/* @atmurray
|
||||
homeassistant/components/adguard/* @frenck
|
||||
homeassistant/components/advantage_air/* @Bre77
|
||||
homeassistant/components/agent_dvr/* @ispysoftware
|
||||
homeassistant/components/airly/* @bieniu
|
||||
homeassistant/components/airvisual/* @bachya
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
"""Advantage Air climate integration."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from advantage_air import ApiError, advantage_air
|
||||
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import ADVANTAGE_AIR_RETRY, DOMAIN
|
||||
|
||||
ADVANTAGE_AIR_SYNC_INTERVAL = 15
|
||||
ADVANTAGE_AIR_PLATFORMS = ["climate"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up AdvantageAir."""
|
||||
hass.data[DOMAIN] = {}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up AdvantageAir Config."""
|
||||
ip_address = config_entry.data[CONF_IP_ADDRESS]
|
||||
port = config_entry.data[CONF_PORT]
|
||||
api = advantage_air(
|
||||
ip_address,
|
||||
port=port,
|
||||
session=async_get_clientsession(hass),
|
||||
retry=ADVANTAGE_AIR_RETRY,
|
||||
)
|
||||
|
||||
async def async_get():
|
||||
try:
|
||||
return await api.async_get()
|
||||
except ApiError as err:
|
||||
raise UpdateFailed(err) from err
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Advantage Air",
|
||||
update_method=async_get,
|
||||
update_interval=timedelta(seconds=ADVANTAGE_AIR_SYNC_INTERVAL),
|
||||
)
|
||||
|
||||
async def async_change(change):
|
||||
try:
|
||||
if await api.async_change(change):
|
||||
await coordinator.async_refresh()
|
||||
except ApiError as err:
|
||||
_LOGGER.warning(err)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.data:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id] = {
|
||||
"coordinator": coordinator,
|
||||
"async_change": async_change,
|
||||
}
|
||||
|
||||
for platform in ADVANTAGE_AIR_PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
)
|
||||
|
||||
return True
|
|
@ -0,0 +1,297 @@
|
|||
"""Climate platform for Advantage Air integration."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
FAN_OFF,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
ADVANTAGE_AIR_STATE_CLOSE,
|
||||
ADVANTAGE_AIR_STATE_OFF,
|
||||
ADVANTAGE_AIR_STATE_ON,
|
||||
ADVANTAGE_AIR_STATE_OPEN,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
ADVANTAGE_AIR_HVAC_MODES = {
|
||||
"heat": HVAC_MODE_HEAT,
|
||||
"cool": HVAC_MODE_COOL,
|
||||
"vent": HVAC_MODE_FAN_ONLY,
|
||||
"dry": HVAC_MODE_DRY,
|
||||
}
|
||||
HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()}
|
||||
|
||||
ADVANTAGE_AIR_FAN_MODES = {
|
||||
"auto": FAN_AUTO,
|
||||
"low": FAN_LOW,
|
||||
"medium": FAN_MEDIUM,
|
||||
"high": FAN_HIGH,
|
||||
}
|
||||
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
|
||||
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
|
||||
|
||||
AC_HVAC_MODES = [
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_DRY,
|
||||
]
|
||||
ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up AdvantageAir climate platform."""
|
||||
|
||||
instance = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities = []
|
||||
for ac_key in instance["coordinator"].data["aircons"]:
|
||||
entities.append(AdvantageAirAC(instance, ac_key))
|
||||
for zone_key in instance["coordinator"].data["aircons"][ac_key]["zones"]:
|
||||
# Only add zone climate control when zone is in temperature control
|
||||
if (
|
||||
instance["coordinator"].data["aircons"][ac_key]["zones"][zone_key][
|
||||
"type"
|
||||
]
|
||||
!= 0
|
||||
):
|
||||
entities.append(AdvantageAirZone(instance, ac_key, zone_key))
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AdvantageAirClimateEntity(CoordinatorEntity, ClimateEntity):
|
||||
"""AdvantageAir Climate class."""
|
||||
|
||||
def __init__(self, instance):
|
||||
"""Initialize the base Advantage Air climate entity."""
|
||||
super().__init__(instance["coordinator"])
|
||||
self.async_change = instance["async_change"]
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the temperature unit."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported temperature step."""
|
||||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum supported temperature."""
|
||||
return 32
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum supported temperature."""
|
||||
return 16
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return parent device information."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.coordinator.data["system"]["rid"])},
|
||||
"name": self.coordinator.data["system"]["name"],
|
||||
"manufacturer": "Advantage Air",
|
||||
"model": self.coordinator.data["system"]["sysType"],
|
||||
"sw_version": self.coordinator.data["system"]["myAppRev"],
|
||||
}
|
||||
|
||||
|
||||
class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
"""AdvantageAir AC unit."""
|
||||
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize the Advantage Air AC climate entity."""
|
||||
super().__init__(instance)
|
||||
self.ac_key = ac_key
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["info"]["name"]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}'
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the current target temperature."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["info"]["setTemp"]
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return the current HVAC modes."""
|
||||
if (
|
||||
self.coordinator.data["aircons"][self.ac_key]["info"]["state"]
|
||||
== ADVANTAGE_AIR_STATE_ON
|
||||
):
|
||||
return ADVANTAGE_AIR_HVAC_MODES.get(
|
||||
self.coordinator.data["aircons"][self.ac_key]["info"]["mode"]
|
||||
)
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the supported HVAC modes."""
|
||||
return AC_HVAC_MODES
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return the current fan modes."""
|
||||
return ADVANTAGE_AIR_FAN_MODES.get(
|
||||
self.coordinator.data["aircons"][self.ac_key]["info"]["fan"], FAN_OFF
|
||||
)
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
"""Return the supported fan modes."""
|
||||
return [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return additional attributes about AC unit."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["info"]
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set the HVAC Mode and State."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self.async_change(
|
||||
{self.ac_key: {"info": {"state": ADVANTAGE_AIR_STATE_OFF}}}
|
||||
)
|
||||
else:
|
||||
await self.async_change(
|
||||
{
|
||||
self.ac_key: {
|
||||
"info": {
|
||||
"state": ADVANTAGE_AIR_STATE_ON,
|
||||
"mode": HASS_HVAC_MODES.get(hvac_mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set the Fan Mode."""
|
||||
await self.async_change(
|
||||
{self.ac_key: {"info": {"fan": HASS_FAN_MODES.get(fan_mode)}}}
|
||||
)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set the Temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
await self.async_change({self.ac_key: {"info": {"setTemp": temp}}})
|
||||
|
||||
|
||||
class AdvantageAirZone(AdvantageAirClimateEntity):
|
||||
"""AdvantageAir Zone control."""
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize the Advantage Air Zone climate entity."""
|
||||
super().__init__(instance)
|
||||
self.ac_key = ac_key
|
||||
self.zone_key = zone_key
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key][
|
||||
"name"
|
||||
]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}'
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key][
|
||||
"measuredTemp"
|
||||
]
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key][
|
||||
"setTemp"
|
||||
]
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return the current HVAC modes."""
|
||||
if (
|
||||
self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key][
|
||||
"state"
|
||||
]
|
||||
== ADVANTAGE_AIR_STATE_OPEN
|
||||
):
|
||||
return HVAC_MODE_FAN_ONLY
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return supported HVAC modes."""
|
||||
return ZONE_HVAC_MODES
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return additional attributes about Zone."""
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set the HVAC Mode and State."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self.async_change(
|
||||
{
|
||||
self.ac_key: {
|
||||
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_CLOSE}}
|
||||
}
|
||||
}
|
||||
)
|
||||
else:
|
||||
await self.async_change(
|
||||
{
|
||||
self.ac_key: {
|
||||
"zones": {self.zone_key: {"state": ADVANTAGE_AIR_STATE_OPEN}}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set the Temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
await self.async_change(
|
||||
{self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}}
|
||||
)
|
|
@ -0,0 +1,61 @@
|
|||
"""Config Flow for Advantage Air integration."""
|
||||
import logging
|
||||
|
||||
from advantage_air import ApiError, advantage_air
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import ADVANTAGE_AIR_RETRY, DOMAIN
|
||||
|
||||
ADVANTAGE_AIR_DEFAULT_PORT = 2025
|
||||
|
||||
ADVANTAGE_AIR_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_IP_ADDRESS): str,
|
||||
vol.Optional(CONF_PORT, default=ADVANTAGE_AIR_DEFAULT_PORT): int,
|
||||
}
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdvantageAirConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Config Advantage Air API connection."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Get configuration from the user."""
|
||||
errors = {}
|
||||
if user_input:
|
||||
ip_address = user_input[CONF_IP_ADDRESS]
|
||||
port = user_input[CONF_PORT]
|
||||
|
||||
try:
|
||||
data = await advantage_air(
|
||||
ip_address,
|
||||
port=port,
|
||||
session=async_get_clientsession(self.hass),
|
||||
retry=ADVANTAGE_AIR_RETRY,
|
||||
).async_get(1)
|
||||
except ApiError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(data["system"]["rid"])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=data["system"]["name"],
|
||||
data=user_input,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=ADVANTAGE_AIR_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
"""Constants used by Advantage Air integration."""
|
||||
DOMAIN = "advantage_air"
|
||||
ADVANTAGE_AIR_RETRY = 5
|
||||
ADVANTAGE_AIR_STATE_OPEN = "open"
|
||||
ADVANTAGE_AIR_STATE_CLOSE = "close"
|
||||
ADVANTAGE_AIR_STATE_ON = "on"
|
||||
ADVANTAGE_AIR_STATE_OFF = "off"
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"domain": "advantage_air",
|
||||
"name": "Advantage Air",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/advantage_air",
|
||||
"codeowners": [
|
||||
"@Bre77"
|
||||
],
|
||||
"requirements": [
|
||||
"advantage_air==0.2.1"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"config": {
|
||||
"flow_title": "Advantage Air Setup",
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"description": "Connect to the API of your Advantage Air wall mounted tablet.",
|
||||
"title": "Connect"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Advantage Air"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"flow_title": "Advantage Air Setup",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"description": "Connect to the API of your Advantage Air wall mounted tablet.",
|
||||
"title": "Connect"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Advantage Air"
|
||||
}
|
|
@ -10,6 +10,7 @@ FLOWS = [
|
|||
"accuweather",
|
||||
"acmeda",
|
||||
"adguard",
|
||||
"advantage_air",
|
||||
"agent_dvr",
|
||||
"airly",
|
||||
"airvisual",
|
||||
|
|
|
@ -122,6 +122,9 @@ adext==0.3
|
|||
# homeassistant.components.adguard
|
||||
adguardhome==0.4.2
|
||||
|
||||
# homeassistant.components.advantage_air
|
||||
advantage_air==0.2.1
|
||||
|
||||
# homeassistant.components.frontier_silicon
|
||||
afsapi==0.0.4
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@ adext==0.3
|
|||
# homeassistant.components.adguard
|
||||
adguardhome==0.4.2
|
||||
|
||||
# homeassistant.components.advantage_air
|
||||
advantage_air==0.2.1
|
||||
|
||||
# homeassistant.components.agent_dvr
|
||||
agent-py==0.0.23
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
"""Tests for the Advantage Air component."""
|
||||
|
||||
from homeassistant.components.advantage_air.const import DOMAIN
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
TEST_SYSTEM_DATA = load_fixture("advantage_air/getSystemData.json")
|
||||
TEST_SET_RESPONSE = load_fixture("advantage_air/setAircon.json")
|
||||
|
||||
USER_INPUT = {
|
||||
CONF_IP_ADDRESS: "1.2.3.4",
|
||||
CONF_PORT: 2025,
|
||||
}
|
||||
|
||||
TEST_SYSTEM_URL = (
|
||||
f"http://{USER_INPUT[CONF_IP_ADDRESS]}:{USER_INPUT[CONF_PORT]}/getSystemData"
|
||||
)
|
||||
TEST_SET_URL = f"http://{USER_INPUT[CONF_IP_ADDRESS]}:{USER_INPUT[CONF_PORT]}/setAircon"
|
||||
|
||||
|
||||
async def add_mock_config(hass):
|
||||
"""Create a fake Advantage Air Config Entry."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="test entry",
|
||||
unique_id="0123456",
|
||||
data=USER_INPUT,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return entry
|
|
@ -0,0 +1,176 @@
|
|||
"""Test the Advantage Air Climate Platform."""
|
||||
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HVAC_MODE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
FAN_OFF,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_OFF,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
|
||||
|
||||
from tests.components.advantage_air import (
|
||||
TEST_SET_RESPONSE,
|
||||
TEST_SET_URL,
|
||||
TEST_SYSTEM_DATA,
|
||||
TEST_SYSTEM_URL,
|
||||
add_mock_config,
|
||||
)
|
||||
|
||||
|
||||
async def test_climate_async_setup_entry(hass, aioclient_mock):
|
||||
"""Test climate setup."""
|
||||
|
||||
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 Main Climate Entity
|
||||
entity_id = "climate.ac_one"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVAC_MODE_FAN_ONLY
|
||||
assert state.attributes.get("min_temp") == 16
|
||||
assert state.attributes.get("max_temp") == 32
|
||||
assert state.attributes.get("current_temperature") is None
|
||||
|
||||
entry = registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == "uniqueid-ac1"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVAC_MODE_FAN_ONLY},
|
||||
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"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVAC_MODE_OFF},
|
||||
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"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_FAN_MODE: FAN_OFF},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 7
|
||||
assert aioclient_mock.mock_calls[-2][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-2][1].path == "/setAircon"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 25},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 9
|
||||
assert aioclient_mock.mock_calls[-2][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-2][1].path == "/setAircon"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
# Test Climate Zone Entity
|
||||
entity_id = "climate.zone_open_with_sensor"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.attributes.get("min_temp") == 16
|
||||
assert state.attributes.get("max_temp") == 32
|
||||
assert state.attributes.get("measuredTemp") == 25
|
||||
assert state.attributes.get("setTemp") == 24
|
||||
|
||||
entry = registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == "uniqueid-ac1-z01"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVAC_MODE_FAN_ONLY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 11
|
||||
assert aioclient_mock.mock_calls[-2][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-2][1].path == "/setAircon"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVAC_MODE_OFF},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 13
|
||||
assert aioclient_mock.mock_calls[-2][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-2][1].path == "/setAircon"
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/getSystemData"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 25},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 15
|
||||
assert aioclient_mock.mock_calls[-2][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-2][1].path == "/setAircon"
|
||||
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."""
|
||||
|
||||
aioclient_mock.get(
|
||||
TEST_SYSTEM_URL,
|
||||
text=TEST_SYSTEM_DATA,
|
||||
)
|
||||
aioclient_mock.get(
|
||||
TEST_SET_URL,
|
||||
exc=SyntaxError,
|
||||
)
|
||||
await add_mock_config(hass)
|
||||
|
||||
assert len(aioclient_mock.mock_calls) == 1
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: ["climate.ac_one"], ATTR_TEMPERATURE: 25},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(aioclient_mock.mock_calls) == 2
|
||||
assert aioclient_mock.mock_calls[-1][0] == "GET"
|
||||
assert aioclient_mock.mock_calls[-1][1].path == "/setAircon"
|
|
@ -0,0 +1,71 @@
|
|||
"""Test the Advantage Air config flow."""
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.advantage_air.const import DOMAIN
|
||||
|
||||
from tests.async_mock import patch
|
||||
from tests.components.advantage_air import TEST_SYSTEM_DATA, TEST_SYSTEM_URL, USER_INPUT
|
||||
|
||||
|
||||
async def test_form(hass, aioclient_mock):
|
||||
"""Test that form shows up."""
|
||||
|
||||
aioclient_mock.get(
|
||||
TEST_SYSTEM_URL,
|
||||
text=TEST_SYSTEM_DATA,
|
||||
)
|
||||
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result1["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result1["step_id"] == "user"
|
||||
assert result1["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.advantage_air.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
|
||||
assert len(aioclient_mock.mock_calls) == 1
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == "testname"
|
||||
assert result2["data"] == USER_INPUT
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
# Test Duplicate Config Flow
|
||||
result3 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass, aioclient_mock):
|
||||
"""Test we handle cannot connect error."""
|
||||
|
||||
aioclient_mock.get(
|
||||
TEST_SYSTEM_URL,
|
||||
exc=SyntaxError,
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
assert len(aioclient_mock.mock_calls) == 1
|
|
@ -0,0 +1,33 @@
|
|||
"""Test the Advantage Air Initialization."""
|
||||
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
from tests.components.advantage_air import (
|
||||
TEST_SYSTEM_DATA,
|
||||
TEST_SYSTEM_URL,
|
||||
add_mock_config,
|
||||
)
|
||||
|
||||
|
||||
async def test_async_setup_entry(hass, aioclient_mock):
|
||||
"""Test a successful setup entry."""
|
||||
|
||||
aioclient_mock.get(
|
||||
TEST_SYSTEM_URL,
|
||||
text=TEST_SYSTEM_DATA,
|
||||
)
|
||||
|
||||
entry = await add_mock_config(hass)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
|
||||
async def test_async_setup_entry_failure(hass, aioclient_mock):
|
||||
"""Test a unsuccessful setup entry."""
|
||||
|
||||
aioclient_mock.get(
|
||||
TEST_SYSTEM_URL,
|
||||
exc=SyntaxError,
|
||||
)
|
||||
|
||||
entry = await add_mock_config(hass)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"aircons": {
|
||||
"ac1": {
|
||||
"info": {
|
||||
"climateControlModeIsRunning": false,
|
||||
"countDownToOff": 0,
|
||||
"countDownToOn": 0,
|
||||
"fan": "high",
|
||||
"filterCleanStatus": 0,
|
||||
"freshAirStatus": "none",
|
||||
"mode": "vent",
|
||||
"myZone": 0,
|
||||
"name": "AC One",
|
||||
"setTemp": 24,
|
||||
"state": "on"
|
||||
},
|
||||
"zones": {
|
||||
"z01": {
|
||||
"error": 0,
|
||||
"maxDamper": 100,
|
||||
"measuredTemp": 25,
|
||||
"minDamper": 0,
|
||||
"motion": 1,
|
||||
"motionConfig": 1,
|
||||
"name": "Zone open with Sensor",
|
||||
"number": 1,
|
||||
"rssi": -50,
|
||||
"setTemp": 24,
|
||||
"state": "open",
|
||||
"type": 1,
|
||||
"value": 100
|
||||
},
|
||||
"z02": {
|
||||
"error": 0,
|
||||
"maxDamper": 100,
|
||||
"measuredTemp": 25,
|
||||
"minDamper": 0,
|
||||
"motion": 1,
|
||||
"motionConfig": 1,
|
||||
"name": "Zone closed with Sensor",
|
||||
"number": 1,
|
||||
"rssi": -50,
|
||||
"setTemp": 24,
|
||||
"state": "close",
|
||||
"type": 1,
|
||||
"value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ac2": {
|
||||
"info": {
|
||||
"climateControlModeIsRunning": false,
|
||||
"countDownToOff": 0,
|
||||
"countDownToOn": 0,
|
||||
"fan": "low",
|
||||
"filterCleanStatus": 0,
|
||||
"freshAirStatus": "none",
|
||||
"mode": "cool",
|
||||
"myZone": 1,
|
||||
"name": "AC Two",
|
||||
"setTemp": 24,
|
||||
"state": "off"
|
||||
},
|
||||
"zones": {
|
||||
"z01": {
|
||||
"error": 0,
|
||||
"maxDamper": 100,
|
||||
"measuredTemp": 0,
|
||||
"minDamper": 0,
|
||||
"motion": 0,
|
||||
"motionConfig": 0,
|
||||
"name": "Zone open without sensor",
|
||||
"number": 1,
|
||||
"rssi": 0,
|
||||
"setTemp": 24,
|
||||
"state": "open",
|
||||
"type": 0,
|
||||
"value": 100
|
||||
},
|
||||
"z02": {
|
||||
"error": 0,
|
||||
"maxDamper": 100,
|
||||
"measuredTemp": 0,
|
||||
"minDamper": 0,
|
||||
"motion": 0,
|
||||
"motionConfig": 0,
|
||||
"name": "Zone closed without sensor",
|
||||
"number": 1,
|
||||
"rssi": 0,
|
||||
"setTemp": 24,
|
||||
"state": "close",
|
||||
"type": 0,
|
||||
"value": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"hasAircons": true,
|
||||
"hasLights": false,
|
||||
"hasSensors": false,
|
||||
"hasThings": false,
|
||||
"hasThingsBOG": false,
|
||||
"hasThingsLight": false,
|
||||
"name": "testname",
|
||||
"rid": "uniqueid",
|
||||
"sysType": "e-zone",
|
||||
"myAppRev": "testversion"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"ack": true,
|
||||
"request": "setAircon"
|
||||
}
|
Loading…
Reference in New Issue