.. _module-pw_hdlc-api:

=============
API reference
=============
.. pigweed-module-subpage::
   :name: pw_hdlc

The ``pw_hdlc`` API has 3 conceptual parts:

* :ref:`module-pw_hdlc-api-encoder`: Encode data as HDLC unnumbered
  information frames.
* :ref:`module-pw_hdlc-api-decoder`: Decode HDLC frames from a stream of data.
* :ref:`module-pw_hdlc-api-rpc`: Use RPC over HDLC.

.. _module-pw_hdlc-api-encoder:

-------
Encoder
-------

Single-Function Encoding
========================
Pigweed offers a single function which will encode an HDLC frame in each of
C++, Python, and TypeScript:

.. tab-set::

   .. tab-item:: C++
      :sync: cpp

      :doxylink:`pw::hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer &writer)`

      Example:

      .. code-block:: cpp

         // Writes a span of data to a pw::stream::Writer and returns the status. This
         // implementation uses the pw_checksum module to compute the CRC-32 frame check
         // sequence.

         #include "pw_hdlc/encoder.h"
         #include "pw_hdlc/sys_io_stream.h"

         int main() {
           pw::stream::SysIoWriter serial_writer;
           Status status = WriteUIFrame(123 /* address */, data, serial_writer);
           if (!status.ok()) {
             PW_LOG_INFO("Writing frame failed! %s", status.str());
           }
         }

   .. tab-item:: Python
      :sync: py

      .. automodule:: pw_hdlc.encode
         :members:
         :noindex:

      Example:

      .. code-block:: python

         # Read bytes from serial and encode HDLC frames

         import serial
         from pw_hdlc import encode

         ser = serial.Serial()
         address = 123
         ser.write(encode.ui_frame(address, b'your data here!'))

   .. tab-item:: TypeScript
      :sync: ts

      ``Encoder`` provides a way to build complete, escaped HDLC unnumbered
      information frames.

      .. js:method:: Encoder.uiFrame(address, data)
         :noindex:

         :param number address: frame address.
         :param Uint8Array data: frame data.
         :returns: ``Uint8Array`` containing a complete HDLC frame.

Piecemeal Encoding
==================

Additionally, the C++ API provides a :doxylink:`pw::hdlc::Encoder` class for
piecemeal encoding of an HDLC frame. This allows frames to be encoded gradually
without ever holding an entire frame in memory at once.

.. _module-pw_hdlc-api-decoder:

-------
Decoder
-------
.. tab-set::

   .. tab-item:: C++
      :sync: cpp

      :doxylink:`pw::hdlc::Decoder`

      Example:

      .. code-block:: cpp

         // Read individual bytes from pw::sys_io and decode HDLC frames.

         #include "pw_hdlc/decoder.h"
         #include "pw_sys_io/sys_io.h"

         int main() {
           std::byte data;
           while (true) {
             if (!pw::sys_io::ReadByte(&data).ok()) {
               // Log serial reading error
             }
             Result<Frame> decoded_frame = decoder.Process(data);

             if (decoded_frame.ok()) {
               // Handle the decoded frame
             }
           }
         }

   .. tab-item:: Python
      :sync: py

      .. autoclass:: pw_hdlc.decode.FrameDecoder
         :members:
         :noindex:

      Example:

      .. code-block:: python

         # Decode data read from serial

         import serial
         from pw_hdlc import decode

         ser = serial.Serial()
         decoder = decode.FrameDecoder()

         while True:
             for frame in decoder.process_valid_frames(ser.read()):
                 # Handle the decoded frame

      It is possible to decode HDLC frames from a stream using different protocols or
      unstructured data. This is not recommended, but may be necessary when
      introducing HDLC to an existing system.

      The ``FrameAndNonFrameDecoder`` Python class supports working with raw data and
      HDLC frames in the same stream.

      .. autoclass:: pw_hdlc.decode.FrameAndNonFrameDecoder
        :members:
        :noindex:

   .. tab-item:: TypeScript
      :sync: ts

      ``Decoder`` unescapes received bytes and adds them to a buffer. Complete,
      valid HDLC frames are yielded as they are received.

      .. js:method:: Decoder.process(data)
         :noindex:

         :param Uint8Array data: bytes to be decoded.
         :yields: HDLC frames, including corrupt frames.
                  The ``Frame.ok()`` method whether the frame is valid.

      .. js:method:: processValidFrames(data)
         :noindex:

         :param Uint8Array data: bytes to be decoded.
         :yields: Valid HDLC frames, logging any errors.

.. _module-pw_hdlc-api-rpc:

---
RPC
---

.. tab-set::

   .. tab-item:: C++
      :sync: cpp

      ``RpcChannelOutput`` implements the ``pw::rpc::ChannelOutput`` interface
      of ``pw_rpc``, simplifying the process of creating an RPC channel over HDLC.
      A ``pw::stream::Writer`` must be provided as the underlying transport
      implementation.

      If your HDLC routing path has a Maximum Transmission Unit (MTU) limitation,
      use the ``FixedMtuChannelOutput`` to verify that the currently configured
      max RPC payload size (dictated by the static encode buffer of ``pw_rpc``)
      will always fit safely within the limits of the fixed HDLC MTU *after*
      HDLC encoding.

   .. tab-item:: Python
      :sync: py

      The ``pw_hdlc`` Python package includes utilities to HDLC-encode and
      decode RPC packets, with examples of RPC client implementations in Python.
      It also provides abstractions for interfaces used to receive RPC Packets.

      The ``pw_hdlc.rpc.CancellableReader`` and ``pw_hdlc.rpc.RpcClient``
      classes and derived classes are context-managed to cleanly cancel the
      read process and stop the reader thread. The ``pw_hdlc.rpc.SocketReader``
      and ``pw_hdlc.rpc.SerialReader`` also close the provided interface on
      context exit. It is recommended to use these in a context statement. For
      example:

      .. code-block:: python

         import serial
         from pw_hdlc import rpc
         from pw_rpc import client_utils

         if __name__ == '__main__':
             serial_device = serial.Serial('/dev/ttyACM0')
             with client_utils.SerialReader(serial_device) as reader:
                 with rpc.HdlcRpcClient(
                     reader,
                     [],
                     rpc.default_channels(serial_device.write)) as rpc_client:
                     # Do something with rpc_client.

             # The serial_device object is closed, and reader thread stopped.
             return 0

      .. autoclass:: pw_hdlc.rpc.channel_output
         :members:
         :noindex:

      .. autoclass:: pw_hdlc.rpc.default_channels
         :members:
         :noindex:

      .. autoclass:: pw_hdlc.rpc.HdlcRpcClient
         :members:
         :noindex:

      .. autoclass:: pw_hdlc.rpc.HdlcRpcLocalServerAndClient
         :members:
         :noindex:

   .. tab-item:: TypeScript
      :sync: ts

      The TypeScript library doesn't have an RPC interface.

-----------------
More pw_hdlc docs
-----------------
.. include:: docs.rst
   :start-after: .. pw_hdlc-nav-start
   :end-before: .. pw_hdlc-nav-end
