-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Triggers are not reloaded on item change #11
Comments
This is not a bug, but a limitation of OH. These libraries simulate the I know there is now an actual |
Thanks, that sounds reasonable.👍 Is there a workaround? Enabling and disabling rules did not work. Only re-save them triggers a reinitialization. |
The resave will be the easiest way. I have some dynamically generated rules who's generating code watches for group membership changes and then deletes and recreates the rule. This approach would not be ideal unless turned into a library (which I kinda like the idea of, unless the OH3 member of trigger works with membership changes). What is your use case for this? Maybe there is another way to handle the situation. |
I like to track the last activation (e.g. Light) of an item with a given tag. Therefore a rule checks upon item changes, if every item of that tag is part of a given group, e.g. When I am changing tags, the rule adds or removes items from the group. The other rule that triggers upon group updates, e.g. on Actually I would really appreciate the reloading of rules with |
That sounds like the hard way, what are you triggering with these tags? Wouldn't a DateTimeItem for each light that gets set when it changes be easier? I agree that dynamic triggers would be better, but it is not a simple thing to do. My current implementation that I use for some of my own rules is not working perfectly even. |
Actually I want to setup everything easily without manual creation of items (so people without detailed knowledge can setup their home easily. So thats why I make much use of the metadata. The rule to sync the tags with the group:
The rule to set the last light activation of a location:
@CrazyIvan359 Maybe we can improve your logic together? ;-) |
That is the same reason I'm doing mine the way I did, though I'm the only one using it right now. One problem with it is that all items get removed when OH shuts down, which may cause strange behaviour. The bigger issue is bulk updates causing multiple triggers, this can cause strange behaviour if the rule delete/generate code tries to create a trigger for item 2 that was removed while it was running when triggered by item 1 being removed. A single bad trigger will cause the rule not to be created. You could have the regen code pop bad triggers before creating the rule, but doing that blindly could hide other issues (though I suppose you would see the error from I will post my code here later for you to look at. I am working on other projects at the moment so I won't be able to actively test anything, but I will respond to messages. |
What you see below is take 2 of this code. Take 1 was ad-hoc and needs improvement but has seen limited production use, the code below is refined and abstracted, though has not seen production use yet. This is the starting point, this rule triggers on items being added, removed, or changed. There are no triggers here because this code is built to be published this rule is generated once on load. The triggers are import typing as t
from community.events.items import ItemEventGroupMember
if t.TYPE_CHECKING: # this will only work if using my stubs with typing installed
from org.openhab.core.items.events import (
ItemAddedEvent,
ItemCommandEvent,
ItemRemovedEvent,
)
def rule_item_event(event):
# type: (t.Union[ItemAddedEvent, ItemRemovedEvent, ItemUpdatedEvent]) -> None
item_event = ItemEventGroupMember(event, GROUP)
if (
item_event.added or (item_event.updated and item_event.membership_changed)
) and item_event.is_member:
# item added to GROUP
# regenerate rule(s)
elif (item_event.removed and item_event.is_member) or (
item_event.updated and item_event.membership_changed and item_event.was_member
):
# item removed from GROUP
# regenerate rules And here is my rule (re)generator. The lambda bit is hard to explain but it just provides a group name to substitute into each trigger in the list. You may not need anything that fancy if you are using this in one place. import typing as t
from core.jsr223.scope import scriptExtension
from core.rules import rule
from core.triggers import when
ruleRegistry = scriptExtension.get("ruleRegistry") # type: RuleRegistry
if t.TYPE_CHECKING:
from org.openhab.core.automation import RuleRegistry
def generate_rule(target):
# type: (function) -> None
""" Creates a rule, removing it if it already exists """
UID = getattr(target, "UID", None) # type: str
if UID:
ruleRegistry.remove(UID)
del UID, target.UID
del target.triggers
# this is using a map and lambdas to get the group name from the config
for trigger in RULE_TRIG[target.__name__]:
when(trigger.format(group=RULE_TRIG_MAP.get(target.__name__, lambda: "")()))(
target
)
triggers = getattr(target, "triggers", None) # type: t.Union[t.List, None]
if triggers and triggers.count(None) != 0:
log.warn("Rule '%s' not created, a trigger is invalid", target.__name__)
elif triggers:
rule(RULE_NAME[target.__name__], RULE_DESC[target.__name__], TAGS)(target)
if getattr(target, "UID", None):
log.debug("Rule '%s' created successfully", target.__name__)
else:
log.error("Failed to create rule '%s'", target.__name__)
else:
log.debug("Rule '%s' not created, no valid triggers", target.__name__) Event ProcessorsThis library pulls out the common code that checks if an item was added, removed, or changed ( I have the code below in __all__ = ["ItemEventProcessor", "ItemEventGroupMember", "ItemEventGroupDescendant"]
import typing as t
from personal.utils import is_member_of_group
from java.lang import String
from org.openhab.core.items import GenericItem
from org.openhab.core.items.dto import ItemDTO
from org.openhab.core.items.events import (
ItemAddedEvent,
ItemRemovedEvent,
ItemUpdatedEvent,
)
class ItemEventProcessor(object):
__slots__ = ["_added", "_removed", "_updated", "_item", "_old_item"]
def __init__(self, event):
# type: (t.Union[ItemAddedEvent, ItemRemovedEvent, ItemUpdatedEvent]) -> None
self._added = False
self._removed = False
self._updated = False
self._item = None
self._old_item = None
if isinstance(event, ItemAddedEvent):
self._added = True
self._item = event.getItem()
elif isinstance(event, ItemRemovedEvent):
self._removed = True
self._item = event.getItem()
elif isinstance(event, ItemUpdatedEvent):
self._updated = True
self._item = event.getItem()
self._old_item = event.getOldItem()
@property
def added(self):
# type: () -> bool
return self._added
@property
def removed(self):
# type: () -> bool
return self._removed
@property
def updated(self):
# type: () -> bool
return self._updated
@property
def item(self):
# type: () -> ItemDTO
return self._item
@property
def old_item(self):
# type: () -> ItemDTO
return self._old_item
class ItemEventGroupMember(ItemEventProcessor):
__slots__ = ["_is_member", "_was_member"]
_recurse = False
def __init__(
self,
event, # type: t.Union[ItemAddedEvent, ItemRemovedEvent, ItemUpdatedEvent]
group, # type: t.Union[str, String, GenericItem, ItemDTO]
): # type: (...) -> None
super(ItemEventGroupMember, self).__init__(event)
self._is_member = False
self._was_member = False
if self._item:
self._is_member = is_member_of_group(self._item, group, self._recurse)
if self._old_item:
self._was_member = is_member_of_group(self._old_item, group, self._recurse)
else:
self._was_member = self._is_member
@property
def is_member(self):
# type: () -> bool
return self._is_member
@property
def was_member(self):
# type: () -> bool
return self._was_member
@property
def membership_changed(self):
# type: () -> bool
return self._is_member | self._was_member and not (
self._is_member and self._was_member
)
class ItemEventGroupDescendant(ItemEventGroupMember):
_recurse = True |
Looks good, thanks! I have some questions:
When I look among the openHAB community topics, I see this issue multiple times. |
|
I am still experimenting with these functions. How do you create the original triggers here?
Unfortunately, I always get the resolved (old) triggers of the old rule instance, so there is no |
Not easy, surfing through the community forums 🤔 I guess this library needs to save the file metadata in addition to the rule registry (at least the original triggers). This could be done while parsing the rule files. After that we can trigger a reinitialization if those triggers. Second approach is to get "descendant of" trigger into core" and replace it in Third approach is to initialize the file parser to restart the parsing of a whole rule file? |
I added arrays of original triggers of each rule and add this to the following method call:
Unfortunately, I receive the following error: @CrazyIvan359 How can I add those triggers? I would say that I am doing the same as you do. |
You are correct that the In your last post, I'm not sure the exact source of the error, but it is stemming from you passing a |
Maybe this will help, here is a real example of those dicts from one of my other libraries. You don't need the lambda if you aren't pulling the group name from the config. RULE_TRIG = {
"rule_link_changed": ["Member of {group} changed"],
}
RULE_TRIG_MAP = {
"rule_link_changed": lambda: config.GROUPS[KEY_LINKS],
} # type: t.Dict[str, t.Callable[[], str]] Without the lambda the loop would look like this: for trigger in RULE_TRIG[target.__name__]:
when(trigger.format(group="MY_GROUP_NAME"))(target) Which would resolve to |
Ok, my approach will not solve the whole thing, but I was able to trigger a rule reload. I am using the following method and call it manually as soon as I need a refresh of group members: # triggers: array of triggers, e.g. ["Member of GROUP received update"]; target: rule function
def reload_rules(triggers, target):
if not len(triggers) or not target:
Log.logError(
'reload_rules',
'Could not reload rule without triggers or target!'
)
return
UID = getattr(target, "UID", None)
if not UID:
Log.logError(
'reload_rules',
'Could not reload rule without UID in target object!'
)
return
r = ruleRegistry.get(UID)
if not r:
Log.logError(
'reload_rules',
'Could not reload rule - uid {} was not found!'.format(UID)
)
return
del target.triggers
for t in triggers:
when(t)(target)
ruleRegistry.remove(UID)
del UID, target.UID
rule(
r.getName(),
r.getDescription(),
r.getTags()
)(target) Additionally, I also refer to an alternative solution, see here: https://community.openhab.org/t/design-pattern-rule-refresh/102317 Shall we keep this issue open? |
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behavior:
@when("Member of gMyGroup received update")
.gMyGroup
.Expected behavior
All members of the group are part of the rule triggers.
Screenshots
If applicable, add screenshots to help explain your problem.
Environment (please complete the following information):
The text was updated successfully, but these errors were encountered: