Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
opcua-asyncio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nikola Balog
opcua-asyncio
Commits
98e4179e
Commit
98e4179e
authored
May 10, 2016
by
ORD
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #180 from zerox1212/event_history
Events History with Tests
parents
d538b57a
e961b9a1
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
549 additions
and
69 deletions
+549
-69
examples/server-history.py
examples/server-history.py
+35
-3
opcua/common/node.py
opcua/common/node.py
+45
-0
opcua/common/subscription.py
opcua/common/subscription.py
+17
-7
opcua/server/event.py
opcua/server/event.py
+5
-2
opcua/server/history.py
opcua/server/history.py
+82
-14
opcua/server/history_sql.py
opcua/server/history_sql.py
+189
-13
opcua/server/internal_server.py
opcua/server/internal_server.py
+21
-3
opcua/server/internal_subscription.py
opcua/server/internal_subscription.py
+5
-1
opcua/ua/uaevents_auto.py
opcua/ua/uaevents_auto.py
+8
-8
tests/tests_history.py
tests/tests_history.py
+132
-15
tests/tests_server.py
tests/tests_server.py
+10
-3
No files found.
examples/server-history.py
View file @
98e4179e
...
@@ -24,7 +24,27 @@ if __name__ == "__main__":
...
@@ -24,7 +24,27 @@ if __name__ == "__main__":
# populating our address space
# populating our address space
myobj
=
objects
.
add_object
(
idx
,
"MyObject"
)
myobj
=
objects
.
add_object
(
idx
,
"MyObject"
)
myvar
=
myobj
.
add_variable
(
idx
,
"MyVariable"
,
ua
.
Variant
(
0
,
ua
.
VariantType
.
Double
))
myvar
=
myobj
.
add_variable
(
idx
,
"MyVariable"
,
ua
.
Variant
(
0
,
ua
.
VariantType
.
Double
))
myvar
.
set_writable
()
# Set MyVariable to be writable by clients
myvar
.
set_writable
()
# Set MyVariable to be writable by clients
# Creating a custom event: Approach 1
# The custom event object automatically will have members from its parent (BaseEventType)
etype
=
server
.
create_custom_event_type
(
2
,
'MyFirstEvent'
,
ua
.
ObjectIds
.
BaseEventType
,
[(
'MyNumericProperty'
,
ua
.
VariantType
.
Float
),
(
'MyStringProperty'
,
ua
.
VariantType
.
String
)])
# create second event
etype2
=
server
.
create_custom_event_type
(
2
,
'MySecondEvent'
,
ua
.
ObjectIds
.
BaseEventType
,
[(
'MyOtherProperty'
,
ua
.
VariantType
.
Float
)])
myevgen
=
server
.
get_event_generator
(
etype
,
myobj
)
myevgen
.
event
.
Severity
=
500
myevgen
.
event
.
MyStringProperty
=
ua
.
Variant
(
"hello world"
)
myevgen2
=
server
.
get_event_generator
(
etype2
,
myobj
)
myevgen2
.
event
.
Severity
=
123
myevgen2
.
event
.
MyOtherProperty
=
ua
.
Variant
(
1.337
)
serverevgen
=
server
.
get_event_generator
()
serverevgen
.
event
.
Severity
=
111
# Configure server to use sqlite as history database (default is a simple in memory dict)
# Configure server to use sqlite as history database (default is a simple in memory dict)
server
.
iserver
.
history_manager
.
set_storage
(
HistorySQLite
(
":memory:"
))
server
.
iserver
.
history_manager
.
set_storage
(
HistorySQLite
(
":memory:"
))
...
@@ -33,13 +53,25 @@ if __name__ == "__main__":
...
@@ -33,13 +53,25 @@ if __name__ == "__main__":
server
.
start
()
server
.
start
()
# enable history for this particular node, must be called after start since it uses subscription
# enable history for this particular node, must be called after start since it uses subscription
server
.
iserver
.
enable_history
(
myvar
,
period
=
None
,
count
=
100
)
server
.
iserver
.
enable_history_data_change
(
myvar
,
period
=
None
,
count
=
100
)
# enable history for myobj events
server
.
iserver
.
enable_history_event
(
myobj
,
period
=
None
)
# enable history for server events
server_node
=
server
.
get_node
(
ua
.
ObjectIds
.
Server
)
server
.
iserver
.
enable_history_event
(
server_node
,
period
=
None
)
try
:
try
:
count
=
0
count
=
0
while
True
:
while
True
:
time
.
sleep
(
1
)
time
.
sleep
(
1
)
count
+=
0.1
count
+=
0.1
myvar
.
set_value
(
math
.
sin
(
count
))
myvar
.
set_value
(
math
.
sin
(
count
))
myevgen
.
trigger
(
message
=
"This is MyFirstEvent with MyNumericProperty and MyStringProperty."
)
myevgen2
.
trigger
(
message
=
"This is MySecondEvent with MyOtherProperty."
)
serverevgen
.
trigger
(
message
=
"Server Event Message"
)
finally
:
finally
:
#
close connection, remove subcs
riptions, etc
#
close connection, remove subsc
riptions, etc
server
.
stop
()
server
.
stop
()
opcua/common/node.py
View file @
98e4179e
...
@@ -377,6 +377,51 @@ class Node(object):
...
@@ -377,6 +377,51 @@ class Node(object):
result
=
self
.
server
.
history_read
(
params
)[
0
]
result
=
self
.
server
.
history_read
(
params
)[
0
]
return
result
return
result
def
read_event_history
(
self
,
evfilter
,
starttime
=
None
,
endtime
=
None
,
numvalues
=
0
):
"""
Read event history of a source node based on supplied UA EventFilter
result code from server is checked and an exception is raised in case of error
If numvalues is > 0 and number of events in period is > numvalues
then result will be truncated
"""
# FIXME event filter must be supplied externally, the problem is the node class doesn't have a way to get
# FIXME another node from the address space as these methods are at the server level, therefore there is
# FIXME no way to build an event filter here (although it could be nicer for a user who doesn't want a filter)
details
=
ua
.
ReadEventDetails
()
if
starttime
:
details
.
StartTime
=
starttime
else
:
details
.
StartTime
=
ua
.
DateTimeMinValue
if
endtime
:
details
.
EndTime
=
endtime
else
:
details
.
EndTime
=
ua
.
DateTimeMinValue
details
.
NumValuesPerNode
=
numvalues
details
.
Filter
=
evfilter
result
=
self
.
history_read_events
(
details
)
return
result
.
HistoryData
.
Events
def
history_read_events
(
self
,
details
):
"""
Read event history of a node, low-level function
result code from server is checked and an exception is raised in case of error
"""
valueid
=
ua
.
HistoryReadValueId
()
valueid
.
NodeId
=
self
.
nodeid
valueid
.
IndexRange
=
''
params
=
ua
.
HistoryReadParameters
()
params
.
HistoryReadDetails
=
details
params
.
TimestampsToReturn
=
ua
.
TimestampsToReturn
.
Both
params
.
ReleaseContinuationPoints
=
False
params
.
NodesToRead
.
append
(
valueid
)
result
=
self
.
server
.
history_read
(
params
)[
0
]
return
result
# Hack for convenience methods
# Hack for convenience methods
# local import is ugly but necessary for python2 support
# local import is ugly but necessary for python2 support
# feel fri to propose something better but I want to split all those
# feel fri to propose something better but I want to split all those
...
...
opcua/common/subscription.py
View file @
98e4179e
...
@@ -23,19 +23,19 @@ class SubHandler(object):
...
@@ -23,19 +23,19 @@ class SubHandler(object):
def
datachange_notification
(
self
,
node
,
val
,
data
):
def
datachange_notification
(
self
,
node
,
val
,
data
):
"""
"""
called for every datachange notfication from server
called for every datachange not
i
fication from server
"""
"""
pass
pass
def
event_notification
(
self
,
event
):
def
event_notification
(
self
,
event
):
"""
"""
called for every event notfication from server
called for every event not
i
fication from server
"""
"""
pass
pass
def
status_change_notification
(
self
,
status
):
def
status_change_notification
(
self
,
status
):
"""
"""
called for every status change notfication from server
called for every status change not
i
fication from server
"""
"""
pass
pass
...
@@ -52,10 +52,20 @@ class EventResult(object):
...
@@ -52,10 +52,20 @@ class EventResult(object):
return
"EventResult({})"
.
format
([
str
(
k
)
+
":"
+
str
(
v
)
for
k
,
v
in
self
.
__dict__
.
items
()])
return
"EventResult({})"
.
format
([
str
(
k
)
+
":"
+
str
(
v
)
for
k
,
v
in
self
.
__dict__
.
items
()])
__repr__
=
__str__
__repr__
=
__str__
def
get_event_props_as_fields_dict
(
self
):
"""
convert all properties of the EventResult class to a dict of variants
"""
field_vars
=
{}
for
key
,
value
in
vars
(
self
).
items
():
if
not
key
.
startswith
(
"__"
)
and
key
is
not
"server_handle"
:
field_vars
[
key
]
=
ua
.
Variant
(
value
)
return
field_vars
class
SubscriptionItemData
(
object
):
class
SubscriptionItemData
(
object
):
"""
"""
To store useful
l
data from a monitored item
To store useful data from a monitored item
"""
"""
def
__init__
(
self
):
def
__init__
(
self
):
self
.
node
=
None
self
.
node
=
None
...
@@ -74,7 +84,7 @@ class DataChangeNotif(object):
...
@@ -74,7 +84,7 @@ class DataChangeNotif(object):
self
.
subscription_data
=
subscription_data
self
.
subscription_data
=
subscription_data
def
__str__
(
self
):
def
__str__
(
self
):
return
"DataChangeNotfication({}, {})"
.
format
(
self
.
subscription_data
,
self
.
monitored_item
)
return
"DataChangeNot
i
fication({}, {})"
.
format
(
self
.
subscription_data
,
self
.
monitored_item
)
__repr__
=
__str__
__repr__
=
__str__
...
@@ -266,7 +276,7 @@ class Subscription(object):
...
@@ -266,7 +276,7 @@ class Subscription(object):
params
.
ItemsToCreate
=
monitored_items
params
.
ItemsToCreate
=
monitored_items
params
.
TimestampsToReturn
=
ua
.
TimestampsToReturn
.
Neither
params
.
TimestampsToReturn
=
ua
.
TimestampsToReturn
.
Neither
# insert monitor
i
ed item into map to avoid notification arrive before result return
# insert monitored item into map to avoid notification arrive before result return
# server_handle is left as None in purpose as we don't get it yet.
# server_handle is left as None in purpose as we don't get it yet.
with
self
.
_lock
:
with
self
.
_lock
:
for
mi
in
monitored_items
:
for
mi
in
monitored_items
:
...
@@ -319,7 +329,7 @@ def get_event_properties_from_type_node(node):
...
@@ -319,7 +329,7 @@ def get_event_properties_from_type_node(node):
break
break
parents
=
curr_node
.
get_referenced_nodes
(
refs
=
ua
.
ObjectIds
.
HasSubtype
,
direction
=
ua
.
BrowseDirection
.
Inverse
,
includesubtypes
=
False
)
parents
=
curr_node
.
get_referenced_nodes
(
refs
=
ua
.
ObjectIds
.
HasSubtype
,
direction
=
ua
.
BrowseDirection
.
Inverse
,
includesubtypes
=
False
)
if
len
(
parents
)
!=
1
:
# Something went wrong
if
len
(
parents
)
!=
1
:
# Something went wrong
return
None
return
None
curr_node
=
parents
[
0
]
curr_node
=
parents
[
0
]
...
...
opcua/server/event.py
View file @
98e4179e
...
@@ -71,7 +71,10 @@ class EventGenerator(object):
...
@@ -71,7 +71,10 @@ class EventGenerator(object):
#result.StatusCode.check()
#result.StatusCode.check()
def
__str__
(
self
):
def
__str__
(
self
):
return
"EventGenerator(Type:{}, Source:{}, Time:{}, Message: {})"
.
format
(
self
.
EventType
,
self
.
SourceNode
,
self
.
Time
,
self
.
Message
)
return
"EventGenerator(Type:{}, Source:{}, Time:{}, Message: {})"
.
format
(
self
.
event
.
EventType
,
self
.
event
.
SourceNode
,
self
.
event
.
Time
,
self
.
event
.
Message
)
__repr__
=
__str__
__repr__
=
__str__
def
trigger
(
self
,
time
=
None
,
message
=
None
):
def
trigger
(
self
,
time
=
None
,
message
=
None
):
...
@@ -83,7 +86,7 @@ class EventGenerator(object):
...
@@ -83,7 +86,7 @@ class EventGenerator(object):
self
.
event
.
Time
=
time
self
.
event
.
Time
=
time
else
:
else
:
self
.
event
.
Time
=
datetime
.
utcnow
()
self
.
event
.
Time
=
datetime
.
utcnow
()
self
.
event
.
Rec
ie
veTime
=
datetime
.
utcnow
()
self
.
event
.
Rec
ei
veTime
=
datetime
.
utcnow
()
#FIXME: LocalTime is wrong but currently know better. For description s. Part 5 page 18
#FIXME: LocalTime is wrong but currently know better. For description s. Part 5 page 18
self
.
event
.
LocalTime
=
datetime
.
utcnow
()
self
.
event
.
LocalTime
=
datetime
.
utcnow
()
if
message
:
if
message
:
...
...
opcua/server/history.py
View file @
98e4179e
import
logging
from
datetime
import
timedelta
from
datetime
import
timedelta
from
datetime
import
datetime
from
datetime
import
datetime
from
opcua
import
Subscription
from
opcua
import
Subscription
from
opcua
import
ua
from
opcua
import
ua
from
opcua.common
import
utils
from
opcua.common
import
utils
from
opcua.common
import
subscription
class
HistoryStorageInterface
(
object
):
class
HistoryStorageInterface
(
object
):
...
@@ -38,7 +40,7 @@ class HistoryStorageInterface(object):
...
@@ -38,7 +40,7 @@ class HistoryStorageInterface(object):
"""
"""
raise
NotImplementedError
raise
NotImplementedError
def
new_historized_event
(
self
,
event
,
period
):
def
new_historized_event
(
self
,
source_id
,
etype
,
period
):
"""
"""
Called when historization of events is enabled on server side
Called when historization of events is enabled on server side
FIXME: we may need to store events per nodes in future...
FIXME: we may need to store events per nodes in future...
...
@@ -53,7 +55,7 @@ class HistoryStorageInterface(object):
...
@@ -53,7 +55,7 @@ class HistoryStorageInterface(object):
"""
"""
raise
NotImplementedError
raise
NotImplementedError
def
read_event_history
(
self
,
s
tart
,
end
,
evfilter
):
def
read_event_history
(
self
,
s
ource_id
,
start
,
end
,
nb_values
,
evfilter
):
"""
"""
Called when a client make a history read request for events
Called when a client make a history read request for events
Start time and end time are inclusive
Start time and end time are inclusive
...
@@ -118,13 +120,13 @@ class HistoryDict(HistoryStorageInterface):
...
@@ -118,13 +120,13 @@ class HistoryDict(HistoryStorageInterface):
results
=
results
[:
nb_values
]
results
=
results
[:
nb_values
]
return
results
,
cont
return
results
,
cont
def
new_historized_event
(
self
,
event
,
period
):
def
new_historized_event
(
self
,
source_id
,
etype
,
period
):
self
.
_events
=
[]
self
.
_events
=
[]
def
save_event
(
self
,
event
):
def
save_event
(
self
,
event
):
raise
NotImplementedError
raise
NotImplementedError
def
read_event_history
(
self
,
s
tart
,
end
,
evfilter
):
def
read_event_history
(
self
,
s
ource_id
,
start
,
end
,
nb_values
,
evfilter
):
raise
NotImplementedError
raise
NotImplementedError
def
stop
(
self
):
def
stop
(
self
):
...
@@ -144,12 +146,16 @@ class SubHandler(object):
...
@@ -144,12 +146,16 @@ class SubHandler(object):
class
HistoryManager
(
object
):
class
HistoryManager
(
object
):
def
__init__
(
self
,
iserver
):
def
__init__
(
self
,
iserver
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
iserver
=
iserver
self
.
iserver
=
iserver
self
.
storage
=
HistoryDict
()
self
.
storage
=
HistoryDict
()
self
.
_sub
=
None
self
.
_sub
=
None
self
.
_handlers
=
{}
self
.
_handlers
=
{}
def
set_storage
(
self
,
storage
):
def
set_storage
(
self
,
storage
):
"""
set the desired HistoryStorageInterface which History Manager will use for historizing
"""
self
.
storage
=
storage
self
.
storage
=
storage
def
_create_subscription
(
self
,
handler
):
def
_create_subscription
(
self
,
handler
):
...
@@ -162,7 +168,10 @@ class HistoryManager(object):
...
@@ -162,7 +168,10 @@ class HistoryManager(object):
params
.
Priority
=
0
params
.
Priority
=
0
return
Subscription
(
self
.
iserver
.
isession
,
params
,
handler
)
return
Subscription
(
self
.
iserver
.
isession
,
params
,
handler
)
def
historize
(
self
,
node
,
period
=
timedelta
(
days
=
7
),
count
=
0
):
def
historize_data_change
(
self
,
node
,
period
=
timedelta
(
days
=
7
),
count
=
0
):
"""
subscribe to the nodes' data changes and store the data in the active storage
"""
if
not
self
.
_sub
:
if
not
self
.
_sub
:
self
.
_sub
=
self
.
_create_subscription
(
SubHandler
(
self
.
storage
))
self
.
_sub
=
self
.
_create_subscription
(
SubHandler
(
self
.
storage
))
if
node
in
self
.
_handlers
:
if
node
in
self
.
_handlers
:
...
@@ -171,9 +180,32 @@ class HistoryManager(object):
...
@@ -171,9 +180,32 @@ class HistoryManager(object):
handler
=
self
.
_sub
.
subscribe_data_change
(
node
)
handler
=
self
.
_sub
.
subscribe_data_change
(
node
)
self
.
_handlers
[
node
]
=
handler
self
.
_handlers
[
node
]
=
handler
def
historize_event
(
self
,
source
,
period
=
timedelta
(
days
=
7
)):
"""
subscribe to the source nodes' events and store the data in the active storage; custom event properties included
"""
if
not
self
.
_sub
:
self
.
_sub
=
self
.
_create_subscription
(
SubHandler
(
self
.
storage
))
if
source
in
self
.
_handlers
:
raise
ua
.
UaError
(
"Events from {} are already historized"
.
format
(
source
))
# get the event types the source node generates and a list of all possible event fields
event_types
,
ev_fields
=
self
.
_get_source_event_data
(
source
)
self
.
storage
.
new_historized_event
(
source
.
nodeid
,
ev_fields
,
period
)
handler
=
self
.
_sub
.
subscribe_events
(
source
)
# FIXME supply list of event types when master is fixed
self
.
_handlers
[
source
]
=
handler
def
dehistorize
(
self
,
node
):
def
dehistorize
(
self
,
node
):
self
.
_sub
.
unsubscribe
(
self
.
_handlers
[
node
])
"""
del
(
self
.
_handlers
[
node
])
remove subscription to the node/source which is being historized
"""
if
node
in
self
.
_handlers
:
self
.
_sub
.
unsubscribe
(
self
.
_handlers
[
node
])
del
(
self
.
_handlers
[
node
])
else
:
self
.
logger
.
error
(
"History Manager isn't subscribed to %s"
,
node
)
def
read_history
(
self
,
params
):
def
read_history
(
self
,
params
):
"""
"""
...
@@ -190,7 +222,7 @@ class HistoryManager(object):
...
@@ -190,7 +222,7 @@ class HistoryManager(object):
def
_read_history
(
self
,
details
,
rv
):
def
_read_history
(
self
,
details
,
rv
):
"""
"""
read history for a
node
determine if the history read is for a data changes or events; then read the history for that
node
"""
"""
result
=
ua
.
HistoryReadResult
()
result
=
ua
.
HistoryReadResult
()
if
isinstance
(
details
,
ua
.
ReadRawModifiedDetails
):
if
isinstance
(
details
,
ua
.
ReadRawModifiedDetails
):
...
@@ -207,9 +239,10 @@ class HistoryManager(object):
...
@@ -207,9 +239,10 @@ class HistoryManager(object):
result
.
HistoryData
=
ua
.
HistoryEvent
()
result
.
HistoryData
=
ua
.
HistoryEvent
()
# FIXME: filter is a cumbersome type, maybe transform it something easier
# FIXME: filter is a cumbersome type, maybe transform it something easier
# to handle for storage
# to handle for storage
result
.
HistoryData
.
Events
=
self
.
storage
.
read_event_history
(
details
.
StartTime
,
ev
,
cont
=
self
.
_read_event_history
(
rv
,
details
)
details
.
EndTime
,
result
.
HistoryData
.
Events
=
ev
details
.
Filter
)
result
.
ContinuationPoint
=
cont
else
:
else
:
# we do not currently support the other types, clients can process data themselves
# we do not currently support the other types, clients can process data themselves
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadNotImplemented
)
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadNotImplemented
)
...
@@ -222,7 +255,7 @@ class HistoryManager(object):
...
@@ -222,7 +255,7 @@ class HistoryManager(object):
# but they also say we can use cont point as timestamp to enable stateless
# but they also say we can use cont point as timestamp to enable stateless
# implementation. This is contradictory, so we assume details is
# implementation. This is contradictory, so we assume details is
# send correctly with continuation point
# send correctly with continuation point
#starttime = bytes_to_datetime(rv.ContinuationPoint)
#
starttime = bytes_to_datetime(rv.ContinuationPoint)
starttime
=
ua
.
unpack_datetime
(
utils
.
Buffer
(
rv
.
ContinuationPoint
))
starttime
=
ua
.
unpack_datetime
(
utils
.
Buffer
(
rv
.
ContinuationPoint
))
dv
,
cont
=
self
.
storage
.
read_node_history
(
rv
.
NodeId
,
dv
,
cont
=
self
.
storage
.
read_node_history
(
rv
.
NodeId
,
...
@@ -231,12 +264,44 @@ class HistoryManager(object):
...
@@ -231,12 +264,44 @@ class HistoryManager(object):
details
.
NumValuesPerNode
)
details
.
NumValuesPerNode
)
if
cont
:
if
cont
:
# cont = datetime_to_bytes(dv[-1].ServerTimestamp)
# cont = datetime_to_bytes(dv[-1].ServerTimestamp)
cont
=
ua
.
pack_datetime
(
dv
[
-
1
].
ServerTimestamp
)
cont
=
ua
.
pack_datetime
(
cont
)
# FIXME, parse index range and filter out if necessary
# rv.IndexRange
# rv.IndexRange
# rv.DataEncoding # xml or binary, seems spec say we can ignore that one
# rv.DataEncoding # xml or binary, seems spec say we can ignore that one
return
dv
,
cont
return
dv
,
cont
def
_read_event_history
(
self
,
rv
,
details
):
starttime
=
details
.
StartTime
if
rv
.
ContinuationPoint
:
# Spec says we should ignore details if cont point is present
# but they also say we can use cont point as timestamp to enable stateless
# implementation. This is contradictory, so we assume details is
# send correctly with continuation point
# starttime = bytes_to_datetime(rv.ContinuationPoint)
starttime
=
ua
.
unpack_datetime
(
utils
.
Buffer
(
rv
.
ContinuationPoint
))
ev
,
cont
=
self
.
storage
.
read_event_history
(
rv
.
NodeId
,
starttime
,
details
.
EndTime
,
details
.
NumValuesPerNode
,
details
.
Filter
)
if
cont
:
# cont = datetime_to_bytes(dv[-1].ServerTimestamp)
cont
=
ua
.
pack_datetime
(
cont
)
return
ev
,
cont
def
_get_source_event_data
(
self
,
source
):
# get all event types which the source node can generate; get the fields of those event types
event_types
=
source
.
get_referenced_nodes
(
ua
.
ObjectIds
.
GeneratesEvent
)
ev_aggregate_fields
=
[]
for
event_type
in
event_types
:
ev_aggregate_fields
.
extend
((
subscription
.
get_event_properties_from_type_node
(
event_type
)))
ev_fields
=
[]
for
field
in
set
(
ev_aggregate_fields
):
ev_fields
.
append
(
field
.
get_display_name
().
Text
.
decode
(
encoding
=
'utf-8'
))
return
event_types
,
ev_fields
def
update_history
(
self
,
params
):
def
update_history
(
self
,
params
):
"""
"""
Update history for a node
Update history for a node
...
@@ -252,4 +317,7 @@ class HistoryManager(object):
...
@@ -252,4 +317,7 @@ class HistoryManager(object):
return
results
return
results
def
stop
(
self
):
def
stop
(
self
):
"""
call stop methods of active storage interface whenever the server is stopped
"""
self
.
storage
.
stop
()
self
.
storage
.
stop
()
opcua/server/history_sql.py
View file @
98e4179e
This diff is collapsed.
Click to expand it.
opcua/server/internal_server.py
View file @
98e4179e
...
@@ -153,16 +153,16 @@ class InternalServer(object):
...
@@ -153,16 +153,16 @@ class InternalServer(object):
def
create_session
(
self
,
name
,
user
=
User
.
Anonymous
,
external
=
False
):
def
create_session
(
self
,
name
,
user
=
User
.
Anonymous
,
external
=
False
):
return
InternalSession
(
self
,
self
.
aspace
,
self
.
subscription_service
,
name
,
user
=
user
,
external
=
external
)
return
InternalSession
(
self
,
self
.
aspace
,
self
.
subscription_service
,
name
,
user
=
user
,
external
=
external
)
def
enable_history
(
self
,
node
,
period
=
timedelta
(
days
=
7
),
count
=
0
):
def
enable_history
_data_change
(
self
,
node
,
period
=
timedelta
(
days
=
7
),
count
=
0
):
"""
"""
Set attribute Historizing of node to True and start storing data for history
Set attribute Historizing of node to True and start storing data for history
"""
"""
node
.
set_attribute
(
ua
.
AttributeIds
.
Historizing
,
ua
.
DataValue
(
True
))
node
.
set_attribute
(
ua
.
AttributeIds
.
Historizing
,
ua
.
DataValue
(
True
))
node
.
set_attr_bit
(
ua
.
AttributeIds
.
AccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
node
.
set_attr_bit
(
ua
.
AttributeIds
.
AccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
node
.
set_attr_bit
(
ua
.
AttributeIds
.
UserAccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
node
.
set_attr_bit
(
ua
.
AttributeIds
.
UserAccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
self
.
history_manager
.
historize
(
node
,
period
,
count
)
self
.
history_manager
.
historize
_data_change
(
node
,
period
,
count
)
def
disable_history
(
self
,
node
):
def
disable_history
_data_change
(
self
,
node
):
"""
"""
Set attribute Historizing of node to False and stop storing data for history
Set attribute Historizing of node to False and stop storing data for history
"""
"""
...
@@ -171,6 +171,24 @@ class InternalServer(object):
...
@@ -171,6 +171,24 @@ class InternalServer(object):
node
.
unset_attr_bit
(
ua
.
AttributeIds
.
UserAccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
node
.
unset_attr_bit
(
ua
.
AttributeIds
.
UserAccessLevel
,
ua
.
AccessLevel
.
HistoryRead
)
self
.
history_manager
.
dehistorize
(
node
)
self
.
history_manager
.
dehistorize
(
node
)
def
enable_history_event
(
self
,
source
,
period
=
timedelta
(
days
=
7
)):
"""
Set attribute History Read of object events to True and start storing data for history
"""
# to historize events of an object, first check if object supports events
source_event_notifier
=
source
.
get_attribute
(
ua
.
AttributeIds
.
EventNotifier
)
if
source_event_notifier
.
Value
.
Value
&
1
==
1
:
# check bit 0
# if it supports events, turn on bit 2 (enables history read)
source
.
set_attr_bit
(
ua
.
AttributeIds
.
EventNotifier
,
2
)
# send the object to history manager
self
.
history_manager
.
historize_event
(
source
,
period
)
def
disable_history_event
(
self
,
source
):
"""
Set attribute History Read of node to False and stop storing data for history
"""
source
.
unset_attr_bit
(
ua
.
AttributeIds
.
EventNotifier
,
2
)
self
.
history_manager
.
dehistorize
(
source
)
class
InternalSession
(
object
):
class
InternalSession
(
object
):
...
...
opcua/server/internal_subscription.py
View file @
98e4179e
...
@@ -91,7 +91,11 @@ class MonitoredItemService(object):
...
@@ -91,7 +91,11 @@ class MonitoredItemService(object):
if
params
.
ItemToMonitor
.
AttributeId
==
ua
.
AttributeIds
.
EventNotifier
:
if
params
.
ItemToMonitor
.
AttributeId
==
ua
.
AttributeIds
.
EventNotifier
:
self
.
logger
.
info
(
"request to subscribe to events for node %s and attribute %s"
,
params
.
ItemToMonitor
.
NodeId
,
params
.
ItemToMonitor
.
AttributeId
)
self
.
logger
.
info
(
"request to subscribe to events for node %s and attribute %s"
,
params
.
ItemToMonitor
.
NodeId
,
params
.
ItemToMonitor
.
AttributeId
)
if
self
.
aspace
.
get_attribute_value
(
params
.
ItemToMonitor
.
NodeId
,
ua
.
AttributeIds
.
EventNotifier
).
Value
.
Value
!=
1
:
ev_notify_byte
=
self
.
aspace
.
get_attribute_value
(
params
.
ItemToMonitor
.
NodeId
,
ua
.
AttributeIds
.
EventNotifier
).
Value
.
Value
if
ev_notify_byte
is
not
None
:
if
ev_notify_byte
&
1
==
0
:
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadServiceUnsupported
)
else
:
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadServiceUnsupported
)
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadServiceUnsupported
)
result
.
FilterResult
=
ua
.
EventFilterResult
()
result
.
FilterResult
=
ua
.
EventFilterResult
()
for
_
in
params
.
RequestedParameters
.
Filter
.
SelectClauses
:
for
_
in
params
.
RequestedParameters
.
Filter
.
SelectClauses
:
...
...
opcua/ua/uaevents_auto.py
View file @
98e4179e
'''
"""
Example auto_generated file with UA Types
Example auto_generated file with UA Types
For now only events!
For now only events!
'''
"""
from
opcua.ua
import
*
from
opcua.ua
import
*
# TODO: This should be auto
generat
d form XML description of EventTypes
# TODO: This should be auto
generate
d form XML description of EventTypes
class
BaseEvent
(
FrozenClass
):
class
BaseEvent
(
FrozenClass
):
'''
"""
BaseEvent implements BaseEventType from which inherit all other events and it is used per default.
BaseEvent implements BaseEventType from which inherit all other events and it is used per default.
'''
"""
def
__init__
(
self
,
sourcenode
=
None
,
message
=
None
,
severity
=
1
,
extended
=
False
):
def
__init__
(
self
,
sourcenode
=
None
,
message
=
None
,
severity
=
1
,
extended
=
False
):
self
.
EventId
=
bytes
()
self
.
EventId
=
bytes
()
self
.
EventType
=
NodeId
(
ObjectIds
.
BaseEventType
)
self
.
EventType
=
NodeId
(
ObjectIds
.
BaseEventType
)
self
.
SourceNode
=
sourcenode
self
.
SourceNode
=
sourcenode
self
.
SourceName
=
None
self
.
SourceName
=
None
self
.
Time
=
None
self
.
Time
=
None
self
.
Rec
ie
veTime
=
None
self
.
Rec
ei
veTime
=
None
self
.
LocalTime
=
None
self
.
LocalTime
=
None
self
.
Message
=
LocalizedText
(
message
)
self
.
Message
=
LocalizedText
(
message
)
self
.
Severity
=
Variant
(
severity
,
VariantType
.
UInt16
)
self
.
Severity
=
Variant
(
severity
,
VariantType
.
UInt16
)
...
@@ -31,9 +31,9 @@ class BaseEvent(FrozenClass):
...
@@ -31,9 +31,9 @@ class BaseEvent(FrozenClass):
class
AuditEvent
(
BaseEvent
):
class
AuditEvent
(
BaseEvent
):
'''
"""
Audit implements AuditEventType from which inherit all other Audit events.
Audit implements AuditEventType from which inherit all other Audit events.
'''
"""
def
__init__
(
self
,
sourcenode
=
None
,
message
=
None
,
severity
=
1
,
extended
=
False
):
def
__init__
(
self
,
sourcenode
=
None
,
message
=
None
,
severity
=
1
,
extended
=
False
):
super
(
AuditEvent
,
self
).
__init__
(
sourcenode
,
message
,
severity
,
True
)
super
(
AuditEvent
,
self
).
__init__
(
sourcenode
,
message
,
severity
,
True
)
self
.
EventType
=
NodeId
(
ObjectIds
.
AuditEventType
)
self
.
EventType
=
NodeId
(
ObjectIds
.
AuditEventType
)
...
...
tests/tests_history.py
View file @
98e4179e
This diff is collapsed.
Click to expand it.
tests/tests_server.py
View file @
98e4179e
...
@@ -129,14 +129,21 @@ class TestServer(unittest.TestCase, CommonTests):
...
@@ -129,14 +129,21 @@ class TestServer(unittest.TestCase, CommonTests):
val
=
v
.
get_value
()
val
=
v
.
get_value
()
self
.
assertEqual
(
val
,
"StringValue"
)
self
.
assertEqual
(
val
,
"StringValue"
)
def
test_historize
(
self
):
def
test_historize
_variable
(
self
):
o
=
self
.
opc
.
get_objects_node
()
o
=
self
.
opc
.
get_objects_node
()
var
=
o
.
add_variable
(
3
,
"test_hist"
,
1.0
)
var
=
o
.
add_variable
(
3
,
"test_hist"
,
1.0
)
self
.
srv
.
iserver
.
enable_history
(
var
,
timedelta
(
days
=
1
))
self
.
srv
.
iserver
.
enable_history
_data_change
(
var
,
timedelta
(
days
=
1
))
time
.
sleep
(
1
)
time
.
sleep
(
1
)
var
.
set_value
(
2.0
)
var
.
set_value
(
2.0
)
var
.
set_value
(
3.0
)
var
.
set_value
(
3.0
)
self
.
srv
.
iserver
.
disable_history
(
var
)
self
.
srv
.
iserver
.
disable_history_data_change
(
var
)
def
test_historize_events
(
self
):
srv_node
=
self
.
srv
.
get_node
(
ua
.
ObjectIds
.
Server
)
srvevgen
=
self
.
srv
.
get_event_generator
()
self
.
srv
.
iserver
.
enable_history_event
(
srv_node
,
period
=
None
)
srvevgen
.
trigger
(
message
=
"Message"
)
self
.
srv
.
iserver
.
disable_history_event
(
srv_node
)
def
test_references_for_added_nodes_method
(
self
):
def
test_references_for_added_nodes_method
(
self
):
objects
=
self
.
opc
.
get_objects_node
()
objects
=
self
.
opc
.
get_objects_node
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment