-
Notifications
You must be signed in to change notification settings - Fork 14
/
rticonnextdds-connector.js
2088 lines (1995 loc) · 72.7 KB
/
rticonnextdds-connector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/******************************************************************************
* (c) 2005-2019 Copyright, Real-Time Innovations. All rights reserved. *
* No duplications, whole or partial, manual or electronic, may be made *
* without express written permission. Any such copies, or revisions thereof, *
* must display this notice unaltered. *
* This code contains trade secrets of Real-Time Innovations, Inc. *
******************************************************************************/
const os = require('os')
const path = require('path')
const koffi = require('koffi');
const EventEmitter = require('events').EventEmitter
const _ConnectorOptions = koffi.struct('RTI_Connector_Options', {
enable_on_data_event: 'int',
one_based_sequence_indexing: 'int'
});
const RTI_HANDLE = koffi.pointer('RTI_HANDLE', koffi.opaque());
// We ignore the loading of the libraries in code coverage since it is
// not easily testable
/* istanbul ignore next */
class _ConnectorBinding {
constructor () {
let libDir = ''
let libName = ''
let additionalLib = null
let isWindows = false
// Obtain the name of the library that contains the Connector native libraries
if (os.arch() === 'arm64') {
if (os.platform() === 'linux') {
libDir = 'linux-arm64'
libName = 'librtiddsconnector.so'
} else {
throw new Error('This platform (' + os.platform() + ' ' + os.arch() + ') is not supported')
}
} else if (os.arch() === 'arm') {
if (os.platform() === 'linux') {
libDir = 'linux-arm'
libName = 'librtiddsconnector.so'
} else {
throw new Error('This platform (' + os.platform() + ' ' + os.arch() + ') is not supported')
}
} else {
// Note that we are intentionally not checking if os.arch() is x64.
// This allows somebody with access to 32-bit libraries to replace them
// in the corresponding x64 directory and we will try to load them.
// This behaviour is not officially supported.
switch (os.platform()) {
case 'darwin':
libDir = 'osx-x64'
libName = 'librtiddsconnector.dylib'
break
case 'linux':
libDir = 'linux-x64'
libName = 'librtiddsconnector.so'
break
// Windows returns win32 even on 64-bit platforms
case 'win32':
libDir = 'win-x64'
libName = 'rtiddsconnector.dll'
isWindows = true
break
default:
throw new Error(os.platform() + ' not yet supported')
}
}
// Connector is not supported on a (non ARM) 32-bit platform
// We continue, incase the user has manually replaced the libraries within
// the directory which we are going to load.
if (os.arch() === 'ia32') {
console.log('Warning: 32-bit ' + os.platform() + ' is not supported')
}
this.library = path.join(__dirname, '/rticonnextdds-connector/lib/', libDir, '/', libName)
this.api = koffi.load(this.library);
// Obtain FFI'd methods for all of the APIs which we require from the binding,
// specifying the argument types and return types. If any of the types are
// not builtin Node types then we have to use the ref module to represent them.
const RTI_Connector_free_string = this.api.func('RTI_Connector_free_string', 'void', ['char *']);
const AllocatedString = koffi.disposable('AllocatedString', 'string', RTI_Connector_free_string);
this.RTI_Connector_new = this.api.func('RTI_Connector_new', RTI_HANDLE, ['string', 'string', koffi.pointer(_ConnectorOptions)]);
this.RTI_Connector_delete = this.api.func('RTI_Connector_delete', 'void', ['RTI_HANDLE']);
this.RTI_Connector_get_datawriter = this.api.func('RTI_Connector_get_datawriter', 'RTI_HANDLE', ['RTI_HANDLE', 'string']);
this.RTI_Connector_get_datareader = this.api.func('RTI_Connector_get_datareader', 'RTI_HANDLE', ['RTI_HANDLE', 'string']);
this.RTI_Connector_get_native_sample = this.api.func('RTI_Connector_get_native_sample', 'RTI_HANDLE', ['RTI_HANDLE', 'string', 'int']);
this.RTI_Connector_set_number_into_samples = this.api.func('RTI_Connector_set_number_into_samples', 'int', ['RTI_HANDLE', 'string', 'string', 'double']);
this.RTI_Connector_set_boolean_into_samples = this.api.func('RTI_Connector_set_boolean_into_samples', 'int', ['RTI_HANDLE', 'string', 'string', 'bool']);
this.RTI_Connector_set_string_into_samples = this.api.func('RTI_Connector_set_string_into_samples', 'int', ['RTI_HANDLE', 'string', 'string', 'string']);
this.RTI_Connector_clear_member = this.api.func('RTI_Connector_clear_member', 'int', ['RTI_HANDLE', 'string', 'string']);
this.RTI_Connector_write = this.api.func('RTI_Connector_write', 'int', ['RTI_HANDLE', 'string', 'string']);
this.RTI_Connector_wait_for_acknowledgments = this.api.func('RTI_Connector_wait_for_acknowledgments', 'int', ['RTI_HANDLE', 'int']);
this.RTI_Connector_read = this.api.func('RTI_Connector_read', 'int', ['RTI_HANDLE', 'string']);
this.RTI_Connector_take = this.api.func('RTI_Connector_take', 'int', ['RTI_HANDLE', 'string']);
this.RTI_Connector_wait_for_data = this.api.func('RTI_Connector_wait_for_data', 'int', ['RTI_HANDLE', 'int']);
this.RTI_Connector_wait_for_data_on_reader = this.api.func('RTI_Connector_wait_for_data_on_reader', 'int', ['RTI_HANDLE', 'int']);
this.RTI_Connector_wait_for_matched_publication = this.api.func('RTI_Connector_wait_for_matched_publication', 'int', ['RTI_HANDLE', 'int', koffi.out(koffi.pointer('int'))]);
this.RTI_Connector_wait_for_matched_subscription = this.api.func('RTI_Connector_wait_for_matched_subscription', 'int', ['RTI_HANDLE', 'int', koffi.out(koffi.pointer('int'))]);
this.RTI_Connector_get_matched_subscriptions = this.api.func('RTI_Connector_get_matched_subscriptions', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer(AllocatedString))]);
this.RTI_Connector_get_matched_publications = this.api.func('RTI_Connector_get_matched_publications', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer(AllocatedString))]);
this.RTI_Connector_clear = this.api.func('RTI_Connector_clear', 'int', ['RTI_HANDLE', 'string']);
this.RTI_Connector_get_boolean_from_infos = this.api.func('RTI_Connector_get_boolean_from_infos', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer('bool')), 'string', 'int', 'string']);
this.RTI_Connector_get_json_from_infos = this.api.func('RTI_Connector_get_json_from_infos', 'int', ['RTI_HANDLE', 'string', 'int', 'string', koffi.out(koffi.pointer(AllocatedString))]);
this.RTI_Connector_get_sample_count = this.api.func('RTI_Connector_get_sample_count', 'int', ['RTI_HANDLE', 'string', koffi.out(koffi.pointer('double'))]);
this.RTI_Connector_get_number_from_sample = this.api.func('RTI_Connector_get_number_from_sample', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer('double')), 'string', 'int', 'string']);
this.RTI_Connector_get_boolean_from_sample = this.api.func('RTI_Connector_get_boolean_from_sample', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer('int')), 'string', 'int', 'string']);
this.RTI_Connector_get_string_from_sample = this.api.func('RTI_Connector_get_string_from_sample', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer(AllocatedString)), 'string', 'int', 'string']);
this.RTI_Connector_get_any_from_sample = this.api.func('RTI_Connector_get_any_from_sample', 'int', ['RTI_HANDLE',koffi.out(koffi.pointer('double')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer(AllocatedString)), koffi.out(koffi.pointer('int')), 'string', 'int', 'string']);
this.RTI_Connector_get_any_from_info = this.api.func('RTI_Connector_get_any_from_info', 'int', ['RTI_HANDLE', koffi.out(koffi.pointer('double')), koffi.out(koffi.pointer('int')), koffi.out(koffi.pointer(AllocatedString)), koffi.out(koffi.pointer('int')), 'string', 'int', 'string']);
this.RTI_Connector_get_json_sample = this.api.func('RTI_Connector_get_json_sample', 'int', ['RTI_HANDLE', 'string', 'int', koffi.out(koffi.pointer(AllocatedString))]);
this.RTI_Connector_get_json_member = this.api.func('RTI_Connector_get_json_member', 'int', ['RTI_HANDLE', 'string', 'int', 'string', koffi.out(koffi.pointer(AllocatedString))]);
this.RTI_Connector_set_json_instance = this.api.func('RTI_Connector_set_json_instance', 'int', ['RTI_HANDLE', 'string', 'string']);
this.RTI_Connector_get_last_error_message = this.api.func('RTI_Connector_get_last_error_message', AllocatedString, []);
this.RTI_Connector_get_native_instance = this.api.func('RTI_Connector_get_native_instance', 'int', ['RTI_HANDLE', 'string', koffi.out(koffi.pointer(RTI_HANDLE))]);
this.RTIDDSConnector_getJSONInstance = this.api.func('RTIDDSConnector_getJSONInstance', AllocatedString, ['RTI_HANDLE', 'string']);
// This API is only used in the unit tests
this.RTI_Connector_create_test_scenario = this.api.func('RTI_Connector_create_test_scenario', 'int', ['RTI_HANDLE', 'int', 'RTI_HANDLE']);
this.RTI_Connector_get_build_versions = this.api.func('RTI_Connector_get_build_versions', 'int', [koffi.out(koffi.pointer('string')), koffi.out(koffi.pointer('string'))]);
}
}
// Create an instance of the connectorBinding class, allowing us to call the FFI'd methods
const connectorBinding = new _ConnectorBinding()
/**
* Obtains the last error message from the *RTI Connext DDS* Core
* @private
*/
function _getLastDdsErrorMessage () {
return connectorBinding.RTI_Connector_get_last_error_message()
}
/**
* Node.js representation of DDS_ReturnCode_t enum.
*
* We only expose the ones we currently care about.
* @private
*/
const _ReturnCodes = {
ok: 0,
timeout: 10,
noData: 11
}
// Make _ReturnCodes immutable
Object.freeze(_ReturnCodes)
/**
* Node.js representation of RTI_Connector_AnyValueKind
* @private
*/
const _AnyValueKind = {
connector_none: 0,
connector_number: 1,
connector_boolean: 2,
connector_string: 3
}
// Make this immutable
Object.freeze(_AnyValueKind)
/**
* A timeout error thrown by operations that can block
*/
class TimeoutError extends Error {
/**
* This error is thrown when blocking errors timeout.
* @private
*/
constructor (message, extra) {
super()
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
this.extra = extra
}
}
/**
* An error originating from the *RTI Connext DDS* Core
*/
class DDSError extends Error {
/**
* This error is thrown when an error is encountered from within one of the
* APIs within the *RTI Connext DDS* Core.
* @private
*/
constructor (message, extra) {
super()
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
this.extra = extra
}
}
/**
* Checks the value returned by the functions in the core for success and
* throws the appropriate error on failure.
*
* We do not handle DDS_RETCODE_NO_DATA here, since in some operations (those
* related with optional members), we need to handle it separately.
*
* @param {number} retcode - The retcode to check
* @private
*/
function _checkRetcode (retcode) {
if (retcode !== _ReturnCodes.ok && retcode !== _ReturnCodes.noData) {
if (retcode === _ReturnCodes.timeout) {
throw new TimeoutError('Timeout error')
} else {
throw new DDSError('DDS Error: ' + _getLastDdsErrorMessage())
}
}
}
/**
* Checks if a value is a string type
* @param {string} value - The value to check the type of
* @private
*/
function _isString (value) {
return typeof value === 'string' || value instanceof String
}
/**
* Checks if a value is a valid index
* @param {number} value - The value to check the type of
* @private
*/
function _isValidIndex (value) {
return _isNumber(value) && Number.isInteger(value) && value >= 0
}
/**
* Checks if a value is a valid number (not NaN or Infinity)
* @param {number} value - The value to check the type of
*/
function _isNumber (value) {
return typeof value === 'number' && isFinite(value)
}
/**
* Function used to get any value from either the samples or infos (depending
* on the supplied getter). The type of the fieldName need not be specified.
*
* @param {function} getter - The function to use to get the value
* @param {Connector} connector - The Connector
* @param {string} inputName - The name of the input to access
* @param {number} index - The index in the samples / infos array
* @param {string} fieldName - The name of the fields to obtain
*
* @private
*/
function _getAnyValue (getter, connector, inputName, index, fieldName) {
let numberVal = [null]
let boolVal = [null]
let stringVal = [null]
let selection = [null]
const retcode = getter(
connector,
numberVal,
boolVal,
stringVal,
selection,
inputName,
index + 1,
fieldName)
_checkRetcode(retcode)
if (retcode === _ReturnCodes.noData) {
return null
}
selection = selection[0]
if (selection === _AnyValueKind.connector_number) {
return numberVal[0]
} else if (selection === _AnyValueKind.connector_boolean) {
return !!boolVal[0]
} else if (selection === _AnyValueKind.connector_string) {
const nodeStr = stringVal[0]
// If this is NOT a numeric string, try to convert the returned string to a
// JSON object. We can now return one of two things:
// - An actual string (if the JSON.parse call fails)
// - A JSON object (if the JSON.parse call succeeds)
if (isNaN(nodeStr)) {
try {
return JSON.parse(nodeStr)
} catch (err) {
return nodeStr
}
} else {
return nodeStr
}
} else {
// This shouldn't happen
throw new Error('Unexpected type returned by ' + getter.name)
}
}
/**
* Provides access to the meta-data contained in samples read by an input.
*
* Note: The Infos class is deprecated and should not be used directly.
* Instead, use :meth:`SampleIterator.info`.
*
* @private
*/
class Infos {
constructor (input) {
this.input = input
}
/**
* Obtains the number of samples in the related :class:`Input`'s queue.
* @private
*/
getLength () {
let length = [null];
const retcode = connectorBinding.RTI_Connector_get_sample_count(
this.input.connector.native,
this.input.name,
length)
_checkRetcode(retcode)
return length[0]
}
/**
* Checks if the sample at the given index contains valid data.
*
* @param {number} index - The index of the sample in the :class:`Input`'s
* queue to check for valid data
* @returns{boolean} True if the sample contains valid data
* @private
*/
isValid (index) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else {
// Increment index since Lua arrays are 1-indexed
index += 1
let value = [null]
const retcode = connectorBinding.RTI_Connector_get_boolean_from_infos(
this.input.connector.native,
value,
this.input.name,
index,
'valid_data')
_checkRetcode(retcode)
if (retcode === _ReturnCodes.noData) {
return null
}
return value[0]
}
}
}
// Public API
/**
* Iterates and provides access to a data sample.
*/
class SampleIterator {
/**
* A SampleIterator provides access to the data receieved by an :class:`Input`.
*
* The :attr:`Input.samples` attribute implements a :class:`SampleIterator`,
* meaning it can be iterated over. An individual sample can be accessed
* using :meth:`Input.samples.get`.
*
* See :class:`ValidSampleIterator`.
*
* This class provides both an iterator and iterable, and is used internally
* by the :class:`Samples` class. The following options to iterate over the
* samples exist::
*
* // option 1 - The iterable can be used in for...of loops
* for (const sample of input.samples)
* // option 2 - Returns an individual sample at the given index
* const individualSample = input.samples.get(0)
* // option 3 - Returns a generator which must be incremented by the application
* const iterator = input.samples.iterator()
*
* @property {boolean} validData - Whether or not the current sample
* contains valid data.
* @property {SampleInfo} infos - The meta-data associated with the
* current sample.
* @property {RTI_HANDLE} native - A native handle that allows accessing
* additional *Connext DDS* APIs in C.
*/
constructor (input, index) {
this.input = input
if (index === undefined) {
index = -1
}
this.index = index
this.length = input.samples.getLength()
}
/**
* Whether or not this sample contains valid data.
*
* If ``false``, the methods to obtain values of the samples
* (e.g., :meth:`SampleIterator.getNumber`,
* :meth:`SampleIterator.getBoolean`, :meth:`SampleIterator.getJson`,
* :meth:`SampleIterator.getString`) should not be called. To avoid
* this restraint, use a :class:`ValidSampleIterator`.
* @type {boolean}
*/
get validData () {
return !!this.input.infos.isValid(this.index)
}
/**
* Provides access to this sample's meta-data.
*
* The ``info`` property expects one of the :class:`SampleInfo` field names::
*
* const value = sample.info.get('field')
*
* The supported field names are:
*
* * ``'source_timestamp'`` returns an integer representing nanoseconds
* * ``'reception_timestamp'`` returns an integer representing nanoseconds
* * ``'sample_identity'`` or ``'identity'`` returns a JSON object
* (see :meth:`Output.write`)
* * ``'related_sample_identity'`` returns a JSON object
* (see :meth:`Output.write`)
* * ``'valid_data'`` returns a boolean (equivalent to
* :attr:`SampleIterator.validData`)
* * ``'view_state'``, returns a string (either "NEW" or "NOT_NEW")
* * ``'instance_state'``, returns a string (one of "ALIVE", "NOT_ALIVE_DISPOSED" or "NOT_ALIVE_NO_WRITERS")
* * ``'sample_state'``, returns a string (either "READ" or "NOT_READ")
*
* These fields are documented in `The SampleInfo Structure
* <https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/index.htm#users_manual/The_SampleInfo_Structure.htm>`__
* section in the *RTI Connext DDS Core Libraries User's Manual*.
*
* See :class:`SampleInfo`.
*/
get info () {
return new SampleInfo(this.input, this.index)
}
/**
* Returns a JSON object with the values of all the fields of this sample.
*
* See :ref:`Accessing the data samples`.
*
* @param {string} [memberName] - The name of the complex member or field
* to obtain.
* @returns {JSON} The obtained JSON object.
*/
getJson (memberName) {
return this.input.samples.getJson(this.index, memberName)
}
/**
* Gets the value of a numeric field in this sample.
*
* .. note::
* This operation should not be used with values with an aboslute value
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
* for more information.
*
* @param {string} fieldName - The name of the field.
* @returns {number} The numeric value of the field.
*/
getNumber (fieldName) {
return this.input.samples.getNumber(this.index, fieldName)
}
/**
* Gets the value of a boolean field in this sample.
*
* @param {string} fieldName - The name of the field.
* @returns {boolean} The boolean value of the field.
*/
getBoolean (fieldName) {
const ret = this.input.samples.getBoolean(this.index, fieldName)
// Performing !! on null produces false, but we want to maintain null
if (ret === null) {
return ret
} else {
// For legacy reasons, Samples.getBoolean returns a number, convert that to
// a bool here
return !!ret
}
}
/**
* Gets the value of a string field in this sample.
*
* @param {string} fieldName - The name of the field.
* @returns {string} The string value of the field.
*/
getString (fieldName) {
return this.input.samples.getString(this.index, fieldName)
}
/**
* Gets the value of a field within this sample.
*
* This API can be used to obtain strings, numbers, booleans and the JSON
* representation of complex members.
* @param {string} fieldName - The name of the field.
* @returns {number|string|boolean|JSON} The value of the field.
*/
get (fieldName) {
return this.input.samples.getValue(this.index, fieldName)
}
/**
* The native RTI_HANDLE to the DynamicData sample.
*
* @type {RTI_HANDLE}
* @private
*/
get native () {
return this.input.samples.getNative(this.index)
}
/**
* The iterator generator (used by the iterable).
*
* This generator is used internally by the iterable.
* A public generator is provided :meth:`Samples.iterator`.
*
* @private
*/
* iterator () {
while ((this.index + 1) < this.length) {
this.index += 1
yield this
}
}
/**
* Implementation of iterable logic. This allows for the following syntax::
* for (const sample of input.samples) {
* json = sample.getJson()
* }
*/
[Symbol.iterator] () {
return this.iterator()
}
}
/**
* Iterates and provides access to data samples with valid data.
*
* This iterator provides the same methods as :class:`SampleIterator`.
* It can be obtained using :attr:`Input.samples.validDataIter`.
* @extends SampleIterator
*
* Using this class, it is possible to iterate through all valid data samples::
* for (let sample of input.samples.validDataIter) {
* console.log(JSON.stringify(sample.getJson()))
* }
*/
class ValidSampleIterator extends SampleIterator {
/**
* The iterator generator (used by the iterable).
*
* Using this method, it is possible to create your own iterable::
*
* const iterator = input.samples.validDataIter.iterator()
* const singleSample = iterator.next().value
*
* @generator
* @yields {ValidSampleIterator} The next sample in the queue with valid data
*/
* iterator () {
while ((this.index + 1) < this.length) {
// Increment the sample to the next one with valid data
while (((this.index + 1) < this.length) && !this.input.infos.isValid(this.index + 1)) {
this.index += 1
}
if ((this.index + 1) < this.length) {
this.index += 1
yield this
}
}
}
}
/**
* Provides access to the data samples read by an :class:`Input`.
*/
class Samples {
/**
* This class provides access to data samples read by an
* :class:`Input` (using either the :meth:`Input.read`
* or :meth:`Input.take` methods).
*
* This class implements a ``[Symbol.iterator]()`` method, making it an
* iterable. This allows it to be used in ``for... of`` loops, to iterate
* through available samples::
*
* for (const sample of input.samples) {
* console.log(JSON.stringify(sample.getJson()))
* }
*
* The method :meth:`Samples.get` returns a :class:`SampleIterator` which
* can also be used to access available samples::
*
* const sample = input.samples.get(0)
* console.log(JSON.stringify(sample.getJson()))
*
* The samples returned by these methods may only contain meta-data
* (see :attr:`SampleIterator.info`). The :attr:`Samples.validDataIter`
* iterable only iterates over samples that contain valid data
* (a :class:`ValidSampleIterator`).
*
* :class:`Samples` and :class:`ValidSampleIterator` both also provide
* generators to the samples, allowing applications to define their own
* iterables (see :meth:`Samples.iterator()` and
* :meth:`ValidSampleIterator.iterator()`).
*
* ``Samples`` is the type of the property :meth:`Input.samples`.
*
* For more information and examples, see :ref:`Accessing the data samples`.
*
* Attributes:
* * length (number) - The number of samples available since the last time
* :meth:`Input.read` or :meth:`Input.take` was called.
* * validDataIter (:class:`ValidSampleIterator`) - The class used to
* iterate through the available samples that have valid data.
*/
constructor (input) {
this.input = input
}
/**
* Returns an iterator to the data samples, starting at the index specified.
*
* The iterator provides access to all the data samples retrieved by the
* most recent call to :meth:`Input.read` or :meth:`Input.take`.
*
* This iterator may return samples with invalid data (samples that only
* contain meta-data).
* Use :attr:`Samples.validDataIter` to avoid having to check
* :attr:`SampleIterator.validData`.
*
* @param {number} [index] The index of the sample from which the iteration
* should begin. By default, the iterator begins with the first sample.
*
* @returns :class:`SampleIterator` - An iterator to the samples (which
* implements both iterable and iterator logic).
*/
get (index) {
return new SampleIterator(this.input, index)
}
/**
* Returns an iterable, allowing the samples to be accessed using a for...of loop.
*
* The iterable provides access to all the data samples retrieved by the most
* recent call to :meth:`Input.read` or :meth:`Input.take`.
*
* This iterable may return samples with invalid data (samples that only contain
* meta-data).
* Use :attr:`Samples.validDataIter` to avoid having to check
* :attr:`SampleIterator.validData`.
*
* Allows for the following syntax::
*
* for (const sample of input.samples) {
* // ..
* }
*
* @returns :class:`SampleIterator` An iterator to the samples.
*/
[Symbol.iterator] () {
const iterable = new SampleIterator(this.input)
return iterable.iterator()
}
/**
* The iterator generator (used by the iterable).
*
* This method returns a generator, which must be incremented manually by the
* application (using the iterator.next() method).
*
* Once incremented, the data can be accessed via the ``.value`` attribute.
* Once no more samples are available, the ``.done`` attribute will be true.
*
* Using this method, it is possible to create your own iterable::
*
* const iterator = input.samples.iterator()
* const singleSample = iterator.next().value
*
* @generator
* @yields {SampleIterator} The next sample in the queue
*/
* iterator () {
const iterator = new SampleIterator(this.input)
while ((iterator.index + 1) < iterator.length) {
iterator.index += 1
yield iterator
}
}
/**
* Returns an iterator to the data samples that contain valid data.
*
* The iterator provides access to all the data samples retrieved by the most
* recent call to :meth:`Input.read` or :meth:`Input.take`, and skips samples
* with invalid data (meta-data only).
*
* By using this iterator, it is not necessary to check if each sample contains
* valid data.
*
* @returns {ValidSampleIterator} An iterator to the samples containing valid
* data (which implements both iterable and iterator logic).
*/
get validDataIter () {
return new ValidSampleIterator(this.input)
}
/**
* The number of samples available.
*/
get length () {
return this.input.samples.getLength()
}
/**
* Returns the number of samples available.
*
* This method is deprecated, use the getter :meth:`Samples.length`.
* @private
*/
getLength () {
let length = [null]
const retcode = connectorBinding.RTI_Connector_get_sample_count(
this.input.connector.native,
this.input.name,
length)
_checkRetcode(retcode)
// We use ~~ to convert from double -> int. This is required to allow:
// for (var i =0; i < input.samples.getLength(); ++i)
// It works since we are doing a bitwise complement (double not).
return ~~length[0]
}
/**
* Obtains the value of a numeric field within this sample.
*
* See :ref:`Accessing the data samples`.
*
* @param {number} index The index of the sample.
* @param {string} fieldName The name of the field.
* @returns {number} The obtained value.
*/
getNumber (index, fieldName) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else if (!_isString(fieldName)) {
throw new TypeError('fieldName must be a string')
} else {
// Increment index since C API is based on Lua with 1-based indexes
index += 1
let value = [null]
const retcode = connectorBinding.RTI_Connector_get_number_from_sample(
this.input.connector.native,
value,
this.input.name,
index,
fieldName)
_checkRetcode(retcode)
// Return null if no_data was returned (unset optional)
if (retcode === _ReturnCodes.noData) {
return null
} else {
return value[0]
}
}
}
/**
* Obtains the value of a boolean field within this sample.
*
* See :ref:`Accessing the data samples`.
*
* @param {number} index The index of the sample.
* @param {string} fieldName The name of the field.
* @returns {number} The obtained value.
*/
getBoolean (index, fieldName) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else if (!_isString(fieldName)) {
throw new TypeError('fieldName must be a string')
} else {
// Increment index since C API is based on Lua with 1-based indexes
index += 1
let value = [null]
const retcode = connectorBinding.RTI_Connector_get_boolean_from_sample(
this.input.connector.native,
value,
this.input.name,
index,
fieldName)
_checkRetcode(retcode)
// Return null if no_data was returned (unset optional)
if (retcode === _ReturnCodes.noData) {
return null
} else {
return value[0]
}
}
}
/**
* Obtains the value of a string field within this sample.
*
* See :ref:`Accessing the data samples`.
*
* @param {number} index The index of the sample.
* @param {string} fieldName The name of the field.
* @returns {string} The obtained value.
*/
getString (index, fieldName) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else if (!_isString(fieldName)) {
throw new TypeError('fieldName must be a string')
} else {
// Increment index since C API is based on Lua with 1-based indexes
index += 1
let value = [null]
const retcode = connectorBinding.RTI_Connector_get_string_from_sample(
this.input.connector.native,
value,
this.input.name,
index,
fieldName)
_checkRetcode(retcode)
if (retcode === _ReturnCodes.noData) {
return null
} else {
return value[0]
}
}
}
/**
* Gets the value of a field within this sample.
*
* See :ref:`Accessing the data samples`.
*
* This API can be used to obtain strings, numbers, booleans and the JSON
* representation of complex members.
*
* @param {string} fieldName - The name of the field.
* @returns {number|string|boolean|JSON} The value of the field.
*/
getValue (index, fieldName) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else if (!_isString(fieldName)) {
throw new TypeError('fieldName must be a string')
} else {
return _getAnyValue(
connectorBinding.RTI_Connector_get_any_from_sample,
this.input.connector.native,
this.input.name,
index,
fieldName)
}
}
/**
* Gets a JSON object with the values of all the fields of this sample.
*
* @param {number} index The index of the sample.
* @param {string} [memberName] The name of the complex member. The type
* of the member with name memberName must be an array, sequence, struct,
* value or union.
* @returns {JSON} The obtained JSON object.
*
* See :ref:`Accessing the data samples`.
*/
getJson (index, memberName) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else {
// Increment index since Lua arrays are 1-indexed
index += 1
let str = [null]
let retcode = _ReturnCodes.noData
// memberName is "optional" - if supplied we will get the JSON object for
// a specific complex member in the sample
if (memberName !== undefined) {
if (!_isString(memberName)) {
throw new TypeError('memberName must be a string')
} else {
retcode = connectorBinding.RTI_Connector_get_json_member(
this.input.connector.native,
this.input.name,
index,
memberName,
str)
}
} else {
retcode = connectorBinding.RTI_Connector_get_json_sample(
this.input.connector.native,
this.input.name,
index,
str)
}
_checkRetcode(retcode)
if (retcode === _ReturnCodes.noData) {
return null
}
return JSON.parse(str[0])
}
}
/**
* Obtains a native handle to the sample, which can be used to access
* additional *Connext DDS* APIs in C.
*
* @param {number} index The index of the sample for which to obtain
* the native RTI_HANDLE.
* @returns {RTI_HANDLE} A native RTI_HANDLE to the sample.
*/
getNative (index) {
if (!_isValidIndex(index)) {
throw new TypeError('index must be an integer')
} else {
// Increment index since Lua arrays are 1-indexed
index += 1
return connectorBinding.RTI_Connector_get_native_sample(
this.input.connector.native,
this.input.name,
index)
}
}
/**
* This method is deprecated, use :meth:`Samples.getJson`.
*
* @param {number} index - The index of the sample for which to obtain
* the JSON object.
* @param {string} [memberName] - The name of the complex member for
* which to obtain the JSON object.
* @returns {JSON} A JSON object representing the current sample.
* @private
*/
getJSON (index, memberName) {
return this.getJson(index, memberName)
}
}
/**
* The type returned by the property :meth:`SampleIterator.info`.
*/
class SampleInfo {
/**
* This class provides a way to access the SampleInfo of a received data sample.
*/
constructor (input, index) {
this.input = input
this.index = index
}
/**
* Type-independent function to obtain any value from the SampleInfo structure.
*
* The supported fieldNames are:
*
* * ``'source_timestamp'`` returns an integer representing nanoseconds
* * ``'reception_timestamp'`` returns an integer representing nanoseconds
* * ``'sample_identity'`` or ``'identity'`` returns a JSON object
* (see :meth:`Output.write`)
* * ``'related_sample_identity'`` returns a JSON object
* (see :meth:`Output.write`)
* * ``'valid_data'`` returns a boolean (equivalent to
* :attr:`SampleIterator.validData`)
*
* These fields are documented in `The SampleInfo Structure
* <https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/index.htm#users_manual/The_SampleInfo_Structure.htm>`__
* section in the *RTI Connext DDS Core Libraries User's Manual*.
*
* @param {string} fieldName - The name of the ``SampleInfo`` field to obtain
* @returns The obtained value from the ``SampleInfo`` structure
* @example const source_timestamp = input.samples.get(0).info.get('source_timestamp')
*/
get (fieldName) {
if (!_isString(fieldName)) {
throw new TypeError('fieldName must be a string')
} else {
return _getAnyValue(
connectorBinding.RTI_Connector_get_any_from_info,
this.input.connector.native,