Skip to content

Commit

Permalink
feat!: handle overrides better
Browse files Browse the repository at this point in the history
feat!: handle overrides better

BREAKING CHANGE: Entering the override state will no longer turn off the control entities. This was implemented with the ratiionale that overriding an EC should not cause further interaction with the control entities. If the entities are on, they remain on when transitioning from active to override state. If the entities are off, they remain off going from other states in to override state.
If your configuration relied on this previous behaviour it will be a breaking change for you. Its unlikely your config took advantage of this weird behaviour.
  • Loading branch information
danobot authored Jan 10, 2020
2 parents 0edf1d4 + af83528 commit 63f7aa8
Show file tree
Hide file tree
Showing 24 changed files with 170 additions and 146 deletions.
5 changes: 3 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ about: Create a report to help us improve

---

Before opening a new issue, please read the documentation and search for keywords relating to your problem. Do not delete the issue template headers. All information must be filled for an issue to be accepted.
Since this is an open source project expect to be involved in the issue resolution because we depend on your support.

<<< Do not delete the following template headers. All information must be filled in because we need it to analyse the problem. Your issue will be auto closed without this information.
Before opening a new issue, please read the documentation and search for keywords relating to your problem. Since this is an open source project expect to be involved in the issue resolution because we depend on your support. >>>

## Description
A clear and concise description of what the bug is.
Expand Down
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Be kind to code reviewers, please try to keep pull requests as small and focused

**IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of the MIT License.

## Deccription
## Description

## Relates Issues
## Related Issues

Closes
60 changes: 0 additions & 60 deletions .gitlab-ci.yml

This file was deleted.

129 changes: 86 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Introduction
Entity Controller (EC) is an implementation of "When This, Then That" using a finite state machine that ensures basic automations do not interfere with the rest of your home automation setup. This component encapsulates common automation scenarios into a neat package that can be configured easily and reused throughout your home. Traditional automations would need to be duplicated _for each instance_ in your config. The use cases for this component are endless because you can use any entity as input and outputs (there is no restriction to motion sensors and lights).
Entity Controller (EC) is an implementation of "When This, Then That for x amount of time" using a finite state machine that ensures basic automations do not interfere with the rest of your home automation setup. This component encapsulates common automation scenarios into a neat package that can be configured easily and reused throughout your home. Traditional automations would need to be duplicated _for each instance_ in your config. The use cases for this component are endless because you can use any entity as input and outputs (there is no restriction to motion sensors and lights).

**Latest stable version `v4.1.1` tested on Home Assistant `0.102.3`.**

Expand All @@ -16,41 +16,53 @@ All the boilerplate for Pytest is set up, but I got stuck mocking the passage of

# Requirements
This component started out as an AppDaemon script implementation of motion activated lighting but it has since been generalised to be able to control any Home Assistant entity. I have discussed the original core requirements for motion lights [on my blog](https://www.danielbkr.net/2018/05/17/appdaemon-motion-lights.html). The basic responsibilities of EC are as follows:
* (1) turn on **control entities** when **state entities** are triggered
* (2) turn off **control entities** when **state entities** remain off for some time
* (1) turn on **control entities** when **sensor entities** are triggered
* (2) turn off **control entities** when **sensor entities** remain off for some time
* (3) Do not interfere with manually controlled entities (tricky and not so obvious)
* (3.1) An entity that is already on should not be affected by time outs. (EC should ignore it)
* (3.1) An entity that is already on should not be affected by time outs. (EC should ignore it and not start a timer,[Read more on my blog...](https://danielbkr.net/2018/03/20/motion-sensor-lights.html#problem-motion-lights-turn-off-manually-controlled-lights-after-time-out-period-expires))
* (3.2) An entity that is manually controlled within the time-out period should have its timer cancelled, and therefore stay on.

In the original context of motion lighting, this means:

* (1) turn on light when motion is detected
* (2) turn off light when no motion is detected for some time
* (3) Do not interfere with manually activated lights
* (3.1) A light that is already on must not be controlled. (EC should ignore it)
* (3.1) A light that is already on must not be controlled. (EC should ignore it and not start a timer)
* (3.2) A light that is dimmed (or color changed) within the time-out period should have its EC timer cancelled, and therefore stay on.

This FSM implementation is by far the most elegant solution I have found for this problem as the typical "if/else" algorythm got way out of hand and unmanagable.
# Terminology
Control entities
: EC will control these entities by turning them on or off.

State entities
: EC will observe the state of these entities and use it to trigger events (in cases where control entities do not supply a sensible state, for example scripts)
## State Meaning

|State|Description|
|---|---|
|idle|EC is observing states, nothing else.|
|active|Momentary, intermediate state. You won't see EC in this state much at all.|
|active_timer|Control entities have been switched on and timer is running|
|active_stay_on|Control entities have been switched on and will remain on until they are switched off manually.|
|overridden|Entity is overridden by an `override_entity`|
|blocked|When a control entity is already in `on` state and a sensor entity is triggered, EC will enter the `blocked` state. This is to ensure the controller does not interfere with other automations or manual control. The idea is, if the entity is already on, then the problem is already taken care of. EC will return to **idle** state once all `control_entites` return to `off` state.|
|constrained|Current time is outside of `start_time` and `end_time`. EC is inactive until `start_time`.|

Note that `control_entities == state_entities` unless you specifically define `state_entities` in your configuration.



# Configuration
EC is very configurable. The following documentation section explain the different ways you can configure EC. In its most basic form, you can define:

|Configuration|Description|
|---|---|
|control entities| The entities you wish to switch on and off depending on _sensor_ entity states.|
|control entities| The entities you wish to switch on and off depending on _sensor_ entity states. EC will control these entities by turning them on or off.|
|sensor entities| Used as triggers. When these entities turn on, your _control entities_ will be switched on|
|state entities|Unless you wish to use non-stateful entities, you need not worry about state entities. Essentially, they allow you to define specific entities that will be used for state observation *in cases where control entities do not supply a usable state*. (As is the case with `scene`.) Optional.|
|override entities| The entities used to override the entire EC logic. Optional.|

## Basic Configuration
The controller needs `sensors` to monitor (such as motion detectors, binary switches, doors, weather, etc) as well as an entity to control (such as a light).

![Basic Controller](images/basic.gif)

```yaml
entity_controller:
motion_light: # serves as a name
Expand All @@ -60,6 +72,11 @@ entity_controller:
```
**Note:** The top-level domain key `entity_controller` will be omitted in the following examples.

**Blocked state demonstration**
R3.1 is implemented using the **blocked** state. See demo below:

![Block demo](images/blocked.gif)

### Using Time Constraints
You may wish to constrain at what time of day your motion lights are activated. You can use the `start_time` and `end_time` parameters for this.
```yaml
Expand All @@ -78,20 +95,21 @@ motion_light_sun:
end_time: sunrise + 00:30:00 # required
```

### Home Assistant State Entities
Since `v1.1.0`, EC creates and updates entities representing the EC itself. Beyond basic state (e.g. active, idle, disabled, etc.), this provides additional state attributes as shown below.

![HASS Entity State Attributes 1](images/state_attributes_1.png)

![HASS Entity State Attributes 2](images/state_attributes_2.png)

![HASS Entity State Attributes 3](images/state_attributes_3.png)

These can be referenced in various `sensor` and `automation` configurations.
# Stay on
This simple option will keep EC in **active_stay_on** state indefinitely until the control entity is manually turned off.
```yaml
override_example:
sensor: binary_sensor.lounge_motion
entity: light.lounge_lamp
delay: 5
stay: true
```

### Overrides
You can define entities which stop EC from transitioning into `active` state if those entities are in `on` state. This allows you to enable/disable your controller based on environmental conditions such as "when I am watching TV" or "when the train is late" (seriously...).

![Override Demo](images/override.gif)

```yaml
override_example:
sensor:
Expand Down Expand Up @@ -138,33 +156,60 @@ There are two types of motion sensors:
1. Sends a signal when motion happens (instantaneous event)
2. Sends a signal when motion happens, stays on for the duration of motion and sends an `off` signal when motion supposedly ceases. (duration)

By default, EC assumes you have a Type 1 motion sensor (event based), these are more useful in home automation because they supply raw, unfiltered and unprocessed data. No assumptions are made about how the motion event data will be used.
By default, EC assumes you have a Type 1 motion sensor (event based), these are more useful in home automation because they supply raw, unfiltered and unprocessed data. No assumptions are made about how the motion event data will be used. Since entties are stateful, the motion sensor entity in the demo below is on for only a brief period. EC only cares about the state change from `off` to `on`. In the future, there will be support for listening to HA events as well, which means the need to create 'dummy' `binary_sensors` for motion sensors is removed. Check out my [`processor` component](https://github.com/danobot/mqtt_payload_processor) for more info.


In the future, there will be support for listening to HA events as well, which means the need to create 'dummy' `binary_sensors` for motion sensors is removed.

If your sensor emits both `on` and `off` signals, then add `sensor_type: duration` to your configuration. This can be useful for motion sensors, door sensors and locks (not an exhaustive list). By default, the controller treats sensors as `event` sensors.
If your motion sensor emits both `on` and `off` signals, then add `sensor_type: duration` to your configuration. This can be useful for motion sensors, door sensors and locks (not an exhaustive list). By default, the controller treats sensors as `event` sensors.

Control entities are turned off when the following events occur (whichever happens last)
Control entities are turned off when the following events occur (whichever happens last):
* the timer expires and sensor is off
* the sensor state changes to `off` and timer already expired

The following demo shows the behaviour in those two scenarios:



If you want the timer to be restarted one last time when the sensor returns to `off`, then add `sensor_resets_timer: True` to your entity configuration.

Notation: `[ ]` indicate internal, `( )` indicates external, `...` indicates passage of time, `->` Indicates related action
#### Sensor Type Demonstrations

Notation for state transition demonstrations:
* `[ ]` indicate internal event,
* `( )` indicates external influence (sensor state change),
* `...` indicates passage of time,
* `->` Indicates flow

**Normal sensor**
Idle -> Active Timer -> [timer started] ... [timer expires] -> Idle

> Idle -> Active Timer -> [timer started] ... [timer expires] -> Idle

![Event Demo](images/event.gif)

**Duration Sensor**
Idle -> Active Timer - [timer started] ... **[Timer expires] ... (sensor goes to off)** -> Idle

> Idle -> Active Timer -> [timer started] ... **[timer expires] ... (sensor goes to off)** -> Idle

![Duration Demo](images/duration.gif)

**With `sensor_resets_timer`**
Idle -> Active Timer -> [timer started] ... [original timer expires] ... (sensor goes to off) ... **[timer restarted] .. [timer expires]** -> Idle

> Idle -> Active Timer -> [timer started] ... [timer expires] ... (sensor goes to off) ... **[timer restarted] ... [timer expires]** -> Idle

![Duration Demo](images/duration_sensor_resets_timer.gif)

### Home Assistant State Entities
Since `v1.1.0`, EC creates and updates entities representing the EC itself. Beyond basic state (e.g. active, idle, overridden, etc.), this provides additional state attributes which update dynamically based on the state of the controller. See GIF animations for examples..

These can be referenced in various `sensor` and `automation` configurations and extracted using `state-attributes-card` and template sensors.

## Advanced Configuration

### 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.

![Backoff demo](images/backoff.gif)

The graph below shows the relationship between number of sensor triggers and timeout values for the shown parameters.
```
delay = 60
Expand Down Expand Up @@ -196,11 +241,15 @@ When `block_timeout` is defined, the controller will start a timer when the sens
The state sequence is as follows:

**Without block_timeout:**
Idle ... (sensor ON) -> Blocked ... **(control entity OFF)** -> Idle

> Idle ... (sensor ON) -> Blocked ... **(control entity OFF)** -> Idle
![block timeout demo](images/blocked.gif)
**With block_timeout:**
Idle ... (sensor ON) -> Blocked ... **(sensor ON) -> [Timer started] ... [Timer expires]** -> Idle

> Idle ... (sensor ON) -> Blocked ... **(sensor ON) -> [Timer started] ... [Timer expires]** -> Idle
![block timeout demo](images/block_timeout.gif)


**Example configuration:**
Expand Down Expand Up @@ -311,18 +360,6 @@ By default, any attribute change is considered significant and will qualify for
- brightness
- color_temp
```
# State Meaning

|State|Description|
|---|---|
|idle|EC is observing states, nothing else.|
|active|Momentary, intermediate state to `active_timer`. You won't see EC in this state much at all.|
|active_timer|Control entities have been switched on and timer is running|
|overridden|Entity is overridden by an `override_entity`|
|blocked|Entities in this state wanted to turn on (a sensor entity triggered) but were blocked because one or more `control_entites`/`state_entities` are already in an `on` state. Entity will return to idle state once all `control_entites` (or `state_entities`, if configured) return to `off` state|
|constrained|Current time is outside of `start_time` and `end_time`. Entity is inactive until `start_time`|

Note that, unless you specifically define `state_entities` in your configuration, that `control_entities == state_entities`.

# Debugging

Expand Down Expand Up @@ -356,6 +393,12 @@ soon_test_case:

EC is a complete rewrite of the original application (version 0), using the Python `transitions` library to implement a [Finite State Machine](https://en.wikipedia.org/wiki/Finite-state_machine). This cleans up code logic considerably due to the nature of this application architecture.

## Related Research and Development

* [Motion Lighting - first steps (superceded)](https://danielbkr.net/2018/03/20/motion-sensor-lights.html)
* [Motion Lighting requirements - A complete guide](https://danielbkr.net/2018/05/17/appdaemon-motion-lights.html)
* [Home Assistant priority locks concept](https://danielbkr.net/2018/08/25/ha-priority-locks.html)
* [How to: Set up Stateless Motion Binary Sensors](https://danielbkr.net/2018/03/19/rf-binary-sensors.html)

# Automatic updates using HACS
EC is available on the Home Assistant Community Store (HACS). This is the recommended installation method to benefit from automated updates and quick release adoption.
Expand Down
3 changes: 0 additions & 3 deletions ci.sh

This file was deleted.

Loading

0 comments on commit 63f7aa8

Please sign in to comment.