1+ #pragma once
2+
13#include < stdbool.h>
24#include < stdint.h>
35
2830
2931namespace audio_tools {
3032
31- enum class HDLCWriteLogic { OnBufferFull, OnFlush, onWrite };
33+ /* *
34+ * @enum HDLCWriteLogic
35+ * @brief Defines when the actual HDLC frame is written to the output stream
36+ */
37+ enum class HDLCWriteLogic {
38+ /* * @brief Write frames when the internal buffer is full */
39+ OnBufferFull,
40+ /* * @brief Write frames when flush() is called */
41+ OnFlush,
42+ /* * @brief Write frames immediately on each write() call */
43+ OnWrite
44+ };
3245
3346/* *
3447 * @brief High-Level Data Link Control (HDLC) is a bit-oriented code-transparent
35- * synchronous data link layer protocol in scenarios where you need reliable,
36- * framed, and error-checked communication between devices at the data link
37- * layer.
48+ * synchronous data link layer protocol for reliable, framed, and error-checked communication.
49+ *
50+ * This class implements HDLC framing with:
51+ * - Frame delimiter (0x7E) for marking frame boundaries
52+ * - Byte stuffing to escape special characters in the data
53+ * - 16-bit CRC-CCITT for error detection
54+ * - Transparent Stream interface for easy integration
3855 *
3956 * @ingroup communications
4057 * @author Phil Schatzmann
4158 */
42-
4359class HDLCStream : public Stream {
4460 public:
45- // / Defines the output for the hdlc encoding
61+ /* *
62+ * @brief Constructor with write-only operation
63+ *
64+ * @param out Print object to which encoded HDLC frames will be written
65+ * @param max_frame_length Maximum size of a single HDLC frame in bytes
66+ */
4667 HDLCStream (Print &out, uint16_t max_frame_length) {
4768 setOutput (out);
4869 this ->max_frame_length = max_frame_length;
4970 begin ();
5071 }
5172
52- // / Defines the input for the hdlc decoding
73+ /* *
74+ * @brief Constructor with read-write operation
75+ *
76+ * @param io Stream object for bidirectional HDLC communication
77+ * @param max_frame_length Maximum size of a single HDLC frame in bytes
78+ */
5379 HDLCStream (Stream &io, uint16_t max_frame_length) {
5480 setStream (io);
5581 this ->max_frame_length = max_frame_length;
5682 begin ();
5783 }
5884
85+ /* *
86+ * @brief Initialize the HDLC stream
87+ *
88+ * Resets the frame position, checksum, and escape character state.
89+ * Allocates the frame buffer to the specified max_frame_length.
90+ *
91+ * @return true if a valid input or output stream has been set
92+ * @return false if no valid stream is available
93+ */
5994 bool begin () {
6095 this ->frame_position = 0 ;
6196 this ->frame_checksum = CRC16_CCITT_INIT_VAL;
6297 this ->escape_character = false ;
63- if (frame_buffer.size () == 0 ) {
64- frame_buffer.resize (max_frame_length + 1 );
98+ if (frame_buffer.size () != max_frame_length ) {
99+ frame_buffer.resize (max_frame_length);
65100 }
66101 return p_out != nullptr || p_in != nullptr ;
67102 }
68103
104+ /* *
105+ * @brief Clean up resources used by the HDLC stream
106+ *
107+ * Releases the frame buffer memory by resizing it to zero.
108+ */
69109 void end () { this ->frame_buffer .resize (0 ); }
70110
111+ /* *
112+ * @brief Get the number of bytes available in the write buffer
113+ *
114+ * @return int Available space for writing or 0 if no output is defined
115+ */
71116 int availableForWrite () override {
72117 return p_out == nullptr ? 0 : DEFAULT_BUFFER_SIZE;
73118 }
74119
75- // / Sends the encoded data to the defined output
120+ /* *
121+ * @brief Write data to be encoded as HDLC frames
122+ *
123+ * Processes data according to the current HDLCWriteLogic setting:
124+ * - OnBufferFull: Stores data in buffer, sends when buffer is full
125+ * - OnFlush: Stores data in buffer, waits for flush() to send
126+ * - OnWrite: Immediately sends data as an HDLC frame
127+ *
128+ * @param data Pointer to the data buffer to be written
129+ * @param len Number of bytes to write
130+ * @return size_t Number of bytes accepted (typically equals len)
131+ */
76132 size_t write (const uint8_t *data, size_t len) override {
77133 LOGD (" HDLCStream::write: %zu" , len);
78134
79135 switch (write_logic) {
136+ case HDLCWriteLogic::OnFlush:
137+ for (int j = 0 ; j < len; j++) {
138+ bool ok = frame_buffer.write (data[j]);
139+ assert (ok);
140+ if (frame_buffer.isFull ()) {
141+ LOGE (" Buffer full - increase size!" );
142+ }
143+ }
144+ break ;
80145 case HDLCWriteLogic::OnBufferFull:
81146 for (int j = 0 ; j < len; j++) {
82147 bool ok = frame_buffer.write (data[j]);
83148 assert (ok);
84- if (frame_buffer.available () == max_frame_length ) {
149+ if (frame_buffer.isFull () ) {
85150 sendFrame (frame_buffer.data (), max_frame_length);
86151 frame_buffer.reset ();
87152 }
88153 }
89154 break ;
90- case HDLCWriteLogic::onWrite :
155+ case HDLCWriteLogic::OnWrite :
91156 sendFrame (data, len);
92157 break ;
93158 }
94159
95160 return len;
96161 }
97162
163+ /* *
164+ * @brief Flush any buffered data as an HDLC frame
165+ *
166+ * If data is pending in the buffer, encodes and sends it as an HDLC frame.
167+ * Also flushes the underlying output stream if available.
168+ */
98169 void flush () override {
99170 LOGD (" HDLCStream::flush" );
100171 if (frame_buffer.available () > 0 ) {
@@ -106,45 +177,94 @@ class HDLCStream : public Stream {
106177 }
107178 }
108179
180+ /* *
181+ * @brief Get number of bytes available for reading
182+ *
183+ * @return int Maximum frame length if input stream is available, 0 otherwise
184+ */
109185 int available () override { return p_in == nullptr ? 0 : max_frame_length; }
110186
111- // / Provides the decoded data
187+ /* *
188+ * @brief Read decoded data from the next valid HDLC frame
189+ *
190+ * Processes incoming bytes through the HDLC protocol decoder until a
191+ * complete valid frame is found or timeout occurs.
192+ *
193+ * @param data Buffer to store the decoded data
194+ * @param len Maximum number of bytes to read
195+ * @return size_t Number of bytes actually read (0 if no valid frame found)
196+ */
112197 size_t readBytes (uint8_t *data, size_t len) override {
113198 if (p_in == nullptr ) {
114199 LOGI (" No data source" );
115200 return 0 ;
116201 }
117202
118203 int result = 0 ;
119- // process bytes from input
120- while (result == 0 ) {
204+ unsigned long startTime = millis ();
205+
206+ // process bytes from input with timeout
207+ while (result == 0 && (millis () - startTime < timeout_ms)) {
208+ // Check if data is available before blocking on read
209+ if (p_in->available () <= 0 ) {
210+ continue ;
211+ }
212+
121213 int ch = p_in->read ();
122- // ch is -1 when no data
123214 if (ch >= 0 ) {
124215 result = charReceiver (ch);
125216 if (result > 0 ) {
217+ // Respect the requested length limit
218+ result = min (result, (int )len);
126219 result = frame_buffer.readArray (data, result);
127220 break ;
128221 }
129222 } else {
130223 break ;
131224 }
132225 }
226+
133227 LOGD (" HDLCStream::readBytes: %zu -> %d" , len, result);
134228 return result;
135229 }
136-
230+
231+ /* *
232+ * @brief Set both input and output streams to the same object
233+ *
234+ * @param io Stream object for bidirectional HDLC communication
235+ */
137236 void setStream (Stream &io) {
138237 p_out = &io;
139238 p_in = &io;
140239 }
141240
241+ /* *
242+ * @brief Set output stream only for write-only operation
243+ *
244+ * @param out Stream object for HDLC output
245+ */
142246 void setStream (Print &out) { p_out = &out; }
143247
248+ /* *
249+ * @brief Set output destination for encoded HDLC frames
250+ *
251+ * @param out Print object to receive encoded HDLC data
252+ */
144253 void setOutput (Print &out) { p_out = &out; }
145254
255+ /* *
256+ * @brief Write a single byte to be encoded in an HDLC frame
257+ *
258+ * @param ch Byte to write
259+ * @return size_t Always returns 1 if successful
260+ */
146261 size_t write (uint8_t ch) override { return write (&ch, 1 ); };
147262
263+ /* *
264+ * @brief Read a single byte from the decoded HDLC data
265+ *
266+ * @return int Byte value (0-255) or -1 if no data available
267+ */
148268 int read () override {
149269 uint8_t c[1 ];
150270 if (readBytes (c, 1 ) == 0 ) {
@@ -153,10 +273,23 @@ class HDLCStream : public Stream {
153273 return c[0 ];
154274 }
155275
156- // / not supported
276+ /* *
277+ * @brief Not supported in this implementation
278+ *
279+ * @return int Always returns -1
280+ */
157281 int peek () override { return -1 ; }
158282
159- // / Defines the framing logic for writing data
283+ /* *
284+ * @brief Set the framing logic for writing data
285+ *
286+ * Controls when data is actually encoded and sent as HDLC frames:
287+ * - OnBufferFull: When internal buffer is full
288+ * - OnFlush: When flush() is called
289+ * - OnWrite: Immediately on each write
290+ *
291+ * @param logic The framing logic to use
292+ */
160293 void setWriteLogic (HDLCWriteLogic logic) { write_logic = logic; }
161294
162295 private:
@@ -213,7 +346,6 @@ class HDLCStream : public Stream {
213346
214347 if (frame_position == max_frame_length) {
215348 frame_position = 0 ;
216- frame_checksum = CRC16_CCITT_INIT_VAL;
217349 }
218350 return result;
219351 }
@@ -252,50 +384,21 @@ class HDLCStream : public Stream {
252384 p_out->flush ();
253385 }
254386
255- static uint16_t crc16_update (uint16_t crc, uint8_t a) {
256- int i;
257- crc ^= a;
258- for (i = 0 ; i < 8 ; ++i) {
259- if (crc & 1 )
260- crc = (crc >> 1 ) ^ 0xA001 ;
261- else
262- crc = (crc >> 1 );
263- }
264- return crc;
265- }
266-
267- static uint16_t crc_xmodem_update (uint16_t crc, uint8_t data) {
268- int i;
269-
270- crc = crc ^ ((uint16_t )data << 8 );
271- for (i = 0 ; i < 8 ; i++) {
272- if (crc & 0x8000 )
273- crc = (crc << 1 ) ^ 0x1021 ;
274- else
275- crc <<= 1 ;
276- }
277-
278- return crc;
279- }
280-
387+ /* *
388+ * @brief Calculate CRC-CCITT checksum
389+ *
390+ * Implementation of CRC-CCITT (0xFFFF) calculation used for HDLC frames
391+ *
392+ * @param crc Current CRC value
393+ * @param data Byte to include in CRC calculation
394+ * @return uint16_t Updated CRC value
395+ */
281396 static uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data) {
282397 data ^= lo8 (crc);
283398 data ^= data << 4 ;
284399 return ((((uint16_t )data << 8 ) | hi8 (crc)) ^ (uint8_t )(data >> 4 ) ^
285400 ((uint16_t )data << 3 ));
286401 }
287-
288- static uint8_t _crc_ibutton_update (uint8_t crc, uint8_t data) {
289- uint8_t i;
290- crc = crc ^ data;
291- for (i = 0 ; i < 8 ; i++) {
292- if (crc & 0x01 )
293- crc = (crc >> 1 ) ^ 0x8C ;
294- else
295- crc >>= 1 ;
296- }
297- return crc;
298- }
299402};
300403
301404} // namespace audio_tools
0 commit comments