mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge pull request #36070 from home-assistant/rc
This commit is contained in:
commit
f897264c7f
@ -434,7 +434,13 @@ omit =
|
|||||||
homeassistant/components/luftdaten/*
|
homeassistant/components/luftdaten/*
|
||||||
homeassistant/components/lupusec/*
|
homeassistant/components/lupusec/*
|
||||||
homeassistant/components/lutron/*
|
homeassistant/components/lutron/*
|
||||||
homeassistant/components/lutron_caseta/*
|
homeassistant/components/lutron_caseta/__init__.py
|
||||||
|
homeassistant/components/lutron_caseta/binary_sensor.py
|
||||||
|
homeassistant/components/lutron_caseta/cover.py
|
||||||
|
homeassistant/components/lutron_caseta/fan.py
|
||||||
|
homeassistant/components/lutron_caseta/light.py
|
||||||
|
homeassistant/components/lutron_caseta/scene.py
|
||||||
|
homeassistant/components/lutron_caseta/switch.py
|
||||||
homeassistant/components/lw12wifi/light.py
|
homeassistant/components/lw12wifi/light.py
|
||||||
homeassistant/components/lyft/sensor.py
|
homeassistant/components/lyft/sensor.py
|
||||||
homeassistant/components/magicseaweed/sensor.py
|
homeassistant/components/magicseaweed/sensor.py
|
||||||
@ -727,6 +733,7 @@ omit =
|
|||||||
homeassistant/components/steam_online/sensor.py
|
homeassistant/components/steam_online/sensor.py
|
||||||
homeassistant/components/stiebel_eltron/*
|
homeassistant/components/stiebel_eltron/*
|
||||||
homeassistant/components/stookalert/*
|
homeassistant/components/stookalert/*
|
||||||
|
homeassistant/components/stream/*
|
||||||
homeassistant/components/streamlabswater/*
|
homeassistant/components/streamlabswater/*
|
||||||
homeassistant/components/suez_water/*
|
homeassistant/components/suez_water/*
|
||||||
homeassistant/components/supervisord/sensor.py
|
homeassistant/components/supervisord/sensor.py
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""The airvisual component."""
|
"""The airvisual component."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
from pyairvisual import Client
|
from pyairvisual import Client
|
||||||
from pyairvisual.errors import AirVisualError, NodeProError
|
from pyairvisual.errors import AirVisualError, NodeProError
|
||||||
@ -37,7 +38,6 @@ from .const import (
|
|||||||
PLATFORMS = ["air_quality", "sensor"]
|
PLATFORMS = ["air_quality", "sensor"]
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
||||||
DEFAULT_GEOGRAPHY_SCAN_INTERVAL = timedelta(minutes=10)
|
|
||||||
DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1)
|
DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1)
|
||||||
DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True}
|
DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True}
|
||||||
|
|
||||||
@ -88,6 +88,37 @@ def async_get_geography_id(geography_dict):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_get_cloud_api_update_interval(hass, api_key):
|
||||||
|
"""Get a leveled scan interval for a particular cloud API key.
|
||||||
|
|
||||||
|
This will shift based on the number of active consumers, thus keeping the user
|
||||||
|
under the monthly API limit.
|
||||||
|
"""
|
||||||
|
num_consumers = len(
|
||||||
|
{
|
||||||
|
config_entry
|
||||||
|
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
||||||
|
if config_entry.data.get(CONF_API_KEY) == api_key
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assuming 10,000 calls per month and a "smallest possible month" of 28 days; note
|
||||||
|
# that we give a buffer of 1500 API calls for any drift, restarts, etc.:
|
||||||
|
minutes_between_api_calls = ceil(1 / (8500 / 28 / 24 / 60 / num_consumers))
|
||||||
|
return timedelta(minutes=minutes_between_api_calls)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_reset_coordinator_update_intervals(hass, update_interval):
|
||||||
|
"""Update any existing data coordinators with a new update interval."""
|
||||||
|
if not hass.data[DOMAIN][DATA_COORDINATOR]:
|
||||||
|
return
|
||||||
|
|
||||||
|
for coordinator in hass.data[DOMAIN][DATA_COORDINATOR].values():
|
||||||
|
coordinator.update_interval = update_interval
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the AirVisual component."""
|
"""Set up the AirVisual component."""
|
||||||
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
|
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
|
||||||
@ -163,6 +194,10 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
|
|
||||||
client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession)
|
client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession)
|
||||||
|
|
||||||
|
update_interval = async_get_cloud_api_update_interval(
|
||||||
|
hass, config_entry.data[CONF_API_KEY]
|
||||||
|
)
|
||||||
|
|
||||||
async def async_update_data():
|
async def async_update_data():
|
||||||
"""Get new data from the API."""
|
"""Get new data from the API."""
|
||||||
if CONF_CITY in config_entry.data:
|
if CONF_CITY in config_entry.data:
|
||||||
@ -185,10 +220,14 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass,
|
hass,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
name="geography data",
|
name="geography data",
|
||||||
update_interval=DEFAULT_GEOGRAPHY_SCAN_INTERVAL,
|
update_interval=update_interval,
|
||||||
update_method=async_update_data,
|
update_method=async_update_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ensure any other, existing config entries that use this API key are updated
|
||||||
|
# with the new scan interval:
|
||||||
|
async_reset_coordinator_update_intervals(hass, update_interval)
|
||||||
|
|
||||||
# Only geography-based entries have options:
|
# Only geography-based entries have options:
|
||||||
config_entry.add_update_listener(async_update_options)
|
config_entry.add_update_listener(async_update_options)
|
||||||
else:
|
else:
|
||||||
|
@ -6,7 +6,7 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from broadlink.exceptions import BroadlinkException, ReadError
|
from broadlink.exceptions import BroadlinkException, ReadError, StorageError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
@ -85,10 +85,11 @@ async def async_setup_service(hass, host, device):
|
|||||||
_LOGGER.info("Press the key you want Home Assistant to learn")
|
_LOGGER.info("Press the key you want Home Assistant to learn")
|
||||||
start_time = utcnow()
|
start_time = utcnow()
|
||||||
while (utcnow() - start_time) < timedelta(seconds=20):
|
while (utcnow() - start_time) < timedelta(seconds=20):
|
||||||
|
await asyncio.sleep(1)
|
||||||
try:
|
try:
|
||||||
packet = await device.async_request(device.api.check_data)
|
packet = await device.async_request(device.api.check_data)
|
||||||
except ReadError:
|
except (ReadError, StorageError):
|
||||||
await asyncio.sleep(1)
|
continue
|
||||||
except BroadlinkException as err_msg:
|
except BroadlinkException as err_msg:
|
||||||
_LOGGER.error("Failed to learn: %s", err_msg)
|
_LOGGER.error("Failed to learn: %s", err_msg)
|
||||||
return
|
return
|
||||||
|
@ -14,6 +14,7 @@ from broadlink.exceptions import (
|
|||||||
BroadlinkException,
|
BroadlinkException,
|
||||||
DeviceOfflineError,
|
DeviceOfflineError,
|
||||||
ReadError,
|
ReadError,
|
||||||
|
StorageError,
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -321,10 +322,11 @@ class BroadlinkRemote(RemoteEntity):
|
|||||||
code = None
|
code = None
|
||||||
start_time = utcnow()
|
start_time = utcnow()
|
||||||
while (utcnow() - start_time) < timedelta(seconds=timeout):
|
while (utcnow() - start_time) < timedelta(seconds=timeout):
|
||||||
|
await asyncio.sleep(1)
|
||||||
try:
|
try:
|
||||||
code = await self.device.async_request(self.device.api.check_data)
|
code = await self.device.async_request(self.device.api.check_data)
|
||||||
except ReadError:
|
except (ReadError, StorageError):
|
||||||
await asyncio.sleep(1)
|
continue
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "cloud",
|
"domain": "cloud",
|
||||||
"name": "Home Assistant Cloud",
|
"name": "Home Assistant Cloud",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
||||||
"requirements": ["hass-nabucasa==0.34.2"],
|
"requirements": ["hass-nabucasa==0.34.3"],
|
||||||
"dependencies": ["http", "webhook", "alexa"],
|
"dependencies": ["http", "webhook", "alexa"],
|
||||||
"after_dependencies": ["google_assistant"],
|
"after_dependencies": ["google_assistant"],
|
||||||
"codeowners": ["@home-assistant/cloud"]
|
"codeowners": ["@home-assistant/cloud"]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Daikin AC",
|
"name": "Daikin AC",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
||||||
"requirements": ["pydaikin==2.0.2"],
|
"requirements": ["pydaikin==2.0.4"],
|
||||||
"codeowners": ["@fredrike"],
|
"codeowners": ["@fredrike"],
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
}
|
}
|
||||||
|
@ -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==20200519.1"],
|
"requirements": ["home-assistant-frontend==20200519.4"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
"auth",
|
"auth",
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from turbojpeg import TurboJPEG
|
|
||||||
|
|
||||||
SUPPORTED_SCALING_FACTORS = [(7, 8), (3, 4), (5, 8), (1, 2), (3, 8), (1, 4), (1, 8)]
|
SUPPORTED_SCALING_FACTORS = [(7, 8), (3, 4), (5, 8), (1, 2), (3, 8), (1, 4), (1, 8)]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -54,6 +52,12 @@ class TurboJPEGSingleton:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Try to create TurboJPEG only once."""
|
"""Try to create TurboJPEG only once."""
|
||||||
try:
|
try:
|
||||||
|
# TurboJPEG checks for libturbojpeg
|
||||||
|
# when its created, but it imports
|
||||||
|
# numpy which may or may not work so
|
||||||
|
# we have to guard the import here.
|
||||||
|
from turbojpeg import TurboJPEG # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
TurboJPEGSingleton.__instance = TurboJPEG()
|
TurboJPEGSingleton.__instance = TurboJPEG()
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
|
@ -89,10 +89,6 @@ class HKDevice:
|
|||||||
# mapped to a HA entity.
|
# mapped to a HA entity.
|
||||||
self.entities = []
|
self.entities = []
|
||||||
|
|
||||||
# There are multiple entities sharing a single connection - only
|
|
||||||
# allow one entity to use pairing at once.
|
|
||||||
self.pairing_lock = asyncio.Lock()
|
|
||||||
|
|
||||||
self.available = True
|
self.available = True
|
||||||
|
|
||||||
self.signal_state_updated = "_".join((DOMAIN, self.unique_id, "state_updated"))
|
self.signal_state_updated = "_".join((DOMAIN, self.unique_id, "state_updated"))
|
||||||
@ -333,13 +329,11 @@ class HKDevice:
|
|||||||
|
|
||||||
async def get_characteristics(self, *args, **kwargs):
|
async def get_characteristics(self, *args, **kwargs):
|
||||||
"""Read latest state from homekit accessory."""
|
"""Read latest state from homekit accessory."""
|
||||||
async with self.pairing_lock:
|
return await self.pairing.get_characteristics(*args, **kwargs)
|
||||||
return await self.pairing.get_characteristics(*args, **kwargs)
|
|
||||||
|
|
||||||
async def put_characteristics(self, characteristics):
|
async def put_characteristics(self, characteristics):
|
||||||
"""Control a HomeKit device state from Home Assistant."""
|
"""Control a HomeKit device state from Home Assistant."""
|
||||||
async with self.pairing_lock:
|
results = await self.pairing.put_characteristics(characteristics)
|
||||||
results = await self.pairing.put_characteristics(characteristics)
|
|
||||||
|
|
||||||
# Feed characteristics back into HA and update the current state
|
# Feed characteristics back into HA and update the current state
|
||||||
# results will only contain failures, so anythin in characteristics
|
# results will only contain failures, so anythin in characteristics
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "HomeKit Controller",
|
"name": "HomeKit Controller",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"requirements": ["aiohomekit[IP]==0.2.37"],
|
"requirements": ["aiohomekit[IP]==0.2.38"],
|
||||||
"zeroconf": ["_hap._tcp.local."],
|
"zeroconf": ["_hap._tcp.local."],
|
||||||
"codeowners": ["@Jc2k"]
|
"codeowners": ["@Jc2k"]
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,12 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
# Forced refresh is not required for setup
|
# Forced refresh is not required for setup
|
||||||
pass
|
pass
|
||||||
|
if ATTR_POSITION_DATA not in shade.raw_data:
|
||||||
|
_LOGGER.info(
|
||||||
|
"The %s shade was skipped because it is missing position data",
|
||||||
|
name_before_refresh,
|
||||||
|
)
|
||||||
|
continue
|
||||||
entities.append(
|
entities.append(
|
||||||
PowerViewShade(
|
PowerViewShade(
|
||||||
shade, name_before_refresh, room_data, coordinator, device_info
|
shade, name_before_refresh, room_data, coordinator, device_info
|
||||||
|
@ -74,6 +74,17 @@ class ShadeEntity(HDEntity):
|
|||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return the device_info of the device."""
|
"""Return the device_info of the device."""
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
"identifiers": {(DOMAIN, self._shade.id)},
|
||||||
|
"name": self._shade_name,
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if FIRMWARE_IN_SHADE not in self._shade.raw_data:
|
||||||
|
return device_info
|
||||||
|
|
||||||
firmware = self._shade.raw_data[FIRMWARE_IN_SHADE]
|
firmware = self._shade.raw_data[FIRMWARE_IN_SHADE]
|
||||||
sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}"
|
sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}"
|
||||||
model = self._shade.raw_data[ATTR_TYPE]
|
model = self._shade.raw_data[ATTR_TYPE]
|
||||||
@ -82,11 +93,6 @@ class ShadeEntity(HDEntity):
|
|||||||
model = shade.description
|
model = shade.description
|
||||||
break
|
break
|
||||||
|
|
||||||
return {
|
device_info["sw_version"] = sw_version
|
||||||
"identifiers": {(DOMAIN, self._shade.id)},
|
device_info["model"] = model
|
||||||
"name": self._shade_name,
|
return device_info
|
||||||
"model": str(model),
|
|
||||||
"sw_version": sw_version,
|
|
||||||
"manufacturer": MANUFACTURER,
|
|
||||||
"via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]),
|
|
||||||
}
|
|
||||||
|
@ -85,12 +85,12 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
unique_id = user_input[CONF_UUID] = info[CONF_UUID]
|
unique_id = user_input[CONF_UUID] = info[CONF_UUID]
|
||||||
|
|
||||||
if unique_id is None and info[CONF_SERIAL] is not None:
|
if not unique_id and info[CONF_SERIAL]:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Printer UUID is missing from IPP response. Falling back to IPP serial number"
|
"Printer UUID is missing from IPP response. Falling back to IPP serial number"
|
||||||
)
|
)
|
||||||
unique_id = info[CONF_SERIAL]
|
unique_id = info[CONF_SERIAL]
|
||||||
elif unique_id is None:
|
elif not unique_id:
|
||||||
_LOGGER.debug("Unable to determine unique id from IPP response")
|
_LOGGER.debug("Unable to determine unique id from IPP response")
|
||||||
|
|
||||||
await self.async_set_unique_id(unique_id)
|
await self.async_set_unique_id(unique_id)
|
||||||
@ -138,17 +138,17 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_abort(reason="ipp_error")
|
return self.async_abort(reason="ipp_error")
|
||||||
|
|
||||||
unique_id = self.discovery_info[CONF_UUID]
|
unique_id = self.discovery_info[CONF_UUID]
|
||||||
if unique_id is None and info[CONF_UUID] is not None:
|
if not unique_id and info[CONF_UUID]:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Printer UUID is missing from discovery info. Falling back to IPP UUID"
|
"Printer UUID is missing from discovery info. Falling back to IPP UUID"
|
||||||
)
|
)
|
||||||
unique_id = self.discovery_info[CONF_UUID] = info[CONF_UUID]
|
unique_id = self.discovery_info[CONF_UUID] = info[CONF_UUID]
|
||||||
elif unique_id is None and info[CONF_SERIAL] is not None:
|
elif not unique_id and info[CONF_SERIAL]:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Printer UUID is missing from discovery info and IPP response. Falling back to IPP serial number"
|
"Printer UUID is missing from discovery info and IPP response. Falling back to IPP serial number"
|
||||||
)
|
)
|
||||||
unique_id = info[CONF_SERIAL]
|
unique_id = info[CONF_SERIAL]
|
||||||
elif unique_id is None:
|
elif not unique_id:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to determine unique id from discovery info and IPP response"
|
"Unable to determine unique id from discovery info and IPP response"
|
||||||
)
|
)
|
||||||
|
@ -39,11 +39,7 @@ LUTRON_CASETA_COMPONENTS = ["light", "switch", "cover", "scene", "fan", "binary_
|
|||||||
async def async_setup(hass, base_config):
|
async def async_setup(hass, base_config):
|
||||||
"""Set up the Lutron component."""
|
"""Set up the Lutron component."""
|
||||||
|
|
||||||
bridge_configs = base_config.get(DOMAIN)
|
bridge_configs = base_config[DOMAIN]
|
||||||
|
|
||||||
if not bridge_configs:
|
|
||||||
return True
|
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
for config in bridge_configs:
|
for config in bridge_configs:
|
||||||
|
@ -94,11 +94,6 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
await bridge.close()
|
await bridge.close()
|
||||||
return True
|
return True
|
||||||
except (KeyError, ValueError):
|
|
||||||
_LOGGER.error(
|
|
||||||
"Error while checking connectivity to bridge %s", self.data[CONF_HOST],
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Unknown exception while checking connectivity to bridge %s",
|
"Unknown exception while checking connectivity to bridge %s",
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
"domain": "lutron_caseta",
|
"domain": "lutron_caseta",
|
||||||
"name": "Lutron Caséta",
|
"name": "Lutron Caséta",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
|
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
|
||||||
"requirements": ["pylutron-caseta==0.6.1"],
|
"requirements": [
|
||||||
"codeowners": ["@swails"],
|
"pylutron-caseta==0.6.1"
|
||||||
"config_flow": true
|
],
|
||||||
}
|
"codeowners": [
|
||||||
|
"@swails"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -82,13 +82,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
|
|
||||||
if device.capabilities.events and await device.events.async_start():
|
if device.capabilities.events and await device.events.async_start():
|
||||||
platforms += ["binary_sensor", "sensor"]
|
platforms += ["binary_sensor", "sensor"]
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.events.async_stop)
|
|
||||||
|
|
||||||
for component in platforms:
|
for component in platforms:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.async_stop)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,7 +219,8 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
media_service = device.create_media_service()
|
media_service = device.create_media_service()
|
||||||
profiles = await media_service.GetProfiles()
|
profiles = await media_service.GetProfiles()
|
||||||
h264 = any(
|
h264 = any(
|
||||||
profile.VideoEncoderConfiguration.Encoding == "H264"
|
profile.VideoEncoderConfiguration
|
||||||
|
and profile.VideoEncoderConfiguration.Encoding == "H264"
|
||||||
for profile in profiles
|
for profile in profiles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ from aiohttp.client_exceptions import ClientConnectionError, ServerDisconnectedE
|
|||||||
import onvif
|
import onvif
|
||||||
from onvif import ONVIFCamera
|
from onvif import ONVIFCamera
|
||||||
from onvif.exceptions import ONVIFError
|
from onvif.exceptions import ONVIFError
|
||||||
from zeep.asyncio import AsyncTransport
|
|
||||||
from zeep.exceptions import Fault
|
from zeep.exceptions import Fault
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -20,7 +19,6 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -141,6 +139,12 @@ class ONVIFDevice:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def async_stop(self, event=None):
|
||||||
|
"""Shut it all down."""
|
||||||
|
if self.events:
|
||||||
|
await self.events.async_stop()
|
||||||
|
await self.device.close()
|
||||||
|
|
||||||
async def async_check_date_and_time(self) -> None:
|
async def async_check_date_and_time(self) -> None:
|
||||||
"""Warns if device and system date not synced."""
|
"""Warns if device and system date not synced."""
|
||||||
LOGGER.debug("Setting up the ONVIF device management service")
|
LOGGER.debug("Setting up the ONVIF device management service")
|
||||||
@ -251,7 +255,10 @@ class ONVIFDevice:
|
|||||||
profiles = []
|
profiles = []
|
||||||
for key, onvif_profile in enumerate(result):
|
for key, onvif_profile in enumerate(result):
|
||||||
# Only add H264 profiles
|
# Only add H264 profiles
|
||||||
if onvif_profile.VideoEncoderConfiguration.Encoding != "H264":
|
if (
|
||||||
|
not onvif_profile.VideoEncoderConfiguration
|
||||||
|
or onvif_profile.VideoEncoderConfiguration.Encoding != "H264"
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
profile = Profile(
|
profile = Profile(
|
||||||
@ -278,9 +285,13 @@ class ONVIFDevice:
|
|||||||
is not None,
|
is not None,
|
||||||
)
|
)
|
||||||
|
|
||||||
ptz_service = self.device.get_service("ptz")
|
try:
|
||||||
presets = await ptz_service.GetPresets(profile.token)
|
ptz_service = self.device.create_ptz_service()
|
||||||
profile.ptz.presets = [preset.token for preset in presets]
|
presets = await ptz_service.GetPresets(profile.token)
|
||||||
|
profile.ptz.presets = [preset.token for preset in presets]
|
||||||
|
except (Fault, ServerDisconnectedError):
|
||||||
|
# It's OK if Presets aren't supported
|
||||||
|
profile.ptz.presets = []
|
||||||
|
|
||||||
profiles.append(profile)
|
profiles.append(profile)
|
||||||
|
|
||||||
@ -326,7 +337,7 @@ class ONVIFDevice:
|
|||||||
LOGGER.warning("PTZ actions are not supported on device '%s'", self.name)
|
LOGGER.warning("PTZ actions are not supported on device '%s'", self.name)
|
||||||
return
|
return
|
||||||
|
|
||||||
ptz_service = self.device.get_service("ptz")
|
ptz_service = self.device.create_ptz_service()
|
||||||
|
|
||||||
pan_val = distance * PAN_FACTOR.get(pan, 0)
|
pan_val = distance * PAN_FACTOR.get(pan, 0)
|
||||||
tilt_val = distance * TILT_FACTOR.get(tilt, 0)
|
tilt_val = distance * TILT_FACTOR.get(tilt, 0)
|
||||||
@ -423,13 +434,11 @@ class ONVIFDevice:
|
|||||||
|
|
||||||
def get_device(hass, host, port, username, password) -> ONVIFCamera:
|
def get_device(hass, host, port, username, password) -> ONVIFCamera:
|
||||||
"""Get ONVIFCamera instance."""
|
"""Get ONVIFCamera instance."""
|
||||||
session = async_get_clientsession(hass)
|
|
||||||
transport = AsyncTransport(None, session=session)
|
|
||||||
return ONVIFCamera(
|
return ONVIFCamera(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
f"{os.path.dirname(onvif.__file__)}/wsdl/",
|
f"{os.path.dirname(onvif.__file__)}/wsdl/",
|
||||||
transport=transport,
|
no_cache=True,
|
||||||
)
|
)
|
||||||
|
@ -91,7 +91,7 @@ class EventManager:
|
|||||||
|
|
||||||
return self.started
|
return self.started
|
||||||
|
|
||||||
async def async_stop(self, event=None) -> None:
|
async def async_stop(self) -> None:
|
||||||
"""Unsubscribe from events."""
|
"""Unsubscribe from events."""
|
||||||
if not self._subscription:
|
if not self._subscription:
|
||||||
return
|
return
|
||||||
@ -110,7 +110,7 @@ class EventManager:
|
|||||||
async def async_pull_messages(self, _now: dt = None) -> None:
|
async def async_pull_messages(self, _now: dt = None) -> None:
|
||||||
"""Pull messages from device."""
|
"""Pull messages from device."""
|
||||||
try:
|
try:
|
||||||
pullpoint = self.device.get_service("pullpoint")
|
pullpoint = self.device.create_pullpoint_service()
|
||||||
req = pullpoint.create_type("PullMessages")
|
req = pullpoint.create_type("PullMessages")
|
||||||
req.MessageLimit = 100
|
req.MessageLimit = 100
|
||||||
req.Timeout = dt.timedelta(seconds=60)
|
req.Timeout = dt.timedelta(seconds=60)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "onvif",
|
"domain": "onvif",
|
||||||
"name": "ONVIF",
|
"name": "ONVIF",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
||||||
"requirements": ["onvif-zeep-async==0.3.0", "WSDiscovery==2.0.0"],
|
"requirements": ["onvif-zeep-async==0.4.0", "WSDiscovery==2.0.0"],
|
||||||
"dependencies": ["ffmpeg"],
|
"dependencies": ["ffmpeg"],
|
||||||
"codeowners": ["@hunterjm"],
|
"codeowners": ["@hunterjm"],
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
|
@ -3,7 +3,15 @@
|
|||||||
"name": "OpenZWave (beta)",
|
"name": "OpenZWave (beta)",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ozw",
|
"documentation": "https://www.home-assistant.io/integrations/ozw",
|
||||||
"requirements": ["python-openzwave-mqtt==1.0.1"],
|
"requirements": [
|
||||||
"after_dependencies": ["mqtt"],
|
"python-openzwave-mqtt==1.0.2"
|
||||||
"codeowners": ["@cgarwood", "@marcelveldt", "@MartinHjelmare"]
|
],
|
||||||
|
"after_dependencies": [
|
||||||
|
"mqtt"
|
||||||
|
],
|
||||||
|
"codeowners": [
|
||||||
|
"@cgarwood",
|
||||||
|
"@marcelveldt",
|
||||||
|
"@MartinHjelmare"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the PrezziBenzina sensor platform."""
|
"""Set up the PrezziBenzina sensor platform."""
|
||||||
|
|
||||||
station = config[CONF_STATION]
|
station = config[CONF_STATION]
|
||||||
@ -65,7 +65,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(dev, True)
|
add_entities(dev, True)
|
||||||
|
|
||||||
|
|
||||||
class PrezziBenzinaSensor(Entity):
|
class PrezziBenzinaSensor(Entity):
|
||||||
@ -114,6 +114,6 @@ class PrezziBenzinaSensor(Entity):
|
|||||||
}
|
}
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
async def async_update(self):
|
def update(self):
|
||||||
"""Get the latest data and updates the states."""
|
"""Get the latest data and updates the states."""
|
||||||
self._data = self._client.get_by_id(self._station)[self._index]
|
self._data = self._client.get_by_id(self._station)[self._index]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "roku",
|
"domain": "roku",
|
||||||
"name": "Roku",
|
"name": "Roku",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roku",
|
"documentation": "https://www.home-assistant.io/integrations/roku",
|
||||||
"requirements": ["rokuecp==0.4.0"],
|
"requirements": ["rokuecp==0.4.1"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "roku:ecp",
|
"st": "roku:ecp",
|
||||||
|
@ -7,6 +7,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
MEDIA_TYPE_APP,
|
MEDIA_TYPE_APP,
|
||||||
MEDIA_TYPE_CHANNEL,
|
MEDIA_TYPE_CHANNEL,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
SUPPORT_PLAY_MEDIA,
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
@ -29,6 +30,7 @@ SUPPORT_ROKU = (
|
|||||||
| SUPPORT_VOLUME_STEP
|
| SUPPORT_VOLUME_STEP
|
||||||
| SUPPORT_VOLUME_MUTE
|
| SUPPORT_VOLUME_MUTE
|
||||||
| SUPPORT_SELECT_SOURCE
|
| SUPPORT_SELECT_SOURCE
|
||||||
|
| SUPPORT_PAUSE
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
| SUPPORT_PLAY_MEDIA
|
| SUPPORT_PLAY_MEDIA
|
||||||
| SUPPORT_TURN_ON
|
| SUPPORT_TURN_ON
|
||||||
@ -167,6 +169,14 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
|||||||
"""Turn off the Roku."""
|
"""Turn off the Roku."""
|
||||||
await self.coordinator.roku.remote("poweroff")
|
await self.coordinator.roku.remote("poweroff")
|
||||||
|
|
||||||
|
async def async_media_pause(self) -> None:
|
||||||
|
"""Send pause command."""
|
||||||
|
await self.coordinator.roku.remote("play")
|
||||||
|
|
||||||
|
async def async_media_play(self) -> None:
|
||||||
|
"""Send play command."""
|
||||||
|
await self.coordinator.roku.remote("play")
|
||||||
|
|
||||||
async def async_media_play_pause(self) -> None:
|
async def async_media_play_pause(self) -> None:
|
||||||
"""Send play/pause command."""
|
"""Send play/pause command."""
|
||||||
await self.coordinator.roku.remote("play")
|
await self.coordinator.roku.remote("play")
|
||||||
|
@ -17,7 +17,7 @@ from homeassistant.components.light import (
|
|||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
SUPPORT_COLOR,
|
SUPPORT_COLOR,
|
||||||
SUPPORT_TRANSITION,
|
SUPPORT_TRANSITION,
|
||||||
Light,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_NAME, CONF_TYPE, STATE_ON
|
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_NAME, CONF_TYPE, STATE_ON
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -104,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities(leds)
|
add_entities(leds)
|
||||||
|
|
||||||
|
|
||||||
class PwmSimpleLed(Light, RestoreEntity):
|
class PwmSimpleLed(LightEntity, RestoreEntity):
|
||||||
"""Representation of a simple one-color PWM LED."""
|
"""Representation of a simple one-color PWM LED."""
|
||||||
|
|
||||||
def __init__(self, led, name):
|
def __init__(self, led, name):
|
||||||
|
@ -79,8 +79,11 @@ class StreamOutput:
|
|||||||
@property
|
@property
|
||||||
def target_duration(self) -> int:
|
def target_duration(self) -> int:
|
||||||
"""Return the average duration of the segments in seconds."""
|
"""Return the average duration of the segments in seconds."""
|
||||||
|
segment_length = len(self._segments)
|
||||||
|
if not segment_length:
|
||||||
|
return 0
|
||||||
durations = [s.duration for s in self._segments]
|
durations = [s.duration for s in self._segments]
|
||||||
return round(sum(durations) // len(self._segments)) or 1
|
return round(sum(durations) // segment_length) or 1
|
||||||
|
|
||||||
def get_segment(self, sequence: int = None) -> Any:
|
def get_segment(self, sequence: int = None) -> Any:
|
||||||
"""Retrieve a specific segment, or the whole list."""
|
"""Retrieve a specific segment, or the whole list."""
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "stream",
|
"domain": "stream",
|
||||||
"name": "Stream",
|
"name": "Stream",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/stream",
|
"documentation": "https://www.home-assistant.io/integrations/stream",
|
||||||
"requirements": ["av==7.0.1"],
|
"requirements": ["av==8.0.1"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"codeowners": ["@hunterjm"],
|
"codeowners": ["@hunterjm"],
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
|
@ -164,3 +164,7 @@ def stream_worker(hass, stream, quit_event):
|
|||||||
# Assign the video packet to the new stream & mux
|
# Assign the video packet to the new stream & mux
|
||||||
packet.stream = buffer.vstream
|
packet.stream = buffer.vstream
|
||||||
buffer.output.mux(packet)
|
buffer.output.mux(packet)
|
||||||
|
|
||||||
|
# Close stream
|
||||||
|
buffer.output.close()
|
||||||
|
container.close()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Telldus Live",
|
"name": "Telldus Live",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tellduslive",
|
"documentation": "https://www.home-assistant.io/integrations/tellduslive",
|
||||||
"requirements": ["tellduslive==0.10.10"],
|
"requirements": ["tellduslive==0.10.11"],
|
||||||
"codeowners": ["@fredrike"],
|
"codeowners": ["@fredrike"],
|
||||||
"quality_scale": "gold"
|
"quality_scale": "gold"
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ def setup(hass, config):
|
|||||||
"""Set up the Zabbix component."""
|
"""Set up the Zabbix component."""
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
conf = config[DOMAIN]
|
||||||
protocol = "https" if config[CONF_SSL] else "http"
|
protocol = "https" if conf[CONF_SSL] else "http"
|
||||||
|
|
||||||
url = urljoin(f"{protocol}://{conf[CONF_HOST]}", conf[CONF_PATH])
|
url = urljoin(f"{protocol}://{conf[CONF_HOST]}", conf[CONF_PATH])
|
||||||
username = conf.get(CONF_USERNAME)
|
username = conf.get(CONF_USERNAME)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 110
|
MINOR_VERSION = 110
|
||||||
PATCH_VERSION = "1"
|
PATCH_VERSION = "2"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@ -83,7 +83,6 @@ FLOWS = [
|
|||||||
"locative",
|
"locative",
|
||||||
"logi_circle",
|
"logi_circle",
|
||||||
"luftdaten",
|
"luftdaten",
|
||||||
"lutron_caseta",
|
|
||||||
"mailgun",
|
"mailgun",
|
||||||
"melcloud",
|
"melcloud",
|
||||||
"met",
|
"met",
|
||||||
|
@ -399,17 +399,25 @@ def async_cleanup(
|
|||||||
ent_reg: "entity_registry.EntityRegistry",
|
ent_reg: "entity_registry.EntityRegistry",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Clean up device registry."""
|
"""Clean up device registry."""
|
||||||
# Find all devices that are no longer referenced in the entity registry.
|
# Find all devices that are referenced by a config_entry.
|
||||||
referenced = {entry.device_id for entry in ent_reg.entities.values()}
|
config_entry_ids = {entry.entry_id for entry in hass.config_entries.async_entries()}
|
||||||
orphan = set(dev_reg.devices) - referenced
|
references_config_entries = {
|
||||||
|
device.id
|
||||||
|
for device in dev_reg.devices.values()
|
||||||
|
for config_entry_id in device.config_entries
|
||||||
|
if config_entry_id in config_entry_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find all devices that are referenced in the entity registry.
|
||||||
|
references_entities = {entry.device_id for entry in ent_reg.entities.values()}
|
||||||
|
|
||||||
|
orphan = set(dev_reg.devices) - references_entities - references_config_entries
|
||||||
|
|
||||||
for dev_id in orphan:
|
for dev_id in orphan:
|
||||||
dev_reg.async_remove_device(dev_id)
|
dev_reg.async_remove_device(dev_id)
|
||||||
|
|
||||||
# Find all referenced config entries that no longer exist
|
# Find all referenced config entries that no longer exist
|
||||||
# This shouldn't happen but have not been able to track down the bug :(
|
# This shouldn't happen but have not been able to track down the bug :(
|
||||||
config_entry_ids = {entry.entry_id for entry in hass.config_entries.async_entries()}
|
|
||||||
|
|
||||||
for device in list(dev_reg.devices.values()):
|
for device in list(dev_reg.devices.values()):
|
||||||
for config_entry_id in device.config_entries:
|
for config_entry_id in device.config_entries:
|
||||||
if config_entry_id not in config_entry_ids:
|
if config_entry_id not in config_entry_ids:
|
||||||
|
@ -431,7 +431,8 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non
|
|||||||
|
|
||||||
# Skip entities that don't have the required feature.
|
# Skip entities that don't have the required feature.
|
||||||
if required_features is not None and not any(
|
if required_features is not None and not any(
|
||||||
entity.supported_features & feature_set for feature_set in required_features
|
entity.supported_features & feature_set == feature_set
|
||||||
|
for feature_set in required_features
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ ciso8601==2.1.3
|
|||||||
cryptography==2.9.2
|
cryptography==2.9.2
|
||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
distro==1.5.0
|
distro==1.5.0
|
||||||
hass-nabucasa==0.34.2
|
hass-nabucasa==0.34.3
|
||||||
home-assistant-frontend==20200519.1
|
home-assistant-frontend==20200519.4
|
||||||
importlib-metadata==1.6.0
|
importlib-metadata==1.6.0
|
||||||
jinja2>=2.11.1
|
jinja2>=2.11.1
|
||||||
netdisco==2.6.0
|
netdisco==2.6.0
|
||||||
|
@ -178,7 +178,7 @@ aioftp==0.12.0
|
|||||||
aioharmony==0.1.13
|
aioharmony==0.1.13
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit[IP]==0.2.37
|
aiohomekit[IP]==0.2.38
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -291,7 +291,7 @@ atenpdu==0.3.0
|
|||||||
aurorapy==0.2.6
|
aurorapy==0.2.6
|
||||||
|
|
||||||
# homeassistant.components.stream
|
# homeassistant.components.stream
|
||||||
av==7.0.1
|
av==8.0.1
|
||||||
|
|
||||||
# homeassistant.components.avea
|
# homeassistant.components.avea
|
||||||
avea==1.4
|
avea==1.4
|
||||||
@ -701,7 +701,7 @@ habitipy==0.2.0
|
|||||||
hangups==0.4.9
|
hangups==0.4.9
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.34.2
|
hass-nabucasa==0.34.3
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
hbmqtt==0.9.5
|
hbmqtt==0.9.5
|
||||||
@ -731,7 +731,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200519.1
|
home-assistant-frontend==20200519.4
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -994,7 +994,7 @@ oemthermostat==1.1
|
|||||||
onkyo-eiscp==1.2.7
|
onkyo-eiscp==1.2.7
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==0.3.0
|
onvif-zeep-async==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.1.4
|
open-garage==0.1.4
|
||||||
@ -1263,7 +1263,7 @@ pycsspeechtts==1.0.3
|
|||||||
# pycups==1.9.73
|
# pycups==1.9.73
|
||||||
|
|
||||||
# homeassistant.components.daikin
|
# homeassistant.components.daikin
|
||||||
pydaikin==2.0.2
|
pydaikin==2.0.4
|
||||||
|
|
||||||
# homeassistant.components.danfoss_air
|
# homeassistant.components.danfoss_air
|
||||||
pydanfossair==0.1.0
|
pydanfossair==0.1.0
|
||||||
@ -1711,7 +1711,7 @@ python-nest==4.1.0
|
|||||||
python-nmap==0.6.1
|
python-nmap==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt==1.0.1
|
python-openzwave-mqtt==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.qbittorrent
|
# homeassistant.components.qbittorrent
|
||||||
python-qbittorrent==0.4.1
|
python-qbittorrent==0.4.1
|
||||||
@ -1871,7 +1871,7 @@ rjpl==0.3.5
|
|||||||
rocketchat-API==0.6.1
|
rocketchat-API==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.roku
|
# homeassistant.components.roku
|
||||||
rokuecp==0.4.0
|
rokuecp==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.1
|
roombapy==1.6.1
|
||||||
@ -2063,7 +2063,7 @@ tellcore-net==0.4
|
|||||||
tellcore-py==1.1.2
|
tellcore-py==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.tellduslive
|
# homeassistant.components.tellduslive
|
||||||
tellduslive==0.10.10
|
tellduslive==0.10.11
|
||||||
|
|
||||||
# homeassistant.components.lg_soundbar
|
# homeassistant.components.lg_soundbar
|
||||||
temescal==0.1
|
temescal==0.1
|
||||||
|
@ -82,7 +82,7 @@ aiofreepybox==0.0.8
|
|||||||
aioharmony==0.1.13
|
aioharmony==0.1.13
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit[IP]==0.2.37
|
aiohomekit[IP]==0.2.38
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -138,7 +138,7 @@ arcam-fmj==0.4.4
|
|||||||
async-upnp-client==0.14.13
|
async-upnp-client==0.14.13
|
||||||
|
|
||||||
# homeassistant.components.stream
|
# homeassistant.components.stream
|
||||||
av==7.0.1
|
av==8.0.1
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==25
|
axis==25
|
||||||
@ -294,7 +294,7 @@ ha-ffmpeg==2.0
|
|||||||
hangups==0.4.9
|
hangups==0.4.9
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.34.2
|
hass-nabucasa==0.34.3
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
hbmqtt==0.9.5
|
hbmqtt==0.9.5
|
||||||
@ -312,7 +312,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200519.1
|
home-assistant-frontend==20200519.4
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -414,7 +414,7 @@ numpy==1.18.4
|
|||||||
oauth2client==4.0.0
|
oauth2client==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==0.3.0
|
onvif-zeep-async==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.1.0
|
openerz-api==0.1.0
|
||||||
@ -533,7 +533,7 @@ pychromecast==5.1.0
|
|||||||
pycoolmasternet==0.0.4
|
pycoolmasternet==0.0.4
|
||||||
|
|
||||||
# homeassistant.components.daikin
|
# homeassistant.components.daikin
|
||||||
pydaikin==2.0.2
|
pydaikin==2.0.4
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==70
|
pydeconz==70
|
||||||
@ -705,7 +705,7 @@ python-miio==0.5.0.1
|
|||||||
python-nest==4.1.0
|
python-nest==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt==1.0.1
|
python-openzwave-mqtt==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.12
|
python-songpal==0.12
|
||||||
@ -762,7 +762,7 @@ rflink==0.0.52
|
|||||||
ring_doorbell==0.6.0
|
ring_doorbell==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.roku
|
# homeassistant.components.roku
|
||||||
rokuecp==0.4.0
|
rokuecp==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.1
|
roombapy==1.6.1
|
||||||
@ -826,7 +826,7 @@ stringcase==1.2.0
|
|||||||
sunwatcher==0.2.1
|
sunwatcher==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.tellduslive
|
# homeassistant.components.tellduslive
|
||||||
tellduslive==0.10.10
|
tellduslive==0.10.11
|
||||||
|
|
||||||
# homeassistant.components.powerwall
|
# homeassistant.components.powerwall
|
||||||
tesla-powerwall==0.2.8
|
tesla-powerwall==0.2.8
|
||||||
|
@ -23,25 +23,19 @@ def test_scale_jpeg_camera_image():
|
|||||||
camera_image = Image("image/jpeg", EMPTY_16_12_JPEG)
|
camera_image = Image("image/jpeg", EMPTY_16_12_JPEG)
|
||||||
|
|
||||||
turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12)
|
turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12)
|
||||||
with patch(
|
with patch("turbojpeg.TurboJPEG", return_value=False):
|
||||||
"homeassistant.components.homekit.img_util.TurboJPEG", return_value=False
|
|
||||||
):
|
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content
|
assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content
|
||||||
|
|
||||||
turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12)
|
turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12)
|
||||||
with patch(
|
with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg):
|
||||||
"homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg
|
|
||||||
):
|
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
assert scale_jpeg_camera_image(camera_image, 16, 12) == EMPTY_16_12_JPEG
|
assert scale_jpeg_camera_image(camera_image, 16, 12) == EMPTY_16_12_JPEG
|
||||||
|
|
||||||
turbo_jpeg = mock_turbo_jpeg(
|
turbo_jpeg = mock_turbo_jpeg(
|
||||||
first_width=16, first_height=12, second_width=8, second_height=6
|
first_width=16, first_height=12, second_width=8, second_height=6
|
||||||
)
|
)
|
||||||
with patch(
|
with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg):
|
||||||
"homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg
|
|
||||||
):
|
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
jpeg_bytes = scale_jpeg_camera_image(camera_image, 8, 6)
|
jpeg_bytes = scale_jpeg_camera_image(camera_image, 8, 6)
|
||||||
|
|
||||||
@ -51,12 +45,10 @@ def test_scale_jpeg_camera_image():
|
|||||||
def test_turbojpeg_load_failure():
|
def test_turbojpeg_load_failure():
|
||||||
"""Handle libjpegturbo not being installed."""
|
"""Handle libjpegturbo not being installed."""
|
||||||
|
|
||||||
with patch(
|
with patch("turbojpeg.TurboJPEG", side_effect=Exception):
|
||||||
"homeassistant.components.homekit.img_util.TurboJPEG", side_effect=Exception
|
|
||||||
):
|
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
assert TurboJPEGSingleton.instance() is False
|
assert TurboJPEGSingleton.instance() is False
|
||||||
|
|
||||||
with patch("homeassistant.components.homekit.img_util.TurboJPEG"):
|
with patch("turbojpeg.TurboJPEG"):
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
assert TurboJPEGSingleton.instance()
|
assert TurboJPEGSingleton.instance()
|
||||||
|
@ -193,9 +193,7 @@ async def test_camera_stream_source_configured(hass, run_driver, events):
|
|||||||
turbo_jpeg = mock_turbo_jpeg(
|
turbo_jpeg = mock_turbo_jpeg(
|
||||||
first_width=16, first_height=12, second_width=300, second_height=200
|
first_width=16, first_height=12, second_width=300, second_height=200
|
||||||
)
|
)
|
||||||
with patch(
|
with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg):
|
||||||
"homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg
|
|
||||||
):
|
|
||||||
TurboJPEGSingleton()
|
TurboJPEGSingleton()
|
||||||
assert await hass.async_add_executor_job(
|
assert await hass.async_add_executor_job(
|
||||||
acc.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200}
|
acc.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200}
|
||||||
|
@ -264,6 +264,24 @@ async def test_zeroconf_with_uuid_device_exists_abort(
|
|||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_empty_unique_id_required_abort(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
|
) -> None:
|
||||||
|
"""Test we abort zeroconf flow if printer lacks (empty) unique identification."""
|
||||||
|
mock_connection(aioclient_mock, no_unique_id=True)
|
||||||
|
|
||||||
|
discovery_info = {
|
||||||
|
**MOCK_ZEROCONF_IPP_SERVICE_INFO,
|
||||||
|
"properties": {**MOCK_ZEROCONF_IPP_SERVICE_INFO["properties"], "UUID": ""},
|
||||||
|
}
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "unique_id_required"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_unique_id_required_abort(
|
async def test_zeroconf_unique_id_required_abort(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Test the Lutron Caseta config flow."""
|
"""Test the Lutron Caseta config flow."""
|
||||||
from asynctest import patch
|
|
||||||
from pylutron_caseta.smartbridge import Smartbridge
|
from pylutron_caseta.smartbridge import Smartbridge
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
@ -14,6 +13,7 @@ from homeassistant.components.lutron_caseta.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
|
|
||||||
|
from tests.async_mock import AsyncMock, patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@ -51,7 +51,11 @@ async def test_bridge_import_flow(hass):
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
||||||
) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls:
|
) as mock_setup_entry, patch(
|
||||||
|
"homeassistant.components.lutron_caseta.async_setup", return_value=True
|
||||||
|
), patch.object(
|
||||||
|
Smartbridge, "create_tls"
|
||||||
|
) as create_tls:
|
||||||
create_tls.return_value = MockBridge(can_connect=True)
|
create_tls.return_value = MockBridge(can_connect=True)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -77,9 +81,7 @@ async def test_bridge_cannot_connect(hass):
|
|||||||
CONF_CA_CERTS: "",
|
CONF_CA_CERTS: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(
|
with patch.object(Smartbridge, "create_tls") as create_tls:
|
||||||
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
|
||||||
) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls:
|
|
||||||
create_tls.return_value = MockBridge(can_connect=False)
|
create_tls.return_value = MockBridge(can_connect=False)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -91,8 +93,41 @@ async def test_bridge_cannot_connect(hass):
|
|||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == STEP_IMPORT_FAILED
|
assert result["step_id"] == STEP_IMPORT_FAILED
|
||||||
assert result["errors"] == {"base": ERROR_CANNOT_CONNECT}
|
assert result["errors"] == {"base": ERROR_CANNOT_CONNECT}
|
||||||
# validate setup_entry was not called
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 0
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_cannot_connect_unknown_error(hass):
|
||||||
|
"""Test checking for connection and encountering an unknown error."""
|
||||||
|
|
||||||
|
entry_mock_data = {
|
||||||
|
CONF_HOST: "",
|
||||||
|
CONF_KEYFILE: "",
|
||||||
|
CONF_CERTFILE: "",
|
||||||
|
CONF_CA_CERTS: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.object(Smartbridge, "create_tls") as create_tls:
|
||||||
|
mock_bridge = MockBridge()
|
||||||
|
mock_bridge.connect = AsyncMock(side_effect=Exception())
|
||||||
|
create_tls.return_value = mock_bridge
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=entry_mock_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == STEP_IMPORT_FAILED
|
||||||
|
assert result["errors"] == {"base": ERROR_CANNOT_CONNECT}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT
|
||||||
|
|
||||||
|
|
||||||
async def test_duplicate_bridge_import(hass):
|
async def test_duplicate_bridge_import(hass):
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
SERVICE_PLAY_MEDIA,
|
SERVICE_PLAY_MEDIA,
|
||||||
SERVICE_SELECT_SOURCE,
|
SERVICE_SELECT_SOURCE,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
SUPPORT_PLAY_MEDIA,
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
@ -30,6 +31,8 @@ from homeassistant.components.media_player.const import (
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
SERVICE_MEDIA_NEXT_TRACK,
|
SERVICE_MEDIA_NEXT_TRACK,
|
||||||
|
SERVICE_MEDIA_PAUSE,
|
||||||
|
SERVICE_MEDIA_PLAY,
|
||||||
SERVICE_MEDIA_PLAY_PAUSE,
|
SERVICE_MEDIA_PLAY_PAUSE,
|
||||||
SERVICE_MEDIA_PREVIOUS_TRACK,
|
SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
@ -142,6 +145,7 @@ async def test_supported_features(
|
|||||||
| SUPPORT_VOLUME_STEP
|
| SUPPORT_VOLUME_STEP
|
||||||
| SUPPORT_VOLUME_MUTE
|
| SUPPORT_VOLUME_MUTE
|
||||||
| SUPPORT_SELECT_SOURCE
|
| SUPPORT_SELECT_SOURCE
|
||||||
|
| SUPPORT_PAUSE
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
| SUPPORT_PLAY_MEDIA
|
| SUPPORT_PLAY_MEDIA
|
||||||
| SUPPORT_TURN_ON
|
| SUPPORT_TURN_ON
|
||||||
@ -170,6 +174,7 @@ async def test_tv_supported_features(
|
|||||||
| SUPPORT_VOLUME_STEP
|
| SUPPORT_VOLUME_STEP
|
||||||
| SUPPORT_VOLUME_MUTE
|
| SUPPORT_VOLUME_MUTE
|
||||||
| SUPPORT_SELECT_SOURCE
|
| SUPPORT_SELECT_SOURCE
|
||||||
|
| SUPPORT_PAUSE
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
| SUPPORT_PLAY_MEDIA
|
| SUPPORT_PLAY_MEDIA
|
||||||
| SUPPORT_TURN_ON
|
| SUPPORT_TURN_ON
|
||||||
@ -267,6 +272,26 @@ async def test_services(
|
|||||||
|
|
||||||
remote_mock.assert_called_once_with("poweron")
|
remote_mock.assert_called_once_with("poweron")
|
||||||
|
|
||||||
|
with patch("homeassistant.components.roku.Roku.remote") as remote_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
SERVICE_MEDIA_PAUSE,
|
||||||
|
{ATTR_ENTITY_ID: MAIN_ENTITY_ID},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
remote_mock.assert_called_once_with("play")
|
||||||
|
|
||||||
|
with patch("homeassistant.components.roku.Roku.remote") as remote_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
SERVICE_MEDIA_PLAY,
|
||||||
|
{ATTR_ENTITY_ID: MAIN_ENTITY_ID},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
remote_mock.assert_called_once_with("play")
|
||||||
|
|
||||||
with patch("homeassistant.components.roku.Roku.remote") as remote_mock:
|
with patch("homeassistant.components.roku.Roku.remote") as remote_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
MP_DOMAIN,
|
MP_DOMAIN,
|
||||||
|
@ -539,7 +539,7 @@ async def test_cleanup_device_registry(hass, registry):
|
|||||||
device_registry.async_cleanup(hass, registry, ent_reg)
|
device_registry.async_cleanup(hass, registry, ent_reg)
|
||||||
|
|
||||||
assert registry.async_get_device({("hue", "d1")}, set()) is not None
|
assert registry.async_get_device({("hue", "d1")}, set()) is not None
|
||||||
assert registry.async_get_device({("hue", "d2")}, set()) is None
|
assert registry.async_get_device({("hue", "d2")}, set()) is not None
|
||||||
assert registry.async_get_device({("hue", "d3")}, set()) is not None
|
assert registry.async_get_device({("hue", "d3")}, set()) is not None
|
||||||
assert registry.async_get_device({("something", "d4")}, set()) is None
|
assert registry.async_get_device({("something", "d4")}, set()) is None
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ from tests.common import (
|
|||||||
mock_service,
|
mock_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SUPPORT_A = 1
|
||||||
|
SUPPORT_B = 2
|
||||||
|
SUPPORT_C = 4
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_handle_entity_call():
|
def mock_handle_entity_call():
|
||||||
@ -52,17 +56,31 @@ def mock_entities(hass):
|
|||||||
entity_id="light.kitchen",
|
entity_id="light.kitchen",
|
||||||
available=True,
|
available=True,
|
||||||
should_poll=False,
|
should_poll=False,
|
||||||
supported_features=1,
|
supported_features=SUPPORT_A,
|
||||||
)
|
)
|
||||||
living_room = MockEntity(
|
living_room = MockEntity(
|
||||||
entity_id="light.living_room",
|
entity_id="light.living_room",
|
||||||
available=True,
|
available=True,
|
||||||
should_poll=False,
|
should_poll=False,
|
||||||
supported_features=0,
|
supported_features=SUPPORT_B,
|
||||||
|
)
|
||||||
|
bedroom = MockEntity(
|
||||||
|
entity_id="light.bedroom",
|
||||||
|
available=True,
|
||||||
|
should_poll=False,
|
||||||
|
supported_features=(SUPPORT_A | SUPPORT_B),
|
||||||
|
)
|
||||||
|
bathroom = MockEntity(
|
||||||
|
entity_id="light.bathroom",
|
||||||
|
available=True,
|
||||||
|
should_poll=False,
|
||||||
|
supported_features=(SUPPORT_B | SUPPORT_C),
|
||||||
)
|
)
|
||||||
entities = OrderedDict()
|
entities = OrderedDict()
|
||||||
entities[kitchen.entity_id] = kitchen
|
entities[kitchen.entity_id] = kitchen
|
||||||
entities[living_room.entity_id] = living_room
|
entities[living_room.entity_id] = living_room
|
||||||
|
entities[bedroom.entity_id] = bedroom
|
||||||
|
entities[bathroom.entity_id] = bathroom
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
|
||||||
@ -307,18 +325,61 @@ async def test_async_get_all_descriptions(hass):
|
|||||||
|
|
||||||
|
|
||||||
async def test_call_with_required_features(hass, mock_entities):
|
async def test_call_with_required_features(hass, mock_entities):
|
||||||
"""Test service calls invoked only if entity has required feautres."""
|
"""Test service calls invoked only if entity has required features."""
|
||||||
test_service_mock = AsyncMock(return_value=None)
|
test_service_mock = AsyncMock(return_value=None)
|
||||||
await service.entity_service_call(
|
await service.entity_service_call(
|
||||||
hass,
|
hass,
|
||||||
[Mock(entities=mock_entities)],
|
[Mock(entities=mock_entities)],
|
||||||
test_service_mock,
|
test_service_mock,
|
||||||
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
||||||
required_features=[1],
|
required_features=[SUPPORT_A],
|
||||||
)
|
)
|
||||||
assert len(mock_entities) == 2
|
|
||||||
# Called once because only one of the entities had the required features
|
assert test_service_mock.call_count == 2
|
||||||
|
expected = [
|
||||||
|
mock_entities["light.kitchen"],
|
||||||
|
mock_entities["light.bedroom"],
|
||||||
|
]
|
||||||
|
actual = [call[0][0] for call in test_service_mock.call_args_list]
|
||||||
|
assert all(entity in actual for entity in expected)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_call_with_both_required_features(hass, mock_entities):
|
||||||
|
"""Test service calls invoked only if entity has both features."""
|
||||||
|
test_service_mock = AsyncMock(return_value=None)
|
||||||
|
await service.entity_service_call(
|
||||||
|
hass,
|
||||||
|
[Mock(entities=mock_entities)],
|
||||||
|
test_service_mock,
|
||||||
|
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
||||||
|
required_features=[SUPPORT_A | SUPPORT_B],
|
||||||
|
)
|
||||||
|
|
||||||
assert test_service_mock.call_count == 1
|
assert test_service_mock.call_count == 1
|
||||||
|
assert [call[0][0] for call in test_service_mock.call_args_list] == [
|
||||||
|
mock_entities["light.bedroom"]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_call_with_one_of_required_features(hass, mock_entities):
|
||||||
|
"""Test service calls invoked with one entity having the required features."""
|
||||||
|
test_service_mock = AsyncMock(return_value=None)
|
||||||
|
await service.entity_service_call(
|
||||||
|
hass,
|
||||||
|
[Mock(entities=mock_entities)],
|
||||||
|
test_service_mock,
|
||||||
|
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
||||||
|
required_features=[SUPPORT_A, SUPPORT_C],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert test_service_mock.call_count == 3
|
||||||
|
expected = [
|
||||||
|
mock_entities["light.kitchen"],
|
||||||
|
mock_entities["light.bedroom"],
|
||||||
|
mock_entities["light.bathroom"],
|
||||||
|
]
|
||||||
|
actual = [call[0][0] for call in test_service_mock.call_args_list]
|
||||||
|
assert all(entity in actual for entity in expected)
|
||||||
|
|
||||||
|
|
||||||
async def test_call_with_sync_func(hass, mock_entities):
|
async def test_call_with_sync_func(hass, mock_entities):
|
||||||
@ -458,7 +519,7 @@ async def test_call_no_context_target_all(hass, mock_handle_entity_call, mock_en
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_handle_entity_call.mock_calls) == 2
|
assert len(mock_handle_entity_call.mock_calls) == 4
|
||||||
assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list(
|
assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list(
|
||||||
mock_entities.values()
|
mock_entities.values()
|
||||||
)
|
)
|
||||||
@ -494,7 +555,7 @@ async def test_call_with_match_all(
|
|||||||
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_handle_entity_call.mock_calls) == 2
|
assert len(mock_handle_entity_call.mock_calls) == 4
|
||||||
assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list(
|
assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list(
|
||||||
mock_entities.values()
|
mock_entities.values()
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user