I'm working towards a 1.5 beta this week sometime, and I just wanted to post a little info about the new event system, since it's something that many folks who are hard into the pure automation thing have been wanting in CQC before they'd be interested. So I figured I'd give you some info on what's coming so that you can comment now if you think something is missing.
Basically it's in parts. There are 'event triggers', where you indicate that you want a particular device field to cause a field change event to occur. In CQC all devices are composed of a set of 'fields' that are named entities that usually represent some readable or writeable value in the device. So you can mark the fields that you are interested in being notified of change for, and only those will send events (which will significantly reduce the level of event activity over just sending them for any change.)
You can indicate that you want it to send an event for any change in the field, or if it the value changes to or away from some value, is greater than or less than some value, is non-zero, and so forth. There are a standard set of such 'expressions' in CQC that are used in a few places, and those standard expressions are used in these event triggers.
On the receiving end, you define an 'event filters', where you define some action that you want to occur, and then you create a filter that decides if an incoming event meets the criteria for triggering that action. These filters are done using a simple expression language, which is actually a subset of the expression language used in our PDL (protocol description language) system used for writing drivers for simpler types of devices.
It's a very straightforward, purely declarative, language that is very fast to parse and create an expression tree from, but quite flexible at the same time. So, a very common filter would look like this:
Code:
IfAll
(
IsFldChangeFor("DevSim.Power")
, FldValIs(True)
)
Basically there must be one top level expression that resolves to true or false, i.e. an expression that has a boolean result. If it resolve to true, then the action is taken, else it is not.
In the example above, IfAll() is the top level expression. It takes an arbitrary number of child expressions and if they are all true, then it's result is true (i.e. it's an AND expression basically.) The two child expressions in the example above check for, respectively, a field change event for the Power field of the device called DevSim and, if it is, then it checks that the new value is True. I.e. it filtering for the DevSim device to be powered on.
The CQC events are effectively xAP messages, and a field change event would look something like this:
Code:
xap-header
{
v=12
hop=1
uid=FF000000
class=cqsl.fldchange
source=cqsl.field:DevSim.Power
}
cqsl.fldval
{
val=True
}
This event would pass the filter above, since the event class is a field change, and the source is a field named DevSim.Power, and the value reported is True. You can actually access any arbitrary event fields using lower level expressions than the ones used above. The equivalent of the first example up there would be this:
Code:
IfAll
(
EventFldIs("/xap-header/class", "cqsl.fldchange")
, EventFldIs("/xap-header/source", "cqsl.field:DevSim.Power")
, EventFldIs("/cqsl.fldval/val", "True")
)
The values of the event are represented via a path type syntax that should be pretty obvious if you look at the example event above. So the first example up there just used some special case expressions that are designed to make the most common scenarios easy, but you can always drill down and get to specific bits of the event data.
You can also do wildcarding, so something like this:
Code:
IfAll
(
EventFldIs("/xap-header/class", "cqsl.fldchange")
, EventFldIsLike("/xap-header/source", "cqsl.field:DevSim.*")
)
Would trigger the action on any field change from the DevSim device.
There are expressions to check for is it daylight, is it nighttime, for doing addition, subtractions, multiplication, and division, for returning one of two expression values based on the true/false state of a third expression, testing for equality and inequality, testing for greater and less than, and so on. And in addition to the IfAll() used in these examples there is IfAny, basically an OR, and IfOne, basically a XOR. Others can be added fairly easily and will be as we move forward and needs arise for them.
You can also get live device status info so that you can react both to the event contents and to the existing status of the system. So, we could extend the initial example to only trigger if it's not been supressed by the SupressEvent field of the driver named Variables being set.
Code:
IfAll
(
IsFldChangeFor("DevSim.Power")
, FldValIs("True")
, IsEqual(GetField("Variables.SupressEvent", False))
)
So in this case GetField gets the value of the field, and Equals() compares it to False and returns true if it is equal. So now we'll trigger if it's a field change event for DevSim.Power, the new value is true, and Variables. SuppressEvent is false. So we can now stop this event from happening by setting the SupressEvent field to True.
So that's basically the deal. It's something that can be easily used for simple stuff by non-programmers since simple events will mostly be like the first example. But it can be taken quite far for fancier filtering.
Keep in mind also that an 'action' in CQC is just a sequences of commands, one type of which can be a CML macro invocation. So you can do even more filtering in such macros if you want. So even if the action gets invoked, if that action just basically invokes a macro, the macro can still decide to effectivley do nothing if the situation is not auspicious. But, for the most part, the filtering language will be sufficient to do any filtering you'll need to do, or if something is missing it can probably easily be added via new expressions once it becomes known to us.
So here's one last one, to show an example of something more complex.
Code:
IfAll
(
IfAny
(
IfAll
(
IfAll(IsFldChangeFor(L"Motion.Bedroom"), FldValIs(True)),
IsEqual(GetField("Lights.Bedroom"), FldValIs(False))
),
IfAll
(
IfAll(IsFldChangeFor(L"Motion.LivingRoom"), FldValIs(True)),
IsEqual(GetField("Lights.LivingRoom"), FldValIs(False))
)
),
IsNight()
)
So this one says that if either motion ws detected in the bedroom and the bedroom lights are off, or if motion was detected in the living room and the livingroom lights are off, and it's night time, then run the action, which will probably turn on the light in the area where the motion was detected.
I'll probably add some more helpers before 1.5, such as DevFieldIs() which would combing the GetField and the FldValIs, since those are going to be done so much, and DevFieldIsLike for regular expression type checks of device fields. With that, the above would get simpler:
Code:
IfAll
(
IfAny
(
IfAll
(
IfAll(IsFldChangeFor(L"Motion.Bedroom"), FldValIs(True)),
DevFldIs("Lights.Bedroom", False)
),
IfAll
(
IfAll(IsFldChangeFor(L"Motion.LivingRoom"), FldValIs(True)),
DevFldIs("Lights.LivingRoom", False)
)
),
IsNight()
)
So, there'tis. Does anyone have any major complaints about this scheme? It should be plenty powerful enough for the hard stuff and simple enough for the easy stuff.