mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Merge pull request #72107 from home-assistant/rc
This commit is contained in:
commit
204762d23a
@ -3,7 +3,7 @@
|
|||||||
"name": "Brother Printer",
|
"name": "Brother Printer",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/brother",
|
"documentation": "https://www.home-assistant.io/integrations/brother",
|
||||||
"codeowners": ["@bieniu"],
|
"codeowners": ["@bieniu"],
|
||||||
"requirements": ["brother==1.2.0"],
|
"requirements": ["brother==1.1.0"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_printer._tcp.local.",
|
"type": "_printer._tcp.local.",
|
||||||
|
@ -23,6 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from . import FIBARO_DEVICES, FibaroDevice
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
PARALLEL_UPDATES = 2
|
||||||
|
|
||||||
|
|
||||||
def scaleto255(value: int | None) -> int:
|
def scaleto255(value: int | None) -> int:
|
||||||
"""Scale the input value from 0-100 to 0-255."""
|
"""Scale the input value from 0-100 to 0-255."""
|
||||||
|
@ -132,7 +132,7 @@ class FileSizeCoordinator(DataUpdateCoordinator):
|
|||||||
async def _async_update_data(self) -> dict[str, float | int | datetime]:
|
async def _async_update_data(self) -> dict[str, float | int | datetime]:
|
||||||
"""Fetch file information."""
|
"""Fetch file information."""
|
||||||
try:
|
try:
|
||||||
statinfo = os.stat(self._path)
|
statinfo = await self.hass.async_add_executor_job(os.stat, self._path)
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
raise UpdateFailed(f"Can not retrieve file statistics {error}") from error
|
raise UpdateFailed(f"Can not retrieve file statistics {error}") from error
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class HistoryStatsState:
|
|||||||
"""The current stats of the history stats."""
|
"""The current stats of the history stats."""
|
||||||
|
|
||||||
hours_matched: float | None
|
hours_matched: float | None
|
||||||
changes_to_match_state: int | None
|
match_count: int | None
|
||||||
period: tuple[datetime.datetime, datetime.datetime]
|
period: tuple[datetime.datetime, datetime.datetime]
|
||||||
|
|
||||||
|
|
||||||
@ -121,14 +121,12 @@ class HistoryStats:
|
|||||||
self._state = HistoryStatsState(None, None, self._period)
|
self._state = HistoryStatsState(None, None, self._period)
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
hours_matched, changes_to_match_state = self._async_compute_hours_and_changes(
|
hours_matched, match_count = self._async_compute_hours_and_changes(
|
||||||
now_timestamp,
|
now_timestamp,
|
||||||
current_period_start_timestamp,
|
current_period_start_timestamp,
|
||||||
current_period_end_timestamp,
|
current_period_end_timestamp,
|
||||||
)
|
)
|
||||||
self._state = HistoryStatsState(
|
self._state = HistoryStatsState(hours_matched, match_count, self._period)
|
||||||
hours_matched, changes_to_match_state, self._period
|
|
||||||
)
|
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def _update_from_database(
|
def _update_from_database(
|
||||||
@ -156,7 +154,7 @@ class HistoryStats:
|
|||||||
)
|
)
|
||||||
last_state_change_timestamp = start_timestamp
|
last_state_change_timestamp = start_timestamp
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
changes_to_match_state = 0
|
match_count = 1 if previous_state_matches else 0
|
||||||
|
|
||||||
# Make calculations
|
# Make calculations
|
||||||
for item in self._history_current_period:
|
for item in self._history_current_period:
|
||||||
@ -166,7 +164,7 @@ class HistoryStats:
|
|||||||
if previous_state_matches:
|
if previous_state_matches:
|
||||||
elapsed += state_change_timestamp - last_state_change_timestamp
|
elapsed += state_change_timestamp - last_state_change_timestamp
|
||||||
elif current_state_matches:
|
elif current_state_matches:
|
||||||
changes_to_match_state += 1
|
match_count += 1
|
||||||
|
|
||||||
previous_state_matches = current_state_matches
|
previous_state_matches = current_state_matches
|
||||||
last_state_change_timestamp = state_change_timestamp
|
last_state_change_timestamp = state_change_timestamp
|
||||||
@ -178,4 +176,4 @@ class HistoryStats:
|
|||||||
|
|
||||||
# Save value in hours
|
# Save value in hours
|
||||||
hours_matched = elapsed / 3600
|
hours_matched = elapsed / 3600
|
||||||
return hours_matched, changes_to_match_state
|
return hours_matched, match_count
|
||||||
|
@ -166,4 +166,4 @@ class HistoryStatsSensor(HistoryStatsSensorBase):
|
|||||||
elif self._type == CONF_TYPE_RATIO:
|
elif self._type == CONF_TYPE_RATIO:
|
||||||
self._attr_native_value = pretty_ratio(state.hours_matched, state.period)
|
self._attr_native_value = pretty_ratio(state.hours_matched, state.period)
|
||||||
elif self._type == CONF_TYPE_COUNT:
|
elif self._type == CONF_TYPE_COUNT:
|
||||||
self._attr_native_value = state.changes_to_match_state
|
self._attr_native_value = state.match_count
|
||||||
|
@ -419,6 +419,8 @@ class LIFXManager:
|
|||||||
if color_resp is None or version_resp is None:
|
if color_resp is None or version_resp is None:
|
||||||
_LOGGER.error("Failed to connect to %s", bulb.ip_addr)
|
_LOGGER.error("Failed to connect to %s", bulb.ip_addr)
|
||||||
bulb.registered = False
|
bulb.registered = False
|
||||||
|
if bulb.mac_addr in self.discoveries_inflight:
|
||||||
|
self.discoveries_inflight.pop(bulb.mac_addr)
|
||||||
else:
|
else:
|
||||||
bulb.timeout = MESSAGE_TIMEOUT
|
bulb.timeout = MESSAGE_TIMEOUT
|
||||||
bulb.retry_count = MESSAGE_RETRIES
|
bulb.retry_count = MESSAGE_RETRIES
|
||||||
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Coroutine, Sequence
|
from collections.abc import Coroutine, Sequence
|
||||||
import contextlib
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -12,9 +11,11 @@ from async_upnp_client.client import UpnpDevice, UpnpService, UpnpStateVariable
|
|||||||
from async_upnp_client.client_factory import UpnpFactory
|
from async_upnp_client.client_factory import UpnpFactory
|
||||||
from async_upnp_client.exceptions import (
|
from async_upnp_client.exceptions import (
|
||||||
UpnpActionResponseError,
|
UpnpActionResponseError,
|
||||||
|
UpnpCommunicationError,
|
||||||
UpnpConnectionError,
|
UpnpConnectionError,
|
||||||
UpnpError,
|
UpnpError,
|
||||||
UpnpResponseError,
|
UpnpResponseError,
|
||||||
|
UpnpXmlContentError,
|
||||||
)
|
)
|
||||||
from async_upnp_client.profiles.dlna import DmrDevice
|
from async_upnp_client.profiles.dlna import DmrDevice
|
||||||
from async_upnp_client.utils import async_get_local_ip
|
from async_upnp_client.utils import async_get_local_ip
|
||||||
@ -270,11 +271,12 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
# NETWORK,NONE
|
# NETWORK,NONE
|
||||||
upnp_factory = UpnpFactory(upnp_requester, non_strict=True)
|
upnp_factory = UpnpFactory(upnp_requester, non_strict=True)
|
||||||
upnp_device: UpnpDevice | None = None
|
upnp_device: UpnpDevice | None = None
|
||||||
with contextlib.suppress(UpnpConnectionError, UpnpResponseError):
|
try:
|
||||||
upnp_device = await upnp_factory.async_create_device(
|
upnp_device = await upnp_factory.async_create_device(
|
||||||
self._ssdp_rendering_control_location
|
self._ssdp_rendering_control_location
|
||||||
)
|
)
|
||||||
if not upnp_device:
|
except (UpnpConnectionError, UpnpResponseError, UpnpXmlContentError) as err:
|
||||||
|
LOGGER.debug("Unable to create Upnp DMR device: %r", err, exc_info=True)
|
||||||
return
|
return
|
||||||
_, event_ip = await async_get_local_ip(
|
_, event_ip = await async_get_local_ip(
|
||||||
self._ssdp_rendering_control_location, self.hass.loop
|
self._ssdp_rendering_control_location, self.hass.loop
|
||||||
@ -307,8 +309,10 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
|
|
||||||
async def _async_resubscribe_dmr(self) -> None:
|
async def _async_resubscribe_dmr(self) -> None:
|
||||||
assert self._dmr_device
|
assert self._dmr_device
|
||||||
with contextlib.suppress(UpnpConnectionError):
|
try:
|
||||||
await self._dmr_device.async_subscribe_services(auto_resubscribe=True)
|
await self._dmr_device.async_subscribe_services(auto_resubscribe=True)
|
||||||
|
except UpnpCommunicationError as err:
|
||||||
|
LOGGER.debug("Device rejected re-subscription: %r", err, exc_info=True)
|
||||||
|
|
||||||
async def _async_shutdown_dmr(self) -> None:
|
async def _async_shutdown_dmr(self) -> None:
|
||||||
"""Handle removal."""
|
"""Handle removal."""
|
||||||
|
@ -812,7 +812,7 @@ class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||||
await self.device.update_status()
|
await self.device.update_status()
|
||||||
except OSError as err:
|
except (OSError, aioshelly.exceptions.RPCTimeout) as err:
|
||||||
raise update_coordinator.UpdateFailed("Device disconnected") from err
|
raise update_coordinator.UpdateFailed("Device disconnected") from err
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -119,6 +119,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
except HTTP_CONNECT_ERRORS:
|
except HTTP_CONNECT_ERRORS:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
except KeyError:
|
||||||
|
errors["base"] = "firmware_not_fully_provisioned"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
@ -160,6 +162,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except aioshelly.exceptions.JSONRPCError:
|
except aioshelly.exceptions.JSONRPCError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
except KeyError:
|
||||||
|
errors["base"] = "firmware_not_fully_provisioned"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
@ -219,6 +223,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.device_info = await validate_input(self.hass, self.host, self.info, {})
|
self.device_info = await validate_input(self.hass, self.host, self.info, {})
|
||||||
|
except KeyError:
|
||||||
|
LOGGER.debug("Shelly host %s firmware not fully provisioned", self.host)
|
||||||
except HTTP_CONNECT_ERRORS:
|
except HTTP_CONNECT_ERRORS:
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
|
|
||||||
@ -229,18 +235,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle discovery confirm."""
|
"""Handle discovery confirm."""
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
if user_input is not None:
|
try:
|
||||||
return self.async_create_entry(
|
if user_input is not None:
|
||||||
title=self.device_info["title"],
|
return self.async_create_entry(
|
||||||
data={
|
title=self.device_info["title"],
|
||||||
"host": self.host,
|
data={
|
||||||
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
|
"host": self.host,
|
||||||
"model": self.device_info["model"],
|
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
|
||||||
"gen": self.device_info["gen"],
|
"model": self.device_info["model"],
|
||||||
},
|
"gen": self.device_info["gen"],
|
||||||
)
|
},
|
||||||
|
)
|
||||||
self._set_confirm_only()
|
except KeyError:
|
||||||
|
errors["base"] = "firmware_not_fully_provisioned"
|
||||||
|
else:
|
||||||
|
self._set_confirm_only()
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="confirm_discovery",
|
step_id="confirm_discovery",
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Failed to connect",
|
"cannot_connect": "Failed to connect",
|
||||||
|
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support",
|
||||||
"invalid_auth": "Invalid authentication",
|
"invalid_auth": "Invalid authentication",
|
||||||
"unknown": "Unexpected error"
|
"unknown": "Unexpected error"
|
||||||
},
|
},
|
||||||
|
@ -297,6 +297,9 @@ def get_rpc_key_instances(keys_dict: dict[str, Any], key: str) -> list[str]:
|
|||||||
if key in keys_dict:
|
if key in keys_dict:
|
||||||
return [key]
|
return [key]
|
||||||
|
|
||||||
|
if key == "switch" and "cover:0" in keys_dict:
|
||||||
|
key = "cover"
|
||||||
|
|
||||||
keys_list: list[str] = []
|
keys_list: list[str] = []
|
||||||
for i in range(MAX_RPC_KEY_INSTANCES):
|
for i in range(MAX_RPC_KEY_INSTANCES):
|
||||||
key_inst = f"{key}:{i}"
|
key_inst = f"{key}:{i}"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "snmp",
|
"domain": "snmp",
|
||||||
"name": "SNMP",
|
"name": "SNMP",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/snmp",
|
"documentation": "https://www.home-assistant.io/integrations/snmp",
|
||||||
"requirements": ["pysnmplib==5.0.10"],
|
"requirements": ["pysnmp==4.4.12"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyasn1", "pysmi", "pysnmp"]
|
"loggers": ["pyasn1", "pysmi", "pysnmp"]
|
||||||
|
@ -28,6 +28,7 @@ from homeassistant.helpers.device_registry import (
|
|||||||
DeviceEntry,
|
DeviceEntry,
|
||||||
async_get_registry as get_dev_reg,
|
async_get_registry as get_dev_reg,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .common import SynoApi
|
from .common import SynoApi
|
||||||
@ -41,6 +42,7 @@ from .const import (
|
|||||||
EXCEPTION_DETAILS,
|
EXCEPTION_DETAILS,
|
||||||
EXCEPTION_UNKNOWN,
|
EXCEPTION_UNKNOWN,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
|
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||||
SYNO_API,
|
SYNO_API,
|
||||||
SYSTEM_LOADED,
|
SYSTEM_LOADED,
|
||||||
UNDO_UPDATE_LISTENER,
|
UNDO_UPDATE_LISTENER,
|
||||||
@ -128,6 +130,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
surveillance_station = api.surveillance_station
|
surveillance_station = api.surveillance_station
|
||||||
|
current_data: dict[str, SynoCamera] = {
|
||||||
|
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(30):
|
async with async_timeout.timeout(30):
|
||||||
@ -135,12 +140,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except SynologyDSMAPIErrorException as err:
|
except SynologyDSMAPIErrorException as err:
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
|
||||||
return {
|
new_data: dict[str, SynoCamera] = {
|
||||||
"cameras": {
|
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for cam_id, cam_data_new in new_data.items():
|
||||||
|
if (
|
||||||
|
(cam_data_current := current_data.get(cam_id)) is not None
|
||||||
|
and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
|
||||||
|
):
|
||||||
|
async_dispatcher_send(
|
||||||
|
hass,
|
||||||
|
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{entry.entry_id}_{cam_id}",
|
||||||
|
cam_data_new.live_view.rtsp,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"cameras": new_data}
|
||||||
|
|
||||||
async def async_coordinator_update_data_central() -> None:
|
async def async_coordinator_update_data_central() -> None:
|
||||||
"""Fetch all device and sensor data from api."""
|
"""Fetch all device and sensor data from api."""
|
||||||
try:
|
try:
|
||||||
|
@ -16,7 +16,8 @@ from homeassistant.components.camera import (
|
|||||||
CameraEntityFeature,
|
CameraEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
@ -27,6 +28,7 @@ from .const import (
|
|||||||
COORDINATOR_CAMERAS,
|
COORDINATOR_CAMERAS,
|
||||||
DEFAULT_SNAPSHOT_QUALITY,
|
DEFAULT_SNAPSHOT_QUALITY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||||
SYNO_API,
|
SYNO_API,
|
||||||
)
|
)
|
||||||
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
||||||
@ -130,6 +132,29 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
|||||||
"""Return the camera motion detection status."""
|
"""Return the camera motion detection status."""
|
||||||
return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return]
|
return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
def _listen_source_updates(self) -> None:
|
||||||
|
"""Listen for camera source changed events."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_signal(url: str) -> None:
|
||||||
|
if self.stream:
|
||||||
|
_LOGGER.debug("Update stream URL for camera %s", self.camera_data.name)
|
||||||
|
self.stream.update_source(url)
|
||||||
|
|
||||||
|
assert self.platform
|
||||||
|
assert self.platform.config_entry
|
||||||
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.platform.config_entry.entry_id}_{self.camera_data.id}",
|
||||||
|
_handle_signal,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Subscribe to signal."""
|
||||||
|
self._listen_source_updates()
|
||||||
|
|
||||||
def camera_image(
|
def camera_image(
|
||||||
self, width: int | None = None, height: int | None = None
|
self, width: int | None = None, height: int | None = None
|
||||||
) -> bytes | None:
|
) -> bytes | None:
|
||||||
@ -162,6 +187,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
|||||||
)
|
)
|
||||||
if not self.available:
|
if not self.available:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
|
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
|
||||||
|
|
||||||
def enable_motion_detection(self) -> None:
|
def enable_motion_detection(self) -> None:
|
||||||
|
@ -43,6 +43,9 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
|
|||||||
|
|
||||||
ENTITY_UNIT_LOAD = "load"
|
ENTITY_UNIT_LOAD = "load"
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed"
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
SERVICE_REBOOT = "reboot"
|
SERVICE_REBOOT = "reboot"
|
||||||
SERVICE_SHUTDOWN = "shutdown"
|
SERVICE_SHUTDOWN = "shutdown"
|
||||||
|
@ -143,7 +143,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
user_input[CONF_VERIFY_SSL] = False
|
user_input[CONF_VERIFY_SSL] = False
|
||||||
nvr_data, errors = await self._async_get_nvr_data(user_input)
|
nvr_data, errors = await self._async_get_nvr_data(user_input)
|
||||||
if nvr_data and not errors:
|
if nvr_data and not errors:
|
||||||
return self._async_create_entry(nvr_data.name, user_input)
|
return self._async_create_entry(
|
||||||
|
nvr_data.name or nvr_data.type, user_input
|
||||||
|
)
|
||||||
|
|
||||||
placeholders = {
|
placeholders = {
|
||||||
"name": discovery_info["hostname"]
|
"name": discovery_info["hostname"]
|
||||||
@ -289,7 +291,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
await self.async_set_unique_id(nvr_data.mac)
|
await self.async_set_unique_id(nvr_data.mac)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self._async_create_entry(nvr_data.name, user_input)
|
return self._async_create_entry(
|
||||||
|
nvr_data.name or nvr_data.type, user_input
|
||||||
|
)
|
||||||
|
|
||||||
user_input = user_input or {}
|
user_input = user_input or {}
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "UniFi Protect",
|
"name": "UniFi Protect",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
||||||
"requirements": ["pyunifiprotect==3.4.1", "unifi-discovery==1.1.2"],
|
"requirements": ["pyunifiprotect==3.5.1", "unifi-discovery==1.1.2"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
|
@ -140,7 +140,7 @@ def _get_doorbell_options(api: ProtectApiClient) -> list[dict[str, Any]]:
|
|||||||
def _get_paired_camera_options(api: ProtectApiClient) -> list[dict[str, Any]]:
|
def _get_paired_camera_options(api: ProtectApiClient) -> list[dict[str, Any]]:
|
||||||
options = [{"id": TYPE_EMPTY_VALUE, "name": "Not Paired"}]
|
options = [{"id": TYPE_EMPTY_VALUE, "name": "Not Paired"}]
|
||||||
for camera in api.bootstrap.cameras.values():
|
for camera in api.bootstrap.cameras.values():
|
||||||
options.append({"id": camera.id, "name": camera.name})
|
options.append({"id": camera.id, "name": camera.name or camera.type})
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
@ -159,6 +159,15 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ufp_value="is_face_detection_on",
|
ufp_value="is_face_detection_on",
|
||||||
ufp_set_method="set_face_detection",
|
ufp_set_method="set_face_detection",
|
||||||
),
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="smart_package",
|
||||||
|
name="Detections: Package",
|
||||||
|
icon="mdi:package-variant-closed",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_required_field="can_detect_package",
|
||||||
|
ufp_value="is_package_detection_on",
|
||||||
|
ufp_set_method="set_package_detection",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
||||||
|
@ -171,8 +171,8 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
|||||||
if hasattr(self.smartfan, "night_light"):
|
if hasattr(self.smartfan, "night_light"):
|
||||||
attr["night_light"] = self.smartfan.night_light
|
attr["night_light"] = self.smartfan.night_light
|
||||||
|
|
||||||
if hasattr(self.smartfan, "air_quality"):
|
if self.smartfan.details.get("air_quality_value") is not None:
|
||||||
attr["air_quality"] = self.smartfan.air_quality
|
attr["air_quality"] = self.smartfan.details["air_quality_value"]
|
||||||
|
|
||||||
if hasattr(self.smartfan, "mode"):
|
if hasattr(self.smartfan, "mode"):
|
||||||
attr["mode"] = self.smartfan.mode
|
attr["mode"] = self.smartfan.mode
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "VeSync",
|
"name": "VeSync",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/vesync",
|
"documentation": "https://www.home-assistant.io/integrations/vesync",
|
||||||
"codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"],
|
"codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"],
|
||||||
"requirements": ["pyvesync==2.0.2"],
|
"requirements": ["pyvesync==2.0.3"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyvesync"]
|
"loggers": ["pyvesync"]
|
||||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 5
|
MINOR_VERSION: Final = 5
|
||||||
PATCH_VERSION: Final = "4"
|
PATCH_VERSION: Final = "5"
|
||||||
__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, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@ -436,7 +436,7 @@ bravia-tv==1.0.11
|
|||||||
broadlink==0.18.1
|
broadlink==0.18.1
|
||||||
|
|
||||||
# homeassistant.components.brother
|
# homeassistant.components.brother
|
||||||
brother==1.2.0
|
brother==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.brottsplatskartan
|
# homeassistant.components.brottsplatskartan
|
||||||
brottsplatskartan==0.0.1
|
brottsplatskartan==0.0.1
|
||||||
@ -1829,7 +1829,7 @@ pysmarty==0.8
|
|||||||
pysml==0.0.7
|
pysml==0.0.7
|
||||||
|
|
||||||
# homeassistant.components.snmp
|
# homeassistant.components.snmp
|
||||||
pysnmplib==5.0.10
|
pysnmp==4.4.12
|
||||||
|
|
||||||
# homeassistant.components.soma
|
# homeassistant.components.soma
|
||||||
pysoma==0.0.10
|
pysoma==0.0.10
|
||||||
@ -1981,7 +1981,7 @@ pytrafikverket==0.1.6.2
|
|||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==3.4.1
|
pyunifiprotect==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -1996,7 +1996,7 @@ pyvera==0.3.13
|
|||||||
pyversasense==0.0.6
|
pyversasense==0.0.6
|
||||||
|
|
||||||
# homeassistant.components.vesync
|
# homeassistant.components.vesync
|
||||||
pyvesync==2.0.2
|
pyvesync==2.0.3
|
||||||
|
|
||||||
# homeassistant.components.vizio
|
# homeassistant.components.vizio
|
||||||
pyvizio==0.1.57
|
pyvizio==0.1.57
|
||||||
|
@ -327,7 +327,7 @@ bravia-tv==1.0.11
|
|||||||
broadlink==0.18.1
|
broadlink==0.18.1
|
||||||
|
|
||||||
# homeassistant.components.brother
|
# homeassistant.components.brother
|
||||||
brother==1.2.0
|
brother==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.brunt
|
# homeassistant.components.brunt
|
||||||
brunt==1.2.0
|
brunt==1.2.0
|
||||||
@ -1304,7 +1304,7 @@ pytrafikverket==0.1.6.2
|
|||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==3.4.1
|
pyunifiprotect==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -1313,7 +1313,7 @@ pyuptimerobot==22.2.0
|
|||||||
pyvera==0.3.13
|
pyvera==0.3.13
|
||||||
|
|
||||||
# homeassistant.components.vesync
|
# homeassistant.components.vesync
|
||||||
pyvesync==2.0.2
|
pyvesync==2.0.3
|
||||||
|
|
||||||
# homeassistant.components.vizio
|
# homeassistant.components.vizio
|
||||||
pyvizio==0.1.57
|
pyvizio==0.1.57
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = homeassistant
|
name = homeassistant
|
||||||
version = 2022.5.4
|
version = 2022.5.5
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@ -438,7 +438,7 @@ async def test_measure(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "83.3"
|
assert hass.states.get("sensor.sensor4").state == "83.3"
|
||||||
|
|
||||||
|
|
||||||
@ -519,7 +519,7 @@ async def test_async_on_entire_period(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.on_sensor1").state == "1.0"
|
assert hass.states.get("sensor.on_sensor1").state == "1.0"
|
||||||
assert hass.states.get("sensor.on_sensor2").state == "1.0"
|
assert hass.states.get("sensor.on_sensor2").state == "1.0"
|
||||||
assert hass.states.get("sensor.on_sensor3").state == "0"
|
assert hass.states.get("sensor.on_sensor3").state == "1"
|
||||||
assert hass.states.get("sensor.on_sensor4").state == "100.0"
|
assert hass.states.get("sensor.on_sensor4").state == "100.0"
|
||||||
|
|
||||||
|
|
||||||
@ -886,7 +886,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.0"
|
assert hass.states.get("sensor.sensor1").state == "0.0"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.0"
|
assert hass.states.get("sensor.sensor2").state == "0.0"
|
||||||
assert hass.states.get("sensor.sensor3").state == "0"
|
assert hass.states.get("sensor.sensor3").state == "1"
|
||||||
assert hass.states.get("sensor.sensor4").state == "0.0"
|
assert hass.states.get("sensor.sensor4").state == "0.0"
|
||||||
|
|
||||||
one_hour_in = start_time + timedelta(minutes=60)
|
one_hour_in = start_time + timedelta(minutes=60)
|
||||||
@ -896,7 +896,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "1.0"
|
assert hass.states.get("sensor.sensor1").state == "1.0"
|
||||||
assert hass.states.get("sensor.sensor2").state == "1.0"
|
assert hass.states.get("sensor.sensor2").state == "1.0"
|
||||||
assert hass.states.get("sensor.sensor3").state == "0"
|
assert hass.states.get("sensor.sensor3").state == "1"
|
||||||
assert hass.states.get("sensor.sensor4").state == "50.0"
|
assert hass.states.get("sensor.sensor4").state == "50.0"
|
||||||
|
|
||||||
turn_off_time = start_time + timedelta(minutes=90)
|
turn_off_time = start_time + timedelta(minutes=90)
|
||||||
@ -908,7 +908,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "1.5"
|
assert hass.states.get("sensor.sensor1").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor2").state == "1.5"
|
assert hass.states.get("sensor.sensor2").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor3").state == "0"
|
assert hass.states.get("sensor.sensor3").state == "1"
|
||||||
assert hass.states.get("sensor.sensor4").state == "75.0"
|
assert hass.states.get("sensor.sensor4").state == "75.0"
|
||||||
|
|
||||||
turn_back_on_time = start_time + timedelta(minutes=105)
|
turn_back_on_time = start_time + timedelta(minutes=105)
|
||||||
@ -918,7 +918,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "1.5"
|
assert hass.states.get("sensor.sensor1").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor2").state == "1.5"
|
assert hass.states.get("sensor.sensor2").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor3").state == "0"
|
assert hass.states.get("sensor.sensor3").state == "1"
|
||||||
assert hass.states.get("sensor.sensor4").state == "75.0"
|
assert hass.states.get("sensor.sensor4").state == "75.0"
|
||||||
|
|
||||||
with freeze_time(turn_back_on_time):
|
with freeze_time(turn_back_on_time):
|
||||||
@ -927,7 +927,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "1.5"
|
assert hass.states.get("sensor.sensor1").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor2").state == "1.5"
|
assert hass.states.get("sensor.sensor2").state == "1.5"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "75.0"
|
assert hass.states.get("sensor.sensor4").state == "75.0"
|
||||||
|
|
||||||
end_time = start_time + timedelta(minutes=120)
|
end_time = start_time + timedelta(minutes=120)
|
||||||
@ -937,7 +937,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "1.75"
|
assert hass.states.get("sensor.sensor1").state == "1.75"
|
||||||
assert hass.states.get("sensor.sensor2").state == "1.75"
|
assert hass.states.get("sensor.sensor2").state == "1.75"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "87.5"
|
assert hass.states.get("sensor.sensor4").state == "87.5"
|
||||||
|
|
||||||
|
|
||||||
@ -1198,7 +1198,7 @@ async def test_measure_sliding_window(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "41.7"
|
assert hass.states.get("sensor.sensor4").state == "41.7"
|
||||||
|
|
||||||
past_next_update = start_time + timedelta(minutes=30)
|
past_next_update = start_time + timedelta(minutes=30)
|
||||||
@ -1211,7 +1211,7 @@ async def test_measure_sliding_window(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "41.7"
|
assert hass.states.get("sensor.sensor4").state == "41.7"
|
||||||
|
|
||||||
|
|
||||||
@ -1291,7 +1291,7 @@ async def test_measure_from_end_going_backwards(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "83.3"
|
assert hass.states.get("sensor.sensor4").state == "83.3"
|
||||||
|
|
||||||
past_next_update = start_time + timedelta(minutes=30)
|
past_next_update = start_time + timedelta(minutes=30)
|
||||||
@ -1304,7 +1304,7 @@ async def test_measure_from_end_going_backwards(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "83.3"
|
assert hass.states.get("sensor.sensor4").state == "83.3"
|
||||||
|
|
||||||
|
|
||||||
@ -1385,7 +1385,7 @@ async def test_measure_cet(hass, recorder_mock):
|
|||||||
|
|
||||||
assert hass.states.get("sensor.sensor1").state == "0.83"
|
assert hass.states.get("sensor.sensor1").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor2").state == "0.83"
|
assert hass.states.get("sensor.sensor2").state == "0.83"
|
||||||
assert hass.states.get("sensor.sensor3").state == "1"
|
assert hass.states.get("sensor.sensor3").state == "2"
|
||||||
assert hass.states.get("sensor.sensor4").state == "83.3"
|
assert hass.states.get("sensor.sensor4").state == "83.3"
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ from unittest.mock import DEFAULT as DEFAULT_MOCK, AsyncMock, Mock, call, patch
|
|||||||
|
|
||||||
from async_upnp_client.exceptions import (
|
from async_upnp_client.exceptions import (
|
||||||
UpnpActionResponseError,
|
UpnpActionResponseError,
|
||||||
|
UpnpCommunicationError,
|
||||||
|
UpnpConnectionError,
|
||||||
UpnpError,
|
UpnpError,
|
||||||
UpnpResponseError,
|
UpnpResponseError,
|
||||||
)
|
)
|
||||||
@ -1368,6 +1370,7 @@ async def test_upnp_not_available(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test for volume control when Upnp is not available."""
|
"""Test for volume control when Upnp is not available."""
|
||||||
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
||||||
|
assert "Unable to create Upnp DMR device" in caplog.text
|
||||||
|
|
||||||
# Upnp action fails
|
# Upnp action fails
|
||||||
assert await hass.services.async_call(
|
assert await hass.services.async_call(
|
||||||
@ -1385,6 +1388,7 @@ async def test_upnp_missing_service(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test for volume control when Upnp is not available."""
|
"""Test for volume control when Upnp is not available."""
|
||||||
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
||||||
|
assert "Unable to create Upnp DMR device" in caplog.text
|
||||||
|
|
||||||
# Upnp action fails
|
# Upnp action fails
|
||||||
assert await hass.services.async_call(
|
assert await hass.services.async_call(
|
||||||
@ -1505,3 +1509,49 @@ async def test_upnp_re_subscribe_events(
|
|||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert dmr_device.async_subscribe_services.call_count == 2
|
assert dmr_device.async_subscribe_services.call_count == 2
|
||||||
assert dmr_device.async_unsubscribe_services.call_count == 1
|
assert dmr_device.async_unsubscribe_services.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("rest_api", "upnp_notify_server")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error",
|
||||||
|
{UpnpConnectionError(), UpnpCommunicationError(), UpnpResponseError(status=400)},
|
||||||
|
)
|
||||||
|
async def test_upnp_failed_re_subscribe_events(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
remotews: Mock,
|
||||||
|
dmr_device: Mock,
|
||||||
|
mock_now: datetime,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
error: Exception,
|
||||||
|
) -> None:
|
||||||
|
"""Test for Upnp event feedback."""
|
||||||
|
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert dmr_device.async_subscribe_services.call_count == 1
|
||||||
|
assert dmr_device.async_unsubscribe_services.call_count == 0
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
remotews, "start_listening", side_effect=WebSocketException("Boom")
|
||||||
|
), patch.object(remotews, "is_alive", return_value=False):
|
||||||
|
next_update = mock_now + timedelta(minutes=5)
|
||||||
|
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
|
||||||
|
async_fire_time_changed(hass, next_update)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert dmr_device.async_subscribe_services.call_count == 1
|
||||||
|
assert dmr_device.async_unsubscribe_services.call_count == 1
|
||||||
|
|
||||||
|
next_update = mock_now + timedelta(minutes=10)
|
||||||
|
with patch("homeassistant.util.dt.utcnow", return_value=next_update), patch.object(
|
||||||
|
dmr_device, "async_subscribe_services", side_effect=error
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, next_update)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert "Device rejected re-subscription" in caplog.text
|
||||||
|
@ -225,6 +225,29 @@ async def test_form_errors_get_info(hass, error):
|
|||||||
assert result2["errors"] == {"base": base_error}
|
assert result2["errors"] == {"base": base_error}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("error", [(KeyError, "firmware_not_fully_provisioned")])
|
||||||
|
async def test_form_missing_key_get_info(hass, error):
|
||||||
|
"""Test we handle missing key."""
|
||||||
|
exc, base_error = error
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"aioshelly.common.get_info",
|
||||||
|
return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False, "gen": "2"},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.shelly.config_flow.validate_input",
|
||||||
|
side_effect=KeyError,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"host": "1.1.1.1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": base_error}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
|
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
|
||||||
)
|
)
|
||||||
|
@ -26,9 +26,13 @@ from .conftest import (
|
|||||||
ids_from_device_description,
|
ids_from_device_description,
|
||||||
)
|
)
|
||||||
|
|
||||||
CAMERA_SWITCHES_NO_FACE = [d for d in CAMERA_SWITCHES if d.name != "Detections: Face"]
|
CAMERA_SWITCHES_BASIC = [
|
||||||
|
d
|
||||||
|
for d in CAMERA_SWITCHES
|
||||||
|
if d.name != "Detections: Face" and d.name != "Detections: Package"
|
||||||
|
]
|
||||||
CAMERA_SWITCHES_NO_EXTRA = [
|
CAMERA_SWITCHES_NO_EXTRA = [
|
||||||
d for d in CAMERA_SWITCHES_NO_FACE if d.name not in ("High FPS", "Privacy Mode")
|
d for d in CAMERA_SWITCHES_BASIC if d.name not in ("High FPS", "Privacy Mode")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -253,7 +257,7 @@ async def test_switch_setup_camera_all(
|
|||||||
|
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
for description in CAMERA_SWITCHES_NO_FACE:
|
for description in CAMERA_SWITCHES_BASIC:
|
||||||
unique_id, entity_id = ids_from_device_description(
|
unique_id, entity_id = ids_from_device_description(
|
||||||
Platform.SWITCH, camera, description
|
Platform.SWITCH, camera, description
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user