openhab-automator
v0.9.1
Published
A utility toolkit for building automation controllers in openHAB (JS Scripting). The package creates a control Item `Automator_<name>` for one or more of your Items, supports a manual override of automation for N minutes with auto-restore, debouncing, per
Downloads
6
Readme
Automator (openHAB JS)
Languages: English · Українська
A utility toolkit for building automation controllers in openHAB (JS Scripting). The package creates a control Item Automator_<name> for one or more of your Items, supports a manual override of automation for N minutes with auto-restore, debouncing, periodic triggers, safe command dispatch, and a convenient rules API.
The package lets you write openHAB JS rules that build automations which compute the state of target Items from source Items. If a target Item is changed outside the automation rule, the rule pauses for a configured time. The Automator also provides syntactic sugar to keep rules concise.
Features
- Automatic creation of:
- Group
gAutomator - Number
Automator_Heatbeat(heartbeat) - String
Automator_<name>with optionsON / OFF / N minutes
- Group
- Enable/disable automation and temporarily disable for N minutes (then auto-return to
ON). - Handlers (Handler) with:
dependsOf()— declare dependencies and auto-generate state-change triggers.triggers()— arbitrary triggers (including system ones).debounce(seconds)— soft debounce powered by a cron timer.interval(seconds)— periodic invocation.log()— accumulate logs and print them only when commands actually change.
- Safe command dispatch
auto(name, command)with loop protection (cache marks for 5 s). - Convenient state and persistence aggregations via
get(name):ItemName_average_5m,…_minimum_1h,…_maximum_24h,…_sum_1d,…_deviation_30m,…_changed_10m.
- Lifecycle callbacks:
onActivate(),onDisable(),onLog().
Limitations
- One automation per Item. Multiple automations for the same Item will conflict.
- Persistence must be configured in OH for all members of group
gAutomator. The group itself is created automatically when the first automation is created.
Example influxdb.persist:
gAutomator* : strategy = everyChange, restoreOnStartupRequirements
- openHAB 4.x with JS Scripting (
openhab-js). - Persistence enabled (MapDB/JDBC/InfluxDB etc.) for aggregations and history.
- Transformation add-on +
automation.mapfile in${OPENHAB_CONF}/transform/.
Example automation.map (localize to your UI):
ON=Automatic
OFF=Disabled
1HOFF=OFF for 1 h
12HOFF=OFF for 12 h
24OFF=OFF for 24 hInstallation
Place the module file somewhere available to require() and import it:
const automator = require('openhab-automator'); // or a relative path to the fileQuick Start
const { rules, triggers } = require('openhab');
const automator = require('openhab-automator');
// Create an automator for the kitchen light
const m = automator.for('Kitchen_Light')
.label('Kitchen Light — Auto', 'Kitchen')
// Custom options (minutes -> label)
.options({
'30': '30 min OFF',
'120': '2 h OFF',
'720': '12 h OFF'
}, '120') // default manual OFF duration (minutes)
.onActivate(() => {
console.log('Kitchen auto: ON');
})
.onDisable(() => {
console.log('Kitchen auto: OFF');
});
// Logic: turn on if there is motion and it’s dark; turn off if no motion
m.handler(function (event) {
const motion = this.Kitchen_Motion;
const lux = this.Kitchen_Lux_average_5m; // persistence average over 5 minutes
if (motion && lux < 50) return true; // ON for Switch
if (!motion) return false; // OFF
})
.dependsOf('Kitchen_Motion', 'Kitchen_Lux') // declare dependencies
.debounce(5) // soft debounce 5 s
.interval(60) // check every minute
.triggers(triggers.SystemStartlevelTrigger(100)); // custom triggerAfter startup the module creates Automator_Kitchen_Light.
ON— automation active.OFF— automation fully disabled.- A number (e.g.,
120) — disable for 120 minutes, then auto-return toON.
Concepts
Control Item
Automator_<name> joins the groups of the respective controlled Items to inherit UI visibility.
Changing Automator_<name>:
ON→ enable allHandlers.OFF→ disable allHandlers.N(minutes) → disable for N minutes and schedule auto-return toON.
During operation the Item receives automator metadata with a time estimate.
Manual commands
If a manual command is sent to any controlled Item, the automator disables itself (to the mode set by manual() or the default from .options()), so it doesn’t fight manual control.
API
Create a manager
const m = automator.for('ItemA', 'ItemB', ...);Alternative (thanks to Proxy export):
const m = automator.ItemA; // same as for('ItemA')Manager methods
label(label, short?)— label and short value for the UI.groups(string[])— add groups toAutomator_<name>.options(map, manualDefault)— configure selection options (key — minutes, value — label) and default duration for manual disable.manual(mode?)— forceOFFor a number of minutes (mode) without changing options.handler(fn)— create a handler (returnsHandler).- Events:
onActivate(cb),onDisable(cb),onLog(cb).
Properties:
id— the manager rule UID.name— name (concatenation offor(...)with dashes).for— array of controlled Item names.automated—trueifAutomator_<name>isON.
Handler methods
dependsOf(...itemNames)— declare dependencies and automatically addItemStateChangeTriggers for them.Every Item you read via
this.get(...)must be listed independsOf(...)(or be inmanager.for), otherwise an error is thrown.triggers(...openhabTriggers)— add any other triggers.debounce(seconds)— enable debounce; events are pooled until the service cron triggerdebouncefires.interval(seconds)— periodic cron trigger.log(...args)— collect a log; printed only if a command actually changed.
Context inside handler(fn)
fn(event) is called with a Proxy context:
this.get(name)— read an Item or aggregation:- Suffixes supported:
_<average|minimum|maximum|sum|deviation|changed>_<period>
whereperiod:\d+[smhdw](seconds/minutes/hours/days/weeks). - Return types:
Switch→true|false(ON/OFF)Contact→true|false(OPEN/CLOSED)Number,Dimmer→ numberNumber:<unit>→QuantityType(quantityState)- others → raw
state
- Suffixes supported:
this.auto(name, command)— safely send a command with loop protection.- Syntactic sugar:
this["ItemA"] = true≡this.auto("ItemA", true)return true/false/number/string/objectfrom the handler:- boolean/number/string → command for
manager.name - object
{ ItemA: ..., ItemB: ... }→ batch commands
- boolean/number/string → command for
Syntactic Sugar
Inside automation rules, getters are available for all Items the automator depends on or controls. This is essentially a wrapper over items that verifies every Item used by the rule is properly declared in dependencies — and slightly simplifies the code.
If an Item is persisted, you can append a suffix like _average_5m to obtain the corresponding aggregation. Supported suffixes: average | minimum | maximum | deviation | sum | changed.
Examples
1) Ventilation control by CO₂ (with hysteresis and debounce)
automator.for('Vent_Fan')
.label('Vent — Auto', 'Vent')
.options({ '30': '30 min OFF', '120': '2 h OFF' }, '120')
.handler(function () {
const co2 = this.Room_CO2_average_5m;
const fan = this.Vent_Fan;
if (co2 > 900) return true;
if (co2 < 750) return false;
return fan; // hold the current state within the dead zone
})
.dependsOf('Room_CO2')
.debounce(10)
.interval(60);If Vent_Fan is changed manually outside the algorithm (e.g., from the UI), the automation will be disabled for 120 minutes, after which it will automatically re-enable.
The automation will execute (compute the state of Vent_Fan) on Room_CO2 changes or every 60 seconds, BUT not more often than every 10 seconds (soft debounce).
2) Lighting scene by motion and time of day
automator.for('Hall_Light')
.handler(function () {
const motion = this.Hall_Motion;
const isNight = this.Astro_IsNight; // e.g., a Switch from Astro
if (motion && isNight) return 60; // Dimmer: 60%
if (!motion) return 0;
})
.dependsOf('Hall_Motion', 'Astro_IsNight')
.debounce(10);If Hall_Light is changed manually (from the UI or another rule/binding), the automation will be disabled for 720 minutes (the default when options aren’t set). A soft 10-second debounce is active, so motion updates are applied no more frequently than every 10 seconds.
UI
A list of created automations with their current state:
component: oh-repeater
config:
cacheSource: false
fetchMetadata: stateDescription, automator
filter: =(items.Automator_Heatbeat.state !== null) && true
for: item
fragment: true
groupItem: gAutomator
sourceType: itemsInGroup
visible: =(items.Automator_Heatbeat.state !== null)
slots:
default:
- component: oh-label-item
config:
action: options
actionItem: =loop.item.name
after: =('' + items.Automator_Heatbeat.state) && (items[loop.item.name].state ==
"ON")?'Automatic':((items[loop.item.name].state ==
"OFF")?'Disabled':('left '+loop.item.metadata.automator.value))
icon: =('' + items.Automator_Heatbeat.state) && (items[loop.item.name].state ==
"ON")?'material:task_alt':((items[loop.item.name].state ==
"OFF")?'material:stop':'material:pause')
iconColor: =('' + items.Automator_Heatbeat.state) &&
(items[loop.item.name].state ==
"ON")?'green':((items[loop.item.name].state == "OFF")?'red':'orange')
item: =('' + items.Automator_Heatbeat.state) && loop.item.name
subtitle: =loop.item.label
title: =('' + items.Automator_Heatbeat.state) &&
loop.item.metadata.stateDescription.value?loop.item.metadata.stateDescription.value:loop.item.state
A button that shows how many automations are currently paused or disabled:
component: f7-button
config:
class: button-compact
fill: true
iconF7: play
iconSize: 32px
popupOpen: "#automator"
text: AUTO
slots:
default:
- component: oh-repeater
config:
for: counter
fragment: true
groupItem: gAutomator
sourceType: itemsInGroup
slots:
default:
- component: f7-badge
config:
color: yellow
textColor: black
tooltip: Disabled automations
visible: =(loop.counter_idx == 0) && (loop.counter_source.filter(i =>
items[i.name].state!='ON').length > 0)
slots:
default:
- component: Label
config:
text: =loop.counter_source.filter(i => items[i.name].state!='ON').length
Configuring UI Options
The .options(map, manual) method accepts:
map:{ "<minutes>": "<label for UI>" }manual: number of minutes (string/number) used as the default manual disable duration.
By default the package adds:
.options({
'60': actions.Transformation.transform('MAP', 'automation.map', '1HOFF'),
'720': actions.Transformation.transform('MAP', 'automation.map', '12HOFF'),
'1440': actions.Transformation.transform('MAP', 'automation.map', '24OFF')
}, '720');Under the Hood (good to know)
- A manual command to a controlled Item → the Manager disables all Handlers (manual mode).
If the command matches the last automatic one (recognized viacache.privatemark), it’s not treated as manual. - Disabling “for N minutes” creates a
setTimeoutwhose ID is cached; on logic restart the timer is restored from persistence history (previousState). Automator_HeatbeatreceivesDate.now()as a heartbeat.- Cron expressions:
0/<sec> * * ? * * *forsec < 600 0/<min> * ? * * *for60 ≤ sec < 3600
Troubleshooting
not persisted?in logs — enable persistence forAutomator_<name>or the controlled Items.automator handler not depends of <Item>— add this Item todependsOf(...).- Debounce “doesn’t fire” — ensure the
Handlerhas at least one other trigger that produces events;debounce()only flushes them via the service cron.
Feedback
Suggestions/questions about the API, additional aggregates (median, percentile, etc.), or support for compound types are welcome.
