mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Update nexia to use asyncio (#72108)
This commit is contained in:
parent
bf63d381b2
commit
d8a580a90f
@ -1,15 +1,16 @@
|
|||||||
"""Support for Nexia / Trane XL Thermostats."""
|
"""Support for Nexia / Trane XL Thermostats."""
|
||||||
from functools import partial
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from nexia.const import BRAND_NEXIA
|
from nexia.const import BRAND_NEXIA
|
||||||
from nexia.home import NexiaHome
|
from nexia.home import NexiaHome
|
||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from .const import CONF_BRAND, DOMAIN, PLATFORMS
|
from .const import CONF_BRAND, DOMAIN, PLATFORMS
|
||||||
@ -30,31 +31,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
brand = conf.get(CONF_BRAND, BRAND_NEXIA)
|
brand = conf.get(CONF_BRAND, BRAND_NEXIA)
|
||||||
|
|
||||||
state_file = hass.config.path(f"nexia_config_{username}.conf")
|
state_file = hass.config.path(f"nexia_config_{username}.conf")
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
nexia_home = NexiaHome(
|
||||||
|
session,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
device_name=hass.config.location_name,
|
||||||
|
state_file=state_file,
|
||||||
|
brand=brand,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
nexia_home = await hass.async_add_executor_job(
|
await nexia_home.login()
|
||||||
partial(
|
except asyncio.TimeoutError as ex:
|
||||||
NexiaHome,
|
raise ConfigEntryNotReady(
|
||||||
username=username,
|
f"Timed out trying to connect to Nexia service: {ex}"
|
||||||
password=password,
|
) from ex
|
||||||
device_name=hass.config.location_name,
|
except aiohttp.ClientResponseError as http_ex:
|
||||||
state_file=state_file,
|
if is_invalid_auth_code(http_ex.status):
|
||||||
brand=brand,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except ConnectTimeout as ex:
|
|
||||||
_LOGGER.error("Unable to connect to Nexia service: %s", ex)
|
|
||||||
raise ConfigEntryNotReady from ex
|
|
||||||
except HTTPError as http_ex:
|
|
||||||
if is_invalid_auth_code(http_ex.response.status_code):
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Access error from Nexia service, please check credentials: %s", http_ex
|
"Access error from Nexia service, please check credentials: %s", http_ex
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
_LOGGER.error("HTTP error from Nexia service: %s", http_ex)
|
raise ConfigEntryNotReady(f"Error from Nexia service: {http_ex}") from http_ex
|
||||||
raise ConfigEntryNotReady from http_ex
|
|
||||||
|
|
||||||
coordinator = NexiaDataUpdateCoordinator(hass, nexia_home)
|
coordinator = NexiaDataUpdateCoordinator(hass, nexia_home)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
@ -123,13 +123,17 @@ async def async_setup_entry(
|
|||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_SET_HUMIDIFY_SETPOINT,
|
SERVICE_SET_HUMIDIFY_SETPOINT,
|
||||||
SET_HUMIDITY_SCHEMA,
|
SET_HUMIDITY_SCHEMA,
|
||||||
SERVICE_SET_HUMIDIFY_SETPOINT,
|
f"async_{SERVICE_SET_HUMIDIFY_SETPOINT}",
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_SET_AIRCLEANER_MODE, SET_AIRCLEANER_SCHEMA, SERVICE_SET_AIRCLEANER_MODE
|
SERVICE_SET_AIRCLEANER_MODE,
|
||||||
|
SET_AIRCLEANER_SCHEMA,
|
||||||
|
f"async_{SERVICE_SET_AIRCLEANER_MODE}",
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_SET_HVAC_RUN_MODE, SET_HVAC_RUN_MODE_SCHEMA, SERVICE_SET_HVAC_RUN_MODE
|
SERVICE_SET_HVAC_RUN_MODE,
|
||||||
|
SET_HVAC_RUN_MODE_SCHEMA,
|
||||||
|
f"async_{SERVICE_SET_HVAC_RUN_MODE}",
|
||||||
)
|
)
|
||||||
|
|
||||||
entities: list[NexiaZone] = []
|
entities: list[NexiaZone] = []
|
||||||
@ -192,20 +196,20 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._thermostat.get_fan_mode()
|
return self._thermostat.get_fan_mode()
|
||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
self._thermostat.set_fan_mode(fan_mode)
|
await self._thermostat.set_fan_mode(fan_mode)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def set_hvac_run_mode(self, run_mode, hvac_mode):
|
async def async_set_hvac_run_mode(self, run_mode, hvac_mode):
|
||||||
"""Set the hvac run mode."""
|
"""Set the hvac run mode."""
|
||||||
if run_mode is not None:
|
if run_mode is not None:
|
||||||
if run_mode == HOLD_PERMANENT:
|
if run_mode == HOLD_PERMANENT:
|
||||||
self._zone.call_permanent_hold()
|
await self._zone.call_permanent_hold()
|
||||||
else:
|
else:
|
||||||
self._zone.call_return_to_schedule()
|
await self._zone.call_return_to_schedule()
|
||||||
if hvac_mode is not None:
|
if hvac_mode is not None:
|
||||||
self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
await self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -213,12 +217,12 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
"""Preset that is active."""
|
"""Preset that is active."""
|
||||||
return self._zone.get_preset()
|
return self._zone.get_preset()
|
||||||
|
|
||||||
def set_humidity(self, humidity):
|
async def async_set_humidity(self, humidity):
|
||||||
"""Dehumidify target."""
|
"""Dehumidify target."""
|
||||||
if self._thermostat.has_dehumidify_support():
|
if self._thermostat.has_dehumidify_support():
|
||||||
self.set_dehumidify_setpoint(humidity)
|
await self.async_set_dehumidify_setpoint(humidity)
|
||||||
else:
|
else:
|
||||||
self.set_humidify_setpoint(humidity)
|
await self.async_set_humidify_setpoint(humidity)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -300,7 +304,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
|
|
||||||
return NEXIA_TO_HA_HVAC_MODE_MAP[mode]
|
return NEXIA_TO_HA_HVAC_MODE_MAP[mode]
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set target temperature."""
|
"""Set target temperature."""
|
||||||
new_heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
new_heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
@ -332,7 +336,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
):
|
):
|
||||||
new_heat_temp = new_cool_temp - deadband
|
new_heat_temp = new_cool_temp - deadband
|
||||||
|
|
||||||
self._zone.set_heat_cool_temp(
|
await self._zone.set_heat_cool_temp(
|
||||||
heat_temperature=new_heat_temp,
|
heat_temperature=new_heat_temp,
|
||||||
cool_temperature=new_cool_temp,
|
cool_temperature=new_cool_temp,
|
||||||
set_temperature=set_temp,
|
set_temperature=set_temp,
|
||||||
@ -366,63 +370,63 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def set_preset_mode(self, preset_mode: str):
|
async def async_set_preset_mode(self, preset_mode: str):
|
||||||
"""Set the preset mode."""
|
"""Set the preset mode."""
|
||||||
self._zone.set_preset(preset_mode)
|
await self._zone.set_preset(preset_mode)
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def turn_aux_heat_off(self):
|
async def async_turn_aux_heat_off(self):
|
||||||
"""Turn. Aux Heat off."""
|
"""Turn. Aux Heat off."""
|
||||||
self._thermostat.set_emergency_heat(False)
|
await self._thermostat.set_emergency_heat(False)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def turn_aux_heat_on(self):
|
async def async_turn_aux_heat_on(self):
|
||||||
"""Turn. Aux Heat on."""
|
"""Turn. Aux Heat on."""
|
||||||
self._thermostat.set_emergency_heat(True)
|
self._thermostat.set_emergency_heat(True)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def turn_off(self):
|
async def async_turn_off(self):
|
||||||
"""Turn. off the zone."""
|
"""Turn. off the zone."""
|
||||||
self.set_hvac_mode(OPERATION_MODE_OFF)
|
await self.set_hvac_mode(OPERATION_MODE_OFF)
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def turn_on(self):
|
async def async_turn_on(self):
|
||||||
"""Turn. on the zone."""
|
"""Turn. on the zone."""
|
||||||
self.set_hvac_mode(OPERATION_MODE_AUTO)
|
await self.set_hvac_mode(OPERATION_MODE_AUTO)
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
|
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
|
||||||
if hvac_mode == HVACMode.AUTO:
|
if hvac_mode == HVACMode.AUTO:
|
||||||
self._zone.call_return_to_schedule()
|
await self._zone.call_return_to_schedule()
|
||||||
self._zone.set_mode(mode=OPERATION_MODE_AUTO)
|
await self._zone.set_mode(mode=OPERATION_MODE_AUTO)
|
||||||
else:
|
else:
|
||||||
self._zone.call_permanent_hold()
|
await self._zone.call_permanent_hold()
|
||||||
self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
await self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode])
|
||||||
|
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def set_aircleaner_mode(self, aircleaner_mode):
|
async def async_set_aircleaner_mode(self, aircleaner_mode):
|
||||||
"""Set the aircleaner mode."""
|
"""Set the aircleaner mode."""
|
||||||
self._thermostat.set_air_cleaner(aircleaner_mode)
|
await self._thermostat.set_air_cleaner(aircleaner_mode)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def set_humidify_setpoint(self, humidity):
|
async def async_set_humidify_setpoint(self, humidity):
|
||||||
"""Set the humidify setpoint."""
|
"""Set the humidify setpoint."""
|
||||||
target_humidity = find_humidity_setpoint(humidity / 100.0)
|
target_humidity = find_humidity_setpoint(humidity / 100.0)
|
||||||
if self._thermostat.get_humidify_setpoint() == target_humidity:
|
if self._thermostat.get_humidify_setpoint() == target_humidity:
|
||||||
# Trying to set the humidify setpoint to the
|
# Trying to set the humidify setpoint to the
|
||||||
# same value will cause the api to timeout
|
# same value will cause the api to timeout
|
||||||
return
|
return
|
||||||
self._thermostat.set_humidify_setpoint(target_humidity)
|
await self._thermostat.set_humidify_setpoint(target_humidity)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
|
||||||
def set_dehumidify_setpoint(self, humidity):
|
async def async_set_dehumidify_setpoint(self, humidity):
|
||||||
"""Set the dehumidify setpoint."""
|
"""Set the dehumidify setpoint."""
|
||||||
target_humidity = find_humidity_setpoint(humidity / 100.0)
|
target_humidity = find_humidity_setpoint(humidity / 100.0)
|
||||||
if self._thermostat.get_dehumidify_setpoint() == target_humidity:
|
if self._thermostat.get_dehumidify_setpoint() == target_humidity:
|
||||||
# Trying to set the dehumidify setpoint to the
|
# Trying to set the dehumidify setpoint to the
|
||||||
# same value will cause the api to timeout
|
# same value will cause the api to timeout
|
||||||
return
|
return
|
||||||
self._thermostat.set_dehumidify_setpoint(target_humidity)
|
await self._thermostat.set_dehumidify_setpoint(target_humidity)
|
||||||
self._signal_thermostat_update()
|
self._signal_thermostat_update()
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
"""Config flow for Nexia integration."""
|
"""Config flow for Nexia integration."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from nexia.const import BRAND_ASAIR, BRAND_NEXIA, BRAND_TRANE
|
from nexia.const import BRAND_ASAIR, BRAND_NEXIA, BRAND_TRANE
|
||||||
from nexia.home import NexiaHome
|
from nexia.home import NexiaHome
|
||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, core, exceptions
|
from homeassistant import config_entries, core, exceptions
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BRAND_ASAIR_NAME,
|
BRAND_ASAIR_NAME,
|
||||||
@ -44,23 +46,23 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
state_file = hass.config.path(
|
state_file = hass.config.path(
|
||||||
f"{data[CONF_BRAND]}_config_{data[CONF_USERNAME]}.conf"
|
f"{data[CONF_BRAND]}_config_{data[CONF_USERNAME]}.conf"
|
||||||
)
|
)
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
nexia_home = NexiaHome(
|
||||||
|
session,
|
||||||
|
username=data[CONF_USERNAME],
|
||||||
|
password=data[CONF_PASSWORD],
|
||||||
|
brand=data[CONF_BRAND],
|
||||||
|
device_name=hass.config.location_name,
|
||||||
|
state_file=state_file,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
nexia_home = NexiaHome(
|
await nexia_home.login()
|
||||||
username=data[CONF_USERNAME],
|
except asyncio.TimeoutError as ex:
|
||||||
password=data[CONF_PASSWORD],
|
|
||||||
brand=data[CONF_BRAND],
|
|
||||||
auto_login=False,
|
|
||||||
auto_update=False,
|
|
||||||
device_name=hass.config.location_name,
|
|
||||||
state_file=state_file,
|
|
||||||
)
|
|
||||||
await hass.async_add_executor_job(nexia_home.login)
|
|
||||||
except ConnectTimeout as ex:
|
|
||||||
_LOGGER.error("Unable to connect to Nexia service: %s", ex)
|
_LOGGER.error("Unable to connect to Nexia service: %s", ex)
|
||||||
raise CannotConnect from ex
|
raise CannotConnect from ex
|
||||||
except HTTPError as http_ex:
|
except aiohttp.ClientResponseError as http_ex:
|
||||||
_LOGGER.error("HTTP error from Nexia service: %s", http_ex)
|
_LOGGER.error("HTTP error from Nexia service: %s", http_ex)
|
||||||
if is_invalid_auth_code(http_ex.response.status_code):
|
if is_invalid_auth_code(http_ex.status):
|
||||||
raise InvalidAuth from http_ex
|
raise InvalidAuth from http_ex
|
||||||
raise CannotConnect from http_ex
|
raise CannotConnect from http_ex
|
||||||
|
|
||||||
|
@ -33,4 +33,4 @@ class NexiaDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
async def _async_update_data(self) -> None:
|
||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
return await self.hass.async_add_executor_job(self.nexia_home.update)
|
return await self.nexia_home.update()
|
||||||
|
@ -3,7 +3,10 @@ from nexia.thermostat import NexiaThermostat
|
|||||||
from nexia.zone import NexiaThermostatZone
|
from nexia.zone import NexiaThermostatZone
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION
|
from homeassistant.const import ATTR_ATTRIBUTION
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
from homeassistant.helpers.dispatcher import (
|
||||||
|
async_dispatcher_connect,
|
||||||
|
async_dispatcher_send,
|
||||||
|
)
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
@ -20,7 +23,9 @@ from .coordinator import NexiaDataUpdateCoordinator
|
|||||||
class NexiaEntity(CoordinatorEntity):
|
class NexiaEntity(CoordinatorEntity):
|
||||||
"""Base class for nexia entities."""
|
"""Base class for nexia entities."""
|
||||||
|
|
||||||
def __init__(self, coordinator, name, unique_id):
|
def __init__(
|
||||||
|
self, coordinator: NexiaDataUpdateCoordinator, name: str, unique_id: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._unique_id = unique_id
|
self._unique_id = unique_id
|
||||||
@ -85,7 +90,7 @@ class NexiaThermostatEntity(NexiaEntity):
|
|||||||
|
|
||||||
Update all the zones on the thermostat.
|
Update all the zones on the thermostat.
|
||||||
"""
|
"""
|
||||||
dispatcher_send(
|
async_dispatcher_send(
|
||||||
self.hass, f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}"
|
self.hass, f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -132,4 +137,4 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity):
|
|||||||
|
|
||||||
Update a single zone.
|
Update a single zone.
|
||||||
"""
|
"""
|
||||||
dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}")
|
async_dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "nexia",
|
"domain": "nexia",
|
||||||
"name": "Nexia/American Standard/Trane",
|
"name": "Nexia/American Standard/Trane",
|
||||||
"requirements": ["nexia==0.9.13"],
|
"requirements": ["nexia==1.0.0"],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Support for Nexia Automations."""
|
"""Support for Nexia Automations."""
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from nexia.automation import NexiaAutomation
|
||||||
|
|
||||||
from homeassistant.components.scene import Scene
|
from homeassistant.components.scene import Scene
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -37,7 +39,9 @@ async def async_setup_entry(
|
|||||||
class NexiaAutomationScene(NexiaEntity, Scene):
|
class NexiaAutomationScene(NexiaEntity, Scene):
|
||||||
"""Provides Nexia automation support."""
|
"""Provides Nexia automation support."""
|
||||||
|
|
||||||
def __init__(self, coordinator, automation):
|
def __init__(
|
||||||
|
self, coordinator: NexiaDataUpdateCoordinator, automation: NexiaAutomation
|
||||||
|
) -> None:
|
||||||
"""Initialize the automation scene."""
|
"""Initialize the automation scene."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
coordinator,
|
coordinator,
|
||||||
@ -60,7 +64,7 @@ class NexiaAutomationScene(NexiaEntity, Scene):
|
|||||||
|
|
||||||
async def async_activate(self, **kwargs: Any) -> None:
|
async def async_activate(self, **kwargs: Any) -> None:
|
||||||
"""Activate an automation scene."""
|
"""Activate an automation scene."""
|
||||||
await self.hass.async_add_executor_job(self._automation.activate)
|
await self._automation.activate()
|
||||||
|
|
||||||
async def refresh_callback(_):
|
async def refresh_callback(_):
|
||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from nexia.const import UNIT_CELSIUS
|
from nexia.const import UNIT_CELSIUS
|
||||||
|
from nexia.thermostat import NexiaThermostat
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -32,7 +33,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
# Thermostat / System Sensors
|
# Thermostat / System Sensors
|
||||||
for thermostat_id in nexia_home.get_thermostat_ids():
|
for thermostat_id in nexia_home.get_thermostat_ids():
|
||||||
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
thermostat: NexiaThermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
||||||
|
|
||||||
entities.append(
|
entities.append(
|
||||||
NexiaThermostatSensor(
|
NexiaThermostatSensor(
|
||||||
|
@ -56,12 +56,12 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity):
|
|||||||
"""Return the icon for the switch."""
|
"""Return the icon for the switch."""
|
||||||
return "mdi:timer-off" if self._zone.is_in_permanent_hold() else "mdi:timer"
|
return "mdi:timer-off" if self._zone.is_in_permanent_hold() else "mdi:timer"
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Enable permanent hold."""
|
"""Enable permanent hold."""
|
||||||
self._zone.call_permanent_hold()
|
await self._zone.call_permanent_hold()
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Disable permanent hold."""
|
"""Disable permanent hold."""
|
||||||
self._zone.call_return_to_schedule()
|
await self._zone.call_return_to_schedule()
|
||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
@ -1077,7 +1077,7 @@ nettigo-air-monitor==1.2.4
|
|||||||
neurio==0.3.1
|
neurio==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.nexia
|
# homeassistant.components.nexia
|
||||||
nexia==0.9.13
|
nexia==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.nextcloud
|
# homeassistant.components.nextcloud
|
||||||
nextcloudmonitor==1.1.0
|
nextcloudmonitor==1.1.0
|
||||||
|
@ -742,7 +742,7 @@ netmap==0.7.0.2
|
|||||||
nettigo-air-monitor==1.2.4
|
nettigo-air-monitor==1.2.4
|
||||||
|
|
||||||
# homeassistant.components.nexia
|
# homeassistant.components.nexia
|
||||||
nexia==0.9.13
|
nexia==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.discord
|
# homeassistant.components.discord
|
||||||
nextcord==2.0.0a8
|
nextcord==2.0.0a8
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
"""Test the nexia config flow."""
|
"""Test the nexia config flow."""
|
||||||
|
import asyncio
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from nexia.const import BRAND_ASAIR, BRAND_NEXIA
|
from nexia.const import BRAND_ASAIR, BRAND_NEXIA
|
||||||
import pytest
|
import pytest
|
||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.nexia.const import CONF_BRAND, DOMAIN
|
from homeassistant.components.nexia.const import CONF_BRAND, DOMAIN
|
||||||
@ -52,7 +53,10 @@ async def test_form_invalid_auth(hass):
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("homeassistant.components.nexia.config_flow.NexiaHome.login"):
|
with patch("homeassistant.components.nexia.config_flow.NexiaHome.login",), patch(
|
||||||
|
"homeassistant.components.nexia.config_flow.NexiaHome.get_name",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
@ -74,7 +78,7 @@ async def test_form_cannot_connect(hass):
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
||||||
side_effect=ConnectTimeout,
|
side_effect=asyncio.TimeoutError,
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -95,11 +99,11 @@ async def test_form_invalid_auth_http_401(hass):
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
response_mock = MagicMock()
|
|
||||||
type(response_mock).status_code = 401
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
||||||
side_effect=HTTPError(response=response_mock),
|
side_effect=aiohttp.ClientResponseError(
|
||||||
|
status=401, request_info=MagicMock(), history=MagicMock()
|
||||||
|
),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -120,11 +124,11 @@ async def test_form_cannot_connect_not_found(hass):
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
response_mock = MagicMock()
|
|
||||||
type(response_mock).status_code = 404
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
"homeassistant.components.nexia.config_flow.NexiaHome.login",
|
||||||
side_effect=HTTPError(response=response_mock),
|
side_effect=aiohttp.ClientResponseError(
|
||||||
|
status=404, request_info=MagicMock(), history=MagicMock()
|
||||||
|
),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -3,13 +3,13 @@ from unittest.mock import patch
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from nexia.home import NexiaHome
|
from nexia.home import NexiaHome
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
from homeassistant.components.nexia.const import DOMAIN
|
from homeassistant.components.nexia.const import DOMAIN
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_fixture
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
from tests.test_util.aiohttp import mock_aiohttp_client
|
||||||
|
|
||||||
|
|
||||||
async def async_init_integration(
|
async def async_init_integration(
|
||||||
@ -21,17 +21,18 @@ async def async_init_integration(
|
|||||||
house_fixture = "nexia/mobile_houses_123456.json"
|
house_fixture = "nexia/mobile_houses_123456.json"
|
||||||
session_fixture = "nexia/session_123456.json"
|
session_fixture = "nexia/session_123456.json"
|
||||||
sign_in_fixture = "nexia/sign_in.json"
|
sign_in_fixture = "nexia/sign_in.json"
|
||||||
nexia = NexiaHome(auto_login=False)
|
with mock_aiohttp_client() as mock_session, patch(
|
||||||
|
|
||||||
with requests_mock.mock() as m, patch(
|
|
||||||
"nexia.home.load_or_create_uuid", return_value=uuid.uuid4()
|
"nexia.home.load_or_create_uuid", return_value=uuid.uuid4()
|
||||||
):
|
):
|
||||||
m.post(nexia.API_MOBILE_SESSION_URL, text=load_fixture(session_fixture))
|
nexia = NexiaHome(mock_session)
|
||||||
m.get(
|
mock_session.post(
|
||||||
|
nexia.API_MOBILE_SESSION_URL, text=load_fixture(session_fixture)
|
||||||
|
)
|
||||||
|
mock_session.get(
|
||||||
nexia.API_MOBILE_HOUSES_URL.format(house_id=123456),
|
nexia.API_MOBILE_HOUSES_URL.format(house_id=123456),
|
||||||
text=load_fixture(house_fixture),
|
text=load_fixture(house_fixture),
|
||||||
)
|
)
|
||||||
m.post(
|
mock_session.post(
|
||||||
nexia.API_MOBILE_ACCOUNTS_SIGN_IN_URL,
|
nexia.API_MOBILE_ACCOUNTS_SIGN_IN_URL,
|
||||||
text=load_fixture(sign_in_fixture),
|
text=load_fixture(sign_in_fixture),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user