Having fun with Semantic Model in JRuby

I love working with Semantic Model in JRuby. Just today, we came up with an idea to tell Google / Alexa to “Turn off Other Lights”. This will quickly turn off lights in all the other rooms where no movements have been detected in the past 10 minutes.

Normally some rooms have much longer timeouts, e.g. 60 minutes. This “manual” command lets us turn them off immediately.

So here’s the rule, utilising the Semantic Model to find all Locations with motion sensors (Tagged as Presence) and getting their last_update. Some locations can have multiple motion sensors, so we take the “max” (i.e. the one with the most recent last_update) to represent the last motion in that location.

Other_Lights.on # Turn it on by default / on startup

rule "Turn off lights where there are no recent movements" do
  received_command Other_Lights, command: OFF
  run do
    Other_Lights.on

    locations_with_no_motion = items.locations.select do |location|
      # Select Locations without Presence sensors
      # If it has Presence sensors, select the ones where the latest motion detected before 10 minutes ago
      latest_motion = persistence(:influxdb) do
        location.equipments.members.points(Semantics::Presence).map(&:last_update).compact.max
      end
      next true unless latest_motion

      latest_motion < 10.minutes.ago
    end

    power = locations_with_no_motion.members
                                    .equipments(Semantics::Lightbulb)
                                    .members
                                    .points(Semantics::Power)
                                    .select(&:on?)

    next if power.empty?

    locations = power.map(&:location).uniq

    logger.info "Turn off lights in #{locations.map(&:label).to_sentence}"
    power.off
  end
end
2 Likes

I am trying to find out whether all blinds in a certain room are opened. I do not like my current approach, is there a better one which checks for OPEN?

What I am currently doing:

items.locations(Semantics::Bedroom).members.equipments(Semantics::Blinds).sum(&:state) == 0

I mean, it looks like it works, but I am sure it could be a little bit more beautiful.

items.locations(Semantics::Bedroom).members.equipments(Semantics::Blinds).open?

does not work unfortunately.

Semantics::Blinds).all?(&:open?)

Assuming Your blinds equipments are items, right?
Open? Is for contacts . If they are roller shutters, might need to use up?

That did not really work as UP is a command but not a state. For now I stay with suming up the states and checking for 0. But thanks anyway.

Edit: It works as expected. Thank you. :slight_smile:

By the way: Where is the syntax like all? and all the other magic which can be performed with groups regarding aggregation documented? Some things I found here but all? as an example I could not find.

.up is to send a command, the equivalent of .command(UP)

.up? is to query the item’s state. It is the equivalent of Item.state == UP which returns a boolean true or false

The methods ending with a question mark such as up?, on?, closed?, etc, are called predicate methods. For item state, the relevant predicates are available depending on the state type. In general you can expect it to be available using the state name in lowercase but if you’re curious, you can see here: Module: OpenHAB::Core::Types::State — openHAB JRuby click into each of the sub-types under “Included in”.

For things like all?, or any? - these are a part of what makes Ruby coding so nice. They are actually built in methods of standard Ruby Array, Hash, and Enumerable. Look on the left side where it says “methods”. Notice the method names start with a # character. That tells you that those methods are instance methods. The ones that start with a . character means they are class methods (or in Java terminology: static methods). So you can search for the method e.g. all? quickly on the page by using your browser’s find shortcut key (Cmd+F on Mac) and search for #all?.

Many things are arrays in the library, for example MyGroup.members is an array. By default it gives you all its members as an array. So

MyGroup.members.each do |item|
  logger.info "Member item name: #{item.name}"
end

# Because item.on? returns true if the item is in the ON state, you could use that as a filter
MyGroup.members.select(&:on?) # This gives you an array of member items that are in the ON state

# The above is a shortcut syntax called "pretzel-colon" (look it up) for:
MyGroup.members.select { |item| item.on? }

There are many many other ways of using this syntax, once you’re used to it, you’ll love it.

Another example: turn on all lights in the room on motion detection

rule do
  changed Motion_Sensors.members, to: ON
  run do |event|
    controls = event.item.location.equipments(Semantics::Lightbulb).members.points(Semantics::Light)
    controls.each(&:on) # Call the `on` method for each array member (which is an openhab Item)
  end
end

As you see, controls above is an array of Items, and .each is just a standard method of a Ruby Array.

1 Like

This is actually different to the State I pointed out earlier. This is actually the event object, not a state object.

Traditionally, the event object has event.state to tell you the state that triggered this event (state updated or changed to this state). So because event.state is a State, and State has all those relevant predicates depending on its type, you get event.state.up? if that State is UpDownType.

But we’re lazy, so instead of typing event.state.up?, we want to be able to type event.up?, and that’s what’s listed on the link you found above. Besides, event.state.up? won’t always work because event.state can be nil (it’s nil when it’s UNDEF or NULL) whereas event.up? handles the nil case for you. It’s the same as event.state&.up?

Yet a different thing again, you have event.item.state.up? which is the same as event.item.up? (with the additional nil check as above). These two things are not always the same as event.state.up/event.up?.

event.state is the exact state that triggered the event
event.item.state is the current state of the item, which may or may not have been updated by the event (it’s done asynchronously I think in the openhab’s event bus). Most of the time they’re the same though, but it’s important to recognise this distinction.

1 Like