Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serial store & forward #448

Merged
merged 2 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 69 additions & 17 deletions utility/SerialFirmata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@

- handlePinMode calls Firmata::setPinMode

Last updated October 16th, 2016
Last updated March 16th, 2020
*/

#include "SerialFirmata.h"

// The RX and TX hardware FIFOs of the ESP8266 hold 128 bytes that can be
// extended using interrupt handlers. The Arduino constants are not available
// for the ESP8266 platform.
#if !defined(SERIAL_RX_BUFFER_SIZE) && defined(UART_TX_FIFO_SIZE)
#define SERIAL_RX_BUFFER_SIZE UART_TX_FIFO_SIZE
#endif


SerialFirmata::SerialFirmata()
{
#if defined(SoftwareSerial_h)
Expand All @@ -29,6 +37,12 @@ SerialFirmata::SerialFirmata()
#endif

serialIndex = -1;

#if defined(FIRMATA_SERIAL_RX_DELAY)
for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) {
maxRxDelay[i] = FIRMATA_SERIAL_RX_DELAY; // @todo provide setter
}
#endif
}

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

switch (mode) {
case SERIAL_CONFIG:
{
long baud = (long)argv[1] | ((long)argv[2] << 7) | ((long)argv[3] << 14);
serial_pins pins;

#if defined(FIRMATA_SERIAL_RX_DELAY)
lastBytesAvailable[portId] = 0;
lastBytesReceived[portId] = 0;
#endif
if (portId < 8) {
serialPort = getPortFromId(portId);
if (serialPort != NULL) {
Expand Down Expand Up @@ -229,6 +247,10 @@ void SerialFirmata::reset()
serialIndex = -1;
for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) {
serialBytesToRead[i] = 0;
#if defined(FIRMATA_SERIAL_RX_DELAY)
lastBytesAvailable[i] = 0;
lastBytesReceived[i] = 0;
#endif
}
}

Expand Down Expand Up @@ -302,6 +324,10 @@ void SerialFirmata::checkSerial()

if (serialIndex > -1) {

#if defined(FIRMATA_SERIAL_RX_DELAY)
unsigned long currentMillis = millis();
#endif

// loop through all reporting (READ_CONTINUOUS) serial ports
for (byte i = 0; i < serialIndex + 1; i++) {
portId = reportSerial[i];
Expand All @@ -316,27 +342,53 @@ void SerialFirmata::checkSerial()
continue;
}
#endif
if (serialPort->available() > 0) {
Firmata.write(START_SYSEX);
Firmata.write(SERIAL_MESSAGE);
Firmata.write(SERIAL_REPLY | portId);

if (bytesToRead == 0 || (serialPort->available() <= bytesToRead)) {
numBytesToRead = serialPort->available();
int bytesAvailable = serialPort->available();
if (bytesAvailable > 0) {
#if defined(FIRMATA_SERIAL_RX_DELAY)
if (bytesAvailable > lastBytesAvailable[portId]) {
lastBytesReceived[portId] = currentMillis;
}
lastBytesAvailable[portId] = bytesAvailable;
#endif
if (bytesToRead <= 0 || (bytesAvailable <= bytesToRead)) {
numBytesToRead = bytesAvailable;
} else {
numBytesToRead = bytesToRead;
}

#if defined(FIRMATA_SERIAL_RX_DELAY)
if (maxRxDelay[portId] >= 0 && numBytesToRead > 0) {
// read and send immediately only if
// - expected bytes are unknown and the receive buffer has reached 50 %
// - expected bytes are available
// - maxRxDelay has expired since last receive (or time counter wrap)
if (!((bytesToRead <= 0 && bytesAvailable >= SERIAL_RX_BUFFER_SIZE/2)
|| (bytesToRead > 0 && bytesAvailable >= bytesToRead)
|| (maxRxDelay[portId] > 0 && (currentMillis < lastBytesReceived[portId] || (currentMillis - lastBytesReceived[portId]) >= maxRxDelay[portId])))) {
// delay
numBytesToRead = 0;
}
}
#endif
// relay serial data to the serial device
while (numBytesToRead > 0) {
serialData = serialPort->read();
Firmata.write(serialData & 0x7F);
Firmata.write((serialData >> 7) & 0x7F);
numBytesToRead--;
if (numBytesToRead > 0) {
#if defined(FIRMATA_SERIAL_RX_DELAY)
lastBytesAvailable[portId] -= numBytesToRead;
#endif
Firmata.write(START_SYSEX);
Firmata.write(SERIAL_MESSAGE);
Firmata.write(SERIAL_REPLY | portId);

// relay serial data to the serial device
while (numBytesToRead > 0) {
serialData = serialPort->read();
Firmata.write(serialData & 0x7F);
Firmata.write((serialData >> 7) & 0x7F);
numBytesToRead--;
}

Firmata.write(END_SYSEX);
}
Firmata.write(END_SYSEX);
}

}
}
}
34 changes: 33 additions & 1 deletion utility/SerialFirmata.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- Defines FIRMATA_SERIAL_FEATURE (could add to Configurable version as well)
- Imports Firmata.h rather than ConfigurableFirmata.h

Last updated October 16th, 2016
Last updated March 11th, 2020
*/

#ifndef SerialFirmata_h
Expand All @@ -30,6 +30,32 @@
#include <SoftwareSerial.h>
#endif

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

#define FIRMATA_SERIAL_FEATURE

// Serial port Ids
Expand Down Expand Up @@ -194,6 +220,12 @@ class SerialFirmata: public FirmataFeature
int serialBytesToRead[SERIAL_READ_ARR_LEN];
signed char serialIndex;

#if defined(FIRMATA_SERIAL_RX_DELAY)
byte maxRxDelay[SERIAL_READ_ARR_LEN];
int lastBytesAvailable[SERIAL_READ_ARR_LEN];
unsigned long lastBytesReceived[SERIAL_READ_ARR_LEN];
#endif

#if defined(SoftwareSerial_h)
Stream *swSerial0;
Stream *swSerial1;
Expand Down