2.x gateway scripts

TCL scripts is a new functionality in OHS 2.x. They allow various enhancements to be added by users to the automation part of the gateway. As the base of TCL language I used the following library: https://github.com/zserge/partcl, and embed it into gateway firmware. Scripts are handled by a separate thread that has a queue that serves all incoming scripts, and passes the result back asynchronously as a call back. Scripts are using their own separated heap, provided by umm_malloc library, that performs great heap management. Embedded SPI FRAM is used as storage for scripts with 64kB of available space. I have written a simple block access library called uBS (micro block system) that allows basic read write operations.

Scripts also have their own tab in the web interface, which allows you to create and run the TCL scripts directly on the gateway. As shown on the picture, there is statistics about the heap and the storage used by scripts. as well as helper icons </> and #. The </>, when you hover over it, shows build in language helps. And the # shows all variables currently in heap with their values.

Button "Run" passes the edited script into execution queue. "Refresh" button simply refreshes the page, allowing the "Last output:" to be seen. "Save" stores the script with given name to uBS storage, and allows this scripts to be assigned to triggers and timers.

Due to performance limitations, since scripts are not primary function of the gateway, there are build in limits to maximum script execution time, and maximum script interactions.

Language syntax

Tcl script is made up of commands separated by semicolons or newline symbols. Commands in their turn are made up of words separated by whitespace. To make whitespace a part of the word one may use double quotes or braces.

An important part of the language is command substitution, when the result of a command inside square braces is returned as a part of the outer command, e.g. puts [+ 1 2] performs addition of 1 + 2 and passes the result to puts.

Currently the only data type of the language is a string. Even numbers are stored in string format and converted to numeric types when needed.

Any symbol can be part of the word, except for the following special symbols:
  •     whitespace, tab - used to delimit words
  •     \r, \n, semicolon or EOF - used to delimit commands
  •     Braces, square brackets, dollar sign - used for substitution and grouping


Partcl interpreter is a simple structure which keeps the current environment, array of available commands and a last result value. Interpreter logic is wrapped around two functions - evaluation and substitution.

  • If argument starts with $ - create a temporary command [set name] and evaluate it. In Tcl $foo is just a shortcut to [set foo], which returns the value of "foo" variable in the current environment.
  • If argument starts with [ - evaluate what's inside the square brackets and return the result.
  • If argument is a quoted string (e.g. {foo bar}) - return it as is, just without braces.
  • Otherwise return the argument as is.
  • Iterates over each token in a list.
  • Appends words into a list.
  • If the command end is met (semicolor, or newline, or end-of-file - our lexer has a special token type TCMD for them) - then find a suitable command (the first word in the list) and call it.
Each command has a name, arity (how many arguments is shall take - interpreter checks it before calling the command, use zero arity for varargs) and a C function pointer that actually implements the command. 

Builtin commands

  • "set" - assigns value to the variable (if any) and returns the current variable value.
  • "subst" - does command substitution in the argument string.
  • "puts" - prints argument to the tcl_stdout buffer, followed by a newline
  • "proc" - creates a new command appending it to the list of current interpreter commands. That's how user-defined commands are built.
  • "if" - does a simple if {cond} {then} {cond2} {then2} {else}.
  • "while" - runs a while loop while {cond} {body}. One may use "break", "continue" or "return" inside the loop to contol the flow.
  • Various math operations are implemented like:  !, +, -, *, /, >, >=, <, <=, ==, !=, &&, ||.
  • "string" - performs simple string manipulation, like"compare" and "length".
  • "clock" - allows time and date manipulation, like "seconds", "format" and "add".
  • "node" - returns value of given sensor node.
  • "group" - returns arm state or status of given group. (group $(number) a_rmed|s_tatus).
  • "timer" - returns timer state. (timer $(number)).
  • "trigger" - returns trigger state. (trigger $(number)).
  • "findex" - finds index (numeric value) of group, timer or trigger. (findex z_one|g_roup|t_imer|t_rigger $(name)

Sub command names can be usually interpreted by abbreviation. Such as command "findex zone bedroom" can be written as "findex z bedroom". The minimum abbreviation need to distinguish a sub command is shown here and in help as "zone" - > "z_one".

No comments:

Post a Comment