mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
commit
905d71c9e3
@ -113,7 +113,10 @@ def async_from_config_dict(config: Dict[str, Any],
|
|||||||
yield from hass.async_add_job(loader.prepare, hass)
|
yield from hass.async_add_job(loader.prepare, hass)
|
||||||
|
|
||||||
# Make a copy because we are mutating it.
|
# Make a copy because we are mutating it.
|
||||||
config = OrderedDict(config)
|
new_config = OrderedDict()
|
||||||
|
for key, value in config.items():
|
||||||
|
new_config[key] = value or {}
|
||||||
|
config = new_config
|
||||||
|
|
||||||
# Merge packages
|
# Merge packages
|
||||||
conf_util.merge_packages_config(
|
conf_util.merge_packages_config(
|
||||||
|
@ -111,6 +111,9 @@ SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(vol.All(
|
|||||||
ATTR_ATTRIBUTES: dict,
|
ATTR_ATTRIBUTES: dict,
|
||||||
ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES),
|
ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES),
|
||||||
ATTR_CONSIDER_HOME: cv.time_period,
|
ATTR_CONSIDER_HOME: cv.time_period,
|
||||||
|
# Temp workaround for iOS app introduced in 0.65
|
||||||
|
vol.Optional('battery_status'): str,
|
||||||
|
vol.Optional('hostname'): str,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
@ -219,7 +222,11 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_see_service(call):
|
def async_see_service(call):
|
||||||
"""Service to see a device."""
|
"""Service to see a device."""
|
||||||
yield from tracker.async_see(**call.data)
|
# Temp workaround for iOS, introduced in 0.65
|
||||||
|
data = dict(call.data)
|
||||||
|
data.pop('hostname', None)
|
||||||
|
data.pop('battery_status', None)
|
||||||
|
yield from tracker.async_see(**data)
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA)
|
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA)
|
||||||
|
@ -5,6 +5,7 @@ from homeassistant.components import (
|
|||||||
cover,
|
cover,
|
||||||
group,
|
group,
|
||||||
fan,
|
fan,
|
||||||
|
input_boolean,
|
||||||
media_player,
|
media_player,
|
||||||
light,
|
light,
|
||||||
scene,
|
scene,
|
||||||
@ -182,6 +183,7 @@ class OnOffTrait(_Trait):
|
|||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
return domain in (
|
return domain in (
|
||||||
group.DOMAIN,
|
group.DOMAIN,
|
||||||
|
input_boolean.DOMAIN,
|
||||||
switch.DOMAIN,
|
switch.DOMAIN,
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
light.DOMAIN,
|
light.DOMAIN,
|
||||||
|
@ -5,16 +5,17 @@ from pyhap.accessory import Accessory, Bridge, Category
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, MANUFACTURER,
|
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, MANUFACTURER,
|
||||||
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER)
|
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_NAME, CHAR_SERIAL_NUMBER)
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def set_accessory_info(acc, model, manufacturer=MANUFACTURER,
|
def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER,
|
||||||
serial_number='0000'):
|
serial_number='0000'):
|
||||||
"""Set the default accessory information."""
|
"""Set the default accessory information."""
|
||||||
service = acc.get_service(SERV_ACCESSORY_INFO)
|
service = acc.get_service(SERV_ACCESSORY_INFO)
|
||||||
|
service.get_characteristic(CHAR_NAME).set_value(name)
|
||||||
service.get_characteristic(CHAR_MODEL).set_value(model)
|
service.get_characteristic(CHAR_MODEL).set_value(model)
|
||||||
service.get_characteristic(CHAR_MANUFACTURER).set_value(manufacturer)
|
service.get_characteristic(CHAR_MANUFACTURER).set_value(manufacturer)
|
||||||
service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number)
|
service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number)
|
||||||
@ -49,7 +50,7 @@ class HomeAccessory(Accessory):
|
|||||||
def __init__(self, display_name, model, category='OTHER', **kwargs):
|
def __init__(self, display_name, model, category='OTHER', **kwargs):
|
||||||
"""Initialize a Accessory object."""
|
"""Initialize a Accessory object."""
|
||||||
super().__init__(display_name, **kwargs)
|
super().__init__(display_name, **kwargs)
|
||||||
set_accessory_info(self, model)
|
set_accessory_info(self, display_name, model)
|
||||||
self.category = getattr(Category, category, Category.OTHER)
|
self.category = getattr(Category, category, Category.OTHER)
|
||||||
|
|
||||||
def _set_services(self):
|
def _set_services(self):
|
||||||
@ -62,7 +63,7 @@ class HomeBridge(Bridge):
|
|||||||
def __init__(self, display_name, model, pincode, **kwargs):
|
def __init__(self, display_name, model, pincode, **kwargs):
|
||||||
"""Initialize a Bridge object."""
|
"""Initialize a Bridge object."""
|
||||||
super().__init__(display_name, pincode=pincode, **kwargs)
|
super().__init__(display_name, pincode=pincode, **kwargs)
|
||||||
set_accessory_info(self, model)
|
set_accessory_info(self, display_name, model)
|
||||||
|
|
||||||
def _set_services(self):
|
def _set_services(self):
|
||||||
add_preload_service(self, SERV_ACCESSORY_INFO)
|
add_preload_service(self, SERV_ACCESSORY_INFO)
|
||||||
|
@ -22,6 +22,7 @@ CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
|
|||||||
CHAR_LINK_QUALITY = 'LinkQuality'
|
CHAR_LINK_QUALITY = 'LinkQuality'
|
||||||
CHAR_MANUFACTURER = 'Manufacturer'
|
CHAR_MANUFACTURER = 'Manufacturer'
|
||||||
CHAR_MODEL = 'Model'
|
CHAR_MODEL = 'Model'
|
||||||
|
CHAR_NAME = 'Name'
|
||||||
CHAR_ON = 'On'
|
CHAR_ON = 'On'
|
||||||
CHAR_POSITION_STATE = 'PositionState'
|
CHAR_POSITION_STATE = 'PositionState'
|
||||||
CHAR_REACHABLE = 'Reachable'
|
CHAR_REACHABLE = 'Reachable'
|
||||||
|
@ -423,19 +423,17 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
for player in self._hass.data[DATA_BLUESOUND]:
|
for player in self._hass.data[DATA_BLUESOUND]:
|
||||||
yield from player.force_update_sync_status()
|
yield from player.force_update_sync_status()
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(SYNC_STATUS_INTERVAL)
|
@Throttle(SYNC_STATUS_INTERVAL)
|
||||||
def async_update_sync_status(self, on_updated_cb=None,
|
async def async_update_sync_status(self, on_updated_cb=None,
|
||||||
raise_timeout=False):
|
raise_timeout=False):
|
||||||
"""Update sync status."""
|
"""Update sync status."""
|
||||||
yield from self.force_update_sync_status(
|
await self.force_update_sync_status(
|
||||||
on_updated_cb, raise_timeout=False)
|
on_updated_cb, raise_timeout=False)
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(UPDATE_CAPTURE_INTERVAL)
|
@Throttle(UPDATE_CAPTURE_INTERVAL)
|
||||||
def async_update_captures(self):
|
async def async_update_captures(self):
|
||||||
"""Update Capture sources."""
|
"""Update Capture sources."""
|
||||||
resp = yield from self.send_bluesound_command(
|
resp = await self.send_bluesound_command(
|
||||||
'RadioBrowse?service=Capture')
|
'RadioBrowse?service=Capture')
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
@ -459,11 +457,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
|
|
||||||
return self._capture_items
|
return self._capture_items
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(UPDATE_PRESETS_INTERVAL)
|
@Throttle(UPDATE_PRESETS_INTERVAL)
|
||||||
def async_update_presets(self):
|
async def async_update_presets(self):
|
||||||
"""Update Presets."""
|
"""Update Presets."""
|
||||||
resp = yield from self.send_bluesound_command('Presets')
|
resp = await self.send_bluesound_command('Presets')
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
self._preset_items = []
|
self._preset_items = []
|
||||||
@ -488,11 +485,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
|
|
||||||
return self._preset_items
|
return self._preset_items
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(UPDATE_SERVICES_INTERVAL)
|
@Throttle(UPDATE_SERVICES_INTERVAL)
|
||||||
def async_update_services(self):
|
async def async_update_services(self):
|
||||||
"""Update Services."""
|
"""Update Services."""
|
||||||
resp = yield from self.send_bluesound_command('Services')
|
resp = await self.send_bluesound_command('Services')
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
self._services_items = []
|
self._services_items = []
|
||||||
|
@ -253,8 +253,7 @@ class Volumio(MediaPlayerDevice):
|
|||||||
return self.send_volumio_msg('commands',
|
return self.send_volumio_msg('commands',
|
||||||
params={'cmd': 'clearQueue'})
|
params={'cmd': 'clearQueue'})
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(PLAYLIST_UPDATE_INTERVAL)
|
@Throttle(PLAYLIST_UPDATE_INTERVAL)
|
||||||
def _async_update_playlists(self, **kwargs):
|
async def _async_update_playlists(self, **kwargs):
|
||||||
"""Update available Volumio playlists."""
|
"""Update available Volumio playlists."""
|
||||||
self._playlists = yield from self.send_volumio_msg('listplaylists')
|
self._playlists = await self.send_volumio_msg('listplaylists')
|
||||||
|
@ -157,13 +157,12 @@ class FidoData(object):
|
|||||||
REQUESTS_TIMEOUT, httpsession)
|
REQUESTS_TIMEOUT, httpsession)
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from Fido."""
|
"""Get the latest data from Fido."""
|
||||||
from pyfido.client import PyFidoError
|
from pyfido.client import PyFidoError
|
||||||
try:
|
try:
|
||||||
yield from self.client.fetch_data()
|
await self.client.fetch_data()
|
||||||
except PyFidoError as exp:
|
except PyFidoError as exp:
|
||||||
_LOGGER.error("Error on receive last Fido data: %s", exp)
|
_LOGGER.error("Error on receive last Fido data: %s", exp)
|
||||||
return False
|
return False
|
||||||
|
@ -182,13 +182,12 @@ class HydroquebecData(object):
|
|||||||
return self.client.get_contracts()
|
return self.client.get_contracts()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def _fetch_data(self):
|
async def _fetch_data(self):
|
||||||
"""Fetch latest data from HydroQuebec."""
|
"""Fetch latest data from HydroQuebec."""
|
||||||
from pyhydroquebec.client import PyHydroQuebecError
|
from pyhydroquebec.client import PyHydroQuebecError
|
||||||
try:
|
try:
|
||||||
yield from self.client.fetch_data()
|
await self.client.fetch_data()
|
||||||
except PyHydroQuebecError as exp:
|
except PyHydroQuebecError as exp:
|
||||||
_LOGGER.error("Error on receive last Hydroquebec data: %s", exp)
|
_LOGGER.error("Error on receive last Hydroquebec data: %s", exp)
|
||||||
return False
|
return False
|
||||||
|
@ -133,13 +133,9 @@ class LuftdatenSensor(Entity):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_update(self):
|
||||||
def async_update(self):
|
|
||||||
"""Get the latest data from luftdaten.info and update the state."""
|
"""Get the latest data from luftdaten.info and update the state."""
|
||||||
try:
|
await self.luftdaten.async_update()
|
||||||
yield from self.luftdaten.async_update()
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LuftdatenData(object):
|
class LuftdatenData(object):
|
||||||
@ -150,12 +146,11 @@ class LuftdatenData(object):
|
|||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
@asyncio.coroutine
|
async def async_update(self):
|
||||||
def async_update(self):
|
|
||||||
"""Get the latest data from luftdaten.info."""
|
"""Get the latest data from luftdaten.info."""
|
||||||
from luftdaten.exceptions import LuftdatenError
|
from luftdaten.exceptions import LuftdatenError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield from self.data.async_get_data()
|
await self.data.async_get_data()
|
||||||
except LuftdatenError:
|
except LuftdatenError:
|
||||||
_LOGGER.error("Unable to retrieve data from luftdaten.info")
|
_LOGGER.error("Unable to retrieve data from luftdaten.info")
|
||||||
|
@ -75,15 +75,14 @@ def setup_sabnzbd(base_url, apikey, name, config,
|
|||||||
for variable in monitored])
|
for variable in monitored])
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def async_update_queue(sab_api):
|
async def async_update_queue(sab_api):
|
||||||
"""
|
"""
|
||||||
Throttled function to update SABnzbd queue.
|
Throttled function to update SABnzbd queue.
|
||||||
|
|
||||||
This ensures that the queue info only gets updated once for all sensors
|
This ensures that the queue info only gets updated once for all sensors
|
||||||
"""
|
"""
|
||||||
yield from sab_api.refresh_queue()
|
await sab_api.refresh_queue()
|
||||||
|
|
||||||
|
|
||||||
def request_configuration(host, name, hass, config, async_add_devices,
|
def request_configuration(host, name, hass, config, async_add_devices,
|
||||||
|
@ -140,21 +140,20 @@ class StartcaData(object):
|
|||||||
"""
|
"""
|
||||||
return float(value) * 10 ** -9
|
return float(value) * 10 ** -9
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the Start.ca bandwidth data from the web service."""
|
"""Get the Start.ca bandwidth data from the web service."""
|
||||||
import xmltodict
|
import xmltodict
|
||||||
_LOGGER.debug("Updating Start.ca usage data")
|
_LOGGER.debug("Updating Start.ca usage data")
|
||||||
url = 'https://www.start.ca/support/usage/api?key=' + \
|
url = 'https://www.start.ca/support/usage/api?key=' + \
|
||||||
self.api_key
|
self.api_key
|
||||||
with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop):
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop):
|
||||||
req = yield from self.websession.get(url)
|
req = await self.websession.get(url)
|
||||||
if req.status != 200:
|
if req.status != 200:
|
||||||
_LOGGER.error("Request failed with status: %u", req.status)
|
_LOGGER.error("Request failed with status: %u", req.status)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
data = yield from req.text()
|
data = await req.text()
|
||||||
try:
|
try:
|
||||||
xml_data = xmltodict.parse(data)
|
xml_data = xmltodict.parse(data)
|
||||||
except ExpatError:
|
except ExpatError:
|
||||||
|
@ -132,22 +132,21 @@ class TekSavvyData(object):
|
|||||||
self.data = {"limit": self.bandwidth_cap} if self.bandwidth_cap > 0 \
|
self.data = {"limit": self.bandwidth_cap} if self.bandwidth_cap > 0 \
|
||||||
else {"limit": float('inf')}
|
else {"limit": float('inf')}
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the TekSavvy bandwidth data from the web service."""
|
"""Get the TekSavvy bandwidth data from the web service."""
|
||||||
headers = {"TekSavvy-APIKey": self.api_key}
|
headers = {"TekSavvy-APIKey": self.api_key}
|
||||||
_LOGGER.debug("Updating TekSavvy data")
|
_LOGGER.debug("Updating TekSavvy data")
|
||||||
url = "https://api.teksavvy.com/"\
|
url = "https://api.teksavvy.com/"\
|
||||||
"web/Usage/UsageSummaryRecords?$filter=IsCurrent%20eq%20true"
|
"web/Usage/UsageSummaryRecords?$filter=IsCurrent%20eq%20true"
|
||||||
with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop):
|
with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop):
|
||||||
req = yield from self.websession.get(url, headers=headers)
|
req = await self.websession.get(url, headers=headers)
|
||||||
if req.status != 200:
|
if req.status != 200:
|
||||||
_LOGGER.error("Request failed with status: %u", req.status)
|
_LOGGER.error("Request failed with status: %u", req.status)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = yield from req.json()
|
data = await req.json()
|
||||||
for (api, ha_name) in API_HA_MAP:
|
for (api, ha_name) in API_HA_MAP:
|
||||||
self.data[ha_name] = float(data["value"][0][api])
|
self.data[ha_name] = float(data["value"][0][api])
|
||||||
on_peak_download = self.data["onpeak_download"]
|
on_peak_download = self.data["onpeak_download"]
|
||||||
|
@ -777,14 +777,13 @@ class WUndergroundData(object):
|
|||||||
|
|
||||||
return url + '.json'
|
return url + '.json'
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from WUnderground."""
|
"""Get the latest data from WUnderground."""
|
||||||
try:
|
try:
|
||||||
with async_timeout.timeout(10, loop=self._hass.loop):
|
with async_timeout.timeout(10, loop=self._hass.loop):
|
||||||
response = yield from self._session.get(self._build_url())
|
response = await self._session.get(self._build_url())
|
||||||
result = yield from response.json()
|
result = await response.json()
|
||||||
if "error" in result['response']:
|
if "error" in result['response']:
|
||||||
raise ValueError(result['response']["error"]["description"])
|
raise ValueError(result['response']["error"]["description"])
|
||||||
self.data = result
|
self.data = result
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 65
|
MINOR_VERSION = 65
|
||||||
PATCH_VERSION = '0'
|
PATCH_VERSION = '1'
|
||||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||||
REQUIRED_PYTHON_VER = (3, 5, 3)
|
REQUIRED_PYTHON_VER = (3, 5, 3)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Helper methods for various modules."""
|
"""Helper methods for various modules."""
|
||||||
|
import asyncio
|
||||||
from collections.abc import MutableSet
|
from collections.abc import MutableSet
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import threading
|
import threading
|
||||||
@ -276,6 +277,16 @@ class Throttle(object):
|
|||||||
is_func = (not hasattr(method, '__self__') and
|
is_func = (not hasattr(method, '__self__') and
|
||||||
'.' not in method.__qualname__.split('.<locals>.')[-1])
|
'.' not in method.__qualname__.split('.<locals>.')[-1])
|
||||||
|
|
||||||
|
# Make sure we return a coroutine if the method is async.
|
||||||
|
if asyncio.iscoroutinefunction(method):
|
||||||
|
async def throttled_value():
|
||||||
|
"""Stand-in function for when real func is being throttled."""
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
def throttled_value():
|
||||||
|
"""Stand-in function for when real func is being throttled."""
|
||||||
|
return None
|
||||||
|
|
||||||
@wraps(method)
|
@wraps(method)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
"""Wrap that allows wrapped to be called only once per min_time.
|
"""Wrap that allows wrapped to be called only once per min_time.
|
||||||
@ -298,7 +309,7 @@ class Throttle(object):
|
|||||||
throttle = host._throttle[id(self)]
|
throttle = host._throttle[id(self)]
|
||||||
|
|
||||||
if not throttle[0].acquire(False):
|
if not throttle[0].acquire(False):
|
||||||
return None
|
return throttled_value()
|
||||||
|
|
||||||
# Check if method is never called or no_throttle is given
|
# Check if method is never called or no_throttle is given
|
||||||
force = kwargs.pop('no_throttle', False) or not throttle[1]
|
force = kwargs.pop('no_throttle', False) or not throttle[1]
|
||||||
@ -309,7 +320,7 @@ class Throttle(object):
|
|||||||
throttle[1] = utcnow()
|
throttle[1] = utcnow()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return None
|
return throttled_value()
|
||||||
finally:
|
finally:
|
||||||
throttle[0].release()
|
throttle[0].release()
|
||||||
|
|
||||||
|
@ -730,3 +730,18 @@ async def test_old_style_track_new_is_skipped(mock_device_tracker_conf, hass):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(mock_device_tracker_conf) == 1
|
assert len(mock_device_tracker_conf) == 1
|
||||||
assert mock_device_tracker_conf[0].track is False
|
assert mock_device_tracker_conf[0].track is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_see_schema_allowing_ios_calls():
|
||||||
|
"""Test SEE service schema allows extra keys.
|
||||||
|
|
||||||
|
Temp work around because the iOS app sends incorrect data.
|
||||||
|
"""
|
||||||
|
device_tracker.SERVICE_SEE_PAYLOAD_SCHEMA({
|
||||||
|
'dev_id': 'Test',
|
||||||
|
"battery": 35,
|
||||||
|
"battery_status": 'Unplugged',
|
||||||
|
"gps": [10.0, 10.0],
|
||||||
|
"gps_accuracy": 300,
|
||||||
|
"hostname": 'beer',
|
||||||
|
})
|
||||||
|
@ -9,8 +9,9 @@ from homeassistant.components import (
|
|||||||
climate,
|
climate,
|
||||||
cover,
|
cover,
|
||||||
fan,
|
fan,
|
||||||
media_player,
|
input_boolean,
|
||||||
light,
|
light,
|
||||||
|
media_player,
|
||||||
scene,
|
scene,
|
||||||
script,
|
script,
|
||||||
switch,
|
switch,
|
||||||
@ -138,6 +139,43 @@ async def test_onoff_group(hass):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_onoff_input_boolean(hass):
|
||||||
|
"""Test OnOff trait support for input_boolean domain."""
|
||||||
|
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
|
||||||
|
|
||||||
|
trt_on = trait.OnOffTrait(State('input_boolean.bla', STATE_ON))
|
||||||
|
|
||||||
|
assert trt_on.sync_attributes() == {}
|
||||||
|
|
||||||
|
assert trt_on.query_attributes() == {
|
||||||
|
'on': True
|
||||||
|
}
|
||||||
|
|
||||||
|
trt_off = trait.OnOffTrait(State('input_boolean.bla', STATE_OFF))
|
||||||
|
assert trt_off.query_attributes() == {
|
||||||
|
'on': False
|
||||||
|
}
|
||||||
|
|
||||||
|
on_calls = async_mock_service(hass, input_boolean.DOMAIN, SERVICE_TURN_ON)
|
||||||
|
await trt_on.execute(hass, trait.COMMAND_ONOFF, {
|
||||||
|
'on': True
|
||||||
|
})
|
||||||
|
assert len(on_calls) == 1
|
||||||
|
assert on_calls[0].data == {
|
||||||
|
ATTR_ENTITY_ID: 'input_boolean.bla',
|
||||||
|
}
|
||||||
|
|
||||||
|
off_calls = async_mock_service(hass, input_boolean.DOMAIN,
|
||||||
|
SERVICE_TURN_OFF)
|
||||||
|
await trt_on.execute(hass, trait.COMMAND_ONOFF, {
|
||||||
|
'on': False
|
||||||
|
})
|
||||||
|
assert len(off_calls) == 1
|
||||||
|
assert off_calls[0].data == {
|
||||||
|
ATTR_ENTITY_ID: 'input_boolean.bla',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_onoff_switch(hass):
|
async def test_onoff_switch(hass):
|
||||||
"""Test OnOff trait support for switch domain."""
|
"""Test OnOff trait support for switch domain."""
|
||||||
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
|
assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
|
||||||
|
@ -13,7 +13,7 @@ from homeassistant.components.homekit.accessories import (
|
|||||||
HomeAccessory, HomeBridge)
|
HomeAccessory, HomeBridge)
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE,
|
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE,
|
||||||
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER)
|
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_NAME, CHAR_SERIAL_NUMBER)
|
||||||
|
|
||||||
from tests.mock.homekit import (
|
from tests.mock.homekit import (
|
||||||
get_patch_paths, mock_preload_service,
|
get_patch_paths, mock_preload_service,
|
||||||
@ -69,21 +69,23 @@ def test_override_properties():
|
|||||||
def test_set_accessory_info():
|
def test_set_accessory_info():
|
||||||
"""Test setting of basic accessory information with MockAccessory."""
|
"""Test setting of basic accessory information with MockAccessory."""
|
||||||
acc = MockAccessory('Accessory')
|
acc = MockAccessory('Accessory')
|
||||||
set_accessory_info(acc, 'model', 'manufacturer', '0000')
|
set_accessory_info(acc, 'name', 'model', 'manufacturer', '0000')
|
||||||
|
|
||||||
assert len(acc.services) == 1
|
assert len(acc.services) == 1
|
||||||
serv = acc.services[0]
|
serv = acc.services[0]
|
||||||
|
|
||||||
assert serv.display_name == SERV_ACCESSORY_INFO
|
assert serv.display_name == SERV_ACCESSORY_INFO
|
||||||
assert len(serv.characteristics) == 3
|
assert len(serv.characteristics) == 4
|
||||||
chars = serv.characteristics
|
chars = serv.characteristics
|
||||||
|
|
||||||
assert chars[0].display_name == CHAR_MODEL
|
assert chars[0].display_name == CHAR_NAME
|
||||||
assert chars[0].value == 'model'
|
assert chars[0].value == 'name'
|
||||||
assert chars[1].display_name == CHAR_MANUFACTURER
|
assert chars[1].display_name == CHAR_MODEL
|
||||||
assert chars[1].value == 'manufacturer'
|
assert chars[1].value == 'model'
|
||||||
assert chars[2].display_name == CHAR_SERIAL_NUMBER
|
assert chars[2].display_name == CHAR_MANUFACTURER
|
||||||
assert chars[2].value == '0000'
|
assert chars[2].value == 'manufacturer'
|
||||||
|
assert chars[3].display_name == CHAR_SERIAL_NUMBER
|
||||||
|
assert chars[3].value == '0000'
|
||||||
|
|
||||||
|
|
||||||
@patch(PATH_ACC, side_effect=mock_preload_service)
|
@patch(PATH_ACC, side_effect=mock_preload_service)
|
||||||
|
@ -280,3 +280,14 @@ class TestUtil(unittest.TestCase):
|
|||||||
mock_random.SystemRandom.return_value = generator
|
mock_random.SystemRandom.return_value = generator
|
||||||
|
|
||||||
assert util.get_random_string(length=3) == 'ABC'
|
assert util.get_random_string(length=3) == 'ABC'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_throttle_async():
|
||||||
|
"""Test Throttle decorator with async method."""
|
||||||
|
@util.Throttle(timedelta(seconds=2))
|
||||||
|
async def test_method():
|
||||||
|
"""Only first call should return a value."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
assert (await test_method()) is True
|
||||||
|
assert (await test_method()) is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user