Merge pull request #33030 from home-assistant/rc

0.107.2
This commit is contained in:
Paulus Schoutsen 2020-03-19 22:51:13 -07:00 committed by GitHub
commit ac2310e7f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 174 additions and 86 deletions

View File

@ -21,6 +21,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .axis_base import AxisEntityBase from .axis_base import AxisEntityBase
from .const import DOMAIN as AXIS_DOMAIN from .const import DOMAIN as AXIS_DOMAIN
AXIS_IMAGE = "http://{host}:{port}/axis-cgi/jpg/image.cgi"
AXIS_VIDEO = "http://{host}:{port}/axis-cgi/mjpg/video.cgi"
AXIS_STREAM = "rtsp://{user}:{password}@{host}/axis-media/media.amp?videocodec=h264"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Axis camera video stream.""" """Set up the Axis camera video stream."""
@ -32,13 +36,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
CONF_NAME: config_entry.data[CONF_NAME], CONF_NAME: config_entry.data[CONF_NAME],
CONF_USERNAME: config_entry.data[CONF_USERNAME], CONF_USERNAME: config_entry.data[CONF_USERNAME],
CONF_PASSWORD: config_entry.data[CONF_PASSWORD], CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
CONF_MJPEG_URL: ( CONF_MJPEG_URL: AXIS_VIDEO.format(
f"http://{config_entry.data[CONF_HOST]}" host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
f":{config_entry.data[CONF_PORT]}/axis-cgi/mjpg/video.cgi"
), ),
CONF_STILL_IMAGE_URL: ( CONF_STILL_IMAGE_URL: AXIS_IMAGE.format(
f"http://{config_entry.data[CONF_HOST]}" host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
f":{config_entry.data[CONF_PORT]}/axis-cgi/jpg/image.cgi"
), ),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
} }
@ -70,19 +72,17 @@ class AxisCamera(AxisEntityBase, MjpegCamera):
async def stream_source(self): async def stream_source(self):
"""Return the stream source.""" """Return the stream source."""
return ( return AXIS_STREAM.format(
f"rtsp://{self.device.config_entry.data[CONF_USERNAME]}´" user=self.device.config_entry.data[CONF_USERNAME],
f":{self.device.config_entry.data[CONF_PASSWORD]}" password=self.device.config_entry.data[CONF_PASSWORD],
f"@{self.device.host}/axis-media/media.amp?videocodec=h264" host=self.device.host,
) )
def _new_address(self): def _new_address(self):
"""Set new device address for video stream.""" """Set new device address for video stream."""
port = self.device.config_entry.data[CONF_PORT] port = self.device.config_entry.data[CONF_PORT]
self._mjpeg_url = (f"http://{self.device.host}:{port}/axis-cgi/mjpg/video.cgi",) self._mjpeg_url = AXIS_VIDEO.format(host=self.device.host, port=port)
self._still_image_url = ( self._still_image_url = AXIS_IMAGE.format(host=self.device.host, port=port)
f"http://{self.device.host}:{port}/axis-cgi/jpg/image.cgi"
)
@property @property
def unique_id(self): def unique_id(self):

View File

@ -141,7 +141,7 @@ async def async_request_stream(hass, entity_id, fmt):
source, source,
fmt=fmt, fmt=fmt,
keepalive=camera_prefs.preload_stream, keepalive=camera_prefs.preload_stream,
options=camera.options, options=camera.stream_options,
) )

View File

@ -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.29.1"], "requirements": ["aiohomekit[IP]==0.2.29.2"],
"dependencies": [], "dependencies": [],
"zeroconf": ["_hap._tcp.local."], "zeroconf": ["_hap._tcp.local."],
"codeowners": ["@Jc2k"] "codeowners": ["@Jc2k"]

View File

@ -25,6 +25,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import verify_domain_control from homeassistant.helpers.service import verify_domain_control
from .const import ( from .const import (
CONF_ZONE_RUN_TIME,
DATA_CLIENT, DATA_CLIENT,
DATA_PROGRAMS, DATA_PROGRAMS,
DATA_PROVISION_SETTINGS, DATA_PROVISION_SETTINGS,
@ -33,6 +34,8 @@ from .const import (
DATA_ZONES, DATA_ZONES,
DATA_ZONES_DETAILS, DATA_ZONES_DETAILS,
DEFAULT_PORT, DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_ZONE_RUN,
DOMAIN, DOMAIN,
PROGRAM_UPDATE_TOPIC, PROGRAM_UPDATE_TOPIC,
SENSOR_UPDATE_TOPIC, SENSOR_UPDATE_TOPIC,
@ -41,19 +44,14 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_LISTENER = "listener"
CONF_CONTROLLERS = "controllers" CONF_CONTROLLERS = "controllers"
CONF_PROGRAM_ID = "program_id" CONF_PROGRAM_ID = "program_id"
CONF_SECONDS = "seconds" CONF_SECONDS = "seconds"
CONF_ZONE_ID = "zone_id" CONF_ZONE_ID = "zone_id"
CONF_ZONE_RUN_TIME = "zone_run_time"
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC" DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
DEFAULT_ICON = "mdi:water" DEFAULT_ICON = "mdi:water"
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_SSL = True DEFAULT_SSL = True
DEFAULT_ZONE_RUN = 60 * 10
SERVICE_ALTER_PROGRAM = vol.Schema({vol.Required(CONF_PROGRAM_ID): cv.positive_int}) SERVICE_ALTER_PROGRAM = vol.Schema({vol.Required(CONF_PROGRAM_ID): cv.positive_int})
@ -109,7 +107,6 @@ async def async_setup(hass, config):
"""Set up the RainMachine component.""" """Set up the RainMachine component."""
hass.data[DOMAIN] = {} hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_CLIENT] = {} hass.data[DOMAIN][DATA_CLIENT] = {}
hass.data[DOMAIN][DATA_LISTENER] = {}
if DOMAIN not in config: if DOMAIN not in config:
return True return True
@ -143,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_IP_ADDRESS],
config_entry.data[CONF_PASSWORD], config_entry.data[CONF_PASSWORD],
port=config_entry.data[CONF_PORT], port=config_entry.data[CONF_PORT],
ssl=config_entry.data[CONF_SSL], ssl=config_entry.data.get(CONF_SSL, DEFAULT_SSL),
) )
except RainMachineError as err: except RainMachineError as err:
_LOGGER.error("An error occurred: %s", err) _LOGGER.error("An error occurred: %s", err)
@ -156,8 +153,10 @@ async def async_setup_entry(hass, config_entry):
rainmachine = RainMachine( rainmachine = RainMachine(
hass, hass,
controller, controller,
config_entry.data[CONF_ZONE_RUN_TIME], config_entry.data.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN),
config_entry.data[CONF_SCAN_INTERVAL], config_entry.data.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds()
),
) )
# Update the data object, which at this point (prior to any sensors registering # Update the data object, which at this point (prior to any sensors registering
@ -260,9 +259,6 @@ async def async_unload_entry(hass, config_entry):
"""Unload an OpenUV config entry.""" """Unload an OpenUV config entry."""
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id)
remove_listener()
tasks = [ tasks = [
hass.config_entries.async_forward_entry_unload(config_entry, component) hass.config_entries.async_forward_entry_unload(config_entry, component)
for component in ("binary_sensor", "sensor", "switch") for component in ("binary_sensor", "sensor", "switch")

View File

@ -4,10 +4,22 @@ from regenmaschine.errors import RainMachineError
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT from homeassistant.const import (
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SSL,
)
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import DEFAULT_PORT, DOMAIN # pylint: disable=unused-import from .const import ( # pylint: disable=unused-import
CONF_ZONE_RUN_TIME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_ZONE_RUN,
DOMAIN,
)
class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@ -53,8 +65,8 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
user_input[CONF_IP_ADDRESS], user_input[CONF_IP_ADDRESS],
user_input[CONF_PASSWORD], user_input[CONF_PASSWORD],
websession, websession,
port=user_input.get(CONF_PORT, DEFAULT_PORT), port=user_input[CONF_PORT],
ssl=True, ssl=user_input.get(CONF_SSL, True),
) )
except RainMachineError: except RainMachineError:
return await self._show_form({CONF_PASSWORD: "invalid_credentials"}) return await self._show_form({CONF_PASSWORD: "invalid_credentials"})
@ -63,5 +75,17 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# access token without using the IP address and password, so we have to # access token without using the IP address and password, so we have to
# store it: # store it:
return self.async_create_entry( return self.async_create_entry(
title=user_input[CONF_IP_ADDRESS], data=user_input title=user_input[CONF_IP_ADDRESS],
data={
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_PORT: user_input[CONF_PORT],
CONF_SSL: user_input.get(CONF_SSL, True),
CONF_SCAN_INTERVAL: user_input.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds()
),
CONF_ZONE_RUN_TIME: user_input.get(
CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN
),
},
) )

View File

@ -1,6 +1,10 @@
"""Define constants for the SimpliSafe component.""" """Define constants for the SimpliSafe component."""
from datetime import timedelta
DOMAIN = "rainmachine" DOMAIN = "rainmachine"
CONF_ZONE_RUN_TIME = "zone_run_time"
DATA_CLIENT = "client" DATA_CLIENT = "client"
DATA_PROGRAMS = "programs" DATA_PROGRAMS = "programs"
DATA_PROVISION_SETTINGS = "provision.settings" DATA_PROVISION_SETTINGS = "provision.settings"
@ -10,6 +14,8 @@ DATA_ZONES = "zones"
DATA_ZONES_DETAILS = "zones_details" DATA_ZONES_DETAILS = "zones_details"
DEFAULT_PORT = 8080 DEFAULT_PORT = 8080
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_ZONE_RUN = 60 * 10
PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update" PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update"
SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update" SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update"

View File

@ -3,6 +3,7 @@
"name": "Sighthound", "name": "Sighthound",
"documentation": "https://www.home-assistant.io/integrations/sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound",
"requirements": [ "requirements": [
"pillow==7.0.0",
"simplehound==0.3" "simplehound==0.3"
], ],
"dependencies": [], "dependencies": [],

View File

@ -3,7 +3,7 @@
"name": "SimpliSafe", "name": "SimpliSafe",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/simplisafe", "documentation": "https://www.home-assistant.io/integrations/simplisafe",
"requirements": ["simplisafe-python==9.0.2"], "requirements": ["simplisafe-python==9.0.3"],
"dependencies": [], "dependencies": [],
"codeowners": ["@bachya"] "codeowners": ["@bachya"]
} }

View File

@ -27,7 +27,7 @@ DOMAIN = "somfy"
CONF_CLIENT_ID = "client_id" CONF_CLIENT_ID = "client_id"
CONF_CLIENT_SECRET = "client_secret" CONF_CLIENT_SECRET = "client_secret"
CONF_OPTIMISTIC = "optimisitic" CONF_OPTIMISTIC = "optimistic"
SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback" SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback"
SOMFY_AUTH_START = "/auth/somfy" SOMFY_AUTH_START = "/auth/somfy"
@ -36,8 +36,8 @@ CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
{ {
vol.Required(CONF_CLIENT_ID): cv.string, vol.Inclusive(CONF_CLIENT_ID, "oauth"): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string, vol.Inclusive(CONF_CLIENT_SECRET, "oauth"): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
} }
) )
@ -51,23 +51,21 @@ SOMFY_COMPONENTS = ["cover", "switch"]
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the Somfy component.""" """Set up the Somfy component."""
hass.data[DOMAIN] = {} hass.data[DOMAIN] = {}
domain_config = config.get(DOMAIN, {})
hass.data[DOMAIN][CONF_OPTIMISTIC] = domain_config.get(CONF_OPTIMISTIC, False)
if DOMAIN not in config: if CONF_CLIENT_ID in domain_config:
return True config_flow.SomfyFlowHandler.async_register_implementation(
hass.data[DOMAIN][CONF_OPTIMISTIC] = config[DOMAIN][CONF_OPTIMISTIC]
config_flow.SomfyFlowHandler.async_register_implementation(
hass,
config_entry_oauth2_flow.LocalOAuth2Implementation(
hass, hass,
DOMAIN, config_entry_oauth2_flow.LocalOAuth2Implementation(
config[DOMAIN][CONF_CLIENT_ID], hass,
config[DOMAIN][CONF_CLIENT_SECRET], DOMAIN,
"https://accounts.somfy.com/oauth/oauth/v2/auth", config[DOMAIN][CONF_CLIENT_ID],
"https://accounts.somfy.com/oauth/oauth/v2/token", config[DOMAIN][CONF_CLIENT_SECRET],
), "https://accounts.somfy.com/oauth/oauth/v2/auth",
) "https://accounts.somfy.com/oauth/oauth/v2/token",
),
)
return True return True

View File

@ -2,7 +2,7 @@
"domain": "velbus", "domain": "velbus",
"name": "Velbus", "name": "Velbus",
"documentation": "https://www.home-assistant.io/integrations/velbus", "documentation": "https://www.home-assistant.io/integrations/velbus",
"requirements": ["python-velbus==2.0.42"], "requirements": ["python-velbus==2.0.43"],
"config_flow": true, "config_flow": true,
"dependencies": [], "dependencies": [],
"codeowners": ["@Cereal2nd", "@brefra"] "codeowners": ["@Cereal2nd", "@brefra"]

View File

@ -89,9 +89,19 @@ async def async_setup_entry(hass, config_entry):
Will automatically load components to support devices found on the network. Will automatically load components to support devices found on the network.
""" """
hass.data[DATA_ZHA] = hass.data.get(DATA_ZHA, {}) zha_data = hass.data.setdefault(DATA_ZHA, {})
hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = [] config = zha_data.get(DATA_ZHA_CONFIG, {})
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
if config.get(CONF_ENABLE_QUIRKS, True):
# needs to be done here so that the ZHA module is finished loading
# before zhaquirks is imported
import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error
zha_gateway = ZHAGateway(hass, config, config_entry)
await zha_gateway.async_initialize()
zha_data[DATA_ZHA_DISPATCHERS] = []
zha_data[DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
platforms = [] platforms = []
for component in COMPONENTS: for component in COMPONENTS:
platforms.append( platforms.append(
@ -102,20 +112,10 @@ async def async_setup_entry(hass, config_entry):
async def _platforms_loaded(): async def _platforms_loaded():
await asyncio.gather(*platforms) await asyncio.gather(*platforms)
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].set() zha_data[DATA_ZHA_PLATFORM_LOADED].set()
hass.async_create_task(_platforms_loaded()) hass.async_create_task(_platforms_loaded())
config = hass.data[DATA_ZHA].get(DATA_ZHA_CONFIG, {})
if config.get(CONF_ENABLE_QUIRKS, True):
# needs to be done here so that the ZHA module is finished loading
# before zhaquirks is imported
import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error
zha_gateway = ZHAGateway(hass, config, config_entry)
await zha_gateway.async_initialize()
device_registry = await hass.helpers.device_registry.async_get_registry() device_registry = await hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
@ -130,8 +130,8 @@ async def async_setup_entry(hass, config_entry):
async def async_zha_shutdown(event): async def async_zha_shutdown(event):
"""Handle shutdown tasks.""" """Handle shutdown tasks."""
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown() await zha_data[DATA_ZHA_GATEWAY].shutdown()
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_update_device_storage() await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage()
hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown) hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown)
hass.async_create_task(zha_gateway.async_load_devices()) hass.async_create_task(zha_gateway.async_load_devices())

View File

@ -85,11 +85,11 @@ class ZigbeeChannel(LogMixin):
self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType
) -> None: ) -> None:
"""Initialize ZigbeeChannel.""" """Initialize ZigbeeChannel."""
self._channel_name = cluster.ep_attribute self._generic_id = f"channel_0x{cluster.cluster_id:04x}"
self._channel_name = getattr(cluster, "ep_attribute", self._generic_id)
if self.CHANNEL_NAME: if self.CHANNEL_NAME:
self._channel_name = self.CHANNEL_NAME self._channel_name = self.CHANNEL_NAME
self._ch_pool = ch_pool self._ch_pool = ch_pool
self._generic_id = f"channel_0x{cluster.cluster_id:04x}"
self._cluster = cluster self._cluster = cluster
self._id = f"{ch_pool.id}:0x{cluster.cluster_id:04x}" self._id = f"{ch_pool.id}:0x{cluster.cluster_id:04x}"
unique_id = ch_pool.unique_id.replace("-", ":") unique_id = ch_pool.unique_id.replace("-", ":")

View File

@ -7,10 +7,12 @@ import logging
import os import os
import traceback import traceback
from serial import SerialException
import zigpy.device as zigpy_dev import zigpy.device as zigpy_dev
from homeassistant.components.system_log import LogEntry, _figure_out_source from homeassistant.components.system_log import LogEntry, _figure_out_source
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
CONNECTION_ZIGBEE, CONNECTION_ZIGBEE,
async_get_registry as get_dev_reg, async_get_registry as get_dev_reg,
@ -98,7 +100,6 @@ class ZHAGateway:
self.ha_entity_registry = None self.ha_entity_registry = None
self.application_controller = None self.application_controller = None
self.radio_description = None self.radio_description = None
hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
self._log_levels = { self._log_levels = {
DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(), DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(),
DEBUG_LEVEL_CURRENT: async_capture_log_levels(), DEBUG_LEVEL_CURRENT: async_capture_log_levels(),
@ -122,7 +123,11 @@ class ZHAGateway:
radio_details = RADIO_TYPES[radio_type] radio_details = RADIO_TYPES[radio_type]
radio = radio_details[ZHA_GW_RADIO]() radio = radio_details[ZHA_GW_RADIO]()
self.radio_description = radio_details[ZHA_GW_RADIO_DESCRIPTION] self.radio_description = radio_details[ZHA_GW_RADIO_DESCRIPTION]
await radio.connect(usb_path, baudrate) try:
await radio.connect(usb_path, baudrate)
except (SerialException, OSError) as exception:
_LOGGER.error("Couldn't open serial port for ZHA: %s", str(exception))
raise ConfigEntryNotReady
if CONF_DATABASE in self._config: if CONF_DATABASE in self._config:
database = self._config[CONF_DATABASE] database = self._config[CONF_DATABASE]
@ -133,7 +138,22 @@ class ZHAGateway:
apply_application_controller_patch(self) apply_application_controller_patch(self)
self.application_controller.add_listener(self) self.application_controller.add_listener(self)
self.application_controller.groups.add_listener(self) self.application_controller.groups.add_listener(self)
await self.application_controller.startup(auto_form=True)
try:
res = await self.application_controller.startup(auto_form=True)
if res is False:
await self.application_controller.shutdown()
raise ConfigEntryNotReady
except asyncio.TimeoutError as exception:
_LOGGER.error(
"Couldn't start %s coordinator",
radio_details[ZHA_GW_RADIO_DESCRIPTION],
exc_info=exception,
)
radio.close()
raise ConfigEntryNotReady from exception
self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str( self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(
self.application_controller.ieee self.application_controller.ieee
) )

View File

@ -565,9 +565,23 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) ->
def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]: def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]:
"""Extract the schema and identify list or dict based.""" """Extract the schema and identify list or dict based."""
try: try:
schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore
except (AttributeError, KeyError): except (AttributeError, StopIteration):
return None, None return None, None
schema = module.CONFIG_SCHEMA.schema[key] # type: ignore
if hasattr(key, "default"):
default_value = schema(key.default())
if isinstance(default_value, dict):
return "dict", schema
if isinstance(default_value, list):
return "list", schema
return None, None
t_schema = str(schema) t_schema = str(schema)
if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema: if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema:
return ("dict", schema) return ("dict", schema)

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 0 MAJOR_VERSION = 0
MINOR_VERSION = 107 MINOR_VERSION = 107
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)

View File

@ -163,7 +163,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.29.1 aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue # homeassistant.components.emulated_hue
# homeassistant.components.http # homeassistant.components.http
@ -1017,6 +1017,7 @@ pilight==0.1.1
# homeassistant.components.proxy # homeassistant.components.proxy
# homeassistant.components.qrcode # homeassistant.components.qrcode
# homeassistant.components.seven_segments # homeassistant.components.seven_segments
# homeassistant.components.sighthound
# homeassistant.components.tensorflow # homeassistant.components.tensorflow
pillow==7.0.0 pillow==7.0.0
@ -1665,7 +1666,7 @@ python-telnet-vlc==1.0.4
python-twitch-client==0.6.0 python-twitch-client==0.6.0
# homeassistant.components.velbus # homeassistant.components.velbus
python-velbus==2.0.42 python-velbus==2.0.43
# homeassistant.components.vlc # homeassistant.components.vlc
python-vlc==1.1.2 python-vlc==1.1.2
@ -1855,7 +1856,7 @@ simplehound==0.3
simplepush==1.1.4 simplepush==1.1.4
# homeassistant.components.simplisafe # homeassistant.components.simplisafe
simplisafe-python==9.0.2 simplisafe-python==9.0.3
# homeassistant.components.sisyphus # homeassistant.components.sisyphus
sisyphus-control==2.2.1 sisyphus-control==2.2.1

View File

@ -62,7 +62,7 @@ aiobotocore==0.11.1
aioesphomeapi==2.6.1 aioesphomeapi==2.6.1
# homeassistant.components.homekit_controller # homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.29.1 aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue # homeassistant.components.emulated_hue
# homeassistant.components.http # homeassistant.components.http
@ -365,6 +365,14 @@ pexpect==4.6.0
# homeassistant.components.pilight # homeassistant.components.pilight
pilight==0.1.1 pilight==0.1.1
# homeassistant.components.doods
# homeassistant.components.proxy
# homeassistant.components.qrcode
# homeassistant.components.seven_segments
# homeassistant.components.sighthound
# homeassistant.components.tensorflow
pillow==7.0.0
# homeassistant.components.plex # homeassistant.components.plex
plexapi==3.3.0 plexapi==3.3.0
@ -587,7 +595,7 @@ python-nest==4.1.0
python-twitch-client==0.6.0 python-twitch-client==0.6.0
# homeassistant.components.velbus # homeassistant.components.velbus
python-velbus==2.0.42 python-velbus==2.0.43
# homeassistant.components.awair # homeassistant.components.awair
python_awair==0.0.4 python_awair==0.0.4
@ -641,7 +649,7 @@ sentry-sdk==0.13.5
simplehound==0.3 simplehound==0.3
# homeassistant.components.simplisafe # homeassistant.components.simplisafe
simplisafe-python==9.0.2 simplisafe-python==9.0.3
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
sleepyq==0.7 sleepyq==0.7

View File

@ -4,7 +4,7 @@ from unittest.mock import patch
from regenmaschine.errors import RainMachineError from regenmaschine.errors import RainMachineError
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.components.rainmachine import DOMAIN, config_flow from homeassistant.components.rainmachine import CONF_ZONE_RUN_TIME, DOMAIN, config_flow
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import ( from homeassistant.const import (
CONF_IP_ADDRESS, CONF_IP_ADDRESS,
@ -98,6 +98,7 @@ async def test_step_import(hass):
CONF_PORT: 8080, CONF_PORT: 8080,
CONF_SSL: True, CONF_SSL: True,
CONF_SCAN_INTERVAL: 60, CONF_SCAN_INTERVAL: 60,
CONF_ZONE_RUN_TIME: 600,
} }
@ -129,4 +130,5 @@ async def test_step_user(hass):
CONF_PORT: 8080, CONF_PORT: 8080,
CONF_SSL: True, CONF_SSL: True,
CONF_SCAN_INTERVAL: 60, CONF_SCAN_INTERVAL: 60,
CONF_ZONE_RUN_TIME: 600,
} }

View File

@ -123,7 +123,7 @@ async def test_tracked_devices(hass):
devices_response=[DEVICE_1, DEVICE_2], devices_response=[DEVICE_1, DEVICE_2],
known_wireless_clients=(CLIENT_4["mac"],), known_wireless_clients=(CLIENT_4["mac"],),
) )
assert len(hass.states.async_entity_ids("device_tracker")) == 5 assert len(hass.states.async_entity_ids("device_tracker")) == 6
client_1 = hass.states.get("device_tracker.client_1") client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None assert client_1 is not None
@ -349,7 +349,7 @@ async def test_option_ssid_filter(hass):
controller = await setup_unifi_integration( controller = await setup_unifi_integration(
hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3], hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3],
) )
assert len(hass.states.async_entity_ids("device_tracker")) == 0 assert len(hass.states.async_entity_ids("device_tracker")) == 1
# SSID filter active # SSID filter active
client_3 = hass.states.get("device_tracker.client_3") client_3 = hass.states.get("device_tracker.client_3")

View File

@ -10,6 +10,7 @@ from unittest.mock import Mock
import asynctest import asynctest
from asynctest import CoroutineMock, patch from asynctest import CoroutineMock, patch
import pytest import pytest
import voluptuous as vol
from voluptuous import Invalid, MultipleInvalid from voluptuous import Invalid, MultipleInvalid
import yaml import yaml
@ -989,3 +990,20 @@ async def test_component_config_exceptions(hass, caplog):
"Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA" "Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA"
in caplog.text in caplog.text
) )
@pytest.mark.parametrize(
"domain, schema, expected",
[
("zone", vol.Schema({vol.Optional("zone", default=[]): list}), "list"),
("zone", vol.Schema({vol.Optional("zone", default=dict): dict}), "dict"),
],
)
def test_identify_config_schema(domain, schema, expected):
"""Test identify config schema."""
assert (
config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))[
0
]
== expected
)