Compare commits

...

26 Commits

Author SHA1 Message Date
Franck Nijhof
d82d7fa2e9 Merge pull request #33080 from home-assistant/rc
0.107.4
2020-03-21 07:51:18 +01:00
Paulus Schoutsen
663db747e9 Bumped version to 0.107.4 2020-03-20 23:02:50 -07:00
Paulus Schoutsen
57998f6f0f Fix package default extraction (#33071) 2020-03-20 23:02:29 -07:00
Knapoc
edbb995fff Bump aioasuswrt to 1.2.3 and fix asuswrt sensor (#33064)
* Bump aioasuswrt to 1.2.3

* Fix asuswrt connection setup parameters

* fix typo
2020-03-20 23:02:29 -07:00
Aaron Bach
312903025d Bump simplisafe-python to 9.0.4 (#33059) 2020-03-20 23:02:28 -07:00
Paulus Schoutsen
0ae5c325fe Add negative tests for identify schema for packages (#33050) 2020-03-20 23:02:27 -07:00
Pascal Vizeli
a309a00929 Merge pull request #33054 from home-assistant/rc
0.107.3
2020-03-20 18:33:47 +01:00
Pascal Vizeli
55be5bf880 Bump version to 0.107.3 2020-03-20 16:29:10 +00:00
Franck Nijhof
7b37dcd8ed Fix packages for schemas without a default (#33045) 2020-03-20 16:27:29 +00:00
cgtobi
e36bdd717a Fix discovery issue with netatmo climate devices (#33040) 2020-03-20 16:27:28 +00:00
cgtobi
fa650b648c Fix netatmo webhook registration issue (#32994)
* Wait for cloud connection

* Just wait

* Remove redundant entry

* Drop webhook before unloading other platforms

* Add missing scope

* Update homeassistant/components/netatmo/__init__.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Fix test

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-03-20 16:27:27 +00:00
Paulus Schoutsen
ac2310e7f9 Merge pull request #33030 from home-assistant/rc
0.107.2
2020-03-19 22:51:13 -07:00
Aaron Bach
aee5c16803 Fix RainMachine not properly storing data in the config entry (#33002)
* Fix bug related to RainMachine's default config flow

* A

* Fix tests

* Code review
2020-03-19 20:54:57 -07:00
Paulus Schoutsen
5f0816ea25 Fix zones in packages (#33027) 2020-03-19 20:52:08 -07:00
Paulus Schoutsen
6a6037790f Bumped version to 0.107.2 2020-03-19 20:50:46 -07:00
Alexei Chetroi
d2b0c35319 Handle zigpy clusters without ep_attribute attribute. (#33028) 2020-03-19 20:50:34 -07:00
Jc2k
d707a1b072 0.107.2 - Bump aiohomekit to fix Insignia NS-CH1XGO8 and Lenno… (#33016) 2020-03-19 20:46:45 -07:00
Aaron Bach
ca12db9271 Bump simplisafe-python to 9.0.3 (#33013) 2020-03-19 20:45:57 -07:00
Robin
346a4b399d Fix sighthound dependency issue (#33010) 2020-03-19 20:45:56 -07:00
Robert Svensson
2090252936 Axis - Fix char in stream url (#33004)
* An unwanted character had found its way into a stream string, reverting f-string work to remove duplication of code and improve readability

* Fix failing tests
2020-03-19 20:45:56 -07:00
tetienne
a28091e94a Fix somfy optimistic mode when missing in conf (#32995)
* Fix optimistic mode when missing in conf #32971

* Ease code using a default value

* Client id and secret are now inclusive
2020-03-19 20:45:55 -07:00
Alexei Chetroi
ae8cb0ccdf Refactor ZHA setup (#32959)
* Refactor ZHA setup.
Catch errors and raise if needed.

* Cleanup.
2020-03-19 20:45:54 -07:00
Maikel Punie
06a608e342 Fix velbus in the 107 release (#32936)
velbus 2.0.41 introduced some data files in the python module. They are not copied when installing the velbus module. 2.0.43 fixes this problem
2020-03-19 20:45:53 -07:00
ochlocracy
9af95e8577 Fix camera.options to camera.stream_options. (#32767) 2020-03-19 20:45:53 -07:00
Paulus Schoutsen
29a9781bf7 Fix unifi tests 2020-03-19 15:28:38 -07:00
Jc2k
877eddf43d 0.107.2 - Bump aiohomekit to fix Insignia NS-CH1XGO8 and Lenno… (#33016) 2020-03-19 15:26:01 -07:00
29 changed files with 231 additions and 119 deletions

View File

@@ -73,8 +73,8 @@ async def async_setup(hass, config):
conf.get("ssh_key", conf.get("pub_key", "")), conf.get("ssh_key", conf.get("pub_key", "")),
conf[CONF_MODE], conf[CONF_MODE],
conf[CONF_REQUIRE_IP], conf[CONF_REQUIRE_IP],
conf[CONF_INTERFACE], interface=conf[CONF_INTERFACE],
conf[CONF_DNSMASQ], dnsmasq=conf[CONF_DNSMASQ],
) )
await api.connection.async_connect() await api.connection.async_connect()

View File

@@ -2,7 +2,7 @@
"domain": "asuswrt", "domain": "asuswrt",
"name": "ASUSWRT", "name": "ASUSWRT",
"documentation": "https://www.home-assistant.io/integrations/asuswrt", "documentation": "https://www.home-assistant.io/integrations/asuswrt",
"requirements": ["aioasuswrt==1.2.2"], "requirements": ["aioasuswrt==1.2.3"],
"dependencies": [], "dependencies": [],
"codeowners": ["@kennedyshead"] "codeowners": ["@kennedyshead"]
} }

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

@@ -28,6 +28,7 @@ from . import api, config_flow
from .const import ( from .const import (
AUTH, AUTH,
CONF_CLOUDHOOK_URL, CONF_CLOUDHOOK_URL,
DATA_DEVICE_IDS,
DATA_PERSONS, DATA_PERSONS,
DOMAIN, DOMAIN,
OAUTH2_AUTHORIZE, OAUTH2_AUTHORIZE,
@@ -65,6 +66,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Netatmo component.""" """Set up the Netatmo component."""
hass.data[DOMAIN] = {} hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_PERSONS] = {} hass.data[DOMAIN][DATA_PERSONS] = {}
hass.data[DOMAIN][DATA_DEVICE_IDS] = {}
if DOMAIN not in config: if DOMAIN not in config:
return True return True
@@ -104,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID]) webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
async def register_webhook(event): async def register_webhook(event):
# Wait for the could integration to be ready # Wait for the cloud integration to be ready
await asyncio.sleep(WAIT_FOR_CLOUD) await asyncio.sleep(WAIT_FOR_CLOUD)
if CONF_WEBHOOK_ID not in entry.data: if CONF_WEBHOOK_ID not in entry.data:
@@ -112,6 +114,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.config_entries.async_update_entry(entry, data=data) hass.config_entries.async_update_entry(entry, data=data)
if hass.components.cloud.async_active_subscription(): if hass.components.cloud.async_active_subscription():
# Wait for cloud connection to be established
await asyncio.sleep(WAIT_FOR_CLOUD)
if CONF_CLOUDHOOK_URL not in entry.data: if CONF_CLOUDHOOK_URL not in entry.data:
webhook_url = await hass.components.cloud.async_create_cloudhook( webhook_url = await hass.components.cloud.async_create_cloudhook(
entry.data[CONF_WEBHOOK_ID] entry.data[CONF_WEBHOOK_ID]
@@ -144,6 +149,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry.""" """Unload a config entry."""
if CONF_WEBHOOK_ID in entry.data:
await hass.async_add_executor_job(
hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook
)
unload_ok = all( unload_ok = all(
await asyncio.gather( await asyncio.gather(
*[ *[
@@ -152,14 +162,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
] ]
) )
) )
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
if CONF_WEBHOOK_ID in entry.data:
await hass.async_add_executor_job(
hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook()
)
return unload_ok return unload_ok

View File

@@ -84,21 +84,11 @@ class NetatmoCamera(Camera):
self._unique_id = f"{self._camera_id}-{self._camera_type}" self._unique_id = f"{self._camera_id}-{self._camera_type}"
self._verify_ssl = verify_ssl self._verify_ssl = verify_ssl
self._quality = quality self._quality = quality
# URLs
self._vpnurl = None self._vpnurl = None
self._localurl = None self._localurl = None
# Monitoring status
self._status = None self._status = None
# SD Card status
self._sd_status = None self._sd_status = None
# Power status
self._alim_status = None self._alim_status = None
# Is local
self._is_local = None self._is_local = None
def camera_image(self): def camera_image(self):
@@ -219,8 +209,6 @@ class NetatmoCamera(Camera):
def update(self): def update(self):
"""Update entity status.""" """Update entity status."""
# Refresh camera data
self._data.update() self._data.update()
camera = self._data.camera_data.get_camera(cid=self._camera_id) camera = self._data.camera_data.get_camera(cid=self._camera_id)

View File

@@ -441,6 +441,11 @@ class ThermostatData:
except TypeError: except TypeError:
_LOGGER.error("ThermostatData::setup() got error") _LOGGER.error("ThermostatData::setup() got error")
return False return False
except pyatmo.exceptions.NoDevice:
_LOGGER.debug(
"No climate devices for %s (%s)", self.home_name, self.home_id
)
return False
return True return True
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)

View File

@@ -33,6 +33,7 @@ class NetatmoFlowHandler(
"read_station", "read_station",
"read_thermostat", "read_thermostat",
"write_camera", "write_camera",
"write_presence",
"write_thermostat", "write_thermostat",
] ]

View File

@@ -14,12 +14,12 @@ MODELS = {
"NOC": "Smart Outdoor Camera", "NOC": "Smart Outdoor Camera",
"NSD": "Smart Smoke Alarm", "NSD": "Smart Smoke Alarm",
"NACamDoorTag": "Smart Door and Window Sensors", "NACamDoorTag": "Smart Door and Window Sensors",
"NHC": "Smart Indoor Air Quality Monitor",
"NAMain": "Smart Home Weather station indoor module", "NAMain": "Smart Home Weather station indoor module",
"NAModule1": "Smart Home Weather station outdoor module", "NAModule1": "Smart Home Weather station outdoor module",
"NAModule4": "Smart Additional Indoor module", "NAModule4": "Smart Additional Indoor module",
"NAModule3": "Smart Rain Gauge", "NAModule3": "Smart Rain Gauge",
"NAModule2": "Smart Anemometer", "NAModule2": "Smart Anemometer",
"NHC": "Home Coach",
} }
AUTH = "netatmo_auth" AUTH = "netatmo_auth"
@@ -32,6 +32,7 @@ CONF_CLOUDHOOK_URL = "cloudhook_url"
OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize" OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize"
OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token" OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token"
DATA_DEVICE_IDS = "netatmo_device_ids"
DATA_PERSONS = "netatmo_persons" DATA_PERSONS = "netatmo_persons"
NETATMO_WEBHOOK_URL = None NETATMO_WEBHOOK_URL = None

View File

@@ -77,7 +77,11 @@ PERSON_SCHEMA = vol.Schema(
) )
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{vol.Optional(DOMAIN): vol.All(cv.ensure_list, cv.remove_falsy, [PERSON_SCHEMA])}, {
vol.Optional(DOMAIN, default=[]): vol.All(
cv.ensure_list, cv.remove_falsy, [PERSON_SCHEMA]
)
},
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )

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.4"],
"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

@@ -562,18 +562,36 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) ->
_LOGGER.error(message) _LOGGER.error(message)
def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]: def _identify_config_schema(module: ModuleType) -> Optional[str]:
"""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
schema = module.CONFIG_SCHEMA.schema[key] # type: ignore
if hasattr(key, "default") and not isinstance(
key.default, vol.schema_builder.Undefined
):
default_value = module.CONFIG_SCHEMA({module.DOMAIN: key.default()})[ # type: ignore
module.DOMAIN # type: ignore
]
if isinstance(default_value, dict):
return "dict"
if isinstance(default_value, list):
return "list"
return 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"
if t_schema.startswith(("[", "All(<function ensure_list")): if t_schema.startswith(("[", "All(<function ensure_list")):
return ("list", schema) return "list"
return "", schema return None
def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]: def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]:
@@ -626,8 +644,7 @@ async def merge_packages_config(
merge_list = hasattr(component, "PLATFORM_SCHEMA") merge_list = hasattr(component, "PLATFORM_SCHEMA")
if not merge_list and hasattr(component, "CONFIG_SCHEMA"): if not merge_list and hasattr(component, "CONFIG_SCHEMA"):
merge_type, _ = _identify_config_schema(component) merge_list = _identify_config_schema(component) == "list"
merge_list = merge_type == "list"
if merge_list: if merge_list:
config[comp_name] = cv.remove_falsy( config[comp_name] = cv.remove_falsy(

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 = "4"
__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

@@ -139,7 +139,7 @@ aio_georss_gdacs==0.3
aioambient==1.0.4 aioambient==1.0.4
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.2.2 aioasuswrt==1.2.3
# homeassistant.components.automatic # homeassistant.components.automatic
aioautomatic==0.6.5 aioautomatic==0.6.5
@@ -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.4
# homeassistant.components.sisyphus # homeassistant.components.sisyphus
sisyphus-control==2.2.1 sisyphus-control==2.2.1

View File

@@ -50,7 +50,7 @@ aio_georss_gdacs==0.3
aioambient==1.0.4 aioambient==1.0.4
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.2.2 aioasuswrt==1.2.3
# homeassistant.components.automatic # homeassistant.components.automatic
aioautomatic==0.6.5 aioautomatic==0.6.5
@@ -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.4
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
sleepyq==0.7 sleepyq==0.7

View File

@@ -65,6 +65,7 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock):
"read_station", "read_station",
"read_thermostat", "read_thermostat",
"write_camera", "write_camera",
"write_presence",
"write_thermostat", "write_thermostat",
] ]
) )

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
@@ -721,7 +722,7 @@ async def test_merge_id_schema(hass):
for domain, expected_type in types.items(): for domain, expected_type in types.items():
integration = await async_get_integration(hass, domain) integration = await async_get_integration(hass, domain)
module = integration.get_component() module = integration.get_component()
typ, _ = config_util._identify_config_schema(module) typ = config_util._identify_config_schema(module)
assert typ == expected_type, f"{domain} expected {expected_type}, got {typ}" assert typ == expected_type, f"{domain} expected {expected_type}, got {typ}"
@@ -989,3 +990,35 @@ 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): [int]}), "list"),
("zone", vol.Schema({vol.Optional("zone", default=[]): [int]}), "list"),
(
"zone",
vol.Schema({vol.Optional("zone", default={}): {vol.Optional("hello"): 1}}),
"dict",
),
(
"zone",
vol.Schema(
{vol.Optional("zone", default=dict): {vol.Optional("hello"): 1}}
),
"dict",
),
("zone", vol.Schema({vol.Optional("zone"): int}), None),
("zone", vol.Schema({"zone": int}), None),
("not_existing", vol.Schema({vol.Optional("zone", default=dict): dict}), None,),
("non_existing", vol.Schema({"zone": int}), None),
("zone", vol.Schema({}), None),
],
)
def test_identify_config_schema(domain, schema, expected):
"""Test identify config schema."""
assert (
config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))
== expected
)