Skip to content

Commit

Permalink
feat: transition behaviours
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Mason committed Jun 19, 2020
1 parent 4d659e0 commit ffbbb47
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 32 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,45 @@ The following is an example coniguration used to control my outside light at nig
end_time: 'sunrise + 01:00:00'
```

### Transition Behaviours (beta)
Transition Behaviours allow you to define what EC should do at these transition points.

**Use Case:**
> One use case for this is when lights turn on just before the end of the active period and then they never turn off because EC is constrained. For me, the light would be triggered by me walking into a room at
7am and the constrain period would begin at 7:16am. Since the light duration is 20 minutes, EC will never turn off the light because it is in `constrained` state at 7:20am. This is annoying because the lights stay on all day. I can use `end_time_action: "off"` to turn off all lights at 7:16am.
#### Supported transition points:
More will be added in the future.
|Key|Description|
|---|---|
|start_time_action|Triggered at the beginning of active period|
|end_time_action|Triggered at the end of active period|
#### Supported transition behaviours:
You must put these in quotes.
|Key|Description|
|---|---|
|"on"| Control entities will be explicitly turned on. |
|"off"| Control entities will be explicitly turned off. |
|"ignore"| (default) Nothing special will happen (control left to state machine) |
```yaml
mtn_outside:
sensor:
- binary_sensor.mtn_kitchen
entities:
- light.kitchen_led
state_entities:
- light.outside_light
delay: 1200
start_time: '18:00:00'
end_time: '07:00:00'
end_time_action: "off" # will turn off all control entities at 7am if they are on.
```
### Exponential Backoff
Enabling the `backoff` option will cause `delay` timeouts to increase exponentially by a factor of `backoff_factor` up until a maximum timeout value of `backoff_max` is reached.

Expand Down
111 changes: 80 additions & 31 deletions custom_components/entity_controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,50 @@
from .const import (
DOMAIN,
STATES,

CONF_START_TIME,
CONF_END_TIME,
CONF_END_TIME_ACTION,
CONF_START_TIME_ACTION,
CONF_TRANSITION_BEHAVIOUR_ON,
CONF_TRANSITION_BEHAVIOUR_OFF,
CONF_TRANSITION_BEHAVIOUR_IGNORE,

SENSOR_TYPE_DURATION,
SENSOR_TYPE_EVENT,
MODE_DAY,
MODE_NIGHT,
DEFAULT_DELAY,
DEFAULT_BRIGHTNESS,
DEFAULT_NAME,
CONF_CONTROL_ENTITIES,
CONF_CONTROL_ENTITY,
CONF_TRIGGER_ON_ACTIVATE,
CONF_TRIGGER_ON_DEACTIVATE,
CONF_SENSOR,
CONF_SENSORS,
CONF_SERVICE_DATA,
CONF_SERVICE_DATA_OFF,
CONF_STATE_ENTITIES,
CONF_DELAY,
CONF_BLOCK_TIMEOUT,
CONF_SENSOR_TYPE_DURATION,
CONF_SENSOR_TYPE,
CONF_SENSOR_RESETS_TIMER,
CONF_NIGHT_MODE,
CONF_STATE_ATTRIBUTES_IGNORE,
CONSTRAIN_START,
CONSTRAIN_END
)

from .entity_services import (
async_setup_entity_services,
)

CONSTRAIN_START = 1
CONSTRAIN_END = 2


VERSION = '5.1.2'
SENSOR_TYPE_DURATION = "duration"
SENSOR_TYPE_EVENT = "event"
MODE_DAY = "day"
MODE_NIGHT = "night"

DEFAULT_DELAY = 180
DEFAULT_BRIGHTNESS = 100
DEFAULT_NAME = "Entity Timer"

# CONF_NAME = 'slug'
CONF_CONTROL_ENTITIES = "entities"
CONF_CONTROL_ENTITY = "entity"
CONF_TRIGGER_ON_ACTIVATE = "trigger_on_activate"
CONF_TRIGGER_ON_DEACTIVATE = "trigger_on_deactivate"
CONF_SENSOR = "sensor"
CONF_SENSORS = "sensors"
CONF_SERVICE_DATA = "service_data"
CONF_SERVICE_DATA_OFF = "service_data_off"
CONF_STATE_ENTITIES = "state_entities"
CONF_DELAY = "delay"
CONF_BLOCK_TIMEOUT = "block_timeout"
CONF_SENSOR_TYPE_DURATION = "sensor_type_duration"
CONF_SENSOR_TYPE = "sensor_type"
CONF_SENSOR_RESETS_TIMER = "sensor_resets_timer"
CONF_NIGHT_MODE = "night_mode"
CONF_STATE_ATTRIBUTES_IGNORE = "state_attributes_ignore"


_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -99,6 +105,9 @@
vol.Optional(CONF_SENSOR_TYPE, default=SENSOR_TYPE_EVENT): vol.All(
vol.Lower, vol.Any(SENSOR_TYPE_EVENT, SENSOR_TYPE_DURATION)
),
vol.Optional(CONF_END_TIME_ACTION, default=CONF_TRANSITION_BEHAVIOUR_IGNORE): vol.All(
vol.Lower, vol.Any(CONF_TRANSITION_BEHAVIOUR_ON, CONF_TRANSITION_BEHAVIOUR_OFF, CONF_TRANSITION_BEHAVIOUR_IGNORE)
),
vol.Optional(CONF_SENSOR_RESETS_TIMER, default=False): cv.boolean,
vol.Optional(CONF_SENSOR, default=[]): cv.entity_ids,
vol.Optional(CONF_SENSORS, default=[]): cv.entity_ids,
Expand Down Expand Up @@ -327,6 +336,7 @@ def __init__(self, hass, config, machine):
_LOGGER.error(
"Configuration error! Please ensure you use plural keys for lists. e.g. sensors, entities"
)
raise e
event.async_call_later(hass, 1, self.do_update)

@property
Expand Down Expand Up @@ -434,6 +444,7 @@ def __init__(self, hass, config, machine, entity):
self.start = None
self.end = None
self.reset_count = None
self.transition_behaviours = {}
# logging.setFormatter(logging.Formatter(FORMAT))
self.log = logging.getLogger(__name__ + "." + config.get(CONF_NAME))

Expand Down Expand Up @@ -931,7 +942,6 @@ def end_time(self):
def config_times(self, config):
self._start_time_private = None
self._end_time_private = None
self.log_config()
if CONF_START_TIME in config and CONF_END_TIME in config:
# FOR OPTIONAL DEBUGGING: for initial setup use the raw input value
self._start_time_private = config.get(CONF_START_TIME)
Expand Down Expand Up @@ -973,6 +983,18 @@ def config_times(self, config):
"Constrain period active. Scheduling transition to 'constrained'"
)
event.async_call_later(self.hass, 1, self.constrain_entity)
if CONF_END_TIME_ACTION in config:
self.store_transition_behaviour(CONF_END_TIME_ACTION, config.get(CONF_END_TIME_ACTION))
if CONF_START_TIME_ACTION in config:
self.store_transition_behaviour(CONF_START_TIME_ACTION, config.get(CONF_START_TIME_ACTION))

else:
if CONF_END_TIME_ACTION in config or CONF_START_TIME_ACTION in config:
self.log.error("You must define %s and %s in your config to use the %s or %s feature." % (CONF_START_TIME, CONF_END_TIME, CONF_END_TIME_ACTION, CONF_END_TIME_ACTION))
self.log_config()




def config_override_entities(self, config):
self.overrideEntities = []
Expand Down Expand Up @@ -1052,12 +1074,12 @@ def end_time_callback(self, evt):
)
self.update(end_time=parsed_end)
# must be down here to make sure new callback is set regardless of exceptions
self.do_transition_behaviour(CONF_END_TIME_ACTION)
self.constrain()

@callback
def start_time_callback(self, evt):
"""
Called when `start_time` is reached, will change state to `idle` and schedule `end_time` callback.
"""
self.log.debug("START TIME CALLBACK.")
Expand All @@ -1077,11 +1099,12 @@ def start_time_callback(self, evt):
)

self.update(start_time=parsed_start)

if self.is_state_entities_on():
self.blocked()
else:
self.enable()
self.do_transition_behaviour(CONF_START_TIME_ACTION)

# =====================================================
# H E L P E R F U N C T I O N S ( N E W )
Expand Down Expand Up @@ -1532,4 +1555,30 @@ def log_config(self):
self.log.debug("Sunset: %s", self.sunset(True))
self.log.debug("Sunset Diff (to now): %s", self.next_sunset() - dt.now())
self.log.debug("Sunrise Diff(to now): %s", self.next_sunset() - dt.now())
self.log.debug("Transition Behaviours: %s", str(self.transition_behaviours))
self.log.debug("--------------------------------------------------")

def store_transition_behaviour(self, key, behaviour):
""" manages transition_behaviour map """
self.transition_behaviours[key] = behaviour

def get_transition_behaviour(self, key):
""" manages transition_behaviour map """
if key in self.transition_behaviours:
return self.transition_behaviours[key]
else:
return None

def do_transition_behaviour(self, behaviour):
""" Wrapper method for acting on transition behaviours such as at time of end constraint of state transitions from override state. """
self.log.debug("Performing Transition Behaviour %s" % (behaviour))
action = self.get_transition_behaviour(CONF_END_TIME_ACTION)
if action:
self.log.debug("Performing Transition Action %s" % (action))
if action == CONF_TRANSITION_BEHAVIOUR_ON or action:
self.log.debug("Performing Transition Action turning on")
self.turn_on_control_entities()
if action == CONF_TRANSITION_BEHAVIOUR_OFF or not action:
self.log.debug("Performing Transition Action turning off")
self.turn_off_control_entities()

33 changes: 32 additions & 1 deletion custom_components/entity_controller/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,41 @@
#configuration
CONF_START_TIME = 'start_time'
CONF_END_TIME = 'end_time'
CONF_END_TIME_ACTION = 'end_time_action'
CONF_START_TIME_ACTION = 'start_time_action'
CONF_TRANSITION_BEHAVIOUR_ON = 'on'
CONF_TRANSITION_BEHAVIOUR_OFF = 'off'
CONF_TRANSITION_BEHAVIOUR_IGNORE = 'ignore'
SENSOR_TYPE_DURATION = "duration"
SENSOR_TYPE_EVENT = "event"
MODE_DAY = "day"
MODE_NIGHT = "night"

DEFAULT_DELAY = 180
DEFAULT_BRIGHTNESS = 100
DEFAULT_NAME = "Entity Timer"

# CONF_NAME = 'slug'
CONF_CONTROL_ENTITIES = "entities"
CONF_CONTROL_ENTITY = "entity"
CONF_TRIGGER_ON_ACTIVATE = "trigger_on_activate"
CONF_TRIGGER_ON_DEACTIVATE = "trigger_on_deactivate"
CONF_SENSOR = "sensor"
CONF_SENSORS = "sensors"
CONF_SERVICE_DATA = "service_data"
CONF_SERVICE_DATA_OFF = "service_data_off"
CONF_STATE_ENTITIES = "state_entities"
CONF_DELAY = "delay"
CONF_BLOCK_TIMEOUT = "block_timeout"
CONF_SENSOR_TYPE_DURATION = "sensor_type_duration"
CONF_SENSOR_TYPE = "sensor_type"
CONF_SENSOR_RESETS_TIMER = "sensor_resets_timer"
CONF_NIGHT_MODE = "night_mode"
CONF_STATE_ATTRIBUTES_IGNORE = "state_attributes_ignore"
MODE_DAY = 'day'
MODE_NIGHT = 'night'

CONSTRAIN_START = 1
CONSTRAIN_END = 2
STATES = ['idle', 'overridden', 'constrained', 'blocked',
{'name': 'active', 'children': ['timer', 'stay_on'],
'initial': False}]

0 comments on commit ffbbb47

Please sign in to comment.