Skip to content

Commit

Permalink
Merge pull request #21 from WimPessemier/master
Browse files Browse the repository at this point in the history
v1.7
  • Loading branch information
WimPessemier committed Jan 28, 2014
2 parents ef76df4 + 144684e commit 47ad513
Show file tree
Hide file tree
Showing 70 changed files with 1,667 additions and 494 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -21,6 +21,8 @@ What?

- OPC UA (OPC Unified Architecture) is the next generation standard for secure, reliable
and scalable industrial communication.

- In short, the UAF / pyUAF is **OPC UA made easy!**


Why?
Expand Down Expand Up @@ -193,6 +195,7 @@ Status?
- create monitored events items (synchronous)
- browse and browse next (synchronous)
- read historical data (raw data + modifications) (synchronous)
- set publishing mode (synchronous)
- Supported on the Server side:
- nothing yet so far!

Expand Down
86 changes: 86 additions & 0 deletions changelog.rst.txt
Expand Up @@ -17,6 +17,92 @@ Bugfixes (which do not have any implications on the API) can only be tracked
via the GIT version control system.



1.7 @ 2014/01/28
----------------------------------------------------------------------------------------------------

Informally:

- The 'SetPublishingMode' service has been implemented.
With this service, you can enable or disable the publishing of notifications by a particular
subscription. It's useful e.g. when you want to receive datachange notifications *only* when
a particular widget of your User Interface is active. In this case, you can group the monitored
items of that widget in a single subscription (could be a "unique" subscription, by setting the
"unique" flag of the SubscriptionSettings), and then subsequently:

- enable the subscription, whenever the widget becomes active
- disable the subscription, whenever the widget becomes inactive.

- A method 'monitoredItemInformation(<ClientHandle>)' has been added to the Client class.
The result of this method is a 'MonitoredItemInformation' instance, which provides you information
about the monitored item that you specified using the client handle. Among other things, it
will tell you the handle of the subscription that owns the monitored item.

- !!!Breaking change!!!

The concept of 'notification handles' was redundant with the concept of 'client handles' of
monitored items. It has therefore been *removed* from the UAF. To make sure your code remains
compatible, do a (case insensitive) search-all for 'notificationHandle', and replace all
occurrences with 'clientHandle'. Note that the term 'notificationhandle' may occur both in
attribute names and in method names.

- !!!Breaking change!!!

BaseServiceSettings has been renamed to ServiceSettings. This -most likely- won't affect your
existing code. To be sure: do a search-all for 'BaseServiceSettings' and replace all occurrences
with 'ServiceSettings'.

- !!!Breaking change!!!

The 'exclusive' flag of subscription settings has been renamed to a more appropriate 'unique'
flag (to force the UAF to create a new subscription instead of reusing an existing one).
This -most likely- won't affect your existing code. To be sure: do a search-all for
'exclusive' and replace all occurrences with 'unique'.

Formally:

- The following methods were added to existing classes:

- uafc::Client::setPublishingMode()
(pyuaf.client.Client.setPublishingMode())
- uafc::Client::monitoredItemInformation()
(pyuaf.client.Client.monitoredItemInformation())

- The following classes were added:

- uafc::MonitoredItemInformation (pyuaf.client.Client.MonitoredItemInformation)

- The following namespaces (modules) were added:

- uafc::monitoreditemstates (pyuaf.client.monitoreditemstates)

- The following attributes have been renamed or removed due to the removal of the redundant
'notification handle' concept in favor of the 'client handle' concept:

- uafc::StatusDiagnostics (pyuaf.util.StatusDiagnostics):

- hasNotificationHandles() -> renamed to hasClientHandles()
- getNotificationHandles() -> renamed to getClientHandles()
- setNotificationHandles() -> renamed to setClientHandles()

- | uafc::MonitoredItemNotification (pyuaf.client.subscriptions.MonitoredItemNotification),
| uafc::DataChangeNotification (pyuaf.client.subscriptions.DataChangeNotification),
| uafc::CreateMonitoredEventsResultTarget (pyuaf.client.results.CreateMonitoredEventsResultTarget),
| uafc::CreateMonitoredDataResultTarget (pyuaf.client.results.CreateMonitoredDataResultTarget)
- notificationHandle -> removed (use clientHandle instead)

- The following classes have been renamed:

- uafc::BaseServiceSettings (pyuaf.client.settings.BaseServiceSettings)
has been renamed to uafc::ServiceSettings (pyuaf.client.settings.ServiceSettings)

- The following attributes have been renamed:

- uafc::SubscriptionSettings::exclusive (pyuaf.client.settings.SubscriptionSettings.exclusive)
has been renamed to uafc::ServiceSettings::unique (pyuaf.client.settings.ServiceSettings.unique)


1.6 @ 2014/01/07
----------------------------------------------------------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions src/pyuaf/client/CMakeLists.txt
Expand Up @@ -20,6 +20,7 @@ set( PYUAF_CLIENT_TARGETS
__init__
sessionstates
subscriptionstates
monitoreditemstates
settings
configs
requests
Expand Down
128 changes: 116 additions & 12 deletions src/pyuaf/client/client.py
Expand Up @@ -203,7 +203,7 @@ def callComplete(self, result):
def __dispatch_dataChangesReceived__(self, dataNotifications):
"""
Dispatch the DataNofications either to a virtual dataChangesReceived function,
or to a callback function (if one is found for the given notificationHandle).
or to a callback function (if one is found for the given client handle).
"""
notificationsWithoutCallback = []

Expand All @@ -212,7 +212,7 @@ def __dispatch_dataChangesReceived__(self, dataNotifications):

for notification in dataNotifications:
try:
f = self.__dataNotificationCallbacks__[notification.notificationHandle]
f = self.__dataNotificationCallbacks__[notification.clientHandle]
t = threading.Thread(target=f, args=[notification])
t.start()
except:
Expand Down Expand Up @@ -256,7 +256,7 @@ def dataChangesReceived(self, dataNotifications):
def __dispatch_eventsReceived__(self, eventNotifications):
"""
Dispatch the EventNofications either to a virtual dataChangesReceived function,
or to a callback function (if one is found for the given notificationHandle).
or to a callback function (if one is found for the given client handle).
"""
notificationsWithoutCallback = []

Expand All @@ -265,7 +265,7 @@ def __dispatch_eventsReceived__(self, eventNotifications):

for notification in eventNotifications:
try:
f = self.__eventNotificationCallbacks__[notification.notificationHandle]
f = self.__eventNotificationCallbacks__[notification.clientHandle]
t = threading.Thread(target=f, args=[notification])
t.start()
except:
Expand Down Expand Up @@ -555,7 +555,7 @@ def sessionInformation(self, clientConnectionId):
Get information about the specified session.

:param clientConnectionId: The client connection id
(always assigned by the client, not by the user!).
(always assigned by the UAF, not by the user!).
:type clientConnectionId: ``int``
:return: Information about the specified session.
:rtype: :class:`pyuaf.client.SessionInformation`
Expand Down Expand Up @@ -618,6 +618,36 @@ def allSubscriptionInformations(self):
return l
def monitoredItemInformation(self, clientHandle):
"""
Get information about the specified monitored item.

Check the :attr:`~pyuaf.client.MonitoredItemInformation.monitoredItemState` before
interpreting the results of the :class:`~pyuaf.client.MonitoredItemInformation`!
Because if the :attr:`~pyuaf.client.MonitoredItemInformation.monitoredItemState` is
:attr:`~pyuaf.client.monitoreditemstates.NotCreated`, then the monitored item not created on
the server, but instead it's cached by the client (which tries to re-create the monitored
item periodically -as configurable by the :class:`~pyuaf.client.settings.ClientSettings`-).

:param clientHandle: The handle of the monitored item (always assigned by the UAF, not
by the user!). This clientHandle is assigned when the monitored item
is requested (e.g. by calling :meth:`~pyuaf.client.Client.createMonitoredData`),
regardless of whether the monitored items were indeed created on the server,
or not (e.g. in case of failures, or in case the server is not on-line yet).
:type clientHandle: ``int``
:return: Information about the specified monitored item.
:rtype: :class:`~pyuaf.client.MonitoredItemInformation`
:raise pyuaf.util.errors.UnknownHandleError:
Raised in case no monitored item is known for the given client handle.
:raise pyuaf.util.errors.UafError:
Base exception, catch this to handle any other errors.
"""
info = pyuaf.client.MonitoredItemInformation()
status = ClientBase.monitoredItemInformation(self, clientHandle, info)
pyuaf.util.errors.evaluate(status)
return info


def read(self, addresses, attributeId=pyuaf.util.attributeids.Value, serviceConfig=None, sessionConfig=None):
"""
Read a number of node attributes synchronously.
Expand Down Expand Up @@ -1502,7 +1532,7 @@ def createMonitoredData(self, addresses, serviceConfig=None, sessionConfig=None,
- via the exception that was raised in case createMonitoredData() was not successful.
This exception has a "status" attribute (of type :class:`~pyuaf.util.Status`),
and this will provide you a diagnostics object (see :meth:`pyuaf.util.Status.additionalDiagnostics`),
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getNotificationHandles`).
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getClientHandles`).

.. warning::

Expand Down Expand Up @@ -1583,7 +1613,7 @@ def createMonitoredData(self, addresses, serviceConfig=None, sessionConfig=None,
if len(notificationCallbacks) > 0:
if len(notificationCallbacks) == len(result.targets):
for i in xrange(len(notificationCallbacks)):
self.__dataNotificationCallbacks__[result.targets[i].notificationHandle] \
self.__dataNotificationCallbacks__[result.targets[i].clientHandle] \
= notificationCallbacks[i]
else:
raise TypeError("The number of result targets does not correspond to the "
Expand Down Expand Up @@ -1629,7 +1659,7 @@ def createMonitoredEvents(self, addresses, eventFilter=None, serviceConfig=None,
- via the exception that was raised in case createMonitoredEvents() was not successful.
This exception has a "status" attribute (of type :class:`~pyuaf.util.Status`),
and this will provide you a diagnostics object (see :meth:`pyuaf.util.Status.additionalDiagnostics`),
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getNotificationHandles`).
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getClientHandles`).

.. warning::

Expand Down Expand Up @@ -1722,7 +1752,7 @@ def createMonitoredEvents(self, addresses, eventFilter=None, serviceConfig=None,
if len(notificationCallbacks) > 0:
if len(notificationCallbacks) == len(result.targets):
for i in xrange(len(notificationCallbacks)):
self.__eventNotificationCallbacks__[result.targets[i].notificationHandle] \
self.__eventNotificationCallbacks__[result.targets[i].clientHandle] \
= notificationCallbacks[i]
else:
raise TypeError("The number of result targets does not correspond to the "
Expand All @@ -1735,7 +1765,81 @@ def createMonitoredEvents(self, addresses, eventFilter=None, serviceConfig=None,
finally:
self.__eventNotificationLock__.release()



def setPublishingMode(self, clientSubscriptionHandle, publishingEnabled, serviceSettings=None):
"""
Set the publishing mode, by specifying a subscription handle.

Note that a subscription handle may *not* be known at the time when you create the
monitored items. E.g. when you call :meth:`~pyuaf.client.Client.createMonitoredData` or
:meth:`~pyuaf.client.Client.createMonitoredEvents`, it can happen that the server that
hosts the monitored items is not on-line yet. In this case, the ClientSubscriptionHandle
is *not* assigned yet, but ClientHandles *are* assigned yet. Therefore it makes sense to
first call :meth:`~pyuaf.client.Client.monitoredItemInformation` of your monitored item,
and get the subscription handle from there.

E.g. like this:

.. doctest::

>>> import pyuaf
>>> from pyuaf.util.errors import UafError
>>> from pyuaf.util import Address, NodeId
>>> from pyuaf.client import Client
>>>
>>> myClient = Client("myClient", ["opc.tcp://localhost:4841"])
>>>
>>> nameSpaceUri = "http://mycompany.com/mymachine"
>>> serverUri = "http://mycompany.com/servers/plc1"
>>> address = Address( NodeId("myMachine.myVariable", nameSpaceUri), serverUri)
>>>
>>> def myCallback(notification):
... print("A data change was received: %s" %notification)
>>>
>>> try:
... result = myClient.createMonitoredData([address],
... notificationCallbacks = [myCallback])
... clientHandle = result.targets[0].clientHandle
... except UafError, e:
... # The monitored items could not be created, because there was some failure
... # (maybe the server is off-line?).
... # Nevertheless, the client handles were already assigned, and we can get them like this:
... diagnostics = e.status.additionalDiagnostics()
... clientHandle = diagnostics.getClientHandles()[0]
>>>
>>>
>>> info = myClient.monitoredItemInformation(clientHandle)
>>>
>>> if info.monitoredItemState == pyuaf.client.monitoreditemstates.Created:
... # enable the subscription that hosts the monitored item:
... myClient.setPublishingMode(info.clientSubscriptionHandle, True)
...
... # ... do some stuff ...
...
... # disable the subscription that hosts the monitored item:
... myClient.setPublishingMode(info.clientSubscriptionHandle, False)

:param clientSubscriptionHandle: The handle identifying the subscription.
:type clientSubscriptionHandle: ``int``
:param publishingEnabled: True to enable the publishing mode, false to disable.
:type publishingEnabled: ``bool``
:param serviceSettings: The service settings to be used (leave None for
default settings).
:type serviceSettings: :class:`pyuaf.client.settings.ServiceSettings`
:raise pyuaf.util.errors.UnknownHandleError:
The clientSubscriptionHandle is unknown!
:raise pyuaf.util.errors.UafError:
Base exception, catch this to handle any UAF errors.
"""
if serviceSettings is None:
serviceSettings = pyuaf.client.settings.ServiceSettings()

status = ClientBase.setPublishingMode(self,
clientSubscriptionHandle,
publishingEnabled,
serviceSettings)
pyuaf.util.errors.evaluate(status)


def processRequest(self, request, resultCallback=None, notificationCallbacks=[]):
"""
Expand Down Expand Up @@ -1773,7 +1877,7 @@ def processRequest(self, request, resultCallback=None, notificationCallbacks=[])
- via the exception that was raised in case processRequest() was not successful.
This exception has a "status" attribute (of type :class:`~pyuaf.util.Status`),
and this will provide you a diagnostics object (see :meth:`pyuaf.util.Status.additionalDiagnostics`),
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getNotificationHandles`).
which will finally provide you the handles (see :meth:`pyuaf.util.StatusDiagnostics.getClientHandles`).

.. warning::
Asynchronous requests MUST be invoked on a single session. Meaning:
Expand Down Expand Up @@ -1865,7 +1969,7 @@ def processRequest(self, request, resultCallback=None, notificationCallbacks=[])
if len(notificationCallbacks) == len(result.targets):
for i in xrange(len(notificationCallbacks)):
if type(request) == pyuaf.client.requests.CreateMonitoredDataRequest:
self.__dataNotificationCallbacks__[result.targets[i].notificationHandle] = notificationCallbacks[i]
self.__dataNotificationCallbacks__[result.targets[i].clientHandle] = notificationCallbacks[i]

pyuaf.util.errors.evaluate(status)

Expand Down
4 changes: 4 additions & 0 deletions src/pyuaf/client/client___init__.i
Expand Up @@ -63,6 +63,8 @@
#include "uaf/client/subscriptions/eventnotification.h"
#include "uaf/client/subscriptions/keepalivenotification.h"
#include "uaf/client/subscriptions/subscriptioninformation.h"
#include "uaf/client/subscriptions/monitoreditemstates.h"
#include "uaf/client/subscriptions/monitorediteminformation.h"
#include "uaf/client/sessions/sessionstates.h"
#include "uaf/client/sessions/sessioninformation.h"
%}
Expand Down Expand Up @@ -120,6 +122,7 @@ import threading
// now import the submodules of pyuaf.client
%import "pyuaf/client/client_sessionstates.i"
%import "pyuaf/client/client_subscriptionstates.i"
%import "pyuaf/client/client_monitoreditemstates.i"
%import "pyuaf/client/client_settings.i"
%import "pyuaf/client/client_configs.i"
%import "pyuaf/client/client_requests.i"
Expand All @@ -137,6 +140,7 @@ import threading

// now include all classes in a generic way
UAF_WRAP_CLASS("uaf/client/subscriptions/subscriptioninformation.h" , uafc , SubscriptionInformation , COPY_YES, TOSTRING_YES, COMP_YES, pyuaf.client, SubscriptionInformationVector)
UAF_WRAP_CLASS("uaf/client/subscriptions/monitorediteminformation.h" , uafc , MonitoredItemInformation , COPY_YES, TOSTRING_YES, COMP_YES, pyuaf.client, MonitoredItemInformationVector)
UAF_WRAP_CLASS("uaf/client/subscriptions/monitoreditemnotification.h" , uafc , MonitoredItemNotification , COPY_YES, TOSTRING_YES, COMP_NO, pyuaf.client, VECTOR_NO)
UAF_WRAP_CLASS("uaf/client/subscriptions/datachangenotification.h" , uafc , DataChangeNotification , COPY_YES, TOSTRING_YES, COMP_NO, pyuaf.client, DataChangeNotificationVector)
UAF_WRAP_CLASS("uaf/client/subscriptions/eventnotification.h" , uafc , EventNotification , COPY_YES, TOSTRING_YES, COMP_NO, pyuaf.client, EventNotificationVector)
Expand Down

0 comments on commit 47ad513

Please sign in to comment.