Multi press and hold to dim js-script for (thing-) buttons

Hey there here I’m posting a multi-press and hold to dim pattern script for buttons because I didn’t found a fitting solution.

What it does

With a single button you can provide as many actions as you want.
You can set different actions for a single press, double press, tripple press and so on.
Additionally the script provides a hold to dim functionallity. Can be used for dimming or customized for rollershutters, etc.


  • Javascript Automation addon (ECMA2011)
  • A button (momentary switch) item

How to use

In MainUI create a rule where your button is the trigger.
Choose Run Script as action and choose ECMAScript Edition 11.
Paste my script.
You have to do some mandatory configurations inside the script. See the comments “User Configuration”.

My setup

  • openHAB 3.4.2 on a RPI4 4GB Ubuntu 20.04 arm64
  • A hardware wall button (momentary switch) wired up with a ShellyI4.
  • A Ikea Tradfri lightbulb (dimming and colortemperature).

Known issues

  • Doesn’t work without an trigger item due to the event requirement.
  • Doesn’t perform well when the script is unloaded. I recommend to higher the rule threads:
    org.eclipse.smarthome.threadpool:ruleEngine=20 (default is 5).


Here it is, feedback is welcome!

The MIT License (MIT)
Copyright © 2023 Felix Krämer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// User Customisations:

// Delays for:
var delayMulti = 500;   // registering multiple pushes
var delayDimStep = 700; // pause between in-/decrease steps. 

// The item to dim:
var dimItem = items.getItem('mytradfribulbbrightness'); // Ikea tradfri bulb

// The button item; in my case a button (electrical button with shellyI4)
// configured as switch (to send ON and OFF states).
var btnItem = items.getItem(event.itemName);

// Change the behaviour for different button presses:
function customizedFunction(n){
    switch (n){
      case 1: //Example: 1 press to toggle light on/off
        dimItem.sendCommand(dimItem.state == 0 ? "ON" : "OFF");
      case 2: //Example: 2 presses to switch between color temperatures
        let tempItem = items.getItem('mytradfribulbcolortemperature')
        var temp;
        if (tempItem.state == 0)
          temp = 33;
        else if (tempItem.state == 100)
          temp = 0;
          temp = 100;
      // Add your Additional push count here like:
      case 3:
        // do something
        console.log('Default case');
// End of user customisations.


var {INCREASE, DECREASE} = require("@runtime");

var cacheId = "TestBtnMultiPressPattern5742";

var storage = cache.private.get(cacheId, () => ({
  'btnTimerId' : null,
  'count' : 0,
  'dimUp' : false,
  'dimTimer' : null,
  'dimTimerId' : 'TestBtnMultiPressPattern8956DimTimerId',

Use 'Function Generators' to avoid variable changing while timeout.
function dimFunction() {
  //early exit
  if (btnItem.state.toString() == "OFF"){
    // Break dim loop
    // reset storage
    storage.btnTimerId = null;
    storage.count = 0;
    storage.dimTimer = null;
    cache.private.put(cacheId, storage);
    console.debug('Button OFF, dimming ended.');
  // Set dim direction
  if (dimItem.state == 100) {
    console.debug('dim Down');
    storage.dimUp = false;
    cache.private.put(cacheId, storage);
  } else if (dimItem.state == 0) {
    console.debug('dim Up');
    storage.dimUp = true;
    cache.private.put(cacheId, storage);
  // Dimming step
  if (storage.dimUp == true){
    console.debug('dim INCREASE');
  } else {
    console.debug('dim DECREASE');
  // reschedule dimmer to loop

function timerEnd(n) {
  console.debug("timerEnd triggered, state=" + btnItem.state.toString())
  if (btnItem.state.toString() == "OFF" ) { // Short press cases'Button pressed ' + n + 'times');
    if (storage.btnTimerId === undefined) {
      console.debug("timer === undefined");
    } else {
      // reset storage
      storage.btnTimerId = null;
      storage.count = 0;
      cache.private.put(cacheId, storage);
      console.debug("timer canceled");
  } else { // hold cases
    console.debug("Btn Timer stopped, hold action.")
    storage.count = 0;
    // set dim timer
    if (storage.dimTimer === null) {
      storage.dimUp = !storage.dimUp
      storage.dimTimer = actions.ScriptExecution.createTimer(storage.dimTimerId, time.toZDT(), dimFunction);
    cache.private.put(cacheId, storage);

console.debug("script called");

if (event.itemState.toString() == "ON") {
  if (storage.btnTimerId !== null){

  console.debug("Click number " + storage.count + " detected.");
  storage.btnTimerId = setTimeout(() => timerEnd(storage.count), delayMulti);
  cache.private.put(cacheId, storage);

This looks quite nice. I was working on a similar setup when I found your post. I will try it out and let you know how it goes! Thanks for the contribution!