Programs (APP)

What really makes of HomeGenie an home automation software is the set of automation programs that are shipped with its factory settings.

If we disable all those programs, HomeGenie will just turn into a dumb box, only capable of switching on and off devices on user request.

This is because the core of HomeGenie is a generic purpose automation engine that is called A.P.E., the Automation Programs' Engine and that does not provide intelligence of any sort by itself.

So, what really specialize the use of HomeGenie for a particular purpose or another, are all the automation programs that are defined into it.

All functionality described so far, like Smart Lights or the Security Alarm System, are in fact implemented through automation programs that we can also explore and modify from the web user interface using the Program Editor.

We'll then see how automation programs can be used either to accomplish simple tasks like creating scenarios or more complex tasks like integrating new devices or services.

Program Editor

The Program Editor can be accessed from the Automation section of the *Configure menu. There, automation programs are conveniently organized into groups. New automation programs can be created by choosing a group and then selecting the Add program option from the Action menu located in the bottom-right corner. An automation program can be coded using one of the following programming languages:

Despite the selected language, all programs can use the same set of Helper Classes to access HG resources or external services in the same way.

Example - Turning off lights in a given group

The following example is using the ModulesManager helper class to turn off all lights in the Porch group:

// C#
var lights = Modules.InGroup("Porch");
porchLights.Off();

// Javascript
lights = hg.Modules.InGroup('Porch');
lights.Off();
// Javascript with camelCase
lights = hg.modules.inGroup('Porch');
lights.off();

// Python
lights = hg.Modules.InGroup('Porch')
lights.Off()

so all of these are very similar, just using own language specific syntax.

Remarks

The hg. prefix, used to address Helper Classes, can be omitted when using C# but must be used for all other languages.

The camelCase coding practice can be adopted for Javascript programs when calling helper methods.

Some Helper Class methods may require a callback function as an argument. The following examples show how callbacks can be passed in the various language flavours.

Example - Using callbacks in different languages

The When.SystemStarted method requires a callback as argument:

// Output a speech message from the speaker
// right after HomeGenie is started and ready.

// C#
When.SystemStarted(()=>{
    Program.Say("HomeGenie is now ready!");
    return true;
});

// Javascript
hg.when.systemStarted(function(){
    hg.program.say('HomeGenie is now ready!');
    return true;
});

// Python
def started_handler():
    hg.Program.Say('HomeGenie is now ready!')
    return True
hg.When.SystemStarted(started_handler)

Further documentation about specific syntax of each language can be found on the following pages (or just searching the web):

Program Code and Startup Code

An automation program is split into two parts. The Program Code, which is the main code, and the Startup Code.

Startup Code is a piece of code that can be used to set various program options and also to tell HG when to run the main program code by using the Program.Run instruction in it.

When the program is idle (thus the program code is not running), the Startup Code is evaluated every minute or as soon as a new event occurs in the system.

Example - Startup Code

// Run the program every day at 7 am
if (Scheduler.IsScheduling("0 7 * * *"))
    Program.Run();

Other instructions commonly used in the Startup Code are:

The use of these command is described later in this page.

Remarks

when Program.Run is called in the Startup Code, the program's code will run only after that Startup Code reaches it's end. So it's a good practice not to put any long-time consuming operation or infinite loop in the Startup Code.

Startup Code and Program Code are not running in the same scope. Data between them that can be shared by using Modules parameters and other structures such as Program Store and System Settings.

Virtual Modules, Program Module and Widgets

Virtual Modules (or just modules) are used in HG as an abstraction for devices or services. So, each of them, represent a particular device or service in the system and holds its data into Parameter fields. For example: a module for a light switch device will have a parameter called Status.Level for indicating the current state of the light (1 = turned on, 0 = turned off); a temperature sensor module will have a Sensor.Temperature parameter... and so on.

Each module is identified by Domain, Address, Type and Widget. The Domain is used as a group for same class of modules. Example of domain names are: HomeAutomation.ZWave, HomeAutomation.PhilipsHue, HomeAutomation.X10. The Address (usually a number) is used to identify uniquely each module belonging to the same domain. The Type will define what kind of module is. Commonly used types are: Program, Switch, Light, Dimmer, Sensor, Temperature, Siren, Fan, Thermostat, Shutter, DoorWindow, DoorLock, MediaTransmitter, MediaReceiver. The Widget will determine how a module will be displayed in the User Interface. There are already a bunch of widgets available in HG, but custom ones can also be designed using the integrated Widget Editor.

Also automation programs have a module associated to each of them, so program's data can be displayed in the UI using a widget. The standard widget that can be used for a program is a simple button that once clicked runs the program.

Example - Associating a widget to a program

// This program will be displayed as a simple button
Program.UseWidget("homegenie/generic/program");

A program can also create and handle futher modules by using the following functions:

Program Options

Automation programs can have an options dialog in the UI, so that the user may configure some aspect of it. As an example we can look at the Weather Underground program. This program needs to know the city name so to display weather data of the given location.

Example - Adding a field to the program options dialog

// Add "city" text field to the program options UI dialog
Program.AddOption(
    "Location", // <-- identifier name of the option
    "autoip", // <-- default value
    "City name", // <-- description
    "text"); // <-- UI field type and parameters
// ...

Example - Getting the entered value of a program option

var location = Program.Option("Location").Value;

The Program.AddOption instruction is meant to be used inside the Program.Setup delegate.

Program Features

In a similar way as described for the program options dialog, module also have an UI options dialog where an automation program can add its own options so to let the user configure different per-module values. As an example we can look at the Automatic turn-off program. This program will add to the options dialog of modules a new field where the user can set the turn-off delay for each module.

Example - Adding a program feature to modules

// This will display a slider for setting the timeout
// in the module options UI dialog
Program.AddFeature(
    "", // <-- affected domain name (empty "" string stands for "any domain")
    "Switch,Light,Dimmer", // <-- affected module types
    "HomeGenie.TurnOffDelay", // <-- identifier name for this feature
    "Automatic turn off delay (seconds)", // <-- description
    "slider:0:3600:1"); // <-- UI field type and parameters
Program.Run();

The last parameter of the Program.AddFeature function will select the type of control that will be displayed in the UI module options dialog. The following are the currently implemented UI field types:

Reacting to module events

Each time a module parameter is updated a new event is raised in the system. By using either the ModuleParameterIsChanging or the ModuleParameterChanged function, a program can listen to the system event stream and react in consequence of a module event. The difference between ModuleParameterIsChanging and ModuleParameterChanged, is that the first one is called before the latter one. In most situations ModuleParameterChanged will be used.

Example - Apply turn off timeout when a module is switched on

CSharp

// this function will be called each time a module parameter is updated
When.ModuleParameterChanged((module, parameter) => {
  // check if the module raising the event has the Turn Off Delay set
  if (module.HasFeature("HomeGenie.TurnOffDelay") &&
      module.Parameter("HomeGenie.TurnOffDelay").DecimalValue > 0)
  {
    // Check if the module has just been turned on
    if (parameter.Is("Status.Level") &&
        parameter.Statistics.Last.Value == 0 &&
        parameter.DecimalValue > 0)
    {
      // Run a background timer that will turn off the light
      var pauseDelay = module.Parameter("HomeGenie.TurnOffDelay").DecimalValue;
      Program.RunAsyncTask(()=>{
        Pause(pauseDelay);
        // Check if the light is still on (also module.IsOn could be used)
        if (parameter.DecimalValue > 0)
        {
          module.Off();
          Program.Notify("Turn Off Delay",
            module.Instance.Name + "<br>" +
            module.Instance.Address +
            " switched off.");
        }
      });
    }
  }
  return true;
});
// the program will be running in the background waiting for events
Program.GoBackground();

Javascript

hg.when.moduleParameterChanged(function(module, parameter) {
    // put code here using "module" and "parameter" objects
    return true;
});
hg.program.goBackground();

Python

def module_updated_fn(mod,par):
    # put code here using "mod" and "par" objects
    return True
hg.When.ModuleParameterChanged(module_updated_fn)
hg.Program.GoBackground()

Automation programs can also raise events, so the system (and other programs as well) will acknowledge when a module has been updated. The function Program.RaiseEvent is meant for that.

Reacting to commands

When the user click a button or any other control of a module widget, an API command request is sent to HG. A standard HTTP API request, follows the syntax:
/api/<module_domain>/<module_address>/<command>[/<option_1>/../<option_n>]
For example when clicking On and Off buttons on the widget of a Z-Wave switch with node id 5, the following HTTP requests are made:

/api/HomeAutomation.ZWave/5/Control.On
/api/HomeAutomation.ZWave/5/Control.Off

So if an automation program creates a virtual module of switch type in the domain MyProgram.Domain

Program.AddVirtualModule(
    "MyProgram.Domain",          // <-- domain name
    "1",                         // <-- module address
    "Switch",                    // <-- module type
    "homegenie/generic/switch"); // <-- widget used to display this module

it will be able to handle commands addressed to this module by listening to API calls going to the MyProgram.Domain domain. The When.WebServiceCallReceived helper function is used for this purpose:

CSharp

// handle requests of type http://<hg_address>/api/MyProgram.Domain/...
When.WebServiceCallReceived("MyProgram.Domain", (args) => {
    // All API requests going to the "MyProgram.Domain"
    // will hit this piece of code.
    // e.g. for the "On" command, <args> will contain the string
    // "MyProgram.Domain/1/Control.On", so...
    string[] req = ((string)args).Split('/');
    string domain = req[0], address = req[1], command = req[2];
    // be optimistic
    var response = "{ \"ResponseValue\" : \"Ok\" }";
    // get a reference to the addressed module
    var module = Modules.InDomain(domain).WithAddress(address).Get();
    // process the command
    switch (command)
    {
        case "Control.On":
            // set the status of the module to on
            Program.RaiseEvent(module, "Status.Level", "1", "Switched on");
            break;
        case "Control.Off":
            // set the status of the module to off
            Program.RaiseEvent(module, "Status.Level", "0", "Switched off");
            break;
        default:
            response = "{ \"ResponseValue\" : \"Error\" }";
            Program.Notify("MyProgram", "Unrecognized command received");
            break;
    }
    return response;
});
Program.GoBackground();

So when the user will click the On and Off buttons of the virtual module widget, the code above will raise an event that will update the module Status.Level property with 1 or 0. This event will be also received by the widget that will so update the displayed data accordingly.

Javascript

// handle requests of type http://<hg_address>/api/MyProgram.Domain/...
hg.when.webServiceCallReceived('MyProgram.Domain', function(args) {
    // handle the request here....
    return { ResponseValue : 'Ok' };
});
hg.program.goBackground();

Python

# handle requests of type http://<hg_address>/api/MyProgram.Domain/...
def handle_request_fn(args):
    # handle the request here....
    return "Ok"
hg.When.WebServiceCallReceived("MyProgram.Domain", handle_request_fn)
hg.Program.GoBackground()

Common operations on modules

See the ModulesManager helper class documentation to find out all available functions and properties.

Example - Selecting modules

// Selecting dimmer lights in the same group
var roomDimmers = Modules.InGroup("Living Room").OfDeviceType("Dimmer");
// Set the level of living room dimmers to 50%
roomDimmers.Level = 50;
// Say the average temperature of all sensors
var tempSensors = Modules
    .OfDeviceType("Sensors")
    .WithParameter("Sensor.Temperature");
Program.Say("The average temperature is " + tempSensors.Temperature);

Example - Reading a module parameter

// Getting a single module
var tempSensor = Modules.InDomain("HomeAutomation.ZWave").WithAddress("5").Get();
var temperature = tempSensor.Parameter("Sensor.Temperature");
// .Value property returns a string
Program.Say("Current temperature value is " + temperature.Value);
// .DecimalValue property returns a number (double)
if (temperature.DecimalValue > 24)
{
    Program.Say("Well... it's actually kinda hot day!");
}

Example - Selecting/Testing modules having a given feature

// Selecting all modules with the "turn off" feature
var mods = Modules.WithFeature("HomeGenie.TurnOffDelay");
// Testing if a module have the "turn off" feature
var porchLight = Modules.WithName("My Porch Light").Get();
if (porchLight.HasFeature("HomeGenie.TurnOffDelay"))
{
    var timeout = porchLight.Parameter("HomeGenie.TurnOffDelay").Value;
    Program.Say("The porch light has a turn off timeout of " + timeout);
}
 
Next arrow_right 

More topics

Setup

Documentation

forum Q & A Discussion Forum
HomeGenie
SERVER 1.3 — Documentation