Skip to content

Implementation notes

Jan Gabrielsson edited this page Jun 12, 2018 · 13 revisions

The reason for developing this framework is to try to find an abstraction model for programming home automation systems, Fibaro's HC2 in this specific case. This means dealing with sensors and actuators and time and be able to describe logic involving those elements. This has driven a few design choices:

  • An underlying event-based programming model, could also be thought of as a message based model. The home automation environment is highly asynchronous. Sensors and switches goes on and off at will. The scene model chosen by Fibaro tries to help by spawning new instances of scenes for every new sensor and switch trigger happening. This allow a scene to not block other triggers, but it makes it difficult to synchronize logic across many triggers. Using an event/message-based model for scenes makes it much more natural to deal and handle with the inherent asynchronicity.
  • A mapping of the event model to a script language based on 'rules'. Rules comes in 3 flavors, rules that fire at specific times during the day, rules that triggers on state changes from sensors and actuators, and rules that trigger on any type of (user) defined event. The last flavor allows for mixing the event model with the rule model in scenes.
  • A script language with a semantic/syntax (abstraction level) that makes it easy to deal with time and events. Time has its own data type, e.g. "10:40+01:20 == 12:00", and to check if a rule is triggered within a time interval the '..' operator can be used e.g. "11:00..12:00". Events have its own syntax, e.g. "#event1 => post(#event2)". Devices' properties are accessed (and set) with the ':' operator. e.g. "55:on", "lamp:isOn", "lamp:value=99". The ':' operator also handles operations on sets of devices. e.g. "{55, lamp}:on". etc...

The framework implements a model where a "main" scene always run and looks at a "mailbox" (global fibaro variable) if there are any new incoming fibaro triggers (sensors actuators etc.). If so they are sent to defined event handlers and/or rules in the framework. Events posted within the framework is handled within the framework and don't need the mailbox. A new incoming extern/fibaro trigger will spawn a new instance of the framework scene, and that instance will quickly post that trigger to the mailbox and quit. This allows the main scene to fetch the trigger and hand it to event handlers and rules. The BIG advantage here is that all triggers will be handled within the same scene instance (main). This allows handlers and rules to "remember" state between invocations. One way to think about the framework is that it converts triggered scene instances into timer instances within a single scene (main).
There are some trickery to do synchronous mailbox handling, and even though Fibaro/Lua don't have primitives to handle that the chosen implementation has empirically proven to work...

High level framework logic in the picture below

The script rules are compiled to a simple stack based 'byte-code'. The advantage is that the byte-code loop is linear/flat allowing the execution of the code to be paused at any time (code is just an array of instructions). A traditional recursive code interpreter over some tree-structure is trickier to suspend. This is used by script functions like 'wait' and 'for' to suspend the code execution and setup a timer to be able to later resume executing the code where it left off. The interpretation of the byte-code in general is surprisingly fast.

"trace(true); b=3;a=8*b+10" translates to

[[push/0,true],[trace/1],[pop/0],
 [setVar/0,"b",3],[pop/0],
 [push/0,8],[var/0,"b"],[*/2],[push/0,10],[+/2],
 [setVar/1,"a"]]

and when run produces the following trace (the tracecommand turns on instruction tracing, so the first instruction that is traced is the 'pop' of the result from trace)

Sun May 06 13:40:58 pc:3   sp:0   [pop/0]
Sun May 06 13:40:58 pc:4   sp:1   setVar("b",3)=3
Sun May 06 13:40:58 pc:5   sp:0   [pop/0]
Sun May 06 13:40:58 pc:6   sp:1   [push/0,8]
Sun May 06 13:40:58 pc:7   sp:2   var("b")=3
Sun May 06 13:40:58 pc:8   sp:1   *(8,3)=24
Sun May 06 13:40:58 pc:9   sp:2   [push/0,10]
Sun May 06 13:40:58 pc:10  sp:1   +(24,10)=34
Sun May 06 13:40:58 pc:11  sp:1   setVar("a",34)=34

It does some simple compile-time optimisations, like folding constants arithmetic expression.

Clone this wiki locally