npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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 options ON / OFF / N minutes
  • 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, restoreOnStartup

Requirements

  • openHAB 4.x with JS Scripting (openhab-js).
  • Persistence enabled (MapDB/JDBC/InfluxDB etc.) for aggregations and history.
  • Transformation add-on + automation.map file 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 h

Installation

Place the module file somewhere available to require() and import it:

const automator = require('openhab-automator'); // or a relative path to the file

Quick 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 trigger

After 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 to ON.

Concepts

Control Item

Automator_<name> joins the groups of the respective controlled Items to inherit UI visibility.
Changing Automator_<name>:

  • ON → enable all Handlers.
  • OFF → disable all Handlers.
  • N (minutes) → disable for N minutes and schedule auto-return to ON.

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 to Automator_<name>.
  • options(map, manualDefault) — configure selection options (key — minutes, value — label) and default duration for manual disable.
  • manual(mode?) — force OFF or a number of minutes (mode) without changing options.
  • handler(fn) — create a handler (returns Handler).
  • Events:
    • onActivate(cb), onDisable(cb), onLog(cb).

Properties:

  • id — the manager rule UID.
  • name — name (concatenation of for(...) with dashes).
  • for — array of controlled Item names.
  • automatedtrue if Automator_<name> is ON.

Handler methods

  • dependsOf(...itemNames) — declare dependencies and automatically add ItemStateChangeTriggers for them.

    Every Item you read via this.get(...) must be listed in dependsOf(...) (or be in manager.for), otherwise an error is thrown.

  • triggers(...openhabTriggers) — add any other triggers.
  • debounce(seconds) — enable debounce; events are pooled until the service cron trigger debounce fires.
  • 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>
      where period: \d+[smhdw] (seconds/minutes/hours/days/weeks).
    • Return types:
      • Switchtrue|false (ON/OFF)
      • Contacttrue|false (OPEN/CLOSED)
      • Number, Dimmer → number
      • Number:<unit>QuantityType (quantityState)
      • others → raw state
  • this.auto(name, command) — safely send a command with loop protection.
  • Syntactic sugar:
    • this["ItemA"] = truethis.auto("ItemA", true)
    • return true/false/number/string/object from the handler:
      • boolean/number/string → command for manager.name
      • object { ItemA: ..., ItemB: ... } → batch commands

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

alt text

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

alt text


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 via cache.private mark), it’s not treated as manual.
  • Disabling “for N minutes” creates a setTimeout whose ID is cached; on logic restart the timer is restored from persistence history (previousState).
  • Automator_Heatbeat receives Date.now() as a heartbeat.
  • Cron expressions:
    • 0/<sec> * * ? * * * for sec < 60
    • 0 0/<min> * ? * * * for 60 ≤ sec < 3600

Troubleshooting

  • not persisted? in logs — enable persistence for Automator_<name> or the controlled Items.
  • automator handler not depends of <Item> — add this Item to dependsOf(...).
  • Debounce “doesn’t fire” — ensure the Handler has 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.