1
+ #pragma once
2
+
3
+ #include < stdbool.h>
4
+ #include < stdint.h>
5
+
6
+ #include " AudioTools/CoreAudio/Buffers.h"
7
+ #include " AudioToolsConfig.h"
8
+
9
+ namespace audio_tools {
10
+
11
+ /* *
12
+ * @brief High-Level Data Link Control (HDLC) is a bit-oriented code-transparent
13
+ * synchronous data link layer protocol for reliable, framed, and error-checked
14
+ * communication.
15
+ *
16
+ * This class implements HDLC framing with:
17
+ * - Frame delimiter (0x7E) for marking frame boundaries
18
+ * - Byte stuffing to escape special characters in the data
19
+ * - 16-bit CRC-CCITT for error detection
20
+ * - Transparent Stream interface for easy integration
21
+ *
22
+ * @ingroup communications
23
+ * @author Phil Schatzmann
24
+ */
25
+
26
+ class HDLCStream : public Stream {
27
+ public:
28
+ /* *
29
+ * @brief Construct a new HDLCStream object using a Stream for input and output
30
+ *
31
+ * @param stream The underlying Stream for both reading and writing
32
+ * @param maxFrameSize Maximum size of a single HDLC frame
33
+ */
34
+ HDLCStream (Stream& stream, size_t maxFrameSize)
35
+ : _maxFrameSize(maxFrameSize) {
36
+ p_stream = &stream;
37
+ p_print = &stream;
38
+ tx_frame_buffer.resize (maxFrameSize);
39
+ rx_frame_buffer.resize (maxFrameSize);
40
+ _rxBuffer.resize (maxFrameSize);
41
+ }
42
+
43
+ /* *
44
+ * @brief Construct a new HDLCStream object using a Print for output only
45
+ *
46
+ * @param stream The underlying Print for writing
47
+ * @param maxFrameSize Maximum size of a single HDLC frame
48
+ */
49
+ HDLCStream (Print& stream, size_t maxFrameSize) : _maxFrameSize(maxFrameSize) {
50
+ p_print = &stream;
51
+ tx_frame_buffer.resize (maxFrameSize);
52
+ rx_frame_buffer.resize (maxFrameSize);
53
+ _rxBuffer.resize (maxFrameSize);
54
+ }
55
+
56
+ /* *
57
+ * @brief Get the number of bytes available to read from the frame buffer
58
+ *
59
+ * @return int Number of bytes available
60
+ */
61
+ int available () override {
62
+ if (rx_frame_buffer.available () == 0 ) _processInput ();
63
+ return rx_frame_buffer.available ();
64
+ }
65
+
66
+ /* *
67
+ * @brief Not supported
68
+ *
69
+ * @return -1
70
+ */
71
+ int read () override { return -1 ; }
72
+
73
+ /* *
74
+ * @brief Read a full frame from the stream into a buffer
75
+ *
76
+ * @param buffer Destination buffer to hold the data
77
+ * @param length Maximum number of bytes to read
78
+ * @return size_t Actual number of bytes read
79
+ */
80
+ size_t readBytes (uint8_t * buffer, size_t length) override {
81
+ size_t available_bytes = rx_frame_buffer.available ();
82
+ // get more data
83
+ if (available_bytes == 0 ) {
84
+ _processInput ();
85
+ available_bytes = rx_frame_buffer.available ();
86
+ }
87
+
88
+ // check that we consume the full frame
89
+ if (length < available_bytes) {
90
+ LOGE (" readBytes len too small %u instead of %u" , (unsigned )length,
91
+ (unsigned )available_bytes);
92
+ return 0 ;
93
+ }
94
+
95
+ // provide the data
96
+ memcpy (buffer, rx_frame_buffer.data (), available_bytes);
97
+ rx_frame_buffer.clear ();
98
+ _frameReady = false ;
99
+ return available_bytes;
100
+ }
101
+
102
+ /* *
103
+ * @brief Not supported
104
+ *
105
+ * @return -1
106
+ */
107
+ int peek () override { return -1 ; }
108
+
109
+ /* *
110
+ * @brief Flush the output buffer of the underlying stream
111
+ */
112
+ void flush () override { p_stream->flush (); }
113
+
114
+ /* *
115
+ * @brief Not supported
116
+ *
117
+ * @param b The byte to write
118
+ * @return 0
119
+ */
120
+ size_t write (uint8_t b) override { return 0 ; }
121
+
122
+ /* *
123
+ * @brief Write multiple bytes to the stream
124
+ *
125
+ * @param data Pointer to the data buffer
126
+ * @param len Number of bytes to write
127
+ * @return size_t Number of bytes written
128
+ */
129
+ size_t write (const uint8_t * data, size_t len) override {
130
+ return writeFrame (data, len);
131
+ }
132
+
133
+ protected:
134
+ Stream* p_stream = nullptr ;
135
+ Print* p_print = nullptr ;
136
+ const size_t _maxFrameSize;
137
+ SingleBuffer<uint8_t > tx_frame_buffer;
138
+ SingleBuffer<uint8_t > rx_frame_buffer;
139
+ Vector<uint8_t > _rxBuffer;
140
+ size_t _frameLen = 0 ;
141
+ size_t _rxLen = 0 ;
142
+ size_t _rxPos = 0 ;
143
+ bool _frameReady = false ;
144
+
145
+ enum RxState { IDLE, RECEIVING, ESCAPED } _rxState = IDLE;
146
+
147
+ static constexpr uint8_t HDLC_FLAG = 0x7E ;
148
+ static constexpr uint8_t HDLC_ESC = 0x7D ;
149
+ static constexpr uint8_t HDLC_ESC_XOR = 0x20 ;
150
+
151
+ /* *
152
+ * @brief Calculate CRC-CCITT (16-bit)
153
+ *
154
+ * @param data Byte to include in CRC calculation
155
+ * @param crc Current CRC value
156
+ * @return uint16_t Updated CRC value
157
+ */
158
+ uint16_t _crc16 (uint8_t data, uint16_t crc) {
159
+ crc ^= (uint16_t )data << 8 ;
160
+ for (int i = 0 ; i < 8 ; i++)
161
+ crc = (crc & 0x8000 ) ? (crc << 1 ) ^ 0x1021 : (crc << 1 );
162
+ return crc;
163
+ }
164
+
165
+ /* *
166
+ * @brief Write a byte with proper HDLC byte stuffing if needed
167
+ *
168
+ * @param b Byte to write
169
+ */
170
+ void _writeEscaped (uint8_t b) {
171
+ if (b == HDLC_FLAG || b == HDLC_ESC) {
172
+ tx_frame_buffer.write (HDLC_ESC);
173
+ tx_frame_buffer.write (b ^ HDLC_ESC_XOR);
174
+ } else {
175
+ tx_frame_buffer.write (b);
176
+ }
177
+ }
178
+
179
+ /* *
180
+ * @brief Write a complete HDLC frame with proper framing and CRC
181
+ *
182
+ * @param data Data to be framed
183
+ * @param len Length of data
184
+ * @return size_t Number of bytes in the original data
185
+ */
186
+ size_t writeFrame (const uint8_t * data, size_t len) {
187
+ if (!data || len == 0 ) return 0 ;
188
+
189
+ uint16_t crc = 0xFFFF ;
190
+ tx_frame_buffer.write (HDLC_FLAG);
191
+
192
+ for (size_t i = 0 ; i < len; ++i) {
193
+ crc = _crc16 (data[i], crc);
194
+ _writeEscaped (data[i]);
195
+ }
196
+
197
+ _writeEscaped (crc >> 8 );
198
+ _writeEscaped (crc & 0xFF );
199
+ tx_frame_buffer.write (HDLC_FLAG);
200
+ p_print->write (tx_frame_buffer.data (), tx_frame_buffer.available ());
201
+ p_print->flush ();
202
+ tx_frame_buffer.clear ();
203
+ return len;
204
+ }
205
+
206
+ /* *
207
+ * @brief Process incoming bytes, detect frames, validate CRC and prepare data
208
+ * for reading
209
+ */
210
+ void _processInput () {
211
+ while (!_frameReady && p_stream->available ()) {
212
+ uint8_t b = p_stream->read ();
213
+
214
+ if (b == HDLC_FLAG) {
215
+ if (_rxLen >= 3 ) {
216
+ uint16_t recvCrc =
217
+ (_rxBuffer[_rxLen - 2 ] << 8 ) | _rxBuffer[_rxLen - 1 ];
218
+ uint16_t calcCrc = 0xFFFF ;
219
+ for (size_t i = 0 ; i < _rxLen - 2 ; ++i)
220
+ calcCrc = _crc16 (_rxBuffer[i], calcCrc);
221
+
222
+ if (calcCrc == recvCrc) {
223
+ for (int j = 0 ; j < _rxLen - 2 ; j++) {
224
+ rx_frame_buffer.write (_rxBuffer[j]);
225
+ }
226
+
227
+ _frameLen = _rxLen - 2 ;
228
+ _rxPos = 0 ;
229
+ _frameReady = true ;
230
+ }
231
+ }
232
+ _rxState = IDLE;
233
+ _rxLen = 0 ;
234
+ continue ;
235
+ }
236
+
237
+ switch (_rxState) {
238
+ case IDLE:
239
+ _rxLen = 0 ;
240
+ if (b == HDLC_ESC) {
241
+ _rxState = ESCAPED;
242
+ } else {
243
+ _rxState = RECEIVING;
244
+ if (_rxLen < _maxFrameSize) _rxBuffer[_rxLen++] = b;
245
+ }
246
+ break ;
247
+
248
+ case RECEIVING:
249
+ if (b == HDLC_ESC) {
250
+ _rxState = ESCAPED;
251
+ } else if (_rxLen < _maxFrameSize) {
252
+ _rxBuffer[_rxLen++] = b;
253
+ }
254
+ break ;
255
+
256
+ case ESCAPED:
257
+ if (_rxLen < _maxFrameSize) {
258
+ _rxBuffer[_rxLen++] = b ^ HDLC_ESC_XOR;
259
+ }
260
+ _rxState = RECEIVING;
261
+ break ;
262
+ }
263
+
264
+ if (_rxLen >= _maxFrameSize) {
265
+ _rxState = IDLE; // overflow
266
+ _rxLen = 0 ;
267
+ }
268
+ }
269
+ }
270
+ };
271
+
272
+ } // namespace audio_tools
0 commit comments