HABApp - Easy automation with openHAB

You are looking in the openhab.log, not the HABApp.log.
Maybe the item already exists (my guess) or there is an non ascii character you are entering.

nope, i’ve added HABApp.log in frontail. anyway is not blocking for me. thank you anyway

I have a few rules that take a couple of seconds complete, because e.g. they’re polling an external website which is slow to respond. That makes HABApp complain about how slow the rule is running, so I’ve stopped this by having the HABApp rule launch a separate thread that polls the website.

For example:

class ReadBins(HABApp.Rule):

    def __init__(self):
        super().__init__()

        self.run.on_every_day(datetime.time(9, 00), self.bins) 
        self.run.on_every_day(datetime.time(12, 30), self.bins) 
        self.run.on_every_day(datetime.time(21, 00), self.bins) 

    
    def bins(self): 
        
        log("BINS: Checking bin dates...")
        threading.Thread(target=BinThread).start()
       

Question is: is this a bad idea? Am I risking trouble by having rules launch independent threads? Will the new thread count towards the maximum 10 threads that HABApp permits?

@Spaceman_Spiff, I’d be grateful for your thoughts!

thanks,

Dan

Yes and totally unnecessary. While it does not account to the 10 threads things might not work as expected e.g. when error occurs or when rules get reloaded.

There are other more elegant ways to do it:

You can run it as an async function (but make sure your logic is non-blocking, e.g. no (IO) wait!!):

    async def bins(self):
        resp = await self.async_http.get('http://slow.website/bla')
        assert resp.status == 200
        data = await resp.json()
        
        # Process, but pay attention to only use non-blocking function
        # Ensure that there are no blocking library calls, when in 
        # doubt rather use the next option.
        # Blocking calls will prevent HABApp from working properly.
        for d in data:
            print(d)
            
        # If you are unsure just process the data with the worker
        # All processing is done in the process_bin() function so it doesn't matter
        # if it is blocking
        self.run.soon(self.process_bin, data)

    def process_bin(self, data: dict):
        # blocking is okay here
        time.sleep(0.3)

Another way would be to run it as a subprocess. That way you have a real subprocess and can do cpu bound operations which can run in parallel:

import ast, sys

    def bins(self):
        self.execute_subprocess(self.process_bin, sys.executable, 'c:/folder/my_script.py')

    def process_bin(self, data):
        assert data.returncode == 0
        _dict = ast.literal_eval(data.stdout)
        print(_dict)        
2 Likes

thanks - I will do that!

Good news everyone!

I just released HABApp 0.30.2!

2 Likes

Hey all,

i am working on my electric energy counter system and i am wondering if it is possible to work with units of measurement in HABApp.

I get electrical consumption value in Wh and it would be nice to use oh measuerement conversions i.e. to kWh

Any hint on that?

Currently that is not supported.
You can either

  • post the raw value to openhab, let openhab make the conversion and work with the converted vale
  • divide the value by 1000 yourself

Thanks for the info.

Just an additional information from my side. HABApp retrieves the converted values from openHAB if UOM is used in openHAB.

I know that my sensore deliveres Wh and for better readability i added a state description to this item. Now it displays kWh.

Number:Energy    SH_EnergyMeter_MeterSensor
                 "Sensor Stromzähler"
                 <measure_power_meter>
                 (SH_EnergyMeter, gFG_PS_TimelineData)
                 {channel="homematic:xxxxx:1#ENERGY_COUNTER", stateDescription="" [pattern="%.2f kWh"]}

Before adding the state description HABApp receives Wh values after adding the state description HABApp is receiving kWh.

Seems to me that HABApp redas the transformedstate instead of state.

@Spaceman_Spiff Am i right with that?

Question on persistence data.

If i get it right it is possible to retrieve data within a defined time frame (starttime - endtime). If there is no value within this frame the result is empty.

If i read the api calls right, there is an option to specify that a value right an left from the intervall is retrieved. I find this very usefull, because often i like to retrieve a special point in time. with the option to retrieve the values right and left i could use the average.

Is it possible to extend the persistence function?

Unfortunately the Rest API does not specify which value is transmitted with the SSE event but I guess you’re right.

Sure - everything is possible! It’s just a matter of doing.
If you like this as a feature you can create an issue with a thorough describe what you want.
I’ll see if I can implement it.

1 Like

In OpenHAB DSL there is an event “System started”.

Can something similar easily be implemented in HABApp?
(i.e. it is triggered when HABApp is restarted.)

Today I do like this:

class MediaCenter_Volume(HABApp.Rule):

    def __init__(self):
        super().__init__()
        self.mcVolumeItem  = DimmerItem.get_item( 'MediaCenter_Volume' )
        self.run.soon( self.update )
        self.mcVolumeItem.listen_event( self.update, ValueUpdateEvent )

    def update(self,event=None):
        mcVolume = self.mcVolumeItem.get_value()
        ...

Ideally I would like this:

class MediaCenter_Volume(HABApp.Rule):

    def __init__(self):
        super().__init__()
        self.mcVolumeItem  = DimmerItem.get_item( 'MediaCenter_Volume' )
        self.mcVolumeItem.listen_event( self.update, SystemStartedEvent | ValueUpdateEvent )

    def update(self,event):
        assert isinstance(event, SystemStartedEvent | ValueUpdateEvent), type(event)
        mcVolume = self.mcVolumeItem.get_value()
        ...

Another approach could be if HABApp called ValueUpdateEvent for all items when HABApp is started,
but that may lead to poor performance.

But what you do is different than what would happen with a system started trigger.
The function update will also be called once you reload the rule (e.g. when you make changes to it) and thus properly initialize everything. A SystemStarted trigger would only be executed once.

For me it’s hard to imagine a use case where a SystemStartedEvent is needed because it will not trigger on rule reload.

Imho:
If you put it at the end of the init block, it doesn’t look so bad …

class MediaCenter_Volume(HABApp.Rule):

    def __init__(self):
        super().__init__()
        self.mcVolumeItem = DimmerItem.get_item( 'MediaCenter_Volume' )
        self.mcVolumeItem.listen_event( self.update, ValueUpdateEvent )

        # Initialize
        self.run.soon(self.update)

    def update(self,event=None):
        mcVolume = self.mcVolumeItem.value  # If you don't supply a default 
        ...                                 # you can use .value

Yeah, you are right about reloading the rule, I didn’t think that way. I will keep my current setup!

Thanks for you input!

Imho the “system started” trigger does only exist, because it’s not possible to run code when the file is loaded. So the idea was to provide some sort of initialization mechanism.
With HABApp this is natively possible so there is no need. :slight_smile:

Today i optimized my thermostat rule. Exchanged the group approach with parameter files. Used the reload on parameter. General approach is to create listeners instead of creating more than one class instance. Nice, Nice, Nice!

In another rule if have additional parameters in a parameter file like offsets. These parameters should be accessible within a listerner function. Due to the fact that i am at the very beginning with python, i have no clue if it is possible to add additional parameters to the listerner function

class MeterManager(HABApp.Rule):

    def __init__(self):
        super().__init__()
        self.meter_reset.listen_event(self.reset_total_consumption(offset=12), ItemCommandEvent)


    def reset_total_consumption (self, event: ItemCommandEvent, new_offset):
        #Do something with offset
        pass

Is that possoble or is there a best practice on that?

Thansk for the help

I’d create multiple rule instances from the parameter file:

class MeterManager(HABApp.Rule):

    def __init__(self, name: str):
        super().__init__()
        self.my_param = HABApp.Parameter('MyMeters', name, 'offset')
        self.meter_reset.listen_event(self.reset_total_consumption(offset=12), ItemCommandEvent)

    def reset_total_consumption (self, event: ItemCommandEvent, new_offset):
        #Do something with offset
        if self.my_param > 1:
                pass

for name in HABApp.Parameter('MyMeters').value:
    MeterManager(name)

MyMeters.yml:

name1:
  offset: 12
  bla: bla
  more: config

But you can always use lambda functions or functools.partialmethod

@Spaceman_Spiff Thanks for your answer.

My question is answere and i could continue with my rules.

But and there is always a but, i recognised that you propose another differnet approach. Thats always the fun in programming for me - so many ways to the goal.

At the end i see two fundamental approaches:

1.) Create multiple instances as you suggest and i have done too

2.) Create multiple listeners based on a parameter file as i have done as well

What is the preferred way? Or is it just a question of personal thinking. What are the benefits from one solution over the other.

I like to group logic that belongs together in one rule.
E.g. I have a rule class that does the shutter automation (up in the morning, down in the evening) and then I instantiate it for every shutter I have.
Additionally I have a rule class that shadows when it’s hot so I instantiate it, too for every shutter I have.
Both rule classes have multiple listenes

It’s not “either or” but rather a mixture of both.
Try building logic blocks and reuse them.

I see your point . Combine some logic in a rule and instantiate it for every item (thermostat, shutter, window, …). I think to do it in that way has some benefits.

Thanks