Skip to content
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

EventFilter: handle objects in EventTypes #906

Merged
merged 1 commit into from
Jun 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions asyncua/common/events.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import copy

from typing import Dict, List, TYPE_CHECKING
from asyncua import ua
import asyncua
from ..ua.uaerrors import UaError
from .ua_utils import get_node_subtypes
if TYPE_CHECKING:
from asyncua.common.node import Node


class Event:
Expand Down Expand Up @@ -142,34 +144,42 @@ async def get_filter_from_event_type(eventtypes):
return evfilter


async def append_new_attribute_to_select_clauses(attribute, select_clauses, already_selected, parent_variable):
browse_path = []
if parent_variable:
browse_path.append(await parent_variable.read_browse_name())
async def _append_new_attribute_to_select_clauses(attribute: "Node", select_clauses: List[ua.SimpleAttributeOperand], already_selected: Dict[str, str], browse_path: List[ua.QualifiedName]):
browse_path.append(await attribute.read_browse_name())
string_path = '/'.join(map(str, browse_path))
if string_path not in already_selected:
already_selected[string_path] = string_path
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess because filters work on browse_names not on nodeids?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense. i just saw that it's required in the spec. Rather strange to me considering this quote from their docs:

Unlike NodeIds, the BrowseName cannot be used to unambiguously identify a Node. Different Nodes may have the same BrowseName.

op = ua.SimpleAttributeOperand()
op.AttributeId = ua.AttributeIds.Value
op.BrowsePath = browse_path
select_clauses.append(op)
select_clauses.append(op)


async def _select_clause_from_childs(child: "Node", select_clauses: List[ua.SimpleAttributeOperand], already_selected: Dict[str, str], browse_path: List[ua.QualifiedName]):
for property in await child.get_properties():
await _append_new_attribute_to_select_clauses(property, select_clauses, already_selected, [*browse_path])
for variable in await child.get_variables():
await _append_new_attribute_to_select_clauses(variable, select_clauses, already_selected, [*browse_path])
await _select_clause_from_childs(variable, select_clauses, already_selected, browse_path + [await variable.read_browse_name()])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Will "variables" actually have nested objects or do they only contain properties? (Just to make sure I have the definition right, properties are leaf nodes that hold values)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Porperties as of spec shouldn't contain child nodes. A non conforming server could do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for object in await child.get_children(refs=ua.ObjectIds.HasComponent, nodeclassmask=ua.NodeClass.Object):
await _select_clause_from_childs(object, select_clauses, already_selected, browse_path + [await object.read_browse_name()])


async def select_clauses_from_evtype(evtypes):
async def select_clauses_from_evtype(evtypes: List["Node"]):
select_clauses = []
already_selected = {}
for evtype in evtypes:
for property in await get_event_properties_from_type_node(evtype):
await append_new_attribute_to_select_clauses(property, select_clauses, already_selected, None)
await _append_new_attribute_to_select_clauses(property, select_clauses, already_selected, [])
for variable in await get_event_variables_from_type_node(evtype):
await append_new_attribute_to_select_clauses(variable, select_clauses, already_selected, None)
for subproperty in await variable.get_properties():
await append_new_attribute_to_select_clauses(subproperty, select_clauses, already_selected, variable)
await _append_new_attribute_to_select_clauses(variable, select_clauses, already_selected, [])
await _select_clause_from_childs(variable, select_clauses, already_selected, [await variable.read_browse_name()])
for object in await get_event_objects_from_type_node(evtype):
await _select_clause_from_childs(object, select_clauses, already_selected, [await object.read_browse_name()])
return select_clauses


async def where_clause_from_evtype(evtypes):
async def where_clause_from_evtype(evtypes: List["Node"]):
cf = ua.ContentFilter()
el = ua.ContentFilterElement()
# operands can be ElementOperand, LiteralOperand, AttributeOperand, SimpleAttribute
Expand Down Expand Up @@ -197,7 +207,7 @@ async def where_clause_from_evtype(evtypes):
return cf


async def select_event_attributes_from_type_node(node, attributeSelector):
async def select_event_attributes_from_type_node(node: "Node", attributeSelector):
attributes = []
curr_node = node
while True:
Expand All @@ -213,14 +223,18 @@ async def select_event_attributes_from_type_node(node, attributeSelector):
return attributes


async def get_event_properties_from_type_node(node):
async def get_event_properties_from_type_node(node: "Node") -> List[ua.NodeId]:
return await select_event_attributes_from_type_node(node, lambda n: n.get_properties())


async def get_event_variables_from_type_node(node):
async def get_event_variables_from_type_node(node: "Node") -> List[ua.NodeId]:
return await select_event_attributes_from_type_node(node, lambda n: n.get_variables())


async def get_event_objects_from_type_node(node: "Node") -> List[ua.NodeId]:
return await select_event_attributes_from_type_node(node, lambda n: n.get_children(refs=ua.ObjectIds.HasComponent, nodeclassmask=ua.NodeClass.Object))


async def get_event_obj_from_type_node(node):
"""
return an Event object from an event type node
Expand Down
10 changes: 10 additions & 0 deletions tests/test_subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,16 @@ async def test_get_filter_from_ConditionType(opc):
assert systemType.nodeid in operandNodeIds


async def test_get_event_contains_object(opc):
""" Shelving State is a object this should be in the filter list!"""
alarm_type = opc.opc.get_node(ua.ObjectIds.AlarmConditionType)
evfilter = await asyncua.common.events.get_filter_from_event_type([alarm_type])
browsePathList = [o.BrowsePath for o in evfilter.SelectClauses if o.BrowsePath]
[print(b) for b in browsePathList]
browsePathId = [ua.QualifiedName('ShelvingState'), ua.QualifiedName('CurrentState'), ua.QualifiedName('Id')]
assert browsePathId in browsePathList


async def test_get_event_from_type_node_CustomEvent(opc):
etype = await opc.server.create_custom_event_type(
2, 'MyEvent', ua.ObjectIds.AuditEventType,
Expand Down