Skip to content

Commit cd4b9fb

Browse files
committed
HDLCStream
1 parent 2aea1fd commit cd4b9fb

File tree

1 file changed

+161
-58
lines changed

1 file changed

+161
-58
lines changed

src/AudioTools/Sandbox/HDLCStream.h

Lines changed: 161 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma once
2+
13
#include <stdbool.h>
24
#include <stdint.h>
35

@@ -28,73 +30,142 @@
2830

2931
namespace 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-
4359
class 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

Comments
 (0)