Package: cdp-client
A simple python interface for the CDP Studio development platform that allows Python scripts to interact with CDP Applications - retrieve CDP Application structures and read-write object values. For more information about CDP Studio seehttps://cdpstudio.com/The API makes heavy use of promise library for asynchronous operations. For more information seehttps://pypi.python.org/pypi/promiseInstallation$ pip install cdp-clientUsageThe example below shows how you subscribe to CDP signal value changes.fromcdp_clientimportcdpdefon_value_changed(value,timestamp):print(value)defsubscribe_to_value_changes(node):node.subscribe_to_value_changes(on_value_changed)client=cdp.Client(host='127.0.0.1')client.find_node('AppName.ComponentName.SignalName').then(subscribe_to_value_changes)client.run_event_loop()APIBefore all examples, you need:fromcdp_clientimportcdpGlobal APIClient(host, port, auto_reconnect, notification_listener, encryption_parameters)Argumentshost - String for hosts ip addressport - Optional port number to connect to. If not specified default port 7689 is used.auto_reconnect - Optional argument to enable/disable automatic reconnect when connection is lost. Defaults to True if not specified.notification_listener - NotificationListener object whose methods are called on different connection events (e.g. when server requires credentials)encryption_parameters - Optional argument to set encryption and its parameters, TLS certificates verification etc. Parameter is compatible with python websocket client ‘sslopt’ parameter. For more information seehttps://pypi.org/project/websocket_clientReturnsThe connected client object.Usage exampleclient=cdp.Client(host='127.0.0.1')Usage example with password authenticationclassMyListener(cdp.NotificationListener):defcredentials_requested(self,request):ifrequest.user_auth_result().code()==cdp.AuthResultCode.CREDENTIALS_REQUIRED:# Do something to gather username and password variables (either sync or async way) and then call:request.accept({'Username':'test','Password':'12345678'});client=cdp.Client(host='127.0.0.1',notification_listener=MyListener())Usage example with password authentication and encryption in use, without server certificate verificationimportsslclassMyListener(cdp.NotificationListener):defcredentials_requested(self,request):ifrequest.user_auth_result().code()==cdp.AuthResultCode.CREDENTIALS_REQUIRED:# Do something to gather username and password variables (either sync or async way) and then call:request.accept({'Username':'test','Password':'12345678'});client=cdp.Client(host='127.0.0.1',notification_listener=MyListener(),encryption_parameters={'use_encryption':True,'cert_reqs':ssl.CERT_NONE})Usage example with password authentication and encryption in use, with server certificate verificationimportsslclassMyListener(cdp.NotificationListener):defcredentials_requested(self,request):ifrequest.user_auth_result().code()==cdp.AuthResultCode.CREDENTIALS_REQUIRED:# Do something to gather username and password variables (either sync or async way) and then call:request.accept({'Username':'test','Password':'12345678'});client=cdp.Client(host='127.0.0.1',notification_listener=MyListener(),encryption_parameters={'use_encryption':True,'cert_reqs':ssl.CERT_REQUIRED,'ca_certs':'StudioAPI.crt','check_hostname':False},Instance Methods / Clientclient.root_node()Gets the application Node object of the connected application.ReturnsPromise containing root Node object when fulfilled.Usageclient.root_node().then(on_success).catch(on_error)client.find_node(path)Searches for the node specified by full dot separated path.The requested node must reside in the application client was connected to. Root node is not considered part of the path.Argumentspath - Dot separated string to target nodeReturnsPromise containing requested Node object when fulfilled. Otherwise NotFoundError when rejected.Usageclient.find_node('AppName.ComponentName.SignalName').then(on_success).catch(on_error)client.run_event_loop()Runs the event loop that serves network communication layer for incoming/outgoing data.This is a blocking call that must be run for any communication to happen.The method can be cancelled by calling disconnect.client.disconnect()Stops the event loop and closes the connection to connected application. This method also releases the blocking run_event_loop call.Instance Methods / Nodenode.name()ReturnsThe name of the Node object. Names in a parent node are all unique.node.path()ReturnsA dot separated path of the Node object starting with application name.node.parent()ReturnsThe parent Node object.node.type()ReturnsThe type of the Node object returned as one of the cdp.NodeType values.node.class_name()ReturnsCDP class name as a string.node.last_value()ReturnsThe last known value received by the Node object.node.set_value(value, timestamp)Sets a new value for the Node object. Timestamp will be ignored in current implementation.Argumentsvalue - New valuetimestamp - UTC time in nanoseconds since Epochnode.is_read_only()ReturnsFalse if nodes value cannot be set, otherwise True.node.is_leaf()ReturnsTrue if node doesn’t have any children, otherwise False.node.child(name)Argumentsname - Child nodes name to search forReturnsPromise containing requested Node object when fulfilled.Usagenode.child('NodeName').then(on_success).catch(on_error)node.children()ReturnsPromise containing all children of this Node object when fulfilled.Usagenode.children().then(on_success).catch(on_error)node.for_each_child(callback)Loops through all children and calls callback function for each of themArgumentscallback - Function(node)ReturnsPromise containing all children of this Node object when fulfilled.Usagedefon_callback(child):dosomethingnode.for_each_child(on_callback)node.subscribe_to_structure_changes(callback)Starts listening structure changes and passes the changes to provided callback funtionArgumentscallback - Function(added_nodes, removed_nodes) where added_nodes and removed_nodes is a listUsagedefon_change(added_nodes,removed_nodes):dosomethingnode.subscribe_to_structure_changes(on_change)node.subscribe_to_value_changes(callback, fs=5, sample_rate=0)Starts listening value changes and passes the changes to provided callback functionArgumentscallback - Function(value, timestamp)fs - Maximum frequency that value updates are expected (controls how many changes are sent in a single packet). Defaults to 5 hz.sample_rate - Maximum amount of value updates sent per second (controls the amount of data transferred). Zero means all samples must be provided. Defaults to 0.Usagedefon_change(value,timestamp):dosomethingnode.subscribe_to_value_changes(on_change)node.unsubscribe_from_structure_changes(callback)Stops listening previously subscribed structure changesArgumentscallback - Function(added_nodes, removed_nodes) where added_nodes and removed_nodes is a listUsagedefon_change(added_nodes,removed_nodes):dosomethingnode.unsubscribe_from_structure_changes(on_change)node.unsubscribe_from_value_changes(callback)Stops listening previously subscribed value changesArgumentscallback - Function(value, timestamp)Usagedefon_change(value,timestamp):dosomethingnode.unsubscribe_from_value_changes(on_change)Notification ListenerTo handle different connection events (like prompt user to accept a system use notification message or request user to enter credentials for authentication or idle lockout re-authentication) a notification_listener parameter must be provided to the Client.
The notification_listener parameter must be a object of type class cdp.NotificationListener.class NotificationListenerclassNotificationListener:defapplication_acceptance_requested(self,request=AuthRequest()):request.accept()defcredentials_requested(self,request=AuthRequest()):raiseNotImplementedError("NotificationListener credentials_requested() not implemented!")NotificationListener.application_acceptance_requested(self, request=AuthRequest())Called by Client when new application TLS or plain TCP connection is established.
Can be used to prompt the user a System Use Notification (a message that can be configured in CDP Studio Security settings).Argumentsrequest - a object that has method accept() that should be called to accept the connection and a reject() to reject the connection.UsageclassMyListener(cdp.NotificationListener):defapplication_acceptance_requested(self,request):ifrequest.system_use_notification():# Pop up a System Use Notification message and ask for confirmation to continue,# then based on the user answer call either request.accept() or request.reject()else:request.accept()client=cdp.Client(host='127.0.0.1',port=7689,notification_listener=MyListener())NotificationListener.credentials_requested(self, request=AuthRequest())Called by Client when server is requesting credentials (authentication or idle lockout re-authentication).Argumentsrequest - a object that has method accept(data=dict()) that should be called (with credentials) for authentication try, and also a method reject() to reject the connection.UsageclassMyListener(cdp.NotificationListener):defcredentials_requested(self,request):ifrequest.user_auth_result().code()==cdp.AuthResultCode.CREDENTIALS_REQUIRED:# Do something to gather username and password variables (either sync or async way) and then call:request.accept({'Username':'test','Password':'12345678'});ifrequest.user_auth_result().code()==cdp.AuthResultCode.REAUTHENTICATION_REQUIRED:# Pop user a message that idle lockout was happened and server requires new authentication to continue:request.accept({'Username':'test','Password':'12345678'});client=cdp.Client(host='127.0.0.1',port=7689,notification_listener=MyListener())TestsTo run the test suite execute the following command in package root folder:$pythonsetup.pytestLicenseMIT
License