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 address = 123 138 ser.write(encode.ui_frame(address, b'your data here!')) 139 140Typescript 141^^^^^^^^^^ 142 143Encoder 144------- 145The Encoder class provides a way to build complete, escaped HDLC UI frames. 146 147.. js:method:: Encoder.uiFrame(address, data) 148 149 :param number address: frame address. 150 :param Uint8Array data: frame data. 151 :returns: Uint8Array containing a complete HDLC frame. 152 153Decoder 154------- 155The decoder class unescapes received bytes and adds them to a buffer. Complete, 156valid HDLC frames are yielded as they are received. 157 158.. js:method:: Decoder.process(bytes) 159 160 :param Uint8Array bytes: bytes received from the medium. 161 :yields: Frame complete frames. 162 163C++ 164^^^ 165.. cpp:class:: pw::hdlc::Decoder 166 167 .. cpp:function:: pw::Result<Frame> Process(std::byte b) 168 169 Parses a single byte of an HDLC stream. Returns a Result with the complete 170 frame if the byte completes a frame. The status is the following: 171 172 - OK - A frame was successfully decoded. The Result contains the Frame, 173 which is invalidated by the next Process call. 174 - UNAVAILABLE - No frame is available. 175 - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in 176 the decoder's buffer. 177 - DATA_LOSS - A frame completed, but it was invalid. The frame was 178 incomplete or the frame check sequence verification failed. 179 180 .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args) 181 182 Processes a span of data and calls the provided callback with each frame or 183 error. 184 185This example demonstrates reading individual bytes from ``pw::sys_io`` and 186decoding HDLC frames: 187 188.. code-block:: cpp 189 190 #include "pw_hdlc/decoder.h" 191 #include "pw_sys_io/sys_io.h" 192 193 int main() { 194 std::byte data; 195 while (true) { 196 if (!pw::sys_io::ReadByte(&data).ok()) { 197 // Log serial reading error 198 } 199 Result<Frame> decoded_frame = decoder.Process(data); 200 201 if (decoded_frame.ok()) { 202 // Handle the decoded frame 203 } 204 } 205 } 206 207Python 208^^^^^^ 209.. autoclass:: pw_hdlc.decode.FrameDecoder 210 :members: 211 212Below is an example using the decoder class to decode data read from serial: 213 214.. code-block:: python 215 216 import serial 217 from pw_hdlc import decode 218 219 ser = serial.Serial() 220 decoder = decode.FrameDecoder() 221 222 while True: 223 for frame in decoder.process_valid_frames(ser.read()): 224 # Handle the decoded frame 225 226Typescript 227^^^^^^^^^^ 228 229Decodes one or more HDLC frames from a stream of data. 230 231.. js:method:: process(data) 232 233 :param Uint8Array data: bytes to be decoded. 234 :yields: HDLC frames, including corrupt frames. 235 The Frame.ok() method whether the frame is valid. 236 237.. js:method:: processValidFrames(data) 238 239 :param Uint8Array data: bytes to be decoded. 240 :yields: Valid HDLC frames, logging any errors. 241 242Additional features 243=================== 244 245pw::stream::SysIoWriter 246------------------------ 247The ``SysIoWriter`` C++ class implements the ``Writer`` interface with 248``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames 249over serial. 250 251HdlcRpcClient 252------------- 253.. autoclass:: pw_hdlc.rpc.HdlcRpcClient 254 :members: 255 256.. autoclass:: pw_hdlc.rpc.HdlcRpcLocalServerAndClient 257 :members: 258 259Roadmap 260======= 261- **Expanded protocol support** - ``pw_hdlc`` currently only supports 262 unnumbered information frames. Support for different frame types and 263 extended control fields may be added in the future. 264 265- **Higher performance** - We plan to improve the overall performance of the 266 decoder and encoder implementations by using SIMD/NEON. 267 268Compatibility 269============= 270C++17 271 272Zephyr 273====== 274To enable ``pw_hdlc`` for Zephyr add ``CONFIG_PIGWEED_HDLC=y`` to the project's 275configuration. 276