cdp-client 2.0.2__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. cdp-client-2.0.3/PKG-INFO +457 -0
  2. {cdp-client-2.0.2 → cdp-client-2.0.3}/README.rst +6 -2
  3. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/cdp.py +20 -12
  4. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/fake_data.py +5 -3
  5. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_connection.py +4 -2
  6. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_node.py +55 -4
  7. cdp-client-2.0.3/cdp_client.egg-info/PKG-INFO +457 -0
  8. {cdp-client-2.0.2 → cdp-client-2.0.3}/setup.py +1 -1
  9. cdp-client-2.0.2/PKG-INFO +0 -453
  10. cdp-client-2.0.2/cdp_client.egg-info/PKG-INFO +0 -453
  11. {cdp-client-2.0.2 → cdp-client-2.0.3}/LICENSE.txt +0 -0
  12. {cdp-client-2.0.2 → cdp-client-2.0.3}/MANIFEST.in +0 -0
  13. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/__init__.py +0 -0
  14. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/cdp_pb2.py +0 -0
  15. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/__init__.py +0 -0
  16. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_client.py +0 -0
  17. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_main.py +0 -0
  18. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_nodetree.py +0 -0
  19. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client/tests/test_requests.py +0 -0
  20. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client.egg-info/SOURCES.txt +0 -0
  21. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client.egg-info/dependency_links.txt +0 -0
  22. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client.egg-info/requires.txt +0 -0
  23. {cdp-client-2.0.2 → cdp-client-2.0.3}/cdp_client.egg-info/top_level.txt +0 -0
  24. {cdp-client-2.0.2 → cdp-client-2.0.3}/setup.cfg +0 -0
@@ -0,0 +1,457 @@
1
+ Metadata-Version: 1.1
2
+ Name: cdp-client
3
+ Version: 2.0.3
4
+ Summary: Provides an API that allows to interact with CDP applications
5
+ Home-page: https://github.com/CDPTechnologies/PythonCDPClient
6
+ Author: CDP Technologies AS
7
+ Author-email: info@cdptech.com
8
+ License: MIT
9
+ Description: CDP-Client
10
+ ==========
11
+
12
+ 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 see https://cdpstudio.com/
13
+
14
+ The API makes heavy use of promise library for asynchronous operations. For more information see https://pypi.python.org/pypi/promise
15
+
16
+ Installation
17
+ ------------
18
+
19
+ ::
20
+
21
+ $ pip install cdp-client
22
+
23
+ Usage
24
+ -----
25
+
26
+ The example below shows how you subscribe to CDP signal value changes.
27
+
28
+ .. code:: python
29
+
30
+ from cdp_client import cdp
31
+
32
+ def on_value_changed(value, timestamp):
33
+ print(value)
34
+
35
+ def subscribe_to_value_changes(node):
36
+ node.subscribe_to_value_changes(on_value_changed)
37
+
38
+ client = cdp.Client(host='127.0.0.1')
39
+ client.find_node('AppName.ComponentName.SignalName').then(subscribe_to_value_changes)
40
+ client.run_event_loop()
41
+
42
+ API
43
+ ---
44
+
45
+ Before all examples, you need:
46
+
47
+ .. code:: python
48
+
49
+ from cdp_client import cdp
50
+
51
+ Global API
52
+ ~~~~~~~~~~
53
+
54
+ Client(host, port, auto_reconnect, notification_listener, encryption_parameters)
55
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56
+
57
+ - Arguments
58
+
59
+ host - String for hosts ip address
60
+
61
+ port - Optional port number to connect to. If not specified default port 7689 is used.
62
+
63
+ auto_reconnect - Optional argument to enable/disable automatic reconnect when connection is lost. Defaults to True if not specified.
64
+
65
+ notification_listener - NotificationListener object whose methods are called on different connection events (e.g. when server requires credentials)
66
+
67
+ 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 see https://pypi.org/project/websocket_client
68
+
69
+ - Returns
70
+
71
+ The connected client object.
72
+
73
+ - Usage example
74
+
75
+ .. code:: python
76
+
77
+ client = cdp.Client(host='127.0.0.1')
78
+
79
+ - Usage example with password authentication
80
+
81
+ .. code:: python
82
+
83
+ class MyListener(cdp.NotificationListener):
84
+ def credentials_requested(self, request):
85
+ if request.user_auth_result().code() == cdp.AuthResultCode.CREDENTIALS_REQUIRED:
86
+ # Do something to gather username and password variables (either sync or async way) and then call:
87
+ request.accept({'Username': 'test', 'Password': '12345678'});
88
+
89
+ client = cdp.Client(host='127.0.0.1', notification_listener=MyListener())
90
+
91
+ - Usage example with password authentication and encryption in use, without server certificate verification
92
+
93
+ .. code:: python
94
+
95
+ import ssl
96
+
97
+ class MyListener(cdp.NotificationListener):
98
+ def credentials_requested(self, request):
99
+ if request.user_auth_result().code() == cdp.AuthResultCode.CREDENTIALS_REQUIRED:
100
+ # Do something to gather username and password variables (either sync or async way) and then call:
101
+ request.accept({'Username': 'test', 'Password': '12345678'});
102
+
103
+ client = cdp.Client(host='127.0.0.1', notification_listener=MyListener(),
104
+ encryption_parameters={'use_encryption': True, 'cert_reqs': ssl.CERT_NONE})
105
+
106
+
107
+ - Usage example with password authentication and encryption in use, with server certificate verification
108
+
109
+ .. code:: python
110
+
111
+ import ssl
112
+
113
+ class MyListener(cdp.NotificationListener):
114
+ def credentials_requested(self, request):
115
+ if request.user_auth_result().code() == cdp.AuthResultCode.CREDENTIALS_REQUIRED:
116
+ # Do something to gather username and password variables (either sync or async way) and then call:
117
+ request.accept({'Username': 'test', 'Password': '12345678'});
118
+
119
+ client = cdp.Client(host='127.0.0.1', notification_listener=MyListener(),
120
+ encryption_parameters={'use_encryption': True,
121
+ 'cert_reqs': ssl.CERT_REQUIRED,
122
+ 'ca_certs': 'StudioAPI.crt',
123
+ 'check_hostname': False},
124
+
125
+ Instance Methods / Client
126
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
127
+
128
+ client.root_node()
129
+ ^^^^^^^^^^^^^^^^^^
130
+
131
+ Gets the application Node object of the connected application.
132
+
133
+ - Returns
134
+
135
+ Promise containing root Node object when fulfilled.
136
+
137
+ - Usage
138
+
139
+ .. code:: python
140
+
141
+ client.root_node().then(on_success).catch(on_error)
142
+
143
+ client.find_node(path)
144
+ ^^^^^^^^^^^^^^^^^^^^^^
145
+
146
+ 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.**
147
+
148
+ - Arguments
149
+
150
+ path - Dot separated string to target node
151
+
152
+ - Returns
153
+
154
+ Promise containing requested Node object when fulfilled. Otherwise NotFoundError when rejected.
155
+
156
+ - Usage
157
+
158
+ .. code:: python
159
+
160
+ client.find_node('AppName.ComponentName.SignalName').then(on_success).catch(on_error)
161
+
162
+ client.run_event_loop()
163
+ ^^^^^^^^^^^^^^^^^^^^^^^
164
+
165
+ 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.
166
+
167
+ client.disconnect()
168
+ ^^^^^^^^^^^^^^^^^^^
169
+
170
+ Stops the event loop and closes the connection to connected application. This method also releases the blocking run_event_loop call.
171
+
172
+ Instance Methods / Node
173
+ ~~~~~~~~~~~~~~~~~~~~~~~
174
+
175
+ node.name()
176
+ ^^^^^^^^^^^
177
+
178
+ - Returns
179
+
180
+ The name of the Node object. Names in a parent node are all unique.
181
+
182
+ node.path()
183
+ ^^^^^^^^^^^
184
+
185
+ - Returns
186
+
187
+ A dot separated path of the Node object starting with application name.
188
+
189
+ node.parent()
190
+ ^^^^^^^^^^^^^
191
+
192
+ - Returns
193
+
194
+ The parent Node object.
195
+
196
+ node.type()
197
+ ^^^^^^^^^^^
198
+
199
+ - Returns
200
+
201
+ The type of the Node object returned as one of the cdp.NodeType values.
202
+
203
+ node.last_value()
204
+ ^^^^^^^^^^^^^^^^^
205
+
206
+ - Returns
207
+
208
+ The last known value received by the Node object.
209
+
210
+ node.set_value(value, timestamp)
211
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
212
+
213
+ Sets a new value for the Node object. Timestamp will be ignored in current implementation.
214
+
215
+ - Arguments
216
+
217
+ value - New value
218
+
219
+ timestamp - UTC time in nanoseconds since Epoch
220
+
221
+ node.is_read_only()
222
+ ^^^^^^^^^^^^^^^^^^^
223
+
224
+ - Returns
225
+
226
+ False if nodes value cannot be set, otherwise True.
227
+
228
+ node.is_leaf()
229
+ ^^^^^^^^^^^^^^
230
+
231
+ - Returns
232
+
233
+ True if node doesn't have any children, otherwise False.
234
+
235
+ node.child(name)
236
+ ^^^^^^^^^^^^^^^^
237
+
238
+ - Arguments
239
+
240
+ name - Child nodes name to search for
241
+
242
+ - Returns
243
+
244
+ Promise containing requested Node object when fulfilled.
245
+
246
+ - Usage
247
+
248
+ .. code:: python
249
+
250
+ node.child('NodeName').then(on_success).catch(on_error)
251
+
252
+ node.children()
253
+ ^^^^^^^^^^^^^^^
254
+
255
+ - Returns
256
+
257
+ Promise containing all children of this Node object when fulfilled.
258
+
259
+ - Usage
260
+
261
+ .. code:: python
262
+
263
+ node.children().then(on_success).catch(on_error)
264
+
265
+ node.for_each_child(callback)
266
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
267
+
268
+ Loops through all children and calls callback function for each of them
269
+
270
+ - Arguments
271
+
272
+ callback - Function(node)
273
+
274
+ - Returns
275
+
276
+ Promise containing all children of this Node object when fulfilled.
277
+
278
+ - Usage
279
+
280
+ .. code:: python
281
+
282
+ def on_callback(child):
283
+ do something
284
+
285
+ node.for_each_child(on_callback)
286
+
287
+ node.subscribe_to_structure_changes(callback)
288
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
289
+
290
+ Starts listening structure changes and passes the changes to provided callback funtion
291
+
292
+ - Arguments
293
+
294
+ callback - Function(added_nodes, removed_nodes) where added_nodes and removed_nodes is a list
295
+
296
+ - Usage
297
+
298
+ .. code:: python
299
+
300
+ def on_change(added_nodes, removed_nodes):
301
+ do something
302
+
303
+ node.subscribe_to_structure_changes(on_change)
304
+
305
+ node.subscribe_to_value_changes(callback, fs=5, sample_rate=0)
306
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
307
+
308
+ Starts listening value changes and passes the changes to provided callback function
309
+
310
+ - Arguments
311
+
312
+ callback - Function(value, timestamp)
313
+
314
+ fs - Maximum frequency that value updates are expected (controls how many changes are sent in a single packet). Defaults to 5 hz.
315
+
316
+ 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.
317
+
318
+ - Usage
319
+
320
+ .. code:: python
321
+
322
+ def on_change(value, timestamp):
323
+ do something
324
+
325
+ node.subscribe_to_value_changes(on_change)
326
+
327
+
328
+ node.unsubscribe_from_structure_changes(callback)
329
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
330
+
331
+ Stops listening previously subscribed structure changes
332
+
333
+ - Arguments
334
+
335
+ callback - Function(added_nodes, removed_nodes) where added_nodes and removed_nodes is a list
336
+
337
+ - Usage
338
+
339
+ .. code:: python
340
+
341
+ def on_change(added_nodes, removed_nodes):
342
+ do something
343
+
344
+ node.unsubscribe_from_structure_changes(on_change)
345
+
346
+ node.unsubscribe_from_value_changes(callback)
347
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
348
+
349
+ Stops listening previously subscribed value changes
350
+
351
+ - Arguments
352
+
353
+ callback - Function(value, timestamp)
354
+
355
+ - Usage
356
+
357
+ .. code:: python
358
+
359
+ def on_change(value, timestamp):
360
+ do something
361
+
362
+ node.unsubscribe_from_value_changes(on_change)
363
+
364
+ Notification Listener
365
+ ~~~~~~~~~~~~~~~~~~~~~
366
+
367
+ To 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.
368
+ The notification_listener parameter must be a object of type class cdp.NotificationListener.
369
+
370
+ class NotificationListener
371
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
372
+
373
+ .. code:: python
374
+
375
+ class NotificationListener:
376
+ def application_acceptance_requested(self, request=AuthRequest()):
377
+ request.accept()
378
+
379
+ def credentials_requested(self, request=AuthRequest()):
380
+ raise NotImplementedError("NotificationListener credentials_requested() not implemented!")
381
+
382
+ NotificationListener.application_acceptance_requested(self, request=AuthRequest())
383
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
384
+
385
+ Called by Client when new application TLS or plain TCP connection is established.
386
+ Can be used to prompt the user a System Use Notification (a message that can be configured in CDP Studio Security settings).
387
+
388
+ - Arguments
389
+
390
+ request - a object that has method accept() that should be called to accept the connection and a reject() to reject the connection.
391
+
392
+ - Usage
393
+
394
+ .. code:: python
395
+
396
+ class MyListener(cdp.NotificationListener):
397
+ def application_acceptance_requested(self, request):
398
+ if request.system_use_notification():
399
+ # Pop up a System Use Notification message and ask for confirmation to continue,
400
+ # then based on the user answer call either request.accept() or request.reject()
401
+ else:
402
+ request.accept()
403
+
404
+ client = cdp.Client(host='127.0.0.1', port=7689, notification_listener=MyListener())
405
+
406
+ NotificationListener.credentials_requested(self, request=AuthRequest())
407
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
408
+ Called by Client when server is requesting credentials (authentication or idle lockout re-authentication).
409
+
410
+ - Arguments
411
+
412
+ request - 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.
413
+
414
+ - Usage
415
+
416
+ .. code:: python
417
+
418
+ class MyListener(cdp.NotificationListener):
419
+ def credentials_requested(self, request):
420
+ if request.user_auth_result().code() == cdp.AuthResultCode.CREDENTIALS_REQUIRED:
421
+ # Do something to gather username and password variables (either sync or async way) and then call:
422
+ request.accept({'Username': 'test', 'Password': '12345678'});
423
+ if request.user_auth_result().code() == cdp.AuthResultCode.REAUTHENTICATION_REQUIRED:
424
+ # Pop user a message that idle lockout was happened and server requires new authentication to continue:
425
+ request.accept({'Username': 'test', 'Password': '12345678'});
426
+
427
+ client = cdp.Client(host='127.0.0.1', port=7689, notification_listener=MyListener())
428
+
429
+ Tests
430
+ -----
431
+
432
+ To run the test suite execute the following command in package root folder:
433
+
434
+ .. code:: sh
435
+
436
+ $ python setup.py test
437
+
438
+ License
439
+ -------
440
+
441
+ `MIT
442
+ License <https://github.com/CDPTechnologies/PythonCDPClient/blob/master/LICENSE.txt>`__
443
+
444
+ Keywords: cdp cdpstudio studio client cdp-client cdp_client
445
+ Platform: UNKNOWN
446
+ Classifier: Development Status :: 5 - Production/Stable
447
+ Classifier: Environment :: Console
448
+ Classifier: Intended Audience :: Developers
449
+ Classifier: License :: OSI Approved :: MIT License
450
+ Classifier: Operating System :: OS Independent
451
+ Classifier: Topic :: Software Development
452
+ Classifier: Programming Language :: Python :: 2.7
453
+ Classifier: Programming Language :: Python :: 3
454
+ Classifier: Programming Language :: Python :: 3.3
455
+ Classifier: Programming Language :: Python :: 3.4
456
+ Classifier: Programming Language :: Python :: 3.5
457
+ Classifier: Programming Language :: Python :: 3.6
@@ -294,8 +294,8 @@ Starts listening structure changes and passes the changes to provided callback f
294
294
 
295
295
  node.subscribe_to_structure_changes(on_change)
296
296
 
297
- node.subscribe_to_value_changes(callback)
298
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
297
+ node.subscribe_to_value_changes(callback, fs=5, sample_rate=0)
298
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
299
299
 
300
300
  Starts listening value changes and passes the changes to provided callback function
301
301
 
@@ -303,6 +303,10 @@ Starts listening value changes and passes the changes to provided callback funct
303
303
 
304
304
  callback - Function(value, timestamp)
305
305
 
306
+ fs - Maximum frequency that value updates are expected (controls how many changes are sent in a single packet). Defaults to 5 hz.
307
+
308
+ 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.
309
+
306
310
  - Usage
307
311
 
308
312
  .. code:: python
@@ -218,16 +218,18 @@ class Node:
218
218
  def subscribe_to_structure_changes(self, callback):
219
219
  self._structure_subscriptions.append(callback)
220
220
 
221
- def subscribe_to_value_changes(self, callback):
222
- self._value_subscriptions.append(callback)
223
- self._connection.send_value_request(self._id())
221
+ def subscribe_to_value_changes(self, callback, fs=5, sample_rate=0):
222
+ self._value_subscriptions.append((callback, fs, sample_rate))
223
+ self._send_value_request()
224
224
 
225
225
  def unsubscribe_from_structure_changes(self, callback):
226
226
  self._structure_subscriptions.remove(callback)
227
227
 
228
228
  def unsubscribe_from_value_changes(self, callback):
229
- self._value_subscriptions.remove(callback)
230
- if not self._value_subscriptions:
229
+ self._value_subscriptions = [i for i in self._value_subscriptions if i[0] != callback]
230
+ if self._value_subscriptions:
231
+ self._send_value_request()
232
+ else:
231
233
  self._connection.send_value_unrequest(self._id())
232
234
 
233
235
  def _id(self):
@@ -243,11 +245,16 @@ class Node:
243
245
 
244
246
  def fetch_value(node):
245
247
  if self._value_subscriptions:
246
- self._connection.send_value_request(self._id())
248
+ self._send_value_request()
247
249
  return Promise(lambda resolve, reject: resolve(node))
248
250
 
249
251
  return fetch_structure().then(update_structure).then(fetch_value)
250
252
 
253
+ def _send_value_request(self):
254
+ max_fs = max(self._value_subscriptions, key=lambda e: e[1])[1]
255
+ max_sample_rate = max(self._value_subscriptions, key=lambda e: e[2])[2]
256
+ self._connection.send_value_request(self._id(), max_fs, max_sample_rate)
257
+
251
258
  def _update_structure(self, structure):
252
259
  self._structure = structure
253
260
  new_children = list(self._structure.node)
@@ -287,7 +294,7 @@ class Node:
287
294
 
288
295
  def _update_value(self, variant):
289
296
  self._value = self._value_from_variant(self._structure.info.value_type, variant)
290
- for callback in self._value_subscriptions:
297
+ for callback, fs, sample_rate in self._value_subscriptions:
291
298
  callback(self._value, variant.timestamp + self._connection.server_time_difference() * nanoseconds_in_second)
292
299
 
293
300
  @staticmethod
@@ -416,13 +423,13 @@ class Connection:
416
423
  self._compose_and_send_structure_request(node_id)
417
424
  return p
418
425
 
419
- def send_value_request(self, node_id):
426
+ def send_value_request(self, node_id, fs, sample_rate):
420
427
  self._update_time_difference()
421
- self._compose_and_send_value_request(node_id)
428
+ self._compose_and_send_value_request(node_id, fs, sample_rate)
422
429
 
423
430
  def send_value_unrequest(self, node_id):
424
431
  self._update_time_difference()
425
- self._compose_and_send_value_request(node_id, True)
432
+ self._compose_and_send_value_request(node_id, 1, 0, True)
426
433
 
427
434
  def send_value(self, variant):
428
435
  self._update_time_difference()
@@ -677,12 +684,13 @@ class Connection:
677
684
  data.structure_request.append(node_id)
678
685
  self._ws.send(data.SerializeToString())
679
686
 
680
- def _compose_and_send_value_request(self, node_id, stop=False):
687
+ def _compose_and_send_value_request(self, node_id, fs, sample_rate, stop=False):
681
688
  data = proto.Container()
682
689
  data.message_type = proto.Container.eGetterRequest
683
690
  value = proto.ValueRequest()
684
691
  value.node_id = node_id
685
- value.fs = 5
692
+ value.fs = fs
693
+ value.sample_rate = sample_rate
686
694
  if stop:
687
695
  value.stop = stop
688
696
  data.getter_request.extend([value])
@@ -101,12 +101,13 @@ def create_error_response():
101
101
  return response
102
102
 
103
103
 
104
- def create_value_request(node_id):
104
+ def create_value_request(node_id, fs, sample_rate):
105
105
  request = proto.Container()
106
106
  request.message_type = proto.Container.eGetterRequest
107
107
  value = proto.ValueRequest()
108
108
  value.node_id = node_id
109
- value.fs = 5
109
+ value.fs = fs
110
+ value.sample_rate = sample_rate
110
111
  request.getter_request.extend([value])
111
112
  return request
112
113
 
@@ -116,7 +117,8 @@ def create_value_unrequest(node_id):
116
117
  request.message_type = proto.Container.eGetterRequest
117
118
  value = proto.ValueRequest()
118
119
  value.node_id = node_id
119
- value.fs = 5
120
+ value.fs = 1
121
+ value.sample_rate = 0
120
122
  value.stop = True
121
123
  request.getter_request.extend([value])
122
124
  return request
@@ -67,8 +67,10 @@ class ConnectionTester(unittest.TestCase):
67
67
  @mock.patch.object(cdp.websocket.WebSocketApp, 'send')
68
68
  def test_sending_value_request(self, mock_send):
69
69
  node_id = 1
70
- self._connection.send_value_request(node_id)
71
- mock_send.assert_any_call(fake_data.create_value_request(node_id).SerializeToString())
70
+ fs = 1
71
+ sample_rate = 1
72
+ self._connection.send_value_request(node_id, fs, sample_rate)
73
+ mock_send.assert_any_call(fake_data.create_value_request(node_id, fs, sample_rate).SerializeToString())
72
74
 
73
75
  @mock.patch.object(cdp.websocket.WebSocketApp, 'send')
74
76
  def test_sending_value_unrequest(self, mock_send):
@@ -149,12 +149,11 @@ class NodeTester(unittest.TestCase):
149
149
  def on_change(value, timestamp):
150
150
  actual_values.append(value)
151
151
  timestamps.append(timestamp)
152
-
153
152
  actual_values = []
154
153
  timestamps = []
155
154
  node = cdp.Node(None, self._connection, data.value1_node)
156
- node.subscribe_to_value_changes(on_change)
157
- mock_send_value_request.assert_called_once_with(node._id())
155
+ node.subscribe_to_value_changes(on_change, 10, 5)
156
+ mock_send_value_request.assert_called_once_with(node._id(), 10, 5)
158
157
 
159
158
  expected_values = [data.value1, data.value2]
160
159
  for value in expected_values:
@@ -165,6 +164,42 @@ class NodeTester(unittest.TestCase):
165
164
  for index, value in enumerate(expected_values):
166
165
  self.assertEquals(actual_values[index], value.d_value)
167
166
  self.assertEquals(timestamps[index], value.timestamp)
167
+
168
+ @mock.patch.object(cdp.Connection, 'send_value_request')
169
+ def test_value_subscription_max_fs(self, mock_send_value_request):
170
+ def on_change1():
171
+ pass
172
+ def on_change2():
173
+ pass
174
+ def on_change3():
175
+ pass
176
+ node = cdp.Node(None, self._connection, data.value1_node)
177
+ node.subscribe_to_value_changes(on_change1, 3, 1)
178
+ mock_send_value_request.assert_called_once_with(node._id(), 3, 1)
179
+ mock_send_value_request.reset_mock()
180
+ node.subscribe_to_value_changes(on_change2, 10, 6)
181
+ mock_send_value_request.assert_called_once_with(node._id(), 10, 6)
182
+ mock_send_value_request.reset_mock()
183
+ node.subscribe_to_value_changes(on_change3, 1, 3)
184
+ mock_send_value_request.assert_called_once_with(node._id(), 10, 6)
185
+
186
+ @mock.patch.object(cdp.Connection, 'send_structure_request')
187
+ @mock.patch.object(cdp.Connection, 'send_value_request')
188
+ def test_value_subscription_max_fs_on_update(self, mock_send_value_request, mock_send_structure_request):
189
+ def on_change1():
190
+ pass
191
+ def on_change2():
192
+ pass
193
+ def on_change3():
194
+ pass
195
+ node = cdp.Node(None, self._connection, data.value1_node)
196
+ node.subscribe_to_value_changes(on_change1, 3, 1)
197
+ node.subscribe_to_value_changes(on_change2, 10, 6)
198
+ node.subscribe_to_value_changes(on_change3, 1, 6)
199
+ mock_send_value_request.reset_mock()
200
+ mock_send_structure_request.return_value = Promise(lambda resolve, reject: resolve(self._root_node))
201
+ node._update()
202
+ mock_send_value_request.assert_called_once_with(node._id(), 10, 6)
168
203
 
169
204
  @mock.patch.object(cdp.Connection, 'send_value_request')
170
205
  @mock.patch.object(cdp.Connection, 'send_value_unrequest')
@@ -175,8 +210,24 @@ class NodeTester(unittest.TestCase):
175
210
  values = []
176
211
  node = cdp.Node(None, self._connection, data.value1_node)
177
212
  node.subscribe_to_value_changes(on_change)
178
- mock_send_value_request.assert_called_once_with(node._id())
213
+ mock_send_value_request.assert_called_once_with(node._id(), 5, 0)
179
214
  node.unsubscribe_from_value_changes(on_change)
180
215
  mock_send_value_unrequest.assert_called_once_with(node._id())
181
216
  node._update_value(data.value1)
182
217
  self.assertEquals(len(values), 0)
218
+
219
+ @mock.patch.object(cdp.Connection, 'send_value_request')
220
+ def test_value_unsubscription_max_fs(self, mock_send_value_request):
221
+ def on_change1():
222
+ pass
223
+ def on_change2():
224
+ pass
225
+ def on_change3():
226
+ pass
227
+ node = cdp.Node(None, self._connection, data.value1_node)
228
+ node.subscribe_to_value_changes(on_change1, 5, 1)
229
+ node.subscribe_to_value_changes(on_change2, 1, 3)
230
+ node.subscribe_to_value_changes(on_change3, 10, 6)
231
+ mock_send_value_request.reset_mock()
232
+ node.unsubscribe_from_value_changes(on_change3)
233
+ mock_send_value_request.assert_called_once_with(node._id(), 5, 3)