
Open media
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.
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.
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();
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.
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
.
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.
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 ...
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;
*/
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.