Java Runtime Environment: Java SE Development Kit 8u212
openHAB version: 2.4.0
I have the following rules file:
var OnOffType state = OnOffType.OFF
rule "System Started"
logInfo("System Started", "Ready");
rule "Test_Switch_Power Changed"
Item Test_Switch_Power changed from ON to OFF or
Item Test_Switch_Power changed from OFF to ON
logInfo("Test_Switch_Power Changed", "---> Test_Switch_Power Changed: " + (state === null).toString);
Test_Switch_Power is MQTT driven, when MQTT subscription happens (I still have to look into this) a change occurs and the rule is fired. Beforehand I actually see the System started log message so I know that ran. What I don’t get is why the state var is null. The logInfo call in the changed even actually tells me it’s null. I would expect the global vars to have been initialized well before.
I actually get either true or false in the log, I’ve seen both. At some point the variable is finally initialized and if the rule is triggered for any reason I will see true. I was getting false after a restart of the OH service (and after the system started message). So I know that piece of code runs, it’s just the var that is somehow null.
This does not only occur with this var, the same issue seems to repeat with other var types and has been plaguing my code for a while. I actually tried initializing my code in the system started rule but that doesn’t always work either.
To elaborate a little, during OH startup sometimes Rules start running before OH is ready for them to start running. In those cases even classes that are part of the OH core, like OnOffType might not yet be available. NOTE: You can just use OFF var state = OFF.
The only way to handle this is to wait a certain amount of time after OH starts, and then copy the Rules files over to the .rules folder. There are lots of examples scattered across the forum.
But this is looking a bit like an XY Problem. Why do you need to keep track of the state like this?
If the Rule triggers at all, you know that it changed. Because you triggered the Rule using a changed trigger, you can get the last state in the previousState implicit variable. What purpose is state serving?
Thanks for the explanation. My script is actually much more complicated than this, I’ve reduced it considerably to demonstrate the issue I’m having.I have a few smart devices (more than one light and switch, different types even; RGB vs RGBW) that will control each other through logic but are not wired together. They can also be controlled through Google Home, I used a virtual Item, I’ve tested a number of different way of doing this and this is what works best so far. Since this is for general lighting, the code needs to be very stable and needs to handle special situations. I don’t want to come back from a week long vacation to find out the lights were on all along.
Things I’m trying to accomplish:
When the system is started, have everything turned off, this is the default state.
If devices connect (MQTT) before or after the system started/initialization they need to return to the state saved in that variable. Currently something is querying the device when it connects (LWT) which triggers a change and messes up everything, I can’t tell the difference between that an a physical press of the button. Still working on this part…
When devices reconnect for whatever reason; network down, router restart, device update, etc. They need to return to a predetermined state (my var), not to what they were last at. Very similar to item #2 but this could be at any time, not just on system start.
Be able to control devices through Google Home even when devices aren’t all connected. The devices should “auto-heal” when they reconnect back to OH.
I eventually want to write a few light strobe/flashing sequences so I’ll need to keep vars of other internal states so it’ll become a lot more complicated than this.
This list may seem unreasonable to some, but I don’t think so. All of these should be simple but the OH framework seems to really get in the way here. I wish I could write a simple jar with all my logic and have events for when devices connect. I actually tried with eclipse, but I didn’t know if what I wanted to do was achievable and the instructions differed enough from what I was going through that it was really hard to follow. I’m using OH on DiskStation which doesn’t have everything setup the same way they are in the docs so that confused me even more, I’m also not very familiar with Unix systems.
PreviousState isn’t available unless I turn on persistence (correct me if I’m wrong), but I’m still going to run into the same issues because of those unwanted change events. Also I don’t want it to return to the previous state after a reboot, they should re-initialize to default.
I don’t quite get the reason for the system started event if it doesn’t guarantee that everything is started or that it runs after the global variables have been initialized. I might as well throw an “if state === null then return” at the top of all my rules. Actually would that be bad?
I’m sorry I really don’t meant to sound grouchy or bitter or anything, OH is great, there’s a lot of work that went into it and I see that. I’m just a tad frustrated with some of the issues or instabilities. Things I don’t think I should be fighting with. The challenge should be around getting my logic right not var initialization.
There are two previousState.
Persisted Items have a method myItem.previousState. Obviously it doesn’t work without persistence.
Every rule triggered by an Item xxx changed event has an implicit variable previousState, which does not rely on any persistence.
For the kinds of restore/recovery you are describing, you probably do want to use some persistence.
Yes. It’s not supposed to be this way. It manifests in a few ways and has been a problem for some time, for some people. There isn’t an instant fix.
You can of course easily trigger a delayed myPersonalSystemStart tuned to your system. Will only take care of some issues.
Put all the lights into a Group. I’ll call it Lights. Then
rule "Set lights to OFF"
createTimer(now.plusSeconds(30), [ | Lights.sendCommand(OFF) ])
The Timer should help to avoid that parallel loading problem.
But the question is why must they be OFF? Why not leave them as they are and only turn some ON or OFF if an automation event was missed (e.g. the time of day changed)? If you use retained messages for the commands, then when OH comes up it will immediately know what state the lights are supposed to be in if they are online. If they are offline then the lights will become that state when they come back online.
Like I described, just use a retained message when you command the light and the light will return to that state when it comes back online (e.g. in a power outage).
There is no such thing as “querying” in MQTT. There are either unretained messages in which case the client needs to be connected to receive the message, or retained messages in which case the client will receive the last published message even if it wasn’t connected when the message was sent.
So you want them to return to the last state they were commanded to from OH. That is exactly what a retained message would do.
It’s not unreasonable, you are just going about achieving it the hard way.
This really isn’t OH’s job. Use the capabilities of MQTT to your advantage.
But even if it were OH’s job, you are still going about it the hard way. Another simple approach would be:
put all the lights into one Group, we’ll call it Lights
configure Lights* to be persisted on everyChange and restoreOnStartup
run a simple Rule at startup to sendCommand to the lights with the restored state from the database
rule "Restore lights"
createTimer[now.plusSeconds(30) | Lights.members.forEach[ light | light.sendCommand(light.state) ]
To restore a light to it’s previously commanded state when it comes online (assuming you don’t use retained messages) you would need to get some sort of message to indicate that it has come online. You could look for the LWT message and then use the first message after that to indicate it has come back online to then sendCommand the Item’s current state in OH. Again, using Persistence the rule would look something like (using Design Pattern: Associated Items):
rule "Came back online?"
Item Member of Lights changed
val lwt = ScriptService.getItemRegistry.getItem(triggeringItem.name+"_LWT")
hist = triggeringItem.previousState(true).getTimeStamp()
If the previous state of the Light that is different from the current state is before the most recent LWT message, this is the first message after the LWT so send the last state of the light back to the light.
NOTE: I just typed in these Rules, they likely contain errors.
That is something your device needs to implement and publish a message. There isn’t anything you can do from OH. MQTT doesn’t work that way.
If you have a Rule triggered with a changed trigger, then there will be a variable called previousState. This variable does not require Persistence.
It’s a long time bug that hasn’t been fixed.
It wouldn’t be bad but I think this entire approach to do what you are after is not the best way to achieve what you are after so I’m questioning whether it’s worth dealing with this particular problem in the first place.
Thanks for the amazing reply, I will have a look at all of these things, but it’ll take me a lot of time to rework all of this.
One thing I did not mention in my last post is that I want the switch to control the lights in an Off/On(White) only. Google Home is used to control the color but can also change to white through the power command. Letting the device reconnect and getting that LWT message triggers a power change which causes the lights to flash and potentially lose the color if any was set while to switch was down (through Google Home). At the time I didn’t know that LWT was what was going on at reconnect. Perhaps if I go back to this earlier version and introduce the previousState and persistence…
One problem I do have about grouping my lights is that I decided to use tasmota and use the RGB string to control them. That allows me to do on/off White/color and RGB all at the same time. I’m finding that simpler than controlling the power and color separately. Now 2 of the lights in the group are RGBWW, one is RGB and the switch, well is a switch so On/Off. Should the switch be grouped with the lights? If I get a command through Google Home (virtual item), it needs to control both, lights and switch.
This is not how LWT works. The broker will publish a message to the LWT topic for a client when it determines that the client is no longer connected. Sometimes a device will publish a message to that same topic when it comes back online. I’ve seen this done very well with retained messages so when ever any client subscribes to that LWT topic, they get a message telling them whether the device is online or offline. But in that case it’s not really a LWT topic anymore and instead it’s an online status topic.
I don’t see why not. In your list of five requirements you are only dealing with Switch actions. You may need to do a little filtering on the Group to do one thing for Switch Items and something else for Color and Dimmer Items or String Items (I guess?) you are using for Color.
So in the end I used a combination of the timer on system start to slightly delay and retry and used proper variable checking before running certain pieces of code. One thing I am doing now, just in a few unique places though) is check for equality rather than non equality,
As an example;
state == OFF
Is not equal to:
state != ON
because state might be null. I also had booleans used in conditions directly and those were also causing oddities when they were null.
I realized issues are still present in the product but this allows me to have a working solution.