Reactor

Reactor is a plugin for Vera Home Automation systems that addresses the shortcomings in the built-in logic for triggering scenes and events, adding powerful, user-configurable logic to drive automation.

What Can I Do with Reactor?

If you've ever wanted to do something with Vera/Luup where it's "if this is on and that's off and this sensor is tripped and that device is in this condition", then Reactor is for you. In particular, any time the word "and" appears in the conditions for an automation you are trying to set up, you may need Reactor.

Here are some examples of things you can do with Reactor (many of which would be difficult to do reliably or at all with Vera scenes and device triggers alone):

  • Send a notification when the garage door is left open for more than 15 minutes between 10pm and 8am;
  • Turn off outdoor lights at 11pm, unless your house is in "Party Mode" (as set by a VirtualSwitch you created);
  • Reboot your Vera at 5am every Sunday;
  • Temporarily run air conditioning if the humidity in your house exceeds 55%;
  • Close the blinds and turn off your pool equipment if the current weather is "lightning";
  • Put your Nest thermostat in "Home" mode when your house mode is "Away" but your caretaker uses their lock code to open the front door, and then later return the Nest to "Away" mode.

Operation

Reactor is the parent of a set of a ReactorSensors. Each ReactorSensor contains a set of logic conditions that when met, cause the sensor to trip (so, a ReactorSensor implements the SecuritySensor1 semantics). When the conditions are not met, the sensor is untripped. This basic binary output can be used to trigger scenes and other logic, even PLEG.

Reactor itself is a single-instance plugin, meaning all ReactorSensors in the system run from a single copy of the Reactor code, making it light on system resources even for large numbers of sensors and logic conditions.

Reactor is currently in its youth. I've only just released the first version in June 2018. It can currently respond to:

  • The change of almost any defined state variable on a device (including other Reactor sensors);
  • Changes in house mode (home, away, night, vacation);
  • Time/date changes (specific days of the week, dates and times; or time and date ranges);
  • Sequences of events (e.g. this must happen before that for the condition to be true).

More conditions are expected as the user community makes its needs clear.

Installation

Reactor can be installed via the "Install Apps" function in the Vera UI (it's available in the Vera Plugin Markeplace, just search for "Reactor"). Reactor can also be installed via the Alternate App Store (aka AltAppStore) under ALTUI, which has available all released versions as well as the current stable development release (for you that like to live on the edge). Reactor can also be installed by directly downloading any release or branch from the project's Github repository (see the README file there for instructions).

Configuration

Configuring Reactor logic is as simple as defining the conditions as a combination of the above types. A ReactorSensor can one or more condition groups. A condition group contains one or more conditions. For a ReactorSensor to transition to tripped state, at least one condition group has to have all of its conditions met (true). So, the state of a condition group is the logical and of all conditions in it, and the condition groups are or'd to see if the ReactorSensor should be tripped or not.

Service (State Variable) Conditions

A service condition reacts to a change in the value of a state variable associated with a device. Whenever device state changes, the ReactorSensor re-evaluates the condition to see if it should trip in response. Every Luup device stores in condition in state variables. To create a service condition, you select the device you want to watch, the service variable that should be watched, and set the condition and value appropriate to the test. For example, if device #17 was a motion sensor, when you select that device from the device list, the variable list will change to the list of variables defined for the device. One of these variables will be "Tripped," which is the tripped condition of the motion sensor, so you would choose that. You would then choose the condition "equals" and set the value to "1" to see if the sensor is tripped.

This is very similar to scene device triggers in Luup, with one key difference. With scene device triggers, you can only react to the predefined events that are defined for the device, and this is usually a small subset of everything that is available. Reactor allows you to set a condition based on the value of any variable that is set on the device.

Service Condition Options

To the right of the service condition value is a downward-pointing arrow that opens the options pane for the condition. Service conditions currently have two options: a sequence restrictions, and a sustained duration.

The sequence restriction is used when the order of triggering is important to the condition group's meaning. Reactor implements "sequences light" (as opposed to PLEG's extensive sequence capability--remember, we're keeping it simple and lightweight). As an example, let's say we want a ReactorSensor that trips when a door sensor is tripped (door opened) while the house is in Away mode, but not if the door is open when the house enters Away mode (perhaps the door is still open because you haven't left yet). To implement this example, the group would have two conditions. The first would be a House Mode condition for Away mode. The second would be a service condition that matches the Tripped=1 state of the door sensor, plus the selection in the options of the first condition from the "Only after" dropdown. This means that the second condition can only become true if the change of the door sensor's Tripped state from 0 to 1 occurs after the time at which Away mode became active. If the door is already open when the house enters Away mode, the timestamp of the door opening is before that of the house entering Away mode, so the ReactorSensor does not trip. If the door is then closed, and later opens, then the timestamp of that later opening is after that of the house entering Away mode (meeting the "Only after" sequence restriction), so the ReactorSensor does trip. The additional "within" time specification restricts the success of the condition to the sequence of action occurring within the specified number of seconds (e.g. if B comes after A within X seconds); if 0, which is the default, there is no time restriction.

The "Sustained for" option allows the condition to become true only if the matching service state is sustained for the set number of seconds. This is useful, for example, if you want to program a condition for a door being left open for more than 5 minutes--you would set a service condition for your door sensor to test if the "Tripped" variable is equal to "1", then add the duration option "For 300 seconds". Any time the door is opened, the state variable value condition is met, but the ReactorSensor will not consider the overall condition met until that state has been sustained for an unbroken period of at least 300 seconds. This timing survives Luup reloads/restarts.

The "Repeats" option allows the condition to become true only if the service state match repeats the specified number of times over the specified interval (e.g. a motion sensor trips 3 times in a minute, or a switch is turned on twice--on-off-on--within 10 seconds).

The "Latch" option fixes the state of a condition, when met, to true until the group of which it is a member becomes false, even if the condition test becomes false.  

The service condition options can be closed (hidden) by clicking the upward-facing arrow that appears below them when the options pane is displayed.

Condition Options Pane

 

The above image shows the options pane open, with a sequence being set up between the two conditions (Inhibit Switch #3 Status must change to 1 before the VirtualSwitch #14 Status goes to 1 for the group to be successful/true).

House Mode Conditions

House mode conditions are pretty straight-forward: just select the house modes you want to match. When your house is in any of the selected modes, the condition will be true.

Day of Week Conditions

Day of week conditions allow you to create triggers for certain days of the week, and in particular, on certain days of the month. This allows you to trigger, for example, every Monday, Wednesday, and Friday; or every 2nd Sunday. Simply select the days of the week on which the condition should be true, and select the ordinal for the day, which is "Every" by default, but can also be one of "First", "Second", "Third", "Fourth", "Fifth" or "Last".

Sunrise/Sunset Conditions

The Sunrise/Sunset conditions allow you to create a condition that is active (true) before or after sunrise or sunset, or between sunrise and sunset (or the reverse, for an overnight period). The before and after conditions are equivalent to between conditions, with the start or end condition being midnight, respectively. That is, "before sunset" is equivalent to "between midnight and sunset". The before and after conditions, therefore, operate within the context of a day.

The between and not between conditions can span a day. If one makes a condition, for example, that is "between sunset and sunrise," then, as should be clear, the condition is active (true) throughout the period from sunset through midnight and until sunrise the following day. It is possible and allowed to set a condition to "between sunrise and sunrise" or "between sunset and sunset," and these conditions are always true (as long as their offsets are equal, see below).

The Sunrise/Sunset condition also allows offsets to the specified milestone, so, for example, one could specify that the condition is active from 30 minutes before sunset ("sunset-30") to 30 minutes after sunrise ("sunrise+30"). The offsets are in minutes, and may be negative or zero.

Date/Time Conditions

Reactor's Date/Time conditions are a powerful tool for matching date and time ranges. Date/time conditions can be "after", "before", "between", and "not between" specified times and dates.

Date/times may be specified as Y-M-D H:M, M-D H:M, or H:M.

  • If a date/time specification is Y-M-D H:M, then it refers to an absolute time, a certain year, month, day, hour and minute;
  • If a date/time specification is M-D H:M, then it refers to a certain time on a recurring day of the month each year;
  • If a date/time specification is H:M (no date spec), it refers to certain time, recurring daily.

Here are some examples:

  • "between Jan 1 2018 00:00 and Jul 1 2018 00:00" refers to the entire period from midnight January 1, 2018 through June 30, 2018 23:59:59 (the end time is not included in the span);
  • "between Jan 1 00:00 and Feb 1 00:00" refers the 744 hour period beginning at midnight every January 1st (of any year) and ending at 23:59:59 on January 31 of that same year (31 days of 24 hours = 744 hours);
  • "before Jun 10" refers to the period from midnight January 1 of any year to 23:59:59 on June 9 of the same year--from midnight June 10 onward, the condition is not true;
  • "after Jun 10" is the inverse: active starting at midnight on June 10 in any year through 23:59:59 of December 31 of that same year;
  • "between Nov 10 and Feb 10" is interesting, because the start date appears to be after the end date--in this case, the period from November 10 of any year to February 10 of the following year is the active period (note this is equivalent to "not between Feb 10 and Nov 10");
  • "between 10:00 and 22:00" is active between 10am and 10pm on any date;
  • "between 22:00 and 10:00", like the "Nov 10" example above, the start time being after the end time creates a span over midnight--this condition is active from 10pm any day to 10am the following day.

Note that user interface currently does not go to exceptional lengths to keep you from doing the impossible--caveat user. One could currently, for example, set the condition up as "between Jan 1 2018 00:00 and Feb 2 2014 00:00", and this condition would simply never be met, with no warning from the user interface.

Sometimes it's desirable to split or combine conditions. For example, you may want a ReactorSensor to be active between 10am and noon (12pm) on the first Tuesday of each month. In this case, you simply use a Weekday condition and a Date/Time condition (H:M only) in the same group. The "and" effect of conditions within a group takes care of combining the logic. Similarly, if you want to be active between 10am and 2pm every day of the month of January, you would set up two conditions in one group: the first, a Date/Time condition with "between Jan 1 00:00 and Feb 1 00:00", and the second another Date/Time condition with hours only "between 10:00 and 14:00". Note that this is different from a single Date/Time condition set to "between Jan 1 10:00 and Feb 1 14:00", as the latter would be active at (for example) 2pm, midnight, and 4am on every day of January, where the former is not. Think about it. If it's not clear to you why this would be, please contact me.

Luup Reload Condition

The "Luup Reload" condition is true once after a Luup reload. It resets automatically, and takes no parameters. If you need to trigger a scene on restart or perform other startup activities, this condition offers another way to get that done.

Comments

Condition groups allow you to have any number of comments. These don't affect the condition logic, but are available for you to use as documentation for your conditions so that at some future date, you can remember why things are the way you configured them.

Variables and Expressions

Reactor (as of version 1.3) allows you to define variables that are the result of expressions. The expressions can reference device state variables and other Reactor variables, and support a rich set of operators and functions. The result of the expression evaluations are stored on the ReactorSensor in device state variables, and so are available to conditions in the current and other ReactorSensors (as well as Lua, PLEG, and other facilities that can use state variables).

Expressions are written "in-fix," such as (Temp-32)/9*5. If you are familiar with JavaScript, Lua, C, or really most calculators (except notably RPN), the expression syntax should be familiar. The expressions are evaluated using LuaXP, which is a stand-alone Lua library I separately developed for parsing expressions (it's also used by SiteSensor). For general information, refer to the documentation for general expression syntax and the core function library.

There are a couple of extra functions defined by Reactor as extensions to LuaXP:

  • finddevice( deviceSpec ) -- This function will return the Luup device number of the first device matching deviceSpec (a string value, device name or UDN). The comparison is not case-sensitive. 
  • getstate( device, serviceId, variableName ) -- This function returns the value of the specified state variable (identified by both serviceId and variableName) for the specified device. If the device argument is a number, it is used as a device number; if it is a string, it may be a UDN or device name.

Expressions give you many important benefits. Among the most important is that it allows you to perform arithmetic on state variable values before they are used in conditions, so you can do things like convert between degrees Fahrenheit and Celsius, scale or round values, etc. For example, to retrieve the current temperature from a sensor named "Multi Temp 1" that returns its value in degrees Celsius, and convert it to degrees Fahrenheit, you could use this expression:

getstate( "Multi Temp 1", "urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature" ) * 9 / 5 + 32

Note that the getstate()'s device argument performs the function of finddevice() internally, so it is not necessary to use both. The whitespace in the example is for clarity only and may be omitted (except for that in the string "Multi Temp 1", which must match exactly any whitespace used in the device name).

Another benefit of expressions is that they allow you to make more complex comparisons than Reactor's intentionally-simple condition editor would otherwise allow. For example, you can compare the value from one device state variable to that of another (which is not possible directly in the condition editor). Let's say we wanted to make a ReactorSensor that trips when the outdoor temperature is 10 or more degrees warmer than the indoor temperature. This takes two simple steps:

  1. Define a variable called HotOutside with the expression getstate("Outdoor Temp", "urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature") >= ( 10 + getstate("Indoor Temp", "urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature") )
    Notice we are using two getstate() calls for separate devices in the same expression--that's fine, in fact, as many as you want/need. Also note we've carefully matched our parentheses on the right side of the ">=" operator. Because we are using a comparison operator, the result of the expression will be boolean, with a value of 0 for false and 1 for true.
  2. Define a condition that checks if HotOutside is not equal to zero. Any time the variable's value is not zero, the condition will be true, and if that's the only condition in the ReactorSensor, the sensor will trip. The sensor will untrip when the variable goes back to zero (i.e. the temperature differential is less than 10 degrees).

Activities -- Running Scenes to Do Work

At the moment, I don't feel it's necessary to build a scene-builder in Reactor. Vera's scene builder isn't that bad. The major problem with Vera's scenes are that the triggering is inadequate, and that running scenes with delayed activity groups fails if Luup reloads or Vera restarts during scene execution.

Reactor addresses both of these problems. Since Reactor is a program logic trigger device, it can trigger any scene on your Vera, simply by using your ReactorSensor as a device trigger in your scenes. In this case, the Vera system runs the scene, and that may be sufficient, as long as you don't have delayed activity groups in your scene. These are "fragile," and Vera will not reschedule and run delayed activity groups in a scene if the Vera reloads or reboots during the delay period.

ReactorScenes is the answer to Vera's fragile scenes. When you use ReactorScenes, Reactor runs the scene rather than Vera running. Reactor tracks the progress of scene execution, and if the scene is interrupted, will restart scene execution from where it left off. ReactorScenes support any Lua that the scene includes, with the usual behavior (the Lua code is run first, and if it returns false, the scene activities are not run; otherwise, they are run).

To make a scene run by Reactor instead of Vera natively, all you need to do is remove any device triggers in the scene itself, and make sure these are conditions in your ReactorSensor (that is, move your triggers out of the scene and into a ReactorSensor). Then, in the "Activities" tab of the ReactorSensor, choose the scene as the "trip" or "untrip" action, as appropriate to your requirements.

You can use ReactorScenes to run all of your scenes. If you follow the above steps for all of your scenes, then all of your scenes will be run by Reactor and you will get the benefit of more robust scene execution that Reactor delivers.

If you also have scenes that you run from your own Lua code, you can still use ReactorScenes. All you need to do is replace the service ID urn:micasaverde-com:serviceId:HomeAutomationGateway1 with the Reactor service ID urn:toggledbits-com:serviceId:Reactor, and change the device number in the action call to the Reactor master device. Reactor implements a RunScene action that functions identically to Vera's native RunScene action (so no further code changes are needed), and will execute any scene on your behalf, even if the scene isn't being driven by a ReactorSensor. For example:

luup.call_action( "urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", { SceneNum=15 }, 0 )

becomes

luup.call_action( "urn:toggledbits-com:serviceId:Reactor", "RunScene", { SceneNum=15 }, reactorDevice )

Other Features

ReactorSensors, like any SecuritySensor1 device, keep a LastTrip variable (in service urn:micasaverde-com:serviceId:SecuritySensor1)  which records the time that the sensor was last tripped. In addition, it keeps a LastReset variable (in service urn:toggledbits-com:serviceId:ReactorSensor) with the time at which the sensor was last reset (untripped).

They also keep a Runtime variable (in service urn:toggledbits-com:serviceId:ReactorSensor) which accumulates the total time (in seconds) that the sensor has been tripped. So, for example, if you created a ReactorSensor that tripped whenever your thermostat was heating or fan was running, you could easily get the runtime over a period to help cue filter changes or other maintenance. The runtime can be reset by directly writing a 0 to this variable in Lua, or using the ResetRuntime action (no parameters) in service urn:toggledbits-com:serviceId:ReactorSensor.

Reacting to Reactors Externally

ReactorSensors can be used in conditions by other ReactorSensors. Caution should be used, however, to avoid creating "loops" where two ReactorSensors alternately trip and untrip each other at machine speed. Reactor has built-in rate limiting of both the update frequency and trip-change rate, and will throttle the sensor if either rate exceeds set thresholds (default 30 updates or 5 tripped state changes per minute; configurable via direct variable access).

Status Display

To help you visualize your conditions, a ReactorSensor's device control panel shows a status display with all of the configured condition groups and their respective conditions. Each condition line shows the condition as configured, as well as the current value for the condition, whether or not the condition is currently matched (true), and the date/time at which the last change in match state occurred. If all conditions within a condition group are met, the background color of the condition group is light green; otherwise, it is white.

Highlighted Group

In the above image, the first group's conditions are both met, so this group is highlighted (and the ReactorSensor is therefore tripped).

If your ReactorSensor also contains variables (defined by expressions, see Variables & Expressions, above), an addition section will display the name, expression, and most recent value for each.

Reactor's State Variables and Actions

Advanced Luup users may be interested in the services, actions, and state variables that Reactor creates and uses. See Reactor Services.

Backup and Restore

The master Reactor device has, in its control panel, options for backing up and restoring the entire set of ReactorSensors' conditions. When the backup link is clicked, the current sensor configurations are written to a file named /etc/cmh-ludl/reactor-config-backup.json, and displayed in a new tab or window. It is recommended that the user use "copy and paste" to copy the displayed configuration to a plain text file (e.g. using NotePad on Windows, or equivalent on Mac) and save it elsewhere for safe keeping. If a backup is not saved elsewhere than on the Vera, a factory reset or replacement of the Vera would lose the only backup available, and require the user to rebuild all ReactorSensors manually.

The restore link restores the configuration previously backed up to /etc/cmh-ludl/reactor-config-backup.json, if it exists. If the user wishes to restore a different backup, make a copy of the file to be restored and name it (exactly) reactor-config-backup.json, and then upload it to the Vera using the Apps > Develop apps > Luup files uploader. The user may then click the backup link to restore the uploaded file.

Support & Suggestions

If you need support or have a suggestion, please post in the Reactor board in the Vera forums.