7.1. Accessing the data

The types you use to write or read data may included nested structs, sequences and arrays of primitive types or structs, etc.

These types are defined in XML, as described in Data types.

To access the data, Instance and SampleIterator provide setters and getters that expect a fieldName string. This section describes the format of this string.

We will use the following type definition of MyType:

<types>
    <enum name="Color">
        <enumerator name="RED"/>
        <enumerator name="GREEN"/>
        <enumerator name="BLUE"/>
    </enum>
    <struct name= "Point">
        <member name="x" type="int32"/>
        <member name="y" type="int32"/>
    </struct>
    <union name="MyUnion">
        <discriminator type="nonBasic" nonBasicTypeName="Color"/>
        <case>
          <caseDiscriminator value="RED"/>
          <member name="point" type="nonBasic"  nonBasicTypeName= "Point"/>
        </case>
        <case>
          <caseDiscriminator value="GREEN"/>
          <member name="my_long" type="int32"/>
        </case>
    </union>
    <struct name= "MyType">
        <member name="my_long" type="int32"/>
        <member name="my_double" type="float64"/>
        <member name="my_enum" type="nonBasic"  nonBasicTypeName= "Color" default="GREEN"/>
        <member name="my_boolean" type="boolean" />
        <member name="my_point" type="nonBasic"  nonBasicTypeName= "Point"/>
        <member name="my_union" type="nonBasic"  nonBasicTypeName= "MyUnion"/>
        <member name="my_int_sequence" sequenceMaxLength="10" type="int32"/>
        <member name="my_point_sequence" sequenceMaxLength="10" type="nonBasic"  nonBasicTypeName= "Point"/>
        <member name="my_point_array" type="nonBasic"  nonBasicTypeName= "Point" arrayDimensions="3"/>
        <member name="my_optional_point" type="nonBasic"  nonBasicTypeName= "Point" optional="true"/>
        <member name="my_optional_long" type="int32" optional="true"/>
    </struct>
</types>

The above XML corresponds to the following IDL definition:

enum Color {
    RED,
    GREEN,
    BLUE
};

struct Point {
    long x;
    long y;
};

union MyUnion switch(Color) {
    case RED: Point point;
    case GREEN: string<512> my_string;
};

struct MyType {
    long my_long;
    double my_double;
    Color my_enum;
    boolean my_boolean;
    string<512> my_string;
    Point my_point;
    MyUnion my_union;
    sequence<long, 10> my_int_sequence;
    sequence<Point, 10> my_point_sequence;
    Point my_point_array[3];
    @optional Point my_optional_point;
    @optional long my_optional_long;
};

Hint

You can get the XML definition of an IDL file with rtiddsgen -convertToXml MyType.idl.

We will refer to an Output named output and an Input named input such that input.samples.length > 0.

7.1.1. Using dictionaries vs. accessing individual members

For an Input or Output, you can access the data all at once by using a dictionary, or member by member. Using a dictionary is usually more efficient if you intend to access most or all of the data members of a large type.

In an Output, Instance.set_dictionary() receives a dictionary with all or some of the Output type members. In an Input, SampleIterator.get_dictionary() retrieves all the members.

It is also possible to provide a member_name to SampleIterator.get_dictionary() to obtain a dictionary that only contains the fields of that nested member.

The methods described in the following section receive a field_name argument to get or set a specific member.

7.1.2. Accessing basic members (numbers, strings and booleans)

To set a field in an Output, use the appropriate setter.

To set any numeric type, including enumerations:

output.instance.set_number("my_long", 2)
output.instance.set_number("my_double", 2.14)
output.instance.set_number("my_enum", 2)

To set booleans:

output.instance.set_boolean("my_boolean", True)

To set strings:

output.instance.set_string("my_string", "Hello, World!")

As an alternative to the setters mentioned above, you can use the special method __setitem__ as follows:

output.instance["my_double"] = 2.14
output.instance["my_boolean"] = True
output.instance["my_string"] = "Hello, World!"

In all cases, the type of the assigned value must be consistent with the type of the field, as defined in the configuration file.

Similarly, to get a field in an Input sample, use the appropriate getter: SampleIterator.get_number(), SampleIterator.get_boolean(), SampleIterator.get_string(), or __getitem__. get_string also works with numeric fields, returning the number as a string. For example:

for sample in input.samples.valid_data_iter:
    value = sample.get_number("my_double")
    value = sample.get_boolean("my_boolean")
    value = sample.get_string("my_string")

    # or alternatively:
    value = sample["my_double"]
    value = sample["my_boolean"]
    value = sample["my_string"]

    # get number as string:
    value = sample.get_string("my_double")

Note

The typed getters and setters perform better than __setitem__ and __getitem__ in applications that write or read at high rates. We also recommend get_dictionary or set_dictionary over __setitem__ or __getitem__ when accessing all or most of the fields of a sample (see previous section).

Note

If a field my_string, defined as a string in the configuration file, contains a value that can be interpreted as a number, sample["my_string"] returns a number, not a string.

7.1.3. Accessing 64-bit integers

Internally, Connector relies on a framework that only contains a single number type, which is an IEEE-754 floating-point number. As a result, not all 64-bit integers can be represented with exact precision. If your type contains uint64 or int64 members, and you expect them to be larger than 2^53 (or smaller than -2^53), then you must take the following into account.

64-bit values with an absolute value greater or equal to 2^53 can be set via:
  • The type-agnostic setter, __setitem__. The values can be supplied as strings, or as numbers, e.g., the_output.instance["my_uint64"] = "18446744073709551615".
  • The Instance.set_string() method, e.g., the_output.instance.set_string("my_uint64", "18446744073709551615")
  • The Instance.set_dictionary() method, e.g., the_output.instance.set_dictionary({"my_uint64": "18446744073709551615"})
64-bit values with an absolute value greater than 2^53 can be retrieved via:
  • The type-agnostic getter, __getitem__. The value will be an instance of int if larger than 2^53, otherwise a float. e.g., sample["my_int64"] # 9223372036854775807
  • The SampleIterator.get_string(), method. The value will be returned as a string, e.g., sample.get_string("my_int64") # "9223372036854775807"
  • The SampleIterator.get_dictionary() method, e.g., sample.get_dictionary()["my_int64"] # "9223372036854775807"

Warning

If SampleIterator.get_number() is used to retrieve a value > 2^53, an Error will be raised.

Warning

If Instance.set_number() is used to set a value >= 2^53, an Error will be raised.

Note

The Instance.set_number() operation can handle abs(value) < 2^53, whereas SampleIterator.get_number() can handle abs(value) <= 2^53.

7.1.4. Accessing structs

To access a nested member, use . to identify the fully qualified field_name and pass it to the corresponding setter or getter.

output.instance.set_number("my_point.x", 10)
output.instance.set_number("my_point.y", 20)

# alternatively:
output.instance["my_point.x"] = 10
output.instance["my_point.y"] = 20

It is possible to reset the value of a complex member back to its default:

output.instance.clear_member("my_point") # x and y are now 0

Structs in dictionaries are set as follows:

output.instance.set_dictionary({"my_point":{"x":10, "y":20}})

When an member of a struct is not set, it retains its previous value. If we run the following code after the previous call to set_dictionary:

output.instance.set_dictionary({"my_point":{"y":200}})

The value of my_point is now {"x":10, "y":200}

It is possible to obtain the dictionary of a nested struct:

for sample in input.samples.valid_data_iter:
   point = sample.get_dictionary("my_point")

member_name must be one of the following types: array, sequence, struct, value or union. If not, the call to get_dictionary() will fail:

for sample in input.samples.valid_data_iter:
   try:
     long = sample.get_dictionary("my_long")
   except rti.Error:
     print("ERROR, my_long is a basic type")

It is also possible to obtain the dictionary of a struct using the __getitem__ operator:

for sample in input.samples.valid_data_iter:
    point = sample["my_point"]
    # point is a dict

The same limitations described in Accessing basic members (numbers, strings and booleans) about using __getitem__ apply here.

7.1.5. Accessing arrays and sequences

Use "field_name[index]" to access an element of a sequence or array, where 0 <= index < length:

value = input.samples[0].get_number("my_int_sequence[1]")
value = input.samples[0].get_number("my_point_sequence[2].y")

You can get the length of a sequence:

length = input.samples[0].get_number("my_int_sequence#")

Another option is to use SampleIterator.get_dictionary("field_name") to obtain a dictionary containing all of the elements of the array or sequence with name field_name:

for sample in input.samples.valid_data_iter:
    point_sequence = sample.get_dictionary("my_point_sequence") # or sample["my_point_sequence"]
    # point_sequence is a list

You can also get a specific element as a dictionary (if the element type is complex):

for sample in input.samples.valid_data_iter:
   point_element = sample.get_dictionary("my_point_sequence[1]")

In an Output, sequences are automatically resized:

output.instance.set_number("my_int_sequence[5]", 10) # length is now 6
output.instance.set_number("my_int_sequence[4]", 9) # length still 6

To clear a sequence:

output.instance.clear_member("my_int_sequence") # my_int_sequence is now empty

In dictionaries, sequences and arrays are represented as lists. For example:

output.instance.set_dictionary({
    "my_int_sequence":[1, 2],
    "my_point_sequence":[{"x":1, "y":1}, {"x":2, "y":2}]})

Arrays have a constant length that can’t be changed. If you don’t set all the elements of an array, the remaining elements retain their previous value. However, sequences are always overwritten. See the following example:

output.instance.set_dictionary({
    "my_point_sequence":[{"x":1, "y":1}, {"x":2, "y":2}],
    "my_point_array":[{"x":1, "y":1}, {"x":2, "y":2}, {"x":3, "y":3}]})

output.instance.set_dictionary({
    "my_point_sequence":[{"x":100}],
    "my_point_array":[{"x":100}, {"y":200}]})

After the second call to set_dictionary(), the contents of my_point_sequence are [{"x":100, "y":0}], but the contents of my_point_array are [{"x":100, "y":1}, {"x":2, "y":200}, {"x":3, "y":3}].

7.1.6. Accessing optional members

An optional member is a member that applications can decide to send or not as part of every published sample. Therefore, optional members may or may not have a value. They are accessed the same way as non-optional members, except that None is a possible value.

On an Input, any of the getters may return None if the field is optional:

if input.samples[0].get_number("my_optional_long") is None:
    print("my_optional_long not set")

if input.samples[0].get_number("my_optional_point.x") is None:
    print("my_optional_point not set")

SampleIterator.get_dictionary() returns a dictionary that doesn’t include unset optional members.

To set an optional member on an Output:

output.instance.set_number("my_optional_long", 10)

If the type of the optional member is not primitive, when any of its members is first set, the rest are initialized to their default values:

output.instance.set_number("my_optional_point.x", 10)

If my_optional_point was not previously set, the previous code also sets y to 0.

There are several ways to reset an optional member. If the type is primitive:

output.instance.set_number("my_optional_long", None) # Option 1
output.instance.clear_member("my_optional_long") # Option 2

If the member type is complex:

output.instance.clear_member("my_optional_point")

Note that Instance.set_dictionary() doesn’t clear those members that are not specified; their value remains. For example:

output.instance.set_number("my_optional_long", 5)
output.instance.set_dictionary({'my_double': 3.3, 'my_long': 4}) # my_optional_long is still 5

To clear a member, set it to None explicitly:

output.instance.set_dictionary({'my_double': 3.3, 'my_long': 4, 'my_optional_long': None})

For more information about optional members in DDS, see Optional Members in the Extensible Types Guide.

7.1.7. Accessing unions

In an Output, the union member is automatically selected when you set it:

output.instance.set_number("my_union.point.x", 10)

You can change it later:

output.instance.set_number("my_union.my_long", 10)

In an Input, you can obtain the selected member as a string:

if input.samples[0].get_string("my_union#") == "point":
    value = input.samples[0].get_number("my_union.point.x")

The __getitem__ operator can be used to obtain unions:

for sample in input.samples.valid_data_iter:
    union = sample["my_union"]
    # union is a dict

The type returned by the operator is a dict for unions.

The same limitations described in Accessing basic members (numbers, strings and booleans) about using __getitem__ apply here.

7.1.8. Accessing key values of disposed samples

Using Output.write(), an Output can write data, or dispose or unregister an instance. Depending on which of these operations is performed, the instance_state of the received sample will be 'ALIVE', 'NOT_ALIVE_NO_WRITERS' or 'NOT_ALIVE_DISPOSED'. If the instance was disposed, this instance_state will be 'NOT_ALIVE_DISPOSED'. In this state, it is possible to access the key fields of the instance that was disposed.

Note

SampleInfo.valid_data will be false when the SampleInfo.instance_state is 'NOT_ALIVE_DISPOSED'. In this situation it’s possible to access the key fields in the received sample.

The key fields can be accessed as follows:

# The output and input are using the following type:
# struct ShapeType {
#     @key string<128> color;
#     long x;
#     long y;
#     long shapesize;
# }

output.instance["x"] = 4
output.instance["color"] = "Green"
# Assume that some data associated with this instance has already been sent
output.write(action="dispose")
input.wait()
input.take()
sample = input.samples[0]

if sample.info["instance_state"] == "NOT_ALIVE_DISPOSED":
    # sample.info.get('valid_data') will be false in this situation
    # Only the key-fields should be accessed
    color = sample["color"] # 'Green'
    # The fields 'x','y' and 'shapesize' cannot be retrieved because they're
    # not part of the key
    # You can also call get_dictionary() to get all of the key fields.
    # Again, only the key fields returned within the dictionary should
    # be accessed.
    key_values = sample.get_dictionary() # { "color": "Green", "x": 0, "y": 0, "shapesize": 0 }

Warning

When the sample has an instance state of 'NOT_ALIVE_DISPOSED' only the key fields should be accessed.