commit
204762d23a
|
@ -3,7 +3,7 @@
|
|||
"name": "Brother Printer",
|
||||
"documentation": "https://www.home-assistant.io/integrations/brother",
|
||||
"codeowners": ["@bieniu"],
|
||||
"requirements": ["brother==1.2.0"],
|
||||
"requirements": ["brother==1.1.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_printer._tcp.local.",
|
||||
|
|
|
@ -23,6 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from . import FIBARO_DEVICES, FibaroDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
PARALLEL_UPDATES = 2
|
||||
|
||||
|
||||
def scaleto255(value: int | None) -> int:
|
||||
"""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]:
|
||||
"""Fetch file information."""
|
||||
try:
|
||||
statinfo = os.stat(self._path)
|
||||
statinfo = await self.hass.async_add_executor_job(os.stat, self._path)
|
||||
except OSError as 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."""
|
||||
|
||||
hours_matched: float | None
|
||||
changes_to_match_state: int | None
|
||||
match_count: int | None
|
||||
period: tuple[datetime.datetime, datetime.datetime]
|
||||
|
||||
|
||||
|
@ -121,14 +121,12 @@ class HistoryStats:
|
|||
self._state = HistoryStatsState(None, None, self._period)
|
||||
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,
|
||||
current_period_start_timestamp,
|
||||
current_period_end_timestamp,
|
||||
)
|
||||
self._state = HistoryStatsState(
|
||||
hours_matched, changes_to_match_state, self._period
|
||||
)
|
||||
self._state = HistoryStatsState(hours_matched, match_count, self._period)
|
||||
return self._state
|
||||
|
||||
def _update_from_database(
|
||||
|
@ -156,7 +154,7 @@ class HistoryStats:
|
|||
)
|
||||
last_state_change_timestamp = start_timestamp
|
||||
elapsed = 0.0
|
||||
changes_to_match_state = 0
|
||||
match_count = 1 if previous_state_matches else 0
|
||||
|
||||
# Make calculations
|
||||
for item in self._history_current_period:
|
||||
|
@ -166,7 +164,7 @@ class HistoryStats:
|
|||
if previous_state_matches:
|
||||
elapsed += state_change_timestamp - last_state_change_timestamp
|
||||
elif current_state_matches:
|
||||
changes_to_match_state += 1
|
||||
match_count += 1
|
||||
|
||||
previous_state_matches = current_state_matches
|
||||
last_state_change_timestamp = state_change_timestamp
|
||||
|
@ -178,4 +176,4 @@ class HistoryStats:
|
|||
|
||||
# Save value in hours
|
||||
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:
|
||||
self._attr_native_value = pretty_ratio(state.hours_matched, state.period)
|
||||
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:
|
||||
_LOGGER.error("Failed to connect to %s", bulb.ip_addr)
|
||||
bulb.registered = False
|
||||
if bulb.mac_addr in self.discoveries_inflight:
|
||||
self.discoveries_inflight.pop(bulb.mac_addr)
|
||||
else:
|
||||
bulb.timeout = MESSAGE_TIMEOUT
|
||||
bulb.retry_count = MESSAGE_RETRIES
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine, Sequence
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
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.exceptions import (
|
||||
UpnpActionResponseError,
|
||||
UpnpCommunicationError,
|
||||
UpnpConnectionError,
|
||||
UpnpError,
|
||||
UpnpResponseError,
|
||||
UpnpXmlContentError,
|
||||
)
|
||||
from async_upnp_client.profiles.dlna import DmrDevice
|
||||
from async_upnp_client.utils import async_get_local_ip
|
||||
|
@ -270,11 +271,12 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||
# NETWORK,NONE
|
||||
upnp_factory = UpnpFactory(upnp_requester, non_strict=True)
|
||||
upnp_device: UpnpDevice | None = None
|
||||
with contextlib.suppress(UpnpConnectionError, UpnpResponseError):
|
||||
try:
|
||||
upnp_device = await upnp_factory.async_create_device(
|
||||
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
|
||||
_, event_ip = await async_get_local_ip(
|
||||
self._ssdp_rendering_control_location, self.hass.loop
|
||||
|
@ -307,8 +309,10 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||
|
||||
async def _async_resubscribe_dmr(self) -> None:
|
||||
assert self._dmr_device
|
||||
with contextlib.suppress(UpnpConnectionError):
|
||||
try:
|
||||
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:
|
||||
"""Handle removal."""
|
||||
|
|
|
@ -812,7 +812,7 @@ class RpcPollingWrapper(update_coordinator.DataUpdateCoordinator):
|
|||
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
||||
await self.device.update_status()
|
||||
except OSError as err:
|
||||
except (OSError, aioshelly.exceptions.RPCTimeout) as err:
|
||||
raise update_coordinator.UpdateFailed("Device disconnected") from err
|
||||
|
||||
@property
|
||||
|
|
|
@ -119,6 +119,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
except HTTP_CONNECT_ERRORS:
|
||||
errors["base"] = "cannot_connect"
|
||||
except KeyError:
|
||||
errors["base"] = "firmware_not_fully_provisioned"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
@ -160,6 +162,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors["base"] = "cannot_connect"
|
||||
except aioshelly.exceptions.JSONRPCError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except KeyError:
|
||||
errors["base"] = "firmware_not_fully_provisioned"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
@ -219,6 +223,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
try:
|
||||
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:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
|
@ -229,18 +235,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
) -> FlowResult:
|
||||
"""Handle discovery confirm."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=self.device_info["title"],
|
||||
data={
|
||||
"host": self.host,
|
||||
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
|
||||
"model": self.device_info["model"],
|
||||
"gen": self.device_info["gen"],
|
||||
},
|
||||
)
|
||||
|
||||
self._set_confirm_only()
|
||||
try:
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=self.device_info["title"],
|
||||
data={
|
||||
"host": self.host,
|
||||
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
|
||||
"model": self.device_info["model"],
|
||||
"gen": self.device_info["gen"],
|
||||
},
|
||||
)
|
||||
except KeyError:
|
||||
errors["base"] = "firmware_not_fully_provisioned"
|
||||
else:
|
||||
self._set_confirm_only()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm_discovery",
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"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": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"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:
|
||||
return [key]
|
||||
|
||||
if key == "switch" and "cover:0" in keys_dict:
|
||||
key = "cover"
|
||||
|
||||
keys_list: list[str] = []
|
||||
for i in range(MAX_RPC_KEY_INSTANCES):
|
||||
key_inst = f"{key}:{i}"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "snmp",
|
||||
"name": "SNMP",
|
||||
"documentation": "https://www.home-assistant.io/integrations/snmp",
|
||||
"requirements": ["pysnmplib==5.0.10"],
|
||||
"requirements": ["pysnmp==4.4.12"],
|
||||
"codeowners": [],
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyasn1", "pysmi", "pysnmp"]
|
||||
|
|
|
@ -28,6 +28,7 @@ from homeassistant.helpers.device_registry import (
|
|||
DeviceEntry,
|
||||
async_get_registry as get_dev_reg,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .common import SynoApi
|
||||
|
@ -41,6 +42,7 @@ from .const import (
|
|||
EXCEPTION_DETAILS,
|
||||
EXCEPTION_UNKNOWN,
|
||||
PLATFORMS,
|
||||
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||
SYNO_API,
|
||||
SYSTEM_LOADED,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
|
@ -128,6 +130,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
return None
|
||||
|
||||
surveillance_station = api.surveillance_station
|
||||
current_data: dict[str, SynoCamera] = {
|
||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||
}
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(30):
|
||||
|
@ -135,12 +140,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
except SynologyDSMAPIErrorException as err:
|
||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||
|
||||
return {
|
||||
"cameras": {
|
||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||
}
|
||||
new_data: dict[str, SynoCamera] = {
|
||||
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:
|
||||
"""Fetch all device and sensor data from api."""
|
||||
try:
|
||||
|
|
|
@ -16,7 +16,8 @@ from homeassistant.components.camera import (
|
|||
CameraEntityFeature,
|
||||
)
|
||||
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_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
@ -27,6 +28,7 @@ from .const import (
|
|||
COORDINATOR_CAMERAS,
|
||||
DEFAULT_SNAPSHOT_QUALITY,
|
||||
DOMAIN,
|
||||
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||
SYNO_API,
|
||||
)
|
||||
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
||||
|
@ -130,6 +132,29 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
|||
"""Return the camera motion detection status."""
|
||||
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(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
|
@ -162,6 +187,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera):
|
|||
)
|
||||
if not self.available:
|
||||
return None
|
||||
|
||||
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
|
||||
|
||||
def enable_motion_detection(self) -> None:
|
||||
|
|
|
@ -43,6 +43,9 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
|
|||
|
||||
ENTITY_UNIT_LOAD = "load"
|
||||
|
||||
# Signals
|
||||
SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed"
|
||||
|
||||
# Services
|
||||
SERVICE_REBOOT = "reboot"
|
||||
SERVICE_SHUTDOWN = "shutdown"
|
||||
|
|
|
@ -143,7 +143,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
user_input[CONF_VERIFY_SSL] = False
|
||||
nvr_data, errors = await self._async_get_nvr_data(user_input)
|
||||
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 = {
|
||||
"name": discovery_info["hostname"]
|
||||
|
@ -289,7 +291,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
await self.async_set_unique_id(nvr_data.mac)
|
||||
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 {}
|
||||
return self.async_show_form(
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "UniFi Protect",
|
||||
"config_flow": true,
|
||||
"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"],
|
||||
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
||||
"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]]:
|
||||
options = [{"id": TYPE_EMPTY_VALUE, "name": "Not Paired"}]
|
||||
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
|
||||
|
||||
|
|
|
@ -159,6 +159,15 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||
ufp_value="is_face_detection_on",
|
||||
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, ...] = (
|
||||
|
|
|
@ -171,8 +171,8 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
|||
if hasattr(self.smartfan, "night_light"):
|
||||
attr["night_light"] = self.smartfan.night_light
|
||||
|
||||
if hasattr(self.smartfan, "air_quality"):
|
||||
attr["air_quality"] = self.smartfan.air_quality
|
||||
if self.smartfan.details.get("air_quality_value") is not None:
|
||||
attr["air_quality"] = self.smartfan.details["air_quality_value"]
|
||||
|
||||
if hasattr(self.smartfan, "mode"):
|
||||
attr["mode"] = self.smartfan.mode
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "VeSync",
|
||||
"documentation": "https://www.home-assistant.io/integrations/vesync",
|
||||
"codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"],
|
||||
"requirements": ["pyvesync==2.0.2"],
|
||||
"requirements": ["pyvesync==2.0.3"],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyvesync"]
|
||||
|
|
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||
|
||||
MAJOR_VERSION: Final = 2022
|
||||
MINOR_VERSION: Final = 5
|
||||
PATCH_VERSION: Final = "4"
|
||||
PATCH_VERSION: Final = "5"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||
|
|
|
@ -436,7 +436,7 @@ bravia-tv==1.0.11
|
|||
broadlink==0.18.1
|
||||
|
||||
# homeassistant.components.brother
|
||||
brother==1.2.0
|
||||
brother==1.1.0
|
||||
|
||||
# homeassistant.components.brottsplatskartan
|
||||
brottsplatskartan==0.0.1
|
||||
|
@ -1829,7 +1829,7 @@ pysmarty==0.8
|
|||
pysml==0.0.7
|
||||
|
||||
# homeassistant.components.snmp
|
||||
pysnmplib==5.0.10
|
||||
pysnmp==4.4.12
|
||||
|
||||
# homeassistant.components.soma
|
||||
pysoma==0.0.10
|
||||
|
@ -1981,7 +1981,7 @@ pytrafikverket==0.1.6.2
|
|||
pyudev==0.22.0
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
pyunifiprotect==3.4.1
|
||||
pyunifiprotect==3.5.1
|
||||
|
||||
# homeassistant.components.uptimerobot
|
||||
pyuptimerobot==22.2.0
|
||||
|
@ -1996,7 +1996,7 @@ pyvera==0.3.13
|
|||
pyversasense==0.0.6
|
||||
|
||||
# homeassistant.components.vesync
|
||||
pyvesync==2.0.2
|
||||
pyvesync==2.0.3
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.57
|
||||
|
|
|
@ -327,7 +327,7 @@ bravia-tv==1.0.11
|
|||
broadlink==0.18.1
|
||||
|
||||
# homeassistant.components.brother
|
||||
brother==1.2.0
|
||||
brother==1.1.0
|
||||
|
||||
# homeassistant.components.brunt
|
||||
brunt==1.2.0
|
||||
|
@ -1304,7 +1304,7 @@ pytrafikverket==0.1.6.2
|
|||
pyudev==0.22.0
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
pyunifiprotect==3.4.1
|
||||
pyunifiprotect==3.5.1
|
||||
|
||||
# homeassistant.components.uptimerobot
|
||||
pyuptimerobot==22.2.0
|
||||
|
@ -1313,7 +1313,7 @@ pyuptimerobot==22.2.0
|
|||
pyvera==0.3.13
|
||||
|
||||
# homeassistant.components.vesync
|
||||
pyvesync==2.0.2
|
||||
pyvesync==2.0.3
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.57
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = homeassistant
|
||||
version = 2022.5.4
|
||||
version = 2022.5.5
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
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.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"
|
||||
|
||||
|
||||
|
@ -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_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"
|
||||
|
||||
|
||||
|
@ -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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
|
||||
|
@ -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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
|
||||
|
@ -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.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"
|
||||
|
||||
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.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"
|
||||
|
||||
|
||||
|
@ -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.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"
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ from unittest.mock import DEFAULT as DEFAULT_MOCK, AsyncMock, Mock, call, patch
|
|||
|
||||
from async_upnp_client.exceptions import (
|
||||
UpnpActionResponseError,
|
||||
UpnpCommunicationError,
|
||||
UpnpConnectionError,
|
||||
UpnpError,
|
||||
UpnpResponseError,
|
||||
)
|
||||
|
@ -1368,6 +1370,7 @@ async def test_upnp_not_available(
|
|||
) -> None:
|
||||
"""Test for volume control when Upnp is not available."""
|
||||
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
||||
assert "Unable to create Upnp DMR device" in caplog.text
|
||||
|
||||
# Upnp action fails
|
||||
assert await hass.services.async_call(
|
||||
|
@ -1385,6 +1388,7 @@ async def test_upnp_missing_service(
|
|||
) -> None:
|
||||
"""Test for volume control when Upnp is not available."""
|
||||
await setup_samsungtv_entry(hass, MOCK_ENTRY_WS)
|
||||
assert "Unable to create Upnp DMR device" in caplog.text
|
||||
|
||||
# Upnp action fails
|
||||
assert await hass.services.async_call(
|
||||
|
@ -1505,3 +1509,49 @@ async def test_upnp_re_subscribe_events(
|
|||
assert state.state == STATE_ON
|
||||
assert dmr_device.async_subscribe_services.call_count == 2
|
||||
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}
|
||||
|
||||
|
||||
@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(
|
||||
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
|
||||
)
|
||||
|
|
|
@ -26,9 +26,13 @@ from .conftest import (
|
|||
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 = [
|
||||
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)
|
||||
|
||||
for description in CAMERA_SWITCHES_NO_FACE:
|
||||
for description in CAMERA_SWITCHES_BASIC:
|
||||
unique_id, entity_id = ids_from_device_description(
|
||||
Platform.SWITCH, camera, description
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue