[SOLVED] Rule 'Extractor Fan sensor changed': null - another case

  • Platform information: 2.4

I’ve read a number of the other posts where “rule:null” error is thrown in the logs and cannot find any similarities to mine - so, here’s hoping someone can help me fix it or change the logic so it no longer throws the errors.

The rule is using temps and humidity from xiaomi sensors to determine if the associated extractor fan should be turned on or not.

items:

Number Wetroom_Xiaomi_Temperature "Wetroom temperature [%.1f]" <temperature>  (g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy) //{ channel="mihome:sensor_ht:158d000201ba44:temperature" }
Number Wetroom_Xiaomi_Humidity <humidity>  (g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy) //{ channel="mihome:sensor_ht:158d000201ba44:humidity" }

Groups of interest are:

Group tmpf //this is temp for testing
Group:Number    g_ExtractorFanTriggers  "Group of sensors to trigger extractor fans"

rule:

rule "Extractor Fan sensor changed"
when Member of g_ExtractorFanTriggers changed
then

logWarn("loggerName", triggeringItem.toString)

    val itmSplit = triggeringItem.name.split("_")
    val location = itmSplit.get(0);
    val itemFan                 = g_ExtractorFanProxy.members.findFirst[item | item.name == location + "_ExtractorFan"] as SwitchItem
    val itemFanMaxHumidity      = g_ExtractorFanProxy.members.findFirst[item | item.name == location + "_MaxHumidity"] as NumberItem
    val itemFanMaxTemperature   = g_ExtractorFanProxy.members.findFirst[item | item.name == location + "_MaxTemp"] as NumberItem
    val itemTemperature         = g_ExtractorFanProxy.members.findFirst[item | item.name == location + "_Xiaomi_Temperature"] as NumberItem
    val itemHumidity            = g_ExtractorFanProxy.members.findFirst[item | item.name == location + "_Xiaomi_Humidity"] as NumberItem

    if (itemFanMaxHumidity === null)        {logWarn("Extractor Fan sensor changed", "itemFanMaxHumidity is null. Cannot continue.");return;}
    if (itemFanMaxTemperature === null)     {logWarn("Extractor Fan sensor changed", "itemFanMaxTemperature is null. Cannot continue.");return;}
    if (itemTemperature === null)           {logWarn("Extractor Fan sensor changed", "itemTemperature is null. Cannot continue.");return;}
    if (itemHumidity === null)              {logWarn("Extractor Fan sensor changed", "itemHumidity is null. Cannot continue.");return;}
    if (itemFan === null)                   {logWarn("Extractor Fan sensor changed", "itemFan is null. Cannot continue.");return;}


    val Number maxHumidity      = (if (itemFanMaxHumidity.state == NULL) 80     else itemFanMaxHumidity.state as Number)
    val Number maxTemperature   = (if (itemFanMaxTemperature.state == NULL) 26  else itemFanMaxTemperature.state as Number)
    val Number curTemperature   = (if (itemTemperature.state == NULL) 20        else itemTemperature.state as Number)
    val Number curHumidity      = (if (itemHumidity.state == NULL) 50           else itemHumidity.state as Number)
    val hysteresis = 2


    //val state = itemFan.state; //whyn is this on.

    // logWarn("loggerName", "curTemperature:" + curTemperature.toString)
    // logWarn("loggerName", "maxTemperature:" + maxTemperature.toString)
    // logWarn("loggerName", "curHumidity:" + curHumidity.toString)
    // logWarn("loggerName", "maxHumidity:" + maxHumidity.toString)
    // logWarn("loggerName", "state:" + state.toString)


    if (
        (
                curTemperature > maxTemperature 
            ||  curHumidity > maxHumidity
        )
        && itemFan.state != ON){
        logWarn("Extractor Fan Control", "Turning on fan " + location + ". Temp is " + curTemperature.toString + " and humidity is " + curHumidity.toString)
        itemFan.sendCommand(ON)
    }

    if (
            (
                    curTemperature + hysteresis <= maxTemperature 
                &&  curHumidity < maxHumidity
            ) && itemFan.state == ON){
        logInfo("Extractor Fan Control", "Turning off fan " + location + ". Temp is " + curTemperature.toString + " and humidity is " + curHumidity.toString)
        itemFan.sendCommand(OFF)
    }

end

What I see in the logs:

2019-12-05 11:12:35.295 [ERROR] [.model.rule.runtime.internal.engine.RuleEngineImpl] - Rule 'Extractor Fan sensor changed': null

If I manually change either of the items’ value, the rule triggers just fine. I can change it as many times as I want and I cannot get the rule to fail.
However, looking into the log and the events it’s clear that the two items change almost the same time - makes sense as they’re from the same binding and device.

2019-12-05 11:12:35.239 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from 23.5 to 23.41
2019-12-05 11:12:35.269 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from 52.4 to 53.15

So, I created the temp group, tmpf, and updated that with a number. It fails quite frequently!

log and events together

2019-12-05 14:56:34.946 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Humidity (Type=NumberItem, State=24, Label=null, Category=humidity, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy])
2019-12-05 14:56:35.972 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Temperature (Type=NumberItem, State=24, Label=Wetroom temperature, Category=temperature, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy])

2019-12-05 14:57:25.101 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Temperature (Type=NumberItem, State=23, Label=Wetroom temperature, Category=temperature, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy])
2019-12-05 14:57:25.109 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Humidity (Type=NumberItem, State=23, Label=null, Category=humidity, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy])

2019-12-05 14:57:26.475 [ERROR] [.model.rule.runtime.internal.engine.RuleEngineImpl] - Rule 'Extractor Fan sensor changed': null
2019-12-05 14:57:26.515 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Temperature (Type=NumberItem, State=23, Label=Wetroom temperature, Category=temperature, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy])

2019-12-05 14:57:28.497 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Humidity (Type=NumberItem, State=23, Label=null, Category=humidity, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy])
2019-12-05 14:57:28.565 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Temperature (Type=NumberItem, State=23, Label=Wetroom temperature, Category=temperature, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy])
2019-12-05 14:57:28.554 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Humidity (Type=NumberItem, State=23, Label=null, Category=humidity, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy])



2019-12-05 14:56:30.557 [ome.event.ItemCommandEvent] - Item 'tmpf' received command 24
2019-12-05 14:56:30.561 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Temperature' received command 24
2019-12-05 14:56:30.568 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Humidity' received command 24
2019-12-05 14:56:30.578 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from NULL to 24
2019-12-05 14:56:30.582 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from NULL to 24

*******************

2019-12-05 14:56:43.928 [ome.event.ItemCommandEvent] - Item 'tmpf' received command 23
2019-12-05 14:56:43.941 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Temperature' received command 23
2019-12-05 14:56:43.945 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Humidity' received command 23
2019-12-05 14:56:43.951 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from 24 to 23
2019-12-05 14:56:43.958 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from 24 to 23

*******************

2019-12-05 14:57:05.860 [ome.event.ItemCommandEvent] - Item 'tmpf' received command 24
2019-12-05 14:57:05.869 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Temperature' received command 24
2019-12-05 14:57:05.875 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Humidity' received command 24
2019-12-05 14:57:05.884 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from 23 to 24
2019-12-05 14:57:05.889 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from 23 to 24

*******************

2019-12-05 14:57:11.433 [ome.event.ItemCommandEvent] - Item 'tmpf' received command 23
2019-12-05 14:57:11.444 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Temperature' received command 23
2019-12-05 14:57:11.450 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Humidity' received command 23
2019-12-05 14:57:11.454 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from 24 to 23
2019-12-05 14:57:11.459 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from 24 to 23

What I did notice when I had log:tail running was the first change was not as fast as I thought it would be but then I had just saved the .rules file so did not think twice.
That one was successful though,

Then there was a delay between sending it and seeing the rule fail (and you can see this in the events/logs) and it was just slow after that. It does run near-instant though -

2019-12-05 15:31:03.640 [ome.event.ItemCommandEvent] - Item 'tmpf' received command 24
2019-12-05 15:31:03.661 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Temperature' received command 24
2019-12-05 15:31:03.666 [ome.event.ItemCommandEvent] - Item 'Wetroom_Xiaomi_Humidity' received command 24
2019-12-05 15:31:03.672 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Temperature changed from 23 to 24
2019-12-05 15:31:03.677 [vent.ItemStateChangedEvent] - Wetroom_Xiaomi_Humidity changed from 23 to 24


2019-12-05 15:31:03.653 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Temperature (Type=NumberItem, State=24, Label=Wetroom temperature, Category=temperature, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, tmpf, g_Xiaomi_Warmup_Temperature, g_ExtractorFanTriggers, g_ExtractorFanProxy])
2019-12-05 15:31:03.659 [WARN ] [org.eclipse.smarthome.model.script.loggerName     ] - Wetroom_Xiaomi_Humidity (Type=NumberItem, State=24, Label=null, Category=humidity, Groups=[g_persist_5minute, Group_HabPanel_Dashboard, g_ExtractorFanTriggers, tmpf, g_ExtractorFanProxy])
2019-12-05 15:31:03.674 [ERROR] [.model.rule.runtime.internal.engine.RuleEngineImpl] - Rule 'Extractor Fan sensor changed': null

Anything in particular I can change in the rule which might be causing this?

Cheers,
C

Why is temp and humidity the same in logs? I would think there should be a difference of some sort.

BTW “null” indicates that a variable does not have a value and “NULL” indicates an Item is not yet initialized. I wonder if the rule should use var instead of val…:thinking:

This is from one of Rich’s post:

  • val - short for “value”, gets initialized with a value and the Rules DSL will prevent you reassigning another value to that name
  • var - short for “variable” , can remain uninitialized (will be null), can be assigned new values, and can be reassigned to another value

Here’s the link to the full post Val vs var?

Unfortunately it’s a little more complex than that. Often you will see null errors like that if you are trying to use an object in a way that is not supported. For example, if you try to use the state of a Number Item in a calculation but that state is NULL or UNDEF, you might just see a null error.

This very much looks like it might be the case I discuss above. I wish it would give a line number when these errors happen but the problem is the error is occurring outside of your Rules and inside the Rules Engine so it doesn’t know the line number. :frowning:

You might need to add a check for item*.state == UNDEF in addition to == NULL. This is particularly true if using MQTT binding but other bindings will also set an Item to UNDEF when, for example, the bridge Thing goes offline.

The parens around (if (itemFanMaxHumidity... et al are superfluous. They won’t cause errors but they are not needed.

I don’t think so. The variables are never reassigned as far as I can see with a quick scan so val is appropriate. The error when you try to reassign to a val is different also (it’s a way more informative error).

I think the best bet here is to add lots and lots and lots of logging. Log out the states of all the relevant Items and add logging to figure out exactly what line is causing the error. Once that get’s identified the cause is usually obvious. NOTE: It’s totally possible that the error is coming from one of your logging statements inside the if statements. So you need to add logging statements that can’t fail (e.g. just a String) around your existing logging statements to be sure.

It’s also quite possible that the error is coming from the if conditions themselves.

1 Like

That’s me trying to break it by setting the group to 23 and 24. The error only seems to creep in when both items are change very close to each other in time. It will, in that instance, kick off two rules; one for each item change.
I have my rules thread set to 30; is there a way I can log, at time of running, how many threads are in play? Even if it’s a different log and I can correlate them.

@rlkoshak
I’ll pepper it with more hereiam logging and see if it makes it clearer. I do not recall seeing the output of logWarn(“loggerName”, triggeringItem.toString) when it does fail so it’s like it does not actually get around to executing. I may just have missed that though - will double check.

The thing that confuses me is why can I not get it to fail when I update one item at a time? If it was because of working with a null item or something like that (I think I have taken care of all of them though?)

Come to think of it, I don’t think this ever failed when I was only monitoring the temperature item. It started after I put humidity in the mix. While not necessarily a problem with the humidity item per se, but rather that there is a rapid “double-execution”.

As part two, I will remove the humidity item for a few days and see what that does.

C

Or it’s the call to triggeringItem.toString that failed for some reason. You need to use static Strings to log especially at the beginning and end of the Rule to be sure that the error isn’t caused by one of the log statements themselves.

That shouldn’t matter. The Rule isn’t trying to use something outside of itself (e.g. a global variable) and thus is wholly self contained. I don’t think that Rules are not thread safe so you should be able to have two or more running at the same time without stomping on each other. It could be the case that the sendCommand from each instance of the Rule get’s processed out of order, but there shouldn’t be a null error because you have two instances running at the same time.

I would guess your rule blows up on this line or one of its sisters. That’s guessing that one or more of that group is getting updated somewhere in your process.

Without ever quite pinning it down, there seems to be the odd issue accessing a Group membership while that group is dealing with an update. Maybe the group update (an iterative process) locks rules out of an Item briefly, but you seem to get the occasional null result.
Casting null as SwitchItem is going to fail before you get to the if() test later, so those won’t help here.

I suspect it might just work if you lose the casting.
Or maybe this casting method would do it, not sure

val itemFan = g_ExtractorFanProxy.members.findFirst[GenericItem item | item.name == location + "_ExtractorFan"]
1 Like

I’ve long suspected / guessed this is the case. I have had a number of posts on here where it looks like it could be something like and internal deadlock. There have been some pretty strong resistance to that idea. Maybe not a deadlock per se but it is concurrency.

I just wish I could create a case that is recreatable so the devs can see.

Maybe it’s just time to switch to jython you’re banging on about :smiley:

edit:
I’ve also added the GenericItem thing. will see if that makes a difference too.

If it is a timing sensitive issue, then “reproducible” on your system isn’t going to be reproducible in some other environment.

I’d imagine there a number of members of g_ExtractorFanProxy, that probably boosts the odds by extending the churning time of any member update.
Does this group have a type and/or an aggregation function? It would be surprising if much happened to it, if it did not.

Not me banging on :wink: - DSL does all I need,
If as we suspect this is something to do with core group handling, I do not think the rules language will matter. It might fail more gracefully of course.

I forgot about that problem. Indeed it could still be an issue.

There are ways to avoid it though.

  • use the Image registry to get the Item instead of filtering on a group. See Design Pattern: Associated Items for an example.

  • use changed triggers instead of received update where possible

  • try to avoid having a rule that triggers really fast if you can help it, sometimes this requires totally restructuring your approach though.

Actually in Python at least, you really need to access the Group in the way. For example, in the Rule above, you’d just pull the Item states it of items[“itemName”] and never touch the Group as it’s changing. So there is no access to the Group’s members while it’s changing through the Group Item and no error.

As the dev always says “huh. It didn’t do that on my machine” :smiley:

Actually, only 4 items, two pairs of two (temperature and humidity)

Nope.
My test one, which can make it fail, is this:

Group tmpf

Normal one is this:

Group:Number    g_ExtractorFanTriggers  "Group of sensors to trigger extractor fans"

I’ll change it to the registry option now and see how that behaves. I stopped using it because of the exception it throws if an item is not found. I found that “untidy”.

Let’s see what the logs say this morning…

Well, no failures overnight. I removed the humidity items from the triggering group and no issues.
I’ve now put them back in to see if it returns.

Unrelated to these items but used in the group, I have removed the two “max” items and hardcoded them.
Is my thinking correct here? By the time the rule fires, the binding has finished updating the two items. Therefore, is it unlikely it’s a deadlock type of issue? However, now that both rules are running, they’re both searching through the group to find the associated items. Perhaps the issue is only on the group search? I’d expect to see a lot more complaints in the forum if it were this.

This dubious. Item updates are individual events. If you trigger rule on either one, the second one can update part way through the first rule. And trigger a second parallel run, of course.

Choosing rule triggers carefully can be very important; see Rich’s point about changed triggers.

Fair enough. It’s a stretch :slight_smile:

Anyway, it still fails. So, having both the humidity and temp items in, both of which are in the triggering group, the errors return. Having only one means no errors.
Going to change now to use the registry and see if that makes a difference.

Well, it’s pretty confirmed then :smiley:
I change it to look up from the registry and not a single error all day.

So, [on my system] a group with two items, both being updated (either by binding ot push from another group) and a 3rd item being set will cause an error when you are searching the group for the item. Using the registry will not cause an error.

Now, to recreate this and give a working example :slight_smile:

Thanks for the help folks!

and here is something that can recreate it :smiley: :smiley: :smiley:

Items

Group:Number g_SearchGroup
Group:Number g_TriggerGroup
Group:Number g_FakePushGroup


Number Item1    (g_SearchGroup, g_TriggerGroup, g_FakePushGroup)
Number Item2    (g_SearchGroup, g_TriggerGroup, g_FakePushGroup)
Switch Switch1  (g_SearchGroup)


rule:


import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "FFGG"
when Member of g_TriggerGroup changed 
then
    logWarn("PPP", triggeringItem.name + " has changed and is firing")



    // //option 1
    val itemSwitch          = g_SearchGroup.members.findFirst[SwitchItem item | item.name == "Switch1"] as SwitchItem
    val itemItem1           = g_SearchGroup.members.findFirst[NumberItem item | item.name == "Item1"] as NumberItem
    val itemItem2           = g_SearchGroup.members.findFirst[NumberItem item | item.name == "Item2"] as NumberItem


    //option 2
//    val itemSwitch          = ScriptServiceUtil.getItemRegistry.getItem("Switch1")
//    val itemItem1           = ScriptServiceUtil.getItemRegistry.getItem("Item1")
//    val itemItem2           = ScriptServiceUtil.getItemRegistry.getItem("Item2")


    if (itemSwitch === null)        {logWarn("ItemSwitch is null Cannot continue.");return;}
    if (itemItem1 === null)        {logWarn("itemItem1 is null Cannot continue.");return;}
    if (itemItem2 === null)        {logWarn("itemItem2 is null Cannot continue.");return;}


    val Number val1         = (if (itemItem1.state == NULL) 80     else itemItem1.state as Number)
    val Number val2         = (if (itemItem2.state == NULL) 26  else itemItem2.state as Number)

    logWarn("PPP", "val1:" + val1.toString + " val2:" + val2.toString + " Switch:" + itemSwitch.state)

    if (
        (
                val1 > 25 
            ||  val2 > 30
        )
        && itemSwitch.state != ON){
        logWarn("PPP", "Turning on switch")
        itemSwitch.sendCommand(ON)
        return;
    }

    if (
            (
                    val1 <= 25
                &&  val2 < 50
            ) && itemSwitch.state == ON){
        logWarn("PPP", "Turning off switch")
        itemSwitch.sendCommand(OFF)
        return;
    }

end 

end 

If you have two consoles open - one with log:tail and the other rapidly (as fast as my fingers can go - maybe two changes a second?) changing the fake group value

You will eventually (after less than ten goes!) see this:

If you comment out option 1 and put option 2 in, you cannot make it fail.
It would be interesting if this example fails for others too?

C

Hi @CDriver,

Thank you for reporting this issue. I am not sure if we can provide a proper solution in the framework for this. Why? Because we are working in a threaded environment. Your group g_SearchGroup is a global entity. A rule will be executed in a separate thread. Meaning each time you try to access a property of this group - in your case members - in a rule (thread A) it may have been touched in the meantime by another thread B. Thus a rule should be implemented thread safe too.

For your example I would suggest to store the members of the group in a local variable first before applying findFirst on them:

...
    // //option 3
    val localMembers = g_SearchGroup.members
    val itemSwitch          = localMembers.findFirst[SwitchItem item | item.name == "Switch1"] as SwitchItem
    val itemItem1           = localMembers.findFirst[NumberItem item | item.name == "Item1"] as NumberItem
    val itemItem2           = localMembers.findFirst[NumberItem item | item.name == "Item2"] as NumberItem
...

Not knowing the internals if how OH does this, but is OH using a thread safe Collection to store the Group members. What’s confusing to me is that the error looks a little like what you get when your are iterating over a collection and some other thread modified the collection. So would a CopyOnWriteArrayList or something like that be a possible solution?

I don’t think that will work because localMambers does not get a copy of the list, it will point to the same object in memory that .members points to. They would have to create a copy of the List, but that will run into the same concurrency problem as he iterates over the list to make the copy.

Unless something else is going on, such as a brand new List of members being created when the Group processed an update of one of it’s members.

Not having looked at the code I may be way off base, but I’ve had to dealt with similar issues in the past on stuff I have coded myself in this way.

Something like that. The GroupItem uses a CopyOnWriteArrayList for managing its members internally. Thus it is thread safe inside the framework. Once - and every time - you access the members (e.g. getMembers) a complete new copy of the members list - not the members itself - will be created and returned as immutal set (LinkedHashSet). Have a look here for details. A local variable is not only thread safe but additionally faster and saves a little bit memory.

1 Like

So would it be possible to change .members to do this for us so that this internal implementation detail need not be exposed to the rules user? I think it would be a great boon to all users as this has been an issue that crops up from time to time since I’ve been using OH.

At a minimum, I need to add this to the docs and change all of my DPs and tutorials and we will have lots of examples in this forum that will never get updated. If .members were refactored with a method that returned the copy instead of pointing at the internal data member that would fix it for everyone.

1 Like

Woah, this is something of a revelation.