Codec Configurations
====================

Most ``ltcodecs`` applications are configured with a field dictionary.  In
Python this is a nested ``dict``.  In files it is usually written as YAML.
Each top-level key is a field name, and each value is a mapping of parameters
for the codec that encodes that field.

Basic Shape
-----------

Every field needs a ``codec`` value.  The value is one of the aliases listed in
:doc:`reference/codec_aliases`.

.. code-block:: yaml

   field_name:
     codec: string
     max_length: 16

The remaining keys are passed to that field codec's constructor.  For example,
``max_length`` belongs to the string codec in the example above.

Scalar Fields
-------------

Use scalar codecs for values that encode as a single value in the message.

.. code-block:: yaml

   name:
     codec: string
     max_length: 10
   count:
     codec: integer
     min_value: 0
     max_value: 100
   enabled:
     codec: bool
   temperature:
     codec: float
     min_value: -5.0
     max_value: 35.0
     precision: 1
   depth:
     codec: linspace_float
     min_value: 0.0
     max_value: 100.0
     resolution: 0.5

Common scalar aliases include ``string``, ``integer``, ``bool``, ``float``,
``uint8``, ``uint16``, ``uint32``, ``int8``, ``int16``, ``float32``, and
``float64``.  Use ``float`` when a fixed number of decimal places is the
natural constraint, and ``linspace_float`` when a physical step size or number
of values is the natural constraint.

Nested Dictionaries
-------------------

Use ``dict`` when a field contains another mapping of named fields.

.. code-block:: yaml

   vehicle:
     codec: dict
     fields:
       id:
         codec: uint8
       name:
         codec: string
         max_length: 12

The input value for ``vehicle`` is a nested dictionary:

.. code-block:: python

   {
       "vehicle": {
           "id": 7,
           "name": "remus",
       },
   }

Nested ROS Messages
-------------------

Use ``msg`` when a field contains a nested ROS message.

.. code-block:: yaml

   position:
     codec: msg
     ros_type: geometry_msgs/Point
     fields:
       x:
         codec: float32
       y:
         codec: float32
       z:
         codec: float32

``ros_type`` may use ROS 1 style names such as ``geometry_msgs/Point`` or ROS
2 introspection names such as ``geometry_msgs/msg/Point``.  ``ltcodecs``
normalizes ROS 2 names internally.

Variable-Length Arrays
----------------------

Use ``variable_len_array`` for a list whose encoded length is included in the
bit stream.

.. code-block:: yaml

   samples:
     codec: variable_len_array
     max_length: 10
     element_type: uint8

``max_length`` is required.  Values longer than ``max_length`` are currently
truncated during encoding.

For element codecs that need parameters, put those parameters under
``element_params``:

.. code-block:: yaml

   labels:
     codec: variable_len_array
     max_length: 4
     element_type: string
     element_params:
       max_length: 12

Set ``nullable: true`` when an empty list should encode as a single bit.  When
``nullable`` is false, the array length is always encoded.

.. code-block:: yaml

   optional_samples:
     codec: variable_len_array
     max_length: 10
     nullable: true
     element_type: uint8

Fixed-Length Arrays
-------------------

Use ``fixed_len_array`` when the number of elements is known from the config.

.. code-block:: yaml

   rgb:
     codec: fixed_len_array
     length: 3
     element_type: uint8

The encoded stream does not include a length field.  The decoder always reads
``length`` elements.

Arrays of Messages
------------------

Array elements can be nested dictionaries or ROS messages.  Use
``element_type: msg`` for ROS message elements and pass the message parameters
with ``element_params``.

.. code-block:: yaml

   points:
     codec: variable_len_array
     max_length: 5
     element_type: msg
     element_params:
       ros_type: geometry_msgs/Point
       fields:
         x:
           codec: float32
         y:
           codec: float32
         z:
           codec: float32

Optional Fields
---------------

Use ``optional`` when a boolean field controls whether additional fields are
present.  The controlling field itself encodes as one bit.  If it is true, each
field in ``target_fields`` is encoded immediately after it.  If false, the
target fields use no bits.

.. code-block:: yaml

   has_extra:
     codec: optional
     target_fields:
       extra:
         codec: uint8

The input dictionary or ROS message must contain both ``has_extra`` and
``extra`` when ``has_extra`` is true.

Padding
-------

Use ``pad`` or ``padding`` to reserve a fixed number of zero bits.

.. code-block:: yaml

   reserved:
     codec: padding
     num_bits: 8

Padding decodes to ``None``.

Metadata Fields
---------------

Some codec aliases represent metadata rather than payload bits.  These fields
are copied to or from packet metadata and are not encoded like ordinary fields.

This functionality is used by ``ros_acomms``, so the currently implemented metadata fields
are all related to encoding or decoding acoustic packet data.

Available metadata decoder aliases include ``src``, ``dest``,
``dest_decoder``, ``toa``, and ``snr_in``.  The metadata encoder alias
currently used by top-level codecs is ``dest``.

.. code-block:: yaml

   dest:
     codec: dest
   data:
     codec: string
     max_length: 20

When encoding, the top-level codec returns the metadata dictionary as the
second value from ``encode()``.

Checksums
---------

Checksums are configured on the top-level codec object, not inside each field.
Both :py:class:`ltcodecs.dict_codec.DictCodec` and
:py:class:`ltcodecs.ros_message_codec.RosMessageCodec` support ``crc8`` and
``crc32``.

.. code-block:: python

   codec = ltcodecs.DictCodec.from_codec_file(
       "message_codec.yaml",
       checksum="crc32",
   )

The checksum is appended after the encoded payload.  During decoding, a
mismatch raises ``ValueError``.

Complete Example
----------------

.. code-block:: yaml

   name:
     codec: string
     max_length: 10
   count:
     codec: integer
     min_value: 0
     max_value: 100
   enabled:
     codec: bool
   nested:
     codec: dict
     fields:
       value:
         codec: uint8
   has_extra:
     codec: optional
     target_fields:
       extra:
         codec: uint8

This config can be loaded with
:py:meth:`ltcodecs.dict_codec.DictCodec.from_codec_file` and used to encode a
dictionary with the same field names.
