Shared Data Types
The communication database allows to define data types which can be used in the receiver and the sender. They allow to define a way to pack multiple individual datapoint into a structure and to define enumeration types and thus restrict possible values for a type. They allow to build message objects that transmit complex and nested data structures, and they even allow to define the base message structure (see Special types).
Definition
They are defined in the sharedDataTypes.json
file.
The file consists of a json object, whose attributes are the shared data types.
The value of these attributes describes information about the type of the shared data type.
In the following example, there are three data types defined, a
, b
and c
. The attributes of
these data types are empty for this example, in a real example the {}
wouldn’t be valid:
{
"a": {},
"b": {},
"c": {}
}
Type description
The value for each attribute of the top level json object describes the type of this shared data type. It can be either a string or an object.
Simple
In the simple case, the type description is a string. In this case, the string is the name of the base type. The base type can also be a unit type. Additionally, the base type can also be a shared data type that was declared before the new type.
In the following example, the a
type is an 8-bit unsigned integer, the b
type is a meter
unit
and the c
type is synonym for the a
type and therefore also an 8-bit unsigned integer:
{
"a": "uint8",
"b": "meter",
"c": "a"
}
Arrays
The simple data type description can have an opening bracket, then a number or a name of a shared constant which resolves to a positive integer number, and then a closing bracket. This indicates that the type represents an array, or a list.
in the following example, two types are declared: The type a
, which represents a list of 5
unsigned 8-bit integers, and the type b
, which also represents a list of unsigned 8-bit integers
whose size depends on the value of the shared constant B_LENGTH
.
{
"a": "uint8[5]",
"b": "uint8[B_LENGTH]"
}
Advanced
A type can be enhanced with additional metadata. To add metadata to a
type from the simple case
example, the value must become an object and the string name of the base type moves into the
__type__
attribute of the new object.
This allows to represent the following metadata about the type:
Attribute Name |
Description |
---|---|
|
The base type of the shared type, has the same restrictions as in the simple case. |
|
An optional human readable description of this type. |
|
An optional default value for this type. This is used for example in telecommands when the value is not provided. |
The following example shows the a
type from the simple example with a description
and a default value:
{
"a": {
"__doc__": "A description of this type.",
"__type__": "uint8",
"__value__": 1
}
}
Enumerations
If a __values__
attribute is given (note the s
at the end), the type is interpreted as an
enumeration type. These types have a restricted set of named values called enumeration members,
that the type can represent.
The enumeration members are automatically assigned integer values based on the C standard for
enumeration default values: If no explicit value is defined for the first member, it is assigned
the value 0
. Fore each other member, if the no explicit value is specified, it’s value becomes
the value of the previous member incremented by 1
. The value of an enumeration member can be
specified via its __value__
attribute. If two enumeration members have the same value, they are
the second is considered an alias of the first. If serialized, both values will be deserialized
to the first member.
If a __type__
attribute is given, it represents the underlying base type of the enumeration type.
If it is omitted, the smallest possible base type is automatically chosen based on the largest
enumeration member value.
These named values are either provided in a list, in which case the list item must be a string representing the name of that enumeration member, or as an object where each value is represented as an attribute of that object. The name of each attribute is the name of that enumeration member, and the value must be an object with additional metadata about this enumeration value:
Attribute Name |
Description |
---|---|
|
An optional description of this enumeration value. |
|
An optional explicit value of this enumeration value. |
In the following example, two enumerations are defined:
a
has two enumeration members, both of which have a documentation.
The first member has the value a.A_STATE_1
= 0
,
the second member has the value a.A_STATE_2
= 42
.
b
has also two enumeration members, but none of them have a documentation.
The first member has the value b.B_STATE_1
= 0
,
the second member has the value b.B_STATE_2
= 1
.
{
"a": {
"__doc__": "A description of the a type.",
"__type__": "uint8",
"__values__": {
"A_STATE_1": {
"__doc__": "Description for the A_STATE_1 value."
},
"A_STATE_2": {
"__doc__": "Description for the A_STATE_2 value.",
"__value__": 42
}
}
},
"b": {
"__doc__": "A description of the b type.",
"__type__": "uint8",
"__values__": [
"B_STATE_1",
"B_STATE_2"
]
}
}
Structure types
Structural datatypes allow for grouping of and nesting of multiple datatypes. A structural type can include members of any other data type that was defined before, or of a new substructure.
Structural types can’t have a base type, therefore they don’t allow to specify a __type__
attribute. They do allow to specify a __doc__
documentation description. All other attributes of
the metadata are interpreted as children of the structural type. The name of the attributes are the
names of the children. If a new type is defined in the child, it’s going to have the same name as
the attribute. The value of each child attribute is a new type description.
Circular types are not possible, because the type can’t reference itself.
The following example defines two structural types: The first one a
has two children, an unsigned
8-bit integer x
and a character y
. The second type b
has a documentation and three children:
A boolean element1
and an element of the previously defined type a
, which has the two mentioned
children. Additionally, b
has a third child named element3
, which is a structural type whose
name is also element3
with two children, a 32-bit signed integer subElement1
and an array of
four bytes named subElement2
.
{
"a": {
"x": "uint8",
"y": "char"
},
"b": {
"__doc__": "A description of this type.",
"element1": "bool",
"element2": "a",
"element3": {
"subElement1": "int32",
"subElement2": "bytes[4]"
}
}
}
Special types
There are some special types that are automatically defined by the database and some that are expected to be defined, for the serializer and parser to function properly. The later are:
TelemetryMessageHeader
: The parser expects a structural type with this name (can be configured when constructing the parser). The type must define an integersync byte 1
,sync byte 2
, as well as antype
child of the automatically generatedTelemetryType
enum type.TelecommandMessageHeader
: The serializer expects a structural type with this name (can also be configured when constructing the serializer). The type must have an integersync byte 1
,sync byte 2
, as well as atype
member which is of the automatically generated enum typeTelecommandType
.