JavaScript

Using JavaScript in HomeGenie programs

JavaScript is a ubiquitous scripting language, famous for its role in web development, but also highly capable for automation tasks. HomeGenie leverages JavaScript's flexibility, allowing developers to write automation programs using familiar syntax to control modules, process data, and interact with external services.

Similar to other scripting options in HomeGenie, JavaScript execution relies on an embedded scripting engine. HomeGenie uses Jint, an engine designed to run JavaScript within a .NET environment. This means you do not need to install Node.js or any other JavaScript runtime separately. The necessary engine is included, providing seamless access to the standard HomeGenie API object, referenced globally as hg. Jint also automatically bridges naming conventions, allowing you to use idiomatic JavaScript camelCase for both methods and properties when interacting with the underlying .NET API.

API usage examples: Virtual Modules Demo

Let's explore common API usage patterns using concepts and code structure inspired by the "Virtual Modules Demo" program (found in the homegenie-home-demo package). We'll use the standard JavaScript camelCase convention for all API interactions.

Adding modules

In HomeGenie, a module represents any controllable entity. Programs can dynamically create new modules using hg.program.addModule. The API supports method chaining, allowing concise creation of modules and their features, as shown in this Setup code example:

// Setup Code - Adding Virtual Modules using Method Chaining

const domain = 'HomeAutomation.Demo';
const type = {
  Sensor: 'Sensor',
  DoorWindow: 'DoorWindow',
  Switch: 'Switch'
};

// Add Sensor module and its features
hg.program
  .addModule(domain, '1', type.Sensor)
  .addFeature(domain, type.Sensor, 'Sensor.Temperature', 'Temperature value', 'slider:-50:100:0.5')
  .addFeature(domain, type.Sensor, 'Sensor.Humidity', 'Humidity value', 'slider:10:100:5')
  .addFeature(domain, type.Sensor, 'Sensor.Luminance', 'Luminance value', 'slider:0:1000:1')
  .addFeature(domain, type.Sensor, 'Sensor.MotionDetect', 'Motion detection status', 'slider:0:1:1')
  .addFeature(domain, type.Sensor, 'Status.Battery', 'Battery level', 'slider:0:100:1');

// Add DoorWindow module and its features
hg.program
  .addModule(domain, '2', type.DoorWindow)
  .addFeature(domain, type.DoorWindow, 'Sensor.DoorWindow', 'Door/Window sensor status (open/closed)', 'slider:0:1:1')
  .addFeature(domain, type.DoorWindow, 'Status.Battery', 'Battery level', 'slider:0:100:1');

// Add Switch module and its features
hg.program
  .addModule(domain, '3', type.Switch)
  .addFeature(domain, type.Switch, 'Status.Level', 'Switch status (on/off)', 'slider:0:1:1');

// Run the Main code block after Setup completes
hg.program.run();
Emitting data/values

To update a module's state and notify the system, use the module.emit(...) method on a module helper object.

// Example: Emitting the 'On' status for a switch module (e.g., inside an API handler)

const mod = hg.modules.inDomain('HomeAutomation.Demo').withAddress('3').get();
if (mod) {
    // Emit the new status ('1' for On) using a standard parameter name
    mod.emit('Status.Level', '1');
}

Initializing vs. Emitting: While emit signals a state change, you can set an initial value for a parameter directly (without triggering events) like this: mod.parameter('Parameter.Name').value = 'InitialValue';.

Note on standard types and properties: HomeGenie uses conventional names for common module types (e.g., Sensor, Light, Switch) and the data properties they emit (e.g., Sensor.Temperature, Status.Level). Using these standards ensures compatibility with built-in widgets and features. A detailed reference list will be provided in a dedicated section or appendix.

Adding a custom API handler

Programs can expose web endpoints using hg.api.handle. Register a path prefix and provide a callback function. The "Virtual Modules Demo" uses this to control its virtual switch.

// Main Code - API Handler Registration and Logic (using Virtual Modules Demo structure)

const domain = 'HomeAutomation.Demo';
const demoModules = hg.modules.inDomain(domain);

// (initialize() function called here - see full code in the demo program)

// Handle API calls for this domain
hg.api.handle(domain, function(apiCall) {

  const ac = hg.api.parse(apiCall);
  let responseStatus = 'ERROR';
  const mod = demoModules.withAddress(ac.address).get();

  if (mod && mod.isOfDeviceType('Switch')) {
    switch (ac.command) {
      case 'Control.On':
        mod.emit('Status.Level', '1');
        responseStatus = 'OK';
        break;
      case 'Control.Off':
        mod.emit('Status.Level', '0');
        responseStatus = 'OK';
        break;
      case 'Control.Toggle':
        var currentLevelParam = mod.parameter('Status.Level');
        var currentLevel = currentLevelParam ? currentLevelParam.value === '1' : false; // Use .value
        mod.emit('Status.Level', currentLevel ? '0' : '1');
        responseStatus = 'OK';
        break;
    }
  }

  // Return a JavaScript object directly for the response
  return {
    ResponseStatus: responseStatus,
    // Optional debug info:
    Command: ac.command,
    Address: ac.address
    // ... other fields if needed ...
  };

});

// Keep the handler active after the main script finishes initial setup
hg.program.goBackground();

// (initialize() function definition here - see full code in the demo program)
/* Example snippet from demo's initialize():
function initialize() {
  const multiSensor = demoModules.withAddress("1").get();
  if (multiSensor && multiSensor.instance && multiSensor.instance.name == '') { // Use .instance.name
    multiSensor.instance.name = 'Multi Sensor';
  }
  const temperature = multiSensor ? multiSensor.parameter("Sensor.Temperature") : null;
  if (temperature && temperature.value == '') { // Use .value
    temperature.value = '25.5';
  }
  // ... and so on ...
}
*/

This handler allows controlling the switch via URLs like http://<hg_address>/api/HomeAutomation.Demo/3/Control.On.

Making an API call

JavaScript programs use hg.api.call to interact with other modules/programs.

// Example: Calling the Control.On command for the virtual demo switch

try {
  hg.program.log.info('Attempting to turn on the virtual demo switch'); // log().info()
  var response = hg.api.call('HomeAutomation.Demo/3/Control.On');
  // Access response properties
  hg.program.log.info('API Call Response Status: ' + response.ResponseStatus);
} catch (e) {
  hg.program.log.error('Error calling API: ' + e); // log().error()
}

Local API call optimization: Calls within the same HomeGenie instance are optimized, bypassing the network and allowing direct object passing for maximum performance.

Adding new module features

Programs use hg.program.addFeature to dynamically add settings to the Settings dialog of modules. The "Virtual Modules Demo" uses this to add slider controls for its virtual modules.

// Setup Code Excerpt - Adding Feature as UI Control

const domain = 'HomeAutomation.Demo';
const type = { Sensor: 'Sensor' /*...*/ };

// Add slider feature
hg.program.addFeature(domain, type.Sensor, 'Sensor.Temperature', 'Temperature value', 'slider:-50:100:0.5');
// ... other features added similarly ...

Adding program options

To add global configuration options (appearing in System -> Settings), use hg.program.addOption.

// Example: Adding a text option (not part of Virtual Modules Demo)

var option_name = 'Settings.ApiKey';
var option_description = 'API Key for an external service.';
var default_key = '';

hg.program.addOption(
    option_name,       // Parameter 1: field (name)
    default_key,       // Parameter 2: defaultValue
    option_description,// Parameter 3: description
    'text'             // Parameter 4: type
);

// Reading the option later:
/*
var optionParam = hg.program.option(option_name);
var apiKey = optionParam ? optionParam.value : default_key;
*/

Using .NET objects and types in JavaScript

Thanks to the Jint engine, HomeGenie can expose .NET objects and types directly to the JavaScript environment. The global hg object is the primary example. When calling methods like hg.program.addModule(...) or accessing properties like hg.modules, you are interacting directly with underlying .NET code via the bridge provided by Jint. Jint typically allows using idiomatic JavaScript camelCase for interacting with these exposed .NET members (both methods and properties). While you cannot load arbitrary .NET assemblies, you can seamlessly interact with any .NET functionality explicitly made available by HomeGenie within the Jint engine's scope.

menu_open Content index
forum Q & A discussion forum
HomeGenie
SERVER 1.4 — Documentation