Skip to content

VirtualEntityController

VirtualEntityController (abbreviated VEC) is a controller that creates and manages virtual entities (or devices) in Reactor. A virtual device/entity is one for which no physical device exists in the real world. Virtual devices are often used to help track states or provide configurable options within your automations. Some examples:

  • A virtual switch called "Party Mode" that may set certain scenes and also disable timed-auto-off automations of indoor and outdoor lights (don't stop the party!);
  • A virtual value (numeric) sensor canned "Pump Hours" may be used to accumulate the daily runtime of pool or fountain pump;
  • A virtual string sensor may be used to store a multi-valued mode or state;
  • A virtual binary sensor may be used to periodically query a remote RESTful API using an HTTP request and set the sensor state according to the response data.

Virtual entities can be used in automations just like any other entities.

The attributes of virtual entities can be either static or dynamic. Static attributes are those that just hold a value, like a variable. They can be modified using an action (described below) in your reactions. Dynamic attributes are derived by supplying an expression that drives them. In these ways, the attributes of virtual entities are very much like Global Expressions.

Creating Virtual Entities

The first step in creating virtual entities is to make sure VirtualEntityController is a configured controller in your system. Add the following to the controllers section of your reactor.yaml file (do not add the controllers: line shown below, that's just to help you indent the rest properly):

controllers:  # do not copy this line
  - id: virtual
    name: VEC
    implementation: VirtualEntityController
    enabled: true
    config:
      entities:
        -
          id: A

After adding this configuration to your reactor.yaml, restart Reactor and hard-refresh any browser pages open to the Reactor UI. You can then go to the Entities list (left navigation), clear any previous filters, and enter "virtual" in the name search field — you should see a couple of new entities.

The configuration above creates a default virtual binary switch entity. This is done by the entities section, which is an array (the - in YAML indicates the start of a new array element). The id: A line tells VEC that the new virtual entity will have the ID A. You can assign any ID you want to a virtual entity, but you need to know:

  1. The ID is required; you have to supply it; it cannot be generated for you;
  2. The ID has to be unique; you can't use the same ID for another virtual entity (but it does not matter if an entity from another controller has the same ID);
  3. The ID must be 1-64 characters long and only contain alphanumeric characters and underscore (i.e. upper- and lower-case A-Z, digits 0-9, _); IDs are case-sensitive (i.e. a and A are different IDs);
  4. You should never change it — Reactor conditions and actions will find the entity by its ID, and if you change the ID, all of those conditions and actions will break.

When we only assign the id in configuration as we've done here, Reactor defaults the entity type to Binary Switch and supplies a default name. VEC has a few preconfigured entity types, and you can choose one by adding the template field with one of the following pre-defined values:

  • Binary Switch — a typical on/off switch, implementing the power_switch and toggle capabilities with actions common to switches;
  • Dimmer — a dimmer with switch capabilities extended with a settable (0%-100%) level;
  • Binary Sensor — a binary state entity that has no actions;
  • Value Sensor — an entity that stores a numeric value (and has no actions);
  • String Sensor — an entity that stores a string value (and has no actions).

When setting the template value, make sure you spell the template name exactly as shown, including capitalization and spaces:

    entities:
      -
        id: A
        template: Binary Switch

You can supply a name for a virtual entity by adding the name field, like this:

    entities:
      -
        id: A
        template: Binary Switch
        name: Guest Mode

Since the Binary Switch and Dimmer entity types support the actions normally supported by switches and dimmers, you can use these actions to change the state of these entities. They also appear in the Reactor dashboard as switches and lights normally would, respectively, and can be operated the same way as well.

The attribute values of the sensor entity type have to be set using the x_virtualentity.set_attribute action. Your automations (specifically Reactions) can use an Entity Action on the virtual sensor entity, selecting this action, and then providing the attribute name and value to be set.

Customizing Virtual Entities

Sometimes a template may not do exactly what you want, so you either need to modify it, or just create your own virtual entity with the capabilities, attributes and actions you want.

Let's say we want to add color temperature capability to a virtual dimmer for some reason. To do this, we would need to add the color_temperature capability to an entity. Here's how our new configuration would look:

    entities:
      -
        id: A
        template: Binary Switch
        name: Guest Mode
      -
        id: color_dimmer
        template: Dimmer
        name: Virtual Color Dimmer
        capabilities:
          - color_temperature

If you add this to your reactor.yaml and restart, you'll see the Virtual Color Dimmer entity with the capabilities assigned by the Dimmer template (power_switch, toggle, and dimming), and you will also see that added capability color_temperature. Now this is a trivial example, and in this case, only the attributes of color_temperature are available; none of the actions. The reason is that we would need to teach VEC what the actions do, and we're not quite ready for that yet.

Let's say you don't want to use any template, and just define your own virtual entity that has, for example, the hvac_control, hvac_heating_unit and temperature_sensor capabilities — the basis of a simple virtual thermostat. That might look like this:

    entities:
      -
        id: virtual_therm
        name: Virtual Heating Thermostat
        capabilities:
          - hvac_control
          - hvac_heating_unit
          - temperature_sensor
        primary_attribute: hvac_control.state

Here again, we are directing VEC to construct a virtual entity with the listed capabilities. It will have the attributes of those capabilities, and you'll be able to set them using x_virtualentity.set_attribute, but it won't have any of the actions until you define them (some day, later...). Remember, things like tracking the current thermostat mode, and turning the heating unit on when the temperature is below the setpoint, are functions performed in real life by the device behind the entity, but here in virtual entity world, there is no device, so there's nothing to manipulate the attributes automatically or perform an action requested.

Defining Dynamic Attributes

The attributes of virtual devices don't need to be static. Let's say, for example, that we want to create a binary sensor that's true when our Hubitat Elevation's Mode is Night, but false otherwise. We'll start by using the Binary Sensor template, but we're going to customize just the binary_sensor.state attribute. To do that, we'll need a slightly different construction than we used in the previous section's examples.

    entities:
      -
        id: mode_night
        name: Mode is Night
        capabilities:
          binary_sensor:
            attributes:
              state:
                expr: getEntity( 'hubitat>mode' ).attributes.string_sensor.value == "night"

In the previous section's examples, the capabilities value was an array of capability names. In this example, we've changed it to an object; the keys of this object are capability names (e.g. binary_sensor). Within a capability, the key attributes is used to tell VEC we're configuring attributes in that capability, and finally, state is the name of the attribute in the binary_sensor capability that we are defining. The expr value is an expression that will drive the value of the attribute binary_sensor.state on this entity.

Dynamic attribute expressions are sensitive to dependencies. In the example above, VEC learns that the expression depends on the hubitat>mode entity, and will automatically re-evaluate the expression every time that entity changes, without using outside Rules or Reactions. And, these expressions can be as complex as you'd like.

If you have long expressions, use block text like this to keep your configuration more readable:

expr: |
  day_of_week = dateparts().weekday,
  getEntity( 'hubitat>mode' ).attributes.string_sensor.value == "night" &&
  ( day_of_week == 0 || day_of_week == 6 )

In some cases, you may not want to update an entity's attribute based on some other condition. The if_expr key can be used to define an expression that determines whether the attribute should be updated at all, or skipped. If the result of the expression is true, or if_expr is not provided, the attribute will be updated; otherwise (i.e. when the if_expr result is false), the attribute is skipped.

HTTP Request-driven Entities

VirtualEntityController incorporates a mechanism for making HTTP requests to fetch data that can then be used to set attributes on virtual entities. Here's an example configuration:

        - id: "H"
          name: "IP Address Changed"
          template: Binary Sensor
          http_request:
            url: "https://api.ipify.org?format=json"
            interval: 3600
          capabilities:
            binary_sensor:
              attributes:
                state:
                  expr: "response.ip != '10.56.71.123'"
            string_sensor:
              attributes:
                value:
                  expr: response.ip

Here you can see we've added an http_request section to the configuration of an otherwise typical virtual binary sensor. With this configuration, VirtualEntityController will request our current public IP address from the ipify.org API every hour. VEC places the response from the server in a variable called response in the expression context that is used by each attribute's value expression. In this case, we've told api.ipify.org (via query parameter on the URL) to respond with a JSON object (i.e. { "ip": "10.56.71.123" }), and VirtualEntityController knows to convert that into an real object for use in the value expression. We've set up the value expression for binary_sensor.state so that it's true if the IP address returned by the API is not the value we expect (that is, our public IP address has changed for some reason). Additionally, this entity is extended with the string_sensor capability, and its value attribute receives the IP address returned by the API (just so we can see it).

The following are the configuration keys known in http_request:

url (required):
The HTTP(S) request URL. This is a required value, and it must be enclosed in quotes.
interval:
The request interval in seconds (a positive, non-zero integer). There is no specific control over the basis of the interval; that is, an interval of one hour is not guaranteed to run at the top of every hour (0 minutes after the hour), and in fact, it's pure luck that it ever would. For strict control of request time, see at below.
at:

An array of fixed times of day (24-hour form) at which the request should be made. Times must be specified as "HH:MM" strings, as shown below:

at:
  - "17:30"  # 5:30pm
  - "00:00"  # midnight

The times do not need to be specified in order. If the request is to be run only once per day, the abbreviated at: "HH:MM" single-line form may be used.

If at is used, interval is ignored.

method (optional):
The HTTP request method. If not specified, GET is used.
auth_type (optional):
If HTTP authentication is required, indicate the type, which must be basic or digest. Omit this key and value if authentication is not required.
auth_username (optional):
Username for HTTP authentication. This value should be enclosed in quotes.
auth_password (optional):
Password for HTTP authentication. This value should be enclosed in quotes.
headers (optional):
This can either be an array of strings formatted key: value (header name/key + colon + space(s) + value), or an object containing key/value pairs.
# This is array form:
headers:
  - "X-API-Key: 23904823985klj238"
  - "X-Auth-Type: simple"

# This is object form:
headers:
  X-API-Key: "23904823985klj238"
  X-Auth-Type: "simple"
retry_interval (optional):
If a request fails, the next request attempt will be made on this interval rather than interval or next at time. Units are seconds, so the value must be a positive integer, and the default is 60 seconds. If the retry interval is 0, it is ignored and scheduling proceeds as if the request had succeeded.
retry_limit (optional):
If a request fails, up to this many attempts will be made using the retry_interval. If the number of retries exceeds this limit, further retries will fall back to interval or at timing. This must be a positive integer or zero; the default is zero, meaning retries on retry_interval continue indefinitely.
quiet_failure (optional):
If an HTTP request fails and this value is boolean true, VirtualEntityController will never issue a notification (shown in the Status page's Current Alerts widget, if present). If this value is an integer, then no notification is issued until the number of attempts exceeds the given value (e.g. if 3 is given, no notification is sent for the first three request attempts, but if the fourth attempt fails, a notification is then sent. Request failures are always logged in Reactor's log file(s) regardless of this setting. See Handling Request Failures below.
skip_update_on_error (optional):
If a request fails and this flag is true, all of the capabilities and attributes of the entity will be left as-is (i.e. the entity will be treated as if the request had not occurred). The default is false, meaning each attribute will be updated and you need to handle the null response value in your expressions. See Handling Request Failures below.
dump_response (optional):
This boolean flag, when true, logs the entire response from the server to the Reactor log file so that you can inspect it. This is helpful for debugging and navigating JSON objects for specific elements of data. JSON data may not be formatted nicely for human reading, but you can copy-paste it into tools like jsonlint.com to improve their readability.
ignore_invalid_certs (optional):
When true, HTTPS requests to servers using self-signed or invalid certificates are permitted. Default is false.
force_json (optional):
If your server returns a JSON response but does not declare it as such (i.e. by failing to provide the required Content-Type: application/json response header), this boolean flag can be used to force VirtualEntityController to handle the response as JSON. It can also be used in instances where a server-side failure produces a 200 status reply with a text or HTML error message rather than JSON containing an error indicator. Any non-JSON response will cause the request to fail (which can then be handled by other request flags or your expression(s)).
body (optional):
The HTTP request body, if any. This can be a string or an object. If an object, it will be converted to a JSON string. It is required that you supply a Content-Type header if you are providing a request body. You do not, however, need to supply Content-Length; VirtualEntityController will compute and supply it based on the body given.

Handling Request Failures

When an HTTP request fails, the reason for the failure is always logged, and the x_virtualentity.last_http_status attribute is updated. Since there is no valid response from the server, the response expression variable will be null.

The quiet_failure request configuration flag will control if (or when) a notification is sent to the Reactor UI (displayed in the Current Alerts widget on the Status panel). See the detail for that flag in the previous section, above.

If you have set the skip_update_on_error flag to true, your expressions will not be run and the attributes of your entity will not be affected. They will simply keep whatever value they last had until a successful request later updates them.

But if skip_update_on_error is not provided or is false, your expressions will still be run and receive the null value for response. Unless you handle this possibility, you will get additional errors from your expressions. To handle the null and avoid expression errors, here are some suggestions:

  1. To avoid updating a single attribute, give it a if_expr: "! isnull( response )". This will cause that attribute's value expression to be skipped, and that attribute will be left with its current value until a later request succeeds.
  2. For attributes where you want to provide a signalling value to your automations that no data is available, handle the null response variable in your value expression as you would for any other variable. For example, you may use the coalesce operators to provide a default value: response?.current?.temperature (the result value will be null if response is null). Or, you could use the ternary operator or an if-then-endif block:

    isnull( response ) ? -1 : response.current.temperature
    
    if isnull( response ) then -1 else response.current.temperature endif
    

    Both of these examples return -1 when response is null. There are countless other ways to handle it, and the value that you use is entirely up to you.

Note that using skip_update_on_error: true is different from using if_expr in that the former applies to all attributes on the entity, where the latter applies only to the attributes having if_expr in their configurations. This gives you a lot of flexibility in handling request errors and what happens to the attributes.

Defining Actions

Yes, you can define actions for the capabilities extended to a virtual entity. Some day, this will get written. I'm not sure anyone will actually need to do this. But it's possible.

Updated: 2022-Nov-20