Skip to content

DynamicGroupController

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 filter 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:

controllers:
  - 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 <= entity.attributes.battery_power.alert_level"

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

DynamicGroupController builds groups in two phases: the selection stage, which occurs at startup, and the filtering stage, which occurs continuously as member entities are updates. The selection stage makes DGC more efficient by reducing the universe of entities it needs to consider in the filtering stage. In our example above, we are reducing the set of entities down to only those that have the battery_power capability. From there, filtering is used to pick for group membership only those entities that match the given filter expression.

Selection Phase (at Startup)

The select section of the group configuration drives the selection stage, and supports the following selectors:

  • 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; if any element is a regular expression bounded by / characters (e.g. "/^hass>input_boolean/"), all entities with canonical IDs matching the pattern will be included;
  • 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; if any element is a regular expression bounded by / characters (e.g. "/^hass>input_boolean/"), all entities with canonical IDs matching the pattern will be excluded;
  • 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;
  • include_attribute — matches the presence and value of an attribute (or multiple); matching entities are included in the group. Note that this selector has special formatting requirements, described below.

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's entity 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".

The include_attribute selector has special formatting. This selector matches entities that both (1) have the specified attributes, and (2) the values of those attributes match the configured value in the selector. The selector allows you to specify multiple key/value pairs, where the keys are full attribute names (e.g. zwave_device.location) and the value is the value to be matched for that attribute. Matching all of the attribute/value pairs selects the entity.

          select:
            - include_entity: "mqtt>shelly_handt3"
            - include_attribute:
                'zwave_device.location': Living Room
                'x_vera_device.room_id': 13
                'x_hass.room_id': "/^(living_room|sun_room)$/i"

In the above example, note that there are two additional spaces required to indent the attribute entries correctly under include_attribute!

This example matches as follows:

  1. The zwave_device.location line will match entities having that attribute with the value Living Room exactly as shown (case-sensitive);
  2. The x_vera_device.room_id entry will match entities having that attribute with the numeric value 13;
  3. The x_hass.room_id entry shows how a regular expression can be used to match multiple values (and also performs a case-insensitive match by including the "i" flag at the end).

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

Assigning and Determining the Primary Attribute

All entities in Reactor can have a primary attribute assigned, and groups are no exception. By default, a group's primary attribute is sys_group.empty, a boolean value that is true when the group is empty, and false if it has any members. You can override the primary attribute for a group and provide an expression to determine its value, if you wish.

The primary attribute can be one of the following:

  1. sys_group.empty — this is the default if you do not configure something else;
  2. Any attribute that already exists in the capabilities assigned to the group (review them by opening the Entity Detail panel in the Entities list);
  3. The result of an expression that you configure in primary_attribute_value;
  4. A custom attribute that is calculated by your configuration (described in the next section).

To assign an attribute that exists natively on the group, or to specify a custom attribute that you've created (via the configuration described in the next section), you need only name the attribute in the primary_attribute configuration key. Always use the capabilityname.attributename form (e.g. binary_sensor.state).

To create a primary attribute and compute its value from an expression you provide, you provide the attribute name in primary_attribute, and then the expression to compute that value in primary_attribute_value. The execution context for that expression defines the variable group as an object containing the group details (same object form as the result of getEntity() with the group's canonical ID), and members as an array of canonical IDs of the group's members (which may change dynamically based on other configuration).

Here's an example that we might use to set the primary attribute to binary_sensor.state, and set its value true when any light in the group is on (it will be false if all lights in the group are off):

          primary_attribute: "binary_sensor.state"
          primary_attribute_value: |
            local d = false;
            each id in members: d = getEntity(id)?.attributes?.power_switch?.state or d,
            d

There are two things to notice in the value expression. First, it's a multi-statement expression that uses a local variable (d, defined on the first line) to "accumulate" the value of the state as it iterates over all of the entities in group (second line), and finally "returns" that value (third line). Second, it uses the predefined variable members, which in this context will be an array of the entity IDs of the group's members. The end result of this expression is that it will return false unless any member entity is powered on.

Here's a slightly modified example that will be true only if all group members are on:

          primary_attribute: "binary_sensor.state"
          primary_attribute_value: |
            local d = true;
            each id in members: d = bool(getEntity(id)?.attributes?.power_switch?.state) and d,
            d

In this example, we've wrapped the attribute check in the bool() function, because it's possible for the attribute value to be null (e.g. if a group member doesn't provide power_switch or the device is offline with some hubs). The result of null and true is null, so we need to guard for that and make sure we only produce a true or false result. Using bool() will coerce a null attribute value to false and keep consistent boolean values in the expression execution.

Note

As you read on in the next section about how to define Custom Attributes, you may see parallels between using primary_attribute_value and Custom Attributes. The ability to compute a value for a primary attribute came first, and later is was suggested by a very astute user that having more user-defined/computed attributes would be useful. Custom Attributes were added later, and expanded on the mechanism used for a primary attribute. For example, the two examples given above would require no expressions as Custom Attributes, just the use of the and or or operator in configuration. So although this now duplicates functionality, there is no plan to deprecate the original basic functionality provided by primary_attribute_value as of this writing, and you may use whichever form you prefer.

Custom Attributes

In addition to setting the primary attribute for a dynamic group, users may add custom attributes to the group. The values of these custom attributes are typically derived from attribute values of the group's members. For example, a group of temperature sensors can have a calculated average of the readings of all the sensors. This process is referred to as reduction; you are taking an array of values (the source attribute values from each group member entity) and reducing them to a single value to be assigned to the custom attribute.

Custom attributes are defined under the calculate: object in a group's configuration. Here's a possible configuration for the example given above as a group with ID temp_sensors. The select configuration simply selects all entities having the temperature_sensor capability, for illustration purposes (in practice, we'd probably want to include_entity some specific entities for this purpose).

        temp_sensors:
          select:
            - include_capability: temperature_sensor
          calculate:
            - attribute: 'temperature_sensor.value'
              source:
                attribute: temperature_sensor.value  # Where to get data values
              op: average

Choosing Capabilities for Custom Attributes

If you can't find a system-defined capability that has an appropriate attribute for your purpose, you can always define an extended capability. By convention, the capability for user-defined custom attributes for groups would be x_group_user. If you use this capability, you'll avoid any collisions with future changes in the system-defined capabilities and attributes. Append your attribute name to it. For example, if you need an attribute to tell you when all the group's lights were in color mode showing blue, you might use x_group_user.all_lights_blue.

This is the most basic form of custom attribute configuration. The calculate object is an array of configuration objects. The attribute key is required for each array element and specifies the attribute to be defined and calculated on the group.

The source substructure tells DynamicGroupController what attribute from the member entities should be used for calculation. Any member entity that does not have the specified source attribute, or for which the attribute's value is null, is simply ignored (unless otherwise specified below).

The op tells DGC how to reduce the multiple values from the member entities' values down to a single value. The pre-defined operations for op are:

Reduction (op) Data Type Description
average, avg, mean numeric The average (arithmetic mean) of the available values.
median numeric The median of the available values (i.e. the middle value of the values when sorted).
min, max numeric The minimum or maximum of the available values.
sum numeric The sum of the available values.
count numeric Count of non-null values.
and, nand boolean Logical AND of available values. All values must be boolean true for the result to be true. If nand is used, the result is inverted (i.e. false when all values are true). Result is null if no boolean values are available.
or, nor boolean Logical OR of available values. If any value is true, the result is true. nor inverts the results (false when any value is true). Result is null if no boolean values are available.
xor, xnor boolean Logical XOR. Since more than two values may be considered, this operation is only true when exactly one of the values is true. If multiple are true, or all are false, the result is false. The xnor inverts the result. Result is null if no boolean values are available.
first string Given a string or an array of strings to match in a value key (at the same level as op), this operation returns the first matching value that any of the member entities has. That is, for each element of value (taken in order given), if a member entity's source has that value, that value becomes the result. If no match is found, null results.

There may be circumstances where the source values require some pre-conditioning or filtering. This is enabled by the value_expr subkey under source. The result of the expression is the value that will be added to the array of values for op or reduce_expr. For example, in our temperature sensor group, some of the sensors return temperatures in Celsius, and others in Fahrenheit. We can use value_expr to convert the values to a specific unit, so that the reduction operation receives and yields consistent values:

        temp_sensors:
          select:
            - include_capability: temperature_sensor
          calculate:
            - attribute: 'temperature_sensor.value'
              source:
                attribute: temperature_sensor.value  # Where to get data values
                # Convert Celsius temperatures to Fahrenheight
                value_expr: >
                  local units = entity.attributes.temperature_sensor.units;
                  local d = ( units === 'C' || units === '°C' ) ? value * 1.8 + 32 : value;
                  ( d <= 0 || indexOf( ['C','F','°C','°F'], units ) < 0 ) ? null : d
              op: average
              prec: 2  # two decimal digit max result

Since the attribute under source is temperature_sensor.value, the context variable value will be defined and contain the raw value from that attribute. Then, taking the expression line by line, the expression first fetches the current member entity's temperature unit into a local variable called units. The second line of the expression then checks if those units are Celsius, and if so, converts them to Fahrenheit (if units are not Celsius, it just takes the current value unmodified) and assigns it to another local variable d. Finally (third line), if the value of local d is in range and the units are known, the value of d is returned, otherwise null is returned.

The prec key allows you to define the number of decimal digits precision for the result value; the result is rounded to comply. For example, a calculated value of 3.14159265 with prec: 4 results in 3.1416.

If none of the pre-defined operations addresses the need, the op: key can be replaced with an expression defined by reduce_expr:. The result value of the expression will be the value assigned to the custom attribute. The context for reduce_expr defines the following variables: group refers to the group entity being defined (as an object like that returned by getEntity()); members is an array of canonical IDs of the member entities at the time of evaluation (which may change in a truly dynamic group); and values is an array of the members' source values.

        temp_sensors:
          select:
            - include_capability: temperature_sensor
          calculate:
            - attribute: 'temperature_sensor.value'
              source:
                attribute: temperature_sensor.value  # Where to get data values
              # Compute harmonic mean (not predefined) using expression (trivialized for illustration)
              reduce_expr: >
                local s = 0;
                each v of values: do
                  s = s + (1.0 / v)
                done;
                len(values) / s
              prec: 2  # two decimal digit max result

New Expression Functions

As of build 26227, the expression language contains two new array functions map() and reduce() to streamline operations in these reduction expressions. The above reduce_expr, for example, could be written as the single line len( values ) / reduce( values, $1 + (1.0 / $2) ). See Expressions & Variables for details.

Finally, custom attributes that should have fixed values can be defined by simply supplying value: under source:, like this:

            - attribute: 'temperature_sensor.units'
              source:
                value: '°F'

Make sure the data type of the value configured matches the expected type of attribute being defined.

Group Actions

When one creates a homogenous group (i.e. a group with the same or similar types of devices, like switches and dimmers), it is sometimes desirable to be able to perform an action on all members of the group. To enable this for a group, set the group_actions configuration switch to true in the group's configuration (at the same level as select).

          select:
            - include_capability:
              - power_switch
              - dimming
          group_actions: true

This will cause DynamicGroupController to examine all of the entities selected for group membership (before filtering by filter_expression) and determine the set of actions possible for any group member. All of these capabilities and their actions will be extended to the group. This can be a very long list, which is why this feature is not enabled by default.

When performing actions on a group, any member entity that is not capable of performing the selected action will simply be ignored. For example, if you set the dimming level of a group, but one member is just a binary switch, no action will be taken on it.

To limit the list of actions down to only those you need (which is recommended), you can use the array form of group_actions to select more specifically which actions should be available:

          select:
            - include_capability:
              - power_switch
              - dimming
          group_actions:
            - power_switch
            - dimming.set

In the above example, group_actions is now an array with two elements. The first is just power_switch, a capability name with no action name, so all actions from power_switch will be made available. The second specifies that only dimming.set will be made available from the dimming capability. The final list of actions available for this group would be power_switch.on, power_switch.off, power_switch.set, and dimming.set.

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

Other DynamicGroupController configuration

The automatic_room_groups configuration boolean (default: true) can be used for system-wide control over DGC's management of system-wide groups. If false, no system-wide room groups will be used regardless of the flag settings on Controller instances.

Updated: 2026-Aug-22