Compare commits

..

40 Commits

Author SHA1 Message Date
Paulus Schoutsen
677c276b41 Merge pull request #33118 from home-assistant/rc
0.107.5
2020-03-21 17:22:31 -07:00
Paulus Schoutsen
ca1c696f54 Bumped version to 0.107.5 2020-03-21 16:34:11 -07:00
Franck Nijhof
78e5878247 Fix Extend ONVIF unique ID with profile index (#33103) 2020-03-21 16:34:05 -07:00
guillempages
470537bc5f Fix tankerkoenig with more than 10 stations (#33098)
* Fix tankerkoenig with more than 10 stations

There seemed to be a problem, if more than 10 fuel stations were tracked.
Added a warning in this case, and split the calls to the API in chunks of
10, so that the data can be fetched anyway.

* Update homeassistant/components/tankerkoenig/__init__.py

Co-Authored-By: Martin Hjelmare <marhje52@gmail.com>

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2020-03-21 16:33:55 -07:00
Austin Mroczek
64556f6f69 Fix totalconnect AttributeError introduced in 0.107 (#33079)
* Fit git

* Remove period from loggging message

* Remove tests for now

* Add const.py

* Fix lint error
2020-03-21 16:32:26 -07:00
Alexei Chetroi
2785b067e3 Split ZHA device loading and entities adding (#33075)
* Split ZHA device loading and entities adding.
Load zha devices from ZHA gateway, but add entities after integration
was setup.

* Use hass.loop.create_task()
* Split ZHA device initialization from loading.
Restore and initialize ZHA devices separately.

* Use hass.async_create_task()
Load devices prior group initialization.
2020-03-21 16:32:26 -07:00
escoand
a129bc05ae Try all Samsung TV websocket ports (#33001)
* Update bridge.py

* add test

* silence pylint

* correct pylint

* add some tests

* Update test_media_player.py
2020-03-21 16:32:25 -07:00
Ville Skyttä
197736f66b Upgrade huawei-lte-api to 1.4.11 (#32791)
https://github.com/Salamek/huawei-lte-api/releases/tag/1.4.11
2020-03-21 15:16:33 -07:00
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
Paulus Schoutsen
88e3e73bb4 Merge pull request #32964 from home-assistant/rc
0.107.1
2020-03-18 22:14:25 -07:00
Paulus Schoutsen
3aa1bcbb77 Fix mobile app test 2020-03-18 22:13:49 -07:00
Paulus Schoutsen
f973b35cef Bumped version to 0.107.1 2020-03-18 18:19:33 -07:00
Paulus Schoutsen
4e08aa8b05 Fix zone config (#32963)
* Fix zone config

* Add zone as dependency again to device tracker

* Fix tests
2020-03-18 18:18:57 -07:00
Paulus Schoutsen
8e917ccf73 Add device automation as frontend dependency (#32962) 2020-03-18 18:17:11 -07:00
Bram Kragten
0b62011626 Updated frontend to 20200318.1 (#32957) 2020-03-18 18:17:11 -07:00
54 changed files with 534 additions and 258 deletions

View File

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

View File

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

View File

@@ -21,6 +21,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .axis_base import AxisEntityBase
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):
"""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_USERNAME: config_entry.data[CONF_USERNAME],
CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
CONF_MJPEG_URL: (
f"http://{config_entry.data[CONF_HOST]}"
f":{config_entry.data[CONF_PORT]}/axis-cgi/mjpg/video.cgi"
CONF_MJPEG_URL: AXIS_VIDEO.format(
host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
),
CONF_STILL_IMAGE_URL: (
f"http://{config_entry.data[CONF_HOST]}"
f":{config_entry.data[CONF_PORT]}/axis-cgi/jpg/image.cgi"
CONF_STILL_IMAGE_URL: AXIS_IMAGE.format(
host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
}
@@ -70,19 +72,17 @@ class AxisCamera(AxisEntityBase, MjpegCamera):
async def stream_source(self):
"""Return the stream source."""
return (
f"rtsp://{self.device.config_entry.data[CONF_USERNAME]}´"
f":{self.device.config_entry.data[CONF_PASSWORD]}"
f"@{self.device.host}/axis-media/media.amp?videocodec=h264"
return AXIS_STREAM.format(
user=self.device.config_entry.data[CONF_USERNAME],
password=self.device.config_entry.data[CONF_PASSWORD],
host=self.device.host,
)
def _new_address(self):
"""Set new device address for video stream."""
port = self.device.config_entry.data[CONF_PORT]
self._mjpeg_url = (f"http://{self.device.host}:{port}/axis-cgi/mjpg/video.cgi",)
self._still_image_url = (
f"http://{self.device.host}:{port}/axis-cgi/jpg/image.cgi"
)
self._mjpeg_url = AXIS_VIDEO.format(host=self.device.host, port=port)
self._still_image_url = AXIS_IMAGE.format(host=self.device.host, port=port)
@property
def unique_id(self):

View File

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

View File

@@ -3,8 +3,8 @@
"name": "Device Tracker",
"documentation": "https://www.home-assistant.io/integrations/device_tracker",
"requirements": [],
"dependencies": [],
"after_dependencies": ["zone"],
"dependencies": ["zone"],
"after_dependencies": [],
"codeowners": [],
"quality_scale": "internal"
}

View File

@@ -3,11 +3,12 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [
"home-assistant-frontend==20200318.0"
"home-assistant-frontend==20200318.1"
],
"dependencies": [
"api",
"auth",
"device_automation",
"http",
"lovelace",
"onboarding",
@@ -19,4 +20,4 @@
"@home-assistant/frontend"
],
"quality_scale": "internal"
}
}

View File

@@ -3,7 +3,7 @@
"name": "HomeKit Controller",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"requirements": ["aiohomekit[IP]==0.2.29.1"],
"requirements": ["aiohomekit[IP]==0.2.29.2"],
"dependencies": [],
"zeroconf": ["_hap._tcp.local."],
"codeowners": ["@Jc2k"]

View File

@@ -5,7 +5,7 @@
"documentation": "https://www.home-assistant.io/integrations/huawei_lte",
"requirements": [
"getmac==0.8.1",
"huawei-lte-api==1.4.10",
"huawei-lte-api==1.4.11",
"stringcase==1.2.0",
"url-normalize==1.4.1"
],

View File

@@ -28,6 +28,7 @@ from . import api, config_flow
from .const import (
AUTH,
CONF_CLOUDHOOK_URL,
DATA_DEVICE_IDS,
DATA_PERSONS,
DOMAIN,
OAUTH2_AUTHORIZE,
@@ -65,6 +66,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Netatmo component."""
hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_PERSONS] = {}
hass.data[DOMAIN][DATA_DEVICE_IDS] = {}
if DOMAIN not in config:
return True
@@ -104,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
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)
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)
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:
webhook_url = await hass.components.cloud.async_create_cloudhook(
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):
"""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(
await asyncio.gather(
*[
@@ -152,14 +162,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
]
)
)
if unload_ok:
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -505,4 +505,6 @@ class ONVIFHassCamera(Camera):
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
if self._profile_index:
return f"{self._mac}_{self._profile_index}"
return self._mac

View File

@@ -77,7 +77,11 @@ PERSON_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,
)

View File

@@ -25,6 +25,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import verify_domain_control
from .const import (
CONF_ZONE_RUN_TIME,
DATA_CLIENT,
DATA_PROGRAMS,
DATA_PROVISION_SETTINGS,
@@ -33,6 +34,8 @@ from .const import (
DATA_ZONES,
DATA_ZONES_DETAILS,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_ZONE_RUN,
DOMAIN,
PROGRAM_UPDATE_TOPIC,
SENSOR_UPDATE_TOPIC,
@@ -41,19 +44,14 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
DATA_LISTENER = "listener"
CONF_CONTROLLERS = "controllers"
CONF_PROGRAM_ID = "program_id"
CONF_SECONDS = "seconds"
CONF_ZONE_ID = "zone_id"
CONF_ZONE_RUN_TIME = "zone_run_time"
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
DEFAULT_ICON = "mdi:water"
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_SSL = True
DEFAULT_ZONE_RUN = 60 * 10
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."""
hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_CLIENT] = {}
hass.data[DOMAIN][DATA_LISTENER] = {}
if DOMAIN not in config:
return True
@@ -143,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
config_entry.data[CONF_IP_ADDRESS],
config_entry.data[CONF_PASSWORD],
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:
_LOGGER.error("An error occurred: %s", err)
@@ -156,8 +153,10 @@ async def async_setup_entry(hass, config_entry):
rainmachine = RainMachine(
hass,
controller,
config_entry.data[CONF_ZONE_RUN_TIME],
config_entry.data[CONF_SCAN_INTERVAL],
config_entry.data.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN),
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
@@ -260,9 +259,6 @@ async def async_unload_entry(hass, config_entry):
"""Unload an OpenUV config entry."""
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 = [
hass.config_entries.async_forward_entry_unload(config_entry, component)
for component in ("binary_sensor", "sensor", "switch")

View File

@@ -4,10 +4,22 @@ from regenmaschine.errors import RainMachineError
import voluptuous as vol
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 .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):
@@ -53,8 +65,8 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
user_input[CONF_IP_ADDRESS],
user_input[CONF_PASSWORD],
websession,
port=user_input.get(CONF_PORT, DEFAULT_PORT),
ssl=True,
port=user_input[CONF_PORT],
ssl=user_input.get(CONF_SSL, True),
)
except RainMachineError:
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
# store it:
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."""
from datetime import timedelta
DOMAIN = "rainmachine"
CONF_ZONE_RUN_TIME = "zone_run_time"
DATA_CLIENT = "client"
DATA_PROGRAMS = "programs"
DATA_PROVISION_SETTINGS = "provision.settings"
@@ -10,6 +14,8 @@ DATA_ZONES = "zones"
DATA_ZONES_DETAILS = "zones_details"
DEFAULT_PORT = 8080
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_ZONE_RUN = 60 * 10
PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update"
SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update"

View File

@@ -206,6 +206,7 @@ class SamsungTVWSBridge(SamsungTVBridge):
CONF_TIMEOUT: 31,
}
result = None
try:
LOGGER.debug("Try config: %s", config)
with SamsungTVWS(
@@ -223,9 +224,13 @@ class SamsungTVWSBridge(SamsungTVBridge):
return RESULT_SUCCESS
except WebSocketException:
LOGGER.debug("Working but unsupported config: %s", config)
return RESULT_NOT_SUPPORTED
result = RESULT_NOT_SUPPORTED
except (OSError, ConnectionFailure) as err:
LOGGER.debug("Failing config: %s, error: %s", config, err)
# pylint: disable=useless-else-on-loop
else:
if result:
return result
return RESULT_NOT_SUCCESSFUL

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
"""Ask tankerkoenig.de for petrol price information."""
from datetime import timedelta
import logging
from math import ceil
import pytankerkoenig
import voluptuous as vol
@@ -164,27 +165,41 @@ class TankerkoenigData:
)
return False
self.add_station(additional_station_data["station"])
if len(self.stations) > 10:
_LOGGER.warning(
"Found more than 10 stations to check. "
"This might invalidate your api-key on the long run. "
"Try using a smaller radius"
)
return True
async def fetch_data(self):
"""Get the latest data from tankerkoenig.de."""
_LOGGER.debug("Fetching new data from tankerkoenig.de")
station_ids = list(self.stations)
data = await self._hass.async_add_executor_job(
pytankerkoenig.getPriceList, self._api_key, station_ids
)
if data["ok"]:
prices = {}
# The API seems to only return at most 10 results, so split the list in chunks of 10
# and merge it together.
for index in range(ceil(len(station_ids) / 10)):
data = await self._hass.async_add_executor_job(
pytankerkoenig.getPriceList,
self._api_key,
station_ids[index * 10 : (index + 1) * 10],
)
_LOGGER.debug("Received data: %s", data)
if not data["ok"]:
_LOGGER.error(
"Error fetching data from tankerkoenig.de: %s", data["message"]
)
raise TankerkoenigError(data["message"])
if "prices" not in data:
_LOGGER.error("Did not receive price information from tankerkoenig.de")
raise TankerkoenigError("No prices in data")
else:
_LOGGER.error(
"Error fetching data from tankerkoenig.de: %s", data["message"]
)
raise TankerkoenigError(data["message"])
return data["prices"]
prices.update(data["prices"])
return prices
def add_station(self, station: dict):
"""Add fuel station to the entity list."""

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_ALARM_TRIGGERED,
)
from . import DOMAIN as TOTALCONNECT_DOMAIN
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
alarms = []
client = hass.data[TOTALCONNECT_DOMAIN].client
client = hass.data[DOMAIN].client
for location_id, location in client.locations.items():
location_name = location.location_name
@@ -71,7 +71,7 @@ class TotalConnectAlarm(alarm.AlarmControlPanel):
def update(self):
"""Return the state of the device."""
status = self._client.get_armed_status(self._location_id)
self._client.get_armed_status(self._location_id)
attr = {
"location_name": self._name,
"location_id": self._location_id,
@@ -79,47 +79,36 @@ class TotalConnectAlarm(alarm.AlarmControlPanel):
"low_battery": self._client.locations[self._location_id].low_battery,
"cover_tampered": self._client.locations[
self._location_id
].is_cover_tampered,
].is_cover_tampered(),
"triggered_source": None,
"triggered_zone": None,
}
if status in (self._client.DISARMED, self._client.DISARMED_BYPASS):
if self._client.locations[self._location_id].is_disarmed():
state = STATE_ALARM_DISARMED
elif status in (
self._client.ARMED_STAY,
self._client.ARMED_STAY_INSTANT,
self._client.ARMED_STAY_INSTANT_BYPASS,
):
elif self._client.locations[self._location_id].is_armed_home():
state = STATE_ALARM_ARMED_HOME
elif status == self._client.ARMED_STAY_NIGHT:
elif self._client.locations[self._location_id].is_armed_night():
state = STATE_ALARM_ARMED_NIGHT
elif status in (
self._client.ARMED_AWAY,
self._client.ARMED_AWAY_BYPASS,
self._client.ARMED_AWAY_INSTANT,
self._client.ARMED_AWAY_INSTANT_BYPASS,
):
elif self._client.locations[self._location_id].is_armed_away():
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_CUSTOM_BYPASS:
elif self._client.locations[self._location_id].is_armed_custom_bypass():
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
elif status == self._client.ARMING:
elif self._client.locations[self._location_id].is_arming():
state = STATE_ALARM_ARMING
elif status == self._client.DISARMING:
elif self._client.locations[self._location_id].is_disarming():
state = STATE_ALARM_DISARMING
elif status == self._client.ALARMING:
elif self._client.locations[self._location_id].is_triggered_police():
state = STATE_ALARM_TRIGGERED
attr["triggered_source"] = "Police/Medical"
elif status == self._client.ALARMING_FIRE_SMOKE:
elif self._client.locations[self._location_id].is_triggered_fire():
state = STATE_ALARM_TRIGGERED
attr["triggered_source"] = "Fire/Smoke"
elif status == self._client.ALARMING_CARBON_MONOXIDE:
elif self._client.locations[self._location_id].is_triggered_gas():
state = STATE_ALARM_TRIGGERED
attr["triggered_source"] = "Carbon Monoxide"
else:
logging.info(
"Total Connect Client returned unknown status code: %s", status
)
logging.info("Total Connect Client returned unknown status")
state = None
self._state = state

View File

@@ -0,0 +1,3 @@
"""TotalConnect constants."""
DOMAIN = "totalconnect"

View File

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

View File

@@ -8,6 +8,8 @@ import voluptuous as vol
from homeassistant import config_entries, const as ha_const
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import HomeAssistantType
from . import api
from .core import ZHAGateway
@@ -27,6 +29,7 @@ from .core.const import (
DEFAULT_BAUDRATE,
DEFAULT_RADIO_TYPE,
DOMAIN,
SIGNAL_ADD_ENTITIES,
RadioType,
)
@@ -89,24 +92,15 @@ async def async_setup_entry(hass, config_entry):
Will automatically load components to support devices found on the network.
"""
hass.data[DATA_ZHA] = hass.data.get(DATA_ZHA, {})
hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = []
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
platforms = []
zha_data = hass.data.setdefault(DATA_ZHA, {})
zha_data[DATA_ZHA_PLATFORM_LOADED] = {}
config = zha_data.get(DATA_ZHA_CONFIG, {})
zha_data[DATA_ZHA_DISPATCHERS] = []
for component in COMPONENTS:
platforms.append(
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, component)
)
)
async def _platforms_loaded():
await asyncio.gather(*platforms)
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].set()
hass.async_create_task(_platforms_loaded())
config = hass.data[DATA_ZHA].get(DATA_ZHA_CONFIG, {})
zha_data[component] = []
coro = hass.config_entries.async_forward_entry_setup(config_entry, component)
zha_data[DATA_ZHA_PLATFORM_LOADED][component] = hass.async_create_task(coro)
if config.get(CONF_ENABLE_QUIRKS, True):
# needs to be done here so that the ZHA module is finished loading
@@ -130,11 +124,11 @@ async def async_setup_entry(hass, config_entry):
async def async_zha_shutdown(event):
"""Handle shutdown tasks."""
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown()
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_update_device_storage()
await zha_data[DATA_ZHA_GATEWAY].shutdown()
await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage()
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(async_load_entities(hass, config_entry))
return True
@@ -152,3 +146,20 @@ async def async_unload_entry(hass, config_entry):
await hass.config_entries.async_forward_entry_unload(config_entry, component)
return True
async def async_load_entities(
hass: HomeAssistantType, config_entry: config_entries.ConfigEntry
) -> None:
"""Load entities after integration was setup."""
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_prepare_entities()
to_setup = [
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED][comp]
for comp in COMPONENTS
if hass.data[DATA_ZHA][comp]
]
results = await asyncio.gather(*to_setup, return_exceptions=True)
for res in results:
if isinstance(res, Exception):
_LOGGER.warning("Couldn't setup zha platform: %s", res)
async_dispatcher_send(hass, SIGNAL_ADD_ENTITIES)

View File

@@ -49,7 +49,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation binary sensor from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

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

View File

@@ -8,6 +8,16 @@ from homeassistant.core import callback
from homeassistant.helpers.typing import HomeAssistantType
from . import const as zha_const, registries as zha_regs, typing as zha_typing
from .. import ( # noqa: F401 pylint: disable=unused-import,
binary_sensor,
cover,
device_tracker,
fan,
light,
lock,
sensor,
switch,
)
from .channels import base
_LOGGER = logging.getLogger(__name__)

View File

@@ -7,10 +7,12 @@ import logging
import os
import traceback
from serial import SerialException
import zigpy.device as zigpy_dev
from homeassistant.components.system_log import LogEntry, _figure_out_source
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import (
CONNECTION_ZIGBEE,
async_get_registry as get_dev_reg,
@@ -34,7 +36,6 @@ from .const import (
DATA_ZHA,
DATA_ZHA_BRIDGE_ID,
DATA_ZHA_GATEWAY,
DATA_ZHA_PLATFORM_LOADED,
DEBUG_COMP_BELLOWS,
DEBUG_COMP_ZHA,
DEBUG_COMP_ZIGPY,
@@ -98,7 +99,6 @@ class ZHAGateway:
self.ha_entity_registry = None
self.application_controller = None
self.radio_description = None
hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
self._log_levels = {
DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(),
DEBUG_LEVEL_CURRENT: async_capture_log_levels(),
@@ -122,7 +122,11 @@ class ZHAGateway:
radio_details = RADIO_TYPES[radio_type]
radio = radio_details[ZHA_GW_RADIO]()
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:
database = self._config[CONF_DATABASE]
@@ -133,38 +137,59 @@ class ZHAGateway:
apply_application_controller_patch(self)
self.application_controller.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.application_controller.ieee
)
await self.async_load_devices()
self._initialize_groups()
async def async_load_devices(self) -> None:
"""Restore ZHA devices from zigpy application state."""
await self._hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].wait()
zigpy_devices = self.application_controller.devices.values()
for zigpy_device in zigpy_devices:
self._async_get_or_create_device(zigpy_device, restored=True)
async def async_prepare_entities(self) -> None:
"""Prepare entities by initializing device channels."""
semaphore = asyncio.Semaphore(2)
async def _throttle(device: zha_typing.ZigpyDeviceType):
async def _throttle(zha_device: zha_typing.ZhaDeviceType, cached: bool):
async with semaphore:
await self.async_device_restored(device)
await zha_device.async_initialize(from_cache=cached)
zigpy_devices = self.application_controller.devices.values()
_LOGGER.debug("Loading battery powered devices")
await asyncio.gather(
*[
_throttle(dev)
for dev in zigpy_devices
if not dev.node_desc.is_mains_powered
_throttle(dev, cached=True)
for dev in self.devices.values()
if not dev.is_mains_powered
]
)
async_dispatcher_send(self._hass, SIGNAL_ADD_ENTITIES)
_LOGGER.debug("Loading mains powered devices")
await asyncio.gather(
*[_throttle(dev) for dev in zigpy_devices if dev.node_desc.is_mains_powered]
*[
_throttle(dev, cached=False)
for dev in self.devices.values()
if dev.is_mains_powered
]
)
async_dispatcher_send(self._hass, SIGNAL_ADD_ENTITIES)
def device_joined(self, device):
"""Handle device joined.

View File

@@ -29,7 +29,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation cover from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -26,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation device tracker from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -53,7 +53,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation fan from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -52,7 +52,7 @@ _REFRESH_INTERVAL = (45, 75)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation light from config entry."""
entities_to_create = hass.data[DATA_ZHA][light.DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][light.DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -36,7 +36,7 @@ VALUE_TO_STATE = dict(enumerate(STATE_LIST))
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation Door Lock from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -68,7 +68,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation sensor from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -26,7 +26,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation switch from config entry."""
entities_to_create = hass.data[DATA_ZHA][DOMAIN] = []
entities_to_create = hass.data[DATA_ZHA][DOMAIN]
unsub = async_dispatcher_connect(
hass,

View File

@@ -1,6 +1,6 @@
"""Support for the definition of zones."""
import logging
from typing import Dict, Optional, cast
from typing import Any, Dict, Optional, cast
import voluptuous as vol
@@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_RADIUS,
EVENT_CORE_CONFIG_UPDATE,
SERVICE_RELOAD,
STATE_UNAVAILABLE,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall, State, callback
from homeassistant.helpers import (
@@ -65,8 +66,20 @@ UPDATE_FIELDS = {
}
def empty_value(value: Any) -> Any:
"""Test if the user has the default config value from adding "zone:"."""
if isinstance(value, dict) and len(value) == 0:
return []
raise vol.Invalid("Not a default value")
CONFIG_SCHEMA = vol.Schema(
{vol.Optional(DOMAIN): vol.All(cv.ensure_list, [vol.Schema(CREATE_FIELDS)])},
{
vol.Optional(DOMAIN, default=[]): vol.Any(
vol.All(cv.ensure_list, [vol.Schema(CREATE_FIELDS)]), empty_value,
)
},
extra=vol.ALLOW_EXTRA,
)
@@ -93,7 +106,7 @@ def async_active_zone(
closest = None
for zone in zones:
if zone.attributes.get(ATTR_PASSIVE):
if zone.state == STATE_UNAVAILABLE or zone.attributes.get(ATTR_PASSIVE):
continue
zone_dist = distance(
@@ -126,6 +139,9 @@ def in_zone(zone: State, latitude: float, longitude: float, radius: float = 0) -
Async friendly.
"""
if zone.state == STATE_UNAVAILABLE:
return False
zone_dist = distance(
latitude,
longitude,
@@ -180,7 +196,7 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool:
component, storage_collection, lambda conf: Zone(conf, True)
)
if DOMAIN in config:
if config[DOMAIN]:
await yaml_collection.async_load(config[DOMAIN])
await storage_collection.async_load()
@@ -206,7 +222,7 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool:
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(conf.get(DOMAIN, []))
await yaml_collection.async_load(conf[DOMAIN])
service.async_register_admin_service(
hass,

View File

@@ -562,18 +562,36 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) ->
_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."""
try:
schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore
except (AttributeError, KeyError):
return None, None
key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore
except (AttributeError, StopIteration):
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)
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")):
return ("list", schema)
return "", schema
return "list"
return None
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")
if not merge_list and hasattr(component, "CONFIG_SCHEMA"):
merge_type, _ = _identify_config_schema(component)
merge_list = merge_type == "list"
merge_list = _identify_config_schema(component) == "list"
if merge_list:
config[comp_name] = cv.remove_falsy(

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 107
PATCH_VERSION = "0"
PATCH_VERSION = "5"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0)

View File

@@ -12,7 +12,7 @@ cryptography==2.8
defusedxml==0.6.0
distro==1.4.0
hass-nabucasa==0.32.2
home-assistant-frontend==20200318.0
home-assistant-frontend==20200318.1
importlib-metadata==1.5.0
jinja2>=2.10.3
netdisco==2.6.0

View File

@@ -139,7 +139,7 @@ aio_georss_gdacs==0.3
aioambient==1.0.4
# homeassistant.components.asuswrt
aioasuswrt==1.2.2
aioasuswrt==1.2.3
# homeassistant.components.automatic
aioautomatic==0.6.5
@@ -163,7 +163,7 @@ aioftp==0.12.0
aioharmony==0.1.13
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.29.1
aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -696,7 +696,7 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200318.0
home-assistant-frontend==20200318.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.9
@@ -712,7 +712,7 @@ horimote==0.4.1
httplib2==0.10.3
# homeassistant.components.huawei_lte
huawei-lte-api==1.4.10
huawei-lte-api==1.4.11
# homeassistant.components.hydrawise
hydrawiser==0.1.1
@@ -1017,6 +1017,7 @@ pilight==0.1.1
# homeassistant.components.proxy
# homeassistant.components.qrcode
# homeassistant.components.seven_segments
# homeassistant.components.sighthound
# homeassistant.components.tensorflow
pillow==7.0.0
@@ -1665,7 +1666,7 @@ python-telnet-vlc==1.0.4
python-twitch-client==0.6.0
# homeassistant.components.velbus
python-velbus==2.0.42
python-velbus==2.0.43
# homeassistant.components.vlc
python-vlc==1.1.2
@@ -1855,7 +1856,7 @@ simplehound==0.3
simplepush==1.1.4
# homeassistant.components.simplisafe
simplisafe-python==9.0.2
simplisafe-python==9.0.4
# homeassistant.components.sisyphus
sisyphus-control==2.2.1

View File

@@ -50,7 +50,7 @@ aio_georss_gdacs==0.3
aioambient==1.0.4
# homeassistant.components.asuswrt
aioasuswrt==1.2.2
aioasuswrt==1.2.3
# homeassistant.components.automatic
aioautomatic==0.6.5
@@ -62,7 +62,7 @@ aiobotocore==0.11.1
aioesphomeapi==2.6.1
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.29.1
aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -263,7 +263,7 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200318.0
home-assistant-frontend==20200318.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.9
@@ -276,7 +276,7 @@ homematicip==0.10.17
httplib2==0.10.3
# homeassistant.components.huawei_lte
huawei-lte-api==1.4.10
huawei-lte-api==1.4.11
# homeassistant.components.iaqualink
iaqualink==0.3.1
@@ -365,6 +365,14 @@ pexpect==4.6.0
# homeassistant.components.pilight
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
plexapi==3.3.0
@@ -587,7 +595,7 @@ python-nest==4.1.0
python-twitch-client==0.6.0
# homeassistant.components.velbus
python-velbus==2.0.42
python-velbus==2.0.43
# homeassistant.components.awair
python_awair==0.0.4
@@ -641,7 +649,7 @@ sentry-sdk==0.13.5
simplehound==0.3
# homeassistant.components.simplisafe
simplisafe-python==9.0.2
simplisafe-python==9.0.4
# homeassistant.components.sleepiq
sleepyq==0.7

View File

@@ -140,16 +140,7 @@ async def test_webhook_update_registration(webhook_client, authed_api_client):
async def test_webhook_handle_get_zones(hass, create_registrations, webhook_client):
"""Test that we can get zones properly."""
await async_setup_component(
hass,
ZONE_DOMAIN,
{
ZONE_DOMAIN: {
"name": "test",
"latitude": 32.880837,
"longitude": -117.237561,
"radius": 250,
}
},
hass, ZONE_DOMAIN, {ZONE_DOMAIN: {}},
)
resp = await webhook_client.post(
@@ -160,10 +151,9 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie
assert resp.status == 200
json = await resp.json()
assert len(json) == 2
assert len(json) == 1
zones = sorted(json, key=lambda entry: entry["entity_id"])
assert zones[0]["entity_id"] == "zone.home"
assert zones[1]["entity_id"] == "zone.test"
async def test_webhook_handle_get_config(hass, create_registrations, webhook_client):

View File

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

View File

@@ -4,7 +4,7 @@ from unittest.mock import patch
from regenmaschine.errors import RainMachineError
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.const import (
CONF_IP_ADDRESS,
@@ -98,6 +98,7 @@ async def test_step_import(hass):
CONF_PORT: 8080,
CONF_SSL: True,
CONF_SCAN_INTERVAL: 60,
CONF_ZONE_RUN_TIME: 600,
}
@@ -129,4 +130,5 @@ async def test_step_user(hass):
CONF_PORT: 8080,
CONF_SSL: True,
CONF_SCAN_INTERVAL: 60,
CONF_ZONE_RUN_TIME: 600,
}

View File

@@ -1,5 +1,5 @@
"""Tests for Samsung TV config flow."""
from unittest.mock import call, patch
from unittest.mock import Mock, PropertyMock, call, patch
from asynctest import mock
import pytest
@@ -19,7 +19,7 @@ from homeassistant.components.ssdp import (
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
)
from homeassistant.const import CONF_HOST, CONF_ID, CONF_METHOD, CONF_NAME
from homeassistant.const import CONF_HOST, CONF_ID, CONF_METHOD, CONF_NAME, CONF_TOKEN
MOCK_USER_DATA = {CONF_HOST: "fake_host", CONF_NAME: "fake_name"}
MOCK_SSDP_DATA = {
@@ -46,6 +46,20 @@ AUTODETECT_LEGACY = {
"host": "fake_host",
"timeout": 31,
}
AUTODETECT_WEBSOCKET_PLAIN = {
"host": "fake_host",
"name": "HomeAssistant",
"port": 8001,
"timeout": 31,
"token": None,
}
AUTODETECT_WEBSOCKET_SSL = {
"host": "fake_host",
"name": "HomeAssistant",
"port": 8002,
"timeout": 31,
"token": None,
}
@pytest.fixture(name="remote")
@@ -446,20 +460,48 @@ async def test_autodetect_websocket(hass, remote, remotews):
with patch(
"homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"),
), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews:
enter = Mock()
type(enter).token = PropertyMock(return_value="123456789")
remote = Mock()
remote.__enter__ = Mock(return_value=enter)
remote.__exit__ = Mock(return_value=False)
remotews.return_value = remote
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789"
assert remotews.call_count == 1
assert remotews.call_args_list == [call(**AUTODETECT_WEBSOCKET_PLAIN)]
async def test_autodetect_websocket_ssl(hass, remote, remotews):
"""Test for send key with autodetection of protocol."""
with patch(
"homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"),
), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS",
side_effect=[WebSocketProtocolException("Boom"), mock.DEFAULT],
) as remotews:
enter = Mock()
type(enter).token = PropertyMock(return_value="123456789")
remote = Mock()
remote.__enter__ = Mock(return_value=enter)
remote.__exit__ = Mock(return_value=False)
remotews.return_value = remote
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA
)
assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789"
assert remotews.call_count == 2
assert remotews.call_args_list == [
call(
host="fake_host",
name="HomeAssistant",
port=8001,
timeout=31,
token=None,
)
call(**AUTODETECT_WEBSOCKET_PLAIN),
call(**AUTODETECT_WEBSOCKET_SSL),
]
@@ -524,18 +566,6 @@ async def test_autodetect_none(hass, remote, remotews):
]
assert remotews.call_count == 2
assert remotews.call_args_list == [
call(
host="fake_host",
name="HomeAssistant",
port=8001,
timeout=31,
token=None,
),
call(
host="fake_host",
name="HomeAssistant",
port=8002,
timeout=31,
token=None,
),
call(**AUTODETECT_WEBSOCKET_PLAIN),
call(**AUTODETECT_WEBSOCKET_SSL),
]

View File

@@ -34,8 +34,11 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME,
ATTR_SUPPORTED_FEATURES,
CONF_HOST,
CONF_IP_ADDRESS,
CONF_METHOD,
CONF_NAME,
CONF_PORT,
CONF_TOKEN,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
@@ -51,7 +54,7 @@ from homeassistant.const import (
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed
from tests.common import MockConfigEntry, async_fire_time_changed
ENTITY_ID = f"{DOMAIN}.fake"
MOCK_CONFIG = {
@@ -64,17 +67,40 @@ MOCK_CONFIG = {
}
]
}
MOCK_CONFIGWS = {
SAMSUNGTV_DOMAIN: [
{
CONF_HOST: "fake",
CONF_NAME: "fake",
CONF_PORT: 8001,
CONF_TOKEN: "123456789",
CONF_ON_ACTION: [{"delay": "00:00:01"}],
}
]
}
MOCK_CALLS_WS = {
"host": "fake",
"port": 8001,
"token": None,
"timeout": 31,
"name": "HomeAssistant",
}
MOCK_ENTRY_WS = {
CONF_IP_ADDRESS: "test",
CONF_HOST: "fake",
CONF_METHOD: "websocket",
CONF_NAME: "fake",
CONF_PORT: 8001,
CONF_TOKEN: "abcde",
}
MOCK_CALLS_ENTRY_WS = {
"host": "fake",
"name": "HomeAssistant",
"port": 8001,
"timeout": 1,
"token": "abcde",
}
ENTITY_ID_NOTURNON = f"{DOMAIN}.fake_noturnon"
MOCK_CONFIG_NOTURNON = {
@@ -155,6 +181,52 @@ async def test_setup_without_turnon(hass, remote):
assert hass.states.get(ENTITY_ID_NOTURNON)
async def test_setup_websocket(hass, remotews, mock_now):
"""Test setup of platform."""
with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class:
enter = mock.Mock()
type(enter).token = mock.PropertyMock(return_value="987654321")
remote = mock.Mock()
remote.__enter__ = mock.Mock(return_value=enter)
remote.__exit__ = mock.Mock()
remote_class.return_value = remote
await setup_samsungtv(hass, MOCK_CONFIGWS)
assert remote_class.call_count == 1
assert remote_class.call_args_list == [call(**MOCK_CALLS_WS)]
assert hass.states.get(ENTITY_ID)
async def test_setup_websocket_2(hass, mock_now):
"""Test setup of platform from config entry."""
entity_id = f"{DOMAIN}.fake"
entry = MockConfigEntry(
domain=SAMSUNGTV_DOMAIN, data=MOCK_ENTRY_WS, unique_id=entity_id,
)
entry.add_to_hass(hass)
config_entries = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN)
assert len(config_entries) == 1
assert entry is config_entries[0]
assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {})
await hass.async_block_till_done()
next_update = mock_now + timedelta(minutes=5)
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remote, patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert remote.call_count == 1
assert remote.call_args_list == [call(**MOCK_CALLS_ENTRY_WS)]
async def test_update_on(hass, remote, mock_now):
"""Testing update tv on."""
await setup_samsungtv(hass, MOCK_CONFIG)

View File

@@ -108,7 +108,7 @@ async def test_no_clients(hass):
"""Test the update_clients function when no clients are found."""
await setup_unifi_integration(hass)
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("device_tracker")) == 0
async def test_tracked_devices(hass):
@@ -123,7 +123,7 @@ async def test_tracked_devices(hass):
devices_response=[DEVICE_1, DEVICE_2],
known_wireless_clients=(CLIENT_4["mac"],),
)
assert len(hass.states.async_all()) == 6
assert len(hass.states.async_entity_ids("device_tracker")) == 6
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -184,7 +184,7 @@ async def test_controller_state_change(hass):
controller = await setup_unifi_integration(
hass, clients_response=[CLIENT_1], devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 2
assert len(hass.states.async_entity_ids("device_tracker")) == 2
# Controller unavailable
controller.async_unifi_signalling_callback(
@@ -214,7 +214,7 @@ async def test_option_track_clients(hass):
controller = await setup_unifi_integration(
hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 3
assert len(hass.states.async_entity_ids("device_tracker")) == 3
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -259,7 +259,7 @@ async def test_option_track_wired_clients(hass):
controller = await setup_unifi_integration(
hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 3
assert len(hass.states.async_entity_ids("device_tracker")) == 3
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -304,7 +304,7 @@ async def test_option_track_devices(hass):
controller = await setup_unifi_integration(
hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 3
assert len(hass.states.async_entity_ids("device_tracker")) == 3
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -349,7 +349,7 @@ async def test_option_ssid_filter(hass):
controller = await setup_unifi_integration(
hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3],
)
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("device_tracker")) == 1
# SSID filter active
client_3 = hass.states.get("device_tracker.client_3")
@@ -387,7 +387,7 @@ async def test_wireless_client_go_wired_issue(hass):
client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow())
controller = await setup_unifi_integration(hass, clients_response=[client_1_client])
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("device_tracker")) == 1
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -460,7 +460,7 @@ async def test_restoring_client(hass):
clients_response=[CLIENT_2],
clients_all_response=[CLIENT_1],
)
assert len(hass.states.async_all()) == 2
assert len(hass.states.async_entity_ids("device_tracker")) == 2
device_1 = hass.states.get("device_tracker.client_1")
assert device_1 is not None
@@ -474,7 +474,7 @@ async def test_dont_track_clients(hass):
clients_response=[CLIENT_1],
devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("device_tracker")) == 1
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is None
@@ -492,7 +492,7 @@ async def test_dont_track_devices(hass):
clients_response=[CLIENT_1],
devices_response=[DEVICE_1],
)
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("device_tracker")) == 1
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None
@@ -509,7 +509,7 @@ async def test_dont_track_wired_clients(hass):
options={unifi.controller.CONF_TRACK_WIRED_CLIENTS: False},
clients_response=[CLIENT_1, CLIENT_2],
)
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("device_tracker")) == 1
client_1 = hass.states.get("device_tracker.client_1")
assert client_1 is not None

View File

@@ -55,7 +55,7 @@ async def test_no_clients(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("sensor")) == 0
async def test_sensors(hass):
@@ -71,7 +71,7 @@ async def test_sensors(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 4
assert len(hass.states.async_entity_ids("sensor")) == 4
wired_client_rx = hass.states.get("sensor.wired_client_name_rx")
assert wired_client_rx.state == "1234.0"

View File

@@ -209,7 +209,7 @@ async def test_no_clients(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("switch")) == 0
async def test_controller_not_client(hass):
@@ -222,7 +222,7 @@ async def test_controller_not_client(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("switch")) == 0
cloudkey = hass.states.get("switch.cloud_key")
assert cloudkey is None
@@ -240,7 +240,7 @@ async def test_not_admin(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("switch")) == 0
async def test_switches(hass):
@@ -258,7 +258,7 @@ async def test_switches(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 3
assert len(hass.states.async_entity_ids("switch")) == 3
switch_1 = hass.states.get("switch.poe_client_1")
assert switch_1 is not None
@@ -312,7 +312,7 @@ async def test_new_client_discovered_on_block_control(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("switch")) == 0
blocked = hass.states.get("switch.block_client_1")
assert blocked is None
@@ -324,7 +324,7 @@ async def test_new_client_discovered_on_block_control(hass):
controller.api.session_handler("data")
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("switch")) == 1
blocked = hass.states.get("switch.block_client_1")
assert blocked is not None
@@ -336,7 +336,7 @@ async def test_option_block_clients(hass):
options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]},
clients_all_response=[BLOCKED, UNBLOCKED],
)
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("switch")) == 1
# Add a second switch
hass.config_entries.async_update_entry(
@@ -344,28 +344,28 @@ async def test_option_block_clients(hass):
options={CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]]},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
assert len(hass.states.async_entity_ids("switch")) == 2
# Remove the second switch again
hass.config_entries.async_update_entry(
controller.config_entry, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("switch")) == 1
# Enable one and remove another one
hass.config_entries.async_update_entry(
controller.config_entry, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("switch")) == 1
# Remove one
hass.config_entries.async_update_entry(
controller.config_entry, options={CONF_BLOCK_CLIENT: []},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
assert len(hass.states.async_entity_ids("switch")) == 0
async def test_new_client_discovered_on_poe_control(hass):
@@ -378,7 +378,7 @@ async def test_new_client_discovered_on_poe_control(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 1
assert len(hass.states.async_entity_ids("switch")) == 1
controller.api.websocket._data = {
"meta": {"message": "sta:sync"},
@@ -391,7 +391,7 @@ async def test_new_client_discovered_on_poe_control(hass):
"switch", "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True
)
assert len(controller.mock_requests) == 5
assert len(hass.states.async_all()) == 2
assert len(hass.states.async_entity_ids("switch")) == 2
assert controller.mock_requests[4] == {
"json": {
"port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}]
@@ -430,7 +430,7 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 3
assert len(hass.states.async_entity_ids("device_tracker")) == 3
switch_1 = hass.states.get("switch.poe_client_1")
switch_2 = hass.states.get("switch.poe_client_2")
@@ -481,7 +481,7 @@ async def test_restoring_client(hass):
)
assert len(controller.mock_requests) == 4
assert len(hass.states.async_all()) == 2
assert len(hass.states.async_entity_ids("switch")) == 2
device_1 = hass.states.get("switch.client_1")
assert device_1 is not None

View File

@@ -487,3 +487,18 @@ async def test_import_config_entry(hass):
assert state.attributes[zone.ATTR_RADIUS] == 3
assert state.attributes[zone.ATTR_PASSIVE] is False
assert state.attributes[ATTR_ICON] == "mdi:from-config-entry"
async def test_zone_empty_setup(hass):
"""Set up zone with empty config."""
assert await setup.async_setup_component(hass, DOMAIN, {"zone": {}})
async def test_unavailable_zone(hass):
"""Test active zone with unavailable zones."""
assert await setup.async_setup_component(hass, DOMAIN, {"zone": {}})
hass.states.async_set("zone.bla", "unavailable", {"restored": True})
assert zone.async_active_zone(hass, 0.0, 0.01) is None
assert zone.in_zone(hass.states.get("zone.bla"), 0, 0) is False

View File

@@ -10,6 +10,7 @@ from unittest.mock import Mock
import asynctest
from asynctest import CoroutineMock, patch
import pytest
import voluptuous as vol
from voluptuous import Invalid, MultipleInvalid
import yaml
@@ -721,7 +722,7 @@ async def test_merge_id_schema(hass):
for domain, expected_type in types.items():
integration = await async_get_integration(hass, domain)
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}"
@@ -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"
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
)