HABApp - Easy automation with openHAB

OK then i will change to name to rtt because otherwise i will always thing thats to high.

Clould your describe what in detail you measure?

It’s measuring the time from the send request to the received update.

Hi Seb,

Just want to let you know that I am making good progress migrating my APIs over to HABApp. Not much so far, but I’ve got the base unit test and the parsing of OpenHab items into my own structures. I am able to retrieves multiple meta data fields by specifying them as a comma separated string. This part is not very well documented (the second parameter). Perhaps you could change it to something like "a comma separate list of metadata keys to retrieve).

I also think that a dedicated section on the treatment of items would be useful. I know you have a quick section on this here. But based on my experience, I think a dedicated section outlining the following points would provide better clarity:

  1. HABApp has a single map/dict that contains all items (remote OpenHab items as well as locally created items).
  2. Upon start-up, HABApp would query the OH REST API to retrieve all items, map them to appropriate classes in packages HABApp.openhab.items, ands store them in the map above.
  3. Rules can also create local items (that is not present in OH). These are useful for unit testings or for transferring states between rules (and thus avoiding the need for global variables). You mentioned the latter somewhere in the doc.
  4. Local item can be created via HABApp.core.items.Item.get_create_item(item.name, item.get_value()). If specific types are desired, concrete classes in HABApp.openhab.items can be used, but they still need to be added to the items map in 1) via HABApp.core.items.Item.get_create_item(item.name, item.get_value()).

Example for 4):

item =  SwitchItem(name, OnOffValue.ON)
HABApp.core.items.Item.get_create_item(item.name, item.get_value())

Thanks,

1 Like

In your fourth example it does not create an item on the openhab side.
This was confusing for users, so I removed the class factory for it.

local items:

from HABApp.core.items import Item

my_item = Item.get_create_item('item_name', initial_value)

local openhab items that do not exist in openhab:

oh_item = SwitchItem(name, 'ON')
HABApp.core.Items.set_item(oh_item)

But while creating local oh-items is no problem from the HABApp side it may be confusing and error prone.
You’ll have all methods from the item but they will just give you http 404 on openhab. You might overlook that you just added it locally and end up with stuff that is not working and you can’t figure out why.
If you use it solely for unit testing this is totally fine and a nice and elegant way to ensure that everything is working.

About the documentation:
I am always looking for contributors, especially for documentation. Since I write the code I am biased and sometimes don’t see the difficulties. I’ll try to add what I can but everyone is always welcome to create a pull request (ideally for the dev branch).

Yup. That part is definitely confusing and can use some more clarifications.

I will see if I can create a PR for the doc this week.

Thanks,

Seb, I’ve just created a PR to update some docs, on the areas that were someone surprised to me.

I’ve also ported enough of my jsr223 module code over to turn on a light, and that was surprisingly not too bad. I only had to do mainly cosmetic changes to get rid of warnings in pycharm, and add a bit of encapsulation to address the core differences between HAPApps and jsr223. The latency between HABApps and OpenHab is very reasonable so far.

I have to say that pycharm really helps to reduce churns. Before I was editing code using vim and the codes sometimes have stupid errors that aren’t discovered until runtime. Another huge advantage I find is that when working with something non trivia requiring libraries, forcing reload of all the modules by restarting HABApp is way quicker than having to restart OpenHab.

Anyway, here is the code I’ve ported so far in case anyone is curious.

1 Like

Hi Sebastian,
Can you please describe how to debug rules using VS code or an other IDE?
I have not found anything about this in the documentation.

@mafe
Here is an article how to debug on your remote machine, however this requires you to stop running your productive HABApp instance and start a debug one through PyCharm in debug mode.

Since I prefer to keep my instance running I start HABApp in debug mode from PyCharm on my main machine and connect to the remote openhab instance in “read_only” mode (see HABApp config file).
Then I can use breakpoints as usual.
Additionally I also use lots of logging of course and multiple log files.

@yfaway
Thank you very much for you PR. I’ll move the unit test part into advanced usage.

If you experience higher delays than usual or sluggy behavior with OH3 it is because of this issue.
Unfortunately no fix has been included in the recent OH3 release. :frowning: :unamused:

A workaround is to enter this command into the openhab console:

bundle:stop org.openhab.core.io.rest.auth

This however is not persistent and has to be done after every restart of openhab.

Good news everyone!

I’ve released HABApp 0.18.0. You can read the change log in the appropriate thread and report your issues and experiences there.

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.