mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Merge pull request #75243 from home-assistant/rc
This commit is contained in:
commit
4e29bdf715
@ -129,8 +129,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/binary_sensor/ @home-assistant/core
|
/homeassistant/components/binary_sensor/ @home-assistant/core
|
||||||
/tests/components/binary_sensor/ @home-assistant/core
|
/tests/components/binary_sensor/ @home-assistant/core
|
||||||
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
||||||
/homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu
|
/homeassistant/components/blebox/ @bbx-a @riokuu
|
||||||
/tests/components/blebox/ @bbx-a @bbx-jp @riokuu
|
/tests/components/blebox/ @bbx-a @riokuu
|
||||||
/homeassistant/components/blink/ @fronzbot
|
/homeassistant/components/blink/ @fronzbot
|
||||||
/tests/components/blink/ @fronzbot
|
/tests/components/blink/ @fronzbot
|
||||||
/homeassistant/components/blueprint/ @home-assistant/core
|
/homeassistant/components/blueprint/ @home-assistant/core
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CLIENT_ID, DOMAIN
|
||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -23,7 +23,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Set up platform from a ConfigEntry."""
|
"""Set up platform from a ConfigEntry."""
|
||||||
username = entry.data[CONF_USERNAME]
|
username = entry.data[CONF_USERNAME]
|
||||||
password = entry.data[CONF_PASSWORD]
|
password = entry.data[CONF_PASSWORD]
|
||||||
acc = AladdinConnectClient(username, password, async_get_clientsession(hass))
|
acc = AladdinConnectClient(
|
||||||
|
username, password, async_get_clientsession(hass), CLIENT_ID
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
if not await acc.login():
|
if not await acc.login():
|
||||||
raise ConfigEntryAuthFailed("Incorrect Password")
|
raise ConfigEntryAuthFailed("Incorrect Password")
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.data_entry_flow import FlowResult
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CLIENT_ID, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -38,7 +38,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
|||||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||||
"""
|
"""
|
||||||
acc = AladdinConnectClient(
|
acc = AladdinConnectClient(
|
||||||
data[CONF_USERNAME], data[CONF_PASSWORD], async_get_clientsession(hass)
|
data[CONF_USERNAME],
|
||||||
|
data[CONF_PASSWORD],
|
||||||
|
async_get_clientsession(hass),
|
||||||
|
CLIENT_ID,
|
||||||
)
|
)
|
||||||
login = await acc.login()
|
login = await acc.login()
|
||||||
await acc.close()
|
await acc.close()
|
||||||
|
@ -18,3 +18,4 @@ STATES_MAP: Final[dict[str, str]] = {
|
|||||||
|
|
||||||
DOMAIN = "aladdin_connect"
|
DOMAIN = "aladdin_connect"
|
||||||
SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||||
|
CLIENT_ID = "1000"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "aladdin_connect",
|
"domain": "aladdin_connect",
|
||||||
"name": "Aladdin Connect",
|
"name": "Aladdin Connect",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||||
"requirements": ["AIOAladdinConnect==0.1.23"],
|
"requirements": ["AIOAladdinConnect==0.1.25"],
|
||||||
"codeowners": ["@mkmer"],
|
"codeowners": ["@mkmer"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aladdin_connect"],
|
"loggers": ["aladdin_connect"],
|
||||||
|
@ -86,7 +86,9 @@ async def async_enable_proactive_mode(hass, smart_home_config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if should_doorbell:
|
if should_doorbell:
|
||||||
if new_state.state == STATE_ON:
|
if new_state.state == STATE_ON and (
|
||||||
|
old_state is None or old_state.state != STATE_ON
|
||||||
|
):
|
||||||
await async_send_doorbell_event_message(
|
await async_send_doorbell_event_message(
|
||||||
hass, smart_home_config, alexa_changed_entity
|
hass, smart_home_config, alexa_changed_entity
|
||||||
)
|
)
|
||||||
|
@ -3,13 +3,7 @@
|
|||||||
from homeassistant.components.cover import CoverDeviceClass
|
from homeassistant.components.cover import CoverDeviceClass
|
||||||
from homeassistant.components.sensor import SensorDeviceClass
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
from homeassistant.components.switch import SwitchDeviceClass
|
from homeassistant.components.switch import SwitchDeviceClass
|
||||||
from homeassistant.const import (
|
from homeassistant.const import TEMP_CELSIUS
|
||||||
STATE_CLOSED,
|
|
||||||
STATE_CLOSING,
|
|
||||||
STATE_OPEN,
|
|
||||||
STATE_OPENING,
|
|
||||||
TEMP_CELSIUS,
|
|
||||||
)
|
|
||||||
|
|
||||||
DOMAIN = "blebox"
|
DOMAIN = "blebox"
|
||||||
PRODUCT = "product"
|
PRODUCT = "product"
|
||||||
@ -30,19 +24,6 @@ BLEBOX_TO_HASS_DEVICE_CLASSES = {
|
|||||||
"temperature": SensorDeviceClass.TEMPERATURE,
|
"temperature": SensorDeviceClass.TEMPERATURE,
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEBOX_TO_HASS_COVER_STATES = {
|
|
||||||
None: None,
|
|
||||||
0: STATE_CLOSING, # moving down
|
|
||||||
1: STATE_OPENING, # moving up
|
|
||||||
2: STATE_OPEN, # manually stopped
|
|
||||||
3: STATE_CLOSED, # lower limit
|
|
||||||
4: STATE_OPEN, # upper limit / open
|
|
||||||
# gateController
|
|
||||||
5: STATE_OPEN, # overload
|
|
||||||
6: STATE_OPEN, # motor failure
|
|
||||||
# 7 is not used
|
|
||||||
8: STATE_OPEN, # safety stop
|
|
||||||
}
|
|
||||||
|
|
||||||
BLEBOX_TO_UNIT_MAP = {"celsius": TEMP_CELSIUS}
|
BLEBOX_TO_UNIT_MAP = {"celsius": TEMP_CELSIUS}
|
||||||
|
|
||||||
|
@ -9,12 +9,26 @@ from homeassistant.components.cover import (
|
|||||||
CoverEntityFeature,
|
CoverEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING
|
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import BleBoxEntity, create_blebox_entities
|
from . import BleBoxEntity, create_blebox_entities
|
||||||
from .const import BLEBOX_TO_HASS_COVER_STATES, BLEBOX_TO_HASS_DEVICE_CLASSES
|
from .const import BLEBOX_TO_HASS_DEVICE_CLASSES
|
||||||
|
|
||||||
|
BLEBOX_TO_HASS_COVER_STATES = {
|
||||||
|
None: None,
|
||||||
|
0: STATE_CLOSING, # moving down
|
||||||
|
1: STATE_OPENING, # moving up
|
||||||
|
2: STATE_OPEN, # manually stopped
|
||||||
|
3: STATE_CLOSED, # lower limit
|
||||||
|
4: STATE_OPEN, # upper limit / open
|
||||||
|
# gateController
|
||||||
|
5: STATE_OPEN, # overload
|
||||||
|
6: STATE_OPEN, # motor failure
|
||||||
|
# 7 is not used
|
||||||
|
8: STATE_OPEN, # safety stop
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from blebox_uniapi.error import BadOnValueError
|
|
||||||
import blebox_uniapi.light
|
import blebox_uniapi.light
|
||||||
from blebox_uniapi.light import BleboxColorMode
|
from blebox_uniapi.light import BleboxColorMode
|
||||||
|
|
||||||
@ -160,16 +159,21 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity):
|
|||||||
else:
|
else:
|
||||||
value = feature.apply_brightness(value, brightness)
|
value = feature.apply_brightness(value, brightness)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._feature.async_on(value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
f"Turning on '{self.name}' failed: Bad value {value}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
if effect is not None:
|
if effect is not None:
|
||||||
effect_value = self.effect_list.index(effect)
|
|
||||||
await self._feature.async_api_command("effect", effect_value)
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
await self._feature.async_on(value)
|
effect_value = self.effect_list.index(effect)
|
||||||
except BadOnValueError as ex:
|
await self._feature.async_api_command("effect", effect_value)
|
||||||
_LOGGER.error(
|
except ValueError as exc:
|
||||||
"Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex
|
raise ValueError(
|
||||||
)
|
f"Turning on with effect '{self.name}' failed: {effect} not in effect list."
|
||||||
|
) from exc
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
"""Turn the light off."""
|
"""Turn the light off."""
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"name": "BleBox devices",
|
"name": "BleBox devices",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/blebox",
|
"documentation": "https://www.home-assistant.io/integrations/blebox",
|
||||||
"requirements": ["blebox_uniapi==2.0.0"],
|
"requirements": ["blebox_uniapi==2.0.1"],
|
||||||
"codeowners": ["@bbx-a", "@bbx-jp", "@riokuu"],
|
"codeowners": ["@bbx-a", "@riokuu"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["blebox_uniapi"]
|
"loggers": ["blebox_uniapi"]
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "frontend",
|
"domain": "frontend",
|
||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": ["home-assistant-frontend==20220707.0"],
|
"requirements": ["home-assistant-frontend==20220707.1"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
"auth",
|
"auth",
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.sensor import (
|
|||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import PERCENTAGE, POWER_KILO_WATT
|
from homeassistant.const import PERCENTAGE, POWER_WATT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="Power",
|
key="Power",
|
||||||
native_unit_of_measurement=POWER_KILO_WATT,
|
native_unit_of_measurement=POWER_WATT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Litter-Robot",
|
"name": "Litter-Robot",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
|
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
|
||||||
"requirements": ["pylitterbot==2022.3.0"],
|
"requirements": ["pylitterbot==2022.7.0"],
|
||||||
"codeowners": ["@natekspencer"],
|
"codeowners": ["@natekspencer"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pylitterbot"]
|
"loggers": ["pylitterbot"]
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
"dhcp": [
|
"dhcp": [
|
||||||
{ "macaddress": "18B430*" },
|
{ "macaddress": "18B430*" },
|
||||||
{ "macaddress": "641666*" },
|
{ "macaddress": "641666*" },
|
||||||
{ "macaddress": "D8EB46*" },
|
{ "macaddress": "D8EB46*" }
|
||||||
{ "macaddress": "1C53F9*" }
|
|
||||||
],
|
],
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["google_nest_sdm", "nest"]
|
"loggers": ["google_nest_sdm", "nest"]
|
||||||
|
@ -57,9 +57,15 @@ from .const import (
|
|||||||
SOURCE_TIMEOUT,
|
SOURCE_TIMEOUT,
|
||||||
STREAM_RESTART_INCREMENT,
|
STREAM_RESTART_INCREMENT,
|
||||||
STREAM_RESTART_RESET_TIME,
|
STREAM_RESTART_RESET_TIME,
|
||||||
TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
|
||||||
)
|
)
|
||||||
from .core import PROVIDERS, IdleTimer, KeyFrameConverter, StreamOutput, StreamSettings
|
from .core import (
|
||||||
|
PROVIDERS,
|
||||||
|
STREAM_SETTINGS_NON_LL_HLS,
|
||||||
|
IdleTimer,
|
||||||
|
KeyFrameConverter,
|
||||||
|
StreamOutput,
|
||||||
|
StreamSettings,
|
||||||
|
)
|
||||||
from .diagnostics import Diagnostics
|
from .diagnostics import Diagnostics
|
||||||
from .hls import HlsStreamOutput, async_setup_hls
|
from .hls import HlsStreamOutput, async_setup_hls
|
||||||
|
|
||||||
@ -224,14 +230,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
hls_part_timeout=2 * conf[CONF_PART_DURATION],
|
hls_part_timeout=2 * conf[CONF_PART_DURATION],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
hass.data[DOMAIN][ATTR_SETTINGS] = StreamSettings(
|
hass.data[DOMAIN][ATTR_SETTINGS] = STREAM_SETTINGS_NON_LL_HLS
|
||||||
ll_hls=False,
|
|
||||||
min_segment_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS
|
|
||||||
- SEGMENT_DURATION_ADJUSTER,
|
|
||||||
part_target_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
|
||||||
hls_advance_part_limit=3,
|
|
||||||
hls_part_timeout=TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Setup HLS
|
# Setup HLS
|
||||||
hls_endpoint = async_setup_hls(hass)
|
hls_endpoint = async_setup_hls(hass)
|
||||||
|
@ -5,6 +5,7 @@ import asyncio
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
from collections.abc import Callable, Coroutine, Iterable
|
from collections.abc import Callable, Coroutine, Iterable
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@ -16,13 +17,20 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
|||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
from .const import ATTR_STREAMS, DOMAIN
|
from .const import (
|
||||||
|
ATTR_STREAMS,
|
||||||
|
DOMAIN,
|
||||||
|
SEGMENT_DURATION_ADJUSTER,
|
||||||
|
TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from av import CodecContext, Packet
|
from av import CodecContext, Packet
|
||||||
|
|
||||||
from . import Stream
|
from . import Stream
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PROVIDERS: Registry[str, type[StreamOutput]] = Registry()
|
PROVIDERS: Registry[str, type[StreamOutput]] = Registry()
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +45,15 @@ class StreamSettings:
|
|||||||
hls_part_timeout: float = attr.ib()
|
hls_part_timeout: float = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
|
STREAM_SETTINGS_NON_LL_HLS = StreamSettings(
|
||||||
|
ll_hls=False,
|
||||||
|
min_segment_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS - SEGMENT_DURATION_ADJUSTER,
|
||||||
|
part_target_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
||||||
|
hls_advance_part_limit=3,
|
||||||
|
hls_part_timeout=TARGET_SEGMENT_DURATION_NON_LL_HLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True)
|
@attr.s(slots=True)
|
||||||
class Part:
|
class Part:
|
||||||
"""Represent a segment part."""
|
"""Represent a segment part."""
|
||||||
@ -426,12 +443,22 @@ class KeyFrameConverter:
|
|||||||
return
|
return
|
||||||
packet = self.packet
|
packet = self.packet
|
||||||
self.packet = None
|
self.packet = None
|
||||||
# decode packet (flush afterwards)
|
for _ in range(2): # Retry once if codec context needs to be flushed
|
||||||
frames = self._codec_context.decode(packet)
|
try:
|
||||||
for _i in range(2):
|
# decode packet (flush afterwards)
|
||||||
if frames:
|
frames = self._codec_context.decode(packet)
|
||||||
|
for _i in range(2):
|
||||||
|
if frames:
|
||||||
|
break
|
||||||
|
frames = self._codec_context.decode(None)
|
||||||
break
|
break
|
||||||
frames = self._codec_context.decode(None)
|
except EOFError:
|
||||||
|
_LOGGER.debug("Codec context needs flushing, attempting to reopen")
|
||||||
|
self._codec_context.close()
|
||||||
|
self._codec_context.open()
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Unable to decode keyframe")
|
||||||
|
return
|
||||||
if frames:
|
if frames:
|
||||||
frame = frames[0]
|
frame = frames[0]
|
||||||
if width and height:
|
if width and height:
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
def find_box(
|
def find_box(
|
||||||
@ -135,3 +139,11 @@ def get_codec_string(mp4_bytes: bytes) -> str:
|
|||||||
codecs.append(codec)
|
codecs.append(codec)
|
||||||
|
|
||||||
return ",".join(codecs)
|
return ",".join(codecs)
|
||||||
|
|
||||||
|
|
||||||
|
def read_init(bytes_io: BytesIO) -> bytes:
|
||||||
|
"""Read the init from a mp4 file."""
|
||||||
|
bytes_io.seek(24)
|
||||||
|
moov_len = int.from_bytes(bytes_io.read(4), byteorder="big")
|
||||||
|
bytes_io.seek(0)
|
||||||
|
return bytes_io.read(24 + moov_len)
|
||||||
|
@ -5,11 +5,12 @@ from collections import defaultdict, deque
|
|||||||
from collections.abc import Callable, Generator, Iterator, Mapping
|
from collections.abc import Callable, Generator, Iterator, Mapping
|
||||||
import contextlib
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
from io import BytesIO
|
from io import SEEK_END, BytesIO
|
||||||
import logging
|
import logging
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
|
import attr
|
||||||
import av
|
import av
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -24,8 +25,16 @@ from .const import (
|
|||||||
SEGMENT_CONTAINER_FORMAT,
|
SEGMENT_CONTAINER_FORMAT,
|
||||||
SOURCE_TIMEOUT,
|
SOURCE_TIMEOUT,
|
||||||
)
|
)
|
||||||
from .core import KeyFrameConverter, Part, Segment, StreamOutput, StreamSettings
|
from .core import (
|
||||||
|
STREAM_SETTINGS_NON_LL_HLS,
|
||||||
|
KeyFrameConverter,
|
||||||
|
Part,
|
||||||
|
Segment,
|
||||||
|
StreamOutput,
|
||||||
|
StreamSettings,
|
||||||
|
)
|
||||||
from .diagnostics import Diagnostics
|
from .diagnostics import Diagnostics
|
||||||
|
from .fmp4utils import read_init
|
||||||
from .hls import HlsStreamOutput
|
from .hls import HlsStreamOutput
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -108,7 +117,7 @@ class StreamMuxer:
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
video_stream: av.video.VideoStream,
|
video_stream: av.video.VideoStream,
|
||||||
audio_stream: av.audio.stream.AudioStream | None,
|
audio_stream: av.audio.stream.AudioStream | None,
|
||||||
audio_bsf: av.BitStreamFilterContext | None,
|
audio_bsf: av.BitStreamFilter | None,
|
||||||
stream_state: StreamState,
|
stream_state: StreamState,
|
||||||
stream_settings: StreamSettings,
|
stream_settings: StreamSettings,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -120,6 +129,7 @@ class StreamMuxer:
|
|||||||
self._input_video_stream: av.video.VideoStream = video_stream
|
self._input_video_stream: av.video.VideoStream = video_stream
|
||||||
self._input_audio_stream: av.audio.stream.AudioStream | None = audio_stream
|
self._input_audio_stream: av.audio.stream.AudioStream | None = audio_stream
|
||||||
self._audio_bsf = audio_bsf
|
self._audio_bsf = audio_bsf
|
||||||
|
self._audio_bsf_context: av.BitStreamFilterContext = None
|
||||||
self._output_video_stream: av.video.VideoStream = None
|
self._output_video_stream: av.video.VideoStream = None
|
||||||
self._output_audio_stream: av.audio.stream.AudioStream | None = None
|
self._output_audio_stream: av.audio.stream.AudioStream | None = None
|
||||||
self._segment: Segment | None = None
|
self._segment: Segment | None = None
|
||||||
@ -151,7 +161,7 @@ class StreamMuxer:
|
|||||||
**{
|
**{
|
||||||
# Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970
|
# Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970
|
||||||
# "cmaf" flag replaces several of the movflags used, but too recent to use for now
|
# "cmaf" flag replaces several of the movflags used, but too recent to use for now
|
||||||
"movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer",
|
"movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov",
|
||||||
# Sometimes the first segment begins with negative timestamps, and this setting just
|
# Sometimes the first segment begins with negative timestamps, and this setting just
|
||||||
# adjusts the timestamps in the output from that segment to start from 0. Helps from
|
# adjusts the timestamps in the output from that segment to start from 0. Helps from
|
||||||
# having to make some adjustments in test_durations
|
# having to make some adjustments in test_durations
|
||||||
@ -164,7 +174,7 @@ class StreamMuxer:
|
|||||||
# Fragment durations may exceed the 15% allowed variance but it seems ok
|
# Fragment durations may exceed the 15% allowed variance but it seems ok
|
||||||
**(
|
**(
|
||||||
{
|
{
|
||||||
"movflags": "empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer",
|
"movflags": "empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov",
|
||||||
# Create a fragment every TARGET_PART_DURATION. The data from each fragment is stored in
|
# Create a fragment every TARGET_PART_DURATION. The data from each fragment is stored in
|
||||||
# a "Part" that can be combined with the data from all the other "Part"s, plus an init
|
# a "Part" that can be combined with the data from all the other "Part"s, plus an init
|
||||||
# section, to reconstitute the data in a "Segment".
|
# section, to reconstitute the data in a "Segment".
|
||||||
@ -194,8 +204,11 @@ class StreamMuxer:
|
|||||||
# Check if audio is requested
|
# Check if audio is requested
|
||||||
output_astream = None
|
output_astream = None
|
||||||
if input_astream:
|
if input_astream:
|
||||||
|
if self._audio_bsf:
|
||||||
|
self._audio_bsf_context = self._audio_bsf.create()
|
||||||
|
self._audio_bsf_context.set_input_stream(input_astream)
|
||||||
output_astream = container.add_stream(
|
output_astream = container.add_stream(
|
||||||
template=self._audio_bsf or input_astream
|
template=self._audio_bsf_context or input_astream
|
||||||
)
|
)
|
||||||
return container, output_vstream, output_astream
|
return container, output_vstream, output_astream
|
||||||
|
|
||||||
@ -238,15 +251,29 @@ class StreamMuxer:
|
|||||||
self._part_has_keyframe |= packet.is_keyframe
|
self._part_has_keyframe |= packet.is_keyframe
|
||||||
|
|
||||||
elif packet.stream == self._input_audio_stream:
|
elif packet.stream == self._input_audio_stream:
|
||||||
if self._audio_bsf:
|
if self._audio_bsf_context:
|
||||||
self._audio_bsf.send(packet)
|
self._audio_bsf_context.send(packet)
|
||||||
while packet := self._audio_bsf.recv():
|
while packet := self._audio_bsf_context.recv():
|
||||||
packet.stream = self._output_audio_stream
|
packet.stream = self._output_audio_stream
|
||||||
self._av_output.mux(packet)
|
self._av_output.mux(packet)
|
||||||
return
|
return
|
||||||
packet.stream = self._output_audio_stream
|
packet.stream = self._output_audio_stream
|
||||||
self._av_output.mux(packet)
|
self._av_output.mux(packet)
|
||||||
|
|
||||||
|
def create_segment(self) -> None:
|
||||||
|
"""Create a segment when the moov is ready."""
|
||||||
|
self._segment = Segment(
|
||||||
|
sequence=self._stream_state.sequence,
|
||||||
|
stream_id=self._stream_state.stream_id,
|
||||||
|
init=read_init(self._memory_file),
|
||||||
|
# Fetch the latest StreamOutputs, which may have changed since the
|
||||||
|
# worker started.
|
||||||
|
stream_outputs=self._stream_state.outputs,
|
||||||
|
start_time=self._start_time,
|
||||||
|
)
|
||||||
|
self._memory_file_pos = self._memory_file.tell()
|
||||||
|
self._memory_file.seek(0, SEEK_END)
|
||||||
|
|
||||||
def check_flush_part(self, packet: av.Packet) -> None:
|
def check_flush_part(self, packet: av.Packet) -> None:
|
||||||
"""Check for and mark a part segment boundary and record its duration."""
|
"""Check for and mark a part segment boundary and record its duration."""
|
||||||
if self._memory_file_pos == self._memory_file.tell():
|
if self._memory_file_pos == self._memory_file.tell():
|
||||||
@ -254,16 +281,10 @@ class StreamMuxer:
|
|||||||
if self._segment is None:
|
if self._segment is None:
|
||||||
# We have our first non-zero byte position. This means the init has just
|
# We have our first non-zero byte position. This means the init has just
|
||||||
# been written. Create a Segment and put it to the queue of each output.
|
# been written. Create a Segment and put it to the queue of each output.
|
||||||
self._segment = Segment(
|
self.create_segment()
|
||||||
sequence=self._stream_state.sequence,
|
# When using delay_moov, the moov is not written until a moof is also ready
|
||||||
stream_id=self._stream_state.stream_id,
|
# Flush the moof
|
||||||
init=self._memory_file.getvalue(),
|
self.flush(packet, last_part=False)
|
||||||
# Fetch the latest StreamOutputs, which may have changed since the
|
|
||||||
# worker started.
|
|
||||||
stream_outputs=self._stream_state.outputs,
|
|
||||||
start_time=self._start_time,
|
|
||||||
)
|
|
||||||
self._memory_file_pos = self._memory_file.tell()
|
|
||||||
else: # These are the ends of the part segments
|
else: # These are the ends of the part segments
|
||||||
self.flush(packet, last_part=False)
|
self.flush(packet, last_part=False)
|
||||||
|
|
||||||
@ -297,6 +318,10 @@ class StreamMuxer:
|
|||||||
# Closing the av_output will write the remaining buffered data to the
|
# Closing the av_output will write the remaining buffered data to the
|
||||||
# memory_file as a new moof/mdat.
|
# memory_file as a new moof/mdat.
|
||||||
self._av_output.close()
|
self._av_output.close()
|
||||||
|
# With delay_moov, this may be the first time the file pointer has
|
||||||
|
# moved, so the segment may not yet have been created
|
||||||
|
if not self._segment:
|
||||||
|
self.create_segment()
|
||||||
elif not self._part_has_keyframe:
|
elif not self._part_has_keyframe:
|
||||||
# Parts which are not the last part or an independent part should
|
# Parts which are not the last part or an independent part should
|
||||||
# not have durations below 0.85 of the part target duration.
|
# not have durations below 0.85 of the part target duration.
|
||||||
@ -305,6 +330,9 @@ class StreamMuxer:
|
|||||||
self._part_start_dts
|
self._part_start_dts
|
||||||
+ 0.85 * self._stream_settings.part_target_duration / packet.time_base,
|
+ 0.85 * self._stream_settings.part_target_duration / packet.time_base,
|
||||||
)
|
)
|
||||||
|
# Undo dts adjustments if we don't have ll_hls
|
||||||
|
if not self._stream_settings.ll_hls:
|
||||||
|
adjusted_dts = packet.dts
|
||||||
assert self._segment
|
assert self._segment
|
||||||
self._memory_file.seek(self._memory_file_pos)
|
self._memory_file.seek(self._memory_file_pos)
|
||||||
self._hass.loop.call_soon_threadsafe(
|
self._hass.loop.call_soon_threadsafe(
|
||||||
@ -445,10 +473,7 @@ def get_audio_bitstream_filter(
|
|||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"ADTS AAC detected. Adding aac_adtstoaac bitstream filter"
|
"ADTS AAC detected. Adding aac_adtstoaac bitstream filter"
|
||||||
)
|
)
|
||||||
bsf = av.BitStreamFilter("aac_adtstoasc")
|
return av.BitStreamFilter("aac_adtstoasc")
|
||||||
bsf_context = bsf.create()
|
|
||||||
bsf_context.set_input_stream(audio_stream)
|
|
||||||
return bsf_context
|
|
||||||
break
|
break
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -489,7 +514,12 @@ def stream_worker(
|
|||||||
audio_stream = None
|
audio_stream = None
|
||||||
# Disable ll-hls for hls inputs
|
# Disable ll-hls for hls inputs
|
||||||
if container.format.name == "hls":
|
if container.format.name == "hls":
|
||||||
stream_settings.ll_hls = False
|
for field in attr.fields(StreamSettings):
|
||||||
|
setattr(
|
||||||
|
stream_settings,
|
||||||
|
field.name,
|
||||||
|
getattr(STREAM_SETTINGS_NON_LL_HLS, field.name),
|
||||||
|
)
|
||||||
stream_state.diagnostics.set_value("container_format", container.format.name)
|
stream_state.diagnostics.set_value("container_format", container.format.name)
|
||||||
stream_state.diagnostics.set_value("video_codec", video_stream.name)
|
stream_state.diagnostics.set_value("video_codec", video_stream.name)
|
||||||
if audio_stream:
|
if audio_stream:
|
||||||
|
@ -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==4.0.9", "unifi-discovery==1.1.4"],
|
"requirements": ["pyunifiprotect==4.0.10", "unifi-discovery==1.1.4"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"pyserial-asyncio==0.6",
|
"pyserial-asyncio==0.6",
|
||||||
"zha-quirks==0.0.77",
|
"zha-quirks==0.0.77",
|
||||||
"zigpy-deconz==0.18.0",
|
"zigpy-deconz==0.18.0",
|
||||||
"zigpy==0.47.2",
|
"zigpy==0.47.3",
|
||||||
"zigpy-xbee==0.15.0",
|
"zigpy-xbee==0.15.0",
|
||||||
"zigpy-zigate==0.9.0",
|
"zigpy-zigate==0.9.0",
|
||||||
"zigpy-znp==0.8.1"
|
"zigpy-znp==0.8.1"
|
||||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 7
|
MINOR_VERSION: Final = 7
|
||||||
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)
|
||||||
|
@ -69,7 +69,6 @@ DHCP: list[dict[str, str | bool]] = [
|
|||||||
{'domain': 'nest', 'macaddress': '18B430*'},
|
{'domain': 'nest', 'macaddress': '18B430*'},
|
||||||
{'domain': 'nest', 'macaddress': '641666*'},
|
{'domain': 'nest', 'macaddress': '641666*'},
|
||||||
{'domain': 'nest', 'macaddress': 'D8EB46*'},
|
{'domain': 'nest', 'macaddress': 'D8EB46*'},
|
||||||
{'domain': 'nest', 'macaddress': '1C53F9*'},
|
|
||||||
{'domain': 'nexia', 'hostname': 'xl857-*', 'macaddress': '000231*'},
|
{'domain': 'nexia', 'hostname': 'xl857-*', 'macaddress': '000231*'},
|
||||||
{'domain': 'nuheat', 'hostname': 'nuheat', 'macaddress': '002338*'},
|
{'domain': 'nuheat', 'hostname': 'nuheat', 'macaddress': '002338*'},
|
||||||
{'domain': 'nuki', 'hostname': 'nuki_bridge_*'},
|
{'domain': 'nuki', 'hostname': 'nuki_bridge_*'},
|
||||||
|
@ -15,7 +15,7 @@ ciso8601==2.2.0
|
|||||||
cryptography==36.0.2
|
cryptography==36.0.2
|
||||||
fnvhash==0.1.0
|
fnvhash==0.1.0
|
||||||
hass-nabucasa==0.54.1
|
hass-nabucasa==0.54.1
|
||||||
home-assistant-frontend==20220707.0
|
home-assistant-frontend==20220707.1
|
||||||
httpx==0.23.0
|
httpx==0.23.0
|
||||||
ifaddr==0.1.7
|
ifaddr==0.1.7
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
@ -118,3 +118,7 @@ pydantic!=1.9.1
|
|||||||
# Breaks asyncio
|
# Breaks asyncio
|
||||||
# https://github.com/pubnub/python/issues/130
|
# https://github.com/pubnub/python/issues/130
|
||||||
pubnub!=6.4.0
|
pubnub!=6.4.0
|
||||||
|
|
||||||
|
# Package's __init__.pyi stub has invalid syntax and breaks mypy
|
||||||
|
# https://github.com/dahlia/iso4217/issues/16
|
||||||
|
iso4217!=1.10.20220401
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2022.7.4"
|
version = "2022.7.5"
|
||||||
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"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
AEMET-OpenData==0.2.1
|
AEMET-OpenData==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.aladdin_connect
|
# homeassistant.components.aladdin_connect
|
||||||
AIOAladdinConnect==0.1.23
|
AIOAladdinConnect==0.1.25
|
||||||
|
|
||||||
# homeassistant.components.adax
|
# homeassistant.components.adax
|
||||||
Adax-local==0.1.4
|
Adax-local==0.1.4
|
||||||
@ -402,7 +402,7 @@ bimmer_connected==0.9.6
|
|||||||
bizkaibus==0.1.1
|
bizkaibus==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.blebox
|
# homeassistant.components.blebox
|
||||||
blebox_uniapi==2.0.0
|
blebox_uniapi==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.19.0
|
blinkpy==0.19.0
|
||||||
@ -828,7 +828,7 @@ hole==0.7.0
|
|||||||
holidays==0.14.2
|
holidays==0.14.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20220707.0
|
home-assistant-frontend==20220707.1
|
||||||
|
|
||||||
# homeassistant.components.home_connect
|
# homeassistant.components.home_connect
|
||||||
homeconnect==0.7.1
|
homeconnect==0.7.1
|
||||||
@ -1625,7 +1625,7 @@ pylibrespot-java==0.1.0
|
|||||||
pylitejet==0.3.0
|
pylitejet==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.litterrobot
|
# homeassistant.components.litterrobot
|
||||||
pylitterbot==2022.3.0
|
pylitterbot==2022.7.0
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.13.1
|
pylutron-caseta==0.13.1
|
||||||
@ -1993,7 +1993,7 @@ pytrafikverket==0.2.0.1
|
|||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==4.0.9
|
pyunifiprotect==4.0.10
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -2519,7 +2519,7 @@ zigpy-zigate==0.9.0
|
|||||||
zigpy-znp==0.8.1
|
zigpy-znp==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.47.2
|
zigpy==0.47.3
|
||||||
|
|
||||||
# homeassistant.components.zoneminder
|
# homeassistant.components.zoneminder
|
||||||
zm-py==0.5.2
|
zm-py==0.5.2
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
AEMET-OpenData==0.2.1
|
AEMET-OpenData==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.aladdin_connect
|
# homeassistant.components.aladdin_connect
|
||||||
AIOAladdinConnect==0.1.23
|
AIOAladdinConnect==0.1.25
|
||||||
|
|
||||||
# homeassistant.components.adax
|
# homeassistant.components.adax
|
||||||
Adax-local==0.1.4
|
Adax-local==0.1.4
|
||||||
@ -314,7 +314,7 @@ bellows==0.31.1
|
|||||||
bimmer_connected==0.9.6
|
bimmer_connected==0.9.6
|
||||||
|
|
||||||
# homeassistant.components.blebox
|
# homeassistant.components.blebox
|
||||||
blebox_uniapi==2.0.0
|
blebox_uniapi==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.19.0
|
blinkpy==0.19.0
|
||||||
@ -595,7 +595,7 @@ hole==0.7.0
|
|||||||
holidays==0.14.2
|
holidays==0.14.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20220707.0
|
home-assistant-frontend==20220707.1
|
||||||
|
|
||||||
# homeassistant.components.home_connect
|
# homeassistant.components.home_connect
|
||||||
homeconnect==0.7.1
|
homeconnect==0.7.1
|
||||||
@ -1101,7 +1101,7 @@ pylibrespot-java==0.1.0
|
|||||||
pylitejet==0.3.0
|
pylitejet==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.litterrobot
|
# homeassistant.components.litterrobot
|
||||||
pylitterbot==2022.3.0
|
pylitterbot==2022.7.0
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.13.1
|
pylutron-caseta==0.13.1
|
||||||
@ -1331,7 +1331,7 @@ pytrafikverket==0.2.0.1
|
|||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==4.0.9
|
pyunifiprotect==4.0.10
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -1680,7 +1680,7 @@ zigpy-zigate==0.9.0
|
|||||||
zigpy-znp==0.8.1
|
zigpy-znp==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.47.2
|
zigpy==0.47.3
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.39.0
|
zwave-js-server-python==0.39.0
|
||||||
|
@ -136,6 +136,10 @@ pydantic!=1.9.1
|
|||||||
# Breaks asyncio
|
# Breaks asyncio
|
||||||
# https://github.com/pubnub/python/issues/130
|
# https://github.com/pubnub/python/issues/130
|
||||||
pubnub!=6.4.0
|
pubnub!=6.4.0
|
||||||
|
|
||||||
|
# Package's __init__.pyi stub has invalid syntax and breaks mypy
|
||||||
|
# https://github.com/dahlia/iso4217/issues/16
|
||||||
|
iso4217!=1.10.20220401
|
||||||
"""
|
"""
|
||||||
|
|
||||||
IGNORE_PRE_COMMIT_HOOK_ID = (
|
IGNORE_PRE_COMMIT_HOOK_ID = (
|
||||||
|
@ -346,7 +346,11 @@ async def test_doorbell_event(hass, aioclient_mock):
|
|||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"binary_sensor.test_doorbell",
|
"binary_sensor.test_doorbell",
|
||||||
"off",
|
"off",
|
||||||
{"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"},
|
{
|
||||||
|
"friendly_name": "Test Doorbell Sensor",
|
||||||
|
"device_class": "occupancy",
|
||||||
|
"linkquality": 42,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
await state_report.async_enable_proactive_mode(hass, get_default_config(hass))
|
await state_report.async_enable_proactive_mode(hass, get_default_config(hass))
|
||||||
@ -354,7 +358,21 @@ async def test_doorbell_event(hass, aioclient_mock):
|
|||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"binary_sensor.test_doorbell",
|
"binary_sensor.test_doorbell",
|
||||||
"on",
|
"on",
|
||||||
{"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"},
|
{
|
||||||
|
"friendly_name": "Test Doorbell Sensor",
|
||||||
|
"device_class": "occupancy",
|
||||||
|
"linkquality": 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
"binary_sensor.test_doorbell",
|
||||||
|
"on",
|
||||||
|
{
|
||||||
|
"friendly_name": "Test Doorbell Sensor",
|
||||||
|
"device_class": "occupancy",
|
||||||
|
"linkquality": 99,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# To trigger event listener
|
# To trigger event listener
|
||||||
@ -386,6 +404,34 @@ async def test_doorbell_event(hass, aioclient_mock):
|
|||||||
assert len(aioclient_mock.mock_calls) == 2
|
assert len(aioclient_mock.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_doorbell_event_from_unknown(hass, aioclient_mock):
|
||||||
|
"""Test doorbell press reports."""
|
||||||
|
aioclient_mock.post(TEST_URL, text="", status=202)
|
||||||
|
|
||||||
|
await state_report.async_enable_proactive_mode(hass, get_default_config(hass))
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
"binary_sensor.test_doorbell",
|
||||||
|
"on",
|
||||||
|
{
|
||||||
|
"friendly_name": "Test Doorbell Sensor",
|
||||||
|
"device_class": "occupancy",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# To trigger event listener
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(aioclient_mock.mock_calls) == 1
|
||||||
|
call = aioclient_mock.mock_calls
|
||||||
|
|
||||||
|
call_json = call[0][2]
|
||||||
|
assert call_json["event"]["header"]["namespace"] == "Alexa.DoorbellEventSource"
|
||||||
|
assert call_json["event"]["header"]["name"] == "DoorbellPress"
|
||||||
|
assert call_json["event"]["payload"]["cause"]["type"] == "PHYSICAL_INTERACTION"
|
||||||
|
assert call_json["event"]["endpoint"]["endpointId"] == "binary_sensor#test_doorbell"
|
||||||
|
|
||||||
|
|
||||||
async def test_doorbell_event_fail(hass, aioclient_mock, caplog):
|
async def test_doorbell_event_fail(hass, aioclient_mock, caplog):
|
||||||
"""Test proactive state retries once."""
|
"""Test proactive state retries once."""
|
||||||
aioclient_mock.post(
|
aioclient_mock.post(
|
||||||
|
@ -7,6 +7,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_EFFECT,
|
||||||
ATTR_RGBW_COLOR,
|
ATTR_RGBW_COLOR,
|
||||||
ATTR_SUPPORTED_COLOR_MODES,
|
ATTR_SUPPORTED_COLOR_MODES,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
@ -509,17 +510,63 @@ async def test_turn_on_failure(feature, hass, config, caplog):
|
|||||||
caplog.set_level(logging.ERROR)
|
caplog.set_level(logging.ERROR)
|
||||||
|
|
||||||
feature_mock, entity_id = feature
|
feature_mock, entity_id = feature
|
||||||
feature_mock.async_on = AsyncMock(side_effect=blebox_uniapi.error.BadOnValueError)
|
feature_mock.async_on = AsyncMock(side_effect=ValueError)
|
||||||
await async_setup_entity(hass, config, entity_id)
|
await async_setup_entity(hass, config, entity_id)
|
||||||
|
|
||||||
feature_mock.sensible_on_value = 123
|
feature_mock.sensible_on_value = 123
|
||||||
|
with pytest.raises(ValueError) as info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{"entity_id": entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert f"Turning on '{feature_mock.full_name}' failed: Bad value 123" in str(
|
||||||
|
info.value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_wlightbox_on_effect(wlightbox, hass, config):
|
||||||
|
"""Test light on."""
|
||||||
|
|
||||||
|
feature_mock, entity_id = wlightbox
|
||||||
|
|
||||||
|
def initial_update():
|
||||||
|
feature_mock.is_on = False
|
||||||
|
|
||||||
|
feature_mock.async_update = AsyncMock(side_effect=initial_update)
|
||||||
|
await async_setup_entity(hass, config, entity_id)
|
||||||
|
feature_mock.async_update = AsyncMock()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
def turn_on(value):
|
||||||
|
feature_mock.is_on = True
|
||||||
|
feature_mock.effect = "POLICE"
|
||||||
|
|
||||||
|
feature_mock.async_on = AsyncMock(side_effect=turn_on)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{"entity_id": entity_id, ATTR_EFFECT: "NOT IN LIST"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
f"Turning on with effect '{feature_mock.full_name}' failed: NOT IN LIST not in effect list."
|
||||||
|
in str(info.value)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"light",
|
"light",
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{"entity_id": entity_id},
|
{"entity_id": entity_id, ATTR_EFFECT: "POLICE"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
state = hass.states.get(entity_id)
|
||||||
f"Turning on '{feature_mock.full_name}' failed: Bad value 123 ()" in caplog.text
|
assert state.attributes[ATTR_EFFECT] == "POLICE"
|
||||||
)
|
|
||||||
|
@ -749,7 +749,9 @@ async def test_durations(hass, worker_finished_stream):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
source = generate_h264_video(duration=SEGMENT_DURATION + 1)
|
source = generate_h264_video(
|
||||||
|
duration=round(SEGMENT_DURATION + target_part_duration + 1)
|
||||||
|
)
|
||||||
worker_finished, mock_stream = worker_finished_stream
|
worker_finished, mock_stream = worker_finished_stream
|
||||||
|
|
||||||
with patch("homeassistant.components.stream.Stream", wraps=mock_stream):
|
with patch("homeassistant.components.stream.Stream", wraps=mock_stream):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user