Skip to content

Commit 5ef4bdc

Browse files
Merge pull request firmata#448 from jnsbyr/serial-store-and-forward
Serial store & forward
2 parents f171e7c + 4048f22 commit 5ef4bdc

File tree

2 files changed

+102
-18
lines changed

2 files changed

+102
-18
lines changed

utility/SerialFirmata.cpp

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@
1414
1515
- handlePinMode calls Firmata::setPinMode
1616
17-
Last updated October 16th, 2016
17+
Last updated March 16th, 2020
1818
*/
1919

2020
#include "SerialFirmata.h"
2121

22+
// The RX and TX hardware FIFOs of the ESP8266 hold 128 bytes that can be
23+
// extended using interrupt handlers. The Arduino constants are not available
24+
// for the ESP8266 platform.
25+
#if !defined(SERIAL_RX_BUFFER_SIZE) && defined(UART_TX_FIFO_SIZE)
26+
#define SERIAL_RX_BUFFER_SIZE UART_TX_FIFO_SIZE
27+
#endif
28+
29+
2230
SerialFirmata::SerialFirmata()
2331
{
2432
#if defined(SoftwareSerial_h)
@@ -29,6 +37,12 @@ SerialFirmata::SerialFirmata()
2937
#endif
3038

3139
serialIndex = -1;
40+
41+
#if defined(FIRMATA_SERIAL_RX_DELAY)
42+
for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) {
43+
maxRxDelay[i] = FIRMATA_SERIAL_RX_DELAY; // @todo provide setter
44+
}
45+
#endif
3246
}
3347

3448
boolean SerialFirmata::handlePinMode(byte pin, int mode)
@@ -56,13 +70,17 @@ boolean SerialFirmata::handleSysex(byte command, byte argc, byte *argv)
5670
Stream *serialPort;
5771
byte mode = argv[0] & SERIAL_MODE_MASK;
5872
byte portId = argv[0] & SERIAL_PORT_ID_MASK;
73+
if (portId >= SERIAL_READ_ARR_LEN) return false;
5974

6075
switch (mode) {
6176
case SERIAL_CONFIG:
6277
{
6378
long baud = (long)argv[1] | ((long)argv[2] << 7) | ((long)argv[3] << 14);
6479
serial_pins pins;
65-
80+
#if defined(FIRMATA_SERIAL_RX_DELAY)
81+
lastBytesAvailable[portId] = 0;
82+
lastBytesReceived[portId] = 0;
83+
#endif
6684
if (portId < 8) {
6785
serialPort = getPortFromId(portId);
6886
if (serialPort != NULL) {
@@ -229,6 +247,10 @@ void SerialFirmata::reset()
229247
serialIndex = -1;
230248
for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) {
231249
serialBytesToRead[i] = 0;
250+
#if defined(FIRMATA_SERIAL_RX_DELAY)
251+
lastBytesAvailable[i] = 0;
252+
lastBytesReceived[i] = 0;
253+
#endif
232254
}
233255
}
234256

@@ -302,6 +324,10 @@ void SerialFirmata::checkSerial()
302324

303325
if (serialIndex > -1) {
304326

327+
#if defined(FIRMATA_SERIAL_RX_DELAY)
328+
unsigned long currentMillis = millis();
329+
#endif
330+
305331
// loop through all reporting (READ_CONTINUOUS) serial ports
306332
for (byte i = 0; i < serialIndex + 1; i++) {
307333
portId = reportSerial[i];
@@ -316,27 +342,53 @@ void SerialFirmata::checkSerial()
316342
continue;
317343
}
318344
#endif
319-
if (serialPort->available() > 0) {
320-
Firmata.write(START_SYSEX);
321-
Firmata.write(SERIAL_MESSAGE);
322-
Firmata.write(SERIAL_REPLY | portId);
323-
324-
if (bytesToRead == 0 || (serialPort->available() <= bytesToRead)) {
325-
numBytesToRead = serialPort->available();
345+
int bytesAvailable = serialPort->available();
346+
if (bytesAvailable > 0) {
347+
#if defined(FIRMATA_SERIAL_RX_DELAY)
348+
if (bytesAvailable > lastBytesAvailable[portId]) {
349+
lastBytesReceived[portId] = currentMillis;
350+
}
351+
lastBytesAvailable[portId] = bytesAvailable;
352+
#endif
353+
if (bytesToRead <= 0 || (bytesAvailable <= bytesToRead)) {
354+
numBytesToRead = bytesAvailable;
326355
} else {
327356
numBytesToRead = bytesToRead;
328357
}
329-
358+
#if defined(FIRMATA_SERIAL_RX_DELAY)
359+
if (maxRxDelay[portId] >= 0 && numBytesToRead > 0) {
360+
// read and send immediately only if
361+
// - expected bytes are unknown and the receive buffer has reached 50 %
362+
// - expected bytes are available
363+
// - maxRxDelay has expired since last receive (or time counter wrap)
364+
if (!((bytesToRead <= 0 && bytesAvailable >= SERIAL_RX_BUFFER_SIZE/2)
365+
|| (bytesToRead > 0 && bytesAvailable >= bytesToRead)
366+
|| (maxRxDelay[portId] > 0 && (currentMillis < lastBytesReceived[portId] || (currentMillis - lastBytesReceived[portId]) >= maxRxDelay[portId])))) {
367+
// delay
368+
numBytesToRead = 0;
369+
}
370+
}
371+
#endif
330372
// relay serial data to the serial device
331-
while (numBytesToRead > 0) {
332-
serialData = serialPort->read();
333-
Firmata.write(serialData & 0x7F);
334-
Firmata.write((serialData >> 7) & 0x7F);
335-
numBytesToRead--;
373+
if (numBytesToRead > 0) {
374+
#if defined(FIRMATA_SERIAL_RX_DELAY)
375+
lastBytesAvailable[portId] -= numBytesToRead;
376+
#endif
377+
Firmata.write(START_SYSEX);
378+
Firmata.write(SERIAL_MESSAGE);
379+
Firmata.write(SERIAL_REPLY | portId);
380+
381+
// relay serial data to the serial device
382+
while (numBytesToRead > 0) {
383+
serialData = serialPort->read();
384+
Firmata.write(serialData & 0x7F);
385+
Firmata.write((serialData >> 7) & 0x7F);
386+
numBytesToRead--;
387+
}
388+
389+
Firmata.write(END_SYSEX);
336390
}
337-
Firmata.write(END_SYSEX);
338391
}
339-
340392
}
341393
}
342394
}

utility/SerialFirmata.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- Defines FIRMATA_SERIAL_FEATURE (could add to Configurable version as well)
1616
- Imports Firmata.h rather than ConfigurableFirmata.h
1717
18-
Last updated October 16th, 2016
18+
Last updated March 11th, 2020
1919
*/
2020

2121
#ifndef SerialFirmata_h
@@ -30,6 +30,32 @@
3030
#include <SoftwareSerial.h>
3131
#endif
3232

33+
// If defined and set to a value between 0 and 255 milliseconds the received bytes
34+
// will be not be read until until one of the following conditions are met:
35+
// 1) the expected number of bytes have been received
36+
// 2) the serial receive buffer is filled to 50 % (default size is 64 bytes)
37+
// 3) the delay since the last received byte exceeds the configured FIRMATA_SERIAL_RX_DELAY
38+
// hints: 5 bytes at 9600 baud take 5 ms, human perception of a delay starts at 50 ms
39+
// This feature can significantly reduce the load on the transport layer when
40+
// the byte receive rate is equal or lower than the average Firmata main loop execution
41+
// duration by preventing single byte transmits if the underlying Firmata stream supports
42+
// transmit buffering (currently only available with EthernetClientStream). The effect
43+
// can be increased with higher values of FIRMATA_SERIAL_RX_DELAY.
44+
// Notes
45+
// 1) Enabling this feature will delay the received data and may concatenate
46+
// bytes into one transmit that would otherwise be transmitted separately.
47+
// 2) The usefulness and configuration of this feature depends on the baud rate and the serial message type:
48+
// a) continuous streaming at higher baud rates: enable but set to 0 (receive buffer store & forward)
49+
// b) messages: set to a value below min. inter message delay (message store & forward)
50+
// c) continuous streaming at lower baud rates or random characters: undefine or set to -1 (disable)
51+
// 3) Smaller delays may not have the desired effect, especially with less powerful CPUs,
52+
// if set to a value near or below the average Firmata main loop duration.
53+
// 4) The Firmata stream write buffer size must be equal or greater than the max.
54+
// serial buffer/message size and the Firmata frame size (4 bytes) to prevent fragmentation
55+
// on the transport layer.
56+
//#define FIRMATA_SERIAL_RX_DELAY 50 // [ms]
57+
#define FIRMATA_SERIAL_RX_DELAY 50
58+
3359
#define FIRMATA_SERIAL_FEATURE
3460

3561
// Serial port Ids
@@ -194,6 +220,12 @@ class SerialFirmata: public FirmataFeature
194220
int serialBytesToRead[SERIAL_READ_ARR_LEN];
195221
signed char serialIndex;
196222

223+
#if defined(FIRMATA_SERIAL_RX_DELAY)
224+
byte maxRxDelay[SERIAL_READ_ARR_LEN];
225+
int lastBytesAvailable[SERIAL_READ_ARR_LEN];
226+
unsigned long lastBytesReceived[SERIAL_READ_ARR_LEN];
227+
#endif
228+
197229
#if defined(SoftwareSerial_h)
198230
Stream *swSerial0;
199231
Stream *swSerial1;

0 commit comments

Comments
 (0)