mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 16:57:19 +00:00
Merge pull request #61625 from home-assistant/rc
This commit is contained in:
commit
5df747276f
@ -2,7 +2,9 @@
|
||||
"domain": "frontend",
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": ["home-assistant-frontend==20211211.0"],
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20211212.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
"auth",
|
||||
@ -15,6 +17,8 @@
|
||||
"system_log",
|
||||
"websocket_api"
|
||||
],
|
||||
"codeowners": ["@home-assistant/frontend"],
|
||||
"codeowners": [
|
||||
"@home-assistant/frontend"
|
||||
],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
}
|
@ -249,14 +249,17 @@ class OpeningDeviceBase(HomeAccessory):
|
||||
def async_update_state(self, new_state):
|
||||
"""Update cover position and tilt after state changed."""
|
||||
# update tilt
|
||||
if not self._supports_tilt:
|
||||
return
|
||||
current_tilt = new_state.attributes.get(ATTR_CURRENT_TILT_POSITION)
|
||||
if isinstance(current_tilt, (float, int)):
|
||||
# HomeKit sends values between -90 and 90.
|
||||
# We'll have to normalize to [0,100]
|
||||
current_tilt = (current_tilt / 100.0 * 180.0) - 90.0
|
||||
current_tilt = int(current_tilt)
|
||||
self.char_current_tilt.set_value(current_tilt)
|
||||
self.char_target_tilt.set_value(current_tilt)
|
||||
if not isinstance(current_tilt, (float, int)):
|
||||
return
|
||||
# HomeKit sends values between -90 and 90.
|
||||
# We'll have to normalize to [0,100]
|
||||
current_tilt = (current_tilt / 100.0 * 180.0) - 90.0
|
||||
current_tilt = int(current_tilt)
|
||||
self.char_current_tilt.set_value(current_tilt)
|
||||
self.char_target_tilt.set_value(current_tilt)
|
||||
|
||||
|
||||
class OpeningDevice(OpeningDeviceBase, HomeAccessory):
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Philips Hue",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hue",
|
||||
"requirements": ["aiohue==3.0.2"],
|
||||
"requirements": ["aiohue==3.0.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
|
@ -96,8 +96,8 @@ class HueSceneEntity(HueBaseEntity, SceneEntity):
|
||||
"""Activate Hue scene."""
|
||||
transition = kwargs.get("transition")
|
||||
if transition is not None:
|
||||
# hue transition duration is in steps of 100 ms
|
||||
transition = int(transition * 100)
|
||||
# hue transition duration is in milliseconds
|
||||
transition = int(transition * 1000)
|
||||
dynamic = kwargs.get("dynamic", self.is_dynamic)
|
||||
await self.bridge.async_request_call(
|
||||
self.controller.recall,
|
||||
|
@ -103,6 +103,9 @@ class HueBaseEntity(Entity):
|
||||
if self.resource.type == ResourceTypes.ZIGBEE_CONNECTIVITY:
|
||||
# the zigbee connectivity sensor itself should be always available
|
||||
return True
|
||||
if self.device.product_data.manufacturer_name != "Signify Netherlands B.V.":
|
||||
# availability status for non-philips brand lights is unreliable
|
||||
return True
|
||||
if zigbee := self.bridge.api.devices.get_zigbee_connectivity(self.device.id):
|
||||
# all device-attached entities get availability from the zigbee connectivity
|
||||
return zigbee.status == ConnectivityServiceStatus.CONNECTED
|
||||
|
@ -24,7 +24,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from ..bridge import HueBridge
|
||||
from ..const import DOMAIN
|
||||
from ..const import CONF_ALLOW_HUE_GROUPS, DOMAIN
|
||||
from .entity import HueBaseEntity
|
||||
|
||||
ALLOWED_ERRORS = [
|
||||
@ -76,8 +76,6 @@ async def async_setup_entry(
|
||||
class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||
"""Representation of a Grouped Hue light."""
|
||||
|
||||
# Entities for Hue groups are disabled by default
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_icon = "mdi:lightbulb-group"
|
||||
|
||||
def __init__(
|
||||
@ -92,6 +90,12 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||
self.api: HueBridgeV2 = bridge.api
|
||||
self._attr_supported_features |= SUPPORT_TRANSITION
|
||||
|
||||
# Entities for Hue groups are disabled by default
|
||||
# unless they were enabled in old version (legacy option)
|
||||
self._attr_entity_registry_enabled_default = bridge.config_entry.data.get(
|
||||
CONF_ALLOW_HUE_GROUPS, False
|
||||
)
|
||||
|
||||
self._update_values()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
@ -146,8 +150,8 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||
# Hue uses a range of [0, 100] to control brightness.
|
||||
brightness = float((brightness / 255) * 100)
|
||||
if transition is not None:
|
||||
# hue transition duration is in steps of 100 ms
|
||||
transition = int(transition * 100)
|
||||
# hue transition duration is in milliseconds
|
||||
transition = int(transition * 1000)
|
||||
|
||||
# NOTE: a grouped_light can only handle turn on/off
|
||||
# To set other features, you'll have to control the attached lights
|
||||
|
@ -158,8 +158,8 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
# Hue uses a range of [0, 100] to control brightness.
|
||||
brightness = float((brightness / 255) * 100)
|
||||
if transition is not None:
|
||||
# hue transition duration is in steps of 100 ms
|
||||
transition = int(transition * 100)
|
||||
# hue transition duration is in milliseconds
|
||||
transition = int(transition * 1000)
|
||||
|
||||
await self.bridge.async_request_call(
|
||||
self.controller.set_state,
|
||||
@ -176,8 +176,8 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
"""Turn the light off."""
|
||||
transition = kwargs.get(ATTR_TRANSITION)
|
||||
if transition is not None:
|
||||
# hue transition duration is in steps of 100 ms
|
||||
transition = int(transition * 100)
|
||||
# hue transition duration is in milliseconds
|
||||
transition = int(transition * 1000)
|
||||
await self.bridge.async_request_call(
|
||||
self.controller.set_state,
|
||||
id=self.resource.id,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "hunterdouglas_powerview",
|
||||
"name": "Hunter Douglas PowerView",
|
||||
"documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview",
|
||||
"requirements": ["aiopvapi==1.6.14"],
|
||||
"requirements": ["aiopvapi==1.6.19"],
|
||||
"codeowners": ["@bdraco"],
|
||||
"config_flow": true,
|
||||
"homekit": {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["ffmpeg", "http", "media_source"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/nest",
|
||||
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.5"],
|
||||
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.6"],
|
||||
"codeowners": ["@allenporter"],
|
||||
"quality_scale": "platinum",
|
||||
"dhcp": [
|
||||
|
@ -24,7 +24,7 @@ import logging
|
||||
|
||||
from google_nest_sdm.camera_traits import CameraClipPreviewTrait, CameraEventImageTrait
|
||||
from google_nest_sdm.device import Device
|
||||
from google_nest_sdm.event import ImageEventBase
|
||||
from google_nest_sdm.event import EventImageType, ImageEventBase
|
||||
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
@ -253,7 +253,7 @@ def _browse_event(
|
||||
event_name=MEDIA_SOURCE_EVENT_TITLE_MAP.get(event.event_type, "Event"),
|
||||
event_time=dt_util.as_local(event.timestamp).strftime(DATE_STR_FORMAT),
|
||||
),
|
||||
can_play=True,
|
||||
can_play=(event.event_image_type == EventImageType.CLIP_PREVIEW),
|
||||
can_expand=False,
|
||||
thumbnail=None,
|
||||
children=[],
|
||||
|
@ -4,16 +4,11 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL,
|
||||
SensorDeviceClass,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ELECTRIC_POTENTIAL_VOLT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
PERCENTAGE,
|
||||
@ -38,35 +33,35 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = (
|
||||
SolarLogSensorEntityDescription(
|
||||
key="time",
|
||||
name="last update",
|
||||
device_class=DEVICE_CLASS_TIMESTAMP,
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="power_ac",
|
||||
name="power AC",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="power_dc",
|
||||
name="power DC",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="voltage_ac",
|
||||
name="voltage AC",
|
||||
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="voltage_dc",
|
||||
name="voltage DC",
|
||||
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="yield_day",
|
||||
@ -101,50 +96,50 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = (
|
||||
name="yield total",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
state_class=STATE_CLASS_TOTAL,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_ac",
|
||||
name="consumption AC",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_day",
|
||||
name="consumption day",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_yesterday",
|
||||
name="consumption yesterday",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_month",
|
||||
name="consumption month",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_year",
|
||||
name="consumption year",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="consumption_total",
|
||||
name="consumption total",
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
factor=0.001,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
@ -152,31 +147,31 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = (
|
||||
name="installed peak power",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="alternator_loss",
|
||||
name="alternator loss",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="capacity",
|
||||
name="capacity",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
factor=100,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="efficiency",
|
||||
name="efficiency",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
factor=100,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
@ -184,15 +179,15 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = (
|
||||
name="power available",
|
||||
icon="mdi:solar-power",
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SolarLogSensorEntityDescription(
|
||||
key="usage",
|
||||
name="usage",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
factor=100,
|
||||
),
|
||||
)
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""Platform for solarlog sensors."""
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.helpers import update_coordinator
|
||||
from homeassistant.helpers.entity import DeviceInfo, StateType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.util.dt import as_local
|
||||
|
||||
from . import SolarlogData
|
||||
from .const import DOMAIN, SENSOR_TYPES, SolarLogSensorEntityDescription
|
||||
@ -38,11 +39,16 @@ class SolarlogSensor(update_coordinator.CoordinatorEntity, SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
def native_value(self):
|
||||
"""Return the native sensor value."""
|
||||
result = getattr(self.coordinator.data, self.entity_description.key)
|
||||
if self.entity_description.factor:
|
||||
state = round(result * self.entity_description.factor, 3)
|
||||
if self.entity_description.key == "time":
|
||||
state = as_local(
|
||||
getattr(self.coordinator.data, self.entity_description.key)
|
||||
)
|
||||
else:
|
||||
state = result
|
||||
result = getattr(self.coordinator.data, self.entity_description.key)
|
||||
if self.entity_description.factor:
|
||||
state = round(result * self.entity_description.factor, 3)
|
||||
else:
|
||||
state = result
|
||||
return state
|
||||
|
@ -193,7 +193,7 @@ class SonosSpeaker:
|
||||
self.volume: int | None = None
|
||||
self.muted: bool | None = None
|
||||
self.night_mode: bool | None = None
|
||||
self.dialog_mode: bool | None = None
|
||||
self.dialog_level: bool | None = None
|
||||
self.cross_fade: bool | None = None
|
||||
self.bass: int | None = None
|
||||
self.treble: int | None = None
|
||||
@ -498,17 +498,18 @@ class SonosSpeaker:
|
||||
if "mute" in variables:
|
||||
self.muted = variables["mute"]["Master"] == "1"
|
||||
|
||||
if "night_mode" in variables:
|
||||
self.night_mode = variables["night_mode"] == "1"
|
||||
for bool_var in (
|
||||
"dialog_level",
|
||||
"night_mode",
|
||||
"sub_enabled",
|
||||
"surround_enabled",
|
||||
):
|
||||
if bool_var in variables:
|
||||
setattr(self, bool_var, variables[bool_var] == "1")
|
||||
|
||||
if "dialog_level" in variables:
|
||||
self.dialog_mode = variables["dialog_level"] == "1"
|
||||
|
||||
if "bass" in variables:
|
||||
self.bass = variables["bass"]
|
||||
|
||||
if "treble" in variables:
|
||||
self.treble = variables["treble"]
|
||||
for int_var in ("bass", "treble"):
|
||||
if int_var in variables:
|
||||
setattr(self, int_var, variables[int_var])
|
||||
|
||||
self.async_write_entity_states()
|
||||
|
||||
@ -982,7 +983,7 @@ class SonosSpeaker:
|
||||
self.volume = self.soco.volume
|
||||
self.muted = self.soco.mute
|
||||
self.night_mode = self.soco.night_mode
|
||||
self.dialog_mode = self.soco.dialog_mode
|
||||
self.dialog_level = self.soco.dialog_mode
|
||||
self.bass = self.soco.bass
|
||||
self.treble = self.soco.treble
|
||||
|
||||
|
@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 12
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||
|
@ -16,7 +16,7 @@ ciso8601==2.2.0
|
||||
cryptography==35.0.0
|
||||
emoji==1.5.0
|
||||
hass-nabucasa==0.50.0
|
||||
home-assistant-frontend==20211211.0
|
||||
home-assistant-frontend==20211212.0
|
||||
httpx==0.21.0
|
||||
ifaddr==0.1.7
|
||||
jinja2==3.0.3
|
||||
|
@ -186,7 +186,7 @@ aiohomekit==0.6.4
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==3.0.2
|
||||
aiohue==3.0.3
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==0.9.0
|
||||
@ -231,7 +231,7 @@ aionotion==3.0.2
|
||||
aiopulse==0.4.3
|
||||
|
||||
# homeassistant.components.hunterdouglas_powerview
|
||||
aiopvapi==1.6.14
|
||||
aiopvapi==1.6.19
|
||||
|
||||
# homeassistant.components.pvpc_hourly_pricing
|
||||
aiopvpc==2.2.4
|
||||
@ -738,7 +738,7 @@ google-cloud-pubsub==2.1.0
|
||||
google-cloud-texttospeech==0.4.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==0.4.5
|
||||
google-nest-sdm==0.4.6
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
@ -819,7 +819,7 @@ hole==0.7.0
|
||||
holidays==0.11.3.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20211211.0
|
||||
home-assistant-frontend==20211212.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
|
@ -131,7 +131,7 @@ aiohomekit==0.6.4
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==3.0.2
|
||||
aiohue==3.0.3
|
||||
|
||||
# homeassistant.components.apache_kafka
|
||||
aiokafka==0.6.0
|
||||
@ -161,7 +161,7 @@ aionotion==3.0.2
|
||||
aiopulse==0.4.3
|
||||
|
||||
# homeassistant.components.hunterdouglas_powerview
|
||||
aiopvapi==1.6.14
|
||||
aiopvapi==1.6.19
|
||||
|
||||
# homeassistant.components.pvpc_hourly_pricing
|
||||
aiopvpc==2.2.4
|
||||
@ -461,7 +461,7 @@ google-api-python-client==1.6.4
|
||||
google-cloud-pubsub==2.1.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==0.4.5
|
||||
google-nest-sdm==0.4.6
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
@ -515,7 +515,7 @@ hole==0.7.0
|
||||
holidays==0.11.3.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20211211.0
|
||||
home-assistant-frontend==20211212.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
|
@ -223,11 +223,15 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, events):
|
||||
assert events[-1].data[ATTR_VALUE] == 75
|
||||
|
||||
|
||||
async def test_window_instantiate(hass, hk_driver, events):
|
||||
"""Test if Window accessory is instantiated correctly."""
|
||||
async def test_window_instantiate_set_position(hass, hk_driver, events):
|
||||
"""Test if Window accessory is instantiated correctly and can set position."""
|
||||
entity_id = "cover.window"
|
||||
|
||||
hass.states.async_set(entity_id, None)
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_OPEN,
|
||||
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 0},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Window(hass, hk_driver, "Window", entity_id, 2, None)
|
||||
await acc.run()
|
||||
@ -239,6 +243,29 @@ async def test_window_instantiate(hass, hk_driver, events):
|
||||
assert acc.char_current_position.value == 0
|
||||
assert acc.char_target_position.value == 0
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_OPEN,
|
||||
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 50},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_current_position.value == 50
|
||||
assert acc.char_target_position.value == 50
|
||||
assert acc.char_position_state.value == 2
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_OPEN,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION,
|
||||
ATTR_CURRENT_POSITION: "GARBAGE",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_current_position.value == 50
|
||||
assert acc.char_target_position.value == 50
|
||||
assert acc.char_position_state.value == 2
|
||||
|
||||
|
||||
async def test_windowcovering_cover_set_tilt(hass, hk_driver, events):
|
||||
"""Test if accessory and HA update slat tilt accordingly."""
|
||||
|
@ -119,7 +119,7 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
|
||||
)
|
||||
assert len(mock_bridge_v2.mock_requests) == 2
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is True
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 600
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 6000
|
||||
|
||||
|
||||
async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_data):
|
||||
@ -164,7 +164,7 @@ async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_da
|
||||
)
|
||||
assert len(mock_bridge_v2.mock_requests) == 2
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is False
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 600
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 6000
|
||||
|
||||
|
||||
async def test_light_added(hass, mock_bridge_v2):
|
||||
|
@ -83,7 +83,7 @@ async def test_scene_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
|
||||
assert len(mock_bridge_v2.mock_requests) == 2
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["recall"] == {
|
||||
"action": "active",
|
||||
"duration": 600,
|
||||
"duration": 6000,
|
||||
}
|
||||
|
||||
|
||||
|
@ -62,7 +62,7 @@ class FakeSubscriber(GoogleNestSubscriber):
|
||||
|
||||
def set_update_callback(self, callback: Callable[[EventMessage], Awaitable[None]]):
|
||||
"""Capture the callback set by Home Assistant."""
|
||||
self._callback = callback
|
||||
self._device_manager.set_update_callback(callback)
|
||||
|
||||
async def create_subscription(self):
|
||||
"""Create the subscription."""
|
||||
@ -93,7 +93,6 @@ class FakeSubscriber(GoogleNestSubscriber):
|
||||
"""Simulate a received pubsub message, invoked by tests."""
|
||||
# Update device state, then invoke HomeAssistant to refresh
|
||||
await self._device_manager.async_handle_event(event_message)
|
||||
await self._callback(event_message)
|
||||
|
||||
|
||||
async def async_setup_sdm_platform(
|
||||
|
@ -4,6 +4,8 @@ These tests fake out the subscriber/devicemanager, and are not using a real
|
||||
pubsub subscriber.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from google_nest_sdm.device import Device
|
||||
from google_nest_sdm.event import EventMessage
|
||||
|
||||
@ -298,3 +300,74 @@ async def test_event_message_without_device_event(hass):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(events) == 0
|
||||
|
||||
|
||||
async def test_doorbell_event_thread(hass):
|
||||
"""Test a series of pubsub messages in the same thread."""
|
||||
events = async_capture_events(hass, NEST_EVENT)
|
||||
subscriber = await async_setup_devices(
|
||||
hass,
|
||||
"sdm.devices.types.DOORBELL",
|
||||
traits={
|
||||
"sdm.devices.traits.Info": {
|
||||
"customName": "Front",
|
||||
},
|
||||
"sdm.devices.traits.CameraLiveStream": {},
|
||||
"sdm.devices.traits.CameraClipPreview": {},
|
||||
"sdm.devices.traits.CameraPerson": {},
|
||||
},
|
||||
)
|
||||
registry = er.async_get(hass)
|
||||
entry = registry.async_get("camera.front")
|
||||
assert entry is not None
|
||||
|
||||
event_message_data = {
|
||||
"eventId": "some-event-id-ignored",
|
||||
"resourceUpdate": {
|
||||
"name": DEVICE_ID,
|
||||
"events": {
|
||||
"sdm.devices.events.CameraMotion.Motion": {
|
||||
"eventSessionId": EVENT_SESSION_ID,
|
||||
"eventId": "n:1",
|
||||
},
|
||||
"sdm.devices.events.CameraClipPreview.ClipPreview": {
|
||||
"eventSessionId": EVENT_SESSION_ID,
|
||||
"previewUrl": "image-url-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"eventThreadId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...",
|
||||
"resourcegroup": [DEVICE_ID],
|
||||
}
|
||||
|
||||
# Publish message #1 that starts the event thread
|
||||
timestamp1 = utcnow()
|
||||
message_data_1 = event_message_data.copy()
|
||||
message_data_1.update(
|
||||
{
|
||||
"timestamp": timestamp1.isoformat(timespec="seconds"),
|
||||
"eventThreadState": "STARTED",
|
||||
}
|
||||
)
|
||||
await subscriber.async_receive_event(EventMessage(message_data_1, auth=None))
|
||||
|
||||
# Publish message #1 that sends a no-op update to end the event thread
|
||||
timestamp2 = timestamp1 + datetime.timedelta(seconds=1)
|
||||
message_data_2 = event_message_data.copy()
|
||||
message_data_2.update(
|
||||
{
|
||||
"timestamp": timestamp2.isoformat(timespec="seconds"),
|
||||
"eventThreadState": "ENDED",
|
||||
}
|
||||
)
|
||||
await subscriber.async_receive_event(EventMessage(message_data_2, auth=None))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The event is only published once
|
||||
assert len(events) == 1
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "camera_motion",
|
||||
"timestamp": timestamp1.replace(microsecond=0),
|
||||
"nest_event_id": EVENT_SESSION_ID,
|
||||
}
|
||||
|
@ -231,6 +231,7 @@ async def test_camera_event(hass, auth, hass_client):
|
||||
assert "Person" in browse.title
|
||||
assert not browse.can_expand
|
||||
assert not browse.children
|
||||
assert not browse.can_play
|
||||
|
||||
# Resolving the event links to the media
|
||||
media = await media_source.async_resolve_media(
|
||||
@ -302,6 +303,7 @@ async def test_event_order(hass, auth):
|
||||
event_timestamp_string = event_timestamp2.strftime(DATE_STR_FORMAT)
|
||||
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
|
||||
assert not browse.children[0].can_expand
|
||||
assert not browse.can_play
|
||||
|
||||
# Person event is next
|
||||
assert browse.children[1].domain == DOMAIN
|
||||
@ -310,6 +312,7 @@ async def test_event_order(hass, auth):
|
||||
event_timestamp_string = event_timestamp1.strftime(DATE_STR_FORMAT)
|
||||
assert browse.children[1].title == f"Person @ {event_timestamp_string}"
|
||||
assert not browse.children[1].can_expand
|
||||
assert not browse.can_play
|
||||
|
||||
|
||||
async def test_browse_invalid_device_id(hass, auth):
|
||||
@ -449,6 +452,7 @@ async def test_camera_event_clip_preview(hass, auth, hass_client):
|
||||
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
|
||||
assert not browse.children[0].can_expand
|
||||
assert len(browse.children[0].children) == 0
|
||||
assert browse.children[0].can_play
|
||||
|
||||
# Resolving the event links to the media
|
||||
media = await media_source.async_resolve_media(
|
||||
|
Loading…
x
Reference in New Issue
Block a user