Quick Start
===========

``ltcodecs`` encodes structured Python dictionaries and ROS messages into
compact bit streams.  A codec is configured with a field dictionary: each field
name maps to a codec alias and the parameters for that field codec.

Install
-------

Install the package from PyPI:

.. code-block:: console

   pip install ltcodecs

For local development, install the checkout in editable mode:

.. code-block:: console

   pip install -e .

Encode a Dictionary
-------------------

The quickest way to use ``ltcodecs`` is with
:py:class:`ltcodecs.dict_codec.DictCodec`.  This path does not require ROS.

.. code-block:: python

   from bitstring import ConstBitStream
   import ltcodecs

   fields = {
       "name": {
           "codec": "string",
           "max_length": 10,
       },
       "count": {
           "codec": "integer",
           "min_value": 0,
           "max_value": 100,
       },
       "enabled": {
           "codec": "bool",
       },
   }

   message = {
       "name": "test",
       "count": 21,
       "enabled": True,
   }

   codec = ltcodecs.DictCodec(fields)
   bits, metadata = codec.encode(message)

   decoded = codec.decode(ConstBitStream(bits))
   assert decoded == message

The ``bits`` value is a ``bitstring.Bits`` object.  The ``metadata`` return
value is usually ``None`` unless the field configuration includes one of the
metadata encoder aliases.

Encode Metadata
---------------

``encode()`` for the Message/Dictionary codecs returns a tuple of ``(bits, metadata)``.
The first value is the encoded bit stream.
The second value is a small metadata dictionary for values
that should travel outside the compressed payload, such as packet routing
fields.  Most codecs return ``None`` for metadata, and you can ignore it.
It is possible to define fields that
use metadata encoders rather than (or in addition to) adding bits to the output stream.

Use YAML Configuration
----------------------

Codec configurations are commonly stored as YAML.  The same dictionary codec
above can be written as:

.. code-block:: yaml

   name:
     codec: string
     max_length: 10
   count:
     codec: integer
     min_value: 0
     max_value: 100
   enabled:
     codec: bool

Load the YAML file with
:py:meth:`ltcodecs.dict_codec.DictCodec.from_codec_file`:

.. code-block:: python

   from bitstring import ConstBitStream
   import ltcodecs

   codec = ltcodecs.DictCodec.from_codec_file("message_codec.yaml")

   bits, _metadata = codec.encode({
       "name": "test",
       "count": 21,
       "enabled": True,
   })

   decoded = codec.decode(ConstBitStream(bits))

Nested Dictionaries and Optional Fields
---------------------------------------

A field can itself be a dictionary.  Use the ``dict`` codec and provide a
nested ``fields`` mapping:

.. code-block:: yaml

   nested:
     codec: dict
     fields:
       value:
         codec: uint8

Optional fields are controlled by a boolean field.  If the controlling value is
true, the target fields are encoded.  If false, those fields use no bits.

.. code-block:: yaml

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

The input dictionary for that configuration would include both the controlling
field and the target field:

.. code-block:: python

   message = {
       "has_extra": True,
       "extra": 42,
   }

Encode a ROS Message
--------------------

Use :py:class:`ltcodecs.ros_message_codec.RosMessageCodec` for ROS messages.
If no field dictionary is provided, ``ltcodecs`` asks ARMW for the message
fields and chooses codecs from the ROS field types.

.. code-block:: python

   from bitstring import ConstBitStream
   import armw
   import ltcodecs

   String = armw.import_message("std_msgs", "String")

   msg = String()
   msg.data = "hello"

   codec = ltcodecs.RosMessageCodec("std_msgs/String")
   bits, metadata = codec.encode(msg)

   decoded = codec.decode(ConstBitStream(bits))
   assert decoded.data == "hello"

You can also provide a YAML field configuration for a ROS message:

.. code-block:: yaml

   data:
     codec: string
     max_length: 10

.. code-block:: python

   codec = ltcodecs.RosMessageCodec.from_codec_file(
       "std_msgs/String",
       "string_msg_codec.yaml",
   )

ROS 1 and ROS 2 Type Names
--------------------------

ROS message fields may use ROS 1 style type names such as ``pkg/Msg`` and
``pkg/Msg[]`` or ROS 2 introspection names such as ``pkg/msg/Msg`` and
``sequence<pkg/msg/Msg>``.  ``ltcodecs`` normalizes these forms when it infers
fields from a ROS message or when an explicit field config contains a ROS type.

For arrays, use the array codecs directly when writing explicit configs:

.. code-block:: yaml

   samples:
     codec: variable_len_array
     max_length: 10
     element_type: uint8

   points:
     codec: fixed_len_array
     length: 3
     element_type: msg
     element_params:
       ros_type: geometry_msgs/Point

Checksums
---------

Top-level dictionary and ROS message codecs can append a checksum.  Supported
values are ``crc8`` and ``crc32``.

.. code-block:: python

   codec = ltcodecs.DictCodec(fields, checksum="crc8")
   bits, _metadata = codec.encode(message)
   decoded = codec.decode(ConstBitStream(bits))

If the received checksum does not match the encoded content, ``decode`` raises
``ValueError``.

Next Steps
----------

See :doc:`reference/codecs` for the codec reference,
:doc:`reference/codec_aliases` for the full alias table, and
:doc:`reference/python_api` for the generated API reference.
