mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Renamed AddCooldown to Throttle and added bypass functionality
This commit is contained in:
parent
31b9f65513
commit
48089b01ab
@ -192,32 +192,65 @@ class TestUtil(unittest.TestCase):
|
|||||||
set1.update([1, 2], [5, 6])
|
set1.update([1, 2], [5, 6])
|
||||||
self.assertEqual([2, 3, 1, 5, 6], set1)
|
self.assertEqual([2, 3, 1, 5, 6], set1)
|
||||||
|
|
||||||
def test_add_cooldown(self):
|
def test_throttle(self):
|
||||||
""" Test the add cooldown decorator. """
|
""" Test the add cooldown decorator. """
|
||||||
calls = []
|
calls1 = []
|
||||||
|
|
||||||
@util.AddCooldown(timedelta(milliseconds=500))
|
@util.Throttle(timedelta(milliseconds=500))
|
||||||
def test_cooldown():
|
def test_throttle1():
|
||||||
calls.append(1)
|
calls1.append(1)
|
||||||
|
|
||||||
self.assertEqual(0, len(calls))
|
calls2 = []
|
||||||
|
|
||||||
test_cooldown()
|
@util.Throttle(
|
||||||
|
timedelta(milliseconds=500), timedelta(milliseconds=250))
|
||||||
|
def test_throttle2():
|
||||||
|
calls2.append(1)
|
||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
# Ensure init is ok
|
||||||
|
self.assertEqual(0, len(calls1))
|
||||||
|
self.assertEqual(0, len(calls2))
|
||||||
|
|
||||||
test_cooldown()
|
# Call first time and ensure methods got called
|
||||||
|
test_throttle1()
|
||||||
|
test_throttle2()
|
||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls1))
|
||||||
|
self.assertEqual(1, len(calls2))
|
||||||
|
|
||||||
|
# Call second time. Methods should not get called
|
||||||
|
test_throttle1()
|
||||||
|
test_throttle2()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(calls1))
|
||||||
|
self.assertEqual(1, len(calls2))
|
||||||
|
|
||||||
|
# Call again, overriding throttle, only first one should fire
|
||||||
|
test_throttle1(no_throttle=True)
|
||||||
|
test_throttle2(no_throttle=True)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(calls1))
|
||||||
|
self.assertEqual(1, len(calls2))
|
||||||
|
|
||||||
|
# Sleep past the no throttle interval for throttle2
|
||||||
time.sleep(.3)
|
time.sleep(.3)
|
||||||
|
|
||||||
test_cooldown()
|
test_throttle1()
|
||||||
|
test_throttle2()
|
||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(2, len(calls1))
|
||||||
|
self.assertEqual(1, len(calls2))
|
||||||
|
|
||||||
time.sleep(.2)
|
test_throttle1(no_throttle=True)
|
||||||
|
test_throttle2(no_throttle=True)
|
||||||
|
|
||||||
test_cooldown()
|
self.assertEqual(3, len(calls1))
|
||||||
|
self.assertEqual(2, len(calls2))
|
||||||
|
|
||||||
self.assertEqual(2, len(calls))
|
time.sleep(.5)
|
||||||
|
|
||||||
|
test_throttle1()
|
||||||
|
test_throttle2()
|
||||||
|
|
||||||
|
self.assertEqual(4, len(calls1))
|
||||||
|
self.assertEqual(3, len(calls2))
|
||||||
|
@ -87,7 +87,7 @@ class LuciDeviceScanner(object):
|
|||||||
return
|
return
|
||||||
return self.mac2name.get(device, None)
|
return self.mac2name.get(device, None)
|
||||||
|
|
||||||
@util.AddCooldown(MIN_TIME_BETWEEN_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
def _update_info(self):
|
def _update_info(self):
|
||||||
""" Ensures the information from the Luci router is up to date.
|
""" Ensures the information from the Luci router is up to date.
|
||||||
Returns boolean if scanning successful. """
|
Returns boolean if scanning successful. """
|
||||||
|
@ -82,7 +82,7 @@ class NetgearDeviceScanner(object):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@util.AddCooldown(MIN_TIME_BETWEEN_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
def _update_info(self):
|
def _update_info(self):
|
||||||
""" Retrieves latest information from the Netgear router.
|
""" Retrieves latest information from the Netgear router.
|
||||||
Returns boolean if scanning successful. """
|
Returns boolean if scanning successful. """
|
||||||
|
@ -78,7 +78,7 @@ class TomatoDeviceScanner(object):
|
|||||||
else:
|
else:
|
||||||
return filter_named[0]
|
return filter_named[0]
|
||||||
|
|
||||||
@util.AddCooldown(MIN_TIME_BETWEEN_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
def _update_tomato_info(self):
|
def _update_tomato_info(self):
|
||||||
""" Ensures the information from the Tomato router is up to date.
|
""" Ensures the information from the Tomato router is up to date.
|
||||||
Returns boolean if scanning successful. """
|
Returns boolean if scanning successful. """
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
""" Support for Hue lights. """
|
""" Support for Hue lights. """
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
import homeassistant.util as util
|
||||||
from homeassistant.components import ToggleDevice, ATTR_FRIENDLY_NAME
|
from homeassistant.components import ToggleDevice, ATTR_FRIENDLY_NAME
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_TRANSITION)
|
ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_TRANSITION)
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
||||||
|
|
||||||
PHUE_CONFIG_FILE = "phue.conf"
|
PHUE_CONFIG_FILE = "phue.conf"
|
||||||
|
|
||||||
@ -37,25 +39,9 @@ def get_lights(hass, config):
|
|||||||
|
|
||||||
lights = {}
|
lights = {}
|
||||||
|
|
||||||
def update_lights(force_reload=False):
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||||
""" Updates the light states. """
|
def update_lights():
|
||||||
now = datetime.now()
|
""" Updates the Hue light objects with latest info from the bridge. """
|
||||||
|
|
||||||
try:
|
|
||||||
time_scans = now - update_lights.last_updated
|
|
||||||
|
|
||||||
# force_reload == True, return if updated in last second
|
|
||||||
# force_reload == False, return if last update was less then
|
|
||||||
# MIN_TIME_BETWEEN_SCANS ago
|
|
||||||
if force_reload and time_scans.seconds < 1 or \
|
|
||||||
not force_reload and time_scans < MIN_TIME_BETWEEN_SCANS:
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
# First time we run last_updated is not set, continue as usual
|
|
||||||
pass
|
|
||||||
|
|
||||||
update_lights.last_updated = now
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = bridge.get_api()
|
api = bridge.get_api()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
@ -142,4 +128,4 @@ class HueLight(ToggleDevice):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Synchronize state with bridge. """
|
""" Synchronize state with bridge. """
|
||||||
self.update_lights(True)
|
self.update_lights(no_throttle=True)
|
||||||
|
@ -4,7 +4,7 @@ homeassistant.components.switch
|
|||||||
Component to interface with various switches that can be controlled remotely.
|
Component to interface with various switches that can be controlled remotely.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
@ -96,21 +96,16 @@ def setup(hass, config):
|
|||||||
ent_to_switch[entity_id] = switch
|
ent_to_switch[entity_id] = switch
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def update_states(time, force_reload=False):
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def update_states(now):
|
||||||
""" Update states of all switches. """
|
""" Update states of all switches. """
|
||||||
|
|
||||||
# First time this method gets called, force_reload should be True
|
logger.info("Updating switch states")
|
||||||
if force_reload or \
|
|
||||||
datetime.now() - update_states.last_updated > \
|
|
||||||
MIN_TIME_BETWEEN_SCANS:
|
|
||||||
|
|
||||||
logger.info("Updating switch states")
|
for switch in switches:
|
||||||
update_states.last_updated = datetime.now()
|
switch.update_ha_state(hass)
|
||||||
|
|
||||||
for switch in switches:
|
update_states(None)
|
||||||
switch.update_ha_state(hass)
|
|
||||||
|
|
||||||
update_states(None, True)
|
|
||||||
|
|
||||||
def handle_switch_service(service):
|
def handle_switch_service(service):
|
||||||
""" Handles calls to the switch services. """
|
""" Handles calls to the switch services. """
|
||||||
|
@ -8,7 +8,7 @@ import collections
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import enum
|
import enum
|
||||||
import socket
|
import socket
|
||||||
@ -52,7 +52,7 @@ def str_to_datetime(dt_str):
|
|||||||
@rtype: datetime
|
@rtype: datetime
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(dt_str, DATE_STR_FORMAT)
|
return datetime.strptime(dt_str, DATE_STR_FORMAT)
|
||||||
except ValueError: # If dt_str did not match our format
|
except ValueError: # If dt_str did not match our format
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ def repr_helper(inp):
|
|||||||
return ", ".join(
|
return ", ".join(
|
||||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||||
in inp.items())
|
in inp.items())
|
||||||
elif isinstance(inp, datetime.datetime):
|
elif isinstance(inp, datetime):
|
||||||
return datetime_to_str(inp)
|
return datetime_to_str(inp)
|
||||||
else:
|
else:
|
||||||
return str(inp)
|
return str(inp)
|
||||||
@ -274,36 +274,48 @@ def validate_config(config, items, logger):
|
|||||||
return not errors_found
|
return not errors_found
|
||||||
|
|
||||||
|
|
||||||
class AddCooldown(object):
|
class Throttle(object):
|
||||||
"""
|
"""
|
||||||
A method decorator to add a cooldown to a method.
|
A method decorator to add a cooldown to a method to prevent it from being
|
||||||
|
called more then 1 time within the timedelta interval `min_time` after it
|
||||||
|
returned its result.
|
||||||
|
|
||||||
If you set a cooldown of 5 seconds. Then if you call a method twice the
|
Calling a method a second time during the interval will return None.
|
||||||
underlaying method will not be called if the second call was within
|
|
||||||
5 seconds of the first. None will be returned instead.
|
|
||||||
|
|
||||||
Makes a last_call attribute available on the wrapped method.
|
Pass keyword argument `no_throttle=True` to the wrapped method to make
|
||||||
|
the call not throttled.
|
||||||
|
|
||||||
|
Decorator takes in an optional second timedelta interval to throttle the
|
||||||
|
'no_throttle' calls.
|
||||||
|
|
||||||
|
Adds a datetime attribute `last_call` to the method.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
def __init__(self, min_time):
|
def __init__(self, min_time, limit_no_throttle=None):
|
||||||
self.min_time = min_time
|
self.min_time = min_time
|
||||||
|
self.limit_no_throttle = limit_no_throttle
|
||||||
|
|
||||||
def __call__(self, method):
|
def __call__(self, method):
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
if self.limit_no_throttle is not None:
|
||||||
|
method = Throttle(self.limit_no_throttle)(method)
|
||||||
|
|
||||||
@wraps(method)
|
@wraps(method)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Wrapper that allows wrapped to be called only once per min_time.
|
Wrapper that allows wrapped to be called only once per min_time.
|
||||||
"""
|
"""
|
||||||
with lock:
|
with lock:
|
||||||
now = datetime.datetime.now()
|
|
||||||
last_call = wrapper.last_call
|
last_call = wrapper.last_call
|
||||||
|
# Check if method is never called or no_throttle is given
|
||||||
|
force = last_call is None or kwargs.pop('no_throttle', False)
|
||||||
|
|
||||||
|
if force or datetime.now() - last_call > self.min_time:
|
||||||
|
|
||||||
if last_call is None or now - last_call > self.min_time:
|
|
||||||
result = method(*args, **kwargs)
|
result = method(*args, **kwargs)
|
||||||
wrapper.last_call = now
|
wrapper.last_call = datetime.now()
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@ -418,7 +430,7 @@ def _threadpool_worker(work_queue, current_jobs, job_handler, quit_task):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Add to current running jobs
|
# Add to current running jobs
|
||||||
job_log = (datetime.datetime.now(), job)
|
job_log = (datetime.now(), job)
|
||||||
current_jobs.append(job_log)
|
current_jobs.append(job_log)
|
||||||
|
|
||||||
# Do the job
|
# Do the job
|
||||||
|
Loading…
x
Reference in New Issue
Block a user