In Part 8 of this series, I explained how to access data from a sensor device using the value name you defined in the piotsever config file. For the example, I used one of the bits from an NCD MCP23008 Relay+I/O device that was hooked up to an Orbit Rain Sensor. The MCP23008 would be read periodically from the I²C bus, and its state would be stored in the value RAIN_SENSOR.
Then, later in Part 11, when I described the sequence I was using to control the sprinkler, I briefly mentioned the following:
I also set up a “condition” entry to evaluate “RAIN_SENSOR == 0”; which is when it is wet enough to short the rain sensor switch to ground.
This condition is checked after the sequence is triggered, but before any of the steps are run. It will also be checked periodically, and the sequence will be aborted if the condition evaluates to false.
"sequence": [
{
"sequenceID": "1000",
"name": "sprinkler control",
"description": "day to day sprinkler sequence",
"condition": "RAIN_SENSOR == 0",
"trigger": {
"timeBase": "sunrise"
},
"steps": [
........
The example above was fairly self-explanatory, but now it’s time to delve a bit deeper into how the expressions in the conditions are evaluated and what other things we can do with this feature.
Expression Evaluation
I wanted to avoid forcing the user of piotserver to work in a scripting system. I have seen how this can get out of control in systems like Home Assistant. I realize the importance of being able to add some customization to automation, but I have mixed feelings about how to achieve this. ( I am not a fan of any of the popular scripting systems.)
However, at a minimum, I believe the user needs a way to describe a conditional expression based on it and possibly set values with it. I am not new to building expression evaluator systems, but after researching, I like the ExprTk Mathematical Expression Toolkit Library from Arash Partow. ExprTk is an open-source software that has been used in a wide range of projects, from game development to experimental physics and industrial control systems.
The best way I found to integrate ExprTk into the piotserver system was to take a snapshot of the latest set of values reported by the devices and pass that to the ExprTk evaluator along with the expression to be processed. This allows us to evaluate something like:
RAIN_SENSOR == 0
COOLER_TEMPERATURE > 45
Or more complex values like
RAIN_SENSOR == 1 and GARDEN_TEMPERATURE < 32
But, as mentioned in the documentation for ExprTk, you can also control structures such as if-then-else statements or loops, and they can be used in the condition part of a sequence as long as the expression evaluates to a true or false value.
However, we can do more than just use an expression to control the execution of sequences. You can also use them as an action in a sequence. Again, back in Part 11, I gave the example of opening a door using the SET command.
"steps": {
"action": {
"cmd": "SET",
"key": "DOOR_1",
"value": "0"
}
I have also added the ability to use the EVAL
command to evaluate an ExprTk expression and set variables. For instance, if we wanted to keep track of how many times the piotserver was restarted, we could write the following sequence that would run at system startup and increment a variable called START_COUNT.
"values": [
{
"data_type": "INT",
"initial.value": 0,
"key": "START_COUNT",
"tracking": "track.latest"
}
],
"sequence": [
{
"trigger": {
"event": "startup"
},
"steps": {
"action": [
{
"cmd": "EVAL",
"expression": "START_COUNT := START_COUNT + 1"
}
]
}
}
]
Notice that we also define the variable START_COUNT,
set its initial value to zero, and specify that we should track the latest value.
The piotserver built-in database does the tracking. But that’s a topic for another article.
Since START_COUNT is a variable, we could query it using the REST interface in the same way as we would the other variables.
http://royal9:8081/values/?START_COUNT
Built-in variables
There are also several variables built into the piotserver that you can use in your expressions, including:
SOLAR_SUNSET
SOLAR_SUNRISE
SOLAR_CIVIL_SUNRISE
SOLAR_CIVIL_SUNSET
SOLAR_MOONPHASE
SOLAR_LASTMIDNIGHT
CPU_TEMPERATURE
CPU_FAN
This allows you to synthesize new variables from them.
"values": [
{
"data_type": "INT",
"formula": "SOLAR_SUNSET - SOLAR_SUNRISE",
"key": "MINUTES_OF_SUNLIGHT",
"tracking": "track.latest"
}
]
Where MINUTES_OF_SUNLIGHT is calculated at midnight when the solar values have been updated. This expression can certainly be much more complex, but I wanted to provide you with a simple example for now.
There is more going on behind the curtain.
This is just a sample of things that you can do for now. A lot is happening behind the curtain that I will explain in future articles. I've mentioned the database more than once, and I plan to write an article focused solely on that topic.