Compare commits

...

78 Commits

Author SHA1 Message Date
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
Franck Nijhof
d520a02b8c Merge pull request #32932 from home-assistant/rc
0.107.0
2020-03-18 15:35:44 +01:00
SukramJ
1e469b39ad Fix flaky tests for HMIPC (#32806) 2020-03-18 14:44:29 +01:00
Franck Nijhof
c2f615839d Bumped version to 0.107.0 2020-03-18 13:31:02 +01:00
Bram Kragten
657bf33e32 Updated frontend to 20200318.0 (#32931) 2020-03-18 13:27:21 +01:00
Paulus Schoutsen
0ca87007fd Bumped version to 0.107.0b8 2020-03-17 17:56:18 -07:00
Paulus Schoutsen
d0d9d853f2 Fix hassio panel load (#32922)
* Fix loading hassio panel

* Remove blacklist
2020-03-17 17:55:57 -07:00
Hans Oischinger
8348878e7e Introduce safe scan_interval for vicare (#32915) 2020-03-17 17:55:56 -07:00
Paulus Schoutsen
b70be5f2f2 Blacklist auto_backup (#32912)
* Blacklist auto_backup

* Mock with a set
2020-03-17 17:55:55 -07:00
Bram Kragten
fddb565e4c Fix input text reload (#32911)
* Fix input text reload

* FIx schema instead
2020-03-17 17:55:54 -07:00
Rami Mosleh
f3e6820042 Fix setting up options due to config data freeze (#32872) 2020-03-17 17:55:54 -07:00
Paulus Schoutsen
ae98f13181 Bumped version to 0.107.0b7 2020-03-17 10:36:59 -07:00
Paulus Schoutsen
ab38e7d98a Bump cast to 4.2.0 (#32906) 2020-03-17 10:35:39 -07:00
brubaked
9797b09d44 Changed Sensor icons to be more emotionally sensitive (#32904)
The existing sensor icons, while descriptive - dead = dead - are perhaps too matter of fact and don't accurately convey the tragedy. I changed emoticon-dead-outline to emoticon-cry-outline, as I think it better conveys the reality of the situation along with the emotions tied to the statistic.
2020-03-17 10:35:39 -07:00
Quentame
4908d4358c Bump iCloud to 0.9.5 (#32901) 2020-03-17 10:35:38 -07:00
Paulus Schoutsen
67d728fc50 Make zone dependency of device tracker an after dep (#32880)
* Make zone dependency of device tracker an after dep

* Fix test
2020-03-17 10:34:34 -07:00
Jason Lachowsky
912409ed0c Corrected minor misspellings (#32857) 2020-03-17 10:32:01 -07:00
Paolo Tuninetto
ac8c889b0f Add default port to samsung tv (#32820)
* Default port for websocket tv

* Update config entry

* move bridge creation

* fix indent

* remove loop
2020-03-17 10:32:00 -07:00
Quentame
67a721d39b Fix iCloud init while pending (#32750)
* Fix iCloud init while pending

Continue if device is pending while setup
Create devices and fetch 15s if pending, otherwise determine interval to fetch.

* Add retried_fetch guard
2020-03-17 10:32:00 -07:00
Paulus Schoutsen
d196fd136d Bumped version to 0.107.0b6 2020-03-16 14:53:13 -07:00
Bram Kragten
4f78674a4c Updated frontend to 20200316.1 (#32878) 2020-03-16 14:53:05 -07:00
Bram Kragten
a7aca10668 Lovelace: storage key based on id instead of url_path (#32873)
* Fix storage key based on url_path

* Fix test
2020-03-16 14:53:04 -07:00
Paulus Schoutsen
03b1c6ddee Remove group as a dependency from entity integrations (#32870)
* remove group dependency

* Update device sun light trigger

* Add zone dep back to device tracker
2020-03-16 14:53:03 -07:00
David F. Mulcahey
661f1b69f2 Bump ZHA quirks to 0.0.37 (#32867) 2020-03-16 14:53:03 -07:00
Bram Kragten
ccb34083fe Add lovelace reload service for yaml resources (#32865)
* Lovelace add reload service for yaml resources

* Clean up imports

* Comments
2020-03-16 14:53:02 -07:00
Bram Kragten
7f6b3c1130 Bumped version to 0.107.0b5 2020-03-16 13:59:27 +01:00
Bram Kragten
f2c3f76b8e Updated frontend to 20200316.0 (#32866) 2020-03-16 13:49:50 +01:00
Pascal Vizeli
b6a3bcf87f Update pyozw 0.1.9 (#32864) 2020-03-16 13:49:49 +01:00
Tom Harris
65423bb62b Bump insteonplm to 0.16.8 (#32847) 2020-03-16 13:49:48 +01:00
Kit Klein
104665d849 Ignore the ignored konnected config entries (#32845)
* ignore the ignored konnected config entries

* key off data instead of source
2020-03-16 13:49:47 +01:00
Alan Tse
fb1ba86b08 Bump teslajsonpy to 0.5.1 (#32827) 2020-03-16 13:49:47 +01:00
David Bonnes
cee72724b6 Ensure unique_ids for all evohome thermostats (#32604)
* initial commit

* small tweak
2020-03-16 13:49:46 +01:00
Paulus Schoutsen
a3d74651a8 Bumped version to 0.107.0b4 2020-03-15 11:56:56 -07:00
Paulus Schoutsen
d88275d6d2 Make sure panel_custom won't crash on invalid data (#32835)
* Make sure panel_custom won't crash on invalid data

* Add a test
2020-03-15 11:52:54 -07:00
SukramJ
42998f898b Add SF transition to HmIP-BSL and remove obsolete code in HMIPC (#32833) 2020-03-15 11:52:53 -07:00
Daniel Høyer Iversen
875671cc2b Add Netatmo Home Coach as model (#32829) 2020-03-15 11:52:53 -07:00
Bram Kragten
3b84b6e6d5 Require a hyphen in lovelace dashboard url (#32816)
* Require a hyphen in lovelace dashboard url

* Keep storage dashboards working

* register during startup again

* Update homeassistant/components/lovelace/dashboard.py

Co-Authored-By: Paulus Schoutsen <balloob@gmail.com>

* Comments

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-03-15 11:52:52 -07:00
Chris Talkington
3b1fb2f416 Remove extra logging from directv init. (#32809) 2020-03-15 11:52:51 -07:00
Chris Talkington
226a0bcaad Fix directv location of unknown error string (#32807)
* Update strings.json

* Update en.json
2020-03-15 11:52:50 -07:00
Greg
57dd45318d Bump eagle_reader API version to v0.2.4 (#32789) 2020-03-15 11:52:50 -07:00
Franck Nijhof
e666485ea9 Fix brightness_pct in light device turn_on action (#32787) 2020-03-15 11:52:49 -07:00
Aidan Timson
b5c8b5b91f Fix onvif error with non ptz cameras (#32783) 2020-03-15 11:52:48 -07:00
David F. Mulcahey
706607f1d2 Fix handling of attribute reports in ZHA sensors and binary sensors (#32776)
* Update sensor tests.

* Update light tests.

* Update binary_sensor tests.

* Update cover tests.

* Update device tracker tests.

* Update fan tests.

* Update lock tests.

* Update switch tests.

* add sensor attr to sensors

* add sensor attr to binary sensors

* cleanup extra var

Co-authored-by: Alexei Chetroi <alexei.chetroi@outlook.com>
2020-03-15 11:52:47 -07:00
Steven Looman
0788bbd629 Add log message on timeout and update less often for upnp devices (#32740)
* Catch asyncio.TimeoutError, show a proper message instead

* Throttle updates to max once per 30s

* Change code owner

* Fix CODEOWNERS + linting

* Warn on connection timeout
2020-03-15 11:52:46 -07:00
Chris Talkington
1b622925a1 Optimize directv client initialization (#32706)
* Optimize directv client initialization.

* Update config_flow.py

* Update media_player.py

* Update media_player.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update test_media_player.py

* Update __init__.py

* Update media_player.py

* Update test_media_player.py

* Update media_player.py

* Update test_media_player.py

* Update config_flow.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update __init__.py

* Update test_config_flow.py

* Update test_config_flow.py

* Update test_media_player.py

* Update test_media_player.py

* Update __init__.py

* Update __init__.py

* Update __init__.py
2020-03-15 11:52:45 -07:00
Slava
86c4fa0fc5 Add brightness state to emulated hue when devices support only color temp and brightness (#31834) 2020-03-15 11:52:45 -07:00
Jc2k
e365dc398b Fix homekit_controller beta connectivity issues (#32810) 2020-03-14 15:43:09 -04:00
Bram Kragten
dfd29e6d73 Bumped version to 0.107.0b3 2020-03-13 22:23:34 +01:00
Bram Kragten
6780bded7e Updated frontend to 20200313.0 (#32777) 2020-03-13 22:19:22 +01:00
Paulus Schoutsen
56686dd14c Bumped version to 0.107.0b2 2020-03-13 12:16:55 -07:00
Bram Kragten
7268bcd9be Check if panel url used and delay dashboard reg till start (#32771)
* Check if panel url used and delay dashboard reg till start

* move storage_dashboard_changed

* fix tests
2020-03-13 12:16:49 -07:00
J. Nick Koston
4f78e04315 Bump py-august to 0.25.0 (#32769)
Fixes a bug in the conversion to async where code validation failed.
2020-03-13 12:16:49 -07:00
Austin Mroczek
7bdac8ef2e Bump total-connect-client to 0.54.1 #32758) 2020-03-13 12:16:48 -07:00
120 changed files with 868 additions and 610 deletions

View File

@@ -386,7 +386,7 @@ homeassistant/components/unifiled/* @florisvdk
homeassistant/components/upc_connect/* @pvizeli
homeassistant/components/upcloud/* @scop
homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @robbiet480
homeassistant/components/upnp/* @StevenLooman
homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes

View File

@@ -3,7 +3,7 @@
"name": "August",
"documentation": "https://www.home-assistant.io/integrations/august",
"requirements": [
"py-august==0.24.0"
"py-august==0.25.0"
],
"dependencies": [
"configurator"

View File

@@ -3,7 +3,8 @@
"name": "Automation",
"documentation": "https://www.home-assistant.io/integrations/automation",
"requirements": [],
"dependencies": ["device_automation", "group", "webhook"],
"dependencies": [],
"after_dependencies": ["device_automation", "webhook"],
"codeowners": ["@home-assistant/core"],
"quality_scale": "internal"
}

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,7 +3,7 @@
"name": "Google Cast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==4.1.1"],
"requirements": ["pychromecast==4.2.0"],
"dependencies": [],
"after_dependencies": ["cloud"],
"zeroconf": ["_googlecast._tcp.local."],

View File

@@ -7,9 +7,9 @@ from .const import ATTRIBUTION, OPTION_WORLDWIDE
SENSORS = {
"confirmed": "mdi:emoticon-neutral-outline",
"current": "mdi:emoticon-frown-outline",
"current": "mdi:emoticon-sad-outline",
"recovered": "mdi:emoticon-happy-outline",
"deaths": "mdi:emoticon-dead-outline",
"deaths": "mdi:emoticon-cry-outline",
}

View File

@@ -3,7 +3,7 @@
"name": "Cover",
"documentation": "https://www.home-assistant.io/integrations/cover",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": ["@home-assistant/core"],
"quality_scale": "internal"
}

View File

@@ -3,7 +3,8 @@
"name": "Presence-based Lights",
"documentation": "https://www.home-assistant.io/integrations/device_sun_light_trigger",
"requirements": [],
"dependencies": ["device_tracker", "group", "light", "person"],
"dependencies": [],
"after_dependencies": ["device_tracker", "group", "light", "person"],
"codeowners": [],
"quality_scale": "internal"
}

View File

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

View File

@@ -1,11 +1,11 @@
{
"config": {
"abort": {
"already_configured": "DirecTV receiver is already configured"
"already_configured": "DirecTV receiver is already configured",
"unknown": "Unexpected error"
},
"error": {
"cannot_connect": "Failed to connect, please try again",
"unknown": "Unexpected error"
"cannot_connect": "Failed to connect, please try again"
},
"flow_title": "DirecTV: {name}",
"step": {
@@ -23,4 +23,4 @@
},
"title": "DirecTV"
}
}
}

View File

@@ -32,7 +32,7 @@ def get_dtv_data(
hass: HomeAssistant, host: str, port: int = DEFAULT_PORT, client_addr: str = "0"
) -> dict:
"""Retrieve a DIRECTV instance, locations list, and version info for the receiver device."""
dtv = DIRECTV(host, port, client_addr)
dtv = DIRECTV(host, port, client_addr, determine_state=False)
locations = dtv.get_locations()
version_info = dtv.get_version()

View File

@@ -29,8 +29,7 @@ def validate_input(data: Dict) -> Dict:
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
# directpy does IO in constructor.
dtv = DIRECTV(data["host"], DEFAULT_PORT)
dtv = DIRECTV(data["host"], DEFAULT_PORT, determine_state=False)
version_info = dtv.get_version()
return {
@@ -76,8 +75,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN):
return self._show_form(errors)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = ERROR_UNKNOWN
return self._show_form(errors)
return self.async_abort(reason=ERROR_UNKNOWN)
await self.async_set_unique_id(info["receiver_id"])
self._abort_if_unique_id_configured()

View File

@@ -83,22 +83,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
def get_dtv_instance(
host: str, port: int = DEFAULT_PORT, client_addr: str = "0"
) -> DIRECTV:
"""Retrieve a DIRECTV instance for the receiver or client device."""
try:
return DIRECTV(host, port, client_addr)
except RequestException as exception:
_LOGGER.debug(
"Request exception %s trying to retrieve DIRECTV instance for client address %s on device %s",
exception,
client_addr,
host,
)
return None
async def async_setup_entry(
hass: HomeAssistantType,
entry: ConfigEntry,
@@ -114,16 +98,15 @@ async def async_setup_entry(
continue
if loc["clientAddr"] != "0":
# directpy does IO in constructor.
dtv = await hass.async_add_executor_job(
get_dtv_instance, entry.data[CONF_HOST], DEFAULT_PORT, loc["clientAddr"]
dtv = DIRECTV(
entry.data[CONF_HOST],
DEFAULT_PORT,
loc["clientAddr"],
determine_state=False,
)
else:
dtv = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT]
if not dtv:
continue
entities.append(
DirecTvDevice(
str.title(loc["locationName"]), loc["clientAddr"], dtv, version_info,
@@ -175,15 +158,6 @@ class DirecTvDevice(MediaPlayerDevice):
self._model = MODEL_HOST
self._software_version = version_info["stbSoftwareVersion"]
if self._is_client:
_LOGGER.debug(
"Created DirecTV media player for client %s on device %s",
self._name,
device,
)
else:
_LOGGER.debug("Created DirecTV media player for device %s", self._name)
def update(self):
"""Retrieve latest state."""
_LOGGER.debug("%s: Updating status", self.entity_id)

View File

@@ -16,11 +16,11 @@
}
},
"error": {
"cannot_connect": "Failed to connect, please try again",
"unknown": "Unexpected error"
"cannot_connect": "Failed to connect, please try again"
},
"abort": {
"already_configured": "DirecTV receiver is already configured"
"already_configured": "DirecTV receiver is already configured",
"unknown": "Unexpected error"
}
}
}

View File

@@ -677,7 +677,11 @@ def entity_to_json(config, entity):
retval["type"] = "Color temperature light"
retval["modelid"] = "HASS312"
retval["state"].update(
{HUE_API_STATE_COLORMODE: "ct", HUE_API_STATE_CT: state[STATE_COLOR_TEMP]}
{
HUE_API_STATE_COLORMODE: "ct",
HUE_API_STATE_CT: state[STATE_COLOR_TEMP],
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
}
)
elif entity_features & (
SUPPORT_BRIGHTNESS

View File

@@ -149,7 +149,12 @@ class EvoZone(EvoChild, EvoClimateDevice):
"""Initialize a Honeywell TCC Zone."""
super().__init__(evo_broker, evo_device)
self._unique_id = evo_device.zoneId
if evo_device.modelType.startswith("VisionProWifi"):
# this system does not have a distinct ID for the zone
self._unique_id = f"{evo_device.zoneId}z"
else:
self._unique_id = evo_device.zoneId
self._name = evo_device.name
self._icon = "mdi:radiator"

View File

@@ -3,7 +3,7 @@
"name": "Fan",
"documentation": "https://www.home-assistant.io/integrations/fan",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": [],
"quality_scale": "internal"
}

View File

@@ -276,8 +276,7 @@ async def async_setup(hass, config):
hass.http.app.router.register_resource(IndexView(repo_path, hass))
for panel in ("kiosk", "states", "profile"):
async_register_built_in_panel(hass, panel)
async_register_built_in_panel(hass, "profile")
# To smooth transition to new urls, add redirects to new urls of dev tools
# Added June 27, 2019. Can be removed in 2021.

View File

@@ -3,11 +3,12 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [
"home-assistant-frontend==20200312.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

@@ -190,16 +190,15 @@ async def async_setup(hass, config):
hass.http.register_view(HassIOView(host, websession))
if "frontend" in hass.config.components:
await hass.components.panel_custom.async_register_panel(
frontend_url_path="hassio",
webcomponent_name="hassio-main",
sidebar_title="Supervisor",
sidebar_icon="hass:home-assistant",
js_url="/api/hassio/app/entrypoint.js",
embed_iframe=True,
require_admin=True,
)
await hass.components.panel_custom.async_register_panel(
frontend_url_path="hassio",
webcomponent_name="hassio-main",
sidebar_title="Supervisor",
sidebar_icon="hass:home-assistant",
js_url="/api/hassio/app/entrypoint.js",
embed_iframe=True,
require_admin=True,
)
await hassio.update_hass_api(config.get("http", {}), refresh_token)

View File

@@ -3,6 +3,7 @@
"name": "Hass.io",
"documentation": "https://www.home-assistant.io/hassio",
"requirements": [],
"dependencies": ["http", "panel_custom"],
"dependencies": ["http"],
"after_dependencies": ["panel_custom"],
"codeowners": ["@home-assistant/hass-io"]
}

View File

@@ -14,7 +14,7 @@
"busy_error": "Device refused to add pairing as it is already pairing with another controller.",
"max_peers_error": "Device refused to add pairing as it has no free pairing storage.",
"max_tries_error": "Device refused to add pairing as it has received more than 100 unsuccessful authentication attempts.",
"pairing_failed": "An unhandled error occured while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently.",
"pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently.",
"unable_to_pair": "Unable to pair, please try again.",
"unknown_error": "Device reported an unknown error. Pairing failed."
},

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

View File

@@ -154,7 +154,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
homekit_state = self.service.value(CharacteristicsTypes.CURRENT_MEDIA_STATE)
if homekit_state is not None:
return HK_TO_HA_STATE[homekit_state]
return HK_TO_HA_STATE.get(homekit_state, STATE_OK)
return STATE_OK

View File

@@ -25,7 +25,7 @@
"max_peers_error": "Device refused to add pairing as it has no free pairing storage.",
"busy_error": "Device refused to add pairing as it is already pairing with another controller.",
"max_tries_error": "Device refused to add pairing as it has received more than 100 unsuccessful authentication attempts.",
"pairing_failed": "An unhandled error occured while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently."
"pairing_failed": "An unhandled error occurred while attempting to pair with this device. This may be a temporary failure or your device may not be supported currently."
},
"abort": {
"no_devices": "No unpaired devices could be found",

View File

@@ -137,11 +137,6 @@ class HomematicipHAP:
job = self.hass.async_create_task(self.get_state())
job.add_done_callback(self.get_state_finished)
self._accesspoint_connected = True
else:
# Update home with the given json from arg[0],
# without devices and groups.
self.home.update_home_only(args[0])
@callback
def async_create_entity(self, *args, **kwargs) -> None:

View File

@@ -20,6 +20,7 @@ from homeassistant.components.light import (
ATTR_TRANSITION,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_TRANSITION,
Light,
)
from homeassistant.config_entries import ConfigEntry
@@ -197,7 +198,7 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light):
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_TRANSITION
@property
def unique_id(self) -> str:

View File

@@ -97,6 +97,7 @@ class IcloudAccount:
self._owner_fullname = None
self._family_members_fullname = {}
self._devices = {}
self._retried_fetch = False
self.listeners = []
@@ -122,10 +123,6 @@ class IcloudAccount:
_LOGGER.error("No iCloud device found")
raise ConfigEntryNotReady
if DEVICE_STATUS_CODES.get(list(api_devices)[0][DEVICE_STATUS]) == "pending":
_LOGGER.warning("Pending devices, trying again ...")
raise ConfigEntryNotReady
self._owner_fullname = f"{user_info['firstName']} {user_info['lastName']}"
self._family_members_fullname = {}
@@ -157,28 +154,15 @@ class IcloudAccount:
)
return
if DEVICE_STATUS_CODES.get(list(api_devices)[0][DEVICE_STATUS]) == "pending":
_LOGGER.warning("Pending devices, trying again in 15s")
self._fetch_interval = 0.25
dispatcher_send(self.hass, self.signal_device_update)
track_point_in_utc_time(
self.hass,
self.keep_alive,
utcnow() + timedelta(minutes=self._fetch_interval),
)
return
# Gets devices infos
new_device = False
for device in api_devices:
status = device.status(DEVICE_STATUS_SET)
device_id = status[DEVICE_ID]
device_name = status[DEVICE_NAME]
device_status = DEVICE_STATUS_CODES.get(status[DEVICE_STATUS], "error")
if (
device_status == "pending"
or status[DEVICE_BATTERY_STATUS] == "Unknown"
status[DEVICE_BATTERY_STATUS] == "Unknown"
or status.get(DEVICE_BATTERY_LEVEL) is None
):
continue
@@ -198,7 +182,16 @@ class IcloudAccount:
self._devices[device_id].update(status)
new_device = True
self._fetch_interval = self._determine_interval()
if (
DEVICE_STATUS_CODES.get(list(api_devices)[0][DEVICE_STATUS]) == "pending"
and not self._retried_fetch
):
_LOGGER.warning("Pending devices, trying again in 15s")
self._fetch_interval = 0.25
self._retried_fetch = True
else:
self._fetch_interval = self._determine_interval()
self._retried_fetch = False
dispatcher_send(self.hass, self.signal_device_update)
if new_device:

View File

@@ -3,7 +3,7 @@
"name": "Apple iCloud",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/icloud",
"requirements": ["pyicloud==0.9.4"],
"requirements": ["pyicloud==0.9.5"],
"dependencies": [],
"codeowners": ["@Quentame"]
}

View File

@@ -88,24 +88,22 @@ def _cv_input_text(cfg):
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
vol.Any(
vol.All(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MIN, default=CONF_MIN_VALUE): vol.Coerce(int),
vol.Optional(CONF_MAX, default=CONF_MAX_VALUE): vol.Coerce(int),
vol.Optional(CONF_INITIAL, ""): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_PATTERN): cv.string,
vol.Optional(CONF_MODE, default=MODE_TEXT): vol.In(
[MODE_TEXT, MODE_PASSWORD]
),
},
_cv_input_text,
),
None,
)
vol.All(
lambda value: value or {},
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MIN, default=CONF_MIN_VALUE): vol.Coerce(int),
vol.Optional(CONF_MAX, default=CONF_MAX_VALUE): vol.Coerce(int),
vol.Optional(CONF_INITIAL, ""): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_PATTERN): cv.string,
vol.Optional(CONF_MODE, default=MODE_TEXT): vol.In(
[MODE_TEXT, MODE_PASSWORD]
),
},
_cv_input_text,
),
)
},
extra=vol.ALLOW_EXTRA,
@@ -203,13 +201,6 @@ class InputText(RestoreEntity):
@classmethod
def from_yaml(cls, config: typing.Dict) -> "InputText":
"""Return entity instance initialized from yaml storage."""
# set defaults for empty config
config = {
CONF_MAX: CONF_MAX_VALUE,
CONF_MIN: CONF_MIN_VALUE,
CONF_MODE: MODE_TEXT,
**config,
}
input_text = cls(config)
input_text.entity_id = f"{DOMAIN}.{config[CONF_ID]}"
input_text.editable = False

View File

@@ -2,7 +2,7 @@
"domain": "insteon",
"name": "Insteon",
"documentation": "https://www.home-assistant.io/integrations/insteon",
"requirements": ["insteonplm==0.16.7"],
"requirements": ["insteonplm==0.16.8"],
"dependencies": [],
"codeowners": []
}

View File

@@ -306,6 +306,7 @@ class KonnectedView(HomeAssistantView):
[
entry.data[CONF_ACCESS_TOKEN]
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.data.get(CONF_ACCESS_TOKEN)
]
)
if auth is None or not next(

View File

@@ -15,7 +15,7 @@ from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from . import ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_STEP_PCT, DOMAIN, SUPPORT_BRIGHTNESS
from . import ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS_STEP_PCT, DOMAIN, SUPPORT_BRIGHTNESS
TYPE_BRIGHTNESS_INCREASE = "brightness_increase"
TYPE_BRIGHTNESS_DECREASE = "brightness_decrease"
@@ -28,7 +28,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
toggle_entity.DEVICE_ACTION_TYPES
+ [TYPE_BRIGHTNESS_INCREASE, TYPE_BRIGHTNESS_DECREASE]
),
vol.Optional(ATTR_BRIGHTNESS): vol.All(
vol.Optional(ATTR_BRIGHTNESS_PCT): vol.All(
vol.Coerce(int), vol.Range(min=0, max=100)
),
}
@@ -57,8 +57,8 @@ async def async_call_action_from_config(
data[ATTR_BRIGHTNESS_STEP_PCT] = 10
elif config[CONF_TYPE] == TYPE_BRIGHTNESS_DECREASE:
data[ATTR_BRIGHTNESS_STEP_PCT] = -10
elif ATTR_BRIGHTNESS in config:
data[ATTR_BRIGHTNESS] = config[ATTR_BRIGHTNESS]
elif ATTR_BRIGHTNESS_PCT in config:
data[ATTR_BRIGHTNESS_PCT] = config[ATTR_BRIGHTNESS_PCT]
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=context
@@ -125,7 +125,7 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di
return {
"extra_fields": vol.Schema(
{
vol.Optional(ATTR_BRIGHTNESS): vol.All(
vol.Optional(ATTR_BRIGHTNESS_PCT): vol.All(
vol.Coerce(int), vol.Range(min=0, max=100)
)
}

View File

@@ -3,7 +3,7 @@
"name": "Light",
"documentation": "https://www.home-assistant.io/integrations/light",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": [],
"quality_scale": "internal"
}

View File

@@ -3,7 +3,7 @@
"name": "Lock",
"documentation": "https://www.home-assistant.io/integrations/lock",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": [],
"quality_scale": "internal"
}

View File

@@ -4,10 +4,14 @@ import logging
import voluptuous as vol
from homeassistant.components import frontend
from homeassistant.config import async_hass_config_yaml, async_process_component_config
from homeassistant.const import CONF_FILENAME
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import collection, config_validation as cv
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType, ServiceCallType
from homeassistant.loader import async_get_integration
from homeassistant.util import sanitize_filename
from . import dashboard, resources, websocket
@@ -25,8 +29,10 @@ from .const import (
MODE_STORAGE,
MODE_YAML,
RESOURCE_CREATE_FIELDS,
RESOURCE_RELOAD_SERVICE_SCHEMA,
RESOURCE_SCHEMA,
RESOURCE_UPDATE_FIELDS,
SERVICE_RELOAD_RESOURCES,
STORAGE_DASHBOARD_CREATE_FIELDS,
STORAGE_DASHBOARD_UPDATE_FIELDS,
url_slug,
@@ -62,29 +68,41 @@ CONFIG_SCHEMA = vol.Schema(
)
async def async_setup(hass, config):
async def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the Lovelace commands."""
mode = config[DOMAIN][CONF_MODE]
yaml_resources = config[DOMAIN].get(CONF_RESOURCES)
frontend.async_register_built_in_panel(hass, DOMAIN, config={"mode": mode})
async def reload_resources_service_handler(service_call: ServiceCallType) -> None:
"""Reload yaml resources."""
try:
conf = await async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(err)
return
integration = await async_get_integration(hass, DOMAIN)
config = await async_process_component_config(hass, conf, integration)
resource_collection = await create_yaml_resource_col(
hass, config[DOMAIN].get(CONF_RESOURCES)
)
hass.data[DOMAIN]["resources"] = resource_collection
if mode == MODE_YAML:
default_config = dashboard.LovelaceYAML(hass, None, None)
resource_collection = await create_yaml_resource_col(hass, yaml_resources)
if yaml_resources is None:
try:
ll_conf = await default_config.async_load(False)
except HomeAssistantError:
pass
else:
if CONF_RESOURCES in ll_conf:
_LOGGER.warning(
"Resources need to be specified in your configuration.yaml. Please see the docs."
)
yaml_resources = ll_conf[CONF_RESOURCES]
resource_collection = resources.ResourceYAMLCollection(yaml_resources or [])
async_register_admin_service(
hass,
DOMAIN,
SERVICE_RELOAD_RESOURCES,
reload_resources_service_handler,
schema=RESOURCE_RELOAD_SERVICE_SCHEMA,
)
else:
default_config = dashboard.LovelaceStorage(hass, None)
@@ -127,25 +145,12 @@ async def async_setup(hass, config):
# We store a dictionary mapping url_path: config. None is the default.
"dashboards": {None: default_config},
"resources": resource_collection,
"yaml_dashboards": config[DOMAIN].get(CONF_DASHBOARDS, {}),
}
if hass.config.safe_mode:
return True
# Process YAML dashboards
for url_path, dashboard_conf in config[DOMAIN].get(CONF_DASHBOARDS, {}).items():
# For now always mode=yaml
config = dashboard.LovelaceYAML(hass, url_path, dashboard_conf)
hass.data[DOMAIN]["dashboards"][url_path] = config
try:
_register_panel(hass, url_path, MODE_YAML, dashboard_conf, False)
except ValueError:
_LOGGER.warning("Panel url path %s is not unique", url_path)
# Process storage dashboards
dashboards_collection = dashboard.DashboardsCollection(hass)
async def storage_dashboard_changed(change_type, item_id, item):
"""Handle a storage dashboard change."""
url_path = item[CONF_URL_PATH]
@@ -156,6 +161,7 @@ async def async_setup(hass, config):
return
if change_type == collection.CHANGE_ADDED:
existing = hass.data[DOMAIN]["dashboards"].get(url_path)
if existing:
@@ -180,6 +186,20 @@ async def async_setup(hass, config):
except ValueError:
_LOGGER.warning("Failed to %s panel %s from storage", change_type, url_path)
# Process YAML dashboards
for url_path, dashboard_conf in hass.data[DOMAIN]["yaml_dashboards"].items():
# For now always mode=yaml
config = dashboard.LovelaceYAML(hass, url_path, dashboard_conf)
hass.data[DOMAIN]["dashboards"][url_path] = config
try:
_register_panel(hass, url_path, MODE_YAML, dashboard_conf, False)
except ValueError:
_LOGGER.warning("Panel url path %s is not unique", url_path)
# Process storage dashboards
dashboards_collection = dashboard.DashboardsCollection(hass)
dashboards_collection.async_add_listener(storage_dashboard_changed)
await dashboards_collection.async_load()
@@ -194,6 +214,24 @@ async def async_setup(hass, config):
return True
async def create_yaml_resource_col(hass, yaml_resources):
"""Create yaml resources collection."""
if yaml_resources is None:
default_config = dashboard.LovelaceYAML(hass, None, None)
try:
ll_conf = await default_config.async_load(False)
except HomeAssistantError:
pass
else:
if CONF_RESOURCES in ll_conf:
_LOGGER.warning(
"Resources need to be specified in your configuration.yaml. Please see the docs."
)
yaml_resources = ll_conf[CONF_RESOURCES]
return resources.ResourceYAMLCollection(yaml_resources or [])
async def system_health_info(hass):
"""Get info for the info page."""
return await hass.data[DOMAIN]["dashboards"][None].async_get_info()

View File

@@ -41,6 +41,9 @@ RESOURCE_UPDATE_FIELDS = {
vol.Optional(CONF_URL): cv.string,
}
SERVICE_RELOAD_RESOURCES = "reload_resources"
RESOURCE_RELOAD_SERVICE_SCHEMA = vol.Schema({})
CONF_TITLE = "title"
CONF_REQUIRE_ADMIN = "require_admin"
CONF_SHOW_IN_SIDEBAR = "show_in_sidebar"
@@ -76,6 +79,8 @@ def url_slug(value: Any) -> str:
"""Validate value is a valid url slug."""
if value is None:
raise vol.Invalid("Slug should not be None")
if "-" not in value:
raise vol.Invalid("Url path needs to contain a hyphen (-)")
str_value = str(value)
slg = slugify(str_value, separator="-")
if str_value == slg:

View File

@@ -3,9 +3,11 @@ from abc import ABC, abstractmethod
import logging
import os
import time
from typing import Optional, cast
import voluptuous as vol
from homeassistant.components.frontend import DATA_PANELS
from homeassistant.const import CONF_FILENAME
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
@@ -86,7 +88,7 @@ class LovelaceStorage(LovelaceConfig):
storage_key = CONFIG_STORAGE_KEY_DEFAULT
else:
url_path = config[CONF_URL_PATH]
storage_key = CONFIG_STORAGE_KEY.format(url_path)
storage_key = CONFIG_STORAGE_KEY.format(config["id"])
super().__init__(hass, url_path, config)
@@ -229,10 +231,32 @@ class DashboardsCollection(collection.StorageCollection):
_LOGGER,
)
async def _async_load_data(self) -> Optional[dict]:
"""Load the data."""
data = await self.store.async_load()
if data is None:
return cast(Optional[dict], data)
updated = False
for item in data["items"] or []:
if "-" not in item[CONF_URL_PATH]:
updated = True
item[CONF_URL_PATH] = f"lovelace-{item[CONF_URL_PATH]}"
if updated:
await self.store.async_save(data)
return cast(Optional[dict], data)
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
if data[CONF_URL_PATH] in self.hass.data[DOMAIN]["dashboards"]:
raise vol.Invalid("Dashboard url path needs to be unique")
if "-" not in data[CONF_URL_PATH]:
raise vol.Invalid("Url path needs to contain a hyphen (-)")
if data[CONF_URL_PATH] in self.hass.data[DATA_PANELS]:
raise vol.Invalid("Panel url path needs to be unique")
return self.CREATE_SCHEMA(data)

View File

@@ -0,0 +1,4 @@
# Describes the format for available lovelace services
reload_resources:
description: Reload Lovelace resources from yaml configuration.

View File

@@ -332,16 +332,17 @@ class MikrotikHub:
async def async_add_options(self):
"""Populate default options for Mikrotik."""
if not self.config_entry.options:
data = dict(self.config_entry.data)
options = {
CONF_ARP_PING: self.config_entry.data.pop(CONF_ARP_PING, False),
CONF_FORCE_DHCP: self.config_entry.data.pop(CONF_FORCE_DHCP, False),
CONF_DETECTION_TIME: self.config_entry.data.pop(
CONF_ARP_PING: data.pop(CONF_ARP_PING, False),
CONF_FORCE_DHCP: data.pop(CONF_FORCE_DHCP, False),
CONF_DETECTION_TIME: data.pop(
CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME
),
}
self.hass.config_entries.async_update_entry(
self.config_entry, options=options
self.config_entry, data=data, options=options
)
async def request_update(self):

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,6 +14,7 @@ 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",
@@ -31,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

@@ -375,7 +375,7 @@ class ONVIFHassCamera(Camera):
def setup_ptz(self):
"""Set up PTZ if available."""
_LOGGER.debug("Setting up the ONVIF PTZ service")
if self._camera.get_service("ptz") is None:
if self._camera.get_service("ptz", create=False) is None:
_LOGGER.debug("PTZ is not available")
else:
self._ptz_service = self._camera.create_ptz_service()

View File

@@ -146,8 +146,6 @@ async def async_setup(hass, config):
if DOMAIN not in config:
return True
success = False
for panel in config[DOMAIN]:
name = panel[CONF_COMPONENT_NAME]
@@ -182,8 +180,13 @@ async def async_setup(hass, config):
hass.http.register_static_path(url, panel_path)
kwargs["html_url"] = url
await async_register_panel(hass, **kwargs)
try:
await async_register_panel(hass, **kwargs)
except ValueError as err:
_LOGGER.error(
"Unable to register panel %s: %s",
panel.get(CONF_SIDEBAR_TITLE, name),
err,
)
success = True
return success
return True

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

@@ -3,7 +3,7 @@
"name": "Plant Monitor",
"documentation": "https://www.home-assistant.io/integrations/plant",
"requirements": [],
"dependencies": ["group", "zone"],
"dependencies": [],
"after_dependencies": ["recorder"],
"codeowners": ["@ChristianKuehnel"],
"quality_scale": "internal"

View File

@@ -3,7 +3,7 @@
"name": "Rainforest Eagle-200",
"documentation": "https://www.home-assistant.io/integrations/rainforest_eagle",
"requirements": [
"eagle200_reader==0.2.1",
"eagle200_reader==0.2.4",
"uEagle==0.0.1"
],
"dependencies": [],

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

@@ -3,6 +3,6 @@
"name": "Remote",
"documentation": "https://www.home-assistant.io/integrations/remote",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": []
}

View File

@@ -46,6 +46,7 @@ class SamsungTVBridge(ABC):
self.method = method
self.host = host
self.token = None
self.default_port = None
self._remote = None
self._callback = None
@@ -191,6 +192,7 @@ class SamsungTVWSBridge(SamsungTVBridge):
"""Initialize Bridge."""
super().__init__(method, host, port)
self.token = token
self.default_port = 8001
def try_connect(self):
"""Try to connect to the Websocket TV."""

View File

@@ -71,13 +71,27 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
):
turn_on_action = hass.data[DOMAIN][ip_address][CONF_ON_ACTION]
on_script = Script(hass, turn_on_action)
async_add_entities([SamsungTVDevice(config_entry, on_script)])
# Initialize bridge
data = config_entry.data.copy()
bridge = SamsungTVBridge.get_bridge(
data[CONF_METHOD], data[CONF_HOST], data[CONF_PORT], data.get(CONF_TOKEN),
)
if bridge.port is None and bridge.default_port is not None:
# For backward compat, set default port for websocket tv
data[CONF_PORT] = bridge.default_port
hass.config_entries.async_update_entry(config_entry, data=data)
bridge = SamsungTVBridge.get_bridge(
data[CONF_METHOD], data[CONF_HOST], data[CONF_PORT], data.get(CONF_TOKEN),
)
async_add_entities([SamsungTVDevice(bridge, config_entry, on_script)])
class SamsungTVDevice(MediaPlayerDevice):
"""Representation of a Samsung TV."""
def __init__(self, config_entry, on_script):
def __init__(self, bridge, config_entry, on_script):
"""Initialize the Samsung device."""
self._config_entry = config_entry
self._manufacturer = config_entry.data.get(CONF_MANUFACTURER)
@@ -93,13 +107,7 @@ class SamsungTVDevice(MediaPlayerDevice):
# Mark the end of a shutdown command (need to wait 15 seconds before
# sending the next command to avoid turning the TV back ON).
self._end_of_power_off = None
# Initialize bridge
self._bridge = SamsungTVBridge.get_bridge(
config_entry.data[CONF_METHOD],
config_entry.data[CONF_HOST],
config_entry.data[CONF_PORT],
config_entry.data.get(CONF_TOKEN),
)
self._bridge = bridge
self._bridge.register_reauth_callback(self.access_denied)
def access_denied(self):

View File

@@ -3,7 +3,7 @@
"name": "Scripts",
"documentation": "https://www.home-assistant.io/integrations/script",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": ["@home-assistant/core"],
"quality_scale": "internal"
}

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

@@ -3,7 +3,7 @@
"name": "Switch",
"documentation": "https://www.home-assistant.io/integrations/switch",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": [],
"quality_scale": "internal"
}

View File

@@ -91,7 +91,7 @@ class LogEntry:
def __init__(self, record, stack, source):
"""Initialize a log entry."""
self.first_occured = self.timestamp = record.created
self.first_occurred = self.timestamp = record.created
self.name = record.name
self.level = record.levelname
self.message = deque([record.getMessage()], maxlen=5)
@@ -117,7 +117,7 @@ class LogEntry:
"timestamp": self.timestamp,
"exception": self.exception,
"count": self.count,
"first_occured": self.first_occured,
"first_occurred": self.first_occurred,
}

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tesla",
"requirements": [
"teslajsonpy==0.4.0"
"teslajsonpy==0.5.1"
],
"dependencies": [],
"codeowners": [

View File

@@ -5,7 +5,7 @@
"client_secret": "The client secret from the configuration is invalid.",
"no_agreements": "This account has no Toon displays.",
"no_app": "You need to configure Toon before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/toon/).",
"unknown_auth_fail": "Unexpected error occured, while authenticating."
"unknown_auth_fail": "Unexpected error occurred, while authenticating."
},
"error": {
"credentials": "The provided credentials are invalid.",

View File

@@ -26,7 +26,7 @@
"abort": {
"client_id": "The client ID from the configuration is invalid.",
"client_secret": "The client secret from the configuration is invalid.",
"unknown_auth_fail": "Unexpected error occured, while authenticating.",
"unknown_auth_fail": "Unexpected error occurred, while authenticating.",
"no_agreements": "This account has no Toon displays.",
"no_app": "You need to configure Toon before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/toon/)."
}

View File

@@ -2,7 +2,7 @@
"domain": "totalconnect",
"name": "Honeywell Total Connect Alarm",
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
"requirements": ["total_connect_client==0.53"],
"requirements": ["total_connect_client==0.54.1"],
"dependencies": [],
"codeowners": ["@austinmroczek"]
}

View File

@@ -142,16 +142,28 @@ class Device:
async def async_get_total_bytes_received(self):
"""Get total bytes received."""
return await self._igd_device.async_get_total_bytes_received()
try:
return await self._igd_device.async_get_total_bytes_received()
except asyncio.TimeoutError:
_LOGGER.warning("Timeout during get_total_bytes_received")
async def async_get_total_bytes_sent(self):
"""Get total bytes sent."""
return await self._igd_device.async_get_total_bytes_sent()
try:
return await self._igd_device.async_get_total_bytes_sent()
except asyncio.TimeoutError:
_LOGGER.warning("Timeout during get_total_bytes_sent")
async def async_get_total_packets_received(self):
"""Get total packets received."""
return await self._igd_device.async_get_total_packets_received()
try:
return await self._igd_device.async_get_total_packets_received()
except asyncio.TimeoutError:
_LOGGER.warning("Timeout during get_total_packets_received")
async def async_get_total_packets_sent(self):
"""Get total packets sent."""
return await self._igd_device.async_get_total_packets_sent()
try:
return await self._igd_device.async_get_total_packets_sent()
except asyncio.TimeoutError:
_LOGGER.warning("Timeout during get_total_packets_sent")

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/upnp",
"requirements": ["async-upnp-client==0.14.12"],
"dependencies": [],
"codeowners": ["@robbiet480"]
"codeowners": ["@StevenLooman"]
}

View File

@@ -1,4 +1,5 @@
"""Support for UPnP/IGD Sensors."""
from datetime import timedelta
import logging
from homeassistant.const import DATA_BYTES, DATA_KIBIBYTES, TIME_SECONDS
@@ -7,6 +8,7 @@ from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
from .const import DOMAIN as DOMAIN_UPNP, SIGNAL_REMOVE_SENSOR
@@ -29,6 +31,8 @@ IN = "received"
OUT = "sent"
KIBIBYTE = 1024
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
async def async_setup_platform(
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
@@ -142,6 +146,7 @@ class RawUPnPIGDSensor(UpnpSensor):
"""Return the unit of measurement of this entity, if any."""
return self._type["unit"]
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
"""Get the latest information from the IGD."""
if self._type_name == BYTES_RECEIVED:

View File

@@ -3,6 +3,6 @@
"name": "Vacuum",
"documentation": "https://www.home-assistant.io/integrations/vacuum",
"requirements": [],
"dependencies": ["group"],
"dependencies": [],
"codeowners": []
}

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

@@ -1,4 +1,5 @@
"""Viessmann ViCare climate device."""
from datetime import timedelta
import logging
import requests
@@ -79,6 +80,9 @@ HA_TO_VICARE_PRESET_HEATING = {
PYVICARE_ERROR = "error"
# Scan interval of 15 minutes seems to be safe to not hit the ViCare server rate limit
SCAN_INTERVAL = timedelta(seconds=900)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the ViCare climate devices."""

View File

@@ -1,4 +1,5 @@
"""Viessmann ViCare water_heater device."""
from datetime import timedelta
import logging
import requests
@@ -42,6 +43,9 @@ HA_TO_VICARE_HVAC_DHW = {
PYVICARE_ERROR = "error"
# Scan interval of 15 minutes seems to be safe to not hit the ViCare server rate limit
SCAN_INTERVAL = timedelta(seconds=900)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the ViCare water_heater devices."""

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.
"""
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()
zha_data = hass.data.setdefault(DATA_ZHA, {})
config = zha_data.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()
zha_data[DATA_ZHA_DISPATCHERS] = []
zha_data[DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
platforms = []
for component in COMPONENTS:
platforms.append(
@@ -102,20 +112,10 @@ async def async_setup_entry(hass, config_entry):
async def _platforms_loaded():
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())
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.async_get_or_create(
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):
"""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())

View File

@@ -64,6 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class BinarySensor(ZhaEntity, BinarySensorDevice):
"""ZHA BinarySensor."""
SENSOR_ATTR = None
DEVICE_CLASS = None
def __init__(self, unique_id, zha_device, channels, **kwargs):
@@ -105,6 +106,8 @@ class BinarySensor(ZhaEntity, BinarySensorDevice):
@callback
def async_set_state(self, attr_id, attr_name, value):
"""Set the state."""
if self.SENSOR_ATTR is None or self.SENSOR_ATTR != attr_name:
return
self._state = bool(value)
self.async_write_ha_state()
@@ -121,6 +124,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice):
class Accelerometer(BinarySensor):
"""ZHA BinarySensor."""
SENSOR_ATTR = "acceleration"
DEVICE_CLASS = DEVICE_CLASS_MOVING
@@ -128,6 +132,7 @@ class Accelerometer(BinarySensor):
class Occupancy(BinarySensor):
"""ZHA BinarySensor."""
SENSOR_ATTR = "occupancy"
DEVICE_CLASS = DEVICE_CLASS_OCCUPANCY
@@ -135,6 +140,7 @@ class Occupancy(BinarySensor):
class Opening(BinarySensor):
"""ZHA BinarySensor."""
SENSOR_ATTR = "on_off"
DEVICE_CLASS = DEVICE_CLASS_OPENING
@@ -142,6 +148,8 @@ class Opening(BinarySensor):
class IASZone(BinarySensor):
"""ZHA IAS BinarySensor."""
SENSOR_ATTR = "zone_status"
async def get_device_class(self) -> None:
"""Get the HA device class from the channel."""
zone_type = await self._channel.get_attribute_value("zone_type")

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

@@ -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,
@@ -98,7 +100,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 +123,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,7 +138,22 @@ 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
)

View File

@@ -5,7 +5,7 @@
"documentation": "https://www.home-assistant.io/integrations/zha",
"requirements": [
"bellows-homeassistant==0.14.0",
"zha-quirks==0.0.36",
"zha-quirks==0.0.37",
"zigpy-cc==0.1.0",
"zigpy-deconz==0.7.0",
"zigpy-homeassistant==0.16.0",

View File

@@ -83,6 +83,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class Sensor(ZhaEntity):
"""Base ZHA sensor."""
SENSOR_ATTR = None
_decimals = 1
_device_class = None
_divisor = 1
@@ -126,6 +127,8 @@ class Sensor(ZhaEntity):
@callback
def async_set_state(self, attr_id, attr_name, value):
"""Handle state update from channel."""
if self.SENSOR_ATTR is None or self.SENSOR_ATTR != attr_name:
return
if value is not None:
value = self.formatter(value)
self._state = value
@@ -154,6 +157,7 @@ class Sensor(ZhaEntity):
class AnalogInput(Sensor):
"""Sensor that displays analog input values."""
SENSOR_ATTR = "present_value"
pass
@@ -161,6 +165,7 @@ class AnalogInput(Sensor):
class Battery(Sensor):
"""Battery sensor of power configuration cluster."""
SENSOR_ATTR = "battery_percentage_remaining"
_device_class = DEVICE_CLASS_BATTERY
_unit = UNIT_PERCENTAGE
@@ -198,6 +203,7 @@ class Battery(Sensor):
class ElectricalMeasurement(Sensor):
"""Active power measurement."""
SENSOR_ATTR = "active_power"
_device_class = DEVICE_CLASS_POWER
_divisor = 10
_unit = POWER_WATT
@@ -232,6 +238,7 @@ class Text(Sensor):
class Humidity(Sensor):
"""Humidity sensor."""
SENSOR_ATTR = "measured_value"
_device_class = DEVICE_CLASS_HUMIDITY
_divisor = 100
_unit = UNIT_PERCENTAGE
@@ -241,6 +248,7 @@ class Humidity(Sensor):
class Illuminance(Sensor):
"""Illuminance Sensor."""
SENSOR_ATTR = "measured_value"
_device_class = DEVICE_CLASS_ILLUMINANCE
_unit = "lx"
@@ -254,6 +262,7 @@ class Illuminance(Sensor):
class SmartEnergyMetering(Sensor):
"""Metering sensor."""
SENSOR_ATTR = "instantaneous_demand"
_device_class = DEVICE_CLASS_POWER
def formatter(self, value):
@@ -270,6 +279,7 @@ class SmartEnergyMetering(Sensor):
class Pressure(Sensor):
"""Pressure sensor."""
SENSOR_ATTR = "measured_value"
_device_class = DEVICE_CLASS_PRESSURE
_decimals = 0
_unit = "hPa"
@@ -279,6 +289,7 @@ class Pressure(Sensor):
class Temperature(Sensor):
"""Temperature Sensor."""
SENSOR_ATTR = "measured_value"
_device_class = DEVICE_CLASS_TEMPERATURE
_divisor = 100
_unit = TEMP_CELSIUS

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

@@ -3,7 +3,7 @@
"name": "Z-Wave",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zwave",
"requirements": ["homeassistant-pyozw==0.1.8", "pydispatcher==2.0.5"],
"requirements": ["homeassistant-pyozw==0.1.9", "pydispatcher==2.0.5"],
"dependencies": [],
"codeowners": ["@home-assistant/z-wave"]
}

View File

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

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 107
PATCH_VERSION = "0b1"
PATCH_VERSION = "3"
__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==20200312.0
home-assistant-frontend==20200318.1
importlib-metadata==1.5.0
jinja2>=2.10.3
netdisco==2.6.0

View File

@@ -163,7 +163,7 @@ aioftp==0.12.0
aioharmony==0.1.13
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.29
aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -469,7 +469,7 @@ dweepy==0.3.0
dynalite_devices==0.1.32
# homeassistant.components.rainforest_eagle
eagle200_reader==0.2.1
eagle200_reader==0.2.4
# homeassistant.components.ebusd
ebusdpy==0.0.16
@@ -696,10 +696,10 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200312.0
home-assistant-frontend==20200318.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.8
homeassistant-pyozw==0.1.9
# homeassistant.components.homematicip_cloud
homematicip==0.10.17
@@ -747,7 +747,7 @@ incomfort-client==0.4.0
influxdb==5.2.3
# homeassistant.components.insteon
insteonplm==0.16.7
insteonplm==0.16.8
# homeassistant.components.iperf3
iperf3==0.1.11
@@ -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
@@ -1088,7 +1089,7 @@ pushover_complete==1.1.1
pwmled==1.5.0
# homeassistant.components.august
py-august==0.24.0
py-august==0.25.0
# homeassistant.components.canary
py-canary==0.5.0
@@ -1188,7 +1189,7 @@ pycfdns==0.0.1
pychannels==1.0.0
# homeassistant.components.cast
pychromecast==4.1.1
pychromecast==4.2.0
# homeassistant.components.cmus
pycmus==0.1.1
@@ -1315,7 +1316,7 @@ pyhomeworks==0.0.6
pyialarm==0.3
# homeassistant.components.icloud
pyicloud==0.9.4
pyicloud==0.9.5
# homeassistant.components.intesishome
pyintesishome==1.6
@@ -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.3
# homeassistant.components.sisyphus
sisyphus-control==2.2.1
@@ -1996,7 +1997,7 @@ temperusb==1.5.3
# tensorflow==1.13.2
# homeassistant.components.tesla
teslajsonpy==0.4.0
teslajsonpy==0.5.1
# homeassistant.components.thermoworks_smoke
thermoworks_smoke==0.1.8
@@ -2017,7 +2018,7 @@ todoist-python==8.0.0
toonapilib==3.2.4
# homeassistant.components.totalconnect
total_connect_client==0.53
total_connect_client==0.54.1
# homeassistant.components.tplink_lte
tp-connected==0.0.4
@@ -2158,7 +2159,7 @@ zengge==0.2
zeroconf==0.24.5
# homeassistant.components.zha
zha-quirks==0.0.36
zha-quirks==0.0.37
# homeassistant.components.zhong_hong
zhong_hong_hvac==1.0.9

View File

@@ -62,7 +62,7 @@ aiobotocore==0.11.1
aioesphomeapi==2.6.1
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.29
aiohomekit[IP]==0.2.29.2
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -263,10 +263,10 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200312.0
home-assistant-frontend==20200318.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.8
homeassistant-pyozw==0.1.9
# homeassistant.components.homematicip_cloud
homematicip==0.10.17
@@ -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
@@ -397,7 +405,7 @@ pure-python-adb==0.2.2.dev0
pushbullet.py==0.11.0
# homeassistant.components.august
py-august==0.24.0
py-august==0.25.0
# homeassistant.components.canary
py-canary==0.5.0
@@ -443,7 +451,7 @@ pyblackbird==0.5
pybotvac==0.0.17
# homeassistant.components.cast
pychromecast==4.1.1
pychromecast==4.2.0
# homeassistant.components.coolmaster
pycoolmasternet==0.0.4
@@ -483,7 +491,7 @@ pyheos==0.6.0
pyhomematic==0.1.65
# homeassistant.components.icloud
pyicloud==0.9.4
pyicloud==0.9.5
# homeassistant.components.ipma
pyipma==2.0.5
@@ -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.3
# homeassistant.components.sleepiq
sleepyq==0.7
@@ -684,7 +692,7 @@ sunwatcher==0.2.1
tellduslive==0.10.10
# homeassistant.components.tesla
teslajsonpy==0.4.0
teslajsonpy==0.5.1
# homeassistant.components.toon
toonapilib==3.2.4
@@ -747,7 +755,7 @@ yahooweather==0.10
zeroconf==0.24.5
# homeassistant.components.zha
zha-quirks==0.0.36
zha-quirks==0.0.37
# homeassistant.components.zha
zigpy-cc==0.1.0

View File

@@ -156,7 +156,7 @@ def calc_allowed_references(integration: Integration) -> Set[str]:
"""Return a set of allowed references."""
allowed_references = (
ALLOWED_USED_COMPONENTS
| set(integration.manifest["dependencies"])
| set(integration.manifest.get("dependencies", []))
| set(integration.manifest.get("after_dependencies", []))
)
@@ -250,7 +250,7 @@ def validate(integrations: Dict[str, Integration], config):
validate_dependencies(integrations, integration)
# check that all referenced dependencies exist
for dep in integration.manifest["dependencies"]:
for dep in integration.manifest.get("dependencies", []):
if dep not in integrations:
integration.add_error(
"dependencies", f"Dependency {dep} does not exist"

View File

@@ -52,8 +52,8 @@ MANIFEST_SCHEMA = vol.Schema(
vol.Url(), documentation_url # pylint: disable=no-value-for-parameter
),
vol.Optional("quality_scale"): vol.In(SUPPORTED_QUALITY_SCALES),
vol.Required("requirements"): [str],
vol.Required("dependencies"): [str],
vol.Optional("requirements"): [str],
vol.Optional("dependencies"): [str],
vol.Optional("after_dependencies"): [str],
vol.Required("codeowners"): [str],
vol.Optional("logo"): vol.Url(), # pylint: disable=no-value-for-parameter

View File

@@ -1,4 +1,6 @@
"""Tests for the DirecTV component."""
from DirectPy import DIRECTV
from homeassistant.components.directv.const import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.helpers.typing import HomeAssistantType
@@ -94,18 +96,23 @@ MOCK_GET_VERSION = {
}
class MockDirectvClass:
class MockDirectvClass(DIRECTV):
"""A fake DirecTV DVR device."""
def __init__(self, ip, port=8080, clientAddr="0"):
def __init__(self, ip, port=8080, clientAddr="0", determine_state=False):
"""Initialize the fake DirecTV device."""
self._host = ip
self._port = port
self._device = clientAddr
self._standby = True
self._play = False
super().__init__(
ip=ip, port=port, clientAddr=clientAddr, determine_state=determine_state,
)
self.attributes = LIVE
self._play = False
self._standby = True
if self.clientAddr == CLIENT_ADDRESS:
self.attributes = RECORDING
self._standby = False
else:
self.attributes = LIVE
def get_locations(self):
"""Mock for get_locations method."""

View File

@@ -114,9 +114,7 @@ async def test_form_cannot_connect(hass: HomeAssistantType) -> None:
)
with patch(
"homeassistant.components.directv.config_flow.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.config_flow.DIRECTV.get_version",
"tests.components.directv.test_config_flow.MockDirectvClass.get_version",
side_effect=RequestException,
) as mock_validate_input:
result = await async_configure_flow(hass, result["flow_id"], {CONF_HOST: HOST},)
@@ -135,15 +133,13 @@ async def test_form_unknown_error(hass: HomeAssistantType) -> None:
)
with patch(
"homeassistant.components.directv.config_flow.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.config_flow.DIRECTV.get_version",
"tests.components.directv.test_config_flow.MockDirectvClass.get_version",
side_effect=Exception,
) as mock_validate_input:
result = await async_configure_flow(hass, result["flow_id"], {CONF_HOST: HOST},)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "unknown"}
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "unknown"
await hass.async_block_till_done()
assert len(mock_validate_input.mock_calls) == 1
@@ -205,9 +201,7 @@ async def test_ssdp_discovery_confirm_abort(hass: HomeAssistantType) -> None:
)
with patch(
"homeassistant.components.directv.config_flow.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.config_flow.DIRECTV.get_version",
"tests.components.directv.test_config_flow.MockDirectvClass.get_version",
side_effect=RequestException,
) as mock_validate_input:
result = await async_configure_flow(hass, result["flow_id"], {})
@@ -227,9 +221,7 @@ async def test_ssdp_discovery_confirm_unknown_error(hass: HomeAssistantType) ->
)
with patch(
"homeassistant.components.directv.config_flow.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.config_flow.DIRECTV.get_version",
"tests.components.directv.test_config_flow.MockDirectvClass.get_version",
side_effect=Exception,
) as mock_validate_input:
result = await async_configure_flow(hass, result["flow_id"], {})

View File

@@ -54,9 +54,7 @@ from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.components.directv import (
CLIENT_ADDRESS,
DOMAIN,
HOST,
MOCK_GET_LOCATIONS_MULTIPLE,
RECORDING,
MockDirectvClass,
@@ -70,15 +68,6 @@ MAIN_ENTITY_ID = f"{MP_DOMAIN}.main_dvr"
# pylint: disable=redefined-outer-name
@fixture
def client_dtv() -> MockDirectvClass:
"""Fixture for a client device."""
mocked_dtv = MockDirectvClass(HOST, clientAddr=CLIENT_ADDRESS)
mocked_dtv.attributes = RECORDING
mocked_dtv._standby = False # pylint: disable=protected-access
return mocked_dtv
@fixture
def mock_now() -> datetime:
"""Fixture for dtutil.now."""
@@ -93,34 +82,19 @@ async def setup_directv(hass: HomeAssistantType) -> MockConfigEntry:
return await setup_integration(hass)
async def setup_directv_with_instance_error(hass: HomeAssistantType) -> MockConfigEntry:
async def setup_directv_with_locations(hass: HomeAssistantType) -> MockConfigEntry:
"""Set up mock DirecTV integration."""
with patch(
"homeassistant.components.directv.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.DIRECTV.get_locations",
"tests.components.directv.test_media_player.MockDirectvClass.get_locations",
return_value=MOCK_GET_LOCATIONS_MULTIPLE,
), patch(
"homeassistant.components.directv.media_player.get_dtv_instance",
return_value=None,
):
return await setup_integration(hass)
async def setup_directv_with_locations(
hass: HomeAssistantType, client_dtv: MockDirectvClass,
) -> MockConfigEntry:
"""Set up mock DirecTV integration."""
with patch(
"homeassistant.components.directv.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.DIRECTV.get_locations",
return_value=MOCK_GET_LOCATIONS_MULTIPLE,
), patch(
"homeassistant.components.directv.media_player.get_dtv_instance",
return_value=client_dtv,
):
return await setup_integration(hass)
with patch(
"homeassistant.components.directv.DIRECTV", new=MockDirectvClass,
), patch(
"homeassistant.components.directv.media_player.DIRECTV",
new=MockDirectvClass,
):
return await setup_integration(hass)
async def async_turn_on(
@@ -204,27 +178,17 @@ async def test_setup(hass: HomeAssistantType) -> None:
assert hass.states.get(MAIN_ENTITY_ID)
async def test_setup_with_multiple_locations(
hass: HomeAssistantType, client_dtv: MockDirectvClass
) -> None:
async def test_setup_with_multiple_locations(hass: HomeAssistantType) -> None:
"""Test setup with basic config with client location."""
await setup_directv_with_locations(hass, client_dtv)
await setup_directv_with_locations(hass)
assert hass.states.get(MAIN_ENTITY_ID)
assert hass.states.get(CLIENT_ENTITY_ID)
async def test_setup_with_instance_error(hass: HomeAssistantType) -> None:
"""Test setup with basic config with client location that results in instance error."""
await setup_directv_with_instance_error(hass)
assert hass.states.get(MAIN_ENTITY_ID)
assert hass.states.async_entity_ids(MP_DOMAIN) == [MAIN_ENTITY_ID]
async def test_unique_id(hass: HomeAssistantType, client_dtv: MockDirectvClass) -> None:
async def test_unique_id(hass: HomeAssistantType) -> None:
"""Test unique id."""
await setup_directv_with_locations(hass, client_dtv)
await setup_directv_with_locations(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
@@ -235,11 +199,9 @@ async def test_unique_id(hass: HomeAssistantType, client_dtv: MockDirectvClass)
assert client.unique_id == "2CA17D1CD30X"
async def test_supported_features(
hass: HomeAssistantType, client_dtv: MockDirectvClass
) -> None:
async def test_supported_features(hass: HomeAssistantType) -> None:
"""Test supported features."""
await setup_directv_with_locations(hass, client_dtv)
await setup_directv_with_locations(hass)
# Features supported for main DVR
state = hass.states.get(MAIN_ENTITY_ID)
@@ -269,10 +231,10 @@ async def test_supported_features(
async def test_check_attributes(
hass: HomeAssistantType, mock_now: dt_util.dt.datetime, client_dtv: MockDirectvClass
hass: HomeAssistantType, mock_now: dt_util.dt.datetime
) -> None:
"""Test attributes."""
await setup_directv_with_locations(hass, client_dtv)
await setup_directv_with_locations(hass)
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
@@ -321,10 +283,10 @@ async def test_check_attributes(
async def test_main_services(
hass: HomeAssistantType, mock_now: dt_util.dt.datetime, client_dtv: MockDirectvClass
hass: HomeAssistantType, mock_now: dt_util.dt.datetime
) -> None:
"""Test the different services."""
await setup_directv_with_locations(hass, client_dtv)
await setup_directv(hass)
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
@@ -373,10 +335,10 @@ async def test_main_services(
async def test_available(
hass: HomeAssistantType, mock_now: dt_util.dt.datetime, client_dtv: MockDirectvClass
hass: HomeAssistantType, mock_now: dt_util.dt.datetime
) -> None:
"""Test available status."""
entry = await setup_directv_with_locations(hass, client_dtv)
entry = await setup_directv(hass)
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):

View File

@@ -106,15 +106,6 @@ async def test_we_cannot_POST_to_root(mock_http_client):
assert resp.status == 405
async def test_states_routes(mock_http_client):
"""All served by index."""
resp = await mock_http_client.get("/states")
assert resp.status == 200
resp = await mock_http_client.get("/states/group.existing")
assert resp.status == 200
async def test_themes_api(hass, hass_ws_client):
"""Test that /api/themes returns correct data."""
assert await async_setup_component(hass, "frontend", CONFIG_THEMES)
@@ -217,7 +208,7 @@ async def test_missing_themes(hass, hass_ws_client):
async def test_extra_urls(mock_http_client_with_urls, mock_onboarded):
"""Test that extra urls are loaded."""
resp = await mock_http_client_with_urls.get("/states?latest")
resp = await mock_http_client_with_urls.get("/lovelace?latest")
assert resp.status == 200
text = await resp.text()
assert text.find('href="https://domain.com/my_extra_url.html"') >= 0

View File

@@ -1,5 +1,5 @@
"""Initializer helpers for HomematicIP fake server."""
from asynctest import CoroutineMock, MagicMock, Mock
from asynctest import CoroutineMock, MagicMock, Mock, patch
from homematicip.aio.auth import AsyncAuth
from homematicip.aio.connection import AsyncConnection
from homematicip.aio.home import AsyncHome
@@ -106,9 +106,10 @@ async def mock_hap_with_service_fixture(
@pytest.fixture(name="simple_mock_home")
def simple_mock_home_fixture() -> AsyncHome:
"""Return a simple AsyncHome Mock."""
return Mock(
def simple_mock_home_fixture():
"""Return a simple mocked connection."""
mock_home = Mock(
spec=AsyncHome,
name="Demo",
devices=[],
@@ -120,6 +121,27 @@ def simple_mock_home_fixture() -> AsyncHome:
connected=True,
)
with patch(
"homeassistant.components.homematicip_cloud.hap.AsyncHome",
autospec=True,
return_value=mock_home,
):
yield
@pytest.fixture(name="mock_connection_init")
def mock_connection_init_fixture():
"""Return a simple mocked connection."""
with patch(
"homeassistant.components.homematicip_cloud.hap.AsyncHome.init",
return_value=None,
), patch(
"homeassistant.components.homematicip_cloud.hap.AsyncAuth.init",
return_value=None,
):
yield
@pytest.fixture(name="simple_mock_auth")
def simple_mock_auth_fixture() -> AsyncAuth:

View File

@@ -31,9 +31,7 @@ async def _async_manipulate_security_zones(
internal_zone = home.search_group_by_id(internal_zone_id)
internal_zone.active = internal_active
home.from_json(json)
home._get_functionalHomes(json)
home._load_functionalChannels()
home.update_home_only(json)
home.fire_update_event(json)
await hass.async_block_till_done()

View File

@@ -16,12 +16,15 @@ DEFAULT_CONFIG = {HMIPC_HAPID: "ABC123", HMIPC_PIN: "123", HMIPC_NAME: "hmip"}
IMPORT_CONFIG = {HMIPC_HAPID: "ABC123", HMIPC_AUTHTOKEN: "123", HMIPC_NAME: "hmip"}
async def test_flow_works(hass):
async def test_flow_works(hass, simple_mock_home):
"""Test config flow."""
with patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
return_value=False,
), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.get_auth",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
HMIPC_DOMAIN, context={"source": "user"}, data=DEFAULT_CONFIG
@@ -137,7 +140,7 @@ async def test_init_already_configured(hass):
assert result["reason"] == "already_configured"
async def test_import_config(hass):
async def test_import_config(hass, simple_mock_home):
"""Test importing a host with an existing config file."""
with patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",

View File

@@ -125,14 +125,11 @@ async def test_hap_create(hass, hmip_config_entry, simple_mock_home):
hass.config.components.add(HMIPC_DOMAIN)
hap = HomematicipHAP(hass, hmip_config_entry)
assert hap
with patch(
"homeassistant.components.homematicip_cloud.hap.AsyncHome",
return_value=simple_mock_home,
), patch.object(hap, "async_connect"):
with patch.object(hap, "async_connect"):
assert await hap.async_setup()
async def test_hap_create_exception(hass, hmip_config_entry):
async def test_hap_create_exception(hass, hmip_config_entry, mock_connection_init):
"""Mock AsyncHome to execute get_hap."""
hass.config.components.add(HMIPC_DOMAIN)

View File

@@ -24,7 +24,9 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def test_config_with_accesspoint_passed_to_config_entry(hass):
async def test_config_with_accesspoint_passed_to_config_entry(
hass, mock_connection, simple_mock_home
):
"""Test that config for a accesspoint are loaded via config entry."""
entry_config = {
@@ -51,7 +53,9 @@ async def test_config_with_accesspoint_passed_to_config_entry(hass):
assert isinstance(hass.data[HMIPC_DOMAIN]["ABC123"], HomematicipHAP)
async def test_config_already_registered_not_passed_to_config_entry(hass):
async def test_config_already_registered_not_passed_to_config_entry(
hass, simple_mock_home
):
"""Test that an already registered accesspoint does not get imported."""
mock_config = {HMIPC_AUTHTOKEN: "123", HMIPC_HAPID: "ABC123", HMIPC_NAME: "name"}
@@ -87,7 +91,9 @@ async def test_config_already_registered_not_passed_to_config_entry(hass):
assert config_entries[0].unique_id == "ABC123"
async def test_load_entry_fails_due_to_connection_error(hass, hmip_config_entry):
async def test_load_entry_fails_due_to_connection_error(
hass, hmip_config_entry, mock_connection_init
):
"""Test load entry fails due to connection error."""
hmip_config_entry.add_to_hass(hass)
@@ -101,7 +107,9 @@ async def test_load_entry_fails_due_to_connection_error(hass, hmip_config_entry)
assert hmip_config_entry.state == ENTRY_STATE_SETUP_RETRY
async def test_load_entry_fails_due_to_generic_exception(hass, hmip_config_entry):
async def test_load_entry_fails_due_to_generic_exception(
hass, hmip_config_entry, simple_mock_home
):
"""Test load entry fails due to generic exception."""
hmip_config_entry.add_to_hass(hass)

View File

@@ -582,6 +582,10 @@ async def test_state_updates(hass, aiohttp_client, mock_panel):
)
entry.add_to_hass(hass)
# Add empty data field to ensure we process it correctly (possible if entry is ignored)
entry = MockConfigEntry(domain="konnected", title="Konnected Alarm Panel", data={},)
entry.add_to_hass(hass)
assert (
await async_setup_component(
hass,

View File

@@ -126,7 +126,7 @@ async def test_get_action_capabilities_brightness(hass, device_reg, entity_reg):
expected_capabilities = {
"extra_fields": [
{
"name": "brightness",
"name": "brightness_pct",
"optional": True,
"type": "integer",
"valueMax": 100,
@@ -218,7 +218,7 @@ async def test_action(hass, calls):
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_on",
"brightness": 75,
"brightness_pct": 75,
},
},
]
@@ -273,11 +273,11 @@ async def test_action(hass, calls):
assert len(turn_on_calls) == 3
assert turn_on_calls[2].data["entity_id"] == ent1.entity_id
assert turn_on_calls[2].data["brightness"] == 75
assert turn_on_calls[2].data["brightness_pct"] == 75
hass.bus.async_fire("test_on")
await hass.async_block_till_done()
assert len(turn_on_calls) == 4
assert turn_on_calls[3].data["entity_id"] == ent1.entity_id
assert "brightness" not in turn_on_calls[3].data
assert "brightness_pct" not in turn_on_calls[3].data

Some files were not shown because too many files have changed in this diff Show More