Currently we have a mixture of rule definition and rule creation.
E.g. in one file there is
class MyRule(Rule):
...
MyRule()
This makes it impossible to import the rule class (rule definition) without creating an instance.
But for testing the rule should only be created in the test case so itâs independent of other test cases which might have been running before.
This is something that has to be solved before itâs possible to write clean and easy to use tests.
I can think of two three ways:
Separate rule definition and rule creation in different files. However this creates much overhead and I think itâs not work the additional effort for the HABApp users. E.g.
class MyRule(Rule):
...
from file_a import MyRule
MyRule()
Write some kind of pre-processor that removes the rule creation statements and dynamically imports the rule module (difficult)
Patch the environment so the automatically created rules will automatically get unloaded again before the tests run (difficult, because rule creation can depend on existing items)
I think it would be nice to have an easy way to test the rules but while it sounds easy at first when done properly an implementation is quite hard.
With a static code checker you can already catch many issues now so I didnât think that an easy way to test was that important.
@nobbi123 I know you already do some testing, how have you solved these issues?
If I create a module, lets say mymodule.py with the following code:
def greeting(name):
return ("Hello, " + name)
And then I use the module in a HABApp rules file by using the import statement:
import mymodule
mymodule.greeting("Adam")
I can now use that mode from several HABApp rules file. However if Iâd like to allow that module to use the openhab object I could probably use a class like this in my module file:
Iâm not sure but it feels wrong to create a HABApp.Rule instance for this because itâs not really a HABApp.Rule. Would there be any other way to access the openhab object that I get access to by using the HABApp.Rule? Small code example would be really helpful. Thanks in advance!
Be aware that you should put the file mymodule.py in the lib folder.
Have you done that?
Itâs not officially documented because itâs not officially supported.
There are so many things that can actually go wrong and so many things that you have to ensure.
Itâs easy to build something that works sometimes but also breaks sometimes and error search can be very very hard.
There is almost always a more elegant option to achieve the same, e.g.
using the HABApp event bus to pass data between rule instances where one rule instance acts as e.g. a worker
self.get_rule()
@RRoe What are you actually trying to do? Maybe there is an easier way ⊠.
Yeah, thatâs correct I put the module in lib of course. In my example above, I just tried to keep the question uncomplicated. What Iâm actually trying to do is to create a module for my heat pump and another module for Nord Pool market hourly energy spotprices. The logic and the properties of those have become so large that they donât belong in a HABApp rule any longer. I need to move those things from regular HABApp rules, but the thing is, the new classes still need to do things that need access to the openhab object, e.g. turn on a switch or use a logger. There might be more needs, I havenât found them yet but I think you can get my point.
As @Tokamak mentioned, I could pass the self.openhab object or even the whole self object from within a HABApp rule when initiating a new object from the new class but it just feels wrong.
However, I might be able to rework the code so that instead of:
I would create a subfolder heat_pump and a subfolder nord_pool in the rules directory and put multiple rule files there.
You can then create one rule that pulls the hourly energy spot prices and posts them to a HABApp internal item. Then next rule will listen to a update/change on that item and do the next calculation. You can even use dataclasses and pass these around (see docs on how to properly type hint them).
So the idea is use the HABApp internal item registry and event bus to build some kind of pipeline which allows you to have small parts of independent logic instead of a huge monolithic rule.
Additionally this makes debugging much easier.
E.g.:
I have a HABApp internal item is_frosty which will turn True if in the last two hours the temperature was below 1°C or in the last 24h the Temperature was < -2°C.
The other rules then build on the state of the internal item instead of e.g. a function call.
If you have static logic you can always put it in the lib folder but as a rule of thumb if it interacts with openHAB or HABApp items it belongs in a rule.
But you can always return e.g. a dict which contains items and the values which then shall be posted by the rule.
And if the logic is independent from HABApp testing the logic e.g. with pytest is much easier.
Youâll have some inputs and expect some output depending on the input.
My approach is totally different, and I am pretty sure you never intended to do that.
Each of my scripts is one rule, a singleton created from a own class derived from HABApp.Rule. That means, only one HABApp rule is instantiated, and the code executed in the script / rule is called in the __init__ of the derived classâ object, after calling super().__init__
Except for HABApp.Rule.on_rule_removed, which I use to clean up the objects I have created in the script, I donât use any HABApp.Ruleâs objects or functions.
That means, your HABApp.Rule.__init__ and HABApp.Rule.on_rule_reomved is the frame of all the code exectued in my scripts.
My own OHRule class, defined in a lib, uses the event_listener_group. With this I can activate or deactivate (cancel) listeners according to my needs. In each of my scripts I create several OHRule class objects, so in the script several event_listener_groups are created.
Because I donât want to use and pass self.oh or self.openhab to all of my classes / objects in order to have access to it, I use interface.post_update and interface.send_command in my classes, as described.
But this is always called within the one framing HABApp.Rule, so it should work.
use the EventListenerGroup to make adding/canceling really easy
from a normal HABApp Rule.
So I fail to see the benefit and tbh. from my point of view it doesnât make any sense at all and seems to be an over complicated way of doing things.
But hey - if it works for you and youâre happy with it why not!
As long as youâre aware that itâs both unsupported and unintended Iâm fine with it.
With OH 2.5 I implemented all my rules using Jython. Unfortunately it was a dead horse I was riding.
There I created some central classes like the Jython version of OHRule and a comfortable timer class. The latter supports tasks comparable to cron, but much more, by using python threads.
Timers are independent from OHRule, that means, they are not only created in a context of a rule.
During an unload of a Jython script I was able to clean up all thinks, especially canceling the timer threads.
With OH3 I migrated from Jython to your HABApp. In order to save most of the Jyhon code I adapted my former OHRule to your framework.
What I did not find was a possibility to clean up the timers created in the script. But I found on_rule_removed(), which is called during unload. So I decided to use a special, outer rule as replacement for the Jython script.
Somewhere I read that it not possibile to create a HABApp.Rule within a HABApp.Rule. This was the reason to use the EventListenerGroup in my OHRule class.
To wrap it up you âhackedâ something together to not loose your legacy code.
In HABApp we have the rule scheduler with self.run.XXX, is there actually no need for a custom (user-) timer implementation.
I can only recommend you try the plain HABApp for new rules so you can gradually switch over (which you could have done with your Jython rules, too) otherwise youâll still be riding a half dead horse
Yes - you should not do it because they will live forever and pollute your system and lead to hard to track down behavior. One of the next HABApp version will probably throw an error if you try it.
But the EventListenerGroup has nothing to with a HABApp Rule.
One is the rule implementation and the other one is a tool to enable/disable one or multiple event bus listeners comfortably.
Trust me, my timer class is a swiss army knife for all of my needs, much more than you are offering.
In addition, from my Jython history, I do not want to depend on a proprietary interface like your self.run.
For this reason I wrapped all of your interfaces, also the OpenhabItem.
This is why I am using it.
If there is a supported function which is called by your framework during unload of a script, I would not use any HABApp.Rule
Youâre depending on my whole proprietary engine and wrappers so the argument is kind of void
To wrap it up:
Itâs good that you found something thatâs working for you but be careful when recommending unsupported solutions to other users who are not as tech savvy.
@RRoe
What do you think about splitting parts of the rule monolith in smaller sub rules?
Do you think thatâs something that will work well for you?
After a very good conversation with @nobbi123 where he explained his testing implementation I created a demo repo to show the idea.
I extended the functionality with a time-aware rule runner so that testing of scheduled tasks is also possible.
I think such a testing functionality would be helpful for many users. @Spaceman_Spiff what do you think? I would gladly prepare a pull-request.
Yes - I think testing is a very good point and definitely something that should be possible and needs to be improved. I am also always looking for contributors so getting a PR would be very nice.
However I am unsure about the about the separation with files because it creates overhead and especially moving rules to the lib folder because they will not be dynamically reloaded. This would require a restart of HABApp to pick up a modified rule and thatâs not something that we should aiming for.
But Iâll gladly have a discussion so why donât you open a PR as is and weâll discuss there?
Hi All,
I am migrating from Windows to Openhabian, and installed HAPApp via the Openhabian user interface.
Version is 1.0.8. OS is Debian 5.10.162-1 (2023-01-21) i686
There is an error occurring at startup (two instances of same error):
Error loading logging config: Unable to configure handler âEventFileâ
Error loading logging config: Unable to configure handler âEventFileâ
Google didnât show anything, but I did find a similar incident in this topic thread from around Nov 20, where the advice was to delete the logging configuration file and allow it to be recreated. That has not solved the issue for me. Any clues on what to consider next?
Hi Sebastian,
I ran fix permissions, and did a reboot. Full transcript below. The config handler error has changed and the first error after the config handler items is new.
1.0.8
Error loading logging config: Unable to configure handler 'HABApp_default'
Error loading logging config: Unable to configure handler 'HABApp_default'
/opt/habapp/lib/python3.9/site-packages/ruamel/yaml/constructor.py:1405: ResourceWarning:unclosed file <_io.FileIO name='/var/log/openhab/HABApp_events.log' mode='ab' closefd=True>
6 validation errors for ApplicationConfig
mqtt -> connection -> tls
value is not a valid dict (type=type_error.dict)
mqtt -> connection -> tls_ca_cert
extra fields not permitted (type=value_error.extra)
mqtt -> connection -> tls_insecure
extra fields not permitted (type=value_error.extra)
mqtt -> subscribe -> topics
'int' object is not iterable (type=type_error)
openhab -> connection -> host
extra fields not permitted (type=value_error.extra)
openhab -> connection -> port
extra fields not permitted (type=value_error.extra)
Executing <Task finished name='Task-1' coro=<Runtime.start() done, defined at /opt/habapp/lib/python3.9/site-packages/HABApp/runtime/runtime.py:29> result=None created at /opt/habapp/lib/python3.9/site-packages/HABApp/__main__.py:38> took 0.506 seconds