Generalized Syntax Expansion Styles Predefined Classes UserDefined Substitution Classes Extending Known Issues Requests Contact Me
Device veraTemplate Library

Overview

The veraTemplate library allows the users to embed Templates into a string. These templates are like variables that are replaced with textual data at a later time when the template is expanded by a template aware plugin or component. A template allows the selection and specifies the formatting of the data as it replaced with the template description in the original string.
A template begins with a { and ends with a }. What's in between describes which data to access and how to format it. To give you a feel of the capability, the following are some examples that might be used in a notification message to the user. This only shows the template fragment of the message string. You can put all kinds of additional fixed text around the template description. There are other templates that allow formatting numbers, strings, and Date/Time information.

Generalized Syntax

Before we get into the details of the kinds of templates that are made available by this library, lets look at the syntax used to define a template. A templated String that has one or more templates descriptions embedded in it's body . A template begins with a { and ends with a }. A single Template is defined by a balanced set of opening and closing curly brackets. A template might have other templates nested in it that provide arguments for the enclosing template. In that case there will be nested brackets as follows:
{OutterTemplate {NestedInnnerTemplateProvidingArgumentsToOuterTemplate} ... }
Nesting is only limited by computer resources to process them. In addition the evaluation of a template can result in another template which will also be expanded.

Now let's look at a more detailed view of the Template syntax. After the opening curly bracket we have the template's class name. i.e. {classname Class names are not case sensitive and are delimited by space or some other punctuation character, including the closing } curly brace.

Optionally it may be followed by the subfunction name of the class as follows {classname.subfunction If it a subfunction is specified, it is separated from the classname with a . period. subfunctions are not case sensitive.
An example class name is String with subfunctions such as Upper, Lower, and Truncate.

Next we optionally have arguments for the template function or subfunction. {classname(arg1, ... argn) or {classname.subfunction(arg1, ... argn).
An argument list is denoted my balanced opening and closing parenthesis, with a comma separated list of arguments. The number of arguments is defined by the class function or subfunction.
NOTE:If more than one argument is required by a function or subfunction, and an argument contains a , comma, than a miss interpretation of the arguments list can happen.

If we want to pass the result of expanding a template class function or subfunction to a different class, whose job it is to continue to format the string we can do this through a technique called piping. In this case the syntax now becomes: classname.subfunction(arg1, ... argn) | nextclass.subfunction(arg2, ... argn)
The | vertical bar separates classes that are piped. You can pipe as many classes as you wish together, only limited by computer resources. Expansion of classes starts with the left most class function or subfunction and moves to the right. The output of the last class function or subfunction provides the final expansion of the whole template. When you pipe classes together, the output of the previous class becomes, the first argument of the current class ... hence you will notice the argument list in the piped class starts with arg2. If a piped class function or subfunction only takes one argument, than when you pipe the classes together, you can ommit the otherwise empty argument list syntax.


Multiple Techniques for Expansion

Since we support nested templates, often one can convert between piped classes or nested classes. You can mix and match. A long chain of piping is equivalent to a deep nesting level. As I indicated earlier, a , comma can confuse things. Sometimes using piping can eliminate one argument ... and if the source of the confusion was caused by a comma in the first argument, this can be avoided. The following are equivalent. If you need to expand a template for other than the first argument, than you must use nesting. Inner most templates are evaluated first.

Predefined Classes

A number of classes are predefined. The architecture allows for the inclusion of additional classes which are specified at run time. You can also replace an existing class definition.

System Class

There are no defined subfunctions for this class.

The sole argument is the name of MCV system variable and is case sensitive.These are desribed by: MCV Luup extensions as Module Luup variables.

Examples
"Vera is running version {System(version)}" and is expanded to:
Vera is running version 1.5.408

DateTime Class

There are no defined subfunctions for this class. Its sole purpose is to convert a date/time value on Vera to a human understandable date and/or time. This class often has the timestamp acquired from a previous class piped to it. You can use it to obtain the current time, by using the case insensitive value of CurrentTime at the first argument.
The first argument of this classes function is a Vera DateTime number. The second, optional argument, specified how to convert this time to a user understandable format. Details of this value can be found by looking at the format string options for the strftime software function.
Examples
"The internal representation for the current time is {DateTime(CurrentTime)}" This will display a large number representing the number of seconds since Jan 1, 1970. It would expand to:
The internal representation for the current time is 1348020935 

"The current human readable time is {DateTime(CurrentTime,%A %B %d, %Y at %H:%M)} and will expand as follows:

The current human readable time is Tuesday September 18, 2012 at 21:15

Often this is piped from another class that might provide some information like the time that a lock was opened.

String Class

This has a number of subfunctions. If no subfunction is specified the default subfunction is Format1 This class can do a lot of string transformations.

String.Format Subfunction

It uses a format string to describe how to transform potentially multiple input strings. It's similar to the Printf Format string It supports the following conversion types: %c, %d, %E, %e, %f, %g, %G, %i, %o, %u, %X, and %x for number strings and %q and %s for any string. %q puts quotes around it's associated string argument. This function takes a variable number of arguments, the first is the format string. For each conversion type specified in the conversion string there is another argument. As a result this subfunction is NOT suitable for having data piped into it. Often you would have a format string and a separate template for each of the arguments.

Examples
"Math is fun ... {String.Format(%6.1f x %6.1f = %6.2f, 2, 3, 6)}" and would expand to:

Math is fun ... 2.0 x 3.0 = 6.00

String.Format1 Subfunction

This function is suitable for being piped. The first string is the data to be formatted, The Second argument is a format string that specified a Single conversion character appropriate for the data being passed. Since this is the default subfunction, you do not need to specify it.
Examples
"Convert to a Hex format {String(159,Hex Format:%04X)} and would expand to:
Convert to a Hex format Hex Format:009F

String.RFill SubFunction

This function is suitable for being piped. The first argument is the data to be formatted. The Second argument is a single character fill character. The Third argument is the output minimum string size. The string is expanded to the minimum size by adding spaces at the end. Then all trailing spaced are replaced with the specified fill character.
Examples
"{String.RFill(W,o,20)}w" would expand to:
Wooooooooooooooooooow

String.LFill SubFunction

This function is suitable for being piped. Similar to RFill, but space is added at the beginning of the string and leading space is replaced with the specified fill character.

String.Truncate SubFunction

This function is suitable for being piped. The first argument is the data to be formatted. The second argument is the maximum string size.
Examples
"Truncate a long string {String.Truncate(ABCDEFGHIJKLMNOPQRSTUVWXYZ,5)}" and would expand to:
Truncate a long string ABCDE

String. Upper SubFunction

This function is suitable for being piped. The first and only argument is the data to be formatted. The output is an uppercase version of the input.
Examples
"Upper cass string {String.Upper(AbCdEfG)}" and would expand to:
Upper cass string ABCDEFG

String.Lower SubFunction

This function is suitable for being piped. The first and only argument is the data to be formatted. The output is a lowercase version of the input.
Examples
"Lower cass string {String.Lower(AbCdEfG)}" and would expand to:
Lower cass string abcdefg

Choose Class

There are no defined subfunctions for this class. This function is suitable for being piped. The input string is translated to an output string. If none are found it tries to use the Other translation. This is used to convert many of the internal numeric values in Vera to a human readable String. The first argument is the data to be transformed. This is usually very simple data. Subsequent arguments define the transformation for a particular value. Each transformation argument is of the form inputvalue=outputvalue.
Examples
"The transformation of 1 is {Choose(1, 0=Off, 1=On, Other=Unknown)}" and expands to:
The transformation of 1 is On

Device Class

There are no defined subfunctions for this class. But this is the most complex class and has a lot of detailed syntax. There are ways we can use to simplify it, and we will describe it later.

This class has a single argument. But syntax of the argument defines the behavior. It starts off with selecting the Vera device you want to get information about. This has three forms:

  1. Select device by the numeric Vera Device ID i.e. {Device([ID])}
  2. Select device by the User Interface name (also called the device description) Vera Device ID i.e. {Device([Device Description])}
  3. The absense of a specific device specification. This means that the software that is expanding the template knows which device to use. i.e. {Device}
Examples
The template "{Device([Office Light])}" would expand to the Id for with a device UI description of Office Light such as:
5

Next we have four types of information we can extract from the specified device:

  1. Device specific variables. See MCV Documentation for the list. These are case sensitive. i.e. {Device(description)} or {Device([5].description)} or {Device([Office Light].description)}. The . period after the closing bracket is optional.
  2. Information about the room for the device.
    Examples
    "The Room ID for Device 5 is {Device([5].room)} and it's UI name is {Device([5].room.name)}" and expands to;
    The Room ID for Device 5 is 2  and it's UI name is Office
    
    The . period after the closing bracket is optional.
  3. The devices specific variables. You can see the list of variables for a device by looking at the 2nd half of the Advanced tab on the Vera UI for a device. To access these variables requires that you have intimate knowledge of the UPNP services that is associated with the variable. There is no way from the UI to figure this out. You can get a good idea from the MCV documentation. By looking for your variable in that document ... you can back up and find which service it belongs to ... however the same variable name can belong to multiple services ... and each has its own meaning. The syntax for this is as follows:
    {Device([5].service[urn:upnp-org:serviceId:SwitchPower1].Status)}
    This syntax is interpreted as getting the Status variable of the urn:upnp-org:serviceId:SwitchPower1 service for Device ID #5. Translated into NON computer geek speak ... this is the ON/OFF state of switch #5.
    This is very verbose ... and soon we will talk about how to simplify this. The . period after the closing brackets are optional.
    Examples
    "The Status of Switch 5 is {Device([5].service[urn:upnp-org:serviceId:SwitchPower1].Status)} or {Device([5].service[urn:upnp-org:serviceId:SwitchPower1].Status) | Choose(0=Off, 1=On, Other=Unknown)} " and expands to;
    The Status of Switch 5 is 1 or On
    
  4. These same previously described three types of information can be extracted from the parent device. This is for those devices whoose behavior is defined by it's parent device.
    Examples
    {Device([5].parent. Description)}
    {Device([5].parent.room.name)}
    {Device([5].parent.service[urn:upnp-org:serviceId:SwitchPower1].Status)}

Service Aliases

We will now introduce the concept of a Service Alias to simplify the device syntax. There are really only a handful of common services. We have created aliases for these that are pretty easy to remember. They are: These can be extended by the user for additional services, as well as additional aliases for these same services that are easier for you to remember. More on this in the section on Using an Extending the Template library.

Examples

With Service Aliases we can now more simply access the previous switch example as:
"The Status of Switch 5 is {Device([5].Swich.Status)} or {Device([5].Switch.Status) | Choose(0=Off, 1=On, Other=Unknown)} " and expands to;
The Status of Switch 5 is 1 or On
The . period after the closing bracket is optional. However the . period between Switch and Status is now required.

User Defined Substitution Classes

We will now introduce the concept of a user defined Substitution Class. In the examples we have used the Choose class to format the data for a Switch from a 0 and 1 to Off and On. If you have a lot of switches this can be redundant. In particular as the number of translations gets larger and you wish to use them in a lot of templates. Similar to the Service Aias, you can create your own Substitution Classes. When we need to exand a substitution class it is replaced with its definition.

Examples

An example would be to define the "BinarySwitchOptions" Substitution class as "0=Off,1=On,Other=Unknown" then the following nested/piped template:
"{Device([5].Switch.Status) | Choose({BinarySwitchOptions}) }" would expand to
On

In fact this would allow the translations to be centrally managed and changed for different languages. At the present time there is no plan to pre-define many Substitution Classes. But as a user you may find it quite useful. More on this in the following section on Using an Extending the Template library.

Using and Extending the Template library

There are two primary types of users for this library. The first is the plugin developer that wants to provide template capability to their plugin. The second is an end user who wished to embed templates into strings that are passed to a template aware plugin.
The things that motivated this work for me was the VeraAlert notification plugin. I wanted to clean up its interface and also to support text to speech (TTS) in the notification. For these TTS notifications I wanted to use templates to access the information from various device properties and I did not want to write a lot of luup code. Also I wanted to addition to embed additional punctuation to impact the cadence of the spoken text.

Template Library for the Plugin Developer

To use in your plugin you can access the Template expansion library from Luup/Lua as follows:
  -- Gain access to the Template Library
  t = require("veraTemplate")
  -- Expand the templates in a string
  outstring = t.expand(instring)

You can define service variables as follows:

  -- Gain access to the Template Library
  t = require("veraTemplate")
   -- Add a Service Alias
  t.register("Lock", "service[urn:micasaverde-com:serviceId:DoorLock1]", "Service")

Addition functions are:

  -- Gain access to the Template Library
  t = require("veraTemplate")
  -- Absent  Defines the behavior of a template that does not expand 
  --  (0: Untouched, 1: Remove curly brackets, 2: Empty String) Default is 0
  t.Absent = 2

  -- Add a substitution class
   t.register("BinarySwitchOptions", "0=Off,1=On,Other=Unknown")

  -- Register your own Functional Template class with optional subfunctions
  mytemplateclass = requre("myTemplateClass")
  t.register(mytemplateclass)

  -- Dump the list/type of templates

  luup.log("Template classes:" .. t.classes())

Creating your own Functional Template Class

The following is the boiler plate for your own Functional Template class file. Just set the name and fill in the details for the expand function for your class:
module("veraTemplateXXXX", package.seeall)
-- Private

-- Public
templateclass="MyClassName"

function expand(SubFunction, Args, PipeArg)
    ...
   return Result
end
Details for the arguments for expand function are as follows: You shoud return nil for a template that cannot be expanded. The calling code will handle this expansion based on the definition for veraTemplate.Absent
All input templates will be expanded before you are called.
If you return a string that contains a template ... it will be expanded by the caller of this particular expansion.

Template Library for the End User

There are two things for an end user to do.
  1. Add Service and Substitution templates to the veraUserTemplates.lua file.
    The Luup/Lua syntax for this is the same as for plugin developers and is shown above.
  2. Embed templates in the string values passed to Template aware plugins.

Installation Files

When you reference the template library as:
  t = require("veraTemplate")
It will load the various predefined functional classes. In addition the following two files will be loaded to define the standard Service Aliases and the user defined Substitution Class templates:
  • veraTemplateDefinitions.lua
  • veraUserTemplateDefinitions.lua

    The latter is where the end user can place their Service and Substitution template definitions ... knowing they will be available during any template expansion activity.


    Issues

    What Next

    Still need to decide:

    Requests


    Contact me at Schaefer@RTS-Services.com


    Logo
    © Copyright 2017, RTS Services Inc. All Rights Reserved
    1675 Hickory Creek Rd
    Marble Falls, Tx 78654