Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added is_connected() method & PEP-8 asyncua/client/client.py #157

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions asyncua/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
from urllib.parse import urlparse

from asyncua import ua
from .ua_client import UaClient
from ..common.xmlimporter import XmlImporter
from ..common.xmlexporter import XmlExporter
from ..common.node import Node
from .ua_client import UaClient, UASocketProtocol
from ..common.manage_nodes import delete_nodes
from ..common.subscription import Subscription
from ..common.node import Node
from ..common.shortcuts import Shortcuts
from ..common.structures import load_type_definitions, load_enums
from ..common.utils import create_nonce
from ..common.subscription import Subscription
from ..common.ua_utils import value_to_datavalue
from ..common.utils import create_nonce
from ..common.xmlexporter import XmlExporter
from ..common.xmlimporter import XmlImporter
from ..crypto import uacrypto, security_policies

_logger = logging.getLogger(__name__)
Expand All @@ -29,6 +29,7 @@ class Client:
use UaClient object, available as self.uaclient
which offers the raw OPC-UA services interface.
"""

def __init__(self, url: str, timeout: int = 4, loop=None):
"""
:param url: url of the server.
Expand Down Expand Up @@ -86,7 +87,8 @@ def find_endpoint(endpoints, security_mode, policy_uri):
"""
_logger.info("find_endpoint %r %r %r", endpoints, security_mode, policy_uri)
for ep in endpoints:
if (ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and ep.SecurityMode == security_mode and ep.SecurityPolicyUri == policy_uri):
if (ep.EndpointUrl.startswith(
ua.OPC_TCP_SCHEME) and ep.SecurityMode == security_mode and ep.SecurityPolicyUri == policy_uri):
return ep
raise ua.UaError("No matching endpoints: {0}, {1}".format(security_mode, policy_uri))

Expand Down Expand Up @@ -266,7 +268,8 @@ async def open_secure_channel(self, renew=False):
params.ClientNonce = create_nonce(self.security_policy.symmetric_key_size)
result = await self.uaclient.open_secure_channel(params)
if self.secure_channel_timeout != result.SecurityToken.RevisedLifetime:
_logger.info("Requested secure channel timeout to be %dms, got %dms instead", self.secure_channel_timeout, result.SecurityToken.RevisedLifetime)
_logger.info("Requested secure channel timeout to be %dms, got %dms instead", self.secure_channel_timeout,
result.SecurityToken.RevisedLifetime)
self.secure_channel_timeout = result.SecurityToken.RevisedLifetime

async def close_secure_channel(self):
Expand Down Expand Up @@ -353,7 +356,8 @@ async def create_session(self):
self._policy_ids = ep.UserIdentityTokens
# Actual maximum number of milliseconds that a Session shall remain open without activity
if self.session_timeout != response.RevisedSessionTimeout:
_logger.warning("Requested session timeout to be %dms, got %dms instead", self.secure_channel_timeout, response.RevisedSessionTimeout)
_logger.warning("Requested session timeout to be %dms, got %dms instead", self.secure_channel_timeout,
response.RevisedSessionTimeout)
self.session_timeout = response.RevisedSessionTimeout
self._renew_channel_task = self.loop.create_task(self._renew_channel_loop())
return response
Expand Down Expand Up @@ -620,5 +624,11 @@ async def write_values(self, nodes, values):
for result in results:
result.check()

async def is_connected(self):
"""
Get OPC-UA client connection status.
"""
return self.uaclient.protocol.state == UASocketProtocol.OPEN
Copy link
Member

Choose a reason for hiding this comment

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

this might work... but does it always work? I man can't we get errors like "protocol has not attrtbute called state",etc...

Copy link
Contributor

Choose a reason for hiding this comment

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

I also tried this in my own local implementation of is_connected and had trouble getting it to be robust. For example, if I disconnected network, the connection_lost callback didn't necessarily get called immediately, and the state still remained OPEN. I settled on querying the status node of the server as it seemed to be the most reliable method, but would be interested in trying to get this method to be robust.

Copy link
Member

Choose a reason for hiding this comment

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

Also from what I remember looking at how prosys and unified automation clients work. They seem to be polling at quite high frequency. Reading the server node once a second or similar. So this seems to be the common way to do it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What if we call get_namespaces()? I know that it is not the cleanest way to do it but it is an option

Copy link
Member

Choose a reason for hiding this comment

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

Why not reading the server state node? If this is what all others do.. Then you can also check what is the state of server


get_values = read_values # legacy compatibility
set_values = write_values # legacy compatibility
3 changes: 3 additions & 0 deletions examples/client-minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ async def main():
url = 'opc.tcp://localhost:4840/freeopcua/server/'
# url = 'opc.tcp://commsvr.com:51234/UA/CAS_UA_Server'
async with Client(url=url) as client:
if not await client.is_connected():
_logger.error("Connection drops")

# Client has a few methods to get proxy to UA nodes that should always be in address space such as Root or Objects
root = client.get_root_node()
_logger.info('Objects node is: %r', root)
Expand Down