Is there a smart way to check the state of an item before sending command to it?

Hi:

Would like to avoid sending command to an item when the item is already in that state. I can create rules in DSL to check if the item is already in that state before sending command, but it’s a bit cumbersome to do it for all items. Is there a smart way to do it?
The reason why I’d like it is to now I have some rules trigger to turn off the lights when outdoor light level above certain threshold, it just keeps sending OFF command to lights when the lights are already off, so I get lots of events like:

2025-02-05 15:46:21.062 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:46:21.071 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:46:30.970 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:46:30.974 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:51:26.348 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:51:26.355 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:51:30.463 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:51:30.484 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:56:20.031 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:56:20.036 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:56:41.533 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:56:41.533 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF
2025-02-05 15:59:47.686 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Xiaomi_gateway_light_miio' received command OFF
2025-02-05 15:59:47.691 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'Xiaomi_gateway_light_miio' predicted to become OFF

Any suggestion is appreciated!

Hi,
I looked into this myself some time ago, as I sometimes use devices (Homematic) that can only receive a certain number of commands per hour, otherwise they go offline (duty cycle).

Unfortunately, I didn’t come up with another solution at the time, which you probably already have in mind, namely working in a DSL rule with an additional if loop:

if (Esszimmerlicht_Schalter.state!=OFF) {
    Esszimmerlicht_Schalter.sendCommand(OFF)
}

Probably works similarly for jruby or JS.

Maybe you could solve it a bit more elegantly using groups:

rule "Turn off lights only when needed"
when
    Item Outdoor_Light_Level changed
then
    if (Outdoor_Light_Level.state > 200) {  
        gLights.members.filter[light | light.state != OFF].forEach[light |
            light.sendCommand(OFF)
        ]
    }
end

or in JS - not tested:

rules.JSRule({
    name: "Turn off lights only when needed",
    triggers: [triggers.ItemStateChangeTrigger("Outdoor_Light_Level")],
    execute: (event) => {
        if (items["Outdoor_Light_Level"] > 200) {
            itemsInGroup("gLights").forEach(light => {
                if (items[light] !== "OFF") {
                    events.sendCommand(light, "OFF");
                }
            });
        }
    }
});

Or what I came up with at the end is not to work directly with the light threshold but to create a proxy item that only switches once when the threshold is exceeded.
Then the light switch is only triggered once.

I have something similar in place:

rule "allgemeine_system-13"
//Tageszeiten umschalten auf Tag
when
  Item Wetterstation_Helligkeit changed or
  Item Tag_Helligkeit changed
then
  if ((Wetterstation_Helligkeit.state > Tag_Helligkeit.state) && (Tageszeiten.state == 'Nacht') && (now.getHour() <= 9) && (tag_timer_eins === null)) {
      logInfo ("RULE", "Tagtimer startet")
      tag_timer_eins = createTimer(now.plusMinutes(1), [|
                tag_timer_eins = null
                Tageszeiten.sendCommand('Tag')
            ])
  }
end

Hope it helps.

1 Like

JS:

items.Xiaomi_gateway_light_miio.sendCommandIfDifferent("OFF")

In JRuby, you’d add .ensure between the item and the command

Xiaomi_gateway_light_miio.ensure.off

Or if you want to do a whole bunch of commands in the same way, just add ensure_states! at the top of the script, e.g.

ensure_states!

# From hereon, all commands will now check to ensure 
# that item's state is different before sending a command
Xiaomi_gateway_light_miio.off
SomeOtherItem.off

AGroup.off

[Item1, Item2, Item3].off

PS: In JRuby you can send a command the “old fashioned way” like:

Xiaomi_gateway_light_miio.command(OFF)

# That's the same as using the shorthand (and preferred)
Xiaomi_gateway_light_miio.off
3 Likes

Thanks I’m still on DSL unfortunately :frowning:

Thank you for the suggestion!

This could be a good excuse to migrate just this particular rule over.

1 Like

Haha, it’s always hard to take the first step.
It was a pain for me to learn all the tricks of dsl (I used to work as Java developer), guess will be even harder to pickup another language and transform my 3000 lines of dsl code

And to start with jrbuy or js is not to hard, you just have to download the add-on:

  • JavaScript Scripting
  • JRuby Scripting
    from the add-on-store.

If you are using text based config for rules you just have to add new rules at:

  • js: Openhab-conf/automation/js/.js
  • jruby: Openhab-conf/automation/ruby/.rb

And if you use vsc be sure to install the copilot extension, even the free version is a real help in both languages ​​at the beginning.

1 Like

Learning new things is always hard at first! But you can ask for help here. You can always ping me if you have any questions or need help with jruby. With jsscripting, there are plenty of people who could help.

That’s because dsl is painful!

I don’t know about javascript, but there’s a ton of things in jruby that would make your life so much easier when writing rules compared to dsl, and your rules will be much shorter and easier to read.

Assuming these lights report their state back to OH, you can and probably should turn of autoupdate on those Items. Then the Items will only change state when the device says it’s changed state and all those “predicted to become” stuff will go away. That solves half your problem by itself. :wink:

As @jimtng points out, in JS as item.MyItem.sendCommandIfDifferent("ON") only sends on the command if the command is different from the Item’s current state.

A more canonical way to render this in JS Scripting is:

items.gLights.members.filter(light => light.state !== "OFF")
                     .forEach(light => light.sendCommand("OFF");

Using events to send commands is an old Nashorn convention. items["ItemName"] as a representation of the Item’s state is also an old Nashorn convention. Neither should be used in JS Scripting and the openhab-js library should be used instead. openhab-js provides a more complete and consistent environment and lets you work in pure JS instead of a mix of Java and JS.

As a case in point items[light] in the old JSR223 Nashorn paradigm, is the raw Java State Object. For a Switch it will be an OnOffType. OnOffType.OFF is never going to == the String "OFF", so I don’t think that line will actually work and it will always return true because items[light] will always not equal "OFF".

To provide a more sane working environment, openhab-js replaces items, a dict of Item names and their states with a JS interface to the ItemRegistry and when you get an Item from that you get a JS representation of the Item which gives you methods to get the state as a string, number or Quantity, send commands or post updates, access persistence, Item metadata, etc. This should be your access point for all things Item related in a JS rule. It has done the same for rules, and things.

No, it will probably be easier because you’ve already learned the hard stuff, what sorts of ways one can interact with OH and you already know some programming. For everything else, any of the other options provide a more complete programming environment (classes, functions, libraries) without a broken type system, and they provide access to the full OH API.

All you really need is a 101 level tutorial on the language you choose (if that, most are pretty easy to pick up if you already know programming) and the ability to search the docs for the automation add-on of choice for how to do the same thing with OH in that language. Both JS and jRuby have complete and comprehensive docs. It’ll be a little slow at first but quickly you’ll start to remember things and need to search less frequently. But even I have to search the docs frequently.

I think the syntax of JS will be closer to what you already know but people who use jRuby love it.

I’d say the same thing for JS. When I moved everything over I doubled the number of rules (I.e. I’m doing more) and halved the lines of code. And I never have to cast a thing because Rules DSL’s broken type system failed to figure it out.

Thanks for the very detailed explanation.
I did think to move to JS once, I used JS for frontend development and I’m not a big fan to be honest. I think it might be more interesting if OH provides full Python environment with integration to all the good ML/AI from Python community - I can imagine one day I don’t have to keep improving my rules to optimise energy warming my house, maybe AI can do it automatically :slight_smile:

Anyways, I find a quick solution in DSL for my issue and would like to share in case anybody could be interested (It works for Switch, Dimmer and Color items):

val controlLight =  [String light, boolean turnOn  |
  var item = ScriptServiceUtil.getItemRegistry.getItems.findFirst[ item | item.name == light ]
  if (item !== null){
    if(turnOn && item.getStateAs(OnOffType) == OFF){
      item.sendCommand(ON)
    } else if (!turnOn && item.getStateAs(OnOffType) == ON){
      item.sendCommand(OFF)
    } 
  }
]

ChatGPT - Bindings | openHAB is a start with people looking to add support (or alternative add-ons) for Ollama and others. You don’t need Python to get integration with AI.

1 Like

Where do I find this in the documentation??

sendCommandIfDifferent

Scroll down to where Item is documented. It’s the 20th bullet point in the list (as you would expect, Item has a lot of methods and properties).

  • .sendCommand(value): value can be a string, a time.ZonedDateTime or a Quantity
  • .sendCommandIfDifferent(value) ⇒ boolean: value can be a string, a time.ZonedDateTime or a Quantity
  • .sendIncreaseCommand(value) ⇒ boolean: value can be a number, or a Quantity
  • .sendDecreaseCommand(value) ⇒ boolean: value can be a number, or a Quantity
  • .sendToggleCommand(): Sends a command to flip the Item’s state (e.g. if it is ‘ON’ an ‘OFF’ command is sent).

Or just open that page and Ctrl+F / Cmd+F (find in page).

There are several bundles available to script and make rules with pure Java:

  • JSR223 Scripting in Java - Beta4. Bundle with very simple and small codebase, so easily modifiable. Not available on the marketplace, but as JAR bundle.
  • SmartHome/J Java Rule Automation. Great and probably very stable, not used by its maintainer anymore (so probably no evolution?). Available on the external SmartHomeJ addon marketplace.
  • Java223 (disclaimer, I’m the author). Available on the marketplace. Still in beta and young, but designed to have every functionnalities of the two aforementioned (and more). Yes, I want feedback :wink:
  • JRule. The only one not fully compatible with all openHAB concepts and integration (as it’s not JSR223 compliant), but the most used, with a big community, many helper functions, and even more functionnalities.

Hi:
Thanks for the links - looks like both Jython and Python3 scripting are basically running Python language with JVM, maybe the reason is that OH is written in Java. However I don’t know how it compares to running Python with interpreter, and support for additional Python libs.

I resolved the multiple OFF commands issue in DSL using the hysteresis profile on a helper item. When the sensor’s illuminance goes below 1000 lux a single ON command is sent to the device. The lights stay ON until the illuminance goes above 4000 lux when a single OFF command is sent to the device.

Item:

Switch LandscapeAutomation "Landscape Automation %s" {channel="weatherflowsmartweather:tempest:HB-00999999:ST-00999999:illuminance" [profile="system:hysteresis", lower="1000 lx", upper="4000 lx",  inverted="true"]} 

Rules:

rule LandscapeLightsOn
when Item LandscapeAutomation changed to ON
then logInfo("LandscapeLightsOn", "LandscapeLights changed to ON")
	gLandscape.members.forEach[ i | i.sendCommand(ON) ]
end

rule LandscapeLightsOff
when Item LandscapeAutomation changed to OFF
then logInfo("LandscapeLightsOff", "LandscapeLights changed to OFF")
	gLandscape.members.forEach[ i | i.sendCommand(OFF) ]
end

Yes. But if that’s a problem for you for some reason, there’s HABApp.

But these two approaches are the only approaches. Either it runs on the JVM or it runs separate from OH and interacts through the API.

Jython can support pypy libraries, but it’s awkward.

Assuming the new Python add-on works like the JS one, it should support most third party libraries in the standard way. There are a few limitations with JS which can break some npm node modules but I don’t think Python will have the same limitation.

Why not just gLandscape.sendCommand(OFF) ?

And since you just forward the new LandscapeAutomation state this can be just one rule.

rule LandscapeLightsOff
when Item LandscapeAutomation changed
then logInfo("LandscapeLightsOff", "LandscapeLights changed to " + newState)
	gLandscape.sendCommand(newState)
end

Good point. I’m still learning openHAB :slight_smile:

Yeah, I learned about newState and previousState a few months ago, but I didn’t go back and update my rules.