DCCL v4
DCCL Interface Descriptor Language (IDL)

DCCL uses the Google Protocol Buffers (Protobuf) language to define messages. The DCCL IDL is defined as extensions to the Protobuf language message and field options to allow more compact encoding than is possible with the default Protobuf meta-data. You should familiarize yourself with basic Protobuf usage before reading the rest of this document: see http://code.google.com/apis/protocolbuffers/docs/overview.html.

An example DCCL message is as follows:

import "dccl/option_extensions.proto";
message NavigationReport {
option (dccl.msg) = { codec_version: 4
id: 124
max_bytes: 32 };
required double x = 1 [(dccl.field) = { min: -10000 max: 10000 precision: 1 }];
required double y = 2 [(dccl.field) = { min: -10000 max: 10000 precision: 1 }];
required double z = 3 [(dccl.field) = { min: -5000 max: 0 precision: 0 }];
enum VehicleClass { AUV = 1; USV = 2; SHIP = 3; }
optional VehicleClass veh_class = 4;
optional bool battery_ok = 5;
}

In the above message, the snippet

option (dccl.msg) = { codec_version: 4 id: 124 max_bytes: 32 };

represents the message options extensions since they affect the design of the entire DCCL message (in this case "NavigationReport"). The field options affect a given field, e.g.

[(dccl.field) = { min: -10000 max: 10000 precision: 1 }];

The full Protobuf definition of the DCCL extensions is given in option_extensions.proto (as messages DCCLFieldOptions and DCCLMessageOptions).

DCCL options

The core set of DCCL options is given in the following table:

DCCL ID: (dccl.msg).id

The DCCL ID is used to uniquely identify a given message name without having to encode the name in the message (encoding a number is much cheaper than a string). To interoperate with other groups, please see http://gobysoft.org/wiki/DcclIdTable. For private work, please use IDs 124-127 (one-byte) and 128-255 (two-byte).

DCCL Maximum bytes: (dccl.msg).max_bytes

This value is the maximum message size before you get an error from DCCL. This is a design tool to help ensure messages do not exceed a desired value, typically the path maximum transmission unit (MTU). Messages that do not take the actual max_bytes size are encoded only as the size they take up (i.e. they are not padded to max_bytes).

DCCL Codec Version: (dccl.msg).codec_version

This option sets the default codec version (which is not wire-compatibility between Goby/DCCL 2, DCCL 3, and DCCL 4). This should always be set to "4" when you are able to use DCCL v4 for all nodes that deploy this message, or an earlier version if required (e.g., if one of the nodes has access only to DCCL3, use "3").

DCCL Static Units

Since the DCCL field bounds (min, max, and precision) are often based off the physical origins of the data, it is important to define the units of measure of those fields. The DCCL IDL has support for defining the units of a numeric field's quantity. When using the DCCL C++ library, this support is directly connected to the Boost Units C++ library: http://www.boost.org/doc/html/boost_units.html. The units of a given field are given by two parameters: the physical dimension (e.g. length, force, mass, etc.), and the unit system which defaults to the International System of Units (SI). The units of the field can also be specified directly, outside of a canonical system (e.g. nautical mile, fathom, yard, knot, etc.).

The fields defined with units generate additional C++ methods using the DCCL plugin (protoc-gen-dccl) to the GPB compiler (protoc). The Debian package for the plugin is

sudo apt-get install dccl4-compiler

These additional methods provide accessors and mutators for the dimensioned Boost Units quantities, with full static "unit safety", and correct conversions between different units of the same dimensions (e.g. feet to meters). Unit safety is defined as static (compiler-checked) dimensional analysis. The term is a blending of the (computer science) notion of type safety with (physical) dimensional analysis. For example, in a unit-safe system, the compiler will not allow the user to set a field with dimensions of length to a quantity of hours.

The Units field extension has the following options:

DCCL Units Generated Code

The DCCL Units C++ generated accessors and mutators mirror those provided by the standard Google Protocol Buffers compiler for numeric fields, with the method name appended by the string "_with_units". For the standard Protobuf generated code see https://developers.google.com/protocol-buffers/docs/reference/cpp-generated. DCCL Units are only valid on numeric fields (either singular or repeated). Two accessors are provided for convenience: a non-template accessor that returns the value as the Quantity (i.e. boost::units::quantity) defined in the DCCL field, and a template accessor that can take any valid Boost Units Quantity (i.e. a type with the same dimensions as the DCCL field) and return the value in that type, accounting for all conversion factors.

Singular Numeric Fields

For a singular (optional or required) field "foo" with the following parameters:

  • Type: "[foo type]", the original unit-less field type (e.g. google::protobuf::int32, double)
  • Unit dimension: "[foo dimension]" (e.g. boost::units::length_dimension, boost::units::derived_dimension< boost::units::length_base_dimension,1, boost::units::time_base_dimension,-1>::type)
  • Unit system: "[foo system]" (e.g. boost::units::si::system, boost::units::degree::system)

the following additional methods are defined for unit safe access to the DCCL message:

typedef [foo dimension] foo_dimension;
typedef boost::units::unit<foo_dimension, [foo system] > foo_unit;
// set the field's value using the given Quantity (which must have the same dimensions as foo_dimension), performing all necessary conversions (e.g. from yards to meters).
template<typename Quantity >
void set_foo_with_units(Quantity value_w_units)
{ set_foo(boost::units::quantity<foo_unit,google::protobuf::int32 >(value_w_units).value() ); };
// get the field's value using the given Quantity (which must have the same dimensions as foo_dimension), performing all necessary conversions.
template<typename Quantity >
Quantity foo_with_units() const
{ return Quantity(foo() * foo_unit()); };
// get the field's value as a Quantity of foo_unit, as defined in the DCCL message.
boost::units::quantity< foo_unit > foo_with_units() const
{ return foo_with_units<boost::units::quantity< foo_unit, [foo type] > >(); };

Repeated Numeric Fields

For a repeated field "foo" with the following parameters:

  • Type: "[foo type]"
  • Unit dimension: "[foo dimension]"
  • Unit system: "[foo system]"

the following additional methods are defined for unit safe access to the DCCL message:

typedef [foo dimension] foo_dimension;
typedef boost::units::unit<foo_dimension, [foo system]> foo_unit;
// set a given index of the repeated field using the given Quantity.
template<typename Quantity >
void set_foo_with_units(int index, Quantity value_w_units)
{ set_foo(index, boost::units::quantity<foo_unit, [foo type]>(value_w_units).value() ); };
// add a new value to the end of the repeated field using the given Quantity.
template<typename Quantity >
void add_foo_with_units(Quantity value_w_units)
{ add_foo(boost::units::quantity<foo_unit,google::protobuf::int32 >(value_w_units).value() ); };
// get the field's value using the given Quantity at the given index
template<typename Quantity >
Quantity foo_with_units(int index) const
{ return Quantity(foo(index) * foo_unit()); };
// get the field's value as a Quantity of foo_unit at a given index
boost::units::quantity< foo_unit > foo_with_units(int index) const
{ return foo_with_units<boost::units::quantity< foo_unit,google::protobuf::int32 > >(index); };

DCCL Units examples

Here are a few example DCCL messages which include unit specification:

AUVStatus

@PROTOBUF_SYNTAX_VERSION@
import "dccl/option_extensions.proto";
message AUVStatus {
option (dccl.msg) = { id: 122
max_bytes: 32
codec_version: 4 };
// Header
required double timestamp = 1 [(dccl.field) = { codec: "_time" in_head: true }];
required int32 source = 2 [(dccl.field) = { min: 0 max: 31 in_head: true }];
required int32 destination = 3 [(dccl.field) = { min: 0 max: 31 in_head: true }];
// Body
required double x = 4 [(dccl.field) = { units { system: "si" base_dimensions: "L" }
min: -10000 max: 10000 precision: 1 }];
required double y = 5 [(dccl.field) = { units { system: "si" base_dimensions: "L" }
min: -10000 max: 10000 precision: 1 }];
required double speed = 6 [(dccl.field) = { units { system: "si" base_dimensions: "LT^-1" }
min: 0
max: 20.0,
precision: 1 }];
required double heading = 7 [(dccl.field) = { units { system: "angle::degree" derived_dimensions: "plane_angle" }
min: 0
max: 360.0,
precision: 1 }];
optional double depth = 8 [(dccl.field) = { units { system: "si" base_dimensions: "L" }
min: 0 max: 6500 precision: 0 }];
optional double altitude = 9 [(dccl.field) = { units { system: "si" base_dimensions: "L" }
min: 0 max: 500 precision: 1 }];
optional double pitch = 10 [(dccl.field) = { units { system: "angle::radian" derived_dimensions: "plane_angle" }
min: -1.57 max: 1.57 precision: 2 }];
optional double roll = 11 [(dccl.field) = { units { system: "angle::radian" derived_dimensions: "plane_angle" }
min: -1.57 max: 1.57 precision: 2 }];
optional MissionState mission_state = 12;
enum MissionState { IDLE = 0;
SEARCH = 1;
CLASSIFY = 2;
WAYPOINT = 3; }
optional DepthMode depth_mode = 13;
enum DepthMode { DEPTH_SINGLE = 0;
DEPTH_YOYO = 1;
DEPTH_BOTTOM_FOLLOWING = 2; }
}

For example, to set an AUVStatus message's x and y fields to meters (the default for the base dimension of length, since the default system is SI), and then later access them as nautical miles, one can use this C++ example:

using namespace boost::units;
typedef metric::nautical_mile_base_unit::unit_type
NauticalMile;
AUVStatus status;
status.set_x_with_units(1000*si::meters);
status.set_y_with_units(500*si::meters);
quantity<NauticalMile> x_nm(status.x_with_units());
quantity<NauticalMile> y_nm(status.y_with_units());

The value of x_nm is 0.54 nautical miles and y_nm is 0.27 nautical miles.

CTDMessage

@PROTOBUF_SYNTAX_VERSION@
import "dccl/option_extensions.proto";
message CTDMessage
{
option (dccl.msg) = { id: 123
max_bytes: 32
codec_version: 4
unit_system: "si" };
required double temperature = 1 [(dccl.field) = { units { derived_dimensions: "temperature"
system: "celsius" }
min: 0
max: 30
precision: 1 }];
required int32 depth = 2 [(dccl.field) = { units { derived_dimensions: "length"
system: "si" }
min: 0
max: 6000 }];
required double salinity = 4 [(dccl.field) = { min: 10
max: 40
precision: 1
units { base_dimensions: "-"} }];
required double sound_speed = 5 [(dccl.field) = { units { base_dimensions: "LT^-1" system: "si" }
min: 1450
max: 1550
precision: 1 }];
}

CommandMessage

@PROTOBUF_SYNTAX_VERSION@
import "dccl/option_extensions.proto";
message CommandMessage
{
option (dccl.msg) = { id: 125 max_bytes: 32 codec_version: 4 unit_system: "si"};
required int32 destination = 1 [(dccl.field) = { max: 31 min: 0 in_head: true }];
optional string description = 2 [(dccl.field).omit = true];
enum SonarPower { NOMINAL = 10; LOW = 5; OFF = 0; }
optional SonarPower sonar_power = 10;
required double speed = 11 [(dccl.field) = { units { base_dimensions: "LT^-1" }
max: 2.0 min: -0.5 precision: 1 }];
repeated int32 waypoint_depth = 12
[(dccl.field) = { units { base_dimensions: "L" }
max: 40 min: 0 max_repeat: 4 }];
}

DCCL Dynamic Conditions (DCCL4+)

Dynamic Conditions is a new feature in DCCL4 that allows for conditional encoding of the fields based on runtime conditions (values of other parts of the message). This feature allows you to omit a field, mark a field "required", or change the values of the min/max bounds based on the value of one or more fields in the message.

Each dynamic condition is a string that contains a script written in Lua (https://www.lua.org/) that is evaluated each time the message is encoded or decoded.

The available dynamic_conditions are:

  • required_if: The Lua script must return a boolean value: true means the field is now required (overriding the optional or required value in the .proto file).
  • omit_if: The Lua script must return a boolean value: true means the field is omitted from the encoded message.
  • only_if: A commonly used combination of required_if and omit_if. If true, this is the same as required_if returning true. If false, this is the same as omit_if returning true.
  • min: The Lua script must return a numeric value setting the new minimum value. Note that this cannot be less than the hardcoded (dccl.field).min value (if it is, (dccl.field).min is used instead).
  • max: The Lua script must return a numeric value setting the new maximum value. Note that this cannot be greater than the hardcoded (dccl.field).max value (if it is, (dccl.field).max is used instead).

Special variables

Within the Lua script you are given access to some special variables set by DCCL:

  • "this" (a Lua table acting as a struct) is the defined as the current contents of the innermost Message
  • "root" (a Lua table) is the outermost message.
  • "this_index" (a numeric, aka integer) is the index to the current repeated field element if this (sub)message is contained within a repeated field.

For example, given the following message:

import "dccl/option_extensions.proto";
message TestMsg
{
option (dccl.msg) = {
id: 2,
max_bytes: 512,
codec_version: 4
};
enum State
{
STATE_1 = 1;
STATE_2 = 2;
STATE_3 = 3;
}
required State state = 1;
optional int32 a = 2 [(dccl.field) = {
min: 0
max: 200
dynamic_conditions {
required_if: "return this.state == 'STATE_1'"
omit_if: "return this.state ~= 'STATE_1'"
}
}];
}

"this" and "root" refer to the contents of TestMsg.

However, given a different message:

import "dccl/option_extensions.proto";
message TestMsg
{
option (dccl.msg) = {
id: 2,
max_bytes: 512,
codec_version: 4
};
message Child
{
enum IncludeI
{
UNUSED = 0;
YES = 1;
NO = 2;
}
required IncludeI include_i = 1;
optional int32 i = 2 [(dccl.field) = {
min: 0
max: 255
dynamic_conditions { only_if: "return this.include_i == 'YES'" }
}];
}
required Child child = 11;
}

Now within the context of field "i", "this" refers to Child, and "root" refers to TestMsg.

Other notes

Since this is a common idiom and to reduce extra code, you can omit "return" if it is the first part of the Lua script. That is, these are identical:

dynamic_conditions { only_if: "return this.include_i == 'YES'" }
dynamic_conditions { only_if: "this.include_i == 'YES'" }

If your script doesn't begin with "return" you must put it in explicitly:

dynamic_conditions { omit_if: "a = 3; return a == this.field_c" }

The Lua Protobuf functionality uses this wonderful Github project: https://github.com/starwing/lua-protobuf. Please reference the documentation in the event you need more details about the "this" or "root" tables, which are built using this library.

For more details, and an example usage, see the dccl_dynamic_conditions unit test.

dccl::int32
google::protobuf::int32 int32
a signed 32 bit integer
Definition: common.h:57
dccl
Dynamic Compact Control Language namespace.
Definition: gen_units_class_plugin.h:49