Short (multi) & hold press to turn on/off/dim items (JSScripting)

Attention!

This script was converted into the Rule Template.
You can now add this from OpenHAB Marketplace.

Template on Marketplace: Switch and Dimmers Rule.

Or you can add this manually. So, read further.

I tried to make a universal script for lights and dimmers based on this script from Felix KrĂ€mer. I’ve modified it for my needs.

Description

This script allows you to unify the control of light through switches without making changes to the script every time you need to change the assignment of switches and target lamps.

Each switch is assigned its own light source via Custom Metadata, Namespace: linkedItem.

Requirements:

  • Javascript Automation Add-in (ECMA2011)
  • Latest openhab-js installed
  • Buttons configured as Switches or Contacts (see “switchState” variable below) with custom metadata (read further)

README

It is necessary to place all switches in one group.
Next, create a rule where the trigger has to be a change in the status of one of the group members from the status OFF (OPEN/OFF) to ON (CLOSED/ON).

triggers:
  - id: "1"
    configuration:
      groupName: gAllSwitches
      state: CLOSED
      previousState: OPEN
    type: core.GroupStateChangeTrigger

See ‘switchState’ variable in script and adjust that for your needs. In my config I’m using switches as Contact type, because don’t need to control them from OH UI.

You also need to add custom metadata for the switches with the namespace “linkedItem” with the name of the light/dimmer item name that it will control.

Principle of operation

The script supports switches and dimmers. The detection occurs automatically, according to the type of lighting element (SwitchItem or DimmerItem).

One short press:

  • switching on/off conventional lamps (Switch).
  • turning on / off dimmers (Dimmer) with the restoration of the previous dimming level.

Long press:

  • switching on/off conventional lamps (Switch).
  • dimmers increase to 100% / decrease to ‘minDim’ values. If you release and press the button again, the dimming direction changes.

Additionally, you can register any other actions for double / triple, etc. clicks by modifying the “shortPressAction” function.

In the variables at the beginning of the script, you can set other time delays for registering short presses, dimming speed, and so on.

The script itself must be added to the Action field - select “ECMAScript 262 Edition 11 (application/javascript; version=ECMAScript-2021)”

/*
The MIT License (MIT)
Copyright © 2023 LazyGatto, 
based on Felix KrÀmer script
https://community.openhab.org/t/multi-press-and-hold-to-dim-js-script-for-thing-buttons/144589
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.

Requirements:
Javascript Automation addon (ECMA2011)
Latest openhab-js installed
A button items configured as switches or contacts (see below 'switchState' variable) with custom metadata (read further)

README:
You have to place all your switches/contacts into one group, which one will be a trigger.
Also you have to add custom metadata for all your switches/contacts 'linkedItem', with item name of light/dimmer it will control.

Script supports switches and dimmers.
For one short press:
- turn ON/OFF switches 
- turn ON/OFF dimmers with restoration of previous level of dimming.
For long press:
- switches will turn ON/OFF
- dimmers will increase to 100 / decrease to minDim values, stop, press and hold again button will revert dimming direction.

You can provide any custom actions for double/triple/etc presses by customizing 'shortPressAction' function and 'delayMulti'
variable to detect multiple buttom presses.
*/

// ==== Configuration ====

// Delays:
var delayMulti = 350;   // registering multiple pushes
var delayDimStep = 100; // pause between in-/decrease steps
var dimStep = 2; // step to increase/decrease dimming level for one iteration
var minDim = 4; // minimum level for dimming

var switchState = {
  ON: 'ON', // CLOSED for Contact Type
  OFF: 'OFF' // OPEN for Contact Type
}

// ==== END Configuration ====

var switchTriggered = items.getItem(triggeringItem.name);

// Change the behaviour for different button presses:
// stor.pressCount - button pressed times
function shortPressAction(stor) {

  switch (stor.pressCount) {

    // 1 press to toggle light on/off
    case 1:

      switch (stor.linkedItem.type) {

        case 'SwitchItem':
          stor.linkedItem.sendCommand(stor.linkedItem.state.toString() == "OFF" ? "ON" : "OFF");
          break;

        case 'DimmerItem':
          var dimToSet = null;
          if (stor.linkedItem.state == 0) {
            dimToSet = stor.dimPreviousState;
          } else {
            stor.dimPreviousState = stor.linkedItem.state;
            dimToSet = "OFF";
          }
          stor.linkedItem.sendCommand(dimToSet);
          break;
      }

      break;

    // 2 presses to switch between color temperatures
    /*
    case 2:
      var temp;
      if (linkedItem.state == 0)
        temp = 33;
      else if (linkedItem.state == 100)
        temp = 0;
      else
        temp = 100;
      linkedItem.sendCommand(temp);
      break;
    */

    // Add your Additional push count here like:
    /*
    case 3:
      // do something
      break;
    */

    default:
      console.log('Default case');
  }
}

function resetStorage(stor) {
  stor.btnTimer = null;
  stor.pressCount = 0;
  stor.dimTimer = null;
  cache.private.put(stor.cacheId, stor);
}

function dimItem(stor) {

  var dimToSet = null;

  //early exit
  if (stor.switchTriggered.state.toString() == switchState.OFF) {
    resetStorage(stor);
    return;
  }

  if (stor.dimUp == true) {
    dimToSet = Number(stor.linkedItem.state) + dimStep;
    if (dimToSet > 100) {
      dimToSet = 100
      resetStorage(stor);
    }

  } else {
    dimToSet = Number(stor.linkedItem.state) - dimStep;
    if (dimToSet < minDim) {
      dimToSet = minDim
      resetStorage(stor);
    }

  }
  stor.linkedItem.sendCommand(dimToSet);

  if (stor.dimTimer) {
    stor.dimTimer.reschedule(time.toZDT(delayDimStep));
  }
}

function getPressCountTimer(stor) {

  if (stor.switchTriggered.state.toString() == switchState.OFF) { // Short press cases

    shortPressAction(stor);
    resetStorage(stor);

  } else { // Hold cases

    resetStorage(stor);

    switch (stor.linkedItem.type) {

      case 'SwitchItem':
        stor.linkedItem.sendCommand(stor.linkedItem.state.toString() == "OFF" ? "ON" : "OFF");
        break;

      case 'DimmerItem':
        // Set dim direction
        if (stor.linkedItem.state > 90) {
          stor.dimUp = false;
        } else if (stor.linkedItem.state < 10) {
          stor.dimUp = true;
        } else {
          stor.dimUp = !stor.dimUp
        }

        if (stor.dimTimer === null) {
          stor.dimTimer = actions.ScriptExecution.createTimer(stor.dimTimerId, time.toZDT(), () => { dimItem(stor) });
        }
        cache.private.put(stor.cacheId, stor);
        break;
    }

  }
}

if (switchTriggered.state.toString() == switchState.ON) {
  var funcGenerator = function (switchTriggered) {
    var cacheId = switchTriggered.name + '_Id';
    var linkedItem = items.getItem(switchTriggered.getMetadata("linkedItem")['value']);
    var stor = cache.private.get(cacheId, () => ({
      'cacheId': cacheId,
      'switchTriggered': switchTriggered,
      'linkedItem': linkedItem,
      'btnTimer': null,
      'pressCount': 0,
      'dimTimer': null,
      'dimTimerId': linkedItem.name + '_dimTimerId',
      'dimUp': false,
      'dimPreviousState': 100,
    }));
    stor.pressCount++;
    if (stor.btnTimer !== null) {
      clearTimeout(stor.btnTimer);
    }
    stor.btnTimer = setTimeout(() => getPressCountTimer(stor), delayMulti);
  }
  funcGenerator(switchTriggered);
}

If you need to separately display information about all switches and what light sources are linked to them in the metadata, you can create a separate widget that will show the necessary information in a list.

uid: widget_96f79b9179
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: May 23, 2023, 10:53:24 AM
component: f7-card
config: {}
slots:
  default:
    - component: oh-list
      config:
        mediaList: true
      slots:
        default:
          - component: oh-repeater
            config:
              fragment: true
              for: item
              sourceType: itemsInGroup
              groupItem: gAllSwitches
              fetchMetadata: linkedItem
            slots:
              default:
                - component: oh-list-item
                  config:
                    accordionList: true
                    icon: ='oh:' + loop.item.category
                    title: =loop.item.label
                    subtitle: >
                      ="item Name: " + loop.item.name
                    footer: >
                      ="linked Item: " + loop.item.metadata.linkedItem.value
                    after: =loop.item.state

Update

  • 2023-05-24:
    • cache switched from shared to private
    • changed saving dimPreviousState to store all states, including changes made from UI.
2 Likes

This would be much easier for end users to find, install, and use if it were published as a rule template on the marketplace. That would let the avoid the need to create the rule, triggers and actions manually. It’s already a UI rule so you are most of the way there already. Your constants at the top can be parameters the user fills out when instantiating the rule from a tempalte.

See How to write a rule template for a tutorial, check out the examples on the marketplace, or ask here if you run into trouble.

Beyond that, I’d recommend using the private cache as opposed to the shared cache. I can’t imagine where some other rule would care bout these variables. The private cache is specific to this one script so there is no chance some other rule will pop up and overwrite your variables.

2 Likes

Tried to make all as you said :grinning:

You’re welcome to test it.

I don’t have any switches or dimmers where I need this behavior but I was able to install and instantiate a rule based on the template. :partying_face:

Thanks!

Just one little note that in OH 4 it will just be application/javascript for the script language and Nashorn JS will be application/javascript; version=ECMAScript-5.1.

If at any point you need to post a different version (e.g. for OH 4) you can put version ranges in the title of the post to indicate which versions of OH it supports. You can see examples in the other posts on the marketplace. I rewrote most of my rule templates for OH 4 so there are two versions of each, one for OH 3 and the other for OH 4.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.