diff --git a/RingStream.cpp b/RingStream.cpp index bf89e33..a03bfbf 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -18,9 +18,17 @@ * along with CommandStation. If not, see . */ + +// NOTE: The use of a marker byte without an escape algorithm means +// RingStream is unsuitable for binary data. Should binary data need to be +// streamed it will be necessary to implementr an escape strategy to handle the +// marker char when embedded in data. + #include "RingStream.h" #include "DIAG.h" +const byte FLASH_INSERT_MARKER=0xff; + RingStream::RingStream( const uint16_t len) { _len=len; @@ -31,6 +39,7 @@ RingStream::RingStream( const uint16_t len) _overflow=false; _mark=0; _count=0; + _flashInsert=0; } size_t RingStream::write(uint8_t b) { @@ -46,8 +55,74 @@ size_t RingStream::write(uint8_t b) { return 1; } +// Ideally, I would prefer to override the Print:print(_FlashStringHelper) function +// but the library authors omitted to make this virtual. +// Therefore we obveride the only other simple function that has no side effects +// in order that StringFormatter can recognise a RingStream and call its +// printFlash() directly. +int RingStream::availableForWrite() { + return THIS_IS_A_RINGSTREAM; + } + +size_t RingStream::printFlash(const FSH * flashBuffer) { +// We are about to add a PROGMEM string to the buffer. +// To save RAM we can insert a marker and the +// progmem address into the buffer instead. +// The buffer reading code must recognise this marker and +// silently extract the progmem bytes. +// In addition, we must make the count correct as if the +// string had been embedded so that things like the wifi code +// can read the expected count before reading the buffer. + +// Establish the actual length of the progmem string. +char * flash=(char *)flashBuffer; +int16_t plength=strlen_P(flash); +if (plength==0) return 0; // just ignore empty string + +// Retain the buffer count as it will be modified by the marker+address insert +int prevCount=_count; +write(FLASH_INSERT_MARKER); // write the marker +uintptr_t iFlash=reinterpret_cast(flash); // expect size match with pointer + +// write address bytes LSB first (size depends on CPU) +for (byte f=0;f>=8; +} + +// correct the buffer count to reflect the flash length, not the marker/addr. +_count=prevCount+plength; +return plength; +} + int RingStream::read() { + if (_flashInsert) { + // we are reading out of a flash string + byte fb=GETFLASH(_flashInsert); + _flashInsert++; + if (fb) return fb; // we have a byte from the flash + // flash insert complete, clear and drop through to next buffer byte + _flashInsert=NULL; + } if ((_pos_read==_pos_write) && !_overflow) return -1; // empty + byte b=readRawByte(); + if (b!=FLASH_INSERT_MARKER) return b; + + // Detected a flash insert + // read address bytes LSB first (size depends on CPU) + uintptr_t iFlash=0; + for (byte f=0; f( iFlash); + // and try again... so will read the first byte of the insert. + return read(); +} + +byte RingStream::readRawByte() { byte b=_buffer[_pos_read]; _pos_read++; if (_pos_read==_len) _pos_read=0; @@ -55,9 +130,8 @@ int RingStream::read() { return b; } - int RingStream::count() { - return (read()<<8) | read(); + return (readRawByte()<<8) | readRawByte(); } int RingStream::freeSpace() { @@ -83,6 +157,7 @@ uint8_t RingStream::peekTargetMark() { } bool RingStream::commit() { + _flashInsert=NULL; // prepared for first read if (_overflow) { DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); // just throw it away @@ -108,8 +183,7 @@ void RingStream::flush() { _pos_write=0; _pos_read=0; _buffer[0]=0; + _flashInsert=NULL; // prepared for first read + } -void RingStream::printBuffer(Print * stream) { - _buffer[_pos_write]='\0'; - stream->print((char *)_buffer); -} + diff --git a/RingStream.h b/RingStream.h index 5821bc4..61c1737 100644 --- a/RingStream.h +++ b/RingStream.h @@ -21,22 +21,32 @@ */ #include +#include "FSH.h" class RingStream : public Print { public: RingStream( const uint16_t len); - + static const int THIS_IS_A_RINGSTREAM=77; virtual size_t write(uint8_t b); + + // This availableForWrite function is subverted from its original intention so that a caller + // can destinguish between a normal stream and a RingStream. + // The Arduino compiler does not support runtime dynamic cast to perform + // an instranceOf check. + // This is necessary since the Print functions are mostly not virtual so + // we cant override the print(__FlashStringHelper *) function. + virtual int availableForWrite() override; using Print::write; + size_t printFlash(const FSH * flashBuffer); int read(); int count(); int freeSpace(); void mark(uint8_t b); bool commit(); uint8_t peekTargetMark(); - void printBuffer(Print * streamer); void flush(); + byte readRawByte(); private: int _len; int _pos_write; @@ -45,6 +55,7 @@ class RingStream : public Print { int _mark; int _count; byte * _buffer; + char * _flashInsert; }; #endif diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 485d8fd..ff5a96a 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -97,7 +97,21 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break; - case 'S': stream->print((const FSH*)va_arg(args, char*)); break; + case 'S': + { + const FSH* flash= (const FSH*)va_arg(args, char*); + +#if WIFI_ON | ETHERNET_ON + // RingStream has special logic to handle flash strings + // but is not implemented unless wifi or ethernet are enabled. + // The define prevents RingStream code being added unnecessariliy. + if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) + ((RingStream *)stream)->printFlash(flash); + else +#endif + stream->print(flash); + break; + } case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; diff --git a/StringFormatter.h b/StringFormatter.h index 50ce0f7..1e225b5 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -20,6 +20,8 @@ #define StringFormatter_h #include #include "FSH.h" +#include "RingStream.h" + #if defined(ARDUINO_ARCH_SAMD) // Some processors use a gcc compiler that renames va_list!!! #include