Skip to content

DynamicGroupController

Attention

DynamicGroupController is new and still viewed as experimental. Its configuration, operation and documentation are subject to change, and probably particular at risk to do so based on feedback from users early on.

DynamicGroupController lets you create groups of entities. The inclusion of the term dynamic in its name reflects its ability to change the group membership in response to its members ability to meet criteria that may include specific attribute values or ranges. For example, you can create a group for entities that are battery powered and have battery level below a certain threshold. Any time an entity's battery level is updated, the group will automatically determine if that entity should have membership or not.

Using that example, let's do a sample configuration. First, like any controller, it needs to have an entry in the controllers section of your reactor.yaml file:

  - id: groups
    name: Dynamic Group Controller
    enable: true
    implementation: DynamicGroupController
    config:
      groups:

From here, we'll add the specific entries we need to create a group called low_battery_entities:

    config:
      groups:
        "low_battery_entities":
          name: Low Battery Entities
          select:
            - include_capability:
              - battery_power
          filter_expression: entity.attributes.battery_power.level < 0.35

The first line under groups: establishes the ID of the group; each group must have a unique ID (groups are themselves entities, so this rule is consistent with all other entities).

The select section tells DynamicGroupController what entities from all of those available in the system it should choose as eligible for the group. In this example, we are chosing only those entities that have the battery_power capability.

The possible selectors are:

  • include_controller — the value can be a string or an array of strings; entities from any of the given controller IDs will be brought into the selection set;
  • exclude_controller — the value can be a string or an array of strings; as each entity is checked for eligibility, if it comes from any of the listed controllers, it is rejected (removed from the set/group);
  • include_group — the value can be a string or array of strings, which are canonical IDs or names (if unique) of another group; each specified group's members are included in the result set.
  • exclude_group — the value can be a string or array of strings, which are canonical IDs or names (if unique) of another group; each specified group's members are removed from the result set.
  • include_entity — the value can be a string or array of strings, which are canonical IDs or names (if unique); each specified entity is added to the result set;
  • exclude_entity — the value can be a string or array of strings, which are canonical IDs or names (if unique); each specified entity is removed from the result set;
  • include_capability — the value can be a string or array of strings, which are capability names; if an entity of the set has any of the listed capabilities; it remains in the set, otherwise it is removed;
  • exclude_capability — the value can be a string or array of strings, which are capability names; if an entity of the set has any of the listed capabilities, it is removed from the set; otherwise it remains.

Notice that select is an array (the - starting the selector line means it's an array element). You can have any number of selectors, and they are interpreted in order, with each selector modifying the set of entities resulting from its predecessor. You are required to have at least one selector in the select section.

If the first selector processed is one of the include_ selectors, then the set is assumed to be empty at the start, and after that first selector completes, the set will contain only those entities matching the selector. If the first selector is one of the exclude_ selectors, then the starting set is assumed to include all entities in the system, and the exclusion is applied to that set. For example, the lone selector exclude_capability: battery_power would result in a set of all of the entities in the system that are not battery powered.

To embelish our example just a bit, for illustration, let's say we have a device with problematic battery reporting, so we want to exclude that from this group (perhaps we'll handle it separately with other rules). If the device has canonical ID vera>device_1789 here's how we exclude it:

          select:
            - include_capability: battery_power
            - exclude_entity: 'vera>device_1789'

A little bit of logic is possible based on the structure of the selectors you give. For example, if you give this as your select list:

          select:
            - include_capability:
              - temperature_sensor
              - humidity_sensor

...then the list of eligible entities will be all those that are either temperature sensors or humidity sensors (or both); when we give an array of capabilities to include_capability, matching any of them accepts the entity. In contrast, if we restructure our selectors like this:

          select:
            - include_capability: temperature_sensor
            - include_capability: humidity_sensor

...then the list of eligible entities will be only those that are both temperature sensors and humidity sensors (combined). Here, the first selector reduces the set to only those entities with the temperature_sensor capability, and the second selector further reduces that result set to only those that also have the humidity_sensor capability. So in effect, listing them together in one selector is an "OR", and listing them separately makes an "AND".

Filtering - The Dynamic Part

Once the set of eligble entities is established, a filter can be used to further refine the members of the set, and this is where the real dynamic aspect of the set is realized. The filter_expression (shown above) will be run against each eligible entity to determine its membership status in the group; if the expression returns (boolean) true, then the entity will be a member of the group; otherwise, it will not. Any time an eligible entity changes, it is re-evaluated for membership, and the entity will be removed from or added to the group as the result requires.

Attention

The filter expression is not allowed to use getEntity(), matchEntities(), or performAction(); these are not defined for the filter expression evaluation and will throw an undefined function error if attempted. Global variables are also not accessible from within these expressions.

If your expression gets long, consider using the YAML block scalar style:

          filter_expression: >
            very long expression can go here and can
            even span several
            lines if you wish

Using Dynamic Groups

It seems likely that the two most common ways of using dynamic groups will be:

  1. In expressions; dynamic groups can be referred to in the groups key of the matchEntities() function, or by passing the group's canonical ID to getEntity() and accessing the membership list stored in its attributes. The former is probably preferable, as it's a little easier to read and understand what is going on. As an example, here are expressions that use each of the foregoing methods to turn the list of low battery devices into a list of device names (rather than entity IDs):

    each id in matchEntities({group:'groups>low_battery_entities'}): getEntity( id ).name
    each id in getEntity('groups>low_battery_entities').attributes.sys_group.members: getEntity( id ).name
    
  2. In Entity Attribute conditions, you can use the changes operator on the group's sys_group.members attribute to trigger when the membership list changes, or use the in operator to see if an important device is in the array, etc.

Dynamic Groups are Efficient

Dynamic groups are much more efficient than filtering entities in expressions using matchEntities() with various include/exclude options, and the filtering offered by DynamicGroupController is much more extensive and flexible. You should lean towards using dynamic groups as much as possible, and minimize the use of matchEntities() to just retrieving group members and handling them... leave the hard work for DynamicGroupController.

Updated: 2021-Jan-09