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
37f69181
Commit
37f69181
authored
Apr 26, 2015
by
Olivier R-D
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
autopep8
parent
e6300bec
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
508 additions
and
445 deletions
+508
-445
client_to_prosys.py
client_to_prosys.py
+5
-5
example-client.py
example-client.py
+12
-9
example-server.py
example-server.py
+16
-11
opcua/__init__.py
opcua/__init__.py
+2
-3
opcua/address_space.py
opcua/address_space.py
+31
-30
opcua/binary_client.py
opcua/binary_client.py
+14
-17
opcua/binary_server.py
opcua/binary_server.py
+7
-5
opcua/client.py
opcua/client.py
+15
-16
opcua/event.py
opcua/event.py
+7
-7
opcua/internal_server.py
opcua/internal_server.py
+15
-12
opcua/node.py
opcua/node.py
+40
-38
opcua/server.py
opcua/server.py
+6
-10
opcua/standard_address_space.py
opcua/standard_address_space.py
+1
-2
opcua/subscription.py
opcua/subscription.py
+11
-10
opcua/subscription_service.py
opcua/subscription_service.py
+21
-25
opcua/uaprocessor.py
opcua/uaprocessor.py
+45
-43
opcua/uaprotocol.py
opcua/uaprotocol.py
+0
-3
opcua/uaprotocol_hand.py
opcua/uaprotocol_hand.py
+36
-20
opcua/uatypes.py
opcua/uatypes.py
+125
-79
opcua/utils.py
opcua/utils.py
+4
-4
tests.py
tests.py
+95
-96
No files found.
client_to_prosys.py
View file @
37f69181
...
...
@@ -3,10 +3,13 @@ import logging
from
opcua
import
Client
from
opcua
import
uaprotocol
as
ua
class
SubHandler
(
object
):
"""
Client to subscription. It will receive events from server
"""
def
data_change
(
self
,
handle
,
node
,
val
,
attr
):
print
(
"Python: New data change event"
,
handle
,
node
,
val
,
attr
)
...
...
@@ -14,8 +17,7 @@ class SubHandler(object):
print
(
"Python: New event"
,
handle
,
event
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
from
IPython
import
embed
logging
.
basicConfig
(
level
=
logging
.
WARN
)
client
=
Client
(
"opc.tcp://localhost:53530/OPCUA/SimulationServer/"
)
...
...
@@ -33,7 +35,7 @@ if __name__ == "__main__":
myuint64
=
client
.
get_node
(
"ns=4;s=UInt64"
)
myint32
=
client
.
get_node
(
"ns=4;s=Int32"
)
myuint32
=
client
.
get_node
(
"ns=4;s=UInt32"
)
var
=
client
.
get_node
(
ua
.
NodeId
(
"Random1"
,
5
))
print
(
"var is: "
,
var
)
print
(
"value of var is: "
,
var
.
get_value
())
...
...
@@ -42,7 +44,6 @@ if __name__ == "__main__":
myfloat
.
set_value
(
ua
.
Variant
(
1.234
,
ua
.
VariantType
.
Float
))
print
(
"reading float value: "
,
myfloat
.
get_value
())
handler
=
SubHandler
()
sub
=
client
.
create_subscription
(
500
,
handler
)
sub
.
subscribe_data_change
(
var
)
...
...
@@ -52,7 +53,6 @@ if __name__ == "__main__":
result
=
device
.
call_method
(
method
,
ua
.
Variant
(
"sin"
),
ua
.
Variant
(
180
,
ua
.
VariantType
.
Double
))
print
(
"Mehtod result is: "
,
result
)
embed
()
client
.
close_session
()
finally
:
...
...
example-client.py
View file @
37f69181
...
...
@@ -5,6 +5,7 @@ try:
from
IPython
import
embed
except
ImportError
:
import
code
def
embed
():
vars
=
globals
()
vars
.
update
(
locals
())
...
...
@@ -15,10 +16,13 @@ except ImportError:
from
opcua
import
Client
from
opcua
import
uaprotocol
as
ua
class
SubHandler
(
object
):
"""
Client to subscription. It will receive events from server
"""
def
data_change
(
self
,
handle
,
node
,
val
,
attr
):
print
(
"Python: New data change event"
,
handle
,
node
,
val
,
attr
)
...
...
@@ -26,11 +30,10 @@ class SubHandler(object):
print
(
"Python: New event"
,
handle
,
event
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
logging
.
basicConfig
(
level
=
logging
.
WARN
)
#logger = logging.getLogger("KeepAlive")
#logger.setLevel(logging.DEBUG)
#
logger.setLevel(logging.DEBUG)
client
=
Client
(
"opc.tcp://localhost:4841/freeopcua/server/"
)
try
:
client
.
connect
()
...
...
@@ -39,8 +42,8 @@ if __name__ == "__main__":
print
(
root
.
get_children
())
print
(
root
.
get_browse_name
())
#var = client.get_node(ua.NodeId(1002, 2))
#print(var)
#print(var.get_value())
#
print(var)
#
print(var.get_value())
#var.set_value(ua.Variant([23], ua.VariantType.Int64))
state
=
root
.
get_child
([
"0:Objects"
,
"0:Server"
])
print
(
state
)
...
...
@@ -52,10 +55,10 @@ if __name__ == "__main__":
handle
=
sub
.
subscribe_data_change
(
myvar
)
time
.
sleep
(
0.1
)
sub
.
subscribe_events
()
#sub.unsubscribe(handle)
#sub.delete()
#calling a method on server
#
sub.unsubscribe(handle)
#
sub.delete()
#
calling a method on server
res
=
obj
.
call_method
(
"2:multiply"
,
3
,
"klk"
)
print
(
"method result is: "
,
res
)
...
...
example-server.py
View file @
37f69181
...
...
@@ -5,6 +5,7 @@ try:
from
IPython
import
embed
except
ImportError
:
import
code
def
embed
():
vars
=
globals
()
vars
.
update
(
locals
())
...
...
@@ -16,38 +17,43 @@ from opcua import ua, uamethod, Server, Event, ObjectIds
class
SubHandler
(
object
):
"""
Client to subscription. It will receive events from server
"""
def
data_change
(
self
,
handle
,
node
,
val
,
attr
):
print
(
"Python: New data change event"
,
handle
,
node
,
val
,
attr
)
def
event
(
self
,
handle
,
event
):
print
(
"Python: New event"
,
handle
,
event
)
#method to be exposed through server
# method to be exposed through server
def
func
(
parent
,
variant
):
return
[
variant
.
Value
*
2
]
#method to be exposed through server
#
method to be exposed through server
# uses a decorator to automatically convert to and from variants
@
uamethod
def
multiply
(
parent
,
x
,
y
):
print
(
"multiply method call with parameters: "
,
x
,
y
)
return
x
*
y
return
x
*
y
if
__name__
==
"__main__"
:
#optional setup logging
#
optional setup logging
logging
.
basicConfig
(
level
=
logging
.
WARN
)
#logger = logging.getLogger("opcua.address_space")
#logger = logging.getLogger("opcua.internal_server")
#logger.setLevel(logging.DEBUG)
#
logger.setLevel(logging.DEBUG)
#logger = logging.getLogger("opcua.subscription_server")
#logger.setLevel(logging.DEBUG)
# logger.setLevel(logging.DEBUG)
# now setup our server
# now setup our server
server
=
Server
()
server
.
set_endpoint
(
"opc.tcp://localhost:4841/freeopcua/server/"
)
server
.
set_server_name
(
"FreeOpcUa Example Server"
)
...
...
@@ -73,13 +79,13 @@ if __name__ == "__main__":
myevent
=
server
.
get_event_object
(
ObjectIds
.
BaseEventType
)
myevent
.
Message
.
Text
=
"This is my event"
myevent
.
Severity
=
300
# starting!
server
.
start
()
print
(
"Available loggers are: "
,
logging
.
Logger
.
manager
.
loggerDict
.
keys
())
try
:
handler
=
SubHandler
()
#enable following if you want to subscribe to nodes on server side
#
enable following if you want to subscribe to nodes on server side
sub
=
server
.
create_subscription
(
500
,
handler
)
handle
=
sub
.
subscribe_data_change
(
myvar
)
# trigger event, all subscribed clients wil receive it
...
...
@@ -88,4 +94,3 @@ if __name__ == "__main__":
embed
()
finally
:
server
.
stop
()
opcua/__init__.py
View file @
37f69181
...
...
@@ -15,7 +15,7 @@ from opcua.server import Server
def
uamethod
(
func
):
"""
Method decorator to automatically convert
Method decorator to automatically convert
arguments and output to and from variants
"""
def
wrapper
(
parent
,
*
args
):
...
...
@@ -23,10 +23,9 @@ def uamethod(func):
return
to_variant
(
result
)
return
wrapper
def
to_variant
(
*
args
):
uaargs
=
[]
for
arg
in
args
:
uaargs
.
append
(
ua
.
Variant
(
arg
))
return
uaargs
opcua/address_space.py
View file @
37f69181
...
...
@@ -4,17 +4,21 @@ import pickle
from
opcua
import
ua
class
AttributeValue
(
object
):
def
__init__
(
self
,
value
):
self
.
value
=
value
self
.
value_callback
=
None
self
.
datachange_callbacks
=
{}
self
.
value_callback
=
None
self
.
datachange_callbacks
=
{}
def
__str__
(
self
):
return
"AttributeValue({})"
.
format
(
self
.
value
)
__repr__
=
__str__
class
NodeData
(
object
):
def
__init__
(
self
,
nodeid
):
self
.
nodeid
=
nodeid
self
.
attributes
=
{}
...
...
@@ -27,6 +31,7 @@ class NodeData(object):
class
AttributeService
(
object
):
def
__init__
(
self
,
aspace
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_aspace
=
aspace
...
...
@@ -45,7 +50,9 @@ class AttributeService(object):
res
.
append
(
self
.
_aspace
.
set_attribute_value
(
writevalue
.
NodeId
,
writevalue
.
AttributeId
,
writevalue
.
Value
))
return
res
class
ViewService
(
object
):
def
__init__
(
self
,
aspace
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_aspace
=
aspace
...
...
@@ -132,7 +139,7 @@ class ViewService(object):
current
=
nodeid
target
=
ua
.
BrowsePathTarget
()
target
.
TargetId
=
current
target
.
RemainingPathIndex
=
4294967295
target
.
RemainingPathIndex
=
4294967295
res
.
Targets
=
[
target
]
return
res
...
...
@@ -140,7 +147,7 @@ class ViewService(object):
with
self
.
_aspace
.
lock
:
nodedata
=
self
.
_aspace
.
nodes
[
nodeid
]
for
ref
in
nodedata
.
references
:
#FIXME: here we should check other arguments!!
#
FIXME: here we should check other arguments!!
if
ref
.
BrowseName
==
el
.
TargetName
:
return
ref
.
NodeId
self
.
logger
.
info
(
"element %s was not found in node %s"
,
el
,
nodeid
)
...
...
@@ -148,6 +155,7 @@ class ViewService(object):
class
NodeManagementService
(
object
):
def
__init__
(
self
,
aspace
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_aspace
=
aspace
...
...
@@ -167,19 +175,19 @@ class NodeManagementService(object):
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadNodeIdExists
)
return
result
nodedata
=
NodeData
(
item
.
RequestedNewNodeId
)
#add common attrs
#
add common attrs
nodedata
.
attributes
[
ua
.
AttributeIds
.
NodeId
]
=
AttributeValue
(
ua
.
DataValue
(
ua
.
Variant
(
item
.
RequestedNewNodeId
,
ua
.
VariantType
.
NodeId
)))
nodedata
.
attributes
[
ua
.
AttributeIds
.
BrowseName
]
=
AttributeValue
(
ua
.
DataValue
(
ua
.
Variant
(
item
.
BrowseName
,
ua
.
VariantType
.
QualifiedName
)))
nodedata
.
attributes
[
ua
.
AttributeIds
.
NodeClass
]
=
AttributeValue
(
ua
.
DataValue
(
ua
.
Variant
(
item
.
NodeClass
,
ua
.
VariantType
.
Int32
)))
#add requested attrs
#
add requested attrs
self
.
_add_nodeattributes
(
item
.
NodeAttributes
,
nodedata
)
#add parent
#
add parent
if
item
.
ParentNodeId
==
ua
.
NodeId
():
#self.logger.warning("add_node: creating node %s without parent", item.RequestedNewNodeId)
#self.logger.warning("add_node: creating node %s without parent", item.RequestedNewNodeId)
pass
elif
not
item
.
ParentNodeId
in
self
.
_aspace
.
nodes
:
#self.logger.warning("add_node: while adding node %s, requested parent node %s does not exists", item.RequestedNewNodeId, item.ParentNodeId)
#self.logger.warning("add_node: while adding node %s, requested parent node %s does not exists", item.RequestedNewNodeId, item.ParentNodeId)
result
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadParentNodeIdInvalid
)
return
result
else
:
...
...
@@ -192,11 +200,11 @@ class NodeManagementService(object):
desc
.
TypeDefinition
=
item
.
TypeDefinition
desc
.
IsForward
=
True
self
.
_aspace
.
nodes
[
item
.
ParentNodeId
].
references
.
append
(
desc
)
#now add our node to db
#
now add our node to db
self
.
_aspace
.
nodes
[
item
.
RequestedNewNodeId
]
=
nodedata
#add type definition
#
add type definition
if
item
.
TypeDefinition
!=
ua
.
NodeId
():
addref
=
ua
.
AddReferencesItem
()
addref
.
SourceNodeId
=
item
.
RequestedNewNodeId
...
...
@@ -230,10 +238,10 @@ class NodeManagementService(object):
rdesc
.
NodeClass
=
addref
.
TargetNodeClass
bname
=
self
.
_aspace
.
get_attribute_value
(
addref
.
TargetNodeId
,
ua
.
AttributeIds
.
BrowseName
).
Value
.
Value
if
bname
:
rdesc
.
BrowseName
=
bname
rdesc
.
BrowseName
=
bname
dname
=
self
.
_aspace
.
get_attribute_value
(
addref
.
TargetNodeId
,
ua
.
AttributeIds
.
DisplayName
).
Value
.
Value
if
dname
:
rdesc
.
DisplayName
=
dname
rdesc
.
DisplayName
=
dname
self
.
_aspace
.
nodes
[
addref
.
SourceNodeId
].
references
.
append
(
rdesc
)
return
ua
.
StatusCode
()
...
...
@@ -241,7 +249,7 @@ class NodeManagementService(object):
if
item
.
SpecifiedAttributes
&
getattr
(
ua
.
NodeAttributesMask
,
name
):
dv
=
ua
.
DataValue
(
ua
.
Variant
(
getattr
(
item
,
name
),
vtype
))
nodedata
.
attributes
[
getattr
(
ua
.
AttributeIds
,
name
)]
=
AttributeValue
(
dv
)
def
_add_nodeattributes
(
self
,
item
,
nodedata
):
item
=
ua
.
downcast_extobject
(
item
)
self
.
_add_node_attr
(
item
,
nodedata
,
"AccessLevel"
,
ua
.
VariantType
.
Byte
)
...
...
@@ -270,6 +278,7 @@ class NodeManagementService(object):
class
MethodService
(
object
):
def
__init__
(
self
,
aspace
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_aspace
=
aspace
...
...
@@ -281,9 +290,9 @@ class MethodService(object):
results
.
append
(
self
.
_call
(
method
))
return
results
def
_call
(
self
,
method
):
def
_call
(
self
,
method
):
res
=
ua
.
CallMethodResult
()
if
method
.
ObjectId
not
in
self
.
_aspace
.
nodes
or
method
.
MethodId
not
in
self
.
_aspace
.
nodes
:
if
method
.
ObjectId
not
in
self
.
_aspace
.
nodes
or
method
.
MethodId
not
in
self
.
_aspace
.
nodes
:
res
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadNodeIdInvalid
)
else
:
node
=
self
.
_aspace
.
nodes
[
method
.
MethodId
]
...
...
@@ -298,19 +307,21 @@ class MethodService(object):
self
.
logger
.
exception
(
"Error executing method call %s, an exception was raised: "
,
method
)
res
.
StatusCode
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadUnexpectedError
)
return
res
class
AddressSpace
(
object
):
"""
The address space object stores all the nodes og the OPC-UA server
and helper methods.
It is NOT threadsafe, lock the lock object before reading of modifying
a node
"""
def
__init__
(
self
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
nodes
=
{}
self
.
lock
=
RLock
()
#
FIXME: should use multiple reader, one writter pattern
self
.
lock
=
RLock
()
#
FIXME: should use multiple reader, one writter pattern
self
.
_datachange_callback_counter
=
200
self
.
_handle_to_attribute_map
=
{}
...
...
@@ -386,13 +397,3 @@ class AddressSpace(object):
with
self
.
lock
:
node
=
self
.
nodes
[
methodid
]
node
.
call
=
callback
opcua/binary_client.py
View file @
37f69181
...
...
@@ -12,6 +12,7 @@ import opcua.utils as utils
class
BinaryClient
(
object
):
"""
low level OPC-UA client.
implement all(well..one day) methods defined in opcua spec
...
...
@@ -19,11 +20,12 @@ class BinaryClient(object):
in python most of the structures are defined in
uaprotocol_auto.py and uaprotocol_hand.py
"""
def
__init__
(
self
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
_socket
=
None
self
.
_do_stop
=
False
self
.
_security_token
=
ua
.
ChannelSecurityToken
()
self
.
_security_token
=
ua
.
ChannelSecurityToken
()
self
.
_authentication_token
=
ua
.
NodeId
()
self
.
_sequence_number
=
0
self
.
_request_id
=
0
...
...
@@ -43,9 +45,9 @@ class BinaryClient(object):
self
.
_thread
.
start
()
def
_send_request
(
self
,
request
,
callback
=
None
,
timeout
=
1000
):
#HACK to make sure we can convert our request to binary before increasing request counter etc ...
#
HACK to make sure we can convert our request to binary before increasing request counter etc ...
request
.
to_binary
()
#END HACK
#
END HACK
with
self
.
_lock
:
request
.
RequestHeader
=
self
.
_create_request_header
(
timeout
)
hdr
=
ua
.
Header
(
ua
.
MessageType
.
SecureMessage
,
ua
.
ChunkType
.
Single
,
self
.
_security_token
.
ChannelId
)
...
...
@@ -62,7 +64,7 @@ class BinaryClient(object):
return
data
def
_check_answer
(
self
,
data
,
context
):
data
=
data
.
copy
(
50
)
#
FIXME check max length nodeid + responseheader
data
=
data
.
copy
(
50
)
#
FIXME check max length nodeid + responseheader
typeid
=
ua
.
NodeId
.
from_binary
(
data
)
if
typeid
==
ua
.
FourByteNodeId
(
ua
.
ObjectIds
.
ServiceFault_Encoding_DefaultBinary
):
self
.
logger
.
warning
(
"ServiceFault from server received %s"
,
context
)
...
...
@@ -159,14 +161,13 @@ class BinaryClient(object):
hdr
.
RequestId
=
self
.
_request_id
return
hdr
def
connect_socket
(
self
,
host
,
port
):
"""
connect to server socket and start receiving thread
"""
self
.
logger
.
info
(
"opening connection"
)
self
.
_socket
=
socket
.
create_connection
((
host
,
port
))
self
.
_socket
.
setsockopt
(
socket
.
IPPROTO_TCP
,
socket
.
TCP_NODELAY
,
1
)
#
nodelay ncessary to avoid packing in one frame, some servers do not like it
self
.
_socket
.
setsockopt
(
socket
.
IPPROTO_TCP
,
socket
.
TCP_NODELAY
,
1
)
#
nodelay ncessary to avoid packing in one frame, some servers do not like it
self
.
start
()
def
disconnect_socket
(
self
):
...
...
@@ -183,7 +184,7 @@ class BinaryClient(object):
self
.
_callbackmap
[
0
]
=
future
self
.
_write_socket
(
header
,
hello
)
return
ua
.
Acknowledge
.
from_binary
(
future
.
result
())
def
open_secure_channel
(
self
,
params
):
self
.
logger
.
info
(
"open_secure_channel"
)
request
=
ua
.
OpenSecureChannelRequest
()
...
...
@@ -229,7 +230,7 @@ class BinaryClient(object):
request
.
DeleteSubscriptions
=
deletesubscriptions
data
=
self
.
_send_request
(
request
)
ua
.
CloseSessionResponse
.
from_binary
(
data
)
#response.ResponseHeader.ServiceResult.check() #disabled, it seems we sent wrong session Id, but where is the sessionId supposed to be sent???
#
response.ResponseHeader.ServiceResult.check() #disabled, it seems we sent wrong session Id, but where is the sessionId supposed to be sent???
def
browse
(
self
,
parameters
):
self
.
logger
.
info
(
"browse"
)
...
...
@@ -281,7 +282,7 @@ class BinaryClient(object):
seqhdr
=
self
.
_create_sequence_header
()
self
.
_write_socket
(
hdr
,
symhdr
,
seqhdr
,
request
)
#some servers send a response here, most do not ... so we ignore
#
some servers send a response here, most do not ... so we ignore
def
translate_browsepaths_to_nodeids
(
self
,
browsepaths
):
self
.
logger
.
info
(
"translate_browsepath_to_nodeid"
)
...
...
@@ -315,7 +316,7 @@ class BinaryClient(object):
def
publish
(
self
,
acks
=
None
):
self
.
logger
.
info
(
"publish"
)
if
acks
is
None
:
if
acks
is
None
:
acks
=
[]
request
=
ua
.
PublishRequest
()
request
.
Parameters
.
SubscriptionAcknowledgements
=
acks
...
...
@@ -326,10 +327,9 @@ class BinaryClient(object):
response
=
ua
.
PublishResponse
.
from_binary
(
future
.
result
())
try
:
self
.
_publishcallbacks
[
response
.
Parameters
.
SubscriptionId
](
response
.
Parameters
)
except
Exception
as
ex
:
#
we call client code, catch everything!
except
Exception
as
ex
:
#
we call client code, catch everything!
self
.
logger
.
exception
(
"Exception while calling user callback"
)
def
create_monitored_items
(
self
,
params
):
self
.
logger
.
info
(
"create_monitored_items"
)
request
=
ua
.
CreateMonitoredItemsRequest
()
...
...
@@ -356,14 +356,11 @@ class BinaryClient(object):
response
=
ua
.
AddNodesResponse
.
from_binary
(
data
)
response
.
ResponseHeader
.
ServiceResult
.
check
()
return
response
.
Results
def
call
(
self
,
methodstocall
):
request
=
ua
.
CallRequest
()
request
.
Parameters
.
MethodsToCall
=
methodstocall
request
.
Parameters
.
MethodsToCall
=
methodstocall
data
=
self
.
_send_request
(
request
)
response
=
ua
.
CallResponse
.
from_binary
(
data
)
response
.
ResponseHeader
.
ServiceResult
.
check
()
return
response
.
Results
opcua/binary_server.py
View file @
37f69181
...
...
@@ -14,10 +14,13 @@ from opcua.uaprocessor import UAProcessor
logger
=
logging
.
getLogger
(
__name__
)
class
BinaryServer
(
Thread
):
"""
Socket server forwarding request to internal server
"""
def
__init__
(
self
,
internal_server
,
hostname
,
port
):
Thread
.
__init__
(
self
)
self
.
socket_server
=
None
...
...
@@ -33,10 +36,10 @@ class BinaryServer(Thread):
def
run
(
self
):
logger
.
warning
(
"Listening on %s:%s"
,
self
.
hostname
,
self
.
port
)
socketserver
.
TCPServer
.
allow_reuse_address
=
True
#
get rid of address already in used warning
socketserver
.
TCPServer
.
allow_reuse_address
=
True
#
get rid of address already in used warning
self
.
socket_server
=
ThreadingTCPServer
((
self
.
hostname
,
self
.
port
),
UAHandler
)
#self.socket_server.daemon_threads = True # this will force a shutdown of all threads, maybe too hard
self
.
socket_server
.
internal_server
=
self
.
iserver
#
allow handler to acces server properties
#
self.socket_server.daemon_threads = True # this will force a shutdown of all threads, maybe too hard
self
.
socket_server
.
internal_server
=
self
.
iserver
#
allow handler to acces server properties
with
self
.
_cond
:
self
.
_cond
.
notify_all
()
self
.
socket_server
.
serve_forever
()
...
...
@@ -47,6 +50,7 @@ class BinaryServer(Thread):
class
UAHandler
(
socketserver
.
BaseRequestHandler
):
"""
The RequestHandler class for our server.
...
...
@@ -65,5 +69,3 @@ class UAHandler(socketserver.BaseRequestHandler):
class
ThreadingTCPServer
(
socketserver
.
ThreadingMixIn
,
socketserver
.
TCPServer
):
pass
opcua/client.py
View file @
37f69181
from
__future__
import
division
#
support for python2
from
__future__
import
division
#
support for python2
from
threading
import
Thread
,
Condition
import
logging
try
:
from
urllib.parse
import
urlparse
except
ImportError
:
#
support for python2
except
ImportError
:
#
support for python2
from
urlparse
import
urlparse
from
opcua
import
uaprotocol
as
ua
...
...
@@ -12,14 +12,16 @@ from opcua import utils
class
KeepAlive
(
Thread
):
"""
Used by Client to keep session opened.
OPCUA defines timeout both for sessions and secure channel
"""
def
__init__
(
self
,
client
,
timeout
):
Thread
.
__init__
(
self
)
self
.
logger
=
logging
.
getLogger
(
__name__
)
if
timeout
==
0
:
# means no timeout bu we do not trust such servers
if
timeout
==
0
:
# means no timeout bu we do not trust such servers
timeout
=
360000
self
.
timeout
=
timeout
self
.
client
=
client
...
...
@@ -31,7 +33,7 @@ class KeepAlive(Thread):
server_state
=
self
.
client
.
get_node
(
ua
.
FourByteNodeId
(
ua
.
ObjectIds
.
Server_ServerStatus_State
))
while
not
self
.
_dostop
:
with
self
.
_cond
:
self
.
_cond
.
wait
(
self
.
timeout
/
1000
)
self
.
_cond
.
wait
(
self
.
timeout
/
1000
)
if
self
.
_dostop
:
break
self
.
logger
.
debug
(
"renewing channel"
)
...
...
@@ -48,8 +50,9 @@ class KeepAlive(Thread):
class
Client
(
object
):
"""
High level client to connect to an OPC-UA server.
High level client to connect to an OPC-UA server.
This class makes it easy to connect and browse address space.
It attemps to expose as much functionality as possible
but if you want to do to special things you will probably need
...
...
@@ -57,6 +60,7 @@ class Client(object):
which offers a raw OPC-UA interface.
"""
def
__init__
(
self
,
url
):
"""
used url argument to connect to server.
...
...
@@ -65,8 +69,8 @@ class Client(object):
"""
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
server_url
=
urlparse
(
url
)
self
.
name
=
"Pure Python Client"
self
.
description
=
self
.
name
self
.
name
=
"Pure Python Client"
self
.
description
=
self
.
name
self
.
application_uri
=
"urn:freeopcua:client"
self
.
product_uri
=
"urn:freeopcua.github.no:client"
self
.
security_policy_uri
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
...
...
@@ -124,7 +128,7 @@ class Client(object):
Send OPC-UA hello to server
"""
ack
=
self
.
bclient
.
send_hello
(
self
.
server_url
.
geturl
())
#FIXME check ack
#
FIXME check ack
def
open_secure_channel
(
self
,
renew
=
False
):
"""
...
...
@@ -161,14 +165,14 @@ class Client(object):
params
=
ua
.
CreateSessionParameters
()
params
.
ClientNonce
=
utils
.
create_nonce
()
params
.
ClientCertificate
=
b''
params
.
ClientDescription
=
desc
params
.
ClientDescription
=
desc
params
.
EndpointUrl
=
self
.
server_url
.
geturl
()
params
.
SessionName
=
self
.
description
+
" Session"
+
str
(
self
.
_session_counter
)
params
.
RequestedSessionTimeout
=
3600000
params
.
MaxResponseMessageSize
=
0
#
means not max size
params
.
MaxResponseMessageSize
=
0
#
means not max size
response
=
self
.
bclient
.
create_session
(
params
)
self
.
session_timeout
=
response
.
RevisedSessionTimeout
self
.
keepalive
=
KeepAlive
(
self
,
min
(
self
.
session_timeout
,
self
.
secure_channel_timeout
)
*
0.7
)
#
0.7 is from spec
self
.
keepalive
=
KeepAlive
(
self
,
min
(
self
.
session_timeout
,
self
.
secure_channel_timeout
)
*
0.7
)
#
0.7 is from spec
self
.
keepalive
.
start
()
return
response
...
...
@@ -224,8 +228,3 @@ class Client(object):
def
get_namespace_index
(
self
,
uri
):
uries
=
self
.
get_namespace_array
()
return
uries
.
index
(
uri
)
opcua/event.py
View file @
37f69181
...
...
@@ -9,6 +9,7 @@ import uuid
class
Event
(
object
):
"""
Create an event based on an event type. Per default is BaseEventType used.
arguments are:
...
...
@@ -16,6 +17,7 @@ class Event(object):
source: The emiting source for the node, either an objectId, NodeId or a Node
etype: The event type, either an objectId, a NodeId or a Node object
"""
def
__init__
(
self
,
isession
,
etype
=
ObjectIds
.
BaseEventType
,
source
=
ObjectIds
.
Server
):
self
.
isession
=
isession
...
...
@@ -25,16 +27,16 @@ class Event(object):
self
.
node
=
Node
(
self
.
isession
,
etype
)
else
:
self
.
node
=
Node
(
self
.
isession
,
ua
.
NodeId
(
etype
))
self
.
set_members_from_node
(
self
.
node
)
if
isinstance
(
source
,
Node
):
self
.
SourceNode
=
source
.
NodeId
self
.
SourceNode
=
source
.
NodeId
elif
isinstance
(
etype
,
ua
.
NodeId
):
self
.
SourceNode
=
source
.
NodeId
self
.
SourceNode
=
source
.
NodeId
else
:
self
.
SourceNode
=
ua
.
NodeId
(
source
)
#set some default values for attributes from BaseEventType, thus that all event must have
#
set some default values for attributes from BaseEventType, thus that all event must have
self
.
EventId
=
uuid
.
uuid4
().
bytes
self
.
EventType
=
self
.
node
.
nodeid
self
.
LocaleTime
=
datetime
.
now
()
...
...
@@ -44,7 +46,7 @@ class Event(object):
self
.
Severity
=
ua
.
Variant
(
1
,
ua
.
VariantType
.
UInt16
)
self
.
SourceName
=
"Server"
#og set some node attributed we also are expected to have
#
og set some node attributed we also are expected to have
self
.
BrowseName
=
self
.
node
.
get_browse_name
()
self
.
DisplayName
=
self
.
node
.
get_display_name
()
self
.
NodeId
=
self
.
node
.
nodeid
...
...
@@ -63,5 +65,3 @@ class Event(object):
for
desc
in
references
:
node
=
Node
(
self
.
isession
,
desc
.
NodeId
)
setattr
(
self
,
desc
.
BrowseName
.
Name
,
node
.
get_value
())
opcua/internal_server.py
View file @
37f69181
...
...
@@ -10,7 +10,7 @@ from threading import Thread
from
enum
import
Enum
import
functools
try
:
#we prefer to use bundles asyncio version, otherwise fallback to trollius
#
we prefer to use bundles asyncio version, otherwise fallback to trollius
import
asyncio
except
ImportError
:
import
trollius
as
asyncio
...
...
@@ -30,6 +30,7 @@ from opcua import standard_address_space
class
ThreadLoop
(
Thread
):
def
__init__
(
self
):
Thread
.
__init__
(
self
)
self
.
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -69,7 +70,9 @@ class SessionState(Enum):
Activated
=
1
Closed
=
2
class
InternalServer
(
object
):
def
__init__
(
self
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
endpoints
=
[]
...
...
@@ -81,13 +84,13 @@ class InternalServer(object):
self
.
method_service
=
MethodService
(
self
.
aspace
)
self
.
node_mgt_service
=
NodeManagementService
(
self
.
aspace
)
standard_address_space
.
fill_address_space
(
self
.
node_mgt_service
)
#standard_address_space.fill_address_space_from_disk(self.aspace)
#
standard_address_space.fill_address_space_from_disk(self.aspace)
self
.
loop
=
ThreadLoop
()
self
.
subcsription_service
=
SubscriptionService
(
self
.
loop
,
self
.
aspace
)
# create a session to use on server side
self
.
isession
=
InternalSession
(
self
,
self
.
aspace
,
self
.
subcsription_service
,
"Internal"
)
self
.
isession
=
InternalSession
(
self
,
self
.
aspace
,
self
.
subcsription_service
,
"Internal"
)
self
.
_timer
=
None
self
.
current_time_node
=
Node
(
self
.
isession
,
ua
.
NodeId
(
ua
.
ObjectIds
.
Server_ServerStatus_CurrentTime
))
uries
=
[
"http://opcfoundation.org/UA/"
]
...
...
@@ -100,7 +103,7 @@ class InternalServer(object):
def
dump_address_space
(
self
,
path
):
self
.
aspace
.
dump
(
path
)
def
start
(
self
):
def
start
(
self
):
self
.
logger
.
info
(
"starting internal server"
)
self
.
loop
.
start
()
Node
(
self
.
isession
,
ua
.
NodeId
(
ua
.
ObjectIds
.
Server_ServerStatus_State
)).
set_value
(
0
)
...
...
@@ -124,15 +127,17 @@ class InternalServer(object):
def
get_endpoints
(
self
,
params
=
None
):
self
.
logger
.
info
(
"get endpoint"
)
#FIXME check params
#
FIXME check params
return
self
.
endpoints
[:]
def
create_session
(
self
,
name
):
return
InternalSession
(
self
,
self
.
aspace
,
self
.
subcsription_service
,
name
)
class
InternalSession
(
object
):
_counter
=
10
_auth_counter
=
1000
def
__init__
(
self
,
internal_server
,
aspace
,
submgr
,
name
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
iserver
=
internal_server
...
...
@@ -144,14 +149,14 @@ class InternalSession(object):
InternalSession
.
_counter
+=
1
self
.
authentication_token
=
ua
.
NodeId
(
self
.
_auth_counter
)
InternalSession
.
_auth_counter
+=
1
self
.
nonce
=
utils
.
create_nonce
()
self
.
nonce
=
utils
.
create_nonce
()
self
.
subscriptions
=
[]
self
.
logger
.
warning
(
"Created internal session %s"
,
self
.
name
)
self
.
_lock
=
Lock
()
def
__str__
(
self
):
return
"InternalSession(name:{}, id:{}, auth_token:{})"
.
format
(
self
.
name
,
self
.
session_id
,
self
.
authentication_token
)
def
get_endpoints
(
self
,
params
=
None
):
return
self
.
iserver
.
get_endpoints
(
params
)
...
...
@@ -160,7 +165,7 @@ class InternalSession(object):
result
=
ua
.
CreateSessionResult
()
result
.
SessionId
=
self
.
session_id
result
.
AuthenticationToken
=
self
.
authentication_token
result
.
AuthenticationToken
=
self
.
authentication_token
result
.
RevisedSessionTimeout
=
params
.
RequestedSessionTimeout
result
.
MaxRequestMessageSize
=
65536
result
.
ServerNonce
=
self
.
nonce
...
...
@@ -230,10 +235,8 @@ class InternalSession(object):
def
delete_monitored_items
(
self
,
params
):
return
self
.
subscription_service
.
delete_monitored_items
(
params
)
def
publish
(
self
,
acks
=
None
):
if
acks
is
None
:
acks
=
[]
return
self
.
subscription_service
.
publish
(
acks
)
opcua/node.py
View file @
37f69181
"""
High level node object, to access node attribute
High level node object, to access node attribute
and browse address space
"""
import
opcua.uaprotocol
as
ua
class
Node
(
object
):
"""
High level node object, to access node attribute,
browse and populate address space
"""
def
__init__
(
self
,
server
,
nodeid
):
self
.
server
=
server
self
.
nodeid
=
None
...
...
@@ -21,6 +24,7 @@ class Node(object):
self
.
nodeid
=
ua
.
NodeId
(
nodeid
,
0
)
else
:
raise
Exception
(
"argument to node must be a NodeId object or a string defining a nodeid found {} of type {}"
.
format
(
nodeid
,
type
(
nodeid
)))
def
__eq__
(
self
,
other
):
if
isinstance
(
other
,
Node
)
and
self
.
nodeid
==
other
.
nodeid
:
return
True
...
...
@@ -61,7 +65,7 @@ class Node(object):
def
get_value
(
self
):
"""
Get value of a node. Only variables(properties) have values.
Get value of a node. Only variables(properties) have values.
An exception will be generated for other node types.
"""
result
=
self
.
get_attribute
(
ua
.
AttributeIds
.
Value
)
...
...
@@ -69,11 +73,11 @@ class Node(object):
def
set_value
(
self
,
value
,
varianttype
=
None
):
"""
Set value of a node. Only variables(properties) have values.
Set value of a node. Only variables(properties) have values.
An exception will be generated for other node types.
"""
variant
=
None
if
type
(
value
)
==
ua
.
Variant
:
if
isinstance
(
value
,
ua
.
Variant
)
:
variant
=
value
else
:
variant
=
ua
.
Variant
(
value
,
varianttype
)
...
...
@@ -148,7 +152,7 @@ class Node(object):
desc
.
IncludeSubtypes
=
includesubtypes
desc
.
NodeClassMask
=
nodeclassmask
desc
.
ResultMask
=
ua
.
BrowseResultMask
.
All
desc
.
NodeId
=
self
.
nodeid
params
=
ua
.
BrowseParameters
()
params
.
NodesToBrowse
.
append
(
desc
)
...
...
@@ -172,7 +176,7 @@ class Node(object):
el
.
ReferenceTypeId
=
ua
.
TwoByteNodeId
(
ua
.
ObjectIds
.
HierarchicalReferences
)
el
.
IsInverse
=
False
el
.
IncludeSubtypes
=
True
if
type
(
item
)
==
ua
.
QualifiedName
:
if
isinstance
(
item
,
ua
.
QualifiedName
)
:
el
.
TargetName
=
item
else
:
el
.
TargetName
=
ua
.
QualifiedName
.
from_string
(
item
)
...
...
@@ -183,7 +187,7 @@ class Node(object):
result
=
self
.
server
.
translate_browsepaths_to_nodeids
([
bpath
])
result
=
result
[
0
]
result
.
StatusCode
.
check
()
#FIXME: seems this method may return several nodes
#
FIXME: seems this method may return several nodes
return
Node
(
self
.
server
,
result
.
Targets
[
0
].
TargetId
)
def
add_folder
(
self
,
*
args
):
...
...
@@ -194,11 +198,11 @@ class Node(object):
"""
nodeid
,
qname
=
self
.
_parse_add_args
(
*
args
)
return
self
.
_add_folder
(
nodeid
,
qname
)
def
_add_folder
(
self
,
nodeid
,
qname
):
node
=
ua
.
AddNodesItem
()
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
NodeClass
=
ua
.
NodeClass
.
Object
node
.
ParentNodeId
=
self
.
nodeid
node
.
ReferenceTypeId
=
ua
.
NodeId
.
from_string
(
"i=35"
)
...
...
@@ -211,7 +215,7 @@ class Node(object):
results
=
self
.
server
.
add_nodes
([
node
])
results
[
0
].
StatusCode
.
check
()
return
Node
(
self
.
server
,
nodeid
)
def
add_object
(
self
,
*
args
):
"""
create a child node object
...
...
@@ -220,13 +224,13 @@ class Node(object):
"""
nodeid
,
qname
=
self
.
_parse_add_args
(
*
args
)
return
self
.
_add_object
(
nodeid
,
qname
)
def
_add_object
(
self
,
nodeid
,
qname
):
node
=
ua
.
AddNodesItem
()
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
NodeClass
=
ua
.
NodeClass
.
Object
node
.
ParentNodeId
=
self
.
nodeid
node
.
ParentNodeId
=
self
.
nodeid
node
.
ReferenceTypeId
=
ua
.
NodeId
.
from_string
(
"i=35"
)
node
.
TypeDefinition
=
ua
.
NodeId
(
ua
.
ObjectIds
.
BaseObjectType
)
attrs
=
ua
.
ObjectAttributes
()
...
...
@@ -247,7 +251,7 @@ class Node(object):
nodeid
,
qname
=
self
.
_parse_add_args
(
*
args
[:
2
])
val
=
self
.
_to_variant
(
*
args
[
2
:])
return
self
.
_add_variable
(
nodeid
,
qname
,
val
,
isproperty
=
True
)
def
add_variable
(
self
,
*
args
):
"""
create a child node variable
...
...
@@ -266,10 +270,10 @@ class Node(object):
def
_add_variable
(
self
,
nodeid
,
qname
,
val
,
isproperty
=
False
):
node
=
ua
.
AddNodesItem
()
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
NodeClass
=
ua
.
NodeClass
.
Variable
node
.
ParentNodeId
=
self
.
nodeid
node
.
ParentNodeId
=
self
.
nodeid
if
isproperty
:
node
.
ReferenceTypeId
=
ua
.
NodeId
(
ua
.
ObjectIds
.
HasProperty
)
node
.
TypeDefinition
=
ua
.
NodeId
(
ua
.
ObjectIds
.
PropertyType
)
...
...
@@ -281,7 +285,7 @@ class Node(object):
attrs
.
DisplayName
=
ua
.
LocalizedText
(
qname
.
Name
)
attrs
.
DataType
=
self
.
_guess_uatype
(
val
)
attrs
.
Value
=
val
attrs
.
ValueRank
=
0
attrs
.
ValueRank
=
0
attrs
.
WriteMask
=
0
attrs
.
UserWriteMask
=
0
attrs
.
Historizing
=
0
...
...
@@ -302,17 +306,17 @@ class Node(object):
nodeid
,
qname
=
self
.
_parse_add_args
(
*
args
[:
2
])
callback
=
args
[
2
]
if
len
(
args
)
>
3
:
inputs
=
args
[
3
]
inputs
=
args
[
3
]
if
len
(
args
)
>
4
:
outputs
=
args
[
4
]
outputs
=
args
[
4
]
return
self
.
_add_method
(
nodeid
,
qname
,
callback
,
inputs
,
outputs
)
def
_add_method
(
self
,
nodeid
,
qname
,
callback
,
inputs
,
outputs
):
node
=
ua
.
AddNodesItem
()
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
RequestedNewNodeId
=
nodeid
node
.
BrowseName
=
qname
node
.
NodeClass
=
ua
.
NodeClass
.
Method
node
.
ParentNodeId
=
self
.
nodeid
node
.
ParentNodeId
=
self
.
nodeid
node
.
ReferenceTypeId
=
ua
.
NodeId
.
from_string
(
"i=47"
)
#node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
attrs
=
ua
.
MethodAttributes
()
...
...
@@ -332,19 +336,18 @@ class Node(object):
method
.
add_property
(
qname
.
NamespaceIndex
,
"OutputArguments"
,
[
self
.
_vtype_to_argument
(
vtype
)
for
vtype
in
outputs
])
self
.
server
.
add_method_callback
(
method
.
nodeid
,
callback
)
return
method
def
call_method
(
self
,
methodid
,
*
args
):
"""
Call an OPC-UA method. methodid is browse name of child method or the
nodeid of method as a NodeId object
Call an OPC-UA method. methodid is browse name of child method or the
nodeid of method as a NodeId object
arguments are variants or python object convertible to variants.
which may be of different types
returns a list of variants which are output of the method
returns a list of variants which are output of the method
"""
if
type
(
methodid
)
is
str
:
if
isinstance
(
methodid
,
str
)
:
methodid
=
self
.
get_child
(
methodid
).
nodeid
elif
type
(
methodid
)
is
Node
:
elif
isinstance
(
methodid
,
Node
)
:
methodid
=
methodid
.
nodeid
arguments
=
[]
...
...
@@ -391,21 +394,20 @@ class Node(object):
return
ua
.
NodeId
(
getattr
(
ua
.
ObjectIds
,
variant
.
VariantType
.
name
))
def
_parse_add_args
(
self
,
*
args
):
if
type
(
args
[
0
])
is
ua
.
NodeId
:
if
isinstance
(
args
[
0
],
ua
.
NodeId
)
:
return
args
[
0
],
args
[
1
]
elif
type
(
args
[
0
])
is
str
:
elif
isinstance
(
args
[
0
],
str
)
:
return
ua
.
NodeId
.
from_string
(
args
[
0
]),
ua
.
QualifiedName
.
from_string
(
args
[
1
])
elif
type
(
args
[
0
])
is
int
:
elif
isinstance
(
args
[
0
],
int
)
:
return
generate_nodeid
(
args
[
0
]),
ua
.
QualifiedName
(
args
[
1
],
args
[
0
])
else
:
raise
TypeError
(
"Add methods takes a nodeid and a qualifiedname as argument, received %s"
%
args
)
__nodeid_counter
=
2000
def
generate_nodeid
(
idx
):
global
__nodeid_counter
__nodeid_counter
+=
1
return
ua
.
NodeId
(
__nodeid_counter
,
idx
)
opcua/server.py
View file @
37f69181
...
...
@@ -16,6 +16,7 @@ from opcua import Node, Subscription, ObjectIds, Event
class
Server
(
object
):
def
__init__
(
self
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
endpoint
=
"opc.tcp://localhost:4841/freeopcua/server/"
...
...
@@ -26,7 +27,7 @@ class Server(object):
self
.
iserver
=
InternalServer
()
self
.
bserver
=
None
#setup some expected values
#
setup some expected values
self
.
register_namespace
(
self
.
server_uri
)
sa_node
=
self
.
get_node
(
ua
.
NodeId
(
ua
.
ObjectIds
.
Server_ServerArray
))
sa_node
.
set_value
([
self
.
server_uri
])
...
...
@@ -38,7 +39,7 @@ class Server(object):
return
self
.
iserver
.
get_endpoints
()
def
_setup_server_nodes
(
self
):
#to be called just before starting server since it needs all parameters to be setup
#
to be called just before starting server since it needs all parameters to be setup
self
.
_set_endpoints
()
def
_set_endpoints
(
self
):
...
...
@@ -48,9 +49,9 @@ class Server(object):
appdesc
=
ua
.
ApplicationDescription
()
appdesc
.
ApplicationName
=
ua
.
LocalizedText
(
self
.
name
)
appdesc
.
ApplicationUri
=
self
.
server_uri
appdesc
.
ApplicationUri
=
self
.
server_uri
appdesc
.
ApplicationType
=
ua
.
ApplicationType
.
Server
appdesc
.
ProductUri
=
self
.
product_uri
appdesc
.
ProductUri
=
self
.
product_uri
appdesc
.
DiscoveryUrls
.
append
(
self
.
endpoint
.
geturl
())
edp
=
ua
.
EndpointDescription
()
...
...
@@ -116,7 +117,7 @@ class Server(object):
uries
=
ns_node
.
get_value
()
uries
.
append
(
uri
)
ns_node
.
set_value
(
uries
)
return
(
len
(
uries
)
-
1
)
return
(
len
(
uries
)
-
1
)
def
get_namespace_index
(
self
,
uri
):
uries
=
self
.
get_namespace_array
()
...
...
@@ -124,8 +125,3 @@ class Server(object):
def
get_event_object
(
self
,
etype
=
ObjectIds
.
BaseEventType
,
source
=
ObjectIds
.
Server
):
return
Event
(
self
.
iserver
.
isession
,
etype
,
source
)
opcua/standard_address_space.py
View file @
37f69181
...
...
@@ -23,9 +23,8 @@ def fill_address_space(nodeservice):
create_standard_address_space_Part11
(
nodeservice
)
create_standard_address_space_Part13
(
nodeservice
)
def
fill_address_space_from_disk
(
aspace
):
dirname
=
os
.
path
.
dirname
(
opcua
.
__file__
)
path
=
os
.
path
.
join
(
dirname
,
"binary_address_space.pickle"
)
aspace
.
load
(
path
)
opcua/subscription.py
View file @
37f69181
...
...
@@ -12,14 +12,16 @@ from opcua import ObjectIds
from
opcua
import
AttributeIds
from
opcua
import
Event
class
EventResult
():
def
__str__
(
self
):
return
"EventResult({})"
.
format
([
str
(
k
)
+
":"
+
str
(
v
)
for
k
,
v
in
self
.
__dict__
.
items
()])
__repr__
=
__str__
class
SubscriptionItemData
():
def
__init__
(
self
):
self
.
node
=
None
self
.
client_handle
=
None
...
...
@@ -27,18 +29,20 @@ class SubscriptionItemData():
self
.
attribute
=
None
self
.
mfilter
=
None
class
Subscription
(
object
):
def
__init__
(
self
,
server
,
params
,
handler
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
server
=
server
self
.
_client_handle
=
200
self
.
_handler
=
handler
self
.
parameters
=
params
#
move to data class
self
.
parameters
=
params
#
move to data class
self
.
_monitoreditems_map
=
{}
self
.
_lock
=
RLock
()
self
.
subscription_id
=
None
response
=
self
.
server
.
create_subscription
(
params
,
self
.
publish_callback
)
self
.
subscription_id
=
response
.
SubscriptionId
#
move to data class
self
.
subscription_id
=
response
.
SubscriptionId
#
move to data class
self
.
server
.
publish
()
self
.
server
.
publish
()
...
...
@@ -63,7 +67,7 @@ class Subscription(object):
self
.
_call_status
(
statuschange
)
else
:
self
.
logger
.
warn
(
"Notification type not supported yet for notification %s"
,
notif
)
ack
=
ua
.
SubscriptionAcknowledgement
()
ack
.
SubscriptionId
=
self
.
subscription_id
ack
.
SequenceNumber
=
publishresult
.
NotificationMessage
.
SequenceNumber
...
...
@@ -133,7 +137,7 @@ class Subscription(object):
rv
=
ua
.
ReadValueId
()
rv
.
NodeId
=
node
.
nodeid
rv
.
AttributeId
=
attr
#rv.IndexRange //We leave it null, then the entire array is returned
#
rv.IndexRange //We leave it null, then the entire array is returned
mparams
=
ua
.
MonitoringParameters
()
self
.
_client_handle
+=
1
mparams
.
ClientHandle
=
self
.
_client_handle
...
...
@@ -143,7 +147,7 @@ class Subscription(object):
if
mfilter
:
mparams
.
Filter
=
mfilter
mir
=
ua
.
MonitoredItemCreateRequest
()
mir
=
ua
.
MonitoredItemCreateRequest
()
mir
.
ItemToMonitor
=
rv
mir
.
MonitoringMode
=
ua
.
MonitoringMode
.
Reporting
mir
.
RequestedParameters
=
mparams
...
...
@@ -156,7 +160,7 @@ class Subscription(object):
results
=
self
.
server
.
create_monitored_items
(
params
)
result
=
results
[
0
]
result
.
StatusCode
.
check
()
data
=
SubscriptionItemData
()
data
.
client_handle
=
mparams
.
ClientHandle
data
.
node
=
node
...
...
@@ -176,6 +180,3 @@ class Subscription(object):
params
.
MonitoredItemIds
=
[
handle
]
results
=
self
.
server
.
delete_monitored_items
(
params
)
results
[
0
].
check
()
opcua/subscription_service.py
View file @
37f69181
...
...
@@ -8,7 +8,9 @@ import logging
from
opcua
import
ua
class
SubscriptionService
(
object
):
def
__init__
(
self
,
loop
,
aspace
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
loop
=
loop
...
...
@@ -90,7 +92,7 @@ class SubscriptionService(object):
def
republish
(
self
,
params
):
with
self
.
_lock
:
if
not
params
.
SubscriptionId
in
self
.
subscriptions
:
#what should I do?
#
what should I do?
return
ua
.
NotificationMessage
()
return
self
.
subscriptions
[
params
.
SubscriptionId
].
republish
(
params
.
RetransmitSequenceNumber
)
...
...
@@ -100,17 +102,18 @@ class SubscriptionService(object):
sub
.
trigger_event
(
event
)
class
MonitoredItemData
(
object
):
def
__init__
(
self
):
self
.
client_handle
=
None
self
.
callback_handle
=
None
self
.
monitored_item_id
=
None
self
.
parameters
=
None
self
.
mode
=
None
class
InternalSubscription
(
object
):
def
__init__
(
self
,
manager
,
data
,
addressspace
,
callback
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
aspace
=
addressspace
...
...
@@ -128,18 +131,18 @@ class InternalSubscription(object):
self
.
_triggered_statuschanges
=
[]
self
.
_notification_seq
=
1
self
.
_not_acknowledged_results
=
{}
self
.
_startup
=
True
self
.
_startup
=
True
self
.
_keep_alive_count
=
0
self
.
_publish_cycles_count
=
0
self
.
_stopev
=
False
def
__str__
(
self
):
return
"Subscription(id:{})"
.
format
(
self
.
data
.
SubscriptionId
)
def
start
(
self
):
self
.
logger
.
debug
(
"starting subscription %s"
,
self
.
data
.
SubscriptionId
)
self
.
_subscription_loop
()
def
stop
(
self
):
self
.
logger
.
debug
(
"stopping subscription %s"
,
self
.
data
.
SubscriptionId
)
self
.
_stopev
=
True
...
...
@@ -151,7 +154,7 @@ class InternalSubscription(object):
def
_subscription_loop
(
self
):
#self.logger.debug("%s loop", self)
if
not
self
.
_stopev
:
self
.
manager
.
loop
.
call_later
(
self
.
data
.
RevisedPublishingInterval
/
1000.0
,
self
.
_sub_loop
)
self
.
manager
.
loop
.
call_later
(
self
.
data
.
RevisedPublishingInterval
/
1000.0
,
self
.
_sub_loop
)
def
_sub_loop
(
self
):
self
.
publish_results
()
...
...
@@ -167,14 +170,14 @@ class InternalSubscription(object):
self
.
_keep_alive_count
+=
1
return
False
def
publish_results
(
self
):
def
publish_results
(
self
):
if
self
.
_publish_cycles_count
>
self
.
data
.
RevisedLifetimeCount
:
self
.
logger
.
warn
(
"Subscription %s has expired, publish cycle count(%s) > lifetime count (%s)"
,
self
,
self
.
_publish_cycles_count
,
self
.
data
.
RevisedLifetimeCount
)
#FIXME this will never be send since we do not have publish request anyway
self
.
trigger_statuschange
(
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadTimeout
))
#
FIXME this will never be send since we do not have publish request anyway
self
.
trigger_statuschange
(
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadTimeout
))
self
.
_stopev
=
True
with
self
.
_lock
:
if
self
.
has_published_results
():
#
FIXME: should we pop a publish request here? or we do not care?
if
self
.
has_published_results
():
#
FIXME: should we pop a publish request here? or we do not care?
self
.
_publish_cycles_count
+=
1
result
=
self
.
_pop_publish_result
()
self
.
callback
(
result
)
...
...
@@ -185,7 +188,7 @@ class InternalSubscription(object):
if
self
.
_triggered_datachanges
:
notif
=
ua
.
DataChangeNotification
()
notif
.
MonitoredItems
=
self
.
_triggered_datachanges
[:]
self
.
_triggered_datachanges
=
[]
self
.
_triggered_datachanges
=
[]
self
.
logger
.
debug
(
"sending datachanges nontification with %s events"
,
len
(
notif
.
MonitoredItems
))
result
.
NotificationMessage
.
NotificationData
.
append
(
notif
)
if
self
.
_triggered_events
:
...
...
@@ -205,7 +208,7 @@ class InternalSubscription(object):
result
.
AvailableSequenceNumbers
=
list
(
self
.
_not_acknowledged_results
.
keys
())
self
.
_not_acknowledged_results
[
result
.
NotificationMessage
.
SequenceNumber
]
=
result
return
result
def
trigger_statuschange
(
self
,
code
):
self
.
_triggered_statuschanges
.
append
(
code
)
...
...
@@ -248,11 +251,11 @@ class InternalSubscription(object):
result
=
ua
.
MonitoredItemCreateResult
()
if
mdata
.
monitored_item_id
==
params
.
MonitoredItemId
:
result
.
RevisedSamplingInterval
=
self
.
data
.
RevisedPublishingInterval
result
.
RevisedQueueSize
=
ua
.
downcast_extobject
(
params
.
RequestedParameters
.
QueueSize
)
#
FIXME check and use value
result
.
RevisedQueueSize
=
ua
.
downcast_extobject
(
params
.
RequestedParameters
.
QueueSize
)
#
FIXME check and use value
result
.
FilterResult
=
params
.
RequestedParameters
.
Filter
mdata
.
parameters
=
result
return
result
#FIXME modify event subscriptions
#
FIXME modify event subscriptions
result
=
ua
.
MonitoredItemCreateResult
()
result
.
StatusCode
(
ua
.
StatusCodes
.
BadMonitoredItemIdInvalid
)
return
result
...
...
@@ -261,7 +264,7 @@ class InternalSubscription(object):
with
self
.
_lock
:
result
=
ua
.
MonitoredItemCreateResult
()
result
.
RevisedSamplingInterval
=
self
.
data
.
RevisedPublishingInterval
result
.
RevisedQueueSize
=
params
.
RequestedParameters
.
QueueSize
#
FIXME check and use value
result
.
RevisedQueueSize
=
params
.
RequestedParameters
.
QueueSize
#
FIXME check and use value
result
.
FilterResult
=
ua
.
downcast_extobject
(
params
.
RequestedParameters
.
Filter
)
self
.
_monitored_item_counter
+=
1
result
.
MonitoredItemId
=
self
.
_monitored_item_counter
...
...
@@ -271,7 +274,7 @@ class InternalSubscription(object):
mdata
.
parameters
=
result
mdata
.
mode
=
params
.
MonitoringMode
mdata
.
client_handle
=
params
.
RequestedParameters
.
ClientHandle
mdata
.
monitored_item_id
=
result
.
MonitoredItemId
mdata
.
monitored_item_id
=
result
.
MonitoredItemId
self
.
_monitored_items
[
result
.
MonitoredItemId
]
=
mdata
...
...
@@ -284,7 +287,7 @@ class InternalSubscription(object):
self
.
logger
.
debug
(
"adding callback return status %s and handle %s"
,
result
.
StatusCode
,
handle
)
mdata
.
callback_handle
=
handle
self
.
_monitored_datachange
[
handle
]
=
result
.
MonitoredItemId
#force data change event generation
#
force data change event generation
self
.
trigger_datachange
(
handle
,
params
.
ItemToMonitor
.
NodeId
,
params
.
ItemToMonitor
.
AttributeId
)
return
result
...
...
@@ -312,7 +315,6 @@ class InternalSubscription(object):
self
.
_monitored_items
.
pop
(
mid
)
return
ua
.
StatusCode
()
def
datachange_callback
(
self
,
handle
,
value
):
self
.
logger
.
info
(
"subscription %s: datachange callback called with handle '%s' and value '%s'"
,
self
,
handle
,
value
.
Value
)
event
=
ua
.
MonitoredItemNotification
()
...
...
@@ -354,9 +356,3 @@ class InternalSubscription(object):
except
AttributeError
:
fields
.
append
(
ua
.
Variant
())
return
fields
opcua/uaprocessor.py
View file @
37f69181
...
...
@@ -6,13 +6,17 @@ from datetime import datetime
from
opcua
import
ua
from
opcua
import
utils
class
PublishRequestData
(
object
):
def
__init__
(
self
):
self
.
requesthdr
=
None
self
.
algohdr
=
None
self
.
seqhdr
=
None
class
UAProcessor
(
object
):
def
__init__
(
self
,
internal_server
,
socket
,
name
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
iserver
=
internal_server
...
...
@@ -26,7 +30,7 @@ class UAProcessor(object):
self
.
_seq_number
=
1
def
loop
(
self
):
#first we want a hello message
#
first we want a hello message
while
True
:
header
=
ua
.
Header
.
from_stream
(
self
.
socket
)
body
=
self
.
_receive_body
(
header
.
body_size
)
...
...
@@ -59,8 +63,8 @@ class UAProcessor(object):
seqhdr
.
SequenceNumber
=
self
.
_seq_number
self
.
_seq_number
+=
1
hdr
=
ua
.
Header
(
msgtype
,
ua
.
ChunkType
.
Single
,
self
.
channel
.
SecurityToken
.
ChannelId
)
if
type
(
algohdr
)
is
ua
.
SymmetricAlgorithmHeader
:
algohdr
.
TokenId
=
self
.
channel
.
SecurityToken
.
TokenId
if
isinstance
(
algohdr
,
ua
.
SymmetricAlgorithmHeader
)
:
algohdr
.
TokenId
=
self
.
channel
.
SecurityToken
.
TokenId
self
.
_write_socket
(
hdr
,
algohdr
,
seqhdr
,
response
)
def
_write_socket
(
self
,
hdr
,
*
args
):
...
...
@@ -89,7 +93,7 @@ class UAProcessor(object):
request
=
ua
.
OpenSecureChannelRequest
.
from_binary
(
body
)
channel
=
self
.
_open_secure_channel
(
request
.
Parameters
)
#send response
#
send response
response
=
ua
.
OpenSecureChannelResponse
()
response
.
Parameters
=
channel
self
.
send_response
(
request
.
RequestHeader
.
RequestHandle
,
algohdr
,
seqhdr
,
response
,
ua
.
MessageType
.
SecureOpen
)
...
...
@@ -123,16 +127,16 @@ class UAProcessor(object):
else
:
self
.
logger
.
warning
(
"Unsupported message type: %s"
,
header
.
MessageType
)
return
True
def
process_message
(
self
,
algohdr
,
seqhdr
,
body
):
typeid
=
ua
.
NodeId
.
from_binary
(
body
)
requesthdr
=
ua
.
RequestHeader
.
from_binary
(
body
)
if
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
CreateSessionRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Create session request"
)
params
=
ua
.
CreateSessionParameters
.
from_binary
(
body
)
self
.
session
=
self
.
iserver
.
create_session
(
self
.
name
)
#
create the session on server
sessiondata
=
self
.
session
.
create_session
(
params
)
#
get a session creation result to send back
self
.
session
=
self
.
iserver
.
create_session
(
self
.
name
)
#
create the session on server
sessiondata
=
self
.
session
.
create_session
(
params
)
#
get a session creation result to send back
response
=
ua
.
CreateSessionResponse
()
response
.
Parameters
=
sessiondata
...
...
@@ -141,7 +145,7 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
CloseSessionRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Close session request"
)
deletesubs
=
ua
.
unpack_uatype
(
'Boolean'
,
body
)
self
.
session
.
close_session
(
deletesubs
)
response
=
ua
.
CloseSessionResponse
()
...
...
@@ -149,11 +153,11 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
ActivateSessionRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Activate session request"
)
params
=
ua
.
ActivateSessionParameters
.
from_binary
(
body
)
params
=
ua
.
ActivateSessionParameters
.
from_binary
(
body
)
if
not
self
.
session
:
#result = ua.ActivateSessionResult()
#result.Results.append(ua.StatusCode(ua.StatusCodes.BadSessionIdInvalid))
#
result.Results.append(ua.StatusCode(ua.StatusCodes.BadSessionIdInvalid))
response
=
ua
.
ServiceFault
()
response
.
ResponseHeader
.
ServiceResult
=
ua
.
StatusCode
(
ua
.
StatusCodes
.
BadSessionIdInvalid
)
self
.
send_response
(
requesthdr
.
RequestHandle
,
algohdr
,
seqhdr
,
response
)
...
...
@@ -166,8 +170,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
ReadRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Read request"
)
params
=
ua
.
ReadParameters
.
from_binary
(
body
)
params
=
ua
.
ReadParameters
.
from_binary
(
body
)
results
=
self
.
session
.
read
(
params
)
response
=
ua
.
ReadResponse
()
...
...
@@ -176,8 +180,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
WriteRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Write request"
)
params
=
ua
.
WriteParameters
.
from_binary
(
body
)
params
=
ua
.
WriteParameters
.
from_binary
(
body
)
results
=
self
.
session
.
write
(
params
)
response
=
ua
.
WriteResponse
()
...
...
@@ -186,8 +190,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
BrowseRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"Browse request"
)
params
=
ua
.
BrowseParameters
.
from_binary
(
body
)
params
=
ua
.
BrowseParameters
.
from_binary
(
body
)
results
=
self
.
session
.
browse
(
params
)
response
=
ua
.
BrowseResponse
()
...
...
@@ -196,8 +200,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
GetEndpointsRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"get endpoints request"
)
params
=
ua
.
GetEndpointsParameters
.
from_binary
(
body
)
params
=
ua
.
GetEndpointsParameters
.
from_binary
(
body
)
endpoints
=
self
.
iserver
.
get_endpoints
(
params
)
response
=
ua
.
GetEndpointsResponse
()
...
...
@@ -207,8 +211,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
TranslateBrowsePathsToNodeIdsRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"translate browsepaths to nodeids request"
)
params
=
ua
.
TranslateBrowsePathsToNodeIdsParameters
.
from_binary
(
body
)
params
=
ua
.
TranslateBrowsePathsToNodeIdsParameters
.
from_binary
(
body
)
paths
=
self
.
session
.
translate_browsepaths_to_nodeids
(
params
.
BrowsePaths
)
response
=
ua
.
TranslateBrowsePathsToNodeIdsResponse
()
...
...
@@ -218,8 +222,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
AddNodesRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"add nodes request"
)
params
=
ua
.
AddNodesParameters
.
from_binary
(
body
)
params
=
ua
.
AddNodesParameters
.
from_binary
(
body
)
results
=
self
.
session
.
add_nodes
(
params
.
NodesToAdd
)
response
=
ua
.
AddNodesResponse
()
...
...
@@ -229,8 +233,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
CreateSubscriptionRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"create subscription request"
)
params
=
ua
.
CreateSubscriptionParameters
.
from_binary
(
body
)
params
=
ua
.
CreateSubscriptionParameters
.
from_binary
(
body
)
result
=
self
.
session
.
create_subscription
(
params
,
self
.
forward_publish_response
)
response
=
ua
.
CreateSubscriptionResponse
()
...
...
@@ -240,8 +244,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
DeleteSubscriptionsRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"delete subscriptions request"
)
params
=
ua
.
DeleteSubscriptionsParameters
.
from_binary
(
body
)
params
=
ua
.
DeleteSubscriptionsParameters
.
from_binary
(
body
)
results
=
self
.
session
.
delete_subscriptions
(
params
.
SubscriptionIds
)
response
=
ua
.
DeleteSubscriptionsResponse
()
...
...
@@ -261,7 +265,7 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
ModifyMonitoredItemsRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"modify monitored items request"
)
params
=
ua
.
ModifyMonitoredItemsParameters
.
from_binary
(
body
)
params
=
ua
.
ModifyMonitoredItemsParameters
.
from_binary
(
body
)
results
=
self
.
session
.
modify_monitored_items
(
params
)
response
=
ua
.
ModifyMonitoredItemsResponse
()
...
...
@@ -271,8 +275,8 @@ class UAProcessor(object):
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
DeleteMonitoredItemsRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"delete monitored items request"
)
params
=
ua
.
DeleteMonitoredItemsParameters
.
from_binary
(
body
)
params
=
ua
.
DeleteMonitoredItemsParameters
.
from_binary
(
body
)
results
=
self
.
session
.
delete_monitored_items
(
params
)
response
=
ua
.
DeleteMonitoredItemsResponse
()
...
...
@@ -285,21 +289,21 @@ class UAProcessor(object):
if
not
self
.
session
:
return
False
params
=
ua
.
PublishParameters
.
from_binary
(
body
)
params
=
ua
.
PublishParameters
.
from_binary
(
body
)
data
=
PublishRequestData
()
data
.
requesthdr
=
requesthdr
data
.
seqhdr
=
seqhdr
data
.
algohdr
=
algohdr
with
self
.
_datalock
:
self
.
_publishdata_queue
.
append
(
data
)
# will be used to send publish answers from server
self
.
_publishdata_queue
.
append
(
data
)
# will be used to send publish answers from server
self
.
session
.
publish
(
params
.
SubscriptionAcknowledgements
)
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
RepublishRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"re-publish request"
)
params
=
ua
.
RepublishParameters
.
from_binary
(
body
)
params
=
ua
.
RepublishParameters
.
from_binary
(
body
)
msg
=
self
.
session
.
republish
(
params
)
response
=
ua
.
RepublishResponse
()
...
...
@@ -313,12 +317,12 @@ class UAProcessor(object):
self
.
send_response
(
requesthdr
.
RequestHandle
,
algohdr
,
seqhdr
,
response
)
self
.
channel
=
None
return
False
elif
typeid
==
ua
.
NodeId
(
ua
.
ObjectIds
.
CallRequest_Encoding_DefaultBinary
):
self
.
logger
.
info
(
"call request"
)
params
=
ua
.
CallParameters
.
from_binary
(
body
)
params
=
ua
.
CallParameters
.
from_binary
(
body
)
results
=
self
.
session
.
call
(
params
.
MethodsToCall
)
response
=
ua
.
CallResponse
()
...
...
@@ -338,13 +342,11 @@ class UAProcessor(object):
self
.
logger
.
info
(
"open secure channel"
)
if
params
.
RequestType
==
ua
.
SecurityTokenRequestType
.
Issue
:
self
.
channel
=
ua
.
OpenSecureChannelResult
()
self
.
channel
.
SecurityToken
.
TokenId
=
13
#
random value
self
.
channel
.
SecurityToken
.
TokenId
=
13
#
random value
self
.
channel
.
SecurityToken
.
ChannelId
=
self
.
iserver
.
get_new_channel_id
()
self
.
channel
.
SecurityToken
.
RevisedLifetime
=
params
.
RequestedLifetime
self
.
channel
.
SecurityToken
.
RevisedLifetime
=
params
.
RequestedLifetime
self
.
channel
.
SecurityToken
.
TokenId
+=
1
self
.
channel
.
SecurityToken
.
CreatedAt
=
datetime
.
now
()
self
.
channel
.
SecurityToken
.
RevisedLifetime
=
params
.
RequestedLifetime
self
.
channel
.
ServerNonce
=
utils
.
create_nonce
()
return
self
.
channel
opcua/uaprotocol.py
View file @
37f69181
...
...
@@ -5,7 +5,6 @@ from opcua.uaprotocol_auto import *
from
opcua.uaprotocol_hand
import
*
# FIXME: this is really crappy, should thing about a better implementation
# maybe never inherit extensionobject and parse only body....
def
downcast_extobject
(
item
):
...
...
@@ -15,5 +14,3 @@ def downcast_extobject(item):
classname
=
objectidname
.
split
(
"_"
)[
0
]
cmd
=
"{}.from_binary(utils.Buffer(item.to_binary()))"
.
format
(
classname
)
return
eval
(
cmd
)
opcua/uaprotocol_hand.py
View file @
37f69181
import
io
import
struct
import
struct
import
logging
import
opcua.uaprotocol_auto
as
auto
import
opcua.uatypes
as
uatypes
import
opcua.uatypes
as
uatypes
import
opcua.utils
as
utils
from
opcua.object_ids
import
ObjectIds
from
opcua.attribute_ids
import
AttributeIds
...
...
@@ -14,14 +14,16 @@ logger = logging.getLogger('opcua.uaprotocol')
class
SocketClosedException
(
Exception
):
pass
def
get_bytes_from_sock
(
sock
,
size
):
data
=
utils
.
recv_all
(
sock
,
size
)
if
len
(
data
)
<
size
:
#
socket has closed!
if
len
(
data
)
<
size
:
#
socket has closed!
raise
SocketClosedException
(
"Server socket has closed"
)
return
io
.
BytesIO
(
data
)
class
Hello
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
ProtocolVersion
=
0
self
.
ReceiveBufferSize
=
65536
...
...
@@ -53,9 +55,8 @@ class Hello(uatypes.FrozenClass):
return
hello
class
MessageType
(
object
):
Invalid
=
b"INV"
#
FIXME: check value
Invalid
=
b"INV"
#
FIXME: check value
Hello
=
b"HEL"
Acknowledge
=
b"ACK"
Error
=
b"ERR"
...
...
@@ -63,15 +64,16 @@ class MessageType(object):
SecureClose
=
b"CLO"
SecureMessage
=
b"MSG"
class
ChunkType
(
object
):
Invalid
=
b"0"
#
FIXME check
Invalid
=
b"0"
#
FIXME check
Single
=
b"F"
Intermediate
=
b"C"
Final
=
b"A"
class
Header
(
uatypes
.
FrozenClass
):
def
__init__
(
self
,
msgType
=
None
,
chunkType
=
None
,
channelid
=
0
):
self
.
MessageType
=
msgType
self
.
ChunkType
=
chunkType
...
...
@@ -113,6 +115,7 @@ class Header(uatypes.FrozenClass):
class
ErrorMessage
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
Error
=
uatypes
.
StatusCode
()
self
.
Reason
=
""
...
...
@@ -137,12 +140,13 @@ class ErrorMessage(uatypes.FrozenClass):
class
Acknowledge
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
ProtocolVersion
=
0
self
.
ReceiveBufferSize
=
65536
self
.
SendBufferSize
=
65536
self
.
MaxMessageSize
=
0
#
No limits
self
.
MaxChunkCount
=
0
#
No limits
self
.
MaxMessageSize
=
0
#
No limits
self
.
MaxChunkCount
=
0
#
No limits
self
.
_freeze
()
def
to_binary
(
self
):
...
...
@@ -154,8 +158,6 @@ class Acknowledge(uatypes.FrozenClass):
b
.
append
(
struct
.
pack
(
"<I"
,
self
.
MaxChunkCount
))
return
b""
.
join
(
b
)
@
staticmethod
def
from_binary
(
data
):
ack
=
Acknowledge
()
...
...
@@ -166,7 +168,9 @@ class Acknowledge(uatypes.FrozenClass):
ack
.
MaxChunkCount
=
struct
.
unpack
(
"<I"
,
data
.
read
(
4
))[
0
]
return
ack
class
AsymmetricAlgorithmHeader
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
SecurityPolicyURI
=
"http://opcfoundation.org/UA/SecurityPolicy#None"
self
.
SenderCertificate
=
b""
...
...
@@ -194,6 +198,7 @@ class AsymmetricAlgorithmHeader(uatypes.FrozenClass):
class
SymmetricAlgorithmHeader
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
TokenId
=
0
self
.
_freeze
()
...
...
@@ -213,6 +218,7 @@ class SymmetricAlgorithmHeader(uatypes.FrozenClass):
class
SequenceHeader
(
uatypes
.
FrozenClass
):
def
__init__
(
self
):
self
.
SequenceNumber
=
None
self
.
RequestId
=
None
...
...
@@ -235,54 +241,64 @@ class SequenceHeader(uatypes.FrozenClass):
return
"{}(SequenceNumber:{}, RequestId:{} )"
.
format
(
self
.
__class__
.
__name__
,
self
.
SequenceNumber
,
self
.
RequestId
)
__repr__
=
__str__
#
##
FIXES for missing switchfield in NodeAttributes classes
#
FIXES for missing switchfield in NodeAttributes classes
ana
=
auto
.
NodeAttributesMask
class
ObjectAttributes
(
auto
.
ObjectAttributes
):
def
__init__
(
self
):
auto
.
ObjectAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
EventNotifier
class
ObjectTypeAttributes
(
auto
.
ObjectTypeAttributes
):
def
__init__
(
self
):
auto
.
ObjectTypeAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
IsAbstract
class
VariableAttributes
(
auto
.
VariableAttributes
):
def
__init__
(
self
):
auto
.
VariableAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
Value
|
ana
.
DataType
|
ana
.
ValueRank
|
ana
.
ArrayDimensions
|
ana
.
AccessLevel
|
ana
.
UserAccessLevel
|
ana
.
MinimumSamplingInterval
|
ana
.
Historizing
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
Value
|
ana
.
DataType
|
ana
.
ValueRank
|
ana
.
ArrayDimensions
|
ana
.
AccessLevel
|
ana
.
UserAccessLevel
|
ana
.
MinimumSamplingInterval
|
ana
.
Historizing
class
VariableTypeAttributes
(
auto
.
VariableTypeAttributes
):
def
__init__
(
self
):
auto
.
VariableTypeAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
Value
|
ana
.
DataType
|
ana
.
ValueRank
|
ana
.
ArrayDimensions
|
ana
.
IsAbstract
class
MethodAttributes
(
auto
.
MethodAttributes
):
def
__init__
(
self
):
auto
.
MethodAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
Executable
|
ana
.
UserExecutable
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
Executable
|
ana
.
UserExecutable
class
ReferenceTypeAttributes
(
auto
.
ReferenceTypeAttributes
):
def
__init__
(
self
):
auto
.
ReferenceTypeAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
IsAbstract
|
ana
.
Symmetric
|
ana
.
InverseName
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
IsAbstract
|
ana
.
Symmetric
|
ana
.
InverseName
class
DataTypeAttributes
(
auto
.
DataTypeAttributes
):
def
__init__
(
self
):
auto
.
DataTypeAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
IsAbstract
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
IsAbstract
class
ViewAttributes
(
auto
.
ViewAttributes
):
def
__init__
(
self
):
auto
.
ViewAttributes
.
__init__
(
self
)
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
ContainsNoLoops
|
ana
.
EventNotifier
self
.
SpecifiedAttributes
=
ana
.
DisplayName
|
ana
.
Description
|
ana
.
WriteMask
|
ana
.
UserWriteMask
|
ana
.
ContainsNoLoops
|
ana
.
EventNotifier
ObjectIdsInv
=
{
v
:
k
for
k
,
v
in
ObjectIds
.
__dict__
.
items
()}
AttributeIdsInv
=
{
v
:
k
for
k
,
v
in
AttributeIds
.
__dict__
.
items
()}
opcua/uatypes.py
View file @
37f69181
...
...
@@ -10,22 +10,25 @@ if sys.version_info.major > 2:
unicode
=
str
import
uuid
import
struct
import
struct
import
opcua.status_code
as
status_code
from
opcua.object_ids
import
ObjectIds
logger
=
logging
.
getLogger
(
'opcua.uaprotocol'
)
#types that will packed and unpacked directly using struct (string, bytes and datetime are handles as special cases
#
types that will packed and unpacked directly using struct (string, bytes and datetime are handles as special cases
UaTypes
=
(
"Boolean"
,
"SByte"
,
"Byte"
,
"Int8"
,
"UInt8"
,
"Int16"
,
"UInt16"
,
"Int32"
,
"UInt32"
,
"Int64"
,
"UInt64"
,
"Float"
,
"Double"
)
EPOCH_AS_FILETIME
=
116444736000000000
# January 1, 1970 as MS file time
HUNDREDS_OF_NANOSECONDS
=
10000000
class
UTC
(
tzinfo
):
"""UTC"""
def
utcoffset
(
self
,
dt
):
return
timedelta
(
0
)
...
...
@@ -36,26 +39,26 @@ class UTC(tzinfo):
return
timedelta
(
0
)
#methods copied from David Buxton <david@gasmark6.com> sample code
#
methods copied from David Buxton <david@gasmark6.com> sample code
def
datetime_to_win_epoch
(
dt
):
if
(
dt
.
tzinfo
is
None
)
or
(
dt
.
tzinfo
.
utcoffset
(
dt
)
is
None
):
dt
=
dt
.
replace
(
tzinfo
=
UTC
())
ft
=
EPOCH_AS_FILETIME
+
(
timegm
(
dt
.
timetuple
())
*
HUNDREDS_OF_NANOSECONDS
)
return
ft
+
(
dt
.
microsecond
*
10
)
def
win_epoch_to_datetime
(
epch
):
(
s
,
ns100
)
=
divmod
(
epch
-
EPOCH_AS_FILETIME
,
HUNDREDS_OF_NANOSECONDS
)
try
:
# TDA, this generates exceptions on systems where RTC is really off
dt
=
datetime
.
utcfromtimestamp
(
s
)
except
:
logger
.
debug
(
"Exception occurred during conversion within 'win_epoch_to_datetime'."
)
logger
.
debug
(
"Exception occurred during conversion within 'win_epoch_to_datetime'."
)
return
datetime
.
now
()
dt
=
dt
.
replace
(
microsecond
=
(
ns100
//
10
))
return
dt
def
uatype_to_fmt
(
uatype
):
if
uatype
==
"Char"
:
return
"B"
...
...
@@ -90,6 +93,7 @@ def uatype_to_fmt(uatype):
else
:
raise
Exception
(
"Error unknown uatype: "
+
uatype
)
def
pack_uatype_array
(
uatype
,
value
):
if
value
is
None
:
return
struct
.
pack
(
"<i"
,
-
1
)
...
...
@@ -99,6 +103,7 @@ def pack_uatype_array(uatype, value):
b
.
append
(
pack_uatype
(
uatype
,
val
))
return
b""
.
join
(
b
)
def
pack_uatype
(
uatype
,
value
):
if
uatype
==
"Null"
:
return
b''
...
...
@@ -115,6 +120,7 @@ def pack_uatype(uatype, value):
else
:
return
value
.
to_binary
()
def
unpack_uatype
(
uatype
,
data
):
if
uatype
==
"String"
:
return
unpack_string
(
data
)
...
...
@@ -132,6 +138,7 @@ def unpack_uatype(uatype, data):
tmp
=
eval
(
code
)
return
tmp
def
unpack_uatype_array
(
uatype
,
data
):
length
=
struct
.
unpack
(
'<i'
,
data
.
read
(
4
))[
0
]
if
length
==
-
1
:
...
...
@@ -142,43 +149,50 @@ def unpack_uatype_array(uatype, data):
result
.
append
(
unpack_uatype
(
uatype
,
data
))
return
result
def
pack_string
(
string
):
length
=
len
(
string
)
if
length
==
0
:
return
struct
.
pack
(
"<i"
,
-
1
)
if
not
type
(
string
)
is
bytes
:
return
struct
.
pack
(
"<i"
,
-
1
)
if
not
isinstance
(
string
,
bytes
)
:
string
=
string
.
encode
()
return
struct
.
pack
(
"<i"
,
length
)
+
string
pack_bytes
=
pack_string
def
unpack_bytes
(
data
):
length
=
struct
.
unpack
(
"<i"
,
data
.
read
(
4
))[
0
]
if
length
==
-
1
:
return
b''
return
data
.
read
(
length
)
def
unpack_string
(
data
):
b
=
unpack_bytes
(
data
)
if
sys
.
version_info
.
major
<
3
:
return
str
(
b
)
return
b
.
decode
(
"utf-8"
)
def
test_bit
(
data
,
offset
):
mask
=
1
<<
offset
return
data
&
mask
def
set_bit
(
data
,
offset
):
mask
=
1
<<
offset
return
data
|
mask
class
FrozenClass
(
object
):
"""
make it impossible to add members to a class.
This is a hack since I found out that most bugs are due to misspelling a variable in protocol
"""
__isfrozen
=
False
def
__setattr__
(
self
,
key
,
value
):
if
self
.
__isfrozen
and
not
hasattr
(
self
,
key
):
raise
TypeError
(
"Error adding member '{}' to class '{}', class is frozen, members are {}"
.
format
(
key
,
self
.
__class__
.
__name__
,
self
.
__dict__
.
keys
()))
...
...
@@ -189,11 +203,12 @@ class FrozenClass(object):
class
Guid
(
object
):
def
__init__
(
self
):
self
.
uuid
=
uuid
.
uuid4
()
def
to_binary
(
self
):
return
self
.
uuid
.
bytes
return
self
.
uuid
.
bytes
@
staticmethod
def
from_binary
(
data
):
...
...
@@ -204,7 +219,9 @@ class Guid(object):
def
__eq__
(
self
,
other
):
return
isinstance
(
other
,
Guid
)
and
self
.
uuid
==
other
.
uuid
class
StatusCode
(
object
):
def
__init__
(
self
,
value
=
0
):
self
.
value
=
value
self
.
name
,
self
.
doc
=
status_code
.
get_name_and_doc
(
value
)
...
...
@@ -212,7 +229,7 @@ class StatusCode(object):
def
to_binary
(
self
):
return
struct
.
pack
(
"<I"
,
self
.
value
)
@
staticmethod
@
staticmethod
def
from_binary
(
data
):
val
=
struct
.
unpack
(
"<I"
,
data
.
read
(
4
))[
0
]
sc
=
StatusCode
(
val
)
...
...
@@ -226,6 +243,7 @@ class StatusCode(object):
return
'StatusCode({})'
.
format
(
self
.
name
)
__repr__
=
__str__
class
NodeIdType
(
object
):
TwoByte
=
0
FourByte
=
1
...
...
@@ -236,6 +254,7 @@ class NodeIdType(object):
class
NodeId
(
object
):
def
__init__
(
self
,
identifier
=
None
,
namespaceidx
=
0
,
nodeidtype
=
None
):
self
.
Identifier
=
identifier
self
.
NamespaceIndex
=
namespaceidx
...
...
@@ -247,17 +266,17 @@ class NodeId(object):
self
.
NodeIdType
=
NodeIdType
.
TwoByte
return
if
self
.
NodeIdType
is
None
:
if
type
(
self
.
Identifier
)
==
int
:
if
isinstance
(
self
.
Identifier
,
int
)
:
self
.
NodeIdType
=
NodeIdType
.
Numeric
elif
type
(
self
.
Identifier
)
==
str
:
elif
isinstance
(
self
.
Identifier
,
str
)
:
self
.
NodeIdType
=
NodeIdType
.
String
elif
type
(
self
.
Identifier
)
==
bytes
:
elif
isinstance
(
self
.
Identifier
,
bytes
)
:
self
.
NodeIdType
=
NodeIdType
.
ByteString
else
:
raise
Exception
(
"NodeId: Could not guess type of NodeId, set NodeIdType"
)
def
__key
(
self
):
if
self
.
NodeIdType
in
(
NodeIdType
.
TwoByte
,
NodeIdType
.
FourByte
,
NodeIdType
.
Numeric
):
#
twobyte, fourbyte and numeric may represent the same node
if
self
.
NodeIdType
in
(
NodeIdType
.
TwoByte
,
NodeIdType
.
FourByte
,
NodeIdType
.
Numeric
):
#
twobyte, fourbyte and numeric may represent the same node
return
self
.
NamespaceIndex
,
self
.
Identifier
else
:
return
self
.
NodeIdType
,
self
.
NamespaceIndex
,
self
.
Identifier
...
...
@@ -307,7 +326,6 @@ class NodeId(object):
nodeid
.
ServerIndex
=
srv
return
nodeid
def
to_string
(
self
):
string
=
""
if
self
.
NamespaceIndex
!=
0
:
...
...
@@ -318,9 +336,9 @@ class NodeId(object):
elif
self
.
NodeIdType
==
NodeIdType
.
String
:
ntype
=
"s"
elif
self
.
NodeIdType
==
NodeIdType
.
TwoByte
:
ntype
=
"i"
ntype
=
"i"
elif
self
.
NodeIdType
==
NodeIdType
.
FourByte
:
ntype
=
"i"
ntype
=
"i"
elif
self
.
NodeIdType
==
NodeIdType
.
Guid
:
ntype
=
"g"
elif
self
.
NodeIdType
==
NodeIdType
.
ByteString
:
...
...
@@ -330,7 +348,7 @@ class NodeId(object):
string
=
"srv="
+
str
(
self
.
ServerIndex
)
+
string
if
self
.
NamespaceUri
:
string
+=
"nsu={}"
.
format
(
self
.
NamespaceUri
)
return
string
return
string
def
__str__
(
self
):
return
"NodeId({})"
.
format
(
self
.
to_string
())
...
...
@@ -384,39 +402,52 @@ class NodeId(object):
return
nid
class
TwoByteNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
):
NodeId
.
__init__
(
self
,
identifier
,
0
,
NodeIdType
.
TwoByte
)
class
FourByteNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
,
namespace
=
0
):
NodeId
.
__init__
(
self
,
identifier
,
namespace
,
NodeIdType
.
FourByte
)
class
NumericNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
,
namespace
=
0
):
NodeId
.
__init__
(
self
,
identifier
,
namespace
,
NodeIdType
.
Numeric
)
class
ByteStringNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
,
namespace
=
0
):
NodeId
.
__init__
(
self
,
identifier
,
namespace
,
NodeIdType
.
ByteString
)
class
GuidNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
,
namespace
=
0
):
NodeId
.
__init__
(
self
,
identifier
,
namespace
,
NodeIdType
.
Guid
)
class
StringNodeId
(
NodeId
):
def
__init__
(
self
,
identifier
,
namespace
=
0
):
NodeId
.
__init__
(
self
,
identifier
,
namespace
,
NodeIdType
.
String
)
ExpandedNodeId
=
NodeId
class
QualifiedName
(
object
):
'''
A string qualified with a namespace index.
'''
def
__init__
(
self
,
name
=
""
,
namespaceidx
=
0
):
self
.
NamespaceIndex
=
namespaceidx
self
.
Name
=
name
...
...
@@ -432,14 +463,14 @@ class QualifiedName(object):
idx
=
0
name
=
string
return
QualifiedName
(
name
,
int
(
idx
))
def
to_binary
(
self
):
packet
=
[]
fmt
=
'<H'
packet
.
append
(
struct
.
pack
(
fmt
,
self
.
NamespaceIndex
))
packet
.
append
(
pack_string
(
self
.
Name
))
return
b''
.
join
(
packet
)
@
staticmethod
def
from_binary
(
data
):
obj
=
QualifiedName
()
...
...
@@ -447,38 +478,43 @@ class QualifiedName(object):
obj
.
NamespaceIndex
=
struct
.
unpack
(
fmt
,
data
.
read
(
2
))[
0
]
obj
.
Name
=
unpack_string
(
data
)
return
obj
def
__eq__
(
self
,
bname
):
return
isinstance
(
bname
,
QualifiedName
)
and
self
.
Name
==
bname
.
Name
and
self
.
NamespaceIndex
==
bname
.
NamespaceIndex
def
__str__
(
self
):
return
'QualifiedName({}:{})'
.
format
(
self
.
NamespaceIndex
,
self
.
Name
)
__repr__
=
__str__
class
LocalizedText
(
FrozenClass
):
'''
A string qualified with a namespace index.
'''
def
__init__
(
self
,
text
=
""
):
self
.
Encoding
=
0
self
.
Text
=
text
.
encode
()
if
self
.
Text
:
self
.
Encoding
|=
(
1
<<
1
)
if
self
.
Text
:
self
.
Encoding
|=
(
1
<<
1
)
self
.
Locale
=
b''
self
.
_freeze
()
def
to_binary
(
self
):
packet
=
[]
if
self
.
Locale
:
self
.
Encoding
|=
(
1
<<
0
)
if
self
.
Text
:
self
.
Encoding
|=
(
1
<<
1
)
if
self
.
Locale
:
self
.
Encoding
|=
(
1
<<
0
)
if
self
.
Text
:
self
.
Encoding
|=
(
1
<<
1
)
packet
.
append
(
pack_uatype
(
'UInt8'
,
self
.
Encoding
))
if
self
.
Locale
:
if
self
.
Locale
:
packet
.
append
(
pack_uatype
(
'CharArray'
,
self
.
Locale
))
if
self
.
Text
:
if
self
.
Text
:
packet
.
append
(
pack_uatype
(
'CharArray'
,
self
.
Text
))
return
b''
.
join
(
packet
)
@
staticmethod
def
from_binary
(
data
):
obj
=
LocalizedText
()
...
...
@@ -488,11 +524,11 @@ class LocalizedText(FrozenClass):
if
obj
.
Encoding
&
(
1
<<
1
):
obj
.
Text
=
unpack_uatype
(
'CharArray'
,
data
)
return
obj
def
__str__
(
self
):
return
'LocalizedText('
+
'Encoding:'
+
str
(
self
.
Encoding
)
+
', '
+
\
'Locale:'
+
str
(
self
.
Locale
)
+
', '
+
\
'Text:'
+
str
(
self
.
Text
)
+
')'
'Locale:'
+
str
(
self
.
Locale
)
+
', '
+
\
'Text:'
+
str
(
self
.
Text
)
+
')'
__repr__
=
__str__
def
__eq__
(
self
,
other
):
...
...
@@ -502,23 +538,26 @@ class LocalizedText(FrozenClass):
class
ExtensionObject
(
FrozenClass
):
'''
'''
def
__init__
(
self
):
self
.
TypeId
=
NodeId
()
self
.
Encoding
=
0
self
.
Body
=
b''
self
.
_freeze
()
def
to_binary
(
self
):
packet
=
[]
if
self
.
Body
:
self
.
Encoding
|=
(
1
<<
0
)
if
self
.
Body
:
self
.
Encoding
|=
(
1
<<
0
)
packet
.
append
(
self
.
TypeId
.
to_binary
())
packet
.
append
(
pack_uatype
(
'UInt8'
,
self
.
Encoding
))
if
self
.
Body
:
if
self
.
Body
:
packet
.
append
(
pack_uatype
(
'ByteString'
,
self
.
Body
))
return
b''
.
join
(
packet
)
@
staticmethod
def
from_binary
(
data
):
obj
=
ExtensionObject
()
...
...
@@ -538,13 +577,14 @@ class ExtensionObject(FrozenClass):
def
__str__
(
self
):
return
'ExtensionObject('
+
'TypeId:'
+
str
(
self
.
TypeId
)
+
', '
+
\
'Encoding:'
+
str
(
self
.
Encoding
)
+
', '
+
\
'Body:'
+
str
(
self
.
Body
)
+
')'
'Encoding:'
+
str
(
self
.
Encoding
)
+
', '
+
\
'Body:'
+
str
(
self
.
Body
)
+
')'
__repr__
=
__str__
class
VariantType
(
Enum
):
'''
The possible types of a variant.
'''
...
...
@@ -575,13 +615,16 @@ class VariantType(Enum):
Variant
=
24
DiagnosticInfo
=
25
class
Variant
(
object
):
"""
Create an OPC-UA Variant object.
if no argument a Null Variant is created.
if not variant type is given, attemps to guess type from python type
if a variant is given as value, the new objects becomes a copy of the argument
"""
def
__init__
(
self
,
value
=
None
,
varianttype
=
None
):
self
.
Encoding
=
0
self
.
Value
=
value
...
...
@@ -605,17 +648,17 @@ class Variant(object):
def
_guess_type
(
self
,
val
):
if
val
is
None
:
return
VariantType
.
Null
elif
type
(
val
)
==
float
:
elif
isinstance
(
val
,
float
)
:
return
VariantType
.
Double
elif
type
(
val
)
==
int
:
elif
isinstance
(
val
,
int
)
:
return
VariantType
.
Int64
elif
type
(
val
)
in
(
str
,
unicode
):
return
VariantType
.
String
elif
type
(
val
)
==
bytes
:
elif
isinstance
(
val
,
bytes
)
:
return
VariantType
.
ByteString
elif
type
(
val
)
==
datetime
:
elif
isinstance
(
val
,
datetime
)
:
return
VariantType
.
DateTime
elif
type
(
val
)
==
bool
:
elif
isinstance
(
val
,
bool
)
:
# TDA, added this because it was missing and causes exceptions when 'bool' type is used
return
VariantType
.
Boolean
else
:
...
...
@@ -656,44 +699,52 @@ class Variant(object):
class
DataValue
(
object
):
'''
A value with an associated timestamp, and quality.
Automatically generated from xml , copied and modified here to fix errors in xml spec
'''
def
__init__
(
self
,
variant
=
None
):
self
.
Encoding
=
0
if
not
type
(
variant
)
is
Variant
:
if
not
isinstance
(
variant
,
Variant
)
:
variant
=
Variant
(
variant
)
self
.
Value
=
variant
self
.
StatusCode
=
StatusCode
()
self
.
SourceTimestamp
=
datetime
.
now
()
#
DateTime()
self
.
SourceTimestamp
=
datetime
.
now
()
#
DateTime()
self
.
SourcePicoseconds
=
0
self
.
ServerTimestamp
=
datetime
.
now
()
#
DateTime()
self
.
ServerTimestamp
=
datetime
.
now
()
#
DateTime()
self
.
ServerPicoseconds
=
0
def
to_binary
(
self
):
packet
=
[]
if
self
.
Value
:
self
.
Encoding
|=
(
1
<<
0
)
if
self
.
StatusCode
:
self
.
Encoding
|=
(
1
<<
1
)
if
self
.
SourceTimestamp
:
self
.
Encoding
|=
(
1
<<
2
)
if
self
.
ServerTimestamp
:
self
.
Encoding
|=
(
1
<<
3
)
if
self
.
SourcePicoseconds
:
self
.
Encoding
|=
(
1
<<
4
)
if
self
.
ServerPicoseconds
:
self
.
Encoding
|=
(
1
<<
5
)
if
self
.
Value
:
self
.
Encoding
|=
(
1
<<
0
)
if
self
.
StatusCode
:
self
.
Encoding
|=
(
1
<<
1
)
if
self
.
SourceTimestamp
:
self
.
Encoding
|=
(
1
<<
2
)
if
self
.
ServerTimestamp
:
self
.
Encoding
|=
(
1
<<
3
)
if
self
.
SourcePicoseconds
:
self
.
Encoding
|=
(
1
<<
4
)
if
self
.
ServerPicoseconds
:
self
.
Encoding
|=
(
1
<<
5
)
packet
.
append
(
pack_uatype
(
'UInt8'
,
self
.
Encoding
))
if
self
.
Value
:
if
self
.
Value
:
packet
.
append
(
self
.
Value
.
to_binary
())
if
self
.
StatusCode
:
if
self
.
StatusCode
:
packet
.
append
(
self
.
StatusCode
.
to_binary
())
if
self
.
SourceTimestamp
:
packet
.
append
(
pack_uatype
(
'DateTime'
,
self
.
SourceTimestamp
))
#
self.SourceTimestamp.to_binary())
if
self
.
ServerTimestamp
:
packet
.
append
(
pack_uatype
(
'DateTime'
,
self
.
ServerTimestamp
))
#
self.ServerTimestamp.to_binary())
if
self
.
SourcePicoseconds
:
if
self
.
SourceTimestamp
:
packet
.
append
(
pack_uatype
(
'DateTime'
,
self
.
SourceTimestamp
))
#
self.SourceTimestamp.to_binary())
if
self
.
ServerTimestamp
:
packet
.
append
(
pack_uatype
(
'DateTime'
,
self
.
ServerTimestamp
))
#
self.ServerTimestamp.to_binary())
if
self
.
SourcePicoseconds
:
packet
.
append
(
pack_uatype
(
'UInt16'
,
self
.
SourcePicoseconds
))
if
self
.
ServerPicoseconds
:
if
self
.
ServerPicoseconds
:
packet
.
append
(
pack_uatype
(
'UInt16'
,
self
.
ServerPicoseconds
))
return
b''
.
join
(
packet
)
@
staticmethod
def
from_binary
(
data
):
obj
=
DataValue
()
...
...
@@ -703,27 +754,22 @@ class DataValue(object):
if
obj
.
Encoding
&
(
1
<<
1
):
obj
.
StatusCode
=
StatusCode
.
from_binary
(
data
)
if
obj
.
Encoding
&
(
1
<<
2
):
obj
.
SourceTimestamp
=
unpack_uatype
(
'DateTime'
,
data
)
#
DateTime.from_binary(data)
obj
.
SourceTimestamp
=
unpack_uatype
(
'DateTime'
,
data
)
#
DateTime.from_binary(data)
if
obj
.
Encoding
&
(
1
<<
3
):
obj
.
ServerTimestamp
=
unpack_uatype
(
'DateTime'
,
data
)
#
DateTime.from_binary(data)
obj
.
ServerTimestamp
=
unpack_uatype
(
'DateTime'
,
data
)
#
DateTime.from_binary(data)
if
obj
.
Encoding
&
(
1
<<
4
):
obj
.
SourcePicoseconds
=
unpack_uatype
(
'UInt16'
,
data
)
if
obj
.
Encoding
&
(
1
<<
5
):
obj
.
ServerPicoseconds
=
unpack_uatype
(
'UInt16'
,
data
)
return
obj
def
__str__
(
self
):
return
'DataValue('
+
'Encoding:'
+
str
(
self
.
Encoding
)
+
', '
+
\
'Value:'
+
str
(
self
.
Value
)
+
', '
+
\
'StatusCode:'
+
str
(
self
.
StatusCode
)
+
', '
+
\
'SourceTimestamp:'
+
str
(
self
.
SourceTimestamp
)
+
', '
+
\
'ServerTimestamp:'
+
str
(
self
.
ServerTimestamp
)
+
', '
+
\
'SourcePicoseconds:'
+
str
(
self
.
SourcePicoseconds
)
+
', '
+
\
'ServerPicoseconds:'
+
str
(
self
.
ServerPicoseconds
)
+
')'
__repr__
=
__str__
'Value:'
+
str
(
self
.
Value
)
+
', '
+
\
'StatusCode:'
+
str
(
self
.
StatusCode
)
+
', '
+
\
'SourceTimestamp:'
+
str
(
self
.
SourceTimestamp
)
+
', '
+
\
'ServerTimestamp:'
+
str
(
self
.
ServerTimestamp
)
+
', '
+
\
'SourcePicoseconds:'
+
str
(
self
.
SourcePicoseconds
)
+
', '
+
\
'ServerPicoseconds:'
+
str
(
self
.
ServerPicoseconds
)
+
')'
__repr__
=
__str__
opcua/utils.py
View file @
37f69181
...
...
@@ -3,10 +3,12 @@ import uuid
class
Buffer
(
object
):
"""
alternative to io.BytesIO making debug easier
and added a few conveniance methods
"""
def
__init__
(
self
,
data
):
self
.
logger
=
logging
.
getLogger
(
__name__
)
self
.
data
=
data
...
...
@@ -45,8 +47,6 @@ class Buffer(object):
return
self
.
data
[:
size
]
def
recv_all
(
socket
,
size
):
"""
Receive up to size bytes from socket
...
...
@@ -60,6 +60,6 @@ def recv_all(socket, size):
size
-=
len
(
chunk
)
return
data
def
create_nonce
():
return
uuid
.
uuid4
().
bytes
+
uuid
.
uuid4
().
bytes
#seems we need at least 32 bytes not 16 as python gives us...
def
create_nonce
():
return
uuid
.
uuid4
().
bytes
+
uuid
.
uuid4
().
bytes
# seems we need at least 32 bytes not 16 as python gives us...
tests.py
View file @
37f69181
...
...
@@ -24,20 +24,26 @@ from opcua import AttributeIds
port_num1
=
48510
port_num2
=
48530
class
SubHandler
():
'''
Dummy subscription client
'''
def
data_change
(
self
,
handle
,
node
,
val
,
attr
):
pass
pass
def
event
(
self
,
handle
,
event
):
pass
pass
class
MySubHandler
():
'''
More advanced subscription client using Future, so we can wait for events in tests
More advanced subscription client using Future, so we can wait for events in tests
'''
def
__init__
(
self
):
self
.
future
=
Future
()
...
...
@@ -52,8 +58,9 @@ class MySubHandler():
class
Unit
(
unittest
.
TestCase
):
'''
Simple unit test that do not need to setup a server or a client
Simple unit test that do not need to setup a server or a client
'''
def
test_guid
(
self
):
...
...
@@ -62,37 +69,37 @@ class Unit(unittest.TestCase):
def
test_nodeid
(
self
):
nid
=
ua
.
NodeId
()
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
TwoByte
)
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
TwoByte
)
nid
=
ua
.
NodeId
(
446
,
3
,
ua
.
NodeIdType
.
FourByte
)
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
FourByte
)
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
FourByte
)
d
=
nid
.
to_binary
()
new_nid
=
nid
.
from_binary
(
io
.
BytesIO
(
d
))
self
.
assertEqual
(
new_nid
,
nid
)
self
.
assertEqual
(
new_nid
.
NodeIdType
,
ua
.
NodeIdType
.
FourByte
)
self
.
assertEqual
(
new_nid
.
Identifier
,
446
)
self
.
assertEqual
(
new_nid
.
NamespaceIndex
,
3
)
self
.
assertEqual
(
new_nid
,
nid
)
self
.
assertEqual
(
new_nid
.
NodeIdType
,
ua
.
NodeIdType
.
FourByte
)
self
.
assertEqual
(
new_nid
.
Identifier
,
446
)
self
.
assertEqual
(
new_nid
.
NamespaceIndex
,
3
)
tb
=
ua
.
TwoByteNodeId
(
53
)
fb
=
ua
.
FourByteNodeId
(
53
)
n
=
ua
.
NumericNodeId
(
53
)
n1
=
ua
.
NumericNodeId
(
53
,
0
)
s
=
ua
.
StringNodeId
(
53
,
0
)
#
should we raise an exception???
s
=
ua
.
StringNodeId
(
53
,
0
)
#
should we raise an exception???
s1
=
ua
.
StringNodeId
(
"53"
,
0
)
bs
=
ua
.
ByteStringNodeId
(
b"53"
,
0
)
gid
=
ua
.
Guid
()
g
=
ua
.
ByteStringNodeId
(
gid
,
0
)
self
.
assertEqual
(
tb
,
fb
)
self
.
assertEqual
(
tb
,
n
)
self
.
assertEqual
(
tb
,
n1
)
self
.
assertEqual
(
n1
,
fb
)
self
.
assertNotEqual
(
n1
,
s
)
self
.
assertNotEqual
(
s
,
bs
)
self
.
assertNotEqual
(
s
,
g
)
self
.
assertEqual
(
tb
,
fb
)
self
.
assertEqual
(
tb
,
n
)
self
.
assertEqual
(
tb
,
n1
)
self
.
assertEqual
(
n1
,
fb
)
self
.
assertNotEqual
(
n1
,
s
)
self
.
assertNotEqual
(
s
,
bs
)
self
.
assertNotEqual
(
s
,
g
)
def
test_nodeid_string
(
self
):
nid0
=
ua
.
NodeId
(
45
)
self
.
assertEqual
(
nid0
,
ua
.
NodeId
.
from_string
(
"i=45"
))
self
.
assertEqual
(
nid0
,
ua
.
NodeId
.
from_string
(
"ns=0;i=45"
))
self
.
assertEqual
(
nid0
,
ua
.
NodeId
.
from_string
(
"i=45"
))
self
.
assertEqual
(
nid0
,
ua
.
NodeId
.
from_string
(
"ns=0;i=45"
))
nid
=
ua
.
NodeId
(
45
,
10
)
self
.
assertEqual
(
nid
,
ua
.
NodeId
.
from_string
(
"i=45; ns=10"
))
self
.
assertNotEqual
(
nid
,
ua
.
NodeId
.
from_string
(
"i=45; ns=11"
))
...
...
@@ -102,7 +109,7 @@ class Unit(unittest.TestCase):
def
test_expandednodeid
(
self
):
nid
=
ua
.
ExpandedNodeId
()
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
TwoByte
)
self
.
assertEqual
(
nid
.
NodeIdType
,
ua
.
NodeIdType
.
TwoByte
)
nid2
=
ua
.
ExpandedNodeId
.
from_binary
(
ua
.
utils
.
Buffer
(
nid
.
to_binary
()))
self
.
assertEqual
(
nid
,
nid2
)
...
...
@@ -111,7 +118,7 @@ class Unit(unittest.TestCase):
obj2
=
ua
.
ExtensionObject
.
from_binary
(
ua
.
utils
.
Buffer
(
obj
.
to_binary
()))
self
.
assertEqual
(
obj2
.
TypeId
,
obj2
.
TypeId
)
self
.
assertEqual
(
obj2
.
Body
,
obj2
.
Body
)
def
test_datetime
(
self
):
now
=
datetime
.
now
()
epch
=
ua
.
datetime_to_win_epoch
(
now
)
...
...
@@ -126,11 +133,11 @@ class Unit(unittest.TestCase):
def
test_equal_nodeid
(
self
):
nid1
=
ua
.
NodeId
(
999
,
2
)
nid2
=
ua
.
NodeId
(
999
,
2
)
self
.
assertTrue
(
nid1
==
nid2
)
self
.
assertTrue
(
id
(
nid1
)
!=
id
(
nid2
))
self
.
assertTrue
(
nid1
==
nid2
)
self
.
assertTrue
(
id
(
nid1
)
!=
id
(
nid2
))
def
test_zero_nodeid
(
self
):
self
.
assertEqual
(
ua
.
NodeId
(),
ua
.
NodeId
(
0
,
0
))
self
.
assertEqual
(
ua
.
NodeId
(),
ua
.
NodeId
(
0
,
0
))
self
.
assertEqual
(
ua
.
NodeId
(),
ua
.
NodeId
.
from_string
(
'ns=0;i=0;'
))
def
test_string_nodeid
(
self
):
...
...
@@ -150,25 +157,24 @@ class Unit(unittest.TestCase):
self
.
assertEqual
(
nid
.
NamespaceIndex
,
2
)
self
.
assertEqual
(
nid
.
Identifier
,
'PLC1.Manufacturer'
)
def
test_strrepr_nodeid
(
self
):
nid
=
ua
.
NodeId
.
from_string
(
'ns=2;s=PLC1.Manufacturer;'
)
self
.
assertEqual
(
nid
.
to_string
(),
'ns=2;s=PLC1.Manufacturer'
)
#self.assertEqual(repr(nid), 'ns=2;s=PLC1.Manufacturer;')
def
test_qualified_name
(
self
):
qn
=
ua
.
QualifiedName
(
'qname'
,
2
)
self
.
assertEqual
(
qn
.
NamespaceIndex
,
2
)
self
.
assertEqual
(
qn
.
Name
,
'qname'
)
self
.
assertEqual
(
qn
.
to_string
(),
'2:qname'
)
def
test_datavalue
(
self
):
dv
=
ua
.
DataValue
(
123
)
self
.
assertEqual
(
dv
.
Value
,
ua
.
Variant
(
123
))
self
.
assertEqual
(
type
(
dv
.
Value
),
ua
.
Variant
)
dv
=
ua
.
DataValue
(
'abc'
)
self
.
assertEqual
(
dv
.
Value
,
ua
.
Variant
(
'abc'
))
now
=
datetime
.
now
()
now
=
datetime
.
now
()
dv
.
source_timestamp
=
now
def
test_variant
(
self
):
...
...
@@ -182,13 +188,13 @@ class Unit(unittest.TestCase):
v2
=
ua
.
Variant
.
from_binary
(
ua
.
utils
.
Buffer
(
v
.
to_binary
()))
self
.
assertEqual
(
v
.
Value
,
v2
.
Value
)
self
.
assertEqual
(
v
.
VariantType
,
v2
.
VariantType
)
#commonity method:
#
commonity method:
self
.
assertEqual
(
v
,
ua
.
Variant
(
v
))
def
test_variant_array
(
self
):
v
=
ua
.
Variant
([
1
,
2
,
3
,
4
,
5
])
v
=
ua
.
Variant
([
1
,
2
,
3
,
4
,
5
])
self
.
assertEqual
(
v
.
Value
[
1
],
2
)
#self.assertEqual(v.VarianType, ua.VariantType.Int64) # we do not care, we should aonly test for sutff that matter
#
self.assertEqual(v.VarianType, ua.VariantType.Int64) # we do not care, we should aonly test for sutff that matter
v2
=
ua
.
Variant
.
from_binary
(
ua
.
utils
.
Buffer
(
v
.
to_binary
()))
self
.
assertEqual
(
v
.
Value
,
v2
.
Value
)
self
.
assertEqual
(
v
.
VariantType
,
v2
.
VariantType
)
...
...
@@ -210,10 +216,12 @@ class Unit(unittest.TestCase):
t4
=
ua
.
LocalizedText
.
from_binary
(
ua
.
utils
.
Buffer
(
t1
.
to_binary
()))
self
.
assertEqual
(
t1
,
t4
)
class
CommonTests
(
object
):
'''
Tests that will be run twice. Once on server side and once on
client side since we have been carefull to have the exact
Tests that will be run twice. Once on server side and once on
client side since we have been carefull to have the exact
same api on server and client side
'''
...
...
@@ -221,13 +229,13 @@ class CommonTests(object):
root
=
self
.
opc
.
get_root_node
()
self
.
assertEqual
(
ua
.
QualifiedName
(
'Root'
,
0
),
root
.
get_browse_name
())
self
.
assertEqual
(
ua
.
LocalizedText
(
'Root'
),
root
.
get_display_name
())
nid
=
ua
.
NodeId
(
84
,
0
)
nid
=
ua
.
NodeId
(
84
,
0
)
self
.
assertEqual
(
nid
,
root
.
nodeid
)
def
test_objects
(
self
):
objects
=
self
.
opc
.
get_objects_node
()
self
.
assertEqual
(
ua
.
QualifiedName
(
'Objects'
,
0
),
objects
.
get_browse_name
())
nid
=
ua
.
NodeId
(
85
,
0
)
nid
=
ua
.
NodeId
(
85
,
0
)
self
.
assertEqual
(
nid
,
objects
.
nodeid
)
def
test_browse
(
self
):
...
...
@@ -265,7 +273,7 @@ class CommonTests(object):
time
.
sleep
(
0.1
)
sub
.
unsubscribe
(
handle
)
sub
.
delete
()
def
test_subscribe_events
(
self
):
sub
=
self
.
opc
.
create_subscription
(
100
,
sclt
)
handle
=
sub
.
subscribe_events
()
...
...
@@ -278,9 +286,9 @@ class CommonTests(object):
#cond = msclt.setup()
sub
=
self
.
opc
.
create_subscription
(
100
,
msclt
)
handle
=
sub
.
subscribe_events
()
ev
=
Event
(
self
.
srv
.
iserver
.
isession
)
msg
=
b"this is my msg "
msg
=
b"this is my msg "
ev
.
Message
.
Text
=
msg
tid
=
datetime
.
now
()
ev
.
Time
=
tid
...
...
@@ -288,23 +296,22 @@ class CommonTests(object):
#ev.source_name = "our server node"
ev
.
Severity
=
500
ev
.
trigger
()
clthandle
,
ev
=
msclt
.
future
.
result
()
#with cond:
#ret = cond.wait(50000)
#if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#else: pass # python2
self
.
assertIsNot
(
ev
,
None
)
# we did not receive event
#
with cond:
#ret = cond.wait(50000)
#
if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#
else: pass # python2
self
.
assertIsNot
(
ev
,
None
)
# we did not receive event
self
.
assertEqual
(
ev
.
Message
.
Text
,
msg
)
#self.assertEqual(msclt.ev.Time, tid)
self
.
assertEqual
(
ev
.
Severity
,
500
)
self
.
assertEqual
(
ev
.
SourceNode
,
self
.
opc
.
get_server_node
().
nodeid
)
#time.sleep(0.1)
#
time.sleep(0.1)
sub
.
unsubscribe
(
handle
)
sub
.
delete
()
def
test_non_existing_path
(
self
):
root
=
self
.
opc
.
get_root_node
()
with
self
.
assertRaises
(
Exception
):
...
...
@@ -334,29 +341,29 @@ class CommonTests(object):
objects
=
self
.
opc
.
get_objects_node
()
v1
=
objects
.
add_variable
(
4
,
"test_datetime"
,
now
)
tid
=
v1
.
get_value
()
self
.
assertEqual
(
now
,
tid
)
self
.
assertEqual
(
now
,
tid
)
def
test_add_numeric_variable
(
self
):
objects
=
self
.
opc
.
get_objects_node
()
v
=
objects
.
add_variable
(
'ns=3;i=888;'
,
'3:numericnodefromstring'
,
99
)
nid
=
ua
.
NodeId
(
888
,
3
)
qn
=
ua
.
QualifiedName
(
'numericnodefromstring'
,
3
)
qn
=
ua
.
QualifiedName
(
'numericnodefromstring'
,
3
)
self
.
assertEqual
(
nid
,
v
.
nodeid
)
self
.
assertEqual
(
qn
,
v
.
get_browse_name
())
def
test_add_string_variable
(
self
):
objects
=
self
.
opc
.
get_objects_node
()
v
=
objects
.
add_variable
(
'ns=3;s=stringid;'
,
'3:stringnodefromstring'
,
[
68
])
nid
=
ua
.
NodeId
(
'stringid'
,
3
)
qn
=
ua
.
QualifiedName
(
'stringnodefromstring'
,
3
)
nid
=
ua
.
NodeId
(
'stringid'
,
3
)
qn
=
ua
.
QualifiedName
(
'stringnodefromstring'
,
3
)
self
.
assertEqual
(
nid
,
v
.
nodeid
)
self
.
assertEqual
(
qn
,
v
.
get_browse_name
())
def
test_add_string_array_variable
(
self
):
objects
=
self
.
opc
.
get_objects_node
()
v
=
objects
.
add_variable
(
'ns=3;s=stringarrayid;'
,
'9:stringarray'
,
[
'l'
,
'b'
])
nid
=
ua
.
NodeId
(
'stringarrayid'
,
3
)
qn
=
ua
.
QualifiedName
(
'stringarray'
,
9
)
nid
=
ua
.
NodeId
(
'stringarrayid'
,
3
)
qn
=
ua
.
QualifiedName
(
'stringarray'
,
9
)
self
.
assertEqual
(
nid
,
v
.
nodeid
)
self
.
assertEqual
(
qn
,
v
.
get_browse_name
())
val
=
v
.
get_value
()
...
...
@@ -430,10 +437,9 @@ class CommonTests(object):
with
self
.
assertRaises
(
Exception
):
bad
.
set_value
(
89
)
with
self
.
assertRaises
(
Exception
):
bad
.
add_object
(
0
,
"myobj"
)
bad
.
add_object
(
0
,
"myobj"
)
with
self
.
assertRaises
(
Exception
):
bad
.
get_child
(
0
,
"myobj"
)
bad
.
get_child
(
0
,
"myobj"
)
def
test_array_value
(
self
):
o
=
self
.
opc
.
get_objects_node
()
...
...
@@ -446,13 +452,13 @@ class CommonTests(object):
v
=
o
.
add_variable
(
3
,
'VariableArrayValue'
,
[
1
,
2
,
3
])
v
.
set_value
([
1
])
val
=
v
.
get_value
()
self
.
assertEqual
([
1
],
val
)
self
.
assertEqual
([
1
],
val
)
def
test_subscription_data_change
(
self
):
'''
test subscriptions. This is far too complicated for
a unittest but, setting up subscriptions requires a lot
of code, so when we first set it up, it is best
of code, so when we first set it up, it is best
to test as many things as possible
'''
msclt
=
MySubHandler
()
...
...
@@ -467,34 +473,34 @@ class CommonTests(object):
handle1
=
sub
.
subscribe_data_change
(
v1
)
# Now check we get the start value
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
#with cond:
#ret = cond.wait(0.5)
#if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#else: pass # XXX
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
#
with cond:
#ret = cond.wait(0.5)
#
if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#
else: pass # XXX
self
.
assertEqual
(
val
,
startv1
)
self
.
assertEqual
(
node
,
v1
)
msclt
.
reset
()
#
reset future object
msclt
.
reset
()
#
reset future object
# modify v1 and check we get value
# modify v1 and check we get value
v1
.
set_value
([
5
])
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
#with cond:
#ret = cond.wait(0.5)
#if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#else: pass # XXX
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
#
with cond:
#ret = cond.wait(0.5)
#
if sys.version_info.major>2: self.assertEqual(ret, True) # we went into timeout waiting for subcsription callback
#
else: pass # XXX
self
.
assertEqual
(
node
,
v1
)
self
.
assertEqual
(
val
,
[
5
])
with
self
.
assertRaises
(
Exception
):
sub
.
unsubscribe
(
999
)
# non existing handle
sub
.
unsubscribe
(
999
)
# non existing handle
sub
.
unsubscribe
(
handle1
)
with
self
.
assertRaises
(
Exception
):
sub
.
unsubscribe
(
handle1
)
# second try should fail
sub
.
unsubscribe
(
handle1
)
# second try should fail
sub
.
delete
()
with
self
.
assertRaises
(
Exception
):
sub
.
unsubscribe
(
handle1
)
# sub does not exist anymore
sub
.
unsubscribe
(
handle1
)
# sub does not exist anymore
def
test_subscribe_server_time
(
self
):
msclt
=
MySubHandler
()
...
...
@@ -503,10 +509,10 @@ class CommonTests(object):
sub
=
self
.
opc
.
create_subscription
(
200
,
msclt
)
handle
=
sub
.
subscribe_data_change
(
server_time_node
)
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
clthandle
,
node
,
val
,
attr
=
msclt
.
future
.
result
()
self
.
assertEqual
(
node
,
server_time_node
)
delta
=
datetime
.
now
()
-
val
delta
=
datetime
.
now
()
-
val
self
.
assertTrue
(
delta
<
timedelta
(
seconds
=
1
))
sub
.
unsubscribe
(
handle
)
...
...
@@ -514,11 +520,11 @@ class CommonTests(object):
def
test_use_namespace
(
self
):
idx
=
self
.
opc
.
get_namespace_index
(
"urn:freeopcua:python:server"
)
self
.
assertEqual
(
idx
,
1
)
self
.
assertEqual
(
idx
,
1
)
root
=
self
.
opc
.
get_root_node
()
myvar
=
root
.
add_variable
(
idx
,
'var_in_custom_namespace'
,
[
5
])
myid
=
myvar
.
nodeid
self
.
assertEqual
(
idx
,
myid
.
NamespaceIndex
)
self
.
assertEqual
(
idx
,
myid
.
NamespaceIndex
)
def
test_method
(
self
):
o
=
self
.
opc
.
get_objects_node
()
...
...
@@ -526,10 +532,10 @@ class CommonTests(object):
result
=
o
.
call_method
(
"2:ServerMethod"
,
2.1
)
self
.
assertEqual
(
result
,
4.2
)
with
self
.
assertRaises
(
Exception
):
#FIXME: we should raise a more precise exception
#
FIXME: we should raise a more precise exception
result
=
o
.
call_method
(
"2:ServerMethod"
,
2.1
,
89
,
9
)
with
self
.
assertRaises
(
Exception
):
result
=
o
.
call_method
(
ua
.
NodeId
(
999
),
2.1
)
#
non existing method
result
=
o
.
call_method
(
ua
.
NodeId
(
999
),
2.1
)
#
non existing method
def
test_method_array
(
self
):
o
=
self
.
opc
.
get_objects_node
()
...
...
@@ -558,9 +564,6 @@ class CommonTests(object):
self
.
assertTrue
(
endpoints
[
0
].
EndpointUrl
.
startswith
(
"opc.tcp://localhost"
))
def
add_server_methods
(
srv
):
@
uamethod
def
func
(
parent
,
value
):
...
...
@@ -569,7 +572,6 @@ def add_server_methods(srv):
o
=
srv
.
get_objects_node
()
v
=
o
.
add_method
(
ua
.
NodeId
(
"ServerMethod"
,
2
),
ua
.
QualifiedName
(
'ServerMethod'
,
2
),
func
,
[
ua
.
VariantType
.
Int64
],
[
ua
.
VariantType
.
Int64
])
@
uamethod
def
func2
(
parent
,
methodname
,
value
):
return
math
.
sin
(
value
)
...
...
@@ -585,18 +587,18 @@ def add_server_methods(srv):
v
=
o
.
add_method
(
ua
.
NodeId
(
"ServerMethodArray2"
,
2
),
ua
.
QualifiedName
(
'ServerMethodArray2'
,
2
),
func3
,
[
ua
.
VariantType
.
Int64
],
[
ua
.
VariantType
.
Int64
])
class
TestClient
(
unittest
.
TestCase
,
CommonTests
):
'''
Run common tests on client side
Of course we need a server so we start a server in another
Of course we need a server so we start a server in another
process using python Process module
Tests that can only be run on client side must be defined here
'''
@
classmethod
def
setUpClass
(
self
):
# start our own server
self
.
srv
=
Server
()
self
.
srv
=
Server
()
self
.
srv
.
set_endpoint
(
'opc.tcp://localhost:%d'
%
port_num1
)
add_server_methods
(
self
.
srv
)
self
.
srv
.
start
()
...
...
@@ -614,13 +616,13 @@ class TestClient(unittest.TestCase, CommonTests):
def
test_service_fault
(
self
):
request
=
ua
.
ReadRequest
()
request
.
TypeId
=
ua
.
FourByteNodeId
(
999
)
# bad type!
request
.
TypeId
=
ua
.
FourByteNodeId
(
999
)
# bad type!
with
self
.
assertRaises
(
Exception
):
self
.
clt
.
bclient
.
_send_request
(
request
)
class
TestServer
(
unittest
.
TestCase
,
CommonTests
):
'''
Run common tests on server side
Tests that can only be run on server side must be defined here
...
...
@@ -631,18 +633,17 @@ class TestServer(unittest.TestCase, CommonTests):
self
.
srv
.
set_endpoint
(
'opc.tcp://localhost:%d'
%
port_num2
)
add_server_methods
(
self
.
srv
)
self
.
srv
.
start
()
self
.
opc
=
self
.
srv
self
.
opc
=
self
.
srv
@
classmethod
def
tearDownClass
(
self
):
self
.
srv
.
stop
()
def
test_register_namespace
(
self
):
uri
=
'http://mycustom.Namespace.com'
idx1
=
self
.
opc
.
register_namespace
(
uri
)
idx2
=
self
.
opc
.
get_namespace_index
(
uri
)
self
.
assertEqual
(
idx1
,
idx2
)
self
.
assertEqual
(
idx1
,
idx2
)
def
test_register_use_namespace
(
self
):
uri
=
'http://my_very_custom.Namespace.com'
...
...
@@ -650,7 +651,7 @@ class TestServer(unittest.TestCase, CommonTests):
root
=
self
.
opc
.
get_root_node
()
myvar
=
root
.
add_variable
(
idx
,
'var_in_custom_namespace'
,
[
5
])
myid
=
myvar
.
nodeid
self
.
assertEqual
(
idx
,
myid
.
NamespaceIndex
)
self
.
assertEqual
(
idx
,
myid
.
NamespaceIndex
)
def
test_server_method
(
self
):
def
func
(
parent
,
variant
):
...
...
@@ -662,10 +663,8 @@ class TestServer(unittest.TestCase, CommonTests):
self
.
assertEqual
(
result
,
4.2
)
if
__name__
==
'__main__'
:
logging
.
basicConfig
(
level
=
logging
.
WARN
)
sclt
=
SubHandler
()
unittest
.
main
(
verbosity
=
3
)
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