HABApp - Easy automation with openHAB

Hi mafe,

yes - it’s very easy and there are several possibilities:
The most recommended is that you can run HABApp on your local machine and connect to openhab in read only mode. Then you use an IDE and set break points in your rules and debug them as a normal python program. Once you are done just copy the rule file to the mounted docker share and it becomes live.

My recommendation is always running the rule on a local machine first (once you’re more secure you can skip the read only mode) because you can use all the local helpers (IDE, type-checkers, etc.).

I also suggest you use loggers since HABApp comes with native python logging support out of the box.
You’ll see the logger definitions in the logging.yml, add your own filehandler and loggers there.

The most important point is using an IDE. I can’t emphasize this enough. I go through all this trouble and annotate everything so you get live type hints, auto-complete and error checks in the IDE.

Edit:
Also check out the MultiModeItem once your comfortable with HABApp. It’s a huge time saver and makes overlapping states really easy.

Hi Sebastian,
Thanks for HABApp which looks very promising and Im planning to use it first to control the new Xiaomi devices which require the new miiot protocol.
I just started reading the docs and have one question - what is the difference between an habapp item and an OH item? For example:
HABApp.openhab.oh_interface.OpenhabInterface.get_item()
vs
HABApp.core.items.Item.get_item()
Are those two methods return the same item if the items exists in OH?

An HABApp Item is an item which is just internal to HABApp.
Use this if you want to share variables or states across rules.

# use get_create_item because you create it
HABApp.core.items.Item.get_create_item('MyItem')

An openhab item is an item which exists in openhab.
Use the config switch wait_for_openhab: True if you use them.

# use get_item, because it already exists and got loaded from openhab
HABApp.openhab.items.NumberItem.get_item('MyOpenhabNumber')

The function you quoted is the one which is available through self.openhab or self.oh.

# use this to get the item definition from openhab
self.openhab.get_item('MyOpenhabNumber')

Thanks for the answer. You can add it in the FAQ for example.

I have created a new release with quite some changes.
Main motivation was because it seemed unnecessary hard to trigger when an item has been constant/hasn’t received an update for a certain period of time.
Now this works:

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

        self.my_item = Item.get_item('Item_Name')
        # This will create an event if the item is 10 secs constant
        watcher = self.my_item.watch_change(10)
        # use .EVENT to always listen to the correct event,
        # that way you can change to watch_update without having to modify this line
        self.listen_event(self.my_item, self.my_function, watcher.EVENT)

Also Things are now in the registry with the status, too.
This allows to check if a Thing is offline/online and the according events are processed, too.


Version 0.12.0


  • Added boundary_func to the scheduled item. If set it will be called with the next datetime and can be used to easily add custom timing function (e.g. on weekdays run on 8, on weekends run on 9)
  • Removed ValueNoChangeEvent & ValueNoUpdateEvent
  • Added ItemNoChangeEvent & ItemNoUpdateEvent
  • Items have a watch_change and watch_update function
  • Added GroupItem, StringItem, DatetimeItem, LocationItem, PlayerItem, Thing to HABAp.openhab.items, now all item types are defined for Openhab
  • All items from Openhab inherit from OpenhabItem
  • All events from Openhab inherit from OpenhabEvent
  • Openhab items don’t have the get_create_item class function any more

Migration to 0.12:

  • Replace ValueNoChangeEvent with ItemNoChangeEvent
  • Replace ValueNoUpdateEvent with ItemNoUpdateEvent

As always: run HABApp on a local machine and connect with the listen_only config to check if everything still works.

I’ve published a new bugfix release which also adds full ImageItem support.
It is now possible to read/set images from HABApp and use them in openhab.
Thanks to @Nicholas_Waterton for providing the test data.

0.12.1


Changes:

  • Allow binary data from MQTT (fixes #101)
  • Added support for ConfigStatusInfoEvent
  • Removed parameters from openhab events. This allows easy unittesting of self created rules
  • Bugfix for ColorItem (fixes #106)
  • added post_rgb to ColorItem
  • Added support for RawValue from openhab
  • Added support for ImageItem (closes #104)

Documentation

  • Added inheritance diagrams for items
  • Added documentation for ImageItem

0.12.2


  • Created Item.listen_event
  • Created OpenhabItem.oh_send_command and OpenhabItem.oh_post_update (closes #109)
  • ImageItem automatically detects type of image
  • Fixed infinite loop when scheduler boundaries changed in boundary_func
  • added next_call to Scheduler obj
  • Bugfix for python 3.8 (fixes #111)

Listening to an event with an item works now intuitively

my_item = Item.get_item('Item_Name')
my_item.listen_event(my_func, ValueChangeEvent)

Posting to openhab from the item works now, too.

my_switch = SwitchItem.get_item('MySwitch')
my_switch.oh_post_update('new_value')
my_switch.oh_post_update()   # This will post the current item value to openhab

my_switch.oh_send_command('new_value')
my_switch.oh_send_command()   # This will send the current item value as a command to openhab

0.12.3


  • Added support for persistence data from openhab
  • Fixes for astral 2.1

usage for persistence

my_item = NumberItem.get_item('TestNumber1')

# this loads the data from openhab
historical_data = my_item.get_persistence_data('rrd4j')

# these are operators on the loaded data set and therfore blazing fast
historical_data.min()
historical_data.max()
historical_data.average()
historical_data.get_data()

0.12.4

  • MultiModeItem supports a custom disable function
  • Reworked folder watcher - no more multiple events when editing a file
  • Added a function profiler which gives better overview for long running functions
  • Docker fixes
  • some cleanup

I recently tried to implement some frost protection which required me to check if the temperature was above 0°C for 2 hours. Whilst this is solvable with the Statistics Item from HABApp.util or through the persistence item there was still lots of manual overhead. Also there was some additional logic need inside the rule to make it possible to trigger on the aggregated state and to recalculate if there were no changes of the input item.
Since I think this is a really common use case (e.g. check if a value is below for a certain time and trigger if it is) I’ve created the AggregationItem.
It does all that in an really easy way.

Example:
Always calculate the maximum of MyInputItem for the last hour

from HABApp.core.items import AggregationItem
my_agg = AggregationItem('MyAggregationItem')


# Connect the source item with the aggregation item
my_agg.aggregation_source('MyInputItem')

# Aggregate all changes in the last hour
my_agg.aggregation_period(3600)

# Use max as an aggregation function
my_agg.aggregation_func = max


# listening to changes of the aggregated item is easy
my_agg.listen_event(my_callback, ValueChangeEvent)

It is even posssible to use a custom function as aggregation function.
To check if there was frost in the last hour I’d use it like this:

def is_frosty(_in):
    return min(_in) < 0
my_agg.aggregation_func = is_frosty

0.12.5

  • Created AggregationItem & small cleanup
  • fixed Error None in __cleanup_objs
1 Like

0.12.6

  • HABApp finds the correct config if the command arg is the config.yml instead of the config folder
  • Files and folders are loaded in alphabetical order
  • Binary data from mqtt work now as expected
  • removed ‘auto_disable_on’ from MultiModeItem
  • Fixes issue where sun functions were triggered multiple times when using scheduler boundaries

0.12.7

  • Bugfix for Dimmer item not getting properly loaded from openhab
  • Added example how to create rules from parameter files
1 Like

Recently I have been trying things with my z-wave nodes and I quickly found out that my configuration of the same sensors were not consistent in my installation.
I tried to compare the parameters in HABmin but it was a rather tedious task.

This is why I created the possibility to configure things through a configuration file with HABApp.
It works with every thing that has been added through the GUI and is of course not limited to z-wave parameters.

Example 1

ThingConfig.yml

# Philio PST02A
zwave:device:controller:node3:
  4: 99     # Light Threshold
  5: 8      # Operation Mode
  7: 20     # Customer Function

Log output

[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node3: PhilioPST02A (Node 03)
[HABApp.openhab.Config]     INFO |  - 4 is already 99
[HABApp.openhab.Config]     INFO |  - 5 is already 8
[HABApp.openhab.Config]     INFO |  - 7 is already 20

Example 2

With the anchor feature of the yml syntax it’s easy to duplicate the definied parameters

ThingConfig.yml

# Philio PST02A
zwave:device:controller:node3:  &PST02A
  4: 99     # Light Threshold
  5: 8      # Operation Mode
  7: 20     # Customer Function

zwave:device:controller:node5: *PST02A
zwave:device:controller:node15: *PST02A
zwave:device:controller:node17: *PST02A

Log output

[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node3: PhilioPST02A (Node 03)
[HABApp.openhab.Config]     INFO |  - 4 is already 99
[HABApp.openhab.Config]     INFO |  - 5 is already 8
[HABApp.openhab.Config]     INFO |  - 7 is already 20
[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node5: PhilioPST02A (Node 05)
[HABApp.openhab.Config]     INFO |  - 4 is already 99
[HABApp.openhab.Config]     INFO |  - 5 is already 8
[HABApp.openhab.Config]     INFO |  - 7 is already 20
[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node6: PhilioPST02A (Node 06)
[HABApp.openhab.Config]     INFO |  - 4 is already 99
[HABApp.openhab.Config]     INFO |  - 5 is already 8
[HABApp.openhab.Config]     INFO |  - 7 is already 20
[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node7: PhilioPST02A (Node 07)
[HABApp.openhab.Config]     INFO |  - 4 is already 99
[HABApp.openhab.Config]     INFO |  - 5 is already 8
[HABApp.openhab.Config]     INFO |  - 7 is already 20

Config Parameter Dump

It’s also possible to dump the available parameters with the current value simply by specifying an parameter name that doesn’t exist:

ThingConfig.yml

zwave:device:controller:node3:
  asdf: asdf

Log output

[HABApp.openhab.Config]     INFO | Checking zwave:device:controller:node3: PhilioPST02A (Node 03)
[HABApp.openhab.Config]    ERROR |  - Config value "asdf" does not exist!
[HABApp.openhab.Config]    ERROR |    Available:
[HABApp.openhab.Config]    ERROR |     - Group1: ['controller']
[HABApp.openhab.Config]    ERROR |     - Group2: []
[HABApp.openhab.Config]    ERROR |     - binding_cmdrepollperiod: 1500
[HABApp.openhab.Config]    ERROR |     - binding_pollperiod: 86400
[HABApp.openhab.Config]    ERROR |     - wakeup_interval: 86400
[HABApp.openhab.Config]    ERROR |     -   2: -1
[HABApp.openhab.Config]    ERROR |     -   3: 80
[HABApp.openhab.Config]    ERROR |     -   4: 99
[HABApp.openhab.Config]    ERROR |     -   5: 8
[HABApp.openhab.Config]    ERROR |     -   6: 0
[HABApp.openhab.Config]    ERROR |     -   7: 20
[HABApp.openhab.Config]    ERROR |     -   8: 3
[HABApp.openhab.Config]    ERROR |     -   9: 4
[HABApp.openhab.Config]    ERROR |     -  10: 12
[HABApp.openhab.Config]    ERROR |     -  11: 12
[HABApp.openhab.Config]    ERROR |     -  12: 12
[HABApp.openhab.Config]    ERROR |     -  13: 12
[HABApp.openhab.Config]    ERROR |     -  20: 30
[HABApp.openhab.Config]    ERROR |     -  21: 0
[HABApp.openhab.Config]    ERROR |     -  22: 0

0.12.8

  • Added possibility to config things textually

Link to full documentation

1 Like

Nice!
After playing around a bit, I think I can get my Aeotec MS6`s back in sync :slight_smile:
Or at least, I am able to add and read comments for the differences without tedious mouse fencing.

1 Like

Hi everyone.

I started playing around with HABApp a bit and am very happy to be able to use a full programming language in conjunction with OpenHAB although I have no experience in Python. (but I work with C# and golang from time to time) :+1:

To my question:
I have a bunch of the same MAX! Thermostats I want to integrate, but I’d like to avoid creating all needed Items in OpenHAB manually. Writing a python class that does that for me following a naming convention won’t be a big issue I think.

What I’m not sure of is how to handle rooms and item aggregation:
Let’s say I’m having a living room containing two Thermostats. For both there is an item Thermostat_ActualTemp. What I would need in that case is a new item LivingRoomActualTemp. I could create this in OpenHAB as a group with AVG-Aggregation on both ActualTemps but as there are a lot of channels on each Thermostat I would need to create a lot of those groups. (ActualTemp, SetTemp, HeatingMode, ValveState etc.)

Can I create such a aggregated group in OpenHAB programatically using HABApp?

My other idea is just listening to all changes on ActualTemp, then look up the room of the Thermostat in a map or smth then recalculate the LivingRoomActualTemp and post the new value to OpenHAB to have it available in my HABPanel.

What are your approaches?

I hardly work with groups any more and do most of my rules and item interactions by naming convention.
It’s easy and way quicker, just make sure to only once use get_create_item and then only get_item to catch typos. Also most of the time it is not just a simple group aggregation.

class ShellyShutter(Rule):
    def __init__(self, name, device_id, delay: float):
        super().__init__()

        item = MultiModeItem.get_create_item(f'mm{name:s}', logger=log)
        item.listen_event(self.move_shutter, ValueChangeEvent)
        ...

It should work (I think I created the interface) but I doubt it’s worth the effort.

class AvgRule(Rule):
    
    def __init__(self, postfix: str):
        super().__init__()
        
        self.sensor1 = NumberItem.get_item(f'Thermostat1_{postfix}')
        self.sensor2 = NumberItem.get_item(f'Thermostat2_{postfix}')
        self.sensor1.listen_event(self.changed, ValueChangeEvent)
        self.sensor2.listen_event(self.changed, ValueChangeEvent)

        self.avg = NumberItem.get_item(f'Livingroom_{postfix}')
        
    def changed(self, event):
        avg = (self.sensor1.value + self.sensor2.value) / 2
        self.avg.oh_post_update(f'{avg:.1f}')
        
AvgRule('ActualTemps')
AvgRule('SetTemp')
AvgRule('HeatingMode')
AvgRule('ValveState ')

Thanks for your answer and the code snippets. I think I’ll also use naming conventions in my code instead of the group feature to have everything in one place and to have more aggregation options. If I come up with something good I will post back. One has to
love the possibility of rules with parameters. :slight_smile:

While defining Items in .item files I was thinking about how to get my namings right. Right now my Item namings are very far from uniformed, so I have a cleaning job to do.

Is it possible to get more info out of Things?
If I had the channel info I could just create Items on the fly.
(just take the Things from the Inbox in PaperUI and give them a name to create Items out of.)

Some Items as examples:

Dimmer testZBhuemini_colortemperature <slider> { channel="zigbee:device:01381602:001788010633dbd1:001788010633DBD1_11_colortemperature" }
Dimmer testZBhuemini_dimmer <slider> { channel="zigbee:device:01381602:001788010633dbd1:001788010633DBD1_11_dimmer" }
Switch  StueTVlampe_Toggle    <switch>        { channel="hue:0220:1:StueTVlampe:brightness" }
Dimmer  StueTVlampe_Dimmer    <slider>        { channel="hue:0220:1:StueTVlampe:brightness" }
Dimmer  StueTVlampe_ColorTemp <colorlight>   { channel="hue:0220:1:StueTVlampe:color_temperature" }
Number sensorBad_Temp "Bad Temperatur [%.1f °C]" <temperature> { channel="zwave:device:89aa3c34:node5:sensor_temperature", expire="3h" }
Number sensorBad_Hum "Bad LuftFuktighet [%.1f %%]" <humidity> { channel="zwave:device:89aa3c34:node5:sensor_relhumidity", expire="3h" }
Number sensorBad_Batt "Bad Sensor Batt [%.0f %%]" <batterylevel> { channel="zwave:device:89aa3c34:node5:battery-level" }
Switch BadeVifte "BadeVifte" <fan> { channel = "zwave:device:89aa3c34:node6:switch_binary" }
Dimmer DimmerGangYtre "Lampe Yttergang Dimmer" <slider> { channel="zwave:device:89aa3c34:node3:switch_dimmer1" }

Also the Things “Attributes” (Habmin) would be quite handy to have access to.

It is quite possible I have missed something important and therefore this is not possible :slight_smile:

It is possible but is it not implemented atm.
But you can always directly access the rest api and get the required information yourself.
Creation of items is fully implemented.

1 Like

I’m in kind of the same situation NilsOF. Currently I do not know how to create an item in OpenHAB and link it to a channel to create the needed items programmatically from HABApp.

What I found however is that reading things is implemented on the connection. For testing I added a get_thing method on the OpenhabInterface and am successfully getting access to all thing-information including channels. (this is more or less a copy-paste of get_item)

    def get_thing(self, thing_name: str) -> OpenhabThingDefinition:
        """ Return the complete OpenHAB thing definition

        :param item_name: name of the item or item
        """
        # if isinstance(thing_name, HABApp.openhab.items.base_item.BaseValueItem):
        #     thing_name = thing_name.name
        # assert isinstance(thing_name, str), type(thing_name)

        fut = asyncio.run_coroutine_threadsafe(
            self.__connection.async_get_thing(thing_name),
            loop
        )
        return fut.result()

Output of

th = self.oh.get_thing("homematic:HG-BC-RT-TRX-CyN:XXXXXXX:XXXXX")
pprint.pprint(th.channels)

from a rule is

[{'channelTypeUID': 'system:signal-strength',                                     
  'configuration': {},                                                            
  'defaultTags': [],                                                              
  'id': '0#SIGNAL_STRENGTH',                                                      
  'itemType': 'Number',                                                           
  'kind': 'STATE',                                                                
  'label': 'Signal Strength',                                                     
  'linkedItems': ['Thermostat_1_0_SignalStrength'],                               
  'properties': {},                                                               
  'uid': 'homematic:HG-BC-RT-TRX-CyN:xxxxx:xxxxxxx:0#SIGNAL_STRENGTH'},     
 {'channelTypeUID': 'homematic:BC-RT-TRX-CyN_0_DELETE_DEVICE',                    
  'configuration': {'delay': 0.0, 'receiveDelay': 0.0},                           
  'defaultTags': [],                                                              
  'description': 'Delete device',                                                 
  'id': '0#DELETE_DEVICE',                                                        
  'itemType': 'Switch',                                                           
  'kind': 'STATE',                                                                
  'label': 'Delete Device',                                                       
  'linkedItems': [],                                                              
  'properties': {},                                                               
  'uid': 'homematic:HG-BC-RT-TRX-CyN:xxxxx:xxxxxxx:0#DELETE_DEVICE'},
... a lot more

@Spaceman_Spiff
I don’t know how you have planned further development of HABApp, but if I could help with little things please let me know.

1 Like