mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
2023.7.3 (#96936)
This commit is contained in:
commit
40b5605caf
@ -1,6 +1,7 @@
|
|||||||
"""The Android TV Remote integration."""
|
"""The Android TV Remote integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from androidtvremote2 import (
|
from androidtvremote2 import (
|
||||||
@ -9,6 +10,7 @@ from androidtvremote2 import (
|
|||||||
ConnectionClosed,
|
ConnectionClosed,
|
||||||
InvalidAuth,
|
InvalidAuth,
|
||||||
)
|
)
|
||||||
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
|
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
|
||||||
@ -43,11 +45,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
api.add_is_available_updated_callback(is_available_updated)
|
api.add_is_available_updated_callback(is_available_updated)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await api.async_connect()
|
async with async_timeout.timeout(5.0):
|
||||||
|
await api.async_connect()
|
||||||
except InvalidAuth as exc:
|
except InvalidAuth as exc:
|
||||||
# The Android TV is hard reset or the certificate and key files were deleted.
|
# The Android TV is hard reset or the certificate and key files were deleted.
|
||||||
raise ConfigEntryAuthFailed from exc
|
raise ConfigEntryAuthFailed from exc
|
||||||
except (CannotConnect, ConnectionClosed) as exc:
|
except (CannotConnect, ConnectionClosed, asyncio.TimeoutError) as exc:
|
||||||
# The Android TV is network unreachable. Raise exception and let Home Assistant retry
|
# The Android TV is network unreachable. Raise exception and let Home Assistant retry
|
||||||
# later. If device gets a new IP address the zeroconf flow will update the config.
|
# later. If device gets a new IP address the zeroconf flow will update the config.
|
||||||
raise ConfigEntryNotReady from exc
|
raise ConfigEntryNotReady from exc
|
||||||
|
@ -139,7 +139,7 @@ async def async_migrate_unique_id(
|
|||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
old_unique_id = config_entry.unique_id
|
old_unique_id = config_entry.unique_id
|
||||||
new_unique_id = api.device.mac
|
new_unique_id = api.device.mac
|
||||||
new_name = api.device.values["name"]
|
new_name = api.device.values.get("name")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["devolo_plc_api"],
|
"loggers": ["devolo_plc_api"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["devolo-plc-api==1.3.1"],
|
"requirements": ["devolo-plc-api==1.3.2"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_dvl-deviceapi._tcp.local.",
|
"type": "_dvl-deviceapi._tcp.local.",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["env_canada"],
|
"loggers": ["env_canada"],
|
||||||
"requirements": ["env-canada==0.5.35"]
|
"requirements": ["env-canada==0.5.36"]
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,10 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity):
|
|||||||
self._attr_native_min_value = static_info.min_value
|
self._attr_native_min_value = static_info.min_value
|
||||||
self._attr_native_max_value = static_info.max_value
|
self._attr_native_max_value = static_info.max_value
|
||||||
self._attr_native_step = static_info.step
|
self._attr_native_step = static_info.step
|
||||||
self._attr_native_unit_of_measurement = static_info.unit_of_measurement
|
# protobuf doesn't support nullable strings so we need to check
|
||||||
|
# if the string is empty
|
||||||
|
if unit_of_measurement := static_info.unit_of_measurement:
|
||||||
|
self._attr_native_unit_of_measurement = unit_of_measurement
|
||||||
if mode := static_info.mode:
|
if mode := static_info.mode:
|
||||||
self._attr_mode = NUMBER_MODES.from_esphome(mode)
|
self._attr_mode = NUMBER_MODES.from_esphome(mode)
|
||||||
else:
|
else:
|
||||||
|
@ -76,7 +76,10 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
|
|||||||
super()._on_static_info_update(static_info)
|
super()._on_static_info_update(static_info)
|
||||||
static_info = self._static_info
|
static_info = self._static_info
|
||||||
self._attr_force_update = static_info.force_update
|
self._attr_force_update = static_info.force_update
|
||||||
self._attr_native_unit_of_measurement = static_info.unit_of_measurement
|
# protobuf doesn't support nullable strings so we need to check
|
||||||
|
# if the string is empty
|
||||||
|
if unit_of_measurement := static_info.unit_of_measurement:
|
||||||
|
self._attr_native_unit_of_measurement = unit_of_measurement
|
||||||
self._attr_device_class = try_parse_enum(
|
self._attr_device_class = try_parse_enum(
|
||||||
SensorDeviceClass, static_info.device_class
|
SensorDeviceClass, static_info.device_class
|
||||||
)
|
)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyfibaro"],
|
"loggers": ["pyfibaro"],
|
||||||
"requirements": ["pyfibaro==0.7.1"]
|
"requirements": ["pyfibaro==0.7.2"]
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,12 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
|
REAUTH_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -42,18 +47,12 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Confirm re-authentication with Honeywell."""
|
"""Confirm re-authentication with Honeywell."""
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
assert self.entry is not None
|
||||||
if user_input:
|
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:
|
try:
|
||||||
await self.is_valid(
|
await self.is_valid(
|
||||||
username=data[CONF_USERNAME], password=data[CONF_PASSWORD]
|
username=user_input[CONF_USERNAME],
|
||||||
|
password=user_input[CONF_PASSWORD],
|
||||||
)
|
)
|
||||||
|
|
||||||
except aiosomecomfort.AuthError:
|
except aiosomecomfort.AuthError:
|
||||||
@ -71,7 +70,7 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self.entry,
|
self.entry,
|
||||||
data={
|
data={
|
||||||
**self.entry.data,
|
**self.entry.data,
|
||||||
CONF_PASSWORD: password,
|
**user_input,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await self.hass.config_entries.async_reload(self.entry.entry_id)
|
await self.hass.config_entries.async_reload(self.entry.entry_id)
|
||||||
@ -79,7 +78,9 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="reauth_confirm",
|
step_id="reauth_confirm",
|
||||||
data_schema=REAUTH_SCHEMA,
|
data_schema=self.add_suggested_values_to_schema(
|
||||||
|
REAUTH_SCHEMA, self.entry.data
|
||||||
|
),
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/honeywell",
|
"documentation": "https://www.home-assistant.io/integrations/honeywell",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["somecomfort"],
|
"loggers": ["somecomfort"],
|
||||||
"requirements": ["AIOSomecomfort==0.0.14"]
|
"requirements": ["AIOSomecomfort==0.0.15"]
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ from typing import Any
|
|||||||
from aioimaplib import AUTH, IMAP4_SSL, NONAUTH, SELECTED, AioImapException
|
from aioimaplib import AUTH, IMAP4_SSL, NONAUTH, SELECTED, AioImapException
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
@ -54,6 +54,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
BACKOFF_TIME = 10
|
BACKOFF_TIME = 10
|
||||||
|
|
||||||
EVENT_IMAP = "imap_content"
|
EVENT_IMAP = "imap_content"
|
||||||
|
MAX_ERRORS = 3
|
||||||
MAX_EVENT_DATA_BYTES = 32168
|
MAX_EVENT_DATA_BYTES = 32168
|
||||||
|
|
||||||
|
|
||||||
@ -174,6 +175,7 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initiate imap client."""
|
"""Initiate imap client."""
|
||||||
self.imap_client = imap_client
|
self.imap_client = imap_client
|
||||||
|
self.auth_errors: int = 0
|
||||||
self._last_message_id: str | None = None
|
self._last_message_id: str | None = None
|
||||||
self.custom_event_template = None
|
self.custom_event_template = None
|
||||||
_custom_event_template = entry.data.get(CONF_CUSTOM_EVENT_DATA_TEMPLATE)
|
_custom_event_template = entry.data.get(CONF_CUSTOM_EVENT_DATA_TEMPLATE)
|
||||||
@ -315,7 +317,9 @@ class ImapPollingDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
async def _async_update_data(self) -> int | None:
|
async def _async_update_data(self) -> int | None:
|
||||||
"""Update the number of unread emails."""
|
"""Update the number of unread emails."""
|
||||||
try:
|
try:
|
||||||
return await self._async_fetch_number_of_messages()
|
messages = await self._async_fetch_number_of_messages()
|
||||||
|
self.auth_errors = 0
|
||||||
|
return messages
|
||||||
except (
|
except (
|
||||||
AioImapException,
|
AioImapException,
|
||||||
UpdateFailed,
|
UpdateFailed,
|
||||||
@ -330,8 +334,15 @@ class ImapPollingDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
self.async_set_update_error(ex)
|
self.async_set_update_error(ex)
|
||||||
raise ConfigEntryError("Selected mailbox folder is invalid.") from ex
|
raise ConfigEntryError("Selected mailbox folder is invalid.") from ex
|
||||||
except InvalidAuth as ex:
|
except InvalidAuth as ex:
|
||||||
_LOGGER.warning("Username or password incorrect, starting reauthentication")
|
|
||||||
await self._cleanup()
|
await self._cleanup()
|
||||||
|
self.auth_errors += 1
|
||||||
|
if self.auth_errors <= MAX_ERRORS:
|
||||||
|
_LOGGER.warning("Authentication failed, retrying")
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Username or password incorrect, starting reauthentication"
|
||||||
|
)
|
||||||
|
self.config_entry.async_start_reauth(self.hass)
|
||||||
self.async_set_update_error(ex)
|
self.async_set_update_error(ex)
|
||||||
raise ConfigEntryAuthFailed() from ex
|
raise ConfigEntryAuthFailed() from ex
|
||||||
|
|
||||||
@ -359,27 +370,28 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _async_wait_push_loop(self) -> None:
|
async def _async_wait_push_loop(self) -> None:
|
||||||
"""Wait for data push from server."""
|
"""Wait for data push from server."""
|
||||||
|
cleanup = False
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
number_of_messages = await self._async_fetch_number_of_messages()
|
number_of_messages = await self._async_fetch_number_of_messages()
|
||||||
except InvalidAuth as ex:
|
except InvalidAuth as ex:
|
||||||
|
self.auth_errors += 1
|
||||||
await self._cleanup()
|
await self._cleanup()
|
||||||
_LOGGER.warning(
|
if self.auth_errors <= MAX_ERRORS:
|
||||||
"Username or password incorrect, starting reauthentication"
|
_LOGGER.warning("Authentication failed, retrying")
|
||||||
)
|
else:
|
||||||
self.config_entry.async_start_reauth(self.hass)
|
_LOGGER.warning(
|
||||||
|
"Username or password incorrect, starting reauthentication"
|
||||||
|
)
|
||||||
|
self.config_entry.async_start_reauth(self.hass)
|
||||||
self.async_set_update_error(ex)
|
self.async_set_update_error(ex)
|
||||||
await asyncio.sleep(BACKOFF_TIME)
|
await asyncio.sleep(BACKOFF_TIME)
|
||||||
except InvalidFolder as ex:
|
except InvalidFolder as ex:
|
||||||
_LOGGER.warning("Selected mailbox folder is invalid")
|
_LOGGER.warning("Selected mailbox folder is invalid")
|
||||||
await self._cleanup()
|
await self._cleanup()
|
||||||
self.config_entry.async_set_state(
|
|
||||||
self.hass,
|
|
||||||
ConfigEntryState.SETUP_ERROR,
|
|
||||||
"Selected mailbox folder is invalid.",
|
|
||||||
)
|
|
||||||
self.async_set_update_error(ex)
|
self.async_set_update_error(ex)
|
||||||
await asyncio.sleep(BACKOFF_TIME)
|
await asyncio.sleep(BACKOFF_TIME)
|
||||||
|
continue
|
||||||
except (
|
except (
|
||||||
UpdateFailed,
|
UpdateFailed,
|
||||||
AioImapException,
|
AioImapException,
|
||||||
@ -390,6 +402,7 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
await asyncio.sleep(BACKOFF_TIME)
|
await asyncio.sleep(BACKOFF_TIME)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
self.auth_errors = 0
|
||||||
self.async_set_updated_data(number_of_messages)
|
self.async_set_updated_data(number_of_messages)
|
||||||
try:
|
try:
|
||||||
idle: asyncio.Future = await self.imap_client.idle_start()
|
idle: asyncio.Future = await self.imap_client.idle_start()
|
||||||
@ -398,6 +411,10 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
async with async_timeout.timeout(10):
|
async with async_timeout.timeout(10):
|
||||||
await idle
|
await idle
|
||||||
|
|
||||||
|
# From python 3.11 asyncio.TimeoutError is an alias of TimeoutError
|
||||||
|
except asyncio.CancelledError as ex:
|
||||||
|
cleanup = True
|
||||||
|
raise asyncio.CancelledError from ex
|
||||||
except (AioImapException, asyncio.TimeoutError):
|
except (AioImapException, asyncio.TimeoutError):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Lost %s (will attempt to reconnect after %s s)",
|
"Lost %s (will attempt to reconnect after %s s)",
|
||||||
@ -406,6 +423,9 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
|
|||||||
)
|
)
|
||||||
await self._cleanup()
|
await self._cleanup()
|
||||||
await asyncio.sleep(BACKOFF_TIME)
|
await asyncio.sleep(BACKOFF_TIME)
|
||||||
|
finally:
|
||||||
|
if cleanup:
|
||||||
|
await self._cleanup()
|
||||||
|
|
||||||
async def shutdown(self, *_: Any) -> None:
|
async def shutdown(self, *_: Any) -> None:
|
||||||
"""Close resources."""
|
"""Close resources."""
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pymazda"],
|
"loggers": ["pymazda"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pymazda==0.3.9"]
|
"requirements": ["pymazda==0.3.10"]
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
info.setdefault("type", 101)
|
info.setdefault("type", 101)
|
||||||
|
|
||||||
device_type = info["type"]
|
device_type = info["type"]
|
||||||
if device_type in [101, 106, 107]:
|
if device_type in [101, 106, 107, 120]:
|
||||||
device = _get_mystrom_switch(host)
|
device = _get_mystrom_switch(host)
|
||||||
platforms = PLATFORMS_SWITCH
|
platforms = PLATFORMS_SWITCH
|
||||||
await _async_get_device_state(device, info["ip"])
|
await _async_get_device_state(device, info["ip"])
|
||||||
@ -86,7 +86,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
device_type = hass.data[DOMAIN][entry.entry_id].info["type"]
|
device_type = hass.data[DOMAIN][entry.entry_id].info["type"]
|
||||||
platforms = []
|
platforms = []
|
||||||
if device_type in [101, 106, 107]:
|
if device_type in [101, 106, 107, 120]:
|
||||||
platforms.extend(PLATFORMS_SWITCH)
|
platforms.extend(PLATFORMS_SWITCH)
|
||||||
elif device_type in [102, 105]:
|
elif device_type in [102, 105]:
|
||||||
platforms.extend(PLATFORMS_BULB)
|
platforms.extend(PLATFORMS_BULB)
|
||||||
|
@ -387,8 +387,12 @@ class ONVIFDevice:
|
|||||||
"WSPullPointSupport"
|
"WSPullPointSupport"
|
||||||
)
|
)
|
||||||
LOGGER.debug("%s: WSPullPointSupport: %s", self.name, pull_point_support)
|
LOGGER.debug("%s: WSPullPointSupport: %s", self.name, pull_point_support)
|
||||||
|
# Even if the camera claims it does not support PullPoint, try anyway
|
||||||
|
# since at least some AXIS and Bosch models do. The reverse is also
|
||||||
|
# true where some cameras claim they support PullPoint but don't so
|
||||||
|
# the only way to know is to try.
|
||||||
return await self.events.async_start(
|
return await self.events.async_start(
|
||||||
pull_point_support is not False,
|
True,
|
||||||
self.config_entry.options.get(
|
self.config_entry.options.get(
|
||||||
CONF_ENABLE_WEBHOOKS, DEFAULT_ENABLE_WEBHOOKS
|
CONF_ENABLE_WEBHOOKS, DEFAULT_ENABLE_WEBHOOKS
|
||||||
),
|
),
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
||||||
"requirements": ["onvif-zeep-async==3.1.9", "WSDiscovery==2.0.0"]
|
"requirements": ["onvif-zeep-async==3.1.12", "WSDiscovery==2.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,8 @@ async def _title(hass: HomeAssistant, discovery_info: HassioServiceInfo) -> str:
|
|||||||
addon_info = await async_get_addon_info(hass, discovery_info.slug)
|
addon_info = await async_get_addon_info(hass, discovery_info.slug)
|
||||||
device = addon_info.get("options", {}).get("device")
|
device = addon_info.get("options", {}).get("device")
|
||||||
|
|
||||||
if _is_yellow(hass) and device == "/dev/TTYAMA1":
|
if _is_yellow(hass) and device == "/dev/ttyAMA1":
|
||||||
return "Home Assistant Yellow"
|
return f"Home Assistant Yellow ({discovery_info.name})"
|
||||||
|
|
||||||
if device and "SkyConnect" in device:
|
if device and "SkyConnect" in device:
|
||||||
return "Home Assistant SkyConnect"
|
return "Home Assistant SkyConnect"
|
||||||
@ -130,6 +130,11 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
url = f"http://{config['host']}:{config['port']}"
|
url = f"http://{config['host']}:{config['port']}"
|
||||||
config_entry_data = {"url": url}
|
config_entry_data = {"url": url}
|
||||||
|
|
||||||
|
if self._async_in_progress(include_uninitialized=True):
|
||||||
|
# We currently don't handle multiple config entries, abort if hassio
|
||||||
|
# discovers multiple addons with otbr support
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
if current_entries := self._async_current_entries():
|
if current_entries := self._async_current_entries():
|
||||||
for current_entry in current_entries:
|
for current_entry in current_entries:
|
||||||
if current_entry.source != SOURCE_HASSIO:
|
if current_entry.source != SOURCE_HASSIO:
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/rainbird",
|
"documentation": "https://www.home-assistant.io/integrations/rainbird",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyrainbird"],
|
"loggers": ["pyrainbird"],
|
||||||
"requirements": ["pyrainbird==2.1.0"]
|
"requirements": ["pyrainbird==3.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": ["python-roborock==0.30.0"]
|
"requirements": ["python-roborock==0.30.1"]
|
||||||
}
|
}
|
||||||
|
@ -151,9 +151,10 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity):
|
|||||||
@async_handle_api_call
|
@async_handle_api_call
|
||||||
async def async_turn_on_timer(self, key: str, value: bool) -> bool:
|
async def async_turn_on_timer(self, key: str, value: bool) -> bool:
|
||||||
"""Make service call to api for setting timer."""
|
"""Make service call to api for setting timer."""
|
||||||
|
new_state = not self.device_data.device_on
|
||||||
data = {
|
data = {
|
||||||
"minutesFromNow": 60,
|
"minutesFromNow": 60,
|
||||||
"acState": {**self.device_data.ac_states, "on": value},
|
"acState": {**self.device_data.ac_states, "on": new_state},
|
||||||
}
|
}
|
||||||
result = await self._client.async_set_timer(self._device_id, data)
|
result = await self._client.async_set_timer(self._device_id, data)
|
||||||
return bool(result.get("status") == "success")
|
return bool(result.get("status") == "success")
|
||||||
|
@ -61,6 +61,8 @@ ENTITY_DESCRIPTION_ALARM = SIAAlarmControlPanelEntityDescription(
|
|||||||
"OS": STATE_ALARM_DISARMED,
|
"OS": STATE_ALARM_DISARMED,
|
||||||
"NC": STATE_ALARM_ARMED_NIGHT,
|
"NC": STATE_ALARM_ARMED_NIGHT,
|
||||||
"NL": STATE_ALARM_ARMED_NIGHT,
|
"NL": STATE_ALARM_ARMED_NIGHT,
|
||||||
|
"NE": STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||||
|
"NF": STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||||
"BR": PREVIOUS_STATE,
|
"BR": PREVIOUS_STATE,
|
||||||
"NP": PREVIOUS_STATE,
|
"NP": PREVIOUS_STATE,
|
||||||
"NO": PREVIOUS_STATE,
|
"NO": PREVIOUS_STATE,
|
||||||
|
@ -127,6 +127,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity):
|
|||||||
AlarmControlPanelEntityFeature.ARM_HOME
|
AlarmControlPanelEntityFeature.ARM_HOME
|
||||||
| AlarmControlPanelEntityFeature.ARM_AWAY
|
| AlarmControlPanelEntityFeature.ARM_AWAY
|
||||||
)
|
)
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, simplisafe: SimpliSafe, system: SystemType) -> None:
|
def __init__(self, simplisafe: SimpliSafe, system: SystemType) -> None:
|
||||||
"""Initialize the SimpliSafe alarm."""
|
"""Initialize the SimpliSafe alarm."""
|
||||||
|
@ -111,7 +111,6 @@ class BatteryBinarySensor(SimpliSafeEntity, BinarySensorEntity):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(simplisafe, system, device=device)
|
super().__init__(simplisafe, system, device=device)
|
||||||
|
|
||||||
self._attr_name = "Battery"
|
|
||||||
self._attr_unique_id = f"{super().unique_id}-battery"
|
self._attr_unique_id = f"{super().unique_id}-battery"
|
||||||
self._device: DeviceV3
|
self._device: DeviceV3
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ async def _async_clear_notifications(system: System) -> None:
|
|||||||
BUTTON_DESCRIPTIONS = (
|
BUTTON_DESCRIPTIONS = (
|
||||||
SimpliSafeButtonDescription(
|
SimpliSafeButtonDescription(
|
||||||
key=BUTTON_KIND_CLEAR_NOTIFICATIONS,
|
key=BUTTON_KIND_CLEAR_NOTIFICATIONS,
|
||||||
name="Clear notifications",
|
translation_key=BUTTON_KIND_CLEAR_NOTIFICATIONS,
|
||||||
push_action=_async_clear_notifications,
|
push_action=_async_clear_notifications,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -29,5 +29,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"button": {
|
||||||
|
"clear_notifications": {
|
||||||
|
"name": "Clear notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,11 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None:
|
|||||||
# Must have one of the min_required
|
# Must have one of the min_required
|
||||||
if any(capability in capabilities for capability in min_required):
|
if any(capability in capabilities for capability in min_required):
|
||||||
# Return all capabilities supported/consumed
|
# Return all capabilities supported/consumed
|
||||||
return min_required + [Capability.battery, Capability.switch_level]
|
return min_required + [
|
||||||
|
Capability.battery,
|
||||||
|
Capability.switch_level,
|
||||||
|
Capability.window_shade_level,
|
||||||
|
]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -74,12 +78,16 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity):
|
|||||||
"""Initialize the cover class."""
|
"""Initialize the cover class."""
|
||||||
super().__init__(device)
|
super().__init__(device)
|
||||||
self._device_class = None
|
self._device_class = None
|
||||||
|
self._current_cover_position = None
|
||||||
self._state = None
|
self._state = None
|
||||||
self._state_attrs = None
|
self._state_attrs = None
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = (
|
||||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||||
)
|
)
|
||||||
if Capability.switch_level in device.capabilities:
|
if (
|
||||||
|
Capability.switch_level in device.capabilities
|
||||||
|
or Capability.window_shade_level in device.capabilities
|
||||||
|
):
|
||||||
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||||
@ -103,7 +111,12 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity):
|
|||||||
if not self.supported_features & CoverEntityFeature.SET_POSITION:
|
if not self.supported_features & CoverEntityFeature.SET_POSITION:
|
||||||
return
|
return
|
||||||
# Do not set_status=True as device will report progress.
|
# Do not set_status=True as device will report progress.
|
||||||
await self._device.set_level(kwargs[ATTR_POSITION], 0)
|
if Capability.window_shade_level in self._device.capabilities:
|
||||||
|
await self._device.set_window_shade_level(
|
||||||
|
kwargs[ATTR_POSITION], set_status=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self._device.set_level(kwargs[ATTR_POSITION], set_status=False)
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Update the attrs of the cover."""
|
"""Update the attrs of the cover."""
|
||||||
@ -117,6 +130,11 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity):
|
|||||||
self._device_class = CoverDeviceClass.GARAGE
|
self._device_class = CoverDeviceClass.GARAGE
|
||||||
self._state = VALUE_TO_STATE.get(self._device.status.door)
|
self._state = VALUE_TO_STATE.get(self._device.status.door)
|
||||||
|
|
||||||
|
if Capability.window_shade_level in self._device.capabilities:
|
||||||
|
self._current_cover_position = self._device.status.shade_level
|
||||||
|
elif Capability.switch_level in self._device.capabilities:
|
||||||
|
self._current_cover_position = self._device.status.level
|
||||||
|
|
||||||
self._state_attrs = {}
|
self._state_attrs = {}
|
||||||
battery = self._device.status.attributes[Attribute.battery].value
|
battery = self._device.status.attributes[Attribute.battery].value
|
||||||
if battery is not None:
|
if battery is not None:
|
||||||
@ -142,9 +160,7 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity):
|
|||||||
@property
|
@property
|
||||||
def current_cover_position(self) -> int | None:
|
def current_cover_position(self) -> int | None:
|
||||||
"""Return current position of cover."""
|
"""Return current position of cover."""
|
||||||
if not self.supported_features & CoverEntityFeature.SET_POSITION:
|
return self._current_cover_position
|
||||||
return None
|
|
||||||
return self._device.status.level
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> CoverDeviceClass | None:
|
def device_class(self) -> CoverDeviceClass | None:
|
||||||
|
@ -30,5 +30,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["httpsig", "pysmartapp", "pysmartthings"],
|
"loggers": ["httpsig", "pysmartapp", "pysmartthings"],
|
||||||
"requirements": ["pysmartapp==0.3.3", "pysmartthings==0.7.6"]
|
"requirements": ["pysmartapp==0.3.5", "pysmartthings==0.7.8"]
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,12 @@ from async_upnp_client.utils import CaseInsensitiveDict
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import network
|
from homeassistant.components import network
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
MATCH_ALL,
|
MATCH_ALL,
|
||||||
__version__ as current_version,
|
__version__ as current_version,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback as core_callback
|
from homeassistant.core import Event, HomeAssistant, callback as core_callback
|
||||||
from homeassistant.data_entry_flow import BaseServiceInfo
|
from homeassistant.data_entry_flow import BaseServiceInfo
|
||||||
from homeassistant.helpers import config_validation as cv, discovery_flow
|
from homeassistant.helpers import config_validation as cv, discovery_flow
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
@ -728,15 +729,18 @@ class Server:
|
|||||||
|
|
||||||
async def async_start(self) -> None:
|
async def async_start(self) -> None:
|
||||||
"""Start the server."""
|
"""Start the server."""
|
||||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
bus = self.hass.bus
|
||||||
await self._async_start_upnp_servers()
|
bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
||||||
|
bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STARTED, self._async_start_upnp_servers
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_get_instance_udn(self) -> str:
|
async def _async_get_instance_udn(self) -> str:
|
||||||
"""Get Unique Device Name for this instance."""
|
"""Get Unique Device Name for this instance."""
|
||||||
instance_id = await async_get_instance_id(self.hass)
|
instance_id = await async_get_instance_id(self.hass)
|
||||||
return f"uuid:{instance_id[0:8]}-{instance_id[8:12]}-{instance_id[12:16]}-{instance_id[16:20]}-{instance_id[20:32]}".upper()
|
return f"uuid:{instance_id[0:8]}-{instance_id[8:12]}-{instance_id[12:16]}-{instance_id[16:20]}-{instance_id[20:32]}".upper()
|
||||||
|
|
||||||
async def _async_start_upnp_servers(self) -> None:
|
async def _async_start_upnp_servers(self, event: Event) -> None:
|
||||||
"""Start the UPnP/SSDP servers."""
|
"""Start the UPnP/SSDP servers."""
|
||||||
# Update UDN with our instance UDN.
|
# Update UDN with our instance UDN.
|
||||||
udn = await self._async_get_instance_udn()
|
udn = await self._async_get_instance_udn()
|
||||||
|
@ -36,6 +36,7 @@ class StookalertBinarySensor(BinarySensorEntity):
|
|||||||
_attr_attribution = "Data provided by rivm.nl"
|
_attr_attribution = "Data provided by rivm.nl"
|
||||||
_attr_device_class = BinarySensorDeviceClass.SAFETY
|
_attr_device_class = BinarySensorDeviceClass.SAFETY
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, client: stookalert.stookalert, entry: ConfigEntry) -> None:
|
def __init__(self, client: stookalert.stookalert, entry: ConfigEntry) -> None:
|
||||||
"""Initialize a Stookalert device."""
|
"""Initialize a Stookalert device."""
|
||||||
|
@ -151,7 +151,7 @@ def find_moov(mp4_io: BufferedIOBase) -> int:
|
|||||||
while 1:
|
while 1:
|
||||||
mp4_io.seek(index)
|
mp4_io.seek(index)
|
||||||
box_header = mp4_io.read(8)
|
box_header = mp4_io.read(8)
|
||||||
if len(box_header) != 8:
|
if len(box_header) != 8 or box_header[0:4] == b"\x00\x00\x00\x00":
|
||||||
raise HomeAssistantError("moov atom not found")
|
raise HomeAssistantError("moov atom not found")
|
||||||
if box_header[4:8] == b"moov":
|
if box_header[4:8] == b"moov":
|
||||||
return index
|
return index
|
||||||
|
@ -205,7 +205,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
|||||||
key="process",
|
key="process",
|
||||||
name="Process",
|
name="Process",
|
||||||
icon=CPU_ICON,
|
icon=CPU_ICON,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
),
|
),
|
||||||
"processor_use": SysMonitorSensorEntityDescription(
|
"processor_use": SysMonitorSensorEntityDescription(
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"tensorflow==2.5.0",
|
"tensorflow==2.5.0",
|
||||||
"tf-models-official==2.5.0",
|
"tf-models-official==2.5.0",
|
||||||
"pycocotools==2.0.1",
|
"pycocotools==2.0.6",
|
||||||
"numpy==1.23.2",
|
"numpy==1.23.2",
|
||||||
"Pillow==9.5.0"
|
"Pillow==9.5.0"
|
||||||
]
|
]
|
||||||
|
@ -84,6 +84,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
|||||||
"""Representation of the fan."""
|
"""Representation of the fan."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
_attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED
|
_attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/vallox",
|
"documentation": "https://www.home-assistant.io/integrations/vallox",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["vallox_websocket_api"],
|
"loggers": ["vallox_websocket_api"],
|
||||||
"requirements": ["vallox-websocket-api==3.2.1"]
|
"requirements": ["vallox-websocket-api==3.3.0"]
|
||||||
}
|
}
|
||||||
|
@ -694,7 +694,7 @@ class ConfigEntry:
|
|||||||
if self._on_unload is not None:
|
if self._on_unload is not None:
|
||||||
while self._on_unload:
|
while self._on_unload:
|
||||||
if job := self._on_unload.pop()():
|
if job := self._on_unload.pop()():
|
||||||
self._tasks.add(hass.async_create_task(job))
|
self.async_create_task(hass, job)
|
||||||
|
|
||||||
if not self._tasks and not self._background_tasks:
|
if not self._tasks and not self._background_tasks:
|
||||||
return
|
return
|
||||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 7
|
MINOR_VERSION: Final = 7
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
aiodiscover==1.4.16
|
aiodiscover==1.4.16
|
||||||
aiohttp-cors==0.7.0
|
aiohttp-cors==0.7.0
|
||||||
aiohttp==3.8.4
|
aiohttp==3.8.5
|
||||||
astral==2.2
|
astral==2.2
|
||||||
async-timeout==4.0.2
|
async-timeout==4.0.2
|
||||||
async-upnp-client==0.33.2
|
async-upnp-client==0.33.2
|
||||||
@ -42,7 +42,7 @@ pyserial==3.5
|
|||||||
python-slugify==4.0.1
|
python-slugify==4.0.1
|
||||||
PyTurboJPEG==1.6.7
|
PyTurboJPEG==1.6.7
|
||||||
pyudev==0.23.2
|
pyudev==0.23.2
|
||||||
PyYAML==6.0
|
PyYAML==6.0.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
scapy==2.5.0
|
scapy==2.5.0
|
||||||
SQLAlchemy==2.0.15
|
SQLAlchemy==2.0.15
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.7.2"
|
version = "2023.7.3"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -24,7 +24,7 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
requires-python = ">=3.10.0"
|
requires-python = ">=3.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp==3.8.4",
|
"aiohttp==3.8.5",
|
||||||
"astral==2.2",
|
"astral==2.2",
|
||||||
"async-timeout==4.0.2",
|
"async-timeout==4.0.2",
|
||||||
"attrs==22.2.0",
|
"attrs==22.2.0",
|
||||||
@ -48,7 +48,7 @@ dependencies = [
|
|||||||
"orjson==3.9.1",
|
"orjson==3.9.1",
|
||||||
"pip>=21.3.1,<23.2",
|
"pip>=21.3.1,<23.2",
|
||||||
"python-slugify==4.0.1",
|
"python-slugify==4.0.1",
|
||||||
"PyYAML==6.0",
|
"PyYAML==6.0.1",
|
||||||
"requests==2.31.0",
|
"requests==2.31.0",
|
||||||
"typing_extensions>=4.6.3,<5.0",
|
"typing_extensions>=4.6.3,<5.0",
|
||||||
"ulid-transform==0.7.2",
|
"ulid-transform==0.7.2",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-c homeassistant/package_constraints.txt
|
-c homeassistant/package_constraints.txt
|
||||||
|
|
||||||
# Home Assistant Core
|
# Home Assistant Core
|
||||||
aiohttp==3.8.4
|
aiohttp==3.8.5
|
||||||
astral==2.2
|
astral==2.2
|
||||||
async-timeout==4.0.2
|
async-timeout==4.0.2
|
||||||
attrs==22.2.0
|
attrs==22.2.0
|
||||||
@ -21,7 +21,7 @@ pyOpenSSL==23.2.0
|
|||||||
orjson==3.9.1
|
orjson==3.9.1
|
||||||
pip>=21.3.1,<23.2
|
pip>=21.3.1,<23.2
|
||||||
python-slugify==4.0.1
|
python-slugify==4.0.1
|
||||||
PyYAML==6.0
|
PyYAML==6.0.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
typing_extensions>=4.6.3,<5.0
|
typing_extensions>=4.6.3,<5.0
|
||||||
ulid-transform==0.7.2
|
ulid-transform==0.7.2
|
||||||
|
@ -8,7 +8,7 @@ AEMET-OpenData==0.2.2
|
|||||||
AIOAladdinConnect==0.1.56
|
AIOAladdinConnect==0.1.56
|
||||||
|
|
||||||
# homeassistant.components.honeywell
|
# homeassistant.components.honeywell
|
||||||
AIOSomecomfort==0.0.14
|
AIOSomecomfort==0.0.15
|
||||||
|
|
||||||
# homeassistant.components.adax
|
# homeassistant.components.adax
|
||||||
Adax-local==0.1.5
|
Adax-local==0.1.5
|
||||||
@ -658,7 +658,7 @@ denonavr==0.11.2
|
|||||||
devolo-home-control-api==0.18.2
|
devolo-home-control-api==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_network
|
# homeassistant.components.devolo_home_network
|
||||||
devolo-plc-api==1.3.1
|
devolo-plc-api==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.directv
|
# homeassistant.components.directv
|
||||||
directv==0.4.0
|
directv==0.4.0
|
||||||
@ -730,7 +730,7 @@ enocean==0.50
|
|||||||
enturclient==0.2.4
|
enturclient==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.environment_canada
|
# homeassistant.components.environment_canada
|
||||||
env-canada==0.5.35
|
env-canada==0.5.36
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
envoy-reader==0.20.1
|
envoy-reader==0.20.1
|
||||||
@ -1327,7 +1327,7 @@ ondilo==0.2.0
|
|||||||
onkyo-eiscp==1.2.7
|
onkyo-eiscp==1.2.7
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==3.1.9
|
onvif-zeep-async==3.1.12
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.2.0
|
open-garage==0.2.0
|
||||||
@ -1600,7 +1600,7 @@ pycketcasts==1.0.1
|
|||||||
pycmus==0.1.1
|
pycmus==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
pycocotools==2.0.1
|
# pycocotools==2.0.6
|
||||||
|
|
||||||
# homeassistant.components.comfoconnect
|
# homeassistant.components.comfoconnect
|
||||||
pycomfoconnect==0.5.1
|
pycomfoconnect==0.5.1
|
||||||
@ -1666,7 +1666,7 @@ pyevilgenius==2.0.0
|
|||||||
pyezviz==0.2.1.2
|
pyezviz==0.2.1.2
|
||||||
|
|
||||||
# homeassistant.components.fibaro
|
# homeassistant.components.fibaro
|
||||||
pyfibaro==0.7.1
|
pyfibaro==0.7.2
|
||||||
|
|
||||||
# homeassistant.components.fido
|
# homeassistant.components.fido
|
||||||
pyfido==2.1.2
|
pyfido==2.1.2
|
||||||
@ -1810,7 +1810,7 @@ pymailgunner==1.4
|
|||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
# homeassistant.components.mazda
|
||||||
pymazda==0.3.9
|
pymazda==0.3.10
|
||||||
|
|
||||||
# homeassistant.components.mediaroom
|
# homeassistant.components.mediaroom
|
||||||
pymediaroom==0.6.5.4
|
pymediaroom==0.6.5.4
|
||||||
@ -1938,7 +1938,7 @@ pyqwikswitch==0.93
|
|||||||
pyrail==0.0.3
|
pyrail==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.rainbird
|
# homeassistant.components.rainbird
|
||||||
pyrainbird==2.1.0
|
pyrainbird==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.recswitch
|
# homeassistant.components.recswitch
|
||||||
pyrecswitch==1.0.2
|
pyrecswitch==1.0.2
|
||||||
@ -2000,10 +2000,10 @@ pysma==0.7.3
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartapp==0.3.3
|
pysmartapp==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==0.7.6
|
pysmartthings==0.7.8
|
||||||
|
|
||||||
# homeassistant.components.edl21
|
# homeassistant.components.edl21
|
||||||
pysml==0.0.12
|
pysml==0.0.12
|
||||||
@ -2139,7 +2139,7 @@ python-qbittorrent==0.4.3
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.30.0
|
python-roborock==0.30.1
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@ -2599,7 +2599,7 @@ url-normalize==1.4.3
|
|||||||
uvcclient==0.11.0
|
uvcclient==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.vallox
|
# homeassistant.components.vallox
|
||||||
vallox-websocket-api==3.2.1
|
vallox-websocket-api==3.3.0
|
||||||
|
|
||||||
# homeassistant.components.rdw
|
# homeassistant.components.rdw
|
||||||
vehicle==1.0.1
|
vehicle==1.0.1
|
||||||
|
@ -10,7 +10,7 @@ AEMET-OpenData==0.2.2
|
|||||||
AIOAladdinConnect==0.1.56
|
AIOAladdinConnect==0.1.56
|
||||||
|
|
||||||
# homeassistant.components.honeywell
|
# homeassistant.components.honeywell
|
||||||
AIOSomecomfort==0.0.14
|
AIOSomecomfort==0.0.15
|
||||||
|
|
||||||
# homeassistant.components.adax
|
# homeassistant.components.adax
|
||||||
Adax-local==0.1.5
|
Adax-local==0.1.5
|
||||||
@ -532,7 +532,7 @@ denonavr==0.11.2
|
|||||||
devolo-home-control-api==0.18.2
|
devolo-home-control-api==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_network
|
# homeassistant.components.devolo_home_network
|
||||||
devolo-plc-api==1.3.1
|
devolo-plc-api==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.directv
|
# homeassistant.components.directv
|
||||||
directv==0.4.0
|
directv==0.4.0
|
||||||
@ -583,7 +583,7 @@ energyzero==0.4.1
|
|||||||
enocean==0.50
|
enocean==0.50
|
||||||
|
|
||||||
# homeassistant.components.environment_canada
|
# homeassistant.components.environment_canada
|
||||||
env-canada==0.5.35
|
env-canada==0.5.36
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
envoy-reader==0.20.1
|
envoy-reader==0.20.1
|
||||||
@ -1011,7 +1011,7 @@ omnilogic==0.4.5
|
|||||||
ondilo==0.2.0
|
ondilo==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==3.1.9
|
onvif-zeep-async==3.1.12
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.2.0
|
open-garage==0.2.0
|
||||||
@ -1227,7 +1227,7 @@ pyevilgenius==2.0.0
|
|||||||
pyezviz==0.2.1.2
|
pyezviz==0.2.1.2
|
||||||
|
|
||||||
# homeassistant.components.fibaro
|
# homeassistant.components.fibaro
|
||||||
pyfibaro==0.7.1
|
pyfibaro==0.7.2
|
||||||
|
|
||||||
# homeassistant.components.fido
|
# homeassistant.components.fido
|
||||||
pyfido==2.1.2
|
pyfido==2.1.2
|
||||||
@ -1338,7 +1338,7 @@ pymailgunner==1.4
|
|||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
# homeassistant.components.mazda
|
||||||
pymazda==0.3.9
|
pymazda==0.3.10
|
||||||
|
|
||||||
# homeassistant.components.melcloud
|
# homeassistant.components.melcloud
|
||||||
pymelcloud==2.5.8
|
pymelcloud==2.5.8
|
||||||
@ -1439,7 +1439,7 @@ pyps4-2ndscreen==1.3.1
|
|||||||
pyqwikswitch==0.93
|
pyqwikswitch==0.93
|
||||||
|
|
||||||
# homeassistant.components.rainbird
|
# homeassistant.components.rainbird
|
||||||
pyrainbird==2.1.0
|
pyrainbird==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.risco
|
# homeassistant.components.risco
|
||||||
pyrisco==0.5.7
|
pyrisco==0.5.7
|
||||||
@ -1486,10 +1486,10 @@ pysma==0.7.3
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartapp==0.3.3
|
pysmartapp==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==0.7.6
|
pysmartthings==0.7.8
|
||||||
|
|
||||||
# homeassistant.components.edl21
|
# homeassistant.components.edl21
|
||||||
pysml==0.0.12
|
pysml==0.0.12
|
||||||
@ -1565,7 +1565,7 @@ python-picnic-api==1.1.0
|
|||||||
python-qbittorrent==0.4.3
|
python-qbittorrent==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.30.0
|
python-roborock==0.30.1
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@ -1899,7 +1899,7 @@ url-normalize==1.4.3
|
|||||||
uvcclient==0.11.0
|
uvcclient==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.vallox
|
# homeassistant.components.vallox
|
||||||
vallox-websocket-api==3.2.1
|
vallox-websocket-api==3.3.0
|
||||||
|
|
||||||
# homeassistant.components.rdw
|
# homeassistant.components.rdw
|
||||||
vehicle==1.0.1
|
vehicle==1.0.1
|
||||||
|
@ -33,6 +33,7 @@ COMMENT_REQUIREMENTS = (
|
|||||||
"face-recognition",
|
"face-recognition",
|
||||||
"opencv-python-headless",
|
"opencv-python-headless",
|
||||||
"pybluez",
|
"pybluez",
|
||||||
|
"pycocotools",
|
||||||
"pycups",
|
"pycups",
|
||||||
"python-eq3bt",
|
"python-eq3bt",
|
||||||
"python-gammu",
|
"python-gammu",
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.components.number import (
|
|||||||
DOMAIN as NUMBER_DOMAIN,
|
DOMAIN as NUMBER_DOMAIN,
|
||||||
SERVICE_SET_VALUE,
|
SERVICE_SET_VALUE,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
|
||||||
@ -89,3 +89,36 @@ async def test_generic_number_nan(
|
|||||||
state = hass.states.get("number.test_my_number")
|
state = hass.states.get("number.test_my_number")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_generic_number_with_unit_of_measurement_as_empty_string(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_generic_device_entry,
|
||||||
|
) -> None:
|
||||||
|
"""Test a generic number entity with nan state."""
|
||||||
|
entity_info = [
|
||||||
|
NumberInfo(
|
||||||
|
object_id="mynumber",
|
||||||
|
key=1,
|
||||||
|
name="my number",
|
||||||
|
unique_id="my_number",
|
||||||
|
max_value=100,
|
||||||
|
min_value=0,
|
||||||
|
step=1,
|
||||||
|
unit_of_measurement="",
|
||||||
|
mode=ESPHomeNumberMode.SLIDER,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
states = [NumberState(key=1, state=42)]
|
||||||
|
user_service = []
|
||||||
|
await mock_generic_device_entry(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=entity_info,
|
||||||
|
user_service=user_service,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
state = hass.states.get("number.test_my_number")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "42"
|
||||||
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
@ -13,7 +13,7 @@ from aioesphomeapi import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass
|
||||||
from homeassistant.const import ATTR_ICON, STATE_UNKNOWN
|
from homeassistant.const import ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
@ -275,3 +275,30 @@ async def test_generic_text_sensor(
|
|||||||
state = hass.states.get("sensor.test_my_sensor")
|
state = hass.states.get("sensor.test_my_sensor")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == "i am a teapot"
|
assert state.state == "i am a teapot"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_generic_numeric_sensor_empty_string_uom(
|
||||||
|
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||||
|
) -> None:
|
||||||
|
"""Test a generic numeric sensor that has an empty string as the uom."""
|
||||||
|
entity_info = [
|
||||||
|
SensorInfo(
|
||||||
|
object_id="mysensor",
|
||||||
|
key=1,
|
||||||
|
name="my sensor",
|
||||||
|
unique_id="my_sensor",
|
||||||
|
unit_of_measurement="",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
states = [SensorState(key=1, state=123, missing_state=False)]
|
||||||
|
user_service = []
|
||||||
|
await mock_generic_device_entry(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=entity_info,
|
||||||
|
user_service=user_service,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.test_my_sensor")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "123"
|
||||||
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
@ -156,14 +156,14 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
|
|||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_PASSWORD: "new-password"},
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result2["type"] == FlowResultType.ABORT
|
assert result2["type"] == FlowResultType.ABORT
|
||||||
assert result2["reason"] == "reauth_successful"
|
assert result2["reason"] == "reauth_successful"
|
||||||
assert mock_entry.data == {
|
assert mock_entry.data == {
|
||||||
CONF_USERNAME: "test-username",
|
CONF_USERNAME: "new-username",
|
||||||
CONF_PASSWORD: "new-password",
|
CONF_PASSWORD: "new-password",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant, client: MagicMock) ->
|
|||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_PASSWORD: "new-password"},
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ async def test_reauth_flow_connnection_error(
|
|||||||
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_PASSWORD: "new-password"},
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -228,6 +228,48 @@ async def test_initial_invalid_folder_error(
|
|||||||
assert (state is not None) == success
|
assert (state is not None) == success
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.imap.coordinator.MAX_ERRORS", 1)
|
||||||
|
@pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"])
|
||||||
|
async def test_late_authentication_retry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
mock_imap_protocol: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test retrying authentication after a search was failed."""
|
||||||
|
|
||||||
|
# Mock an error in waiting for a pushed update
|
||||||
|
mock_imap_protocol.wait_server_push.side_effect = AioImapException(
|
||||||
|
"Something went wrong"
|
||||||
|
)
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Mock that the search fails, this will trigger
|
||||||
|
# that the connection will be restarted
|
||||||
|
# Then fail selecting the folder
|
||||||
|
mock_imap_protocol.search.return_value = Response(*BAD_RESPONSE)
|
||||||
|
mock_imap_protocol.login.side_effect = Response(*BAD_RESPONSE)
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert "Authentication failed, retrying" in caplog.text
|
||||||
|
|
||||||
|
# we still should have an entity with an unavailable state
|
||||||
|
state = hass.states.get("sensor.imap_email_email_com")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.imap.coordinator.MAX_ERRORS", 0)
|
||||||
@pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"])
|
@pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"])
|
||||||
async def test_late_authentication_error(
|
async def test_late_authentication_error(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -68,7 +68,7 @@ async def test_init_switch_and_unload(
|
|||||||
(110, "sensor", ConfigEntryState.SETUP_ERROR, True),
|
(110, "sensor", ConfigEntryState.SETUP_ERROR, True),
|
||||||
(113, "switch", ConfigEntryState.SETUP_ERROR, True),
|
(113, "switch", ConfigEntryState.SETUP_ERROR, True),
|
||||||
(118, "button", ConfigEntryState.SETUP_ERROR, True),
|
(118, "button", ConfigEntryState.SETUP_ERROR, True),
|
||||||
(120, "switch", ConfigEntryState.SETUP_ERROR, True),
|
(120, "switch", ConfigEntryState.LOADED, False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_init_bulb(
|
async def test_init_bulb(
|
||||||
|
@ -23,6 +23,12 @@ HASSIO_DATA = hassio.HassioServiceInfo(
|
|||||||
slug="otbr",
|
slug="otbr",
|
||||||
uuid="12345",
|
uuid="12345",
|
||||||
)
|
)
|
||||||
|
HASSIO_DATA_2 = hassio.HassioServiceInfo(
|
||||||
|
config={"host": "core-silabs-multiprotocol_2", "port": 8082},
|
||||||
|
name="Silicon Labs Multiprotocol",
|
||||||
|
slug="other_addon",
|
||||||
|
uuid="23456",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_info")
|
@pytest.fixture(name="addon_info")
|
||||||
@ -234,7 +240,7 @@ async def test_hassio_discovery_flow_yellow(
|
|||||||
addon_info.return_value = {
|
addon_info.return_value = {
|
||||||
"available": True,
|
"available": True,
|
||||||
"hostname": None,
|
"hostname": None,
|
||||||
"options": {"device": "/dev/TTYAMA1"},
|
"options": {"device": "/dev/ttyAMA1"},
|
||||||
"state": None,
|
"state": None,
|
||||||
"update_available": False,
|
"update_available": False,
|
||||||
"version": None,
|
"version": None,
|
||||||
@ -255,7 +261,7 @@ async def test_hassio_discovery_flow_yellow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "Home Assistant Yellow"
|
assert result["title"] == "Home Assistant Yellow (Silicon Labs Multiprotocol)"
|
||||||
assert result["data"] == expected_data
|
assert result["data"] == expected_data
|
||||||
assert result["options"] == {}
|
assert result["options"] == {}
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
@ -263,7 +269,7 @@ async def test_hassio_discovery_flow_yellow(
|
|||||||
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
|
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
|
||||||
assert config_entry.data == expected_data
|
assert config_entry.data == expected_data
|
||||||
assert config_entry.options == {}
|
assert config_entry.options == {}
|
||||||
assert config_entry.title == "Home Assistant Yellow"
|
assert config_entry.title == "Home Assistant Yellow (Silicon Labs Multiprotocol)"
|
||||||
assert config_entry.unique_id == HASSIO_DATA.uuid
|
assert config_entry.unique_id == HASSIO_DATA.uuid
|
||||||
|
|
||||||
|
|
||||||
@ -313,6 +319,80 @@ async def test_hassio_discovery_flow_sky_connect(
|
|||||||
assert config_entry.unique_id == HASSIO_DATA.uuid
|
assert config_entry.unique_id == HASSIO_DATA.uuid
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hassio_discovery_flow_2x_addons(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info
|
||||||
|
) -> None:
|
||||||
|
"""Test the hassio discovery flow when the user has 2 addons with otbr support."""
|
||||||
|
url1 = "http://core-silabs-multiprotocol:8081"
|
||||||
|
url2 = "http://core-silabs-multiprotocol_2:8081"
|
||||||
|
aioclient_mock.get(f"{url1}/node/dataset/active", text="aa")
|
||||||
|
aioclient_mock.get(f"{url2}/node/dataset/active", text="bb")
|
||||||
|
|
||||||
|
async def _addon_info(hass, slug):
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
if slug == "otbr":
|
||||||
|
return {
|
||||||
|
"available": True,
|
||||||
|
"hostname": None,
|
||||||
|
"options": {
|
||||||
|
"device": (
|
||||||
|
"/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_"
|
||||||
|
"9e2adbd75b8beb119fe564a0f320645d-if00-port0"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"state": None,
|
||||||
|
"update_available": False,
|
||||||
|
"version": None,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"available": True,
|
||||||
|
"hostname": None,
|
||||||
|
"options": {
|
||||||
|
"device": (
|
||||||
|
"/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_"
|
||||||
|
"9e2adbd75b8beb119fe564a0f320645d-if00-port1"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"state": None,
|
||||||
|
"update_available": False,
|
||||||
|
"version": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_info.side_effect = _addon_info
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.otbr.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
results = await asyncio.gather(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA
|
||||||
|
),
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA_2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_data = {
|
||||||
|
"url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']}",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert results[0]["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert results[0]["title"] == "Home Assistant SkyConnect"
|
||||||
|
assert results[0]["data"] == expected_data
|
||||||
|
assert results[0]["options"] == {}
|
||||||
|
assert results[1]["type"] == FlowResultType.ABORT
|
||||||
|
assert results[1]["reason"] == "single_instance_allowed"
|
||||||
|
assert len(hass.config_entries.async_entries(otbr.DOMAIN)) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
|
||||||
|
assert config_entry.data == expected_data
|
||||||
|
assert config_entry.options == {}
|
||||||
|
assert config_entry.title == "Home Assistant SkyConnect"
|
||||||
|
assert config_entry.unique_id == HASSIO_DATA.uuid
|
||||||
|
|
||||||
|
|
||||||
async def test_hassio_discovery_flow_router_not_setup(
|
async def test_hassio_discovery_flow_router_not_setup(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -103,8 +103,10 @@ async def test_close(hass: HomeAssistant, device_factory) -> None:
|
|||||||
assert state.state == STATE_CLOSING
|
assert state.state == STATE_CLOSING
|
||||||
|
|
||||||
|
|
||||||
async def test_set_cover_position(hass: HomeAssistant, device_factory) -> None:
|
async def test_set_cover_position_switch_level(
|
||||||
"""Test the cover sets to the specific position."""
|
hass: HomeAssistant, device_factory
|
||||||
|
) -> None:
|
||||||
|
"""Test the cover sets to the specific position for legacy devices that use Capability.switch_level."""
|
||||||
# Arrange
|
# Arrange
|
||||||
device = device_factory(
|
device = device_factory(
|
||||||
"Shade",
|
"Shade",
|
||||||
@ -130,6 +132,37 @@ async def test_set_cover_position(hass: HomeAssistant, device_factory) -> None:
|
|||||||
assert device._api.post_device_command.call_count == 1 # type: ignore
|
assert device._api.post_device_command.call_count == 1 # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_cover_position(hass: HomeAssistant, device_factory) -> None:
|
||||||
|
"""Test the cover sets to the specific position."""
|
||||||
|
# Arrange
|
||||||
|
device = device_factory(
|
||||||
|
"Shade",
|
||||||
|
[Capability.window_shade, Capability.battery, Capability.window_shade_level],
|
||||||
|
{
|
||||||
|
Attribute.window_shade: "opening",
|
||||||
|
Attribute.battery: 95,
|
||||||
|
Attribute.shade_level: 10,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await setup_platform(hass, COVER_DOMAIN, devices=[device])
|
||||||
|
# Act
|
||||||
|
await hass.services.async_call(
|
||||||
|
COVER_DOMAIN,
|
||||||
|
SERVICE_SET_COVER_POSITION,
|
||||||
|
{ATTR_POSITION: 50, "entity_id": "all"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("cover.shade")
|
||||||
|
# Result of call does not update state
|
||||||
|
assert state.state == STATE_OPENING
|
||||||
|
assert state.attributes[ATTR_BATTERY_LEVEL] == 95
|
||||||
|
assert state.attributes[ATTR_CURRENT_POSITION] == 10
|
||||||
|
# Ensure API called
|
||||||
|
|
||||||
|
assert device._api.post_device_command.call_count == 1 # type: ignore
|
||||||
|
|
||||||
|
|
||||||
async def test_set_cover_position_unsupported(
|
async def test_set_cover_position_unsupported(
|
||||||
hass: HomeAssistant, device_factory
|
hass: HomeAssistant, device_factory
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -742,6 +742,8 @@ async def test_bind_failure_skips_adapter(
|
|||||||
SsdpListener.async_start = _async_start
|
SsdpListener.async_start = _async_start
|
||||||
UpnpServer.async_start = _async_start
|
UpnpServer.async_start = _async_start
|
||||||
await init_ssdp_component(hass)
|
await init_ssdp_component(hass)
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert "Failed to setup listener for" in caplog.text
|
assert "Failed to setup listener for" in caplog.text
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ class FakePyAvBuffer:
|
|||||||
# Forward to appropriate FakeStream
|
# Forward to appropriate FakeStream
|
||||||
packet.stream.mux(packet)
|
packet.stream.mux(packet)
|
||||||
# Make new init/part data available to the worker
|
# Make new init/part data available to the worker
|
||||||
self.memory_file.write(b"\x00\x00\x00\x00moov")
|
self.memory_file.write(b"\x00\x00\x00\x08moov")
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close the buffer."""
|
"""Close the buffer."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user