This commit is contained in:
Paulus Schoutsen 2023-02-02 20:52:38 -05:00 committed by GitHub
commit dbd8ffc282
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 378 additions and 131 deletions

View File

@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20230201.0"],
"requirements": ["home-assistant-frontend==20230202.0"],
"dependencies": [
"api",
"auth",

View File

@ -2,12 +2,12 @@
import asyncio
from dataclasses import dataclass
import AIOSomecomfort
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.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
@ -50,22 +50,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
username = config_entry.data[CONF_USERNAME]
password = config_entry.data[CONF_PASSWORD]
client = AIOSomecomfort.AIOSomeComfort(
client = aiosomecomfort.AIOSomeComfort(
username, password, session=async_get_clientsession(hass)
)
try:
await client.login()
await client.discover()
except AIOSomecomfort.AuthError as ex:
raise ConfigEntryNotReady(
"Failed to initialize the Honeywell client: "
"Check your configuration (username, password), "
) from ex
except aiosomecomfort.device.AuthError as ex:
raise ConfigEntryAuthFailed("Incorrect Password") from ex
except (
AIOSomecomfort.ConnectionError,
AIOSomecomfort.ConnectionTimeout,
aiosomecomfort.device.ConnectionError,
aiosomecomfort.device.ConnectionTimeout,
asyncio.TimeoutError,
) as ex:
raise ConfigEntryNotReady(
@ -117,5 +114,5 @@ class HoneywellData:
"""Shared data for Honeywell."""
entry_id: str
client: AIOSomecomfort.AIOSomeComfort
devices: dict[str, AIOSomecomfort.device.Device]
client: aiosomecomfort.AIOSomeComfort
devices: dict[str, aiosomecomfort.device.Device]

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import datetime
from typing import Any
import AIOSomecomfort
import aiosomecomfort
from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH,
@ -100,7 +100,7 @@ class HoneywellUSThermostat(ClimateEntity):
def __init__(
self,
data: HoneywellData,
device: AIOSomecomfort.device.Device,
device: aiosomecomfort.device.Device,
cool_away_temp: int | None,
heat_away_temp: int | None,
) -> None:
@ -295,7 +295,7 @@ class HoneywellUSThermostat(ClimateEntity):
if mode == "heat":
await self._device.set_setpoint_heat(temperature)
except AIOSomecomfort.SomeComfortError as err:
except aiosomecomfort.SomeComfortError as err:
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
async def async_set_temperature(self, **kwargs: Any) -> None:
@ -308,7 +308,7 @@ class HoneywellUSThermostat(ClimateEntity):
if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW):
await self._device.set_setpoint_heat(temperature)
except AIOSomecomfort.SomeComfortError as err:
except aiosomecomfort.SomeComfortError as err:
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
async def async_set_fan_mode(self, fan_mode: str) -> None:
@ -330,7 +330,7 @@ class HoneywellUSThermostat(ClimateEntity):
try:
# Get current mode
mode = self._device.system_mode
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
_LOGGER.error("Can not get system mode")
return
try:
@ -344,8 +344,7 @@ class HoneywellUSThermostat(ClimateEntity):
await self._device.set_hold_heat(True)
await self._device.set_setpoint_heat(self._heat_away_temp)
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
_LOGGER.error(
"Temperature out of range. Mode: %s, Heat Temperature: %.1f, Cool Temperature: %.1f",
mode,
@ -358,7 +357,7 @@ class HoneywellUSThermostat(ClimateEntity):
try:
# Get current mode
mode = self._device.system_mode
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
_LOGGER.error("Can not get system mode")
return
# Check that we got a valid mode back
@ -370,7 +369,7 @@ class HoneywellUSThermostat(ClimateEntity):
if mode in HEATING_MODES:
await self._device.set_hold_heat(True)
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
_LOGGER.error("Couldn't set permanent hold")
else:
_LOGGER.error("Invalid system mode returned: %s", mode)
@ -382,7 +381,7 @@ class HoneywellUSThermostat(ClimateEntity):
# Disabling all hold modes
await self._device.set_hold_cool(False)
await self._device.set_hold_heat(False)
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
_LOGGER.error("Can not stop hold mode")
async def async_set_preset_mode(self, preset_mode: str) -> None:
@ -411,13 +410,13 @@ class HoneywellUSThermostat(ClimateEntity):
try:
await self._device.refresh()
except (
AIOSomecomfort.SomeComfortError,
aiosomecomfort.SomeComfortError,
OSError,
):
try:
await self._data.client.login()
except AIOSomecomfort.SomeComfortError:
except aiosomecomfort.SomeComfortError:
self._attr_available = False
await self.hass.async_create_task(
self.hass.config_entries.async_reload(self._data.entry_id)

View File

@ -2,8 +2,10 @@
from __future__ import annotations
import asyncio
from collections.abc import Mapping
from typing import Any
import AIOSomecomfort
import aiosomecomfort
import voluptuous as vol
from homeassistant import config_entries
@ -20,11 +22,67 @@ from .const import (
DOMAIN,
)
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a honeywell config flow."""
VERSION = 1
entry: config_entries.ConfigEntry | None
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle re-authentication with Honeywell."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm re-authentication with Honeywell."""
errors: dict[str, str] = {}
if user_input:
assert self.entry is not None
password = user_input[CONF_PASSWORD]
data = {
CONF_USERNAME: self.entry.data[CONF_USERNAME],
CONF_PASSWORD: password,
}
try:
await self.is_valid(
username=data[CONF_USERNAME], password=data[CONF_PASSWORD]
)
except aiosomecomfort.AuthError:
errors["base"] = "invalid_auth"
except (
aiosomecomfort.ConnectionError,
aiosomecomfort.ConnectionTimeout,
asyncio.TimeoutError,
):
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
data={
**self.entry.data,
CONF_PASSWORD: password,
},
)
await self.hass.config_entries.async_reload(self.entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors,
)
async def async_step_user(self, user_input=None) -> FlowResult:
"""Create config entry. Show the setup form to the user."""
@ -32,11 +90,11 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
try:
await self.is_valid(**user_input)
except AIOSomecomfort.AuthError:
except aiosomecomfort.AuthError:
errors["base"] = "invalid_auth"
except (
AIOSomecomfort.ConnectionError,
AIOSomecomfort.ConnectionTimeout,
aiosomecomfort.ConnectionError,
aiosomecomfort.ConnectionTimeout,
asyncio.TimeoutError,
):
errors["base"] = "cannot_connect"
@ -57,7 +115,7 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def is_valid(self, **kwargs) -> bool:
"""Check if login credentials are valid."""
client = AIOSomecomfort.AIOSomeComfort(
client = aiosomecomfort.AIOSomeComfort(
kwargs[CONF_USERNAME],
kwargs[CONF_PASSWORD],
session=async_get_clientsession(self.hass),

View File

@ -3,7 +3,7 @@
"name": "Honeywell Total Connect Comfort (US)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/honeywell",
"requirements": ["aiosomecomfort==0.0.3"],
"requirements": ["aiosomecomfort==0.0.6"],
"codeowners": ["@rdfurman", "@mkmer"],
"iot_class": "cloud_polling",
"loggers": ["somecomfort"]

View File

@ -5,7 +5,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from AIOSomecomfort.device import Device
from aiosomecomfort.device import Device
from homeassistant.components.sensor import (
SensorDeviceClass,

View File

@ -255,7 +255,7 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = {
FILTER_STATES: ["open", "closed", "closing", "opening", "stopped"],
FILTER_NODE_DEF_ID: ["DimmerMotorSwitch_ADV"],
FILTER_INSTEON_TYPE: [TYPE_CATEGORY_COVER],
FILTER_ZWAVE_CAT: [],
FILTER_ZWAVE_CAT: ["106", "107"],
},
Platform.LIGHT: {
FILTER_UOM: ["51"],

View File

@ -36,8 +36,12 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
DataRateConverter,
DistanceConverter,
ElectricCurrentConverter,
ElectricPotentialConverter,
EnergyConverter,
InformationConverter,
MassConverter,
PowerConverter,
PressureConverter,
@ -128,8 +132,15 @@ QUERY_STATISTIC_META = [
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
**{unit: DataRateConverter for unit in DataRateConverter.VALID_UNITS},
**{unit: DistanceConverter for unit in DistanceConverter.VALID_UNITS},
**{unit: ElectricCurrentConverter for unit in ElectricCurrentConverter.VALID_UNITS},
**{
unit: ElectricPotentialConverter
for unit in ElectricPotentialConverter.VALID_UNITS
},
**{unit: EnergyConverter for unit in EnergyConverter.VALID_UNITS},
**{unit: InformationConverter for unit in InformationConverter.VALID_UNITS},
**{unit: MassConverter for unit in MassConverter.VALID_UNITS},
**{unit: PowerConverter for unit in PowerConverter.VALID_UNITS},
**{unit: PressureConverter for unit in PressureConverter.VALID_UNITS},

View File

@ -15,13 +15,18 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.json import JSON_DUMP
from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import (
DataRateConverter,
DistanceConverter,
ElectricCurrentConverter,
ElectricPotentialConverter,
EnergyConverter,
InformationConverter,
MassConverter,
PowerConverter,
PressureConverter,
SpeedConverter,
TemperatureConverter,
UnitlessRatioConverter,
VolumeConverter,
)
@ -47,6 +52,24 @@ from .util import (
_LOGGER: logging.Logger = logging.getLogger(__package__)
UNIT_SCHEMA = vol.Schema(
{
vol.Optional("data_rate"): vol.In(DataRateConverter.VALID_UNITS),
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
vol.Optional("electric_current"): vol.In(ElectricCurrentConverter.VALID_UNITS),
vol.Optional("voltage"): vol.In(ElectricPotentialConverter.VALID_UNITS),
vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS),
vol.Optional("information"): vol.In(InformationConverter.VALID_UNITS),
vol.Optional("mass"): vol.In(MassConverter.VALID_UNITS),
vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS),
vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS),
vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS),
vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS),
vol.Optional("unitless"): vol.In(UnitlessRatioConverter.VALID_UNITS),
vol.Optional("volume"): vol.In(VolumeConverter.VALID_UNITS),
}
)
@callback
def async_setup(hass: HomeAssistant) -> None:
@ -93,18 +116,7 @@ def _ws_get_statistic_during_period(
vol.Optional("types"): vol.All(
[vol.Any("max", "mean", "min", "change")], vol.Coerce(set)
),
vol.Optional("units"): vol.Schema(
{
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS),
vol.Optional("mass"): vol.In(MassConverter.VALID_UNITS),
vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS),
vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS),
vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS),
vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS),
vol.Optional("volume"): vol.In(VolumeConverter.VALID_UNITS),
}
),
vol.Optional("units"): UNIT_SCHEMA,
**PERIOD_SCHEMA.schema,
}
)
@ -211,18 +223,7 @@ async def ws_handle_get_statistics_during_period(
vol.Optional("end_time"): str,
vol.Optional("statistic_ids"): [str],
vol.Required("period"): vol.Any("5minute", "hour", "day", "week", "month"),
vol.Optional("units"): vol.Schema(
{
vol.Optional("distance"): vol.In(DistanceConverter.VALID_UNITS),
vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS),
vol.Optional("mass"): vol.In(MassConverter.VALID_UNITS),
vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS),
vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS),
vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS),
vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS),
vol.Optional("volume"): vol.In(VolumeConverter.VALID_UNITS),
}
),
vol.Optional("units"): UNIT_SCHEMA,
vol.Optional("types"): vol.All(
[vol.Any("last_reset", "max", "mean", "min", "state", "sum")],
vol.Coerce(set),

View File

@ -257,7 +257,7 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
device_class=SensorDeviceClass.ENERGY,
name="Battery available energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
state_class=SensorStateClass.MEASUREMENT,
state_class=SensorStateClass.TOTAL,
),
RenaultSensorEntityDescription(
key="battery_temperature",

View File

@ -9,14 +9,7 @@ import logging
from aiohttp import ClientConnectorError
import async_timeout
from reolink_aio.exceptions import (
ApiError,
InvalidContentTypeError,
LoginError,
NoDataError,
ReolinkError,
UnexpectedDataError,
)
from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
@ -48,17 +41,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
try:
await host.async_init()
except UserNotAdmin as err:
except (UserNotAdmin, CredentialsInvalidError) as err:
await host.stop()
raise ConfigEntryAuthFailed(err) from err
except (
ClientConnectorError,
asyncio.TimeoutError,
ApiError,
InvalidContentTypeError,
LoginError,
NoDataError,
ReolinkException,
UnexpectedDataError,
ReolinkError,
) as err:
await host.stop()
raise ConfigEntryNotReady(
@ -90,7 +80,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
update_interval=timedelta(seconds=DEVICE_UPDATE_INTERVAL),
)
# Fetch initial data so we have data when entities subscribe
try:
await coordinator_device_config_update.async_config_entry_first_refresh()
except ConfigEntryNotReady as err:
await host.stop()
raise err
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = ReolinkData(
host=host,

View File

@ -9,7 +9,7 @@ from typing import Any
import aiohttp
from aiohttp.web import Request
from reolink_aio.api import Host
from reolink_aio.exceptions import ReolinkError
from reolink_aio.exceptions import ReolinkError, SubscriptionError
from homeassistant.components import webhook
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
@ -76,7 +76,6 @@ class ReolinkHost:
raise ReolinkSetupException("Could not get mac address")
if not self._api.is_admin:
await self.stop()
raise UserNotAdmin(
f"User '{self._api.username}' has authorization level "
f"'{self._api.user_level}', only admin users can change camera settings"
@ -182,22 +181,19 @@ class ReolinkHost:
)
return
if await self._api.subscribe(self._webhook_url):
await self._api.subscribe(self._webhook_url)
_LOGGER.debug(
"Host %s: subscribed successfully to webhook %s",
self._api.host,
self._webhook_url,
)
else:
raise ReolinkWebhookException(
f"Host {self._api.host}: webhook subscription failed"
)
async def renew(self) -> None:
"""Renew the subscription of motion events (lease time is 15 minutes)."""
try:
await self._renew()
except ReolinkWebhookException as err:
except SubscriptionError as err:
if not self._lost_subscription:
self._lost_subscription = True
_LOGGER.error(
@ -220,25 +216,33 @@ class ReolinkHost:
return
timer = self._api.renewtimer
_LOGGER.debug(
"Host %s:%s should renew subscription in: %i seconds",
self._api.host,
self._api.port,
timer,
)
if timer > SUBSCRIPTION_RENEW_THRESHOLD:
return
if timer > 0:
if await self._api.renew():
try:
await self._api.renew()
except SubscriptionError as err:
_LOGGER.debug(
"Host %s: error renewing Reolink subscription, "
"trying to subscribe again: %s",
self._api.host,
err,
)
else:
_LOGGER.debug(
"Host %s successfully renewed Reolink subscription", self._api.host
)
return
_LOGGER.debug(
"Host %s: error renewing Reolink subscription, "
"trying to subscribe again",
self._api.host,
)
if not await self._api.subscribe(self._webhook_url):
raise ReolinkWebhookException(
f"Host {self._api.host}: webhook re-subscription failed"
)
await self._api.subscribe(self._webhook_url)
_LOGGER.debug(
"Host %s: Reolink re-subscription successful after it was expired",
self._api.host,
@ -246,7 +250,7 @@ class ReolinkHost:
async def register_webhook(self) -> None:
"""Register the webhook for motion events."""
self.webhook_id = f"{DOMAIN}_{self.unique_id.replace(':', '')}"
self.webhook_id = f"{DOMAIN}_{self.unique_id.replace(':', '')}_ONVIF"
event_id = self.webhook_id
webhook.async_register(

View File

@ -3,7 +3,7 @@
"name": "Reolink IP NVR/camera",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/reolink",
"requirements": ["reolink-aio==0.3.0"],
"requirements": ["reolink-aio==0.3.2"],
"dependencies": ["webhook"],
"codeowners": ["@starkillerOG"],
"iot_class": "local_polling",

View File

@ -2,7 +2,7 @@
"domain": "synology_dsm",
"name": "Synology DSM",
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
"requirements": ["py-synologydsm-api==2.0.2"],
"requirements": ["py-synologydsm-api==2.1.1"],
"codeowners": ["@hacf-fr", "@Quentame", "@mib1185"],
"config_flow": true,
"ssdp": [

View File

@ -1,7 +1,7 @@
"""Representation of a sirenBinary."""
from typing import Any
from homeassistant.components.siren import SirenEntity
from homeassistant.components.siren import SirenEntity, SirenEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -41,6 +41,13 @@ async def async_setup_entry(
class ZWaveMeSiren(ZWaveMeEntity, SirenEntity):
"""Representation of a ZWaveMe siren."""
def __init__(self, controller, device):
"""Initialize the device."""
super().__init__(controller, device)
self._attr_supported_features = (
SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF
)
@property
def is_on(self) -> bool:
"""Return the state of the siren."""

View File

@ -8,7 +8,7 @@ from .backports.enum import StrEnum
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2023
MINOR_VERSION: Final = 2
PATCH_VERSION: Final = "0"
PATCH_VERSION: Final = "1"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)

View File

@ -757,7 +757,7 @@ class _ScriptRun:
with trace_path(condition_path):
for idx, cond in enumerate(conditions):
with trace_path(str(idx)):
if not cond(hass, variables):
if cond(hass, variables) is False:
return False
except exceptions.ConditionError as ex:
_LOGGER.warning("Error in '%s[%s]' evaluation: %s", name, idx, ex)

View File

@ -23,7 +23,7 @@ fnvhash==0.1.0
hass-nabucasa==0.61.0
hassil==0.2.6
home-assistant-bluetooth==1.9.2
home-assistant-frontend==20230201.0
home-assistant-frontend==20230202.0
home-assistant-intents==2023.1.31
httpx==0.23.3
ifaddr==0.1.7

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2023.2.0"
version = "2023.2.1"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"

View File

@ -276,7 +276,7 @@ aioskybell==22.7.0
aioslimproto==2.1.1
# homeassistant.components.honeywell
aiosomecomfort==0.0.3
aiosomecomfort==0.0.6
# homeassistant.components.steamist
aiosteamist==0.3.2
@ -907,7 +907,7 @@ hole==0.8.0
holidays==0.18.0
# homeassistant.components.frontend
home-assistant-frontend==20230201.0
home-assistant-frontend==20230202.0
# homeassistant.components.conversation
home-assistant-intents==2023.1.31
@ -1442,7 +1442,7 @@ py-schluter==0.1.7
py-sucks==0.9.8
# homeassistant.components.synology_dsm
py-synologydsm-api==2.0.2
py-synologydsm-api==2.1.1
# homeassistant.components.zabbix
py-zabbix==1.1.7
@ -2227,7 +2227,7 @@ regenmaschine==2022.11.0
renault-api==0.1.11
# homeassistant.components.reolink
reolink-aio==0.3.0
reolink-aio==0.3.2
# homeassistant.components.python_script
restrictedpython==6.0

View File

@ -254,7 +254,7 @@ aioskybell==22.7.0
aioslimproto==2.1.1
# homeassistant.components.honeywell
aiosomecomfort==0.0.3
aiosomecomfort==0.0.6
# homeassistant.components.steamist
aiosteamist==0.3.2
@ -690,7 +690,7 @@ hole==0.8.0
holidays==0.18.0
# homeassistant.components.frontend
home-assistant-frontend==20230201.0
home-assistant-frontend==20230202.0
# homeassistant.components.conversation
home-assistant-intents==2023.1.31
@ -1051,7 +1051,7 @@ py-melissa-climate==2.1.4
py-nightscout==1.2.2
# homeassistant.components.synology_dsm
py-synologydsm-api==2.0.2
py-synologydsm-api==2.1.1
# homeassistant.components.seventeentrack
py17track==2021.12.2
@ -1572,7 +1572,7 @@ regenmaschine==2022.11.0
renault-api==0.1.11
# homeassistant.components.reolink
reolink-aio==0.3.0
reolink-aio==0.3.2
# homeassistant.components.python_script
restrictedpython==6.0

View File

@ -2,7 +2,7 @@
from unittest.mock import AsyncMock, create_autospec, patch
import AIOSomecomfort
import aiosomecomfort
import pytest
from homeassistant.components.honeywell.const import DOMAIN
@ -30,7 +30,7 @@ def config_entry(config_data):
@pytest.fixture
def device():
"""Mock a somecomfort.Device."""
mock_device = create_autospec(AIOSomecomfort.device.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(AIOSomecomfort.device.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(AIOSomecomfort.device.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(AIOSomecomfort.location.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,13 +94,13 @@ def location(device):
@pytest.fixture(autouse=True)
def client(location):
"""Mock a somecomfort.SomeComfort client."""
client_mock = create_autospec(AIOSomecomfort.AIOSomeComfort, 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.AIOSomecomfort.AIOSomeComfort"
"homeassistant.components.honeywell.aiosomecomfort.AIOSomeComfort"
) as sc_class_mock:
sc_class_mock.return_value = client_mock
yield client_mock

View File

@ -1,7 +1,9 @@
"""Tests for honeywell config flow."""
import asyncio
from unittest.mock import MagicMock, patch
import AIOSomecomfort
import aiosomecomfort
import pytest
from homeassistant import data_entry_flow
from homeassistant.components.honeywell.const import (
@ -9,8 +11,10 @@ from homeassistant.components.honeywell.const import (
CONF_HEAT_AWAY_TEMPERATURE,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER, ConfigEntryState
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
@ -35,8 +39,7 @@ async def test_show_authenticate_form(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
client.login.side_effect = aiosomecomfort.device.ConnectionError
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
)
@ -45,7 +48,7 @@ async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None:
async def test_auth_error(hass: HomeAssistant, client: MagicMock) -> None:
"""Test that an error message is shown on login fail."""
client.login.side_effect = AIOSomecomfort.AuthError
client.login.side_effect = aiosomecomfort.device.AuthError
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
@ -116,3 +119,137 @@ async def test_create_option_entry(
CONF_COOL_AWAY_TEMPERATURE: 1,
CONF_HEAT_AWAY_TEMPERATURE: 2,
}
async def test_reauth_flow(hass: HomeAssistant) -> None:
"""Test a successful reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"
assert mock_entry.data == {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "new-password",
}
async def test_reauth_flow_auth_error(hass: HomeAssistant, client: MagicMock) -> None:
"""Test an authorization error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
client.login.side_effect = aiosomecomfort.device.AuthError
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
@pytest.mark.parametrize(
"error",
[
aiosomecomfort.device.ConnectionError,
aiosomecomfort.device.ConnectionTimeout,
asyncio.TimeoutError,
],
)
async def test_reauth_flow_connnection_error(
hass: HomeAssistant, client: MagicMock, error
) -> None:
"""Test a connection error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
client.login.side_effect = error
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}

View File

@ -2,7 +2,7 @@
from unittest.mock import create_autospec, patch
import AIOSomecomfort
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(AIOSomecomfort.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

View File

@ -1,6 +1,6 @@
"""Test honeywell sensor."""
from AIOSomecomfort.device import Device
from AIOSomecomfort.location import Location
from aiosomecomfort.device import Device
from aiosomecomfort.location import Location
import pytest
from homeassistant.core import HomeAssistant

View File

@ -139,7 +139,7 @@ MOCK_VEHICLES = {
ATTR_DEVICE_CLASS: SensorDeviceClass.ENERGY,
ATTR_ENTITY_ID: "sensor.reg_number_battery_available_energy",
ATTR_STATE: "31",
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
ATTR_UNIQUE_ID: "vf1aaaaa555777999_battery_available_energy",
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,
},
@ -368,7 +368,7 @@ MOCK_VEHICLES = {
ATTR_DEVICE_CLASS: SensorDeviceClass.ENERGY,
ATTR_ENTITY_ID: "sensor.reg_number_battery_available_energy",
ATTR_STATE: "0",
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
ATTR_UNIQUE_ID: "vf1aaaaa555777999_battery_available_energy",
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,
},
@ -597,7 +597,7 @@ MOCK_VEHICLES = {
ATTR_DEVICE_CLASS: SensorDeviceClass.ENERGY,
ATTR_ENTITY_ID: "sensor.reg_number_battery_available_energy",
ATTR_STATE: "31",
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
ATTR_UNIQUE_ID: "vf1aaaaa555777123_battery_available_energy",
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,
},

View File

@ -2915,6 +2915,45 @@ async def test_if(
assert_action_trace(expected_trace)
async def test_if_disabled(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test if action with a disabled condition."""
sequence = cv.SCRIPT_SCHEMA(
{
"if": {
"alias": "if condition",
"condition": "template",
"value_template": "{{ var == 1 }}",
"enabled": "false",
},
"then": {
"alias": "if then",
"event": "test_event",
"event_data": {"if": "then"},
},
"else": {
"alias": "if else",
"event": "test_event",
"event_data": {"if": "else"},
},
}
)
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
await script_obj.async_run(context=Context())
await hass.async_block_till_done()
expected_trace = {
"0": [{"result": {"choice": "then"}}],
"0/if": [{"result": {"result": True}}],
"0/if/condition/0": [{"result": {"result": None}}],
"0/then/0": [{"result": {"event": "test_event", "event_data": {"if": "then"}}}],
}
assert_action_trace(expected_trace)
async def test_if_condition_validation(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None: