DCCL v3
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 (using Protobuf version 2.4.0 or newer; see old syntax for older versions):

import "dccl/option_extensions.proto";
message NavigationReport {
option (dccl.msg) = { codec_version: 3
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: 3 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:

idl-table.png

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 and DCCL 3). Hence, the default is "2" to preserve backwards-compatibility on a message-by-message basis, but this should always be set to "3" when you are able to use DCCL v3 for all nodes that deploy this message.

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 dccl3-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.

base-dimensions.png

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:

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:

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: 3 };
// 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: 3
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: 3 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 Syntax for older Protobuf versions (< 2.4.0)

When using older versions of Google Protocol Buffers than 2.4.0, options cannot be aggregated. Thus, the example NavigationReport message would be written as such:

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

Note that this syntax is valid for newer versions of Protobuf as well, so it's the choice to use if you are concerned about portability over readability.