HABApp - Easy automation with openHAB

Thank you for the new release! :slight_smile:

  1. Is there an easy way to bind an event listener to a group of items? I would like to detect if the status of a window changes.

  2. When iterating over the items, do I really need to fetch the type separately?

         for item in self.openhab.get_item(contacts).members:
             my_item = ContactItem.get_item(item.name)  <--- necessary?
             if my_item.is_open():

If you do not want to supply the items as a rule argument I suggest you create a list of contact items and store it. Then iterate over the list without fetching the item again.
If you want to post an example I can try help you optimize your rule.

Good thing I found this post! Just upgraded to OH3 and since 99% of my rules are HABApp since a few weeks, the system became useless. With OH2 I had small latencies around 100-200ms for some cascaded sequences that now take 10-15 seconds!

I disabled the AUTH for now and wouldn’t mind if that was a permanent option in openHAB, but would it be possible to add an option for using the API key in HABApp?

Doing some simple tests with cURL shows that using an API key is way faster:

[16:32:55] openhabian@openhab:~$ time curl -X POST “http://user:pass@192.168.1.17:8080/rest/items/LivingRoomAVR_Mute” -H “accept: /” -H “Content-Type: text/plain” -d “OFF”

real 0m3.403s
user 0m0.046s
sys 0m0.027s

vs:

[16:33:31] openhabian@openhab:~$ time curl -X POST “http://192.168.1.17:8080/rest/items/LivingRoomAVR_Mute” -H “accept: /” -H “Content-Type: text/plain” -H “Authorization: Bearer eyJraWQi…Npcw5X7DHLQ” -d “OFF”`

real 0m0.094s
user 0m0.033s
sys 0m0.040s

I’m sorry for the inconvenience and really disappointed that there was no fix included in the openhab release especially since I addressed the issue with M5 and a simple off switch in the config or a small cache would have been sufficient.

I’ll investigate about the token and it is on my roadmap but it’s a little bit tricky since I am using multiple libraries because of the SSE event listener.

Thank you for that, but if it is a lot of work, I can live with the workaround. Once auth is off, I have no problem with timing anymore.
In my simple setup, I don’t really see the need for any authentication anyway.

Good news everyone!

HABApp 0.18.1 is out!

1 Like

Have a question on the automatic reload dependend on other files.

I tried to create a rule that is dependend on a parameter file, but i get an error message that the file does not exist.

[2021-01-01 19:16:19,927] [             HABApp.files]  WARNING | File /etc/openhab/habapp/plantcare.py reloads on file that doesn't exist: /etc/openhab/habapp/param/plantcare.yml

The rule file contains

# Dependency definitions
#HABApp:
#  reloads on:
#    - /etc/openhab/habapp/param/plantcare.yml

import HABApp

What is wrong with that?

The file name is the same that is used in the RequestFileLoadEvent from the EventBus.
In your case that would be params/plantcare.yml.
Also you can use multiple spaces in the beginning:

# Dependency definitions
# HABApp:
#   reloads on:
#    - params/plantcare.yml

import HABApp

Edit:
I modified the docs so this is more clear.

@Dibbler42
What exactly are you doing? Creating rules based on parameter files?

Hmm, i gave up i chaned my param folder to params in config.yml and in my python file and changed the path to a relative path

Maybe you should clarify relative to what. i suppose it is the rules folder. in my case the rules folder is:

/etc/openhab/habapp

and the params folder is:

/etc/openhab/habapp/params

so relative to the rules path is

/params/something.yml

And yes i try to create rules on parameter files. In some of my rules i use the

Member of Group changed

and from what i understand the solution with habapp is to create a rule for each member of the group and this coud be done by a parameter file

Example:

# Dependency definitions
#HABApp:
#  reloads on:
#    - params/plantcare.yml

import logging
import HABApp

from HABApp.core.items import Item
from HABApp.core.events import ValueUpdateEvent, ValueChangeEvent
from HABApp.openhab.items import SwitchItem, ContactItem, DatetimeItem


log = logging.getLogger('Rules')


class PlantStatusUpdater(HABApp.Rule):
    def __init__(self, plant_prefix):
        super().__init__()

        log.info(f'Initialising plant status updater on prefix: {plant_prefix}')

        # Collect all required items
        self.control = SwitchItem.get_item(f'{plant_prefix}_Control')

        self.listen_event(f'{plant_prefix}_LastUpdate', self.update_status, ValueChangeEvent)

    def update_status(self, event):
        assert isinstance(event, ValueChangeEvent), type(event)
        log.info(f'Rule triggerd with value: {event}')
        if self.control.value == "ON":
            log.info(f'Sensordata should be processed!')
        else:
            log.info(f'Mööp - No processing!')
        
# Rules
plantList = HABApp.Parameter('plantcare', 'plants')
for entry in plantList.value:
    PlantStatusUpdater(entry)

Maybe it is a good idea to create some documentation about how to replace dsl trigger conditions with habapp

Why did you do that? I never stated that you should change your configuration!
It is also not a relative path to the rule folder, I don’t know how you got that idea.
It’s params/ + name relative to the parameters folder.

When in doubt just touch your parameter file, watch for the RequestFileLoadEvent on the HABApp event bus and use the name from the event for the file property as written in the docs.


Correct. If the logic is always the same you can create your triggers inside the rule since the event contains the name of the item that changed.

it’s either one rule for each item

class PlantStatusUpdater(HABApp.Rule):
    def __init__(self, plant_prefix):
        super().__init__()
        
        log.info(f'Initialising plant status updater on prefix: {plant_prefix}')
        
        # Collect all required items
        self.control = SwitchItem.get_item(f'{plant_prefix}_Control')
        self.time = DatetimeItem.get_item(f'{plant_prefix}_LastUpdate')
        self.time.listen_event(self.update_status, ValueChangeEvent)

or one rule for all items

class PlantStatusUpdater(HABApp.Rule):
    def __init__(self):
        super().__init__()

        for plant_prefix in HABApp.Parameter('plantcare', 'plants').value:
            item = DatetimeItem.get_item(f'{plant_prefix}_LastUpdate')
            item.listen_event(self.update_status, ValueChangeEvent)
    
    def update_status(self, event: ValueChangeEvent):
        assert isinstance(event, ValueChangeEvent), type(event)
        log.info(f'Rule triggerd with value: {event}')
        
        # Strip away "_LastUpdate" to build the prefix
        # plant_prefix = event.name[:-11]
        plant_prefix = event.name.replace('_LastUpdate', '')
        switch = SwitchItem.get_item(f'{plant_prefix}_Control')
        
        if switch.is_on():
            log.info(f'Sensordata should be processed!')
        else:
            log.info(f'Mööp - No processing!')

PlantStatusUpdater()

Note that I use the class factories every time because it helps catch typos.
I’d also prefer the first version because all items get resolved when the rule is created.
That way everything is less error prone.

It is not easy to dig into all the deatils of your development process and so i try to rebuild things in examples and then change them step by step. Beginner problems.

So next step look at the RequestFileLoadEvent … Found it … Did i get it now right, that params is the placeholder for the param path in the configuration yaml?

And then i have to decide which approach to go “many Rules” or only “one Rule”

No, it’s literally just params/. You can name your parameter folder how you want.
The prefix name is not related to the configured folder - it’s always params/ for parameters.

Yea - the docs could definitely be improved. I’m already working on it for the 0.19.0 release.

With great power comes great responsibility. :wink:
Imho it highly depends on the logic you want to implement.
However rules are inexpensive so why not create many of them.

Yes that right.

Did i get it right that the use of transformations is not possible like the use of actions?

If you want to transform the value in multiple rule files you can use a Parameter and create key/value pairs in the file.

param = DictParameter('my_file')
param[key]

If you want to transform in just one rule file you can create a global dict and simply use it and look up.
If you want to do more complex stuff you can use an HABApp Item as proxy

Using the transforms from openhab is (currently) not possible and to be honest I never saw the need for them.

I understand. The only reason to use OG transformation was to use the same files.

Now i build it on a parameter file. This is a nice benefit from the parameter files. I could put all types of information in it.

plants:
  - ZO_PC_Miflora01
  - ZO_PC_Miflora02
  - ZO_PC_Miflora03
  - ZO_PC_Miflora04
  - ZO_PC_Miflora05
  - ZO_PC_Miflora06
  - ZO_PC_Miflora07
  - ZO_PC_Miflora08
  - ZO_PC_Miflora09
  - ZO_PC_Miflora10
  - ZO_PC_Miflora11
  - ZO_PC_Miflora12
  - ZO_PC_Miflora13
  - ZO_PC_Miflora14
  - ZO_PC_Miflora15

states:
  -1 : 'deaktiviert'
  0  : 'ok'
  2  : 'gießen'
  4  : 'düngen'
  6  : 'gießen & düngen'

Key “plants” is used to iterate through all plant items (Have choosen the approach with only one rule. ;-)) From key States i generate a dictionary to do the mapping with

        # Get plant state mapping
        self.plant_states = dict(HABApp.DictParameter('plantcare', 'states').value)
            log.info(f'State changed from "{self.plant_states.get(last_state)}" to "{self.plant_states.get(target_state)}"')

Next step is to add locations (a new parameter file) and plant names

1 Like

Have you guys moved to OH3 yet? I am trying to move more and more of my rules over to HABapp before the upgrade. This is another advantage of HABApp: reduce coupling. The chance of HABApps and OH upgrade at the same time is very low. And as we mainly use OH3 for its core, binding and definition of items, our upgrades should be a lot less painful than when we’re fully within OH. At the same time, we have another process/system to maintain.

When I have sometimes, I will write up a section on “Why HABapps and how is it difference from other approaches?”.

I think we should also have a section that reference source code / projects that based on HABApps. It will provide examples for people new to the project.

1 Like

@Dibbler42
You can use Dicts in the parameter files:

ZO_PC_Miflora01:
  location: loc_1
  param1: value2
ZO_PC_Miflora02:
  location: loc_2
  param1: value4

and use this to set up your rule

for name, params in HABApp.DictParameter('plantcare', 'plants').items():
    PlantStatusUpdater(name, config['param1'])

Also if you cache the parameter value you might as well put a global dict in your rule files.
It’s either

self.plant_states = HABApp.DictParameter('plantcare', 'states')  # note you don't have to cast to dict
log.info(f'State changed from "{self.plant_states.get(last_state)}" to "{self.plant_states.get(target_state)}"')

or

PLANT_STATES = {-1: 'deaktiviert', 0: 'OK', ... }
...

log.info(f'State changed from "{PLANT_STATES.get(last_state)}" to "{PLANT_STATES.get(target_state)}"')

I am still on 2.5 since I don’t have a lot of time right now and I’d like to fix some issues before I migrate.

That would be nice. You can create a new thread so we can get a discussion going.

1 Like

I was scratching my head yesterday trying to work around this strange issue. On the HABApp side, I attempted to set a String item to an empty string value like this:

HABApp.openhab.interface.send_command('EmailBody', '')

Naturally I expect that on the JSR223 side, its value via the following statement would be a string value as well.

body = scope.items['EmailBody'].toString()

but that is not the case. Its value is in fact a scope.UnDefType.NULL.

I wonder if this is a bug on the HABapp side or on the JSR223 side.

I recommend you use the class factory to interact with openhab:

StringItem.get_item('EmailBody').oh_send_command('')

Also, I’d use oh_post_update since oh_send_command might not work as intended depending on your autoupdate settings.
You then can check the events.log and HABApp_events.log and observe the state change.

Posting an empty string as an update definitely works, I do this in one of my rules, too.

Is it possible to send NULL or UNDEF value to openhab items?