Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 1 | .. _module-pw_hdlc: |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 2 | |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 3 | ------- |
| 4 | pw_hdlc |
| 5 | ------- |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 6 | `High-Level Data Link Control (HDLC) |
| 7 | <https://en.wikipedia.org/wiki/High-Level_Data_Link_Control>`_ is a data link |
| 8 | layer protocol intended for serial communication between devices. HDLC is |
| 9 | standardized as `ISO/IEC 13239:2002 <https://www.iso.org/standard/37010.html>`_. |
shaneajg | 0869f2c | 2020-07-08 10:39:14 -0400 | [diff] [blame] | 10 | |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 11 | The ``pw_hdlc`` module provides a simple, robust frame-oriented transport that |
| 12 | uses a subset of the HDLC protocol. ``pw_hdlc`` supports sending between |
| 13 | embedded devices or the host. It can be used with :ref:`module-pw_rpc` to enable |
| 14 | remote procedure calls (RPCs) on embedded on devices. |
shaneajg | 0869f2c | 2020-07-08 10:39:14 -0400 | [diff] [blame] | 15 | |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 16 | **Why use the pw_hdlc module?** |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 17 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 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. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 23 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 24 | .. admonition:: Try it out! |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 25 | |
Wyatt Hepler | f9fb90f | 2020-09-30 18:59:33 -0700 | [diff] [blame] | 26 | For an example of how to use HDLC with :ref:`module-pw_rpc`, see the |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 27 | :ref:`module-pw_hdlc-rpc-example`. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 28 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 29 | .. toctree:: |
| 30 | :maxdepth: 1 |
| 31 | :hidden: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 32 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 33 | rpc_example/docs |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 34 | |
| 35 | Protocol Description |
| 36 | ==================== |
| 37 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 38 | Frames |
| 39 | ------ |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 40 | The HDLC implementation in ``pw_hdlc`` supports only HDLC unnumbered |
Alexei Frolov | 6053c31 | 2020-12-09 22:43:55 -0800 | [diff] [blame] | 41 | information frames. These frames are encoded as follows: |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 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 | |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 57 | Encoding and sending data |
| 58 | ------------------------- |
| 59 | This module first writes an initial frame delimiter byte (0x7E) to indicate the |
| 60 | beginning of the frame. Before sending any of the payload data through serial, |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 61 | the special bytes are escaped: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 62 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 63 | +-------------------------+-----------------------+ |
| 64 | | Unescaped Special Bytes | Escaped Special Bytes | |
| 65 | +=========================+=======================+ |
| 66 | | 7E | 7D 5E | |
| 67 | +-------------------------+-----------------------+ |
| 68 | | 7D | 7D 5D | |
| 69 | +-------------------------+-----------------------+ |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 70 | |
| 71 | The bytes of the payload are escaped and written in a single pass. The |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 72 | frame check sequence is calculated, escaped, and written after. After this, a |
| 73 | final frame delimiter byte (0x7E) is written to mark the end of the frame. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 74 | |
| 75 | Decoding received bytes |
| 76 | ----------------------- |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 77 | Frames may be received in multiple parts, so we need to store the received data |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 78 | in a buffer until the ending frame delimiter (0x7E) is read. When the |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 79 | ``pw_hdlc`` decoder receives data, it unescapes it and adds it to a buffer. |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 80 | When the frame is complete, it calculates and verifies the frame check sequence |
| 81 | and does the following: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 82 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 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. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 86 | |
| 87 | API Usage |
| 88 | ========= |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 89 | There are two primary functions of the ``pw_hdlc`` module: |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 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. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 95 | |
| 96 | Encoder |
| 97 | ------- |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 98 | The Encoder API provides a single function that encodes data as an HDLC |
Alexei Frolov | 6053c31 | 2020-12-09 22:43:55 -0800 | [diff] [blame] | 99 | unnumbered information frame. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 100 | |
| 101 | C++ |
| 102 | ^^^ |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 103 | .. cpp:namespace:: pw |
| 104 | |
Alexei Frolov | b9fda58 | 2021-03-13 18:02:52 -0800 | [diff] [blame] | 105 | .. cpp:function:: Status hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer& writer) |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 106 | |
Wyatt Hepler | f9fb90f | 2020-09-30 18:59:33 -0700 | [diff] [blame] | 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` |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 109 | module to compute the CRC-32 frame check sequence. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 110 | |
| 111 | .. code-block:: cpp |
| 112 | |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 113 | #include "pw_hdlc/encoder.h" |
| 114 | #include "pw_hdlc/sys_io_stream.h" |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 115 | |
| 116 | int main() { |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 117 | pw::stream::SysIoWriter serial_writer; |
Alexei Frolov | 6053c31 | 2020-12-09 22:43:55 -0800 | [diff] [blame] | 118 | Status status = WriteUIFrame(123 /* address */, |
| 119 | data, |
| 120 | serial_writer); |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 121 | if (!status.ok()) { |
| 122 | PW_LOG_INFO("Writing frame failed! %s", status.str()); |
| 123 | } |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 124 | } |
| 125 | |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 126 | Python |
| 127 | ^^^^^^ |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 128 | .. automodule:: pw_hdlc.encode |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 129 | :members: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 130 | |
| 131 | .. code-block:: python |
| 132 | |
| 133 | import serial |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 134 | from pw_hdlc import encode |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 135 | |
| 136 | ser = serial.Serial() |
Paul Mathieu | 94705a9 | 2021-05-13 15:07:41 -0700 | [diff] [blame] | 137 | address = 123 |
| 138 | ser.write(encode.ui_frame(address, b'your data here!')) |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 139 | |
Jared Weinstein | 7c658f6 | 2021-07-19 12:59:44 -0700 | [diff] [blame] | 140 | Typescript |
| 141 | ^^^^^^^^^^ |
| 142 | |
Paul Mathieu | 4779939 | 2021-09-14 10:40:50 -0700 | [diff] [blame] | 143 | Encoder |
| 144 | ------- |
| 145 | The Encoder class provides a way to build complete, escaped HDLC UI frames. |
| 146 | |
| 147 | .. js:method:: Encoder.uiFrame(address, data) |
Jared Weinstein | 7c658f6 | 2021-07-19 12:59:44 -0700 | [diff] [blame] | 148 | |
| 149 | :param number address: frame address. |
| 150 | :param Uint8Array data: frame data. |
| 151 | :returns: Uint8Array containing a complete HDLC frame. |
| 152 | |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 153 | Decoder |
| 154 | ------- |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 155 | The decoder class unescapes received bytes and adds them to a buffer. Complete, |
| 156 | valid HDLC frames are yielded as they are received. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 157 | |
Paul Mathieu | 4779939 | 2021-09-14 10:40:50 -0700 | [diff] [blame] | 158 | .. js:method:: Decoder.process(bytes) |
| 159 | |
| 160 | :param Uint8Array bytes: bytes received from the medium. |
| 161 | :yields: Frame complete frames. |
| 162 | |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 163 | C++ |
| 164 | ^^^ |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 165 | .. cpp:class:: pw::hdlc::Decoder |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 166 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 167 | .. cpp:function:: pw::Result<Frame> Process(std::byte b) |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 168 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 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 | |
| 185 | This example demonstrates reading individual bytes from ``pw::sys_io`` and |
| 186 | decoding HDLC frames: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 187 | |
| 188 | .. code-block:: cpp |
| 189 | |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 190 | #include "pw_hdlc/decoder.h" |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 191 | #include "pw_sys_io/sys_io.h" |
| 192 | |
| 193 | int main() { |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 194 | std::byte data; |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 195 | while (true) { |
| 196 | if (!pw::sys_io::ReadByte(&data).ok()) { |
| 197 | // Log serial reading error |
| 198 | } |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 199 | Result<Frame> decoded_frame = decoder.Process(data); |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 200 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 201 | if (decoded_frame.ok()) { |
| 202 | // Handle the decoded frame |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 203 | } |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | Python |
| 208 | ^^^^^^ |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 209 | .. autoclass:: pw_hdlc.decode.FrameDecoder |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 210 | :members: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 211 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 212 | Below is an example using the decoder class to decode data read from serial: |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 213 | |
| 214 | .. code-block:: python |
| 215 | |
| 216 | import serial |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 217 | from pw_hdlc import decode |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 218 | |
| 219 | ser = serial.Serial() |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 220 | decoder = decode.FrameDecoder() |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 221 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 222 | while True: |
| 223 | for frame in decoder.process_valid_frames(ser.read()): |
| 224 | # Handle the decoded frame |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 225 | |
Jared Weinstein | 2395810 | 2021-07-29 12:46:12 -0700 | [diff] [blame] | 226 | Typescript |
| 227 | ^^^^^^^^^^ |
| 228 | |
| 229 | Decodes 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 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 242 | Additional features |
| 243 | =================== |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 244 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 245 | pw::stream::SysIoWriter |
shaneajg | 0869f2c | 2020-07-08 10:39:14 -0400 | [diff] [blame] | 246 | ------------------------ |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 247 | The ``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 |
| 249 | over serial. |
shaneajg | 0869f2c | 2020-07-08 10:39:14 -0400 | [diff] [blame] | 250 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 251 | HdlcRpcClient |
| 252 | ------------- |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 253 | .. autoclass:: pw_hdlc.rpc.HdlcRpcClient |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 254 | :members: |
shaneajg | 0869f2c | 2020-07-08 10:39:14 -0400 | [diff] [blame] | 255 | |
Wyatt Hepler | e04d468 | 2021-07-21 08:52:28 -0700 | [diff] [blame] | 256 | .. autoclass:: pw_hdlc.rpc.HdlcRpcLocalServerAndClient |
| 257 | :members: |
| 258 | |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 259 | Roadmap |
| 260 | ======= |
Alexei Frolov | d3e5cb7 | 2021-01-08 13:08:45 -0800 | [diff] [blame] | 261 | - **Expanded protocol support** - ``pw_hdlc`` currently only supports |
Alexei Frolov | b9fda58 | 2021-03-13 18:02:52 -0800 | [diff] [blame] | 262 | unnumbered information frames. Support for different frame types and |
| 263 | extended control fields may be added in the future. |
shaneajg | f8325f9 | 2020-08-05 12:35:10 -0400 | [diff] [blame] | 264 | |
| 265 | - **Higher performance** - We plan to improve the overall performance of the |
| 266 | decoder and encoder implementations by using SIMD/NEON. |
Wyatt Hepler | 455b492 | 2020-09-18 00:19:21 -0700 | [diff] [blame] | 267 | |
| 268 | Compatibility |
| 269 | ============= |
| 270 | C++17 |
Yuval Peress | b8f3ad2 | 2021-10-26 22:55:27 -0600 | [diff] [blame] | 271 | |
| 272 | Zephyr |
| 273 | ====== |
| 274 | To enable ``pw_hdlc`` for Zephyr add ``CONFIG_PIGWEED_HDLC=y`` to the project's |
| 275 | configuration. |