HABApp - Easy automation with openHAB

@Spaceman_Spiff is there somewhere an example how to write unittests for own rules?

Hi to All.
Somehow I can not get the Numpy library to work.
I succesfully installed it by: python -m pip install numpy
But keep getting this message when i run a script: ModuleNotFoundError: No module named ‘numpy’
Any ideas?

You have to install it in the virtual environment where HABApp is installed, too. Then a restart is required to pick up the updated packages.

Thanks for your swift reply. But how do I install it in the virtual environment? I read about creating a docker to install packages but I did it manually (Habapp wasn’t running after instaling from Openhabian config because no modules were installed but is running now).

Could you elaborate again how exactly you installed habapp?
If you followed the instruction from readthedocs for virtual environment you just do steps 1, 3 and 6 but instead of habapp in step 6 you just type numpy

I installed Habapp by the openhabian-config menu. When I checked if Habapp was running, as described in the documentation, it didn’t and returned messages that no modules were installed. After manual installation of the modules (as in step 6) and a reboot Habbapp was running.

Just followed the steps exactly as you describe, when I enter python3 -m pip install numpy again it returns the message Requirement already satisfied. That is where I initially got stuck, it looks like it is installed and Habapp is running but it doesn’t pick-up numpy.

If you type python3 and then try import numpy in the venv what is the output?

When I type python3 and then try import numpy in Karaf, I get no messages. Than when I type Help() and then numpy I get the help from numpy. So it looks installed correctly. But again when I run the script I get the message ModuleNotFoundError: No module named ‘numpy’

Solved, needed to add “sudo” :wink:

1 Like

Hello again Sebastian! Finally getting back to HABApp scripting again.

I’m now successfully using ItemNoChangeEvent. Very useful! :slight_smile:

But, running into a problem with CountdownJob.
It’s great to be able to reset() it to make it count again, but sometimes I would like to be able to cancel() it so that it does not occur. And then I would like to be able to reset() to make it start counting again.

I’m getting the JobAlreadyCanceledException exception if I cancel an already cancelled job.

I looked at your documentation here but the remaining() function doesn’t seem to exist.

How do I cancel the countdown in a non-destructive manner which allows me to start the countdown again?
This would be a great thing to include in the documentation, surely I can’t be the only one. :slight_smile:
Thanks in advance.

Howdy!

Edit: After sleeping on it, I realized that this is probably the special “running rules from another rule” case. I will try to organize the code differently to avoid the issue.


So, I’m writing a script that is going to make cool light transitions. I’m relying on being able to create an object which performs the transition upon command. Obviously looping with sleep doesn’t work in an event-based system, so I’m using self.run.at to get called periodically.

Unfortunately, I have found that creating an object that starts a self.run.at sequence, does not work from a callback! The object gets created but run.at never executes.

I have created an example that shows the problem clearly. Could you take a look?

import HABApp
import logging

log = logging.getLogger('HABApp.RunTest')

class LoopTest(HABApp.Rule):
	def __init__(self, loops):
		super().__init__()
		self.current=loops
		self.run.at(0.1,self.main)
		log.info(f"Running {self.current} loops!")
		
	def main(self):
		if self.current > 0:
			self.current-=1
			log.info(f"Loop #{self.current}")
			if self.current > 0:
				self.run.at(0.1,self.main)

class Startup(HABApp.Rule):
	def __init__(self):
		super().__init__()
		
	
		#self.run_test()	#this works
		self.run.soon(self.run_test)	#this does not work -- LoopTest gets created but self.run.at never executes


	def run_test(self):
		test=LoopTest(10)

		
Startup()



Okay, here’s my attempt at reorganizing. It works now, but if you have a more elegant way of doing it I’d love to know, before I develop too many anti-patterns that are hard to get out of :slight_smile:

So, what I’m trying to do is:

I’m trying to set off a chain of asynchronous events. Multiple chains may happen in parallel, that is, I may start another chain before the previous one has finished. Each chain needs to keep track of its own data, so I made it a class.

import HABApp
import logging

log = logging.getLogger('HABApp.RunTest')

class LoopTest():
	def __init__(self, loops):
		self.current=loops
		log.info(f"Running {self.current} loops!")
		
	def main(self):
		if self.current > 0:
			self.current-=1
			log.info(f"Loop #{self.current}")
			return 1 if self.current > 0 else 0

class Startup(HABApp.Rule):
	def __init__(self):
		super().__init__()
		
	
		#self.run_test()	#this works
		self.run.soon(self.run_test)	#this works too

	def handle_run_at(self, looptest_object):
		if looptest_object.main():
			self.run_at(0.1, self.handle_run_at, looptest_object)

	def run_test(self):
		test=LoopTest(10)
		self.handle_run_at(test)

		
Startup()


Again, this works but I would love some pointers @Spaceman_Spiff :slight_smile:

You have to manually update eascheduler since this is only available from the latest version (which will be included in the next HABApp release).

Just to be clear you are creating a new rule every time you are creating an object in the example.
It should work non the less, but it’s not just an object but a full blow rule.

Imho there are two ways to tackle this:

  • Create a rule per light/item that consolidates the fade logic and the listeners to start them
  • Create a worker object and pass in the callback.

I would go with the first solution because it groups all the fade logic together nicely.
If you go with the second solution I would pass the rule as an argument during object creation.
That way you can schedule the calls directly from the worker. The scheduler is rule bound so you have to use the rule context in which the fade is running in.

PS: The next HABApp release will contain a helper to run fades

Ah! That explains it. I did notice that the latest release of eascheduler was newer than the latest release of HABApp. I will wait for the next release for this.

Good point – I did this to have access to self.run.at.

Actually the fade logic is already done in each individual light, each of which is equipped with an ESP8266 and runs my own firmware. However, there are 28 lights in a 4 x 7 grid. The HABApp logic controls the wipe which incorporates all the lights. It’s working now, I’ll shoot a video and show you. :slight_smile:
Because of this, the second solution is more straightforward.

Ah! Thank you. I didn’t think about that, that is much more straightforward.

It’s not forbidden to create 28 rules in a for-loop! :wink:
I’d like to see the video!

1 Like

Done! Here it is :slight_smile: https://imgur.com/a/BVWUI9z

28 rules in a for-loop could work too, but there’s one caveat: as the wipes for different dimmer levels go in different direction, if you’re too fast with a new command, one wipe will start before the previous one finished, creating a conflict where they’ll be fighting over some lamps. I was never able to solve this in my Node-RED prototype of this but I solved it in my HABApp script by making sure the latest command wins. 28 rules in a for-loop with delays would need some kind of solution like this as well.

With regards to passing the rule as an argument, I have a follow-up question:
What happens under hood if you do that? Are you passing a reference, a pointer, or a copy of the entire object? I’m asking as a C++ programmer, where nothing happens automatically as you know, so when you make a mistake, it’s always your own fault. :slight_smile:

By the way here is the script as it’s running:

It’s pretty clunky but does seem to work perfectly, I have not been able to break it.

One key feature is that the 28 lamps are in turn on a relay, so they are not using idle power when off. The first transition you see is actually a startup delay in the software of every lamp. Everything subsequent is the HABApp script.

In python mutable objects always get passed by reference.
It’s good style to clean up references if you don’t use them any more, but it’s not required because python has a cyclic reference detector built in so things can still get garbage collected.

Very nice! I like it a lot!

Hello again!
This is probably as much an openhab question as a habapp question.

I have my own wireless door sensor, and when the door is opened or closed, my HABApp rule is notified. In this case it’s through 433 mhz and MQTT, but that’s not important. the HABApp rule gets the message and knows the state of the door.

Now, I have created a Contact item in openHAB, and I want to set the state of this Contact so that other rules can act on it in a standardized way. That’s where I’m running into trouble.

The HA documentation lists “oh_post_update” and “oh_send_command” under ContactItem, but they don’t actually work.

[2022-04-16 18:07:03,215] [HABApp.openhab.connection]  WARNING | Status 400 for PUT http://openhab.lan:80/rest/items/BasementDoor/state/ open

The OH documentation does describe the Contact item as “Status of contacts, e.g. door/window contacts. Does not accept commands, only status updates.”

So, I can see why it doesn’t work. But, what now? :slight_smile:

I could just use a Switch item instead, and that will be my immediate workaround, but it would be nicer if I could use a Contact item since that’s what they’re for. I think.

oh_post_update should definitely work, there is even a test for that. oh_send_command does indeed fail for a ContactItem.

You’re right! It does. I was sending it the wrong things. “OPEN” and “CLOSED” work fine with oh_post_update. My bad. Thank you!

Documentation could be clearer, though. ContactItem should probably not list oh_send_command. The description for oh_post_update is currently:
(optional) value to be posted. If not specified the item value will be used.

Let’s see
(optional) value to be posted.
(optional) "OPEN" or "CLOSED".

Oh look, they happen to be the same length. :slight_smile: