mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Change Honeywell somecomfort API to AIOSomecomfort API (#86102)
* Move to AIOSomecomfort * Remove unused constant * Improve test coverage to 100 * Update homeassistant/components/honeywell/__init__.py remove "todo" from code Co-authored-by: Erik Montnemery <erik@montnemery.com> * Missing cannot_connect translation * add asyncio errors update devices per entity rework retry login Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
f0ba7a3795
commit
5e6ba594aa
@ -509,8 +509,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/homematic/ @pvizeli @danielperna84
|
||||
/homeassistant/components/homewizard/ @DCSBL
|
||||
/tests/components/homewizard/ @DCSBL
|
||||
/homeassistant/components/honeywell/ @rdfurman
|
||||
/tests/components/honeywell/ @rdfurman
|
||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||
/tests/components/honeywell/ @rdfurman @mkmer
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
|
@ -1,14 +1,13 @@
|
||||
"""Support for Honeywell (US) Total Connect Comfort climate systems."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
|
||||
import somecomfort
|
||||
import AIOSomecomfort
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
@ -20,7 +19,6 @@ from .const import (
|
||||
)
|
||||
|
||||
UPDATE_LOOP_SLEEP_TIME = 5
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
|
||||
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR]
|
||||
|
||||
MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE}
|
||||
@ -51,18 +49,33 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
username = config_entry.data[CONF_USERNAME]
|
||||
password = config_entry.data[CONF_PASSWORD]
|
||||
|
||||
client = await hass.async_add_executor_job(
|
||||
get_somecomfort_client, username, password
|
||||
client = AIOSomecomfort.AIOSomeComfort(
|
||||
username, password, session=async_get_clientsession(hass)
|
||||
)
|
||||
try:
|
||||
await client.login()
|
||||
await client.discover()
|
||||
|
||||
if client is None:
|
||||
return False
|
||||
except AIOSomecomfort.AuthError as ex:
|
||||
raise ConfigEntryNotReady(
|
||||
"Failed to initialize the Honeywell client: "
|
||||
"Check your configuration (username, password), "
|
||||
) from ex
|
||||
|
||||
except (
|
||||
AIOSomecomfort.ConnectionError,
|
||||
AIOSomecomfort.ConnectionTimeout,
|
||||
asyncio.TimeoutError,
|
||||
) as ex:
|
||||
raise ConfigEntryNotReady(
|
||||
"Failed to initialize the Honeywell client: "
|
||||
"Connection error: maybe you have exceeded the API rate limit?"
|
||||
) from ex
|
||||
|
||||
loc_id = config_entry.data.get(CONF_LOC_ID)
|
||||
dev_id = config_entry.data.get(CONF_DEV_ID)
|
||||
|
||||
devices = {}
|
||||
|
||||
for location in client.locations_by_id.values():
|
||||
if not loc_id or location.locationid == loc_id:
|
||||
for device in location.devices_by_id.values():
|
||||
@ -74,7 +87,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
return False
|
||||
|
||||
data = HoneywellData(hass, config_entry, client, username, password, devices)
|
||||
await data.async_update()
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = data
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
@ -99,21 +111,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
return unload_ok
|
||||
|
||||
|
||||
def get_somecomfort_client(username: str, password: str) -> somecomfort.SomeComfort:
|
||||
"""Initialize the somecomfort client."""
|
||||
try:
|
||||
return somecomfort.SomeComfort(username, password)
|
||||
except somecomfort.AuthError:
|
||||
_LOGGER.error("Failed to login to honeywell account %s", username)
|
||||
return None
|
||||
except somecomfort.SomeComfortError as ex:
|
||||
raise ConfigEntryNotReady(
|
||||
"Failed to initialize the Honeywell client: "
|
||||
"Check your configuration (username, password), "
|
||||
"or maybe you have exceeded the API rate limit?"
|
||||
) from ex
|
||||
|
||||
|
||||
class HoneywellData:
|
||||
"""Get the latest data and update."""
|
||||
|
||||
@ -121,10 +118,10 @@ class HoneywellData:
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
client: somecomfort.SomeComfort,
|
||||
client: AIOSomecomfort.AIOSomeComfort,
|
||||
username: str,
|
||||
password: str,
|
||||
devices: dict[str, somecomfort.Device],
|
||||
devices: dict[str, AIOSomecomfort.device.Device],
|
||||
) -> None:
|
||||
"""Initialize the data object."""
|
||||
self._hass = hass
|
||||
@ -134,73 +131,13 @@ class HoneywellData:
|
||||
self._password = password
|
||||
self.devices = devices
|
||||
|
||||
async def _retry(self) -> bool:
|
||||
"""Recreate a new somecomfort client.
|
||||
async def retry_login(self) -> bool:
|
||||
"""Fire of a login retry."""
|
||||
|
||||
When we got an error, the best way to be sure that the next query
|
||||
will succeed, is to recreate a new somecomfort client.
|
||||
"""
|
||||
self._client = await self._hass.async_add_executor_job(
|
||||
get_somecomfort_client, self._username, self._password
|
||||
)
|
||||
|
||||
if self._client is None:
|
||||
return False
|
||||
|
||||
refreshed_devices = [
|
||||
device
|
||||
for location in self._client.locations_by_id.values()
|
||||
for device in location.devices_by_id.values()
|
||||
]
|
||||
|
||||
if len(refreshed_devices) == 0:
|
||||
_LOGGER.error("Failed to find any devices after retry")
|
||||
return False
|
||||
|
||||
for updated_device in refreshed_devices:
|
||||
if updated_device.deviceid in self.devices:
|
||||
self.devices[updated_device.deviceid] = updated_device
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"New device with ID %s detected, reload the honeywell integration"
|
||||
" if you want to access it in Home Assistant"
|
||||
)
|
||||
|
||||
await self._hass.config_entries.async_reload(self._config.entry_id)
|
||||
return True
|
||||
|
||||
async def _refresh_devices(self):
|
||||
"""Refresh each enabled device."""
|
||||
for device in self.devices.values():
|
||||
await self._hass.async_add_executor_job(device.refresh)
|
||||
try:
|
||||
await self._client.login()
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
await asyncio.sleep(UPDATE_LOOP_SLEEP_TIME)
|
||||
return False
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self) -> None:
|
||||
"""Update the state."""
|
||||
retries = 3
|
||||
while retries > 0:
|
||||
try:
|
||||
await self._refresh_devices()
|
||||
break
|
||||
except (
|
||||
somecomfort.client.APIRateLimited,
|
||||
somecomfort.client.ConnectionError,
|
||||
somecomfort.client.ConnectionTimeout,
|
||||
OSError,
|
||||
) as exp:
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
_LOGGER.error(
|
||||
"Ran out of retry attempts (3 attempts allocated). Error: %s",
|
||||
exp,
|
||||
)
|
||||
raise exp
|
||||
|
||||
result = await self._retry()
|
||||
|
||||
if not result:
|
||||
_LOGGER.error("Retry result was empty. Error: %s", exp)
|
||||
raise exp
|
||||
|
||||
_LOGGER.info("SomeComfort update failed, retrying. Error: %s", exp)
|
||||
return True
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
import somecomfort
|
||||
import AIOSomecomfort
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
@ -70,7 +70,7 @@ HW_FAN_MODE_TO_HA = {
|
||||
"follow schedule": FAN_AUTO,
|
||||
}
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
SCAN_INTERVAL = datetime.timedelta(seconds=10)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -230,7 +230,7 @@ class HoneywellUSThermostat(ClimateEntity):
|
||||
cool_status = self._device.raw_ui_data.get("StatusCool", 0)
|
||||
return heat_status == 2 or cool_status == 2
|
||||
|
||||
def _set_temperature(self, **kwargs) -> None:
|
||||
async def _set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
@ -246,35 +246,43 @@ class HoneywellUSThermostat(ClimateEntity):
|
||||
# Get next period time
|
||||
hour, minute = divmod(next_period * 15, 60)
|
||||
# Set hold time
|
||||
setattr(self._device, f"hold_{mode}", datetime.time(hour, minute))
|
||||
if mode == HVACMode.COOL:
|
||||
await self._device.set_hold_cool(datetime.time(hour, minute))
|
||||
elif mode == HVACMode.HEAT:
|
||||
await self._device.set_hold_heat(datetime.time(hour, minute))
|
||||
|
||||
# Set temperature
|
||||
setattr(self._device, f"setpoint_{mode}", temperature)
|
||||
except somecomfort.SomeComfortError:
|
||||
if mode == HVACMode.COOL:
|
||||
await self._device.set_setpoint_cool(temperature)
|
||||
elif mode == HVACMode.HEAT:
|
||||
await self._device.set_setpoint_heat(temperature)
|
||||
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error("Temperature %.1f out of range", temperature)
|
||||
|
||||
def set_temperature(self, **kwargs: Any) -> None:
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if {HVACMode.COOL, HVACMode.HEAT} & set(self._hvac_mode_map):
|
||||
self._set_temperature(**kwargs)
|
||||
await self._set_temperature(**kwargs)
|
||||
|
||||
try:
|
||||
if HVACMode.HEAT_COOL in self._hvac_mode_map:
|
||||
if temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH):
|
||||
self._device.setpoint_cool = temperature
|
||||
await self._device.set_setpoint_cool(temperature)
|
||||
if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW):
|
||||
self._device.setpoint_heat = temperature
|
||||
except somecomfort.SomeComfortError as err:
|
||||
await self._device.set_setpoint_heat(temperature)
|
||||
except AIOSomecomfort.SomeComfortError as err:
|
||||
_LOGGER.error("Invalid temperature %s: %s", temperature, err)
|
||||
|
||||
def set_fan_mode(self, fan_mode: str) -> None:
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
self._device.fan_mode = self._fan_mode_map[fan_mode]
|
||||
await self._device.set_fan_mode(self._fan_mode_map[fan_mode])
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
self._device.system_mode = self._hvac_mode_map[hvac_mode]
|
||||
await self._device.set_system_mode(self._hvac_mode_map[hvac_mode])
|
||||
|
||||
def _turn_away_mode_on(self) -> None:
|
||||
async def _turn_away_mode_on(self) -> None:
|
||||
"""Turn away on.
|
||||
|
||||
Somecomfort does have a proprietary away mode, but it doesn't really
|
||||
@ -285,73 +293,87 @@ class HoneywellUSThermostat(ClimateEntity):
|
||||
try:
|
||||
# Get current mode
|
||||
mode = self._device.system_mode
|
||||
except somecomfort.SomeComfortError:
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error("Can not get system mode")
|
||||
return
|
||||
try:
|
||||
|
||||
# Set permanent hold
|
||||
setattr(self._device, f"hold_{mode}", True)
|
||||
# Set temperature
|
||||
setattr(
|
||||
self._device,
|
||||
f"setpoint_{mode}",
|
||||
getattr(self, f"_{mode}_away_temp"),
|
||||
)
|
||||
except somecomfort.SomeComfortError:
|
||||
# and Set temperature
|
||||
away_temp = getattr(self, f"_{mode}_away_temp")
|
||||
if mode == HVACMode.COOL:
|
||||
self._device.set_hold_cool(True)
|
||||
self._device.set_setpoint_cool(away_temp)
|
||||
elif mode == HVACMode.HEAT:
|
||||
self._device.set_hold_heat(True)
|
||||
self._device.set_setpoint_heat(away_temp)
|
||||
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error(
|
||||
"Temperature %.1f out of range", getattr(self, f"_{mode}_away_temp")
|
||||
)
|
||||
|
||||
def _turn_hold_mode_on(self) -> None:
|
||||
async def _turn_hold_mode_on(self) -> None:
|
||||
"""Turn permanent hold on."""
|
||||
try:
|
||||
# Get current mode
|
||||
mode = self._device.system_mode
|
||||
except somecomfort.SomeComfortError:
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error("Can not get system mode")
|
||||
return
|
||||
# Check that we got a valid mode back
|
||||
if mode in HW_MODE_TO_HVAC_MODE:
|
||||
try:
|
||||
# Set permanent hold
|
||||
setattr(self._device, f"hold_{mode}", True)
|
||||
except somecomfort.SomeComfortError:
|
||||
if mode == HVACMode.COOL:
|
||||
await self._device.set_hold_cool(True)
|
||||
elif mode == HVACMode.HEAT:
|
||||
await self._device.set_hold_heat(True)
|
||||
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error("Couldn't set permanent hold")
|
||||
else:
|
||||
_LOGGER.error("Invalid system mode returned: %s", mode)
|
||||
|
||||
def _turn_away_mode_off(self) -> None:
|
||||
async def _turn_away_mode_off(self) -> None:
|
||||
"""Turn away/hold off."""
|
||||
self._away = False
|
||||
try:
|
||||
# Disabling all hold modes
|
||||
self._device.hold_cool = False
|
||||
self._device.hold_heat = False
|
||||
except somecomfort.SomeComfortError:
|
||||
await self._device.set_hold_cool(False)
|
||||
await self._device.set_hold_heat(False)
|
||||
except AIOSomecomfort.SomeComfortError:
|
||||
_LOGGER.error("Can not stop hold mode")
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_AWAY:
|
||||
self._turn_away_mode_on()
|
||||
await self._turn_away_mode_on()
|
||||
elif preset_mode == PRESET_HOLD:
|
||||
self._away = False
|
||||
self._turn_hold_mode_on()
|
||||
await self._turn_hold_mode_on()
|
||||
else:
|
||||
self._turn_away_mode_off()
|
||||
await self._turn_away_mode_off()
|
||||
|
||||
def turn_aux_heat_on(self) -> None:
|
||||
async def async_turn_aux_heat_on(self) -> None:
|
||||
"""Turn auxiliary heater on."""
|
||||
self._device.system_mode = "emheat"
|
||||
await self._device.system_mode("emheat")
|
||||
|
||||
def turn_aux_heat_off(self) -> None:
|
||||
async def async_turn_aux_heat_off(self) -> None:
|
||||
"""Turn auxiliary heater off."""
|
||||
if HVACMode.HEAT in self.hvac_modes:
|
||||
self.set_hvac_mode(HVACMode.HEAT)
|
||||
await self.async_set_hvac_mode(HVACMode.HEAT)
|
||||
else:
|
||||
self.set_hvac_mode(HVACMode.OFF)
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest state from the service."""
|
||||
await self._data.async_update()
|
||||
try:
|
||||
await self._device.refresh()
|
||||
except (
|
||||
AIOSomecomfort.device.APIRateLimited,
|
||||
AIOSomecomfort.device.ConnectionError,
|
||||
AIOSomecomfort.device.ConnectionTimeout,
|
||||
OSError,
|
||||
):
|
||||
await self._data.retry_login()
|
||||
|
@ -1,13 +1,17 @@
|
||||
"""Config flow to configure the honeywell integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import AIOSomecomfort
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from . import get_somecomfort_client
|
||||
from .const import (
|
||||
CONF_COOL_AWAY_TEMPERATURE,
|
||||
CONF_HEAT_AWAY_TEMPERATURE,
|
||||
@ -22,20 +26,28 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(self, user_input=None) -> FlowResult:
|
||||
"""Create config entry. Show the setup form to the user."""
|
||||
errors = {}
|
||||
|
||||
valid = False
|
||||
if user_input is not None:
|
||||
valid = await self.is_valid(**user_input)
|
||||
try:
|
||||
valid = await self.is_valid(**user_input)
|
||||
except AIOSomecomfort.AuthError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except (
|
||||
AIOSomecomfort.ConnectionError,
|
||||
AIOSomecomfort.ConnectionTimeout,
|
||||
asyncio.TimeoutError,
|
||||
):
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
if valid:
|
||||
return self.async_create_entry(
|
||||
title=DOMAIN,
|
||||
data=user_input,
|
||||
)
|
||||
|
||||
errors["base"] = "invalid_auth"
|
||||
|
||||
data_schema = {
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
@ -46,11 +58,14 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def is_valid(self, **kwargs) -> bool:
|
||||
"""Check if login credentials are valid."""
|
||||
client = await self.hass.async_add_executor_job(
|
||||
get_somecomfort_client, kwargs[CONF_USERNAME], kwargs[CONF_PASSWORD]
|
||||
client = AIOSomecomfort.AIOSomeComfort(
|
||||
kwargs[CONF_USERNAME],
|
||||
kwargs[CONF_PASSWORD],
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
|
||||
return client is not None
|
||||
await client.login()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
@ -68,7 +83,7 @@ class HoneywellOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Initialize Honeywell options flow."""
|
||||
self.config_entry = entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(self, user_input=None) -> FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||
|
@ -3,8 +3,8 @@
|
||||
"name": "Honeywell Total Connect Comfort (US)",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/honeywell",
|
||||
"requirements": ["somecomfort==0.8.0"],
|
||||
"codeowners": ["@rdfurman"],
|
||||
"requirements": ["aiosomecomfort==0.0.2"],
|
||||
"codeowners": ["@rdfurman", "@mkmer"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["somecomfort"]
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from somecomfort import Device
|
||||
from AIOSomecomfort.device import Device
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
|
@ -10,7 +10,8 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -278,6 +278,9 @@ aioskybell==22.7.0
|
||||
# homeassistant.components.slimproto
|
||||
aioslimproto==2.1.1
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
aiosomecomfort==0.0.2
|
||||
|
||||
# homeassistant.components.steamist
|
||||
aiosteamist==0.3.2
|
||||
|
||||
@ -2362,9 +2365,6 @@ solaredge==0.0.2
|
||||
# homeassistant.components.solax
|
||||
solax==0.3.0
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
somecomfort==0.8.0
|
||||
|
||||
# homeassistant.components.somfy_mylink
|
||||
somfy-mylink-synergy==1.0.6
|
||||
|
||||
|
@ -256,6 +256,9 @@ aioskybell==22.7.0
|
||||
# homeassistant.components.slimproto
|
||||
aioslimproto==2.1.1
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
aiosomecomfort==0.0.2
|
||||
|
||||
# homeassistant.components.steamist
|
||||
aiosteamist==0.3.2
|
||||
|
||||
@ -1659,9 +1662,6 @@ solaredge==0.0.2
|
||||
# homeassistant.components.solax
|
||||
solax==0.3.0
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
somecomfort==0.8.0
|
||||
|
||||
# homeassistant.components.somfy_mylink
|
||||
somfy-mylink-synergy==1.0.6
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""Fixtures for honeywell tests."""
|
||||
|
||||
from unittest.mock import create_autospec, patch
|
||||
from unittest.mock import AsyncMock, create_autospec, patch
|
||||
|
||||
import AIOSomecomfort
|
||||
import pytest
|
||||
import somecomfort
|
||||
|
||||
from homeassistant.components.honeywell.const import DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
@ -30,7 +30,7 @@ def config_entry(config_data):
|
||||
@pytest.fixture
|
||||
def device():
|
||||
"""Mock a somecomfort.Device."""
|
||||
mock_device = create_autospec(somecomfort.Device, instance=True)
|
||||
mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True)
|
||||
mock_device.deviceid = 1234567
|
||||
mock_device._data = {
|
||||
"canControlHumidification": False,
|
||||
@ -48,7 +48,7 @@ def device():
|
||||
@pytest.fixture
|
||||
def device_with_outdoor_sensor():
|
||||
"""Mock a somecomfort.Device."""
|
||||
mock_device = create_autospec(somecomfort.Device, instance=True)
|
||||
mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True)
|
||||
mock_device.deviceid = 1234567
|
||||
mock_device._data = {
|
||||
"canControlHumidification": False,
|
||||
@ -67,7 +67,7 @@ def device_with_outdoor_sensor():
|
||||
@pytest.fixture
|
||||
def another_device():
|
||||
"""Mock a somecomfort.Device."""
|
||||
mock_device = create_autospec(somecomfort.Device, instance=True)
|
||||
mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True)
|
||||
mock_device.deviceid = 7654321
|
||||
mock_device._data = {
|
||||
"canControlHumidification": False,
|
||||
@ -85,7 +85,7 @@ def another_device():
|
||||
@pytest.fixture
|
||||
def location(device):
|
||||
"""Mock a somecomfort.Location."""
|
||||
mock_location = create_autospec(somecomfort.Location, instance=True)
|
||||
mock_location = create_autospec(AIOSomecomfort.location.Location, instance=True)
|
||||
mock_location.locationid.return_value = "location1"
|
||||
mock_location.devices_by_id = {device.deviceid: device}
|
||||
return mock_location
|
||||
@ -94,11 +94,13 @@ def location(device):
|
||||
@pytest.fixture(autouse=True)
|
||||
def client(location):
|
||||
"""Mock a somecomfort.SomeComfort client."""
|
||||
client_mock = create_autospec(somecomfort.SomeComfort, instance=True)
|
||||
client_mock = create_autospec(AIOSomecomfort.AIOSomeComfort, instance=True)
|
||||
client_mock.locations_by_id = {location.locationid: location}
|
||||
client_mock.login = AsyncMock(return_value=True)
|
||||
client_mock.discover = AsyncMock()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.honeywell.somecomfort.SomeComfort"
|
||||
"homeassistant.components.honeywell.AIOSomecomfort.AIOSomeComfort"
|
||||
) as sc_class_mock:
|
||||
sc_class_mock.return_value = client_mock
|
||||
yield client_mock
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Tests for honeywell config flow."""
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import somecomfort
|
||||
import AIOSomecomfort
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.honeywell.const import (
|
||||
@ -33,28 +33,32 @@ async def test_show_authenticate_form(hass: HomeAssistant) -> None:
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_connection_error(hass: HomeAssistant) -> None:
|
||||
async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None:
|
||||
"""Test that an error message is shown on connection fail."""
|
||||
client.login.side_effect = AIOSomecomfort.ConnectionError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
|
||||
)
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_auth_error(hass: HomeAssistant, client: MagicMock) -> None:
|
||||
"""Test that an error message is shown on login fail."""
|
||||
with patch(
|
||||
"somecomfort.SomeComfort",
|
||||
side_effect=somecomfort.AuthError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
|
||||
)
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
client.login.side_effect = AIOSomecomfort.AuthError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
|
||||
)
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_create_entry(hass: HomeAssistant) -> None:
|
||||
"""Test that the config entry is created."""
|
||||
with patch(
|
||||
"somecomfort.SomeComfort",
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
|
||||
)
|
||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
|
||||
)
|
||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
|
||||
|
||||
@patch("homeassistant.components.honeywell.UPDATE_LOOP_SLEEP_TIME", 0)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import create_autospec, patch
|
||||
|
||||
import somecomfort
|
||||
import AIOSomecomfort
|
||||
|
||||
from homeassistant.components.honeywell.const import (
|
||||
CONF_COOL_AWAY_TEMPERATURE,
|
||||
@ -46,7 +46,7 @@ async def test_setup_multiple_thermostats_with_same_deviceid(
|
||||
hass: HomeAssistant, caplog, config_entry: MockConfigEntry, device, client
|
||||
) -> None:
|
||||
"""Test Honeywell TCC API returning duplicate device IDs."""
|
||||
mock_location2 = create_autospec(somecomfort.Location, instance=True)
|
||||
mock_location2 = create_autospec(AIOSomecomfort.Location, instance=True)
|
||||
mock_location2.locationid.return_value = "location2"
|
||||
mock_location2.devices_by_id = {device.deviceid: device}
|
||||
client.locations_by_id["location2"] = mock_location2
|
||||
@ -71,13 +71,10 @@ async def test_away_temps_migration(hass: HomeAssistant) -> None:
|
||||
options={},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.honeywell.somecomfort.SomeComfort",
|
||||
):
|
||||
legacy_config.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(legacy_config.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert legacy_config.options == {
|
||||
CONF_COOL_AWAY_TEMPERATURE: 1,
|
||||
CONF_HEAT_AWAY_TEMPERATURE: 2,
|
||||
}
|
||||
legacy_config.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(legacy_config.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert legacy_config.options == {
|
||||
CONF_COOL_AWAY_TEMPERATURE: 1,
|
||||
CONF_HEAT_AWAY_TEMPERATURE: 2,
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
"""Test honeywell sensor."""
|
||||
from somecomfort import Device, Location
|
||||
from AIOSomecomfort.device import Device
|
||||
from AIOSomecomfort.location import Location
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit,temp", [("C", "5"), ("F", "-15")])
|
||||
async def test_outdoor_sensor(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
location: Location,
|
||||
device_with_outdoor_sensor: Device,
|
||||
unit,
|
||||
temp,
|
||||
):
|
||||
"""Test outdoor temperature sensor."""
|
||||
device_with_outdoor_sensor.temperature_unit = unit
|
||||
location.devices_by_id[
|
||||
device_with_outdoor_sensor.deviceid
|
||||
] = device_with_outdoor_sensor
|
||||
@ -25,5 +31,5 @@ async def test_outdoor_sensor(
|
||||
|
||||
assert temperature_state
|
||||
assert humidity_state
|
||||
assert temperature_state.state == "5"
|
||||
assert temperature_state.state == temp
|
||||
assert humidity_state.state == "25"
|
||||
|
Loading…
x
Reference in New Issue
Block a user