Skip to content

Commit

Permalink
feat: add customisable grace_period paramater to cater for long laten…
Browse files Browse the repository at this point in the history
…cies in control entities
  • Loading branch information
Daniel Mason committed Sep 4, 2020
1 parent 233f96d commit b59d70c
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 7 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,23 @@ By default, any attribute change is considered significant and will qualify for
- color_temp
```

## Troubleshooting

### EC goes into blocked state
Check how long it takes your control entities to actually turn on after the service call is dispatched. The default `grace_period` is 2 seconds.
This means EC will make the call to turn on all entities and then it will ignore any state updates that come in for 2 seconds (default). The reason for this is that without this grace period, EC would actually block itself because it would interpret the state updates as manual control.

Unfortunately, it is not possible to detect *who* called a service in Home Assistant. If that was the case we could simply ignore any service calls originating from EC itself.

To solve your issue with EC ending up in `blocked` state, you can take a look at increasing the `grace_period` to something like 5 seconds. If your lights have a lot of latency then increasing this period will most likely resolve your issue.

```yaml
grace_period_ec:
sensor: binary_sensor.living_room_motion
entity: light.tv_backlight
grace_period: 5 # default is 2
```

# Debugging

## Enabling Debug Logging
Expand Down
21 changes: 14 additions & 7 deletions custom_components/entity_controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
CONF_NIGHT_MODE,
CONF_STATE_ATTRIBUTES_IGNORE,
CONSTRAIN_START,
CONSTRAIN_END
CONSTRAIN_END,
CONF_IGNORE_STATE_CHANGES_UNTIL
)

from .entity_services import (
Expand Down Expand Up @@ -143,6 +144,7 @@
vol.Optional(CONF_TRIGGER_ON_DEACTIVATE, default=None): cv.entity_ids,
vol.Optional(CONF_STATE_ENTITIES, default=[]): cv.entity_ids,
vol.Optional(CONF_BLOCK_TIMEOUT, default=None): cv.positive_int,
vol.Optional(CONF_IGNORE_STATE_CHANGES_UNTIL, default=None): cv.positive_int,
vol.Optional(CONF_NIGHT_MODE, default=None): MODE_SCHEMA,
vol.Optional(CONF_STATE_ATTRIBUTES_IGNORE, default=[]): cv.ensure_list,
vol.Optional(CONF_SERVICE_DATA, default=None): vol.Coerce(
Expand Down Expand Up @@ -302,7 +304,7 @@ async def async_setup(hass, config):
trigger="control",
source="active_timer",
dest="idle",
conditions=["is_state_entities_off"],
conditions=["is_state_entities_off"]
)
machine.add_transition(trigger='control', source='active_timer',
dest='blocked', conditions=['is_state_entities_on'])
Expand Down Expand Up @@ -457,6 +459,7 @@ def __init__(self, hass, config, machine, entity):
self.config = (
{}
) # new way of storing configuration (avoids having an attribue for each)
self.config = config
self.debug_day_length = config.get("day_length", None)
self.stateEntities = []
self.controlEntities = []
Expand Down Expand Up @@ -618,12 +621,11 @@ def state_entity_state_change(self, entity, old, new):
+ str(a)
)
if self.is_active_timer():
if datetime.now() > self.ignore_state_changes_until: # check if we are within the grace period after making a service call (this avoids EC blocking itself)
if self.is_within_grace_period(): # check if we are within the grace period after making a service call (this avoids EC blocking itself)
self.log.debug("state_entity_state_change :: This state change is within %i seconds of calling a service. Ignoring this state change because its probably caused by EC itself." % self.config.get(CONF_IGNORE_STATE_CHANGES_UNTIL, 2))
else:
self.log.debug("state_entity_state_change :: We are in active timer and the state of observed state entities changed.")

self.control()
else:
self.log.debug("state_entity_state_change :: This state change is within 2 seconds of calling a service. Ignoring this state change because its probably caused by EC itself.")

if self.is_blocked() or self.is_active_stay_on(): # if statement required to avoid MachineErrors, cleaner than adding transitions to all possible states.
self.enable()
Expand Down Expand Up @@ -706,6 +708,11 @@ def _override_entity_state(self):
def is_override_state_off(self):
return self._override_entity_state() is None

def is_within_grace_period(self):
""" Dtermines if the last service call EC made was within the last 2 seconds.
This is important or else EC will react to state changes caused by EC itself which results in going into blocked state."""
return datetime.now() < self.ignore_state_changes_until

def is_override_state_on(self):
return self._override_entity_state() is not None

Expand Down Expand Up @@ -1442,7 +1449,7 @@ def prepare_service_data(self):
def call_service(self, entity, service, **kwargs):
""" Helper for calling HA services with the correct parameters """
self.log.debug("call_service :: Calling service " + service + " on " + entity)
self.ignore_state_changes_until = datetime.now() + timedelta(seconds=2)
self.ignore_state_changes_until = datetime.now() + timedelta(seconds=self.config.get(CONF_IGNORE_STATE_CHANGES_UNTIL, 2))
self.log.debug("call_service :: Setting ignore_state_changes_until to " + str(self.ignore_state_changes_until))

domain, e = entity.split(".")
Expand Down
1 change: 1 addition & 0 deletions custom_components/entity_controller/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@
STATES = ['idle', 'overridden', 'constrained', 'blocked',
{'name': 'active', 'children': ['timer', 'stay_on'],
'initial': False}]
CONF_IGNORE_STATE_CHANGES_UNTIL = "grace_period"

0 comments on commit b59d70c

Please sign in to comment.