Merge pull request #38443 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2020-08-01 15:37:05 +02:00 committed by GitHub
commit c3aa9f9a6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 193 additions and 59 deletions

View File

@ -46,7 +46,7 @@ jobs:
run: | run: |
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -U pip setuptools pip install -U pip==20.1.1 setuptools
pip install -r requirements.txt -r requirements_test.txt pip install -r requirements.txt -r requirements_test.txt
# Uninstalling typing as a workaround. Eventually we should make sure # Uninstalling typing as a workaround. Eventually we should make sure
# all our dependencies drop typing. # all our dependencies drop typing.
@ -603,7 +603,7 @@ jobs:
run: | run: |
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -U pip setuptools wheel pip install -U pip==20.1.1 setuptools wheel
pip install -r requirements_all.txt pip install -r requirements_all.txt
pip install -r requirements_test.txt pip install -r requirements_test.txt
# Uninstalling typing as a workaround. Eventually we should make sure # Uninstalling typing as a workaround. Eventually we should make sure

View File

@ -261,6 +261,7 @@ def setup_abode_events(hass):
TIMELINE.AUTOMATION_GROUP, TIMELINE.AUTOMATION_GROUP,
TIMELINE.DISARM_GROUP, TIMELINE.DISARM_GROUP,
TIMELINE.ARM_GROUP, TIMELINE.ARM_GROUP,
TIMELINE.ARM_FAULT_GROUP,
TIMELINE.TEST_GROUP, TIMELINE.TEST_GROUP,
TIMELINE.CAPTURE_GROUP, TIMELINE.CAPTURE_GROUP,
TIMELINE.DEVICE_GROUP, TIMELINE.DEVICE_GROUP,

View File

@ -82,8 +82,21 @@ class AbodeCamera(AbodeDevice, Camera):
return None return None
def turn_on(self):
"""Turn on camera."""
self._device.privacy_mode(False)
def turn_off(self):
"""Turn off camera."""
self._device.privacy_mode(True)
def _capture_callback(self, capture): def _capture_callback(self, capture):
"""Update the image with the device then refresh device.""" """Update the image with the device then refresh device."""
self._device.update_image_location(capture) self._device.update_image_location(capture)
self.get_image() self.get_image()
self.schedule_update_ha_state() self.schedule_update_ha_state()
@property
def is_on(self):
"""Return true if on."""
return self._device.is_on

View File

@ -3,7 +3,7 @@
"name": "Abode", "name": "Abode",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/abode", "documentation": "https://www.home-assistant.io/integrations/abode",
"requirements": ["abodepy==0.19.0"], "requirements": ["abodepy==1.1.0"],
"codeowners": ["@shred86"], "codeowners": ["@shred86"],
"homekit": { "homekit": {
"models": ["Abode", "Iota"] "models": ["Abode", "Iota"]

View File

@ -230,7 +230,13 @@ class AdsHub:
hnotify = int(contents.hNotification) hnotify = int(contents.hNotification)
_LOGGER.debug("Received notification %d", hnotify) _LOGGER.debug("Received notification %d", hnotify)
data = contents.data
# get dynamically sized data array
data_size = contents.cbSampleSize
data = (ctypes.c_ubyte * data_size).from_address(
ctypes.addressof(contents)
+ pyads.structs.SAdsNotificationHeader.data.offset
)
try: try:
with self._lock: with self._lock:
@ -241,17 +247,17 @@ class AdsHub:
# Parse data to desired datatype # Parse data to desired datatype
if notification_item.plc_datatype == self.PLCTYPE_BOOL: if notification_item.plc_datatype == self.PLCTYPE_BOOL:
value = bool(struct.unpack("<?", bytearray(data)[:1])[0]) value = bool(struct.unpack("<?", bytearray(data))[0])
elif notification_item.plc_datatype == self.PLCTYPE_INT: elif notification_item.plc_datatype == self.PLCTYPE_INT:
value = struct.unpack("<h", bytearray(data)[:2])[0] value = struct.unpack("<h", bytearray(data))[0]
elif notification_item.plc_datatype == self.PLCTYPE_BYTE: elif notification_item.plc_datatype == self.PLCTYPE_BYTE:
value = struct.unpack("<B", bytearray(data)[:1])[0] value = struct.unpack("<B", bytearray(data))[0]
elif notification_item.plc_datatype == self.PLCTYPE_UINT: elif notification_item.plc_datatype == self.PLCTYPE_UINT:
value = struct.unpack("<H", bytearray(data)[:2])[0] value = struct.unpack("<H", bytearray(data))[0]
elif notification_item.plc_datatype == self.PLCTYPE_DINT: elif notification_item.plc_datatype == self.PLCTYPE_DINT:
value = struct.unpack("<i", bytearray(data)[:4])[0] value = struct.unpack("<i", bytearray(data))[0]
elif notification_item.plc_datatype == self.PLCTYPE_UDINT: elif notification_item.plc_datatype == self.PLCTYPE_UDINT:
value = struct.unpack("<I", bytearray(data)[:4])[0] value = struct.unpack("<I", bytearray(data))[0]
else: else:
value = bytearray(data) value = bytearray(data)
_LOGGER.warning("No callback available for this datatype") _LOGGER.warning("No callback available for this datatype")

View File

@ -2,6 +2,6 @@
"domain": "ads", "domain": "ads",
"name": "ADS", "name": "ADS",
"documentation": "https://www.home-assistant.io/integrations/ads", "documentation": "https://www.home-assistant.io/integrations/ads",
"requirements": ["pyads==3.1.3"], "requirements": ["pyads==3.2.1"],
"codeowners": [] "codeowners": []
} }

View File

@ -3,8 +3,8 @@
"name": "Android TV", "name": "Android TV",
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell[async]==0.2.0", "adb-shell[async]==0.2.1",
"androidtv[async]==0.0.46", "androidtv[async]==0.0.47",
"pure-python-adb==0.2.2.dev0" "pure-python-adb==0.2.2.dev0"
], ],
"codeowners": ["@JeffLIrion"] "codeowners": ["@JeffLIrion"]

View File

@ -1,16 +1,25 @@
"""Config flow for Cast.""" """Config flow for Cast."""
from pychromecast.discovery import discover_chromecasts import functools
from pychromecast.discovery import discover_chromecasts, stop_discovery
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
from .const import DOMAIN from .const import DOMAIN
from .helpers import ChromeCastZeroconf
async def _async_has_devices(hass): async def _async_has_devices(hass):
"""Return if there are devices that can be discovered.""" """Return if there are devices that can be discovered."""
return await hass.async_add_executor_job(discover_chromecasts) casts, browser = await hass.async_add_executor_job(
functools.partial(
discover_chromecasts, zeroconf_instance=ChromeCastZeroconf.get_zeroconf()
)
)
stop_discovery(browser)
return casts
config_entry_flow.register_discovery_flow( config_entry_flow.register_discovery_flow(

View File

@ -3,7 +3,7 @@
"name": "Google Cast", "name": "Google Cast",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast", "documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==7.1.2"], "requirements": ["pychromecast==7.2.0"],
"after_dependencies": ["cloud","zeroconf"], "after_dependencies": ["cloud","zeroconf"],
"zeroconf": ["_googlecast._tcp.local."], "zeroconf": ["_googlecast._tcp.local."],
"codeowners": ["@emontnemery"] "codeowners": ["@emontnemery"]

View File

@ -8,7 +8,6 @@ from aiohttp.hdrs import REFERER, USER_AGENT
import async_timeout import async_timeout
from gtts_token import gtts_token from gtts_token import gtts_token
import voluptuous as vol import voluptuous as vol
import yarl
from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
from homeassistant.const import HTTP_OK from homeassistant.const import HTTP_OK
@ -129,7 +128,7 @@ class GoogleProvider(Provider):
url_param = { url_param = {
"ie": "UTF-8", "ie": "UTF-8",
"tl": language, "tl": language,
"q": yarl.URL(part).raw_path, "q": part,
"tk": part_token, "tk": part_token,
"total": len(message_parts), "total": len(message_parts),
"idx": idx, "idx": idx,

View File

@ -164,6 +164,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def _host_already_configured(self, host): def _host_already_configured(self, host):
"""See if we already have a harmony entry matching the host.""" """See if we already have a harmony entry matching the host."""
for entry in self._async_current_entries(): for entry in self._async_current_entries():
if CONF_HOST not in entry.data:
continue
if entry.data[CONF_HOST] == host: if entry.data[CONF_HOST] == host:
return True return True
return False return False

View File

@ -2,7 +2,7 @@
"domain": "harmony", "domain": "harmony",
"name": "Logitech Harmony Hub", "name": "Logitech Harmony Hub",
"documentation": "https://www.home-assistant.io/integrations/harmony", "documentation": "https://www.home-assistant.io/integrations/harmony",
"requirements": ["aioharmony==0.2.5"], "requirements": ["aioharmony==0.2.6"],
"codeowners": ["@ehendrix23", "@bramkragten", "@bdraco"], "codeowners": ["@ehendrix23", "@bramkragten", "@bdraco"],
"ssdp": [ "ssdp": [
{ {

View File

@ -1,5 +1,7 @@
"""Support for interfacing with the XBMC/Kodi JSON-RPC API.""" """Support for interfacing with the XBMC/Kodi JSON-RPC API."""
import asyncio
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta
from functools import wraps from functools import wraps
import logging import logging
import re import re
@ -53,6 +55,7 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv, script from homeassistant.helpers import config_validation as cv, script
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.util.yaml import dump from homeassistant.util.yaml import dump
@ -82,6 +85,8 @@ DEPRECATED_TURN_OFF_ACTIONS = {
"shutdown": "System.Shutdown", "shutdown": "System.Shutdown",
} }
WEBSOCKET_WATCHDOG_INTERVAL = timedelta(minutes=3)
# https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h # https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h
MEDIA_TYPES = { MEDIA_TYPES = {
"music": MEDIA_TYPE_MUSIC, "music": MEDIA_TYPE_MUSIC,
@ -435,6 +440,26 @@ class KodiDevice(MediaPlayerEntity):
# run until the websocket connection is closed. # run until the websocket connection is closed.
self.hass.loop.create_task(ws_loop_wrapper()) self.hass.loop.create_task(ws_loop_wrapper())
async def async_added_to_hass(self):
"""Connect the websocket if needed."""
if not self._enable_websocket:
return
asyncio.create_task(self.async_ws_connect())
self.async_on_remove(
async_track_time_interval(
self.hass,
self._async_connect_websocket_if_disconnected,
WEBSOCKET_WATCHDOG_INTERVAL,
)
)
async def _async_connect_websocket_if_disconnected(self, *_):
"""Reconnect the websocket if it fails."""
if not self._ws_server.connected:
await self.async_ws_connect()
async def async_update(self): async def async_update(self):
"""Retrieve latest state.""" """Retrieve latest state."""
self._players = await self._get_players() self._players = await self._get_players()
@ -445,9 +470,6 @@ class KodiDevice(MediaPlayerEntity):
self._app_properties = {} self._app_properties = {}
return return
if self._enable_websocket and not self._ws_server.connected:
self.hass.async_create_task(self.async_ws_connect())
self._app_properties = await self.server.Application.GetProperties( self._app_properties = await self.server.Application.GetProperties(
["volume", "muted"] ["volume", "muted"]
) )

View File

@ -216,6 +216,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
existing_host_port_aliases = { existing_host_port_aliases = {
_format_host_port_alias(entry.data) _format_host_port_alias(entry.data)
for entry in self._async_current_entries() for entry in self._async_current_entries()
if CONF_HOST in entry.data
} }
return _format_host_port_alias(user_input) in existing_host_port_aliases return _format_host_port_alias(user_input) in existing_host_port_aliases

View File

@ -115,7 +115,7 @@ class PlexServer:
self._plextv_clients = [ self._plextv_clients = [
x x
for x in self.account.resources() for x in self.account.resources()
if "player" in x.provides and x.presence if "player" in x.provides and x.presence and x.publicAddressMatches
] ]
_LOGGER.debug( _LOGGER.debug(
"Current available clients from plex.tv: %s", self._plextv_clients "Current available clients from plex.tv: %s", self._plextv_clients

View File

@ -100,7 +100,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
tasks = [sensor.async_update() for sensor in sensors] tasks = [sensor.async_update() for sensor in sensors]
if tasks: if tasks:
await asyncio.wait(tasks) await asyncio.wait(tasks)
if not all(sensor.data.departures for sensor in sensors):
if not any(sensor.data for sensor in sensors):
raise PlatformNotReady raise PlatformNotReady
async_add_entities(sensors) async_add_entities(sensors)
@ -165,6 +166,7 @@ class RMVDepartureSensor(Entity):
"minutes": self.data.departures[0].get("minutes"), "minutes": self.data.departures[0].get("minutes"),
"departure_time": self.data.departures[0].get("departure_time"), "departure_time": self.data.departures[0].get("departure_time"),
"product": self.data.departures[0].get("product"), "product": self.data.departures[0].get("product"),
ATTR_ATTRIBUTION: ATTRIBUTION,
} }
except IndexError: except IndexError:
return {} return {}
@ -183,13 +185,16 @@ class RMVDepartureSensor(Entity):
"""Get the latest data and update the state.""" """Get the latest data and update the state."""
await self.data.async_update() await self.data.async_update()
if self._name == DEFAULT_NAME:
self._name = self.data.station
self._station = self.data.station
if not self.data.departures: if not self.data.departures:
self._state = None self._state = None
self._icon = ICONS[None] self._icon = ICONS[None]
return return
if self._name == DEFAULT_NAME:
self._name = self.data.station
self._station = self.data.station
self._state = self.data.departures[0].get("minutes") self._state = self.data.departures[0].get("minutes")
self._icon = ICONS[self.data.departures[0].get("product")] self._icon = ICONS[self.data.departures[0].get("product")]
@ -220,6 +225,7 @@ class RMVDepartureData:
self._max_journeys = max_journeys self._max_journeys = max_journeys
self.rmv = RMVtransport(session, timeout) self.rmv = RMVtransport(session, timeout)
self.departures = [] self.departures = []
self._error_notification = False
@Throttle(SCAN_INTERVAL) @Throttle(SCAN_INTERVAL)
async def async_update(self): async def async_update(self):
@ -231,31 +237,49 @@ class RMVDepartureData:
direction_id=self._direction, direction_id=self._direction,
max_journeys=50, max_journeys=50,
) )
except RMVtransportApiConnectionError: except RMVtransportApiConnectionError:
self.departures = [] self.departures = []
_LOGGER.warning("Could not retrieve data from rmv.de") _LOGGER.warning("Could not retrieve data from rmv.de")
return return
self.station = _data.get("station") self.station = _data.get("station")
_deps = [] _deps = []
_deps_not_found = set(self._destinations)
for journey in _data["journeys"]: for journey in _data["journeys"]:
# find the first departure meeting the criteria # find the first departure meeting the criteria
_nextdep = {ATTR_ATTRIBUTION: ATTRIBUTION} _nextdep = {}
if self._destinations: if self._destinations:
dest_found = False dest_found = False
for dest in self._destinations: for dest in self._destinations:
if dest in journey["stops"]: if dest in journey["stops"]:
dest_found = True dest_found = True
if dest in _deps_not_found:
_deps_not_found.remove(dest)
_nextdep["destination"] = dest _nextdep["destination"] = dest
if not dest_found: if not dest_found:
continue continue
elif self._lines and journey["number"] not in self._lines: elif self._lines and journey["number"] not in self._lines:
continue continue
elif journey["minutes"] < self._time_offset: elif journey["minutes"] < self._time_offset:
continue continue
for attr in ["direction", "departure_time", "product", "minutes"]: for attr in ["direction", "departure_time", "product", "minutes"]:
_nextdep[attr] = journey.get(attr, "") _nextdep[attr] = journey.get(attr, "")
_nextdep["line"] = journey.get("number", "") _nextdep["line"] = journey.get("number", "")
_deps.append(_nextdep) _deps.append(_nextdep)
if len(_deps) > self._max_journeys: if len(_deps) > self._max_journeys:
break break
if not self._error_notification and _deps_not_found:
self._error_notification = True
_LOGGER.info("Destination(s) %s not found", ", ".join(_deps_not_found))
self.departures = _deps self.departures = _deps

View File

@ -9,6 +9,7 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.core import callback
from .const import CONF_ENDPOINT, DOMAIN # pylint: disable=unused-import from .const import CONF_ENDPOINT, DOMAIN # pylint: disable=unused-import
@ -74,7 +75,7 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_init(self, user_input=None): async def async_step_init(self, user_input=None):
"""Handle a flow start.""" """Handle a flow start."""
# Check if already configured # Check if already configured
if self._endpoint_already_configured(): if self._async_endpoint_already_configured():
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
if user_input is None: if user_input is None:
@ -145,9 +146,10 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_step_init(user_input) return await self.async_step_init(user_input)
def _endpoint_already_configured(self): @callback
def _async_endpoint_already_configured(self):
"""See if we already have an endpoint matching user input configured.""" """See if we already have an endpoint matching user input configured."""
existing_endpoints = [ for entry in self._async_current_entries():
entry.data[CONF_ENDPOINT] for entry in self._async_current_entries() if entry.data.get(CONF_ENDPOINT) == self.conf.endpoint:
] return True
return self.conf.endpoint in existing_endpoints return False

View File

@ -71,6 +71,9 @@ class ToonDataUpdateCoordinator(DataUpdateCoordinator):
self.entry.data[CONF_WEBHOOK_ID] self.entry.data[CONF_WEBHOOK_ID]
) )
# Ensure the webhook is not registered already
webhook_unregister(self.hass, self.entry.data[CONF_WEBHOOK_ID])
webhook_register( webhook_register(
self.hass, self.hass,
DOMAIN, DOMAIN,

View File

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

View File

@ -27,6 +27,7 @@ ruamel.yaml==0.15.100
sqlalchemy==1.3.18 sqlalchemy==1.3.18
voluptuous-serialize==2.4.0 voluptuous-serialize==2.4.0
voluptuous==0.11.7 voluptuous==0.11.7
yarl==1.4.2
zeroconf==0.27.1 zeroconf==0.27.1
pycryptodome>=3.6.6 pycryptodome>=3.6.6

View File

@ -20,3 +20,4 @@ requests==2.24.0
ruamel.yaml==0.15.100 ruamel.yaml==0.15.100
voluptuous==0.11.7 voluptuous==0.11.7
voluptuous-serialize==2.4.0 voluptuous-serialize==2.4.0
yarl==1.4.2

View File

@ -100,7 +100,7 @@ WazeRouteCalculator==0.12
YesssSMS==0.4.1 YesssSMS==0.4.1
# homeassistant.components.abode # homeassistant.components.abode
abodepy==0.19.0 abodepy==1.1.0
# homeassistant.components.mcp23017 # homeassistant.components.mcp23017
adafruit-blinka==3.9.0 adafruit-blinka==3.9.0
@ -112,7 +112,7 @@ adafruit-circuitpython-bmp280==3.1.1
adafruit-circuitpython-mcp230xx==2.2.2 adafruit-circuitpython-mcp230xx==2.2.2
# homeassistant.components.androidtv # homeassistant.components.androidtv
adb-shell[async]==0.2.0 adb-shell[async]==0.2.1
# homeassistant.components.alarmdecoder # homeassistant.components.alarmdecoder
adext==0.3 adext==0.3
@ -164,7 +164,7 @@ aioftp==0.12.0
aioguardian==1.0.1 aioguardian==1.0.1
# homeassistant.components.harmony # homeassistant.components.harmony
aioharmony==0.2.5 aioharmony==0.2.6
# homeassistant.components.homekit_controller # homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.45 aiohomekit[IP]==0.2.45
@ -231,7 +231,7 @@ ambiclimate==0.2.1
amcrest==1.7.0 amcrest==1.7.0
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv[async]==0.0.46 androidtv[async]==0.0.47
# homeassistant.components.anel_pwrctrl # homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2 anel_pwrctrl-homeassistant==0.0.1.dev2
@ -1190,7 +1190,7 @@ py_nextbusnext==0.1.4
# py_noaa==0.3.0 # py_noaa==0.3.0
# homeassistant.components.ads # homeassistant.components.ads
pyads==3.1.3 pyads==3.2.1
# homeassistant.components.hisense_aehw4a1 # homeassistant.components.hisense_aehw4a1
pyaehw4a1==0.3.5 pyaehw4a1==0.3.5
@ -1241,7 +1241,7 @@ pycfdns==0.0.1
pychannels==1.0.0 pychannels==1.0.0
# homeassistant.components.cast # homeassistant.components.cast
pychromecast==7.1.2 pychromecast==7.2.0
# homeassistant.components.cmus # homeassistant.components.cmus
pycmus==0.1.1 pycmus==0.1.1

View File

@ -43,10 +43,10 @@ WSDiscovery==2.0.0
YesssSMS==0.4.1 YesssSMS==0.4.1
# homeassistant.components.abode # homeassistant.components.abode
abodepy==0.19.0 abodepy==1.1.0
# homeassistant.components.androidtv # homeassistant.components.androidtv
adb-shell[async]==0.2.0 adb-shell[async]==0.2.1
# homeassistant.components.adguard # homeassistant.components.adguard
adguardhome==0.4.2 adguardhome==0.4.2
@ -89,7 +89,7 @@ aiofreepybox==0.0.8
aioguardian==1.0.1 aioguardian==1.0.1
# homeassistant.components.harmony # homeassistant.components.harmony
aioharmony==0.2.5 aioharmony==0.2.6
# homeassistant.components.homekit_controller # homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.45 aiohomekit[IP]==0.2.45
@ -132,7 +132,7 @@ airly==0.0.2
ambiclimate==0.2.1 ambiclimate==0.2.1
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv[async]==0.0.46 androidtv[async]==0.0.47
# homeassistant.components.apns # homeassistant.components.apns
apns2==0.3.0 apns2==0.3.0
@ -577,7 +577,7 @@ pyblackbird==0.5
pybotvac==0.0.17 pybotvac==0.0.17
# homeassistant.components.cast # homeassistant.components.cast
pychromecast==7.1.2 pychromecast==7.2.0
# homeassistant.components.coolmaster # homeassistant.components.coolmaster
pycoolmasternet==0.0.4 pycoolmasternet==0.0.4

View File

@ -52,6 +52,7 @@ REQUIRES = [
"ruamel.yaml==0.15.100", "ruamel.yaml==0.15.100",
"voluptuous==0.11.7", "voluptuous==0.11.7",
"voluptuous-serialize==2.4.0", "voluptuous-serialize==2.4.0",
"yarl==1.4.2",
] ]
MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER)) MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER))

View File

@ -38,3 +38,33 @@ async def test_capture_image(hass):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
mock_capture.assert_called_once() mock_capture.assert_called_once()
async def test_camera_on(hass):
"""Test the camera turn on service."""
await setup_platform(hass, CAMERA_DOMAIN)
with patch("abodepy.AbodeCamera.privacy_mode") as mock_capture:
await hass.services.async_call(
CAMERA_DOMAIN,
"turn_on",
{ATTR_ENTITY_ID: "camera.test_cam"},
blocking=True,
)
await hass.async_block_till_done()
mock_capture.assert_called_once_with(False)
async def test_camera_off(hass):
"""Test the camera turn off service."""
await setup_platform(hass, CAMERA_DOMAIN)
with patch("abodepy.AbodeCamera.privacy_mode") as mock_capture:
await hass.services.async_call(
CAMERA_DOMAIN,
"turn_off",
{ATTR_ENTITY_ID: "camera.test_cam"},
blocking=True,
)
await hass.async_block_till_done()
mock_capture.assert_called_once_with(True)

View File

@ -13,7 +13,9 @@ async def test_creating_entry_sets_up_media_player(hass):
"homeassistant.components.cast.media_player.async_setup_entry", "homeassistant.components.cast.media_player.async_setup_entry",
return_value=True, return_value=True,
) as mock_setup, patch( ) as mock_setup, patch(
"pychromecast.discovery.discover_chromecasts", return_value=True "pychromecast.discovery.discover_chromecasts", return_value=(True, None)
), patch(
"pychromecast.discovery.stop_discovery"
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
cast.DOMAIN, context={"source": config_entries.SOURCE_USER} cast.DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -34,9 +36,7 @@ async def test_configuring_cast_creates_entry(hass):
"""Test that specifying config will create an entry.""" """Test that specifying config will create an entry."""
with patch( with patch(
"homeassistant.components.cast.async_setup_entry", return_value=True "homeassistant.components.cast.async_setup_entry", return_value=True
) as mock_setup, patch( ) as mock_setup:
"pychromecast.discovery.discover_chromecasts", return_value=True
):
await async_setup_component( await async_setup_component(
hass, cast.DOMAIN, {"cast": {"some_config": "to_trigger_import"}} hass, cast.DOMAIN, {"cast": {"some_config": "to_trigger_import"}}
) )
@ -49,9 +49,7 @@ async def test_not_configuring_cast_not_creates_entry(hass):
"""Test that no config will not create an entry.""" """Test that no config will not create an entry."""
with patch( with patch(
"homeassistant.components.cast.async_setup_entry", return_value=True "homeassistant.components.cast.async_setup_entry", return_value=True
) as mock_setup, patch( ) as mock_setup:
"pychromecast.discovery.discover_chromecasts", return_value=True
):
await async_setup_component(hass, cast.DOMAIN, {}) await async_setup_component(hass, cast.DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -153,6 +153,9 @@ async def test_form_ssdp_aborts_before_checking_remoteid_if_host_known(hass):
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
config_entry_without_host = MockConfigEntry(domain=DOMAIN, data={"name": "other"},)
config_entry_without_host.add_to_hass(hass)
harmonyapi = _get_mock_harmonyapi(connect=True) harmonyapi = _get_mock_harmonyapi(connect=True)
with patch( with patch(

View File

@ -247,7 +247,25 @@ async def test_form_import_dupe(hass):
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "import"}, data=VALID_CONFIG DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=VALID_CONFIG
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
async def test_form_import_with_ignored_entry(hass):
"""Test we get abort on duplicate import when there is an ignored one."""
await setup.async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(domain=DOMAIN, data=VALID_CONFIG)
entry.add_to_hass(hass)
ignored_entry = MockConfigEntry(
domain=DOMAIN, data={}, source=config_entries.SOURCE_IGNORE
)
ignored_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=VALID_CONFIG
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"

View File

@ -53,6 +53,7 @@ class MockResource:
self.provides = ["player"] self.provides = ["player"]
self.device = MockPlexClient(f"http://192.168.0.1{index}:32500", index + 10) self.device = MockPlexClient(f"http://192.168.0.1{index}:32500", index + 10)
self.presence = index == 0 self.presence = index == 0
self.publicAddressMatches = True
def connect(self, timeout): def connect(self, timeout):
"""Mock the resource connect method.""" """Mock the resource connect method."""

View File

@ -48,7 +48,7 @@ VALID_CONFIG_DEST = {
def get_departures_mock(): def get_departures_mock():
"""Mock rmvtransport departures loading.""" """Mock rmvtransport departures loading."""
data = { return {
"station": "Frankfurt (Main) Hauptbahnhof", "station": "Frankfurt (Main) Hauptbahnhof",
"stationId": "3000010", "stationId": "3000010",
"filter": "11111111111", "filter": "11111111111",
@ -145,18 +145,16 @@ def get_departures_mock():
}, },
], ],
} }
return data
def get_no_departures_mock(): def get_no_departures_mock():
"""Mock no departures in results.""" """Mock no departures in results."""
data = { return {
"station": "Frankfurt (Main) Hauptbahnhof", "station": "Frankfurt (Main) Hauptbahnhof",
"stationId": "3000010", "stationId": "3000010",
"filter": "11111111111", "filter": "11111111111",
"journeys": [], "journeys": [],
} }
return data
async def test_rmvtransport_min_config(hass): async def test_rmvtransport_min_config(hass):
@ -232,4 +230,4 @@ async def test_rmvtransport_no_departures(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") state = hass.states.get("sensor.frankfurt_main_hauptbahnhof")
assert not state assert state.state == "unavailable"