Skip to content

Commit 7cd8d0d

Browse files
committed
Fix serial2socket lock after a while
1 parent 6d0e43a commit 7cd8d0d

File tree

4 files changed

+223
-47
lines changed

4 files changed

+223
-47
lines changed

docs/ESP3DLib 3.0 Serial2Socket.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Serial2Socket API
2+
3+
Serial2Socket is a class that allows to emulate serial port but transfert data so the class is used to connect to wifi and so facilitate the data between ESP3DLib and Marlin, ESP3DLib is in charge to dispatch/collect the data and send it to the right place. ESP3DLib can get data over wifi by telnet, http, websocket and forward them to Marlin using the serial2socket class. in same way Marlin can send data to ESP3DLib using the serial2socket class and ESP3DLib will forward them to the right place (websocket, telnet, http).
4+
5+
To achieve this it emulate serial port functions to communicate with Marlin:
6+
- begin
7+
The begin function take the baudrate as parameter for compatibility but do not use it. It is used to initialize the serial2socket class and prepare it for communication.
8+
- end
9+
The end function is used to close the serial2socket class and free the resources used by it.
10+
- available
11+
The available function is used to check if there is data available to read from the RX circular buffer. It returns the number of bytes available to read.
12+
- read
13+
The read function is used to read a byte from the RX circular buffer. It returns the byte read or -1 if there is no data available.
14+
- peek
15+
The peek function is used to read a byte from the RX circular buffer without removing it. It returns the byte read or -1 if there is no data available.
16+
To transfer data to Marlin the class use the dispatch(ESP3DMessage *message), the message contain the data and the size of the data to be send. To transfer data, the class feed an RX circular buffer by using the function push2RX(const uint8_t *buffer, size_t size), it will push an array of size bytes to the RX circular buffer.
17+
18+
Marlin periodicaly check the available() function to see if there is data available to read. If there is data available, it will call the read() function to read the data and process it. each call to read move the read pointer to the next byte in the RX circular buffer.
19+
20+
21+
To collect data from Marlin, the class rely on usage of the function write(const uint8_t *buffer, size_t size) to send data to the TX circular buffer. The function write is used to send data to the TX circular buffer. The function will push the data to the TX circular buffer and return the number of bytes written. The function will chunk the data in strings if there a `\n`, the string will be transformed to a ESP3DMessage and send by the flush function which will send the new message using esp3d_commands.process(msg);
22+
the flush is also done in addition of \n detection if:
23+
1 - the buffer is full
24+
2 - the buffer is not empty but has not been flushed for S2S_FLUSHTIMEOUT milliseconds (500ms by default)
25+
26+
The esp3d_commands.process(msg); function will process the message and send it to the right place (websocket, telnet, http) or if it is an ESP3D command, to the ESP3D command processor.

src/include/esp3d_version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#define _VERSION_ESP3D_H
2323

2424
// version and sources location
25-
#define FW_VERSION "3.0.0.6b1"
25+
#define FW_VERSION "3.0.0.7b1"
2626
#define REPOSITORY "https://github.com/luc-github/ESP3D/tree/3.0"
2727

2828
#endif //_VERSION_ESP3D_H

src/modules/serial2socket/serial2socket.cpp

Lines changed: 188 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,67 @@
2323

2424
#if defined(ESP3DLIB_ENV) && COMMUNICATION_PROTOCOL == SOCKET_SERIAL
2525
#include <Arduino.h>
26+
#include "freertos/FreeRTOS.h"
27+
#include "freertos/semphr.h"
2628

2729
#include "../../core/esp3d_commands.h"
2830
#include "../../core/esp3d_message.h"
2931
#include "serial2socket.h"
3032

3133
Serial_2_Socket Serial2Socket;
3234

33-
Serial_2_Socket::Serial_2_Socket() { end(); }
34-
Serial_2_Socket::~Serial_2_Socket() { end(); }
35+
Serial_2_Socket::Serial_2_Socket() {
36+
_rxBufferMutex = (void*)xSemaphoreCreateMutex();
37+
if (_rxBufferMutex == NULL) {
38+
esp3d_log_e("Serial2Socket: Failed to create RX mutex");
39+
}
40+
41+
_txBufferMutex = (void*)xSemaphoreCreateMutex();
42+
if (_txBufferMutex == NULL) {
43+
esp3d_log_e("Serial2Socket: Failed to create TX mutex");
44+
}
45+
46+
end();
47+
}
48+
49+
Serial_2_Socket::~Serial_2_Socket() {
50+
if (_rxBufferMutex != NULL) {
51+
vSemaphoreDelete((SemaphoreHandle_t)_rxBufferMutex);
52+
_rxBufferMutex = NULL;
53+
}
54+
55+
if (_txBufferMutex != NULL) {
56+
vSemaphoreDelete((SemaphoreHandle_t)_txBufferMutex);
57+
_txBufferMutex = NULL;
58+
}
59+
60+
end();
61+
}
62+
3563
void Serial_2_Socket::begin(long speed) { end(); }
3664

3765
void Serial_2_Socket::enable(bool enable) { _started = enable; }
3866

3967
void Serial_2_Socket::pause(bool state) {
4068
_paused = state;
4169
if (_paused) {
42-
_TXbufferSize = 0;
43-
_RXbufferSize = 0;
44-
_RXbufferpos = 0;
70+
// Protect TX buffer access with mutex
71+
if (_txBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_txBufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
72+
_TXbufferSize = 0;
73+
xSemaphoreGive((SemaphoreHandle_t)_txBufferMutex);
74+
} else {
75+
_TXbufferSize = 0;
76+
}
77+
78+
// Protect RX buffer access with mutex
79+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
80+
_RXbufferSize = 0;
81+
_RXbufferpos = 0;
82+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
83+
} else {
84+
_RXbufferSize = 0;
85+
_RXbufferpos = 0;
86+
}
4587
} else {
4688
_lastflush = millis();
4789
}
@@ -50,9 +92,24 @@ void Serial_2_Socket::pause(bool state) {
5092
bool Serial_2_Socket::isPaused() { return _paused; }
5193

5294
void Serial_2_Socket::end() {
53-
_TXbufferSize = 0;
54-
_RXbufferSize = 0;
55-
_RXbufferpos = 0;
95+
// Protect TX buffer access with mutex
96+
if (_txBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_txBufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
97+
_TXbufferSize = 0;
98+
xSemaphoreGive((SemaphoreHandle_t)_txBufferMutex);
99+
} else {
100+
_TXbufferSize = 0;
101+
}
102+
103+
// Protect RX buffer access with mutex
104+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
105+
_RXbufferSize = 0;
106+
_RXbufferpos = 0;
107+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
108+
} else {
109+
_RXbufferSize = 0;
110+
_RXbufferpos = 0;
111+
}
112+
56113
_started = false;
57114
_paused = false;
58115
_lastflush = millis();
@@ -70,16 +127,32 @@ bool Serial_2_Socket::started() { return _started; }
70127
Serial_2_Socket::operator bool() const { return true; }
71128

72129
int Serial_2_Socket::available() {
73-
if (_paused) {
130+
if (_paused || !_started) {
74131
return 0;
75132
}
76-
return _RXbufferSize;
133+
if (_RXbufferSize == 0) {
134+
return 0;
135+
}
136+
137+
// Protect RX buffer access with mutex
138+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
139+
esp3d_log_e("Serial2Socket: Failed to take mutex for available");
140+
return 0;
141+
}
142+
143+
int size = _RXbufferSize;
144+
145+
if (_rxBufferMutex != NULL) {
146+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
147+
}
148+
return size;
77149
}
78150

79151
size_t Serial_2_Socket::write(uint8_t c) {
80152
if (!_started || _paused) {
81153
return 1;
82154
}
155+
esp3d_log_d("Serial2Socket: write one char %c", c);
83156
return write(&c, 1);
84157
}
85158

@@ -88,39 +161,81 @@ size_t Serial_2_Socket::write(const uint8_t *buffer, size_t size) {
88161
esp3d_log("Serial2Socket: no data, not started or paused");
89162
return size;
90163
}
164+
165+
// Take TX buffer mutex once for the entire function
166+
if (_txBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_txBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
167+
esp3d_log_e("Serial2Socket: Failed to take mutex for write");
168+
return 0;
169+
}
170+
91171
if (_TXbufferSize == 0) {
92172
_lastflush = millis();
93173
}
94-
// send full line
174+
175+
// Check if buffer is full and needs flushing
95176
if (_TXbufferSize + size > S2S_TXBUFFERSIZE) {
96-
flush();
177+
esp3d_log_d("Serial2Socket: buffer full, flush it");
178+
flush(false); // Use flush without mutex since we already have it
97179
}
98-
// need periodic check to force to flush in case of no end
180+
181+
// Add data to buffer and flush on newline
99182
for (int i = 0; i < size; i++) {
100183
_TXbuffer[_TXbufferSize] = buffer[i];
101184
_TXbufferSize++;
102185
if (buffer[i] == (const uint8_t)'\n') {
103-
esp3d_log("S2S: %s TXSize: %d", (const char *)_TXbuffer, _TXbufferSize);
104-
flush();
186+
esp3d_log_d("S2S: %s TXSize: %d", (const char *)_TXbuffer, _TXbufferSize);
187+
flush(false); // Use flush without mutex since we already have it
105188
}
106189
}
107-
handle_flush();
190+
191+
// Check if we need to flush based on time
192+
if (_TXbufferSize > 0 && ((millis() - _lastflush) > S2S_FLUSHTIMEOUT)) {
193+
flush(false); // Use flush without mutex since we already have it
194+
}
195+
196+
// Release the mutex at the end
197+
if (_txBufferMutex != NULL) {
198+
xSemaphoreGive((SemaphoreHandle_t)_txBufferMutex);
199+
}
200+
108201
return size;
109202
}
110203

111204
int Serial_2_Socket::peek(void) {
112-
if (_RXbufferSize > 0 && _started) {
113-
return _RXbuffer[_RXbufferpos];
114-
} else {
205+
esp3d_log_d("Serial2Socket: peek first of %d", _RXbufferSize);
206+
if (_RXbufferSize <= 0 || !_started) {
115207
return -1;
116208
}
209+
210+
// Protect RX buffer access with mutex
211+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
212+
esp3d_log_e("Serial2Socket: Failed to take mutex for peek");
213+
return -1;
214+
}
215+
216+
uint8_t v = _RXbuffer[_RXbufferpos];
217+
218+
if (_rxBufferMutex != NULL) {
219+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
220+
}
221+
return v;
117222
}
118223

119-
bool Serial_2_Socket::push(const uint8_t *buffer, size_t size) {
224+
// Send data to socket output buffer
225+
bool Serial_2_Socket::push2RX(const uint8_t *buffer, size_t size) {
120226
if (buffer == NULL || size == 0 || !_started || _paused) {
121227
return false;
122228
}
229+
230+
// Protect RX buffer access with mutex
231+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
232+
esp3d_log_e("Serial2Socket: cannot take mutex for push2RX");
233+
return false;
234+
}
235+
123236
int data_size = size;
237+
bool success = false;
238+
esp3d_log_d("Serial2Socket: pushing %d chars to buffer", data_size);
124239
if ((data_size + _RXbufferSize) <= S2S_RXBUFFERSIZE) {
125240
int current = _RXbufferpos + _RXbufferSize;
126241
if (current > S2S_RXBUFFERSIZE) {
@@ -134,24 +249,41 @@ bool Serial_2_Socket::push(const uint8_t *buffer, size_t size) {
134249
current++;
135250
}
136251
_RXbufferSize += size;
137-
_RXbuffer[current] = 0;
138-
return true;
252+
if (current < S2S_RXBUFFERSIZE) {
253+
_RXbuffer[current] = 0;
254+
}
255+
success = true;
139256
}
140-
return false;
257+
258+
if (_rxBufferMutex != NULL) {
259+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
260+
}
261+
return success;
141262
}
142263

143264
int Serial_2_Socket::read(void) {
144-
if (_RXbufferSize > 0 && _started && !_paused) {
145-
int v = _RXbuffer[_RXbufferpos];
146-
_RXbufferpos++;
147-
if (_RXbufferpos > (S2S_RXBUFFERSIZE - 1)) {
148-
_RXbufferpos = 0;
149-
}
150-
_RXbufferSize--;
151-
return v;
152-
} else {
265+
if (_RXbufferSize <= 0 || !_started || _paused) {
266+
return -1;
267+
}
268+
269+
// Protect RX buffer access with mutex
270+
if (_rxBufferMutex != NULL && xSemaphoreTake((SemaphoreHandle_t)_rxBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
271+
esp3d_log_e("Serial2Socket: Failed to take mutex for read");
153272
return -1;
154273
}
274+
275+
uint8_t v = _RXbuffer[_RXbufferpos];
276+
_RXbufferpos++;
277+
if (_RXbufferpos > (S2S_RXBUFFERSIZE - 1)) {
278+
_RXbufferpos = 0;
279+
}
280+
_RXbufferSize--;
281+
esp3d_log_d("Serial2Socket: read one char %c", v);
282+
283+
if (_rxBufferMutex != NULL) {
284+
xSemaphoreGive((SemaphoreHandle_t)_rxBufferMutex);
285+
}
286+
return v;
155287
}
156288

157289
void Serial_2_Socket::handle() { handle_flush(); }
@@ -161,27 +293,42 @@ void Serial_2_Socket::handle_flush() {
161293
if ((_TXbufferSize >= S2S_TXBUFFERSIZE) ||
162294
((millis() - _lastflush) > S2S_FLUSHTIMEOUT)) {
163295
esp3d_log("force socket flush");
164-
flush();
296+
flush(true);
165297
}
166298
}
167299
}
168-
void Serial_2_Socket::flush(void) {
300+
301+
void Serial_2_Socket::flush(bool useMutex) {
302+
303+
// Process buffer if there's data and we're not paused
169304
if (_TXbufferSize > 0 && _started && !_paused) {
305+
// Protect TX buffer access with mutex if requested
306+
if (useMutex && _txBufferMutex != NULL) {
307+
if (xSemaphoreTake((SemaphoreHandle_t)_txBufferMutex, pdMS_TO_TICKS(100)) != pdTRUE) {
308+
esp3d_log_e("Serial2Socket: Failed to take mutex for flush");
309+
return;
310+
}
311+
}
170312
ESP3DMessage *msg = esp3d_message_manager.newMsg(
171313
ESP3DClientType::socket_serial, ESP3DClientType::all_clients, _TXbuffer,
172314
_TXbufferSize, _auth);
173-
// dispatch command
315+
316+
// Reset buffer before processing
317+
_lastflush = millis();
318+
_TXbufferSize = 0;
319+
320+
// Release mutex if we took it
321+
if (useMutex && _txBufferMutex != NULL) {
322+
xSemaphoreGive((SemaphoreHandle_t)_txBufferMutex);
323+
}
324+
174325
if (msg) {
175326
// process command
176327
msg->type = ESP3DMessageType::unique;
177328
esp3d_commands.process(msg);
178329
} else {
179330
esp3d_log_e("Cannot create message");
180331
}
181-
// refresh timout
182-
_lastflush = millis();
183-
// reset buffer
184-
_TXbufferSize = 0;
185332
}
186333
}
187334

@@ -191,7 +338,8 @@ bool Serial_2_Socket::dispatch(ESP3DMessage *message) {
191338
return false;
192339
}
193340
if (message->size > 0 && message->data) {
194-
if (!push(message->data, message->size)) {
341+
esp3d_log_d("Serial2Socket: dispatch message %d", message->size);
342+
if (!push2RX(message->data, message->size)) {
195343
esp3d_log_e("Serial2Socket: cannot push all data");
196344
return false;
197345
}

0 commit comments

Comments
 (0)