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 source configuration, which may include 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:
- The ID is required; you have to supply it; it cannot be generated for you;
- 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);
- 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
andA
are different IDs); - 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 thepower_switch
andtoggle
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:
You can supply a name for a virtual entity by adding the name
field, like this:
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:
entities:
- 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 expr
ession. 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 expr
ession. 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
ordigest
. 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 nextat
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 tointerval
orat
timing. This must be a positive integer or zero; the default is zero, meaning retries onretry_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 supplyContent-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:
- To avoid updating a single attribute, give it a
if_expr: "! isnull( response )"
. This will cause that attribute's valueexpr
ession to be skipped, and that attribute will be left with its current value until a later request succeeds. -
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 valueexpr
ession 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 ifresponse
is null). Or, you could use the ternary operator or anif-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.
Time Series
Experimental!
This feature is in the experimental stage and subject to breaking changes and, if it doesn't pan out, being completely changed or dropped. Feedback is welcome!
It is often useful to collect data for analysis as a series of values over a period of time, referred to as a time-series. VEC allows you to create a time-series from the value of another attribute on another entity, or the value of a global variable. Some instances where a time-series may be helpful in handling a device may include:
- The device sends updates frequently and causes excessive rule evaluation. Using a time-series can dampen-out the frequent updates from the device and reduce them to a reasonable set to handle.
- You want to respond to trends in data, and you need a way to detect them. For example, you have a humidity sensor in a bathroom, and you want to use it to control an exhaust fan. A fixed trigger value isn't working, because the natural change in humidity inside your home over the course of a day and from season to season changes gradually, but you notice that there's a sudden change over short period when showering/bathing occurs (so the rate of change is more important than the actual values).
To define a time-series in VEC, add the following config to your virtual entity's attribute definition:
config:
entities:
-
id: time_series
name: "Time Series Example"
capabilities:
value_sensor:
attributes:
value:
model: time series
entity: "weather>home"
attribute: "wx.temperature"
interval: 60 # minutes
retention: 240 # minutes
aggregate: sma
depth: 3
precision: 2 # round the result to 2 digits right of decimal
primary_attribute: value_sensor.value
type: ValueSensor
The model
key with the string value time series
tells VEC that you want to collect data in a time-series. The source for the time-series is defined by entity
and attribute
, which are the entity (by canonical ID or name) and the attribute (specified as capabilityName.attributeName
, e.g. temperature_sensor.value
) on the entity for data collection. Alternately, you may use global_variable: name
to use the named global variable as the source for the series.
The interval
value, expressed in minutes, is the interval for sampling the source. On this interval, the source entity/attribute or global variable value will be collected and stored. The retention
value, also in minutes, defines the length of time over which the data are stored. In the example given here, we are sampling the data on a 60-minute (one hour) interval, and keeping the data for 240 minutes (four hours). That means the storage will contain at most 5 samples: the most recent sample at time t, and samples at times t-60, t-120, t-180, and t-240 minutes (or n = floor(retention / interval) + 1). It is recommended, although not required, that the retention
value be a multiple of the interval
.
Info
Time is the essence of the time-series collection. Restarts of Reactor do not cause additional samples to be collected. Changes in the source value do not cause additional samples to be collected. Samples are only collected on the given interval
, and the value collected is whatever the value is at that moment. That means sudden, short changes in value that occur between two intervals may be missed. Perhaps some day, Reactor will do event-based collection and resample the data, but for now, it's strictly managed by the clock.
The final step is to process (or aggregate) the time-series data to a scalar value, which is then stored on the virtual entity's designated attribute (in this case value_sensor.value
). The aggregate
configuration directive is given a (string) method name to define how that scalar value is computed. Additional configuration directives modify the method. The following table defines the supported methods:
aggregate value |
Method Description |
---|---|
sma |
Simple Moving Average — the average of the time-series values is computed and given as the scalar result. The optional depth: <integer> directive may be given to reduce the "lookback" of the average to the given number of samples. In our example configuration, we are keeping five samples in the series, but if we only want to consider the last three (i.e. from the last two hours), we can specify depth: 3 . |
wa |
Weighted Average — the average is computed, weighting each value considered with a factor. Factors may be given in an array called weight , with the first element of the array applying to the most recent sample in the series, the second weight element applying to the most recent sample's predecessor, and so on. If no weight array is given, a default set that weights newer values higher than older values is used. To preserve the range of result values, the number of weights given should equal depth (i.e. if depth is 3, supply 3 weight values), and their sum should be 1.0 (the default set will follow these rules). |
ses |
Simple Exponential Smoothing — the time-series is put through an exponential smoothing algorithm with a given alpha (0.0 < alpha < 1.0; default 0.61803; closer to 0.0 means more aggressive smoothing). The scalar value is the last value in the smoothed series. This algorithm may be most useful for data that is given to sudden, short spikes in value. Hint: if you turn on debug level 5 for VirtualEntityController in config/logging.yaml , the time-series values and smoothed values will be written to the log in CSV format that you can import into Excel and plot in a graph. This may help you determine a suitable value for alpha . |
rate , derivative |
The rate of change of the time-series. The depth may be specified (see sma ) to limit the "lookback" in the series; default depth for this method is 2 (meaning the reported rate of change will be that of the two most recent samples; if depth is 3, the rate will be determined from the most recent sample and its predecessor's predecessor; another way to look at this is to number the samples backwards, with the most recent sample being 1, its predecessor is 2, etc.). The rate is expressed in units of the source value per minute (i.e. if the source value is temperature reported in degrees Celsius, then the scalar value's units will be degrees Celsius per minute). |
accel , derivative2 |
The acceleration of the recent samples (i.e. the rate of change of the rate of change, the second derivative). Here, too, depth may be applied, and like rate , the default depth is 2. The acceleration is computed by comparing the rate of change of the most recent depth values to the prior group of depth values. The time series must have at least 2 * depth - 1 samples. See the detailed example and explanation below. |
first , oldest |
The first (i.e. oldest) value in the time-series is returned. |
last , latest |
The last (i.e. latest) value in the time-series is returned. |
max |
The maximum of the time-series' values is returned. |
min |
The minimum of the time-series' values is returned. |
median |
The median of the time-series' values is computed and returned as the scalar result. |
raw |
The time-series itself is handed back as the value. This exposes the time-series data to an expr configuration directive that allows you to write an aggregate algorithm of your own using the expression language. The time-series data will appear in that context in the value variable as an array of objects with keys time (milliseconds since the Epoch) and value (in units of the source). |
The configuration keys expr
and precision
can be used to affect the aggregate's result value before it saved to the target entity's attribute. When using expr
, the result value of the aggregations is available in a local variable called value
. Example: expr: "value*2"
will double the result of the aggregation, and precision: 2
will round the result to two digits right of the decimal point.
Time Series Example 1: weighted average
Here's an example of using wa
(weighted average) to look back at the last four samples, with significant custom weighting on the two most recent samples:
model: time series
...
aggregate: wa # Weighted moving average
depth: 4
weight:
- 0.50 # most recent sample, 50% weight
- 0.35 # 35%
- 0.10 # 10%
- 0.05 # 5% oldest sample of the four considered
Again, if you don't supply a weight
array, Reactor will compute default weights. If you supply a weight
array but it's too short (i.e. fewer than depth
values in the array), missing weights are treated as zeroes (0.0); if the array is too long, the excess weights are simply ignored.
Sum of Weights = 1.0
In order for the scalar result to be in a similar range of values to those in the time-series, it's very important that you supply all of the weights needed, no more no less, and that the sum of the weights is 1.0. If either of these is not true, then your scalar result will be skewed out of the range. That may be OK, and you may even find crafty ways to use that fact to produce useful results, but for most users, sticking to perfect size/perfect sum is the way to go.
Time Series Example 2: accel
Here's an example of a time-series of 15 minute samples from am entity, comparing the acceleration of the most recent hour to that of the previous hour:
attributes:
value:
model: time series
entity: "mqtt>garage_humidity_sensor"
attribute: "humidity_sensor.value"
interval: 15 # minutes
retention: 120 # minutes
aggregate: accel
depth: 5
The interval
for this example is 15 minutes, with a retention
of 120 minutes (two hours), so Reactor will store 9 samples (as shown here, 9 is oldest and 1 is most recent):
Sample | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
time | t-120 | t-105 | t-90 | t-75 | t-60 | t-45 | t-30 | t-15 | t |
Acceleration is the change in rate, so two rates must be computed to be able to compute the change in rate. The meaning of depth
for this aggregate is the depth of the rate samples. Given the depth of 5 here, Reactor will compare the rate of change for the most-recent hour's samples 1 through 5 with the prior hour's samples 5 through 9. Notice these overlap at sample 5, t-60, because it is considered to be the start of the most recent hour and also the end of the prior hour. Because two rates are computed using depth
, this aggregate requires at least (2*depth)-1
samples in the time series. If there are insufficient samples, the result of the aggregate will always be null.
Defining Actions
Actions for virtual devices can do three things:
- Computationally/procedurally modify attributes on the virtual entity;
- Make HTTP(S) requests and, optionally, process the return data and update attributes on the virtual entity;
- Start a Global Reaction.
Defining actions for a capabilities is done in an actions
section underneath each configured capability. Consider the following trivial example where only attributes are modified (case 1 above):
entities:
- id: 'A'
name: Virtual Switch A
capabilities:
power_switch:
attributes:
state:
value: false
actions:
"on":
target: state
value: true
"off":
target: state
value: false
In this simple case, a virtual switch entity A
is defined having the power_switch
capability. The initial state of the switch is off. When the power_switch.on
action is performed, it writes the state
attribute with the boolean value true
. When the power_switch.off
action is performed, the state
attribute receives false
. This makes it a fully-functional virtual switch that can be turned on and off like any other.
The target
value tells VEC which attribute is going to be written. The value
key provides a literal value to be written. There are other ways to get the value for the target attribute, such as parameter
(the value of a parameter passed to the action), entity
(the entity object, allowing you to plunge the depths of values in its structure for a particular source), and attribute
(a synonym for entity: attributes.<value>
, that is, the value of a specific attribute on the entity). Here's an implementation of power_switch.set
to add to our virtual switch that shows how parameter
can be used. Note that the parameter is called state
on the power_switch.set
action:
If this case, if power_switch.set
is performed with a parameter state
set to true
, the true
value will be written to the target power_switch.state
attribute. Likewise if the action is performed with state
passed as false
, the target attribute will receive false
.
Did you know?
Did you know you don't need to provide an implementation of power_switch.set
? If your entity doesn't have an implementation for this action, Reactor's default implementation will use power_switch.on
and power_switch.off
to emulate it. The same is also true in reverse: if you implement power_switch.set
but not .on
and .off
, Reactor will emulate .on
and .off
using .set
. So we're actually doing a little more work in this example than is necessary, but it's good for a simple illustration of principles.
Actions can also make HTTP requests. This is useful for using virtual entities to map to actual devices or external data. As an example, let's say we have a Shelly relay that we can access via HTTP:
entities:
- id: 'vec_range_hood_shelly'
name: Range Hood
capabilities:
power_switch:
attributes:
state:
value: false
actions:
"on":
http_request: "http://192.168.0.125/relay/0?turn=on"
target: state
expr: "response?.ison"
"off":
http_request: "http://192.168.0.125/relay/0?turn=off"
target: state
expr: "response?.ison"
In this case, when we perform the power_switch.on
action, an HTTP request with the given URL is made. Shelly relays respond with a JSON data structure that includes an ison
key/value pair that provides the new state of the relay. The expr
expression uses that to derive the value for the target power_switch.state
attribute. Details of how expr
functions and works with other directives is further explained below.
The URL string specified allows substitution using %expression%
syntax. The string between %
s is evaluated as an expression, and its result is replaced in the URL string. Note that if this is being used for URL query parameter values, it is recommended that the expression be wrapped in the urlencode()
function so that special characters are correctly escaped/encoded.
The following example shows substitition in a URL with an implementation of power_switch.set
for a Shelly relay, where the (boolean) state
parameter passed to the action is mapped to the words on
or off
in the URL query parameter as appropriate:
"set":
http_request: "http://192.168.0.125/relay/0?turn=%urlencode(parameters.state ? 'on' : 'off')%"
target: state
expr: "response?.ison"
The final thing an action can do is run a Reaction. This is pretty straightforward:
actions:
"on":
run: "MyOnReaction" # supply Global Reaction name or ID here
"off":
run:
reaction: "MyOffReaction" # name or ID
wait: true
This example shows two different forms of the run
directive, which runs a specified Reaction. The former form, where the value of run
is the Reaction name or ID, enqueues the specified reaction and continues execution immediately (it doesn't wait for the Reaction to complete). The latter form specifies an object containing reaction
and wait
key/value pairs, where reaction
specifies the name or ID of the Reaction to be run, and wait
, when true specifies that VEC should wait at this point for the Reaction to complete.
Here's the full description of the possible key/value pairs used for an action implementation:
actions:
"action-name":
target: "<attribute_name>" or "<capability_name.attribute_name>" # default capability is action's capability
run: "<reaction_id or name>"
run: # alternate object form gives more options/control:
reaction: "<reaction_id or name>"
wait: true|false # default false
http_request: "<url>"
http_request: # alternate object form gives more options/control:
url: <url>
method: <GET|POST|PUT> # optional
headers:
"header-name": "header-value"
"header-name": "header-value" # etc
body: "body-string"
body: # alternate object form
# body object contents here
# other http_request options
value: <constant num, string, or bool>
parameter: "<parameter_name>"
attribute: "<attribute_name>"
entity: "dereference_expression"
system: "dereference_expression"
expr: "expression"
map:
"key1": "value1"
"keyn": "valuen"
map_unmatched: <constant num, string, or bool>
skip_empty: true|false # default false
skip_null: true|false # default false
default: <constant num, string, or bool>
Key | Value & Usage |
---|---|
target |
(string, optional) Target attribute to be written. May be specified as a simple attribute name, in which case the capability name is the same as the action's, or in capability_name.attribute_name form to write an attribute in a different capability from the action. |
run |
(string or object; optional) Runs the given Reaction. If a string is given, the Reaction with that ID or name is run, and the action doesn't wait for the Reaction to complete. Using the object form, the reaction specifies the Reaction, and the wait key (boolean) determines if the action pauses until the Reaction completes. When the run key is used, no value is produced, none of the other keys is considered, and nothing is written to any attribute. |
http_request |
(string or object, optional) Causes an HTTP request to be made to the given URL. When given as a string only, the string is the URL to be fetched (via HTTP GET). When given as an object, additional control over the form and content of the request is possible. The key/value pairs allowed by the object are the same as those described for http_request in HTTP Request-driven Entities above. |
value |
(string, number, or boolean; optional) Value to be assigned to the target attribute. |
parameter |
(string; optional) Name of a parameter defined for the action; the value to be assigned to target will be taken from the named action parameter. |
attribute |
(string; optional) Name of an attribute on the entity on which the action is being performed. The value of this attribute will be assigned to the target attribute. For example, specifying attribute: "dimming.level" could cause the current dimming level on the entity to be written to the target attribute. |
entity |
(string; optional) A dereferencing expression to draw a value from within the current entity object. Specifing attribute: "dimming.level" is the same as specifying entity: "attributes.dimming.level" (so the former is a shortcut, really). |
system |
(string; optional) A dereferencing expression to draw a value from within VEC's system object. |
expr |
(string/expression; optional) An expression to be evaluated. The expression is evaluated after the above keys have been processed, so it is possible for the expression to manipulate the value derived from them. The current derived value is available in the expression as the variable value . For example, the current dimming level of an entity could be doubled by taking the value from the dimming.level attribute with attribute: "dimming.level" followed by the expression min(1, value*2) (the use of the min() function ensures that the new dimming value is not greater than 1.0). |
map |
(object; optional) A set of key/value pairs. If the current value matches a key in the map, it is replaced with the key's corresponding value. |
map_unmatched |
(string, number, or boolean; optional) A value to be used if the current value does not match any key in the given map . This optional directive is only considered when map is also used. |
skip_empty |
(boolean; optional) If true and the value is an empty string or null, it is not written to the target attribute. |
skip_null |
(boolean; optional) If true and the value is null, it is not written to the target attribute. |
default |
(string, number, or boolean; optional) If the value ends up being null, replace it with this default value. |
Basically, the keys value
, parameter
, attribute
, system
, entity
and http_request
all determine a value from their respective sources. From there, the expr
expression can manipulate the value if necessary. The map
is then applied, followed by the skip_
directives and finally default
. The target
determines what attribute the final value is written to, if any. When the run
key is used, it does not produce a value and all other keys are ignored (and no attribute is written).
The action configuration can also be an array of these objects, and each element of the array is performed in sequence. This makes it possible to write multiple attributes in a single action, for example.
hvac_heating_unit:
actions:
"set_setpoint":
-
target: setpoint
parameter: setpoint
-
target: units
value: "C"
-
target: null
http_request: "http://192.168.0.33/thermocontrol/api/set?target=%parameters.setpoint%"
-
run: "Notify Setpoint Adjusted"
In this example, the hvac_heating_unit.set_setpoint
action performs four distinct steps:
- It sets the
hvac_heating_unit.setpoint
attribute to the value of thesetpoint
parameter given to the action; - It sets the
hvac_heating_unit.units
attribute to "C" to indicate temperature is in Celsius; - It makes an HTTP request to a remote endpoint, presumably to set the target temperature of the related device using its local API. Any response from the endpoint is discarded;
- Finally, a Reaction called Notify Setpoint Adjusted is started.
Actions That Return Responses
If you are providing an action implementation for an action that is defined to return a response, the final value is the returned response data (whether it is written to a target attribute or not). If an action is meant only to generate a response and not write an attribute, target: null
is supported (no attribute will be written, but all other processing occurs).
Updated: 2024-Nov-10