1.. _module-pw_hdlc:
2
3-------
4pw_hdlc
5-------
6`High-Level Data Link Control (HDLC)
7<https://en.wikipedia.org/wiki/High-Level_Data_Link_Control>`_ is a data link
8layer protocol intended for serial communication between devices. HDLC is
9standardized as `ISO/IEC 13239:2002 <https://www.iso.org/standard/37010.html>`_.
10
11The ``pw_hdlc`` module provides a simple, robust frame-oriented transport that
12uses a subset of the HDLC protocol. ``pw_hdlc`` supports sending between
13embedded devices or the host. It can be used with :ref:`module-pw_rpc` to enable
14remote procedure calls (RPCs) on embedded on devices.
15
16**Why use the pw_hdlc module?**
17
18  * Enables the transmission of RPCs and other data between devices over serial.
19  * Detects corruption and data loss.
20  * Light-weight, simple, and easy to use.
21  * Supports streaming to transport without buffering, since the length is not
22    encoded.
23
24.. admonition:: Try it out!
25
26  For an example of how to use HDLC with :ref:`module-pw_rpc`, see the
27  :ref:`module-pw_hdlc-rpc-example`.
28
29.. toctree::
30  :maxdepth: 1
31  :hidden:
32
33  rpc_example/docs
34
35Protocol Description
36====================
37
38Frames
39------
40The HDLC implementation in ``pw_hdlc`` supports only HDLC unnumbered
41information frames. These frames are encoded as follows:
42
43.. code-block:: text
44
45    _________________________________________
46    | | | |                          |    | |...
47    | | | |                          |    | |... [More frames]
48    |_|_|_|__________________________|____|_|...
49     F A C       Payload              FCS  F
50
51     F = flag byte (0x7e, the ~ character)
52     A = address field
53     C = control field
54     FCS = frame check sequence (CRC-32)
55
56
57Encoding and sending data
58-------------------------
59This module first writes an initial frame delimiter byte (0x7E) to indicate the
60beginning of the frame. Before sending any of the payload data through serial,
61the special bytes are escaped:
62
63            +-------------------------+-----------------------+
64            | Unescaped Special Bytes | Escaped Special Bytes |
65            +=========================+=======================+
66            |           7E            |        7D 5E          |
67            +-------------------------+-----------------------+
68            |           7D            |        7D 5D          |
69            +-------------------------+-----------------------+
70
71The bytes of the payload are escaped and written in a single pass. The
72frame check sequence is calculated, escaped, and written after. After this, a
73final frame delimiter byte (0x7E) is written to mark the end of the frame.
74
75Decoding received bytes
76-----------------------
77Frames may be received in multiple parts, so we need to store the received data
78in a buffer until the ending frame delimiter (0x7E) is read. When the
79``pw_hdlc`` decoder receives data, it unescapes it and adds it to a buffer.
80When the frame is complete, it calculates and verifies the frame check sequence
81and does the following:
82
83* If correctly verified, the decoder returns the decoded frame.
84* If the checksum verification fails, the frame is discarded and an error is
85  reported.
86
87API Usage
88=========
89There are two primary functions of the ``pw_hdlc`` module:
90
91  * **Encoding** data by constructing a frame with the escaped payload bytes and
92    frame check sequence.
93  * **Decoding** data by unescaping the received bytes, verifying the frame
94    check sequence, and returning successfully decoded frames.
95
96Encoder
97-------
98The Encoder API provides a single function that encodes data as an HDLC
99unnumbered information frame.
100
101C++
102^^^
103.. cpp:namespace:: pw
104
105.. cpp:function:: Status hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer& writer)
106
107  Writes a span of data to a :ref:`pw::stream::Writer <module-pw_stream>` and
108  returns the status. This implementation uses the :ref:`module-pw_checksum`
109  module to compute the CRC-32 frame check sequence.
110
111.. code-block:: cpp
112
113  #include "pw_hdlc/encoder.h"
114  #include "pw_hdlc/sys_io_stream.h"
115
116  int main() {
117    pw::stream::SysIoWriter serial_writer;
118    Status status = WriteUIFrame(123 /* address */,
119                                 data,
120                                 serial_writer);
121    if (!status.ok()) {
122      PW_LOG_INFO("Writing frame failed! %s", status.str());
123    }
124  }
125
126Python
127^^^^^^
128.. automodule:: pw_hdlc.encode
129  :members:
130
131.. code-block:: python
132
133  import serial
134  from pw_hdlc import encode
135
136  ser = serial.Serial()
137  ser.write(encode.ui_frame(b'your data here!'))
138
139Decoder
140-------
141The decoder class unescapes received bytes and adds them to a buffer. Complete,
142valid HDLC frames are yielded as they are received.
143
144C++
145^^^
146.. cpp:class:: pw::hdlc::Decoder
147
148  .. cpp:function:: pw::Result<Frame> Process(std::byte b)
149
150    Parses a single byte of an HDLC stream. Returns a Result with the complete
151    frame if the byte completes a frame. The status is the following:
152
153      - OK - A frame was successfully decoded. The Result contains the Frame,
154        which is invalidated by the next Process call.
155      - UNAVAILABLE - No frame is available.
156      - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
157        the decoder's buffer.
158      - DATA_LOSS - A frame completed, but it was invalid. The frame was
159        incomplete or the frame check sequence verification failed.
160
161  .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args)
162
163    Processes a span of data and calls the provided callback with each frame or
164    error.
165
166This example demonstrates reading individual bytes from ``pw::sys_io`` and
167decoding HDLC frames:
168
169.. code-block:: cpp
170
171  #include "pw_hdlc/decoder.h"
172  #include "pw_sys_io/sys_io.h"
173
174  int main() {
175    std::byte data;
176    while (true) {
177      if (!pw::sys_io::ReadByte(&data).ok()) {
178        // Log serial reading error
179      }
180      Result<Frame> decoded_frame = decoder.Process(data);
181
182      if (decoded_frame.ok()) {
183        // Handle the decoded frame
184      }
185    }
186  }
187
188Python
189^^^^^^
190.. autoclass:: pw_hdlc.decode.FrameDecoder
191  :members:
192
193Below is an example using the decoder class to decode data read from serial:
194
195.. code-block:: python
196
197  import serial
198  from pw_hdlc import decode
199
200  ser = serial.Serial()
201  decoder = decode.FrameDecoder()
202
203  while True:
204      for frame in decoder.process_valid_frames(ser.read()):
205          # Handle the decoded frame
206
207Additional features
208===================
209
210pw::stream::SysIoWriter
211------------------------
212The ``SysIoWriter`` C++ class implements the ``Writer`` interface with
213``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames
214over serial.
215
216HdlcRpcClient
217-------------
218.. autoclass:: pw_hdlc.rpc.HdlcRpcClient
219  :members:
220
221Roadmap
222=======
223- **Expanded protocol support** - ``pw_hdlc`` currently only supports
224  unnumbered information frames. Support for different frame types and
225  extended control fields may be added in the future.
226
227- **Higher performance** - We plan to improve the overall performance of the
228  decoder and encoder implementations by using SIMD/NEON.
229
230Compatibility
231=============
232C++17
233