This commit is contained in:
Paulus Schoutsen 2023-05-04 12:45:55 -04:00 committed by GitHub
commit bce18bf61a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 162 additions and 106 deletions

View File

@ -18,7 +18,7 @@
"bleak==0.20.2", "bleak==0.20.2",
"bleak-retry-connector==3.0.2", "bleak-retry-connector==3.0.2",
"bluetooth-adapters==0.15.3", "bluetooth-adapters==0.15.3",
"bluetooth-auto-recovery==1.1.1", "bluetooth-auto-recovery==1.1.2",
"bluetooth-data-tools==0.4.0", "bluetooth-data-tools==0.4.0",
"dbus-fast==1.85.0" "dbus-fast==1.85.0"
] ]

View File

@ -24,7 +24,6 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.homeassistant.exposed_entities import ( from homeassistant.components.homeassistant.exposed_entities import (
async_expose_entity, async_expose_entity,
async_get_assistant_settings, async_get_assistant_settings,
async_get_entity_settings,
async_listen_entity_updates, async_listen_entity_updates,
async_should_expose, async_should_expose,
) )
@ -201,10 +200,6 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
return return
for state in self.hass.states.async_all(): for state in self.hass.states.async_all():
with suppress(HomeAssistantError):
entity_settings = async_get_entity_settings(self.hass, state.entity_id)
if CLOUD_ALEXA in entity_settings:
continue
async_expose_entity( async_expose_entity(
self.hass, self.hass,
CLOUD_ALEXA, CLOUD_ALEXA,
@ -212,10 +207,6 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
self._should_expose_legacy(state.entity_id), self._should_expose_legacy(state.entity_id),
) )
for entity_id in self._prefs.alexa_entity_configs: for entity_id in self._prefs.alexa_entity_configs:
with suppress(HomeAssistantError):
entity_settings = async_get_entity_settings(self.hass, entity_id)
if CLOUD_ALEXA in entity_settings:
continue
async_expose_entity( async_expose_entity(
self.hass, self.hass,
CLOUD_ALEXA, CLOUD_ALEXA,

View File

@ -1,6 +1,5 @@
"""Google config for Cloud.""" """Google config for Cloud."""
import asyncio import asyncio
from contextlib import suppress
from http import HTTPStatus from http import HTTPStatus
import logging import logging
from typing import Any from typing import Any
@ -178,10 +177,6 @@ class CloudGoogleConfig(AbstractConfig):
for state in self.hass.states.async_all(): for state in self.hass.states.async_all():
entity_id = state.entity_id entity_id = state.entity_id
with suppress(HomeAssistantError):
entity_settings = async_get_entity_settings(self.hass, entity_id)
if CLOUD_GOOGLE in entity_settings:
continue
async_expose_entity( async_expose_entity(
self.hass, self.hass,
CLOUD_GOOGLE, CLOUD_GOOGLE,
@ -197,10 +192,6 @@ class CloudGoogleConfig(AbstractConfig):
_2fa_disabled, _2fa_disabled,
) )
for entity_id in self._prefs.google_entity_configs: for entity_id in self._prefs.google_entity_configs:
with suppress(HomeAssistantError):
entity_settings = async_get_entity_settings(self.hass, entity_id)
if CLOUD_GOOGLE in entity_settings:
continue
async_expose_entity( async_expose_entity(
self.hass, self.hass,
CLOUD_GOOGLE, CLOUD_GOOGLE,

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend", "documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system", "integration_type": "system",
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["home-assistant-frontend==20230503.1"] "requirements": ["home-assistant-frontend==20230503.2"]
} }

View File

@ -205,13 +205,20 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
methods, DEFAULT_ATTEMPTS, OVERALL_TIMEOUT methods, DEFAULT_ATTEMPTS, OVERALL_TIMEOUT
) )
def get_number_of_zones(self) -> int:
"""Return the number of zones.
If the number of zones is not yet populated, return 0
"""
return len(self.device.color_zones) if self.device.color_zones else 0
@callback @callback
def _async_build_color_zones_update_requests(self) -> list[Callable]: def _async_build_color_zones_update_requests(self) -> list[Callable]:
"""Build a color zones update request.""" """Build a color zones update request."""
device = self.device device = self.device
return [ return [
partial(device.get_color_zones, start_index=zone) partial(device.get_color_zones, start_index=zone)
for zone in range(0, len(device.color_zones), 8) for zone in range(0, self.get_number_of_zones(), 8)
] ]
async def _async_update_data(self) -> None: async def _async_update_data(self) -> None:
@ -224,7 +231,7 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
): ):
await self._async_populate_device_info() await self._async_populate_device_info()
num_zones = len(device.color_zones) if device.color_zones is not None else 0 num_zones = self.get_number_of_zones()
features = lifx_features(self.device) features = lifx_features(self.device)
is_extended_multizone = features["extended_multizone"] is_extended_multizone = features["extended_multizone"]
is_legacy_multizone = not is_extended_multizone and features["multizone"] is_legacy_multizone = not is_extended_multizone and features["multizone"]
@ -256,7 +263,7 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
if is_extended_multizone or is_legacy_multizone: if is_extended_multizone or is_legacy_multizone:
self.active_effect = FirmwareEffect[self.device.effect.get("effect", "OFF")] self.active_effect = FirmwareEffect[self.device.effect.get("effect", "OFF")]
if is_legacy_multizone and num_zones != len(device.color_zones): if is_legacy_multizone and num_zones != self.get_number_of_zones():
# The number of zones has changed so we need # The number of zones has changed so we need
# to update the zones again. This happens rarely. # to update the zones again. This happens rarely.
await self.async_get_color_zones() await self.async_get_color_zones()

View File

@ -382,7 +382,7 @@ class LIFXMultiZone(LIFXColor):
"""Send a color change to the bulb.""" """Send a color change to the bulb."""
bulb = self.bulb bulb = self.bulb
color_zones = bulb.color_zones color_zones = bulb.color_zones
num_zones = len(color_zones) num_zones = self.coordinator.get_number_of_zones()
# Zone brightness is not reported when powered off # Zone brightness is not reported when powered off
if not self.is_on and hsbk[HSBK_BRIGHTNESS] is None: if not self.is_on and hsbk[HSBK_BRIGHTNESS] is None:

View File

@ -7,5 +7,5 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aionotion"], "loggers": ["aionotion"],
"requirements": ["aionotion==2023.04.2"] "requirements": ["aionotion==2023.05.0"]
} }

View File

@ -12,7 +12,7 @@ from httpx import RequestError
import onvif import onvif
from onvif import ONVIFCamera from onvif import ONVIFCamera
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
from zeep.exceptions import Fault, XMLParseError from zeep.exceptions import Fault, TransportError, XMLParseError, XMLSyntaxError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -203,81 +203,104 @@ class ONVIFDevice:
"""Warns if device and system date not synced.""" """Warns if device and system date not synced."""
LOGGER.debug("%s: Setting up the ONVIF device management service", self.name) LOGGER.debug("%s: Setting up the ONVIF device management service", self.name)
device_mgmt = self.device.create_devicemgmt_service() device_mgmt = self.device.create_devicemgmt_service()
system_date = dt_util.utcnow()
LOGGER.debug("%s: Retrieving current device date/time", self.name) LOGGER.debug("%s: Retrieving current device date/time", self.name)
try: try:
system_date = dt_util.utcnow()
device_time = await device_mgmt.GetSystemDateAndTime() device_time = await device_mgmt.GetSystemDateAndTime()
if not device_time:
LOGGER.debug(
"""Couldn't get device '%s' date/time.
GetSystemDateAndTime() return null/empty""",
self.name,
)
return
LOGGER.debug("%s: Device time: %s", self.name, device_time)
tzone = dt_util.DEFAULT_TIME_ZONE
cdate = device_time.LocalDateTime
if device_time.UTCDateTime:
tzone = dt_util.UTC
cdate = device_time.UTCDateTime
elif device_time.TimeZone:
tzone = dt_util.get_time_zone(device_time.TimeZone.TZ) or tzone
if cdate is None:
LOGGER.warning(
"%s: Could not retrieve date/time on this camera", self.name
)
else:
cam_date = dt.datetime(
cdate.Date.Year,
cdate.Date.Month,
cdate.Date.Day,
cdate.Time.Hour,
cdate.Time.Minute,
cdate.Time.Second,
0,
tzone,
)
cam_date_utc = cam_date.astimezone(dt_util.UTC)
LOGGER.debug(
"%s: Device date/time: %s | System date/time: %s",
self.name,
cam_date_utc,
system_date,
)
dt_diff = cam_date - system_date
self._dt_diff_seconds = dt_diff.total_seconds()
# It could be off either direction, so we need to check the absolute value
if abs(self._dt_diff_seconds) > 5:
LOGGER.warning(
(
"The date/time on %s (UTC) is '%s', "
"which is different from the system '%s', "
"this could lead to authentication issues"
),
self.name,
cam_date_utc,
system_date,
)
if device_time.DateTimeType == "Manual":
# Set Date and Time ourselves if Date and Time is set manually in the camera.
await self.async_manually_set_date_and_time()
except RequestError as err: except RequestError as err:
LOGGER.warning( LOGGER.warning(
"Couldn't get device '%s' date/time. Error: %s", self.name, err "Couldn't get device '%s' date/time. Error: %s", self.name, err
) )
return
if not device_time:
LOGGER.debug(
"""Couldn't get device '%s' date/time.
GetSystemDateAndTime() return null/empty""",
self.name,
)
return
LOGGER.debug("%s: Device time: %s", self.name, device_time)
tzone = dt_util.DEFAULT_TIME_ZONE
cdate = device_time.LocalDateTime
if device_time.UTCDateTime:
tzone = dt_util.UTC
cdate = device_time.UTCDateTime
elif device_time.TimeZone:
tzone = dt_util.get_time_zone(device_time.TimeZone.TZ) or tzone
if cdate is None:
LOGGER.warning("%s: Could not retrieve date/time on this camera", self.name)
return
cam_date = dt.datetime(
cdate.Date.Year,
cdate.Date.Month,
cdate.Date.Day,
cdate.Time.Hour,
cdate.Time.Minute,
cdate.Time.Second,
0,
tzone,
)
cam_date_utc = cam_date.astimezone(dt_util.UTC)
LOGGER.debug(
"%s: Device date/time: %s | System date/time: %s",
self.name,
cam_date_utc,
system_date,
)
dt_diff = cam_date - system_date
self._dt_diff_seconds = dt_diff.total_seconds()
# It could be off either direction, so we need to check the absolute value
if abs(self._dt_diff_seconds) < 5:
return
LOGGER.warning(
(
"The date/time on %s (UTC) is '%s', "
"which is different from the system '%s', "
"this could lead to authentication issues"
),
self.name,
cam_date_utc,
system_date,
)
if device_time.DateTimeType != "Manual":
return
# Set Date and Time ourselves if Date and Time is set manually in the camera.
try:
await self.async_manually_set_date_and_time()
except (RequestError, TransportError):
LOGGER.warning("%s: Could not sync date/time on this camera", self.name)
async def async_get_device_info(self) -> DeviceInfo: async def async_get_device_info(self) -> DeviceInfo:
"""Obtain information about this device.""" """Obtain information about this device."""
device_mgmt = self.device.create_devicemgmt_service() device_mgmt = self.device.create_devicemgmt_service()
device_info = await device_mgmt.GetDeviceInformation() manufacturer = None
model = None
firmware_version = None
serial_number = None
try:
device_info = await device_mgmt.GetDeviceInformation()
except (XMLParseError, XMLSyntaxError, TransportError) as ex:
# Some cameras have invalid UTF-8 in their device information (TransportError)
# and others have completely invalid XML (XMLParseError, XMLSyntaxError)
LOGGER.warning("%s: Failed to fetch device information: %s", self.name, ex)
else:
manufacturer = device_info.Manufacturer
model = device_info.Model
firmware_version = device_info.FirmwareVersion
serial_number = device_info.SerialNumber
# Grab the last MAC address for backwards compatibility # Grab the last MAC address for backwards compatibility
mac = None mac = None
@ -297,10 +320,10 @@ class ONVIFDevice:
) )
return DeviceInfo( return DeviceInfo(
device_info.Manufacturer, manufacturer,
device_info.Model, model,
device_info.FirmwareVersion, firmware_version,
device_info.SerialNumber, serial_number,
mac, mac,
) )
@ -328,7 +351,7 @@ class ONVIFDevice:
"""Start the event handler.""" """Start the event handler."""
with suppress(*GET_CAPABILITIES_EXCEPTIONS, XMLParseError): with suppress(*GET_CAPABILITIES_EXCEPTIONS, XMLParseError):
onvif_capabilities = self.onvif_capabilities or {} onvif_capabilities = self.onvif_capabilities or {}
pull_point_support = onvif_capabilities.get("Events", {}).get( pull_point_support = (onvif_capabilities.get("Events") or {}).get(
"WSPullPointSupport" "WSPullPointSupport"
) )
LOGGER.debug("%s: WSPullPointSupport: %s", self.name, pull_point_support) LOGGER.debug("%s: WSPullPointSupport: %s", self.name, pull_point_support)

View File

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

View File

@ -14,7 +14,7 @@ bcrypt==4.0.1
bleak-retry-connector==3.0.2 bleak-retry-connector==3.0.2
bleak==0.20.2 bleak==0.20.2
bluetooth-adapters==0.15.3 bluetooth-adapters==0.15.3
bluetooth-auto-recovery==1.1.1 bluetooth-auto-recovery==1.1.2
bluetooth-data-tools==0.4.0 bluetooth-data-tools==0.4.0
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.3.0 ciso8601==2.3.0
@ -25,7 +25,7 @@ ha-av==10.0.0
hass-nabucasa==0.66.2 hass-nabucasa==0.66.2
hassil==1.0.6 hassil==1.0.6
home-assistant-bluetooth==1.10.0 home-assistant-bluetooth==1.10.0
home-assistant-frontend==20230503.1 home-assistant-frontend==20230503.2
home-assistant-intents==2023.4.26 home-assistant-intents==2023.4.26
httpx==0.24.0 httpx==0.24.0
ifaddr==0.1.7 ifaddr==0.1.7

View File

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

View File

@ -223,7 +223,7 @@ aionanoleaf==0.2.1
aionotify==0.2.0 aionotify==0.2.0
# homeassistant.components.notion # homeassistant.components.notion
aionotion==2023.04.2 aionotion==2023.05.0
# homeassistant.components.oncue # homeassistant.components.oncue
aiooncue==0.3.4 aiooncue==0.3.4
@ -465,7 +465,7 @@ bluemaestro-ble==0.2.3
bluetooth-adapters==0.15.3 bluetooth-adapters==0.15.3
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==1.1.1 bluetooth-auto-recovery==1.1.2
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
# homeassistant.components.esphome # homeassistant.components.esphome
@ -911,7 +911,7 @@ hole==0.8.0
holidays==0.21.13 holidays==0.21.13
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20230503.1 home-assistant-frontend==20230503.2
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2023.4.26 home-assistant-intents==2023.4.26

View File

@ -204,7 +204,7 @@ aiomusiccast==0.14.8
aionanoleaf==0.2.1 aionanoleaf==0.2.1
# homeassistant.components.notion # homeassistant.components.notion
aionotion==2023.04.2 aionotion==2023.05.0
# homeassistant.components.oncue # homeassistant.components.oncue
aiooncue==0.3.4 aiooncue==0.3.4
@ -385,7 +385,7 @@ bluemaestro-ble==0.2.3
bluetooth-adapters==0.15.3 bluetooth-adapters==0.15.3
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==1.1.1 bluetooth-auto-recovery==1.1.2
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
# homeassistant.components.esphome # homeassistant.components.esphome
@ -700,7 +700,7 @@ hole==0.8.0
holidays==0.21.13 holidays==0.21.13
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20230503.1 home-assistant-frontend==20230503.2
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2023.4.26 home-assistant-intents==2023.4.26

View File

@ -628,7 +628,7 @@ async def test_alexa_config_migrate_expose_entity_prefs(
"cloud.alexa": {"should_expose": True} "cloud.alexa": {"should_expose": True}
} }
assert async_get_entity_settings(hass, entity_migrated.entity_id) == { assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
"cloud.alexa": {"should_expose": False} "cloud.alexa": {"should_expose": True}
} }
assert async_get_entity_settings(hass, entity_config.entity_id) == { assert async_get_entity_settings(hass, entity_config.entity_id) == {
"cloud.alexa": {"should_expose": False} "cloud.alexa": {"should_expose": False}

View File

@ -580,7 +580,7 @@ async def test_google_config_migrate_expose_entity_prefs(
"cloud.google_assistant": {"should_expose": True} "cloud.google_assistant": {"should_expose": True}
} }
assert async_get_entity_settings(hass, entity_migrated.entity_id) == { assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
"cloud.google_assistant": {"should_expose": False} "cloud.google_assistant": {"should_expose": True}
} }
assert async_get_entity_settings(hass, entity_no_2fa_exposed.entity_id) == { assert async_get_entity_settings(hass, entity_no_2fa_exposed.entity_id) == {
"cloud.google_assistant": {"disable_2fa": True, "should_expose": True} "cloud.google_assistant": {"disable_2fa": True, "should_expose": True}

View File

@ -36,6 +36,7 @@ from homeassistant.components.light import (
ATTR_TRANSITION, ATTR_TRANSITION,
ATTR_XY_COLOR, ATTR_XY_COLOR,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
SERVICE_TURN_ON,
ColorMode, ColorMode,
) )
from homeassistant.const import ( from homeassistant.const import (
@ -1741,3 +1742,46 @@ async def test_set_hev_cycle_state_fails_for_color_bulb(hass: HomeAssistant) ->
{ATTR_ENTITY_ID: entity_id, ATTR_POWER: True}, {ATTR_ENTITY_ID: entity_id, ATTR_POWER: True},
blocking=True, blocking=True,
) )
async def test_light_strip_zones_not_populated_yet(hass: HomeAssistant) -> None:
"""Test a light strip were zones are not populated initially."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_light_strip()
bulb.power_level = 65535
bulb.color_zones = None
bulb.color = [65535, 65535, 65535, 65535]
with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
device=bulb
), _patch_device(device=bulb):
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
entity_id = "light.my_bulb"
state = hass.states.get(entity_id)
assert state.state == "on"
attributes = state.attributes
assert attributes[ATTR_BRIGHTNESS] == 255
assert attributes[ATTR_COLOR_MODE] == ColorMode.HS
assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
ColorMode.COLOR_TEMP,
ColorMode.HS,
]
assert attributes[ATTR_HS_COLOR] == (360.0, 100.0)
assert attributes[ATTR_RGB_COLOR] == (255, 0, 0)
assert attributes[ATTR_XY_COLOR] == (0.701, 0.299)
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
)
assert bulb.set_power.calls[0][0][0] is True
bulb.set_power.reset_mock()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_ON