1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-16 00:43:00 +03:00

Stream::send() (#6979)

This commit is contained in:
david gauchard
2021-03-15 01:36:20 +01:00
committed by GitHub
parent 4cc1472821
commit c720c0d9e8
48 changed files with 2136 additions and 650 deletions

View File

@ -26,15 +26,15 @@
class Client: public Stream {
public:
virtual int connect(IPAddress ip, uint16_t port) =0;
virtual int connect(const char *host, uint16_t port) =0;
virtual size_t write(uint8_t) =0;
virtual size_t write(const uint8_t *buf, size_t size) =0;
virtual int available() = 0;
virtual int read() = 0;
virtual int read(uint8_t *buf, size_t size) = 0;
virtual int peek() = 0;
virtual void flush() = 0;
virtual int connect(IPAddress ip, uint16_t port) = 0;
virtual int connect(const char *host, uint16_t port) = 0;
virtual size_t write(uint8_t) override = 0;
virtual size_t write(const uint8_t *buf, size_t size) override = 0;
virtual int available() override = 0;
virtual int read() override = 0;
virtual int read(uint8_t *buf, size_t size) override = 0;
virtual int peek() override = 0;
virtual void flush() override = 0;
virtual void stop() = 0;
virtual uint8_t connected() = 0;
virtual operator bool() = 0;

View File

@ -66,7 +66,7 @@ int File::read() {
return result;
}
size_t File::read(uint8_t* buf, size_t size) {
int File::read(uint8_t* buf, size_t size) {
if (!_p)
return 0;

View File

@ -67,13 +67,14 @@ public:
size_t readBytes(char *buffer, size_t length) override {
return read((uint8_t*)buffer, length);
}
size_t read(uint8_t* buf, size_t size);
int read(uint8_t* buf, size_t size) override;
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
virtual ssize_t streamRemaining() override { return (ssize_t)size() - (ssize_t)position(); }
void close();
operator bool() const;
const char* name() const;
@ -84,6 +85,7 @@ public:
bool isDirectory() const;
// Arduino "class SD" methods for compatibility
//TODO use stream::send / check read(buf,size) result
template<typename T> size_t write(T &src){
uint8_t obuf[256];
size_t doneLen = 0;

View File

@ -30,7 +30,7 @@ class FileImpl {
public:
virtual ~FileImpl() { }
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual size_t read(uint8_t* buf, size_t size) = 0;
virtual int read(uint8_t* buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;

View File

@ -135,16 +135,45 @@ public:
// return -1 when data is unvailable (arduino api)
return uart_peek_char(_uart);
}
virtual bool hasPeekBufferAPI () const override
{
return true;
}
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() before calling peekConsume()
const char* peekBuffer () override
{
return uart_peek_buffer(_uart);
}
// return number of byte accessible by peekBuffer()
size_t peekAvailable () override
{
return uart_peek_available(_uart);
}
// consume bytes after use (see peekBuffer)
void peekConsume (size_t consume) override
{
return uart_peek_consume(_uart, consume);
}
int read(void) override
{
// return -1 when data is unvailable (arduino api)
return uart_read_char(_uart);
}
// ::read(buffer, size): same as readBytes without timeout
size_t read(char* buffer, size_t size)
int read(char* buffer, size_t size)
{
return uart_read(_uart, buffer, size);
}
int read(uint8_t* buffer, size_t size) override
{
return uart_read(_uart, (char*)buffer, size);
}
size_t readBytes(char* buffer, size_t size) override;
size_t readBytes(uint8_t* buffer, size_t size) override
{

View File

@ -33,15 +33,7 @@
/* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size) {
#ifdef DEBUG_ESP_CORE
static char not_the_best_way [] PROGMEM STORE_ATTR = "Print::write(data,len) should be overridden for better efficiency\r\n";
static bool once = false;
if (!once) {
once = true;
os_printf_plus(not_the_best_way);
}
#endif
IAMSLOW();
size_t n = 0;
while (size--) {

View File

@ -111,6 +111,10 @@ class Print {
size_t println(void);
virtual void flush() { /* Empty implementation for backward compatibility */ }
// by default write timeout is possible (outgoing data from network,serial..)
// (children can override to false (like String))
virtual bool outputCanTimeout () { return true; }
};
template<> size_t Print::printNumber(double number, uint8_t digits);

View File

@ -22,6 +22,7 @@
#include <Arduino.h>
#include <Stream.h>
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
@ -210,6 +211,8 @@ float Stream::parseFloat(char skipChar) {
// the buffer is NOT null terminated.
//
size_t Stream::readBytes(char *buffer, size_t length) {
IAMSLOW();
size_t count = 0;
while(count < length) {
int c = timedRead();
@ -258,3 +261,20 @@ String Stream::readStringUntil(char terminator) {
}
return ret;
}
// read what can be read, immediate exit on unavailable data
// prototype similar to Arduino's `int Client::read(buf, len)`
int Stream::read (uint8_t* buffer, size_t maxLen)
{
IAMSLOW();
size_t nbread = 0;
while (nbread < maxLen && available())
{
int c = read();
if (c == -1)
break;
buffer[nbread++] = read();
}
return nbread;
}

View File

@ -22,10 +22,13 @@
#ifndef Stream_h
#define Stream_h
#include <debug.h>
#include <inttypes.h>
#include "Print.h"
#include <Print.h>
#include <PolledTimeout.h>
#include <sys/types.h> // ssize_t
// compatability macros for testing
// compatibility macros for testing
/*
#define getInt() parseInt()
#define getInt(skipChar) parseInt(skipchar)
@ -35,6 +38,15 @@
readBytesBetween( pre_string, terminator, buffer, length)
*/
// Arduino `Client: public Stream` class defines `virtual int read(uint8_t *buf, size_t size) = 0;`
// This function is now imported into `Stream::` for `Stream::send*()`.
// Other classes inheriting from `Stream::` and implementing `read(uint8_t *buf, size_t size)`
// must consequently use `int` as return type, namely Hardware/SoftwareSerial, FileSystems...
#define STREAM_READ_RETURNS_INT 1
// Stream::send API is present
#define STREAMSEND_API 1
class Stream: public Print {
protected:
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
@ -53,6 +65,7 @@ class Stream: public Print {
// parsing methods
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
unsigned long getTimeout () const { return _timeout; }
bool find(const char *target); // reads data from the stream until the target string is found
bool find(uint8_t *target) {
@ -102,12 +115,114 @@ class Stream: public Print {
virtual String readString();
String readStringUntil(char terminator);
virtual int read (uint8_t* buffer, size_t len);
int read (char* buffer, size_t len) { return read((uint8_t*)buffer, len); }
//////////////////// extension: direct access to input buffer
// to provide when possible a pointer to available data for read
// informs user and ::to*() on effective buffered peek API implementation
// by default: not available
virtual bool hasPeekBufferAPI () const { return false; }
// returns number of byte accessible by peekBuffer()
virtual size_t peekAvailable () { return 0; }
// returns a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of ::read()
// - after calling peekBuffer()
// - and before calling peekConsume()
virtual const char* peekBuffer () { return nullptr; }
// consumes bytes after peekBuffer() use
// (then ::read() is allowed)
virtual void peekConsume (size_t consume) { (void)consume; }
// by default read timeout is possible (incoming data from network,serial..)
// children can override to false (like String::)
virtual bool inputCanTimeout () { return true; }
// (outputCanTimeout() is defined in Print::)
////////////////////////
//////////////////// extensions: Streaming streams to streams
// Stream::send*()
//
// Stream::send*() uses 1-copy transfers when peekBuffer API is
// available, or makes a regular transfer through a temporary buffer.
//
// - for efficiency, Stream classes should implement peekAPI when
// possible
// - for an efficient timeout management, Print/Stream classes
// should implement {output,input}CanTimeout()
using oneShotMs = esp8266::polledTimeout::oneShotFastMs;
static constexpr int temporaryStackBufferSize = 64;
// ::send*() methods:
// - always stop before timeout when "no-more-input-possible-data"
// or "no-more-output-possible-data" condition is met
// - always return number of transfered bytes
// When result is 0 or less than requested maxLen, Print::getLastSend()
// contains an error reason.
// transfers already buffered / immediately available data (no timeout)
// returns number of transfered bytes
size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); }
size_t sendAvailable (Print& to) { return sendAvailable(&to); }
// transfers data until timeout
// returns number of transfered bytes
size_t sendAll (Print* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); }
size_t sendAll (Print& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); }
// transfers data until a char is encountered (the char is swallowed but not transfered) with timeout
// returns number of transfered bytes
size_t sendUntil (Print* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); }
size_t sendUntil (Print& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); }
// transfers data until requested size or timeout
// returns number of transfered bytes
size_t sendSize (Print* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); }
size_t sendSize (Print& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); }
// remaining size (-1 by default = unknown)
virtual ssize_t streamRemaining () { return -1; }
enum class Report
{
Success = 0,
TimedOut,
ReadError,
WriteError,
ShortOperation,
};
Report getLastSendReport () const { return _sendReport; }
protected:
long parseInt(char skipChar); // as above but the given skipChar is ignored
// as above but the given skipChar is ignored
size_t sendGeneric (Print* to,
const ssize_t len = -1,
const int readUntilChar = -1,
oneShotMs::timeType timeoutMs = oneShotMs::neverExpires /* neverExpires=>getTimeout() */);
size_t SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
size_t SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
size_t SendGenericRegular(Print* to, const ssize_t len, const oneShotMs::timeType timeoutMs);
void setReport (Report report) { _sendReport = report; }
private:
Report _sendReport = Report::Success;
//////////////////// end of extensions
protected:
long parseInt(char skipChar); // as parseInt() but the given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
float parseFloat(char skipChar); // as above but the given skipChar is ignored
float parseFloat(char skipChar); // as parseFloat() but the given skipChar is ignored
};
#endif

248
cores/esp8266/StreamDev.h Normal file
View File

@ -0,0 +1,248 @@
/*
StreamDev.h - Stream helpers
Copyright (c) 2019 David Gauchard. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __STREAMDEV_H
#define __STREAMDEV_H
#include <limits>
#include <esp_priv.h>
#include <StreamString.h>
///////////////////////////////////////////////
// /dev/null
// - black hole as output, swallow everything, availableForWrite = infinite
// - black hole as input, nothing to read, available = 0
class StreamNull: public Stream
{
public:
// Print
virtual size_t write(uint8_t) override
{
return 1;
}
virtual size_t write(const uint8_t* buffer, size_t size) override
{
(void)buffer;
(void)size;
return size;
}
virtual int availableForWrite() override
{
return std::numeric_limits<int16_t>::max();
}
// Stream
virtual int available() override
{
return 0;
}
virtual int read() override
{
return -1;
}
virtual int peek() override
{
return -1;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
(void)buffer;
(void)len;
return 0;
}
virtual int read(uint8_t* buffer, size_t len) override
{
(void)buffer;
(void)len;
return 0;
}
virtual bool outputCanTimeout() override
{
return false;
}
virtual bool inputCanTimeout() override
{
return false;
}
virtual ssize_t streamRemaining() override
{
return 0;
}
};
///////////////////////////////////////////////
// /dev/zero
// - black hole as output, swallow everything, availableForWrite = infinite
// - big bang as input, gives infinity to read, available = infinite
class StreamZero: public StreamNull
{
protected:
char _zero;
public:
StreamZero(char zero = 0): _zero(zero) { }
// Stream
virtual int available() override
{
return std::numeric_limits<int16_t>::max();
}
virtual int read() override
{
return _zero;
}
virtual int peek() override
{
return _zero;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
memset(buffer, _zero, len);
return len;
}
virtual int read(uint8_t* buffer, size_t len) override
{
memset((char*)buffer, _zero, len);
return len;
}
virtual ssize_t streamRemaining() override
{
return std::numeric_limits<int16_t>::max();
}
};
///////////////////////////////////////////////
// static buffer (in flash or ram)
// - black hole as output, swallow everything, availableForWrite = infinite
// - Stream buffer out as input, resettable
class StreamConstPtr: public StreamNull
{
protected:
const char* _buffer;
size_t _size;
bool _byteAddressable;
size_t _peekPointer = 0;
public:
StreamConstPtr(const String& string): _buffer(string.c_str()), _size(string.length()), _byteAddressable(true) { }
StreamConstPtr(const char* buffer, size_t size): _buffer(buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
StreamConstPtr(const uint8_t* buffer, size_t size): _buffer((const char*)buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
StreamConstPtr(const __FlashStringHelper* buffer, size_t size): _buffer(reinterpret_cast<const char*>(buffer)), _size(size), _byteAddressable(false) { }
StreamConstPtr(const __FlashStringHelper* text): _buffer(reinterpret_cast<const char*>(text)), _size(strlen_P((PGM_P)text)), _byteAddressable(false) { }
void resetPointer(int pointer = 0)
{
_peekPointer = pointer;
}
// Stream
virtual int available() override
{
return peekAvailable();
}
virtual int read() override
{
return _peekPointer < _size ? _buffer[_peekPointer++] : -1;
}
virtual int peek() override
{
return _peekPointer < _size ? _buffer[_peekPointer] : -1;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
if (_peekPointer >= _size)
{
return 0;
}
size_t cpylen = std::min(_size - _peekPointer, len);
memcpy_P(buffer, _buffer + _peekPointer, cpylen); // whether byte adressible is true
_peekPointer += cpylen;
return cpylen;
}
virtual int read(uint8_t* buffer, size_t len) override
{
return readBytes((char*)buffer, len);
}
virtual ssize_t streamRemaining() override
{
return _size;
}
// peekBuffer
virtual bool hasPeekBufferAPI() const override
{
return _byteAddressable;
}
virtual size_t peekAvailable() override
{
return _peekPointer < _size ? _size - _peekPointer : 0;
}
virtual const char* peekBuffer() override
{
return _peekPointer < _size ? _buffer + _peekPointer : nullptr;
}
virtual void peekConsume(size_t consume) override
{
_peekPointer += consume;
}
};
///////////////////////////////////////////////
Stream& operator << (Stream& out, String& string);
Stream& operator << (Stream& out, Stream& stream);
Stream& operator << (Stream& out, StreamString& stream);
Stream& operator << (Stream& out, const char* text);
Stream& operator << (Stream& out, const __FlashStringHelper* text);
///////////////////////////////////////////////
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
extern StreamNull devnull;
#endif
#endif // __STREAMDEV_H

View File

@ -0,0 +1,383 @@
/*
StreamDev.cpp - 1-copy transfer related methods
Copyright (c) 2019 David Gauchard. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
parsing functions based on TextFinder library by Michael Margolis
*/
#include <Arduino.h>
#include <StreamDev.h>
size_t Stream::sendGeneric(Print* to,
const ssize_t len,
const int readUntilChar,
const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
setReport(Report::Success);
if (len == 0)
{
return 0; // conveniently avoids timeout for no requested data
}
// There are two timeouts:
// - read (network, serial, ...)
// - write (network, serial, ...)
// However
// - getTimeout() is for reading only
// - there is no getOutputTimeout() api
// So we use getTimeout() for both,
// (also when inputCanTimeout() is false)
if (hasPeekBufferAPI())
{
return SendGenericPeekBuffer(to, len, readUntilChar, timeoutMs);
}
if (readUntilChar >= 0)
{
return SendGenericRegularUntil(to, len, readUntilChar, timeoutMs);
}
return SendGenericRegular(to, len, timeoutMs);
}
size_t Stream::SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avpk = peekAvailable();
if (avpk == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
{
// no more data can be written, ever
break;
}
w = std::min(w, avpk);
if (maxLen)
{
w = std::min(w, maxLen - written);
}
if (w)
{
const char* directbuf = peekBuffer();
bool foundChar = false;
if (readUntilChar >= 0)
{
const char* last = (const char*)memchr(directbuf, readUntilChar, w);
if (last)
{
w = std::min((size_t)(last - directbuf), w);
foundChar = true;
}
}
if (w && ((w = to->write(directbuf, w))))
{
peekConsume(w);
written += w;
if (maxLen)
{
timedOut.reset();
}
}
if (foundChar)
{
peekConsume(1);
break;
}
}
if (!w && !maxLen && readUntilChar < 0)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
size_t Stream::SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// regular Stream API
// no other choice than reading byte by byte
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avr = available();
if (avr == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
{
// no more data can be written, ever
break;
}
int c = read();
if (c != -1)
{
if (c == readUntilChar)
{
break;
}
w = to->write(c);
if (w != 1)
{
setReport(Report::WriteError);
break;
}
written += 1;
if (maxLen)
{
timedOut.reset();
}
}
if (!w && !maxLen && readUntilChar < 0)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
size_t Stream::SendGenericRegular(Print* to, const ssize_t len, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// regular Stream API
// use an intermediary buffer
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avr = available();
if (avr == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
// no more data can be written, ever
{
break;
}
w = std::min(w, avr);
if (maxLen)
{
w = std::min(w, maxLen - written);
}
w = std::min(w, (decltype(w))temporaryStackBufferSize);
if (w)
{
char temp[w];
ssize_t r = read(temp, w);
if (r < 0)
{
setReport(Report::ReadError);
break;
}
w = to->write(temp, r);
written += w;
if ((size_t)r != w)
{
setReport(Report::WriteError);
break;
}
if (maxLen && w)
{
timedOut.reset();
}
}
if (!w && !maxLen)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
Stream& operator << (Stream& out, String& string)
{
StreamConstPtr(string).sendAll(out);
return out;
}
Stream& operator << (Stream& out, StreamString& stream)
{
stream.sendAll(out);
return out;
}
Stream& operator << (Stream& out, Stream& stream)
{
if (stream.streamRemaining() < 0)
{
if (stream.inputCanTimeout())
{
// restrict with only what's buffered on input
stream.sendAvailable(out);
}
else
{
// take all what is in input
stream.sendAll(out);
}
}
else
{
stream.sendSize(out, stream.streamRemaining());
}
return out;
}
Stream& operator << (Stream& out, const char* text)
{
StreamConstPtr(text).sendAll(out);
return out;
}
Stream& operator << (Stream& out, const __FlashStringHelper* text)
{
StreamConstPtr(text).sendAll(out);
return out;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
StreamNull devnull;
#endif

View File

@ -1,68 +0,0 @@
/**
StreamString.cpp
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "StreamString.h"
size_t StreamString::write(const uint8_t *data, size_t size) {
if(size && data) {
const unsigned int newlen = length() + size;
if(reserve(newlen + 1)) {
memcpy((void *) (wbuffer() + len()), (const void *) data, size);
setLen(newlen);
*(wbuffer() + newlen) = 0x00; // add null for string end
return size;
}
DEBUGV(":stream2string: OOM (%d->%d)\n", length(), newlen+1);
}
return 0;
}
size_t StreamString::write(uint8_t data) {
return concat((char) data);
}
int StreamString::available() {
return length();
}
int StreamString::read() {
if(length()) {
char c = charAt(0);
remove(0, 1);
return c;
}
return -1;
}
int StreamString::peek() {
if(length()) {
char c = charAt(0);
return c;
}
return -1;
}
void StreamString::flush() {
}

View File

@ -1,39 +1,293 @@
/**
StreamString.h
StreamString.h
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
Copyright (c) 2020 D. Gauchard. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef STREAMSTRING_H_
#define STREAMSTRING_H_
#ifndef __STREAMSTRING_H
#define __STREAMSTRING_H
#include <limits>
#include "WString.h"
class StreamString: public Stream, public String {
///////////////////////////////////////////////////////////////
// S2Stream points to a String and makes it a Stream
// (it is also the helper for StreamString)
class S2Stream: public Stream
{
public:
size_t write(const uint8_t *buffer, size_t size) override;
size_t write(uint8_t data) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
S2Stream(String& string, int peekPointer = -1):
string(&string), peekPointer(peekPointer)
{
}
S2Stream(String* string, int peekPointer = -1):
string(string), peekPointer(peekPointer)
{
}
virtual int available() override
{
return string->length();
}
virtual int availableForWrite() override
{
return std::numeric_limits<int16_t>::max();
}
virtual int read() override
{
if (peekPointer < 0)
{
// consume chars
if (string->length())
{
char c = string->charAt(0);
string->remove(0, 1);
return c;
}
}
else if (peekPointer < (int)string->length())
{
// return pointed and move pointer
return string->charAt(peekPointer++);
}
// everything is read
return -1;
}
virtual size_t write(uint8_t data) override
{
return string->concat((char)data);
}
virtual int read(uint8_t* buffer, size_t len) override
{
if (peekPointer < 0)
{
// string will be consumed
size_t l = std::min(len, (size_t)string->length());
memcpy(buffer, string->c_str(), l);
string->remove(0, l);
return l;
}
if (peekPointer >= (int)string->length())
{
return 0;
}
// only the pointer is moved
size_t l = std::min(len, (size_t)(string->length() - peekPointer));
memcpy(buffer, string->c_str() + peekPointer, l);
peekPointer += l;
return l;
}
virtual size_t write(const uint8_t* buffer, size_t len) override
{
return string->concat((const char*)buffer, len) ? len : 0;
}
virtual int peek() override
{
if (peekPointer < 0)
{
if (string->length())
{
return string->charAt(0);
}
}
else if (peekPointer < (int)string->length())
{
return string->charAt(peekPointer);
}
return -1;
}
virtual void flush() override
{
// nothing to do
}
virtual bool inputCanTimeout() override
{
return false;
}
virtual bool outputCanTimeout() override
{
return false;
}
//// Stream's peekBufferAPI
virtual bool hasPeekBufferAPI() const override
{
return true;
}
virtual size_t peekAvailable()
{
if (peekPointer < 0)
{
return string->length();
}
return string->length() - peekPointer;
}
virtual const char* peekBuffer() override
{
if (peekPointer < 0)
{
return string->c_str();
}
if (peekPointer < (int)string->length())
{
return string->c_str() + peekPointer;
}
return nullptr;
}
virtual void peekConsume(size_t consume) override
{
if (peekPointer < 0)
{
// string is really consumed
string->remove(0, consume);
}
else
{
// only the pointer is moved
peekPointer = std::min((size_t)string->length(), peekPointer + consume);
}
}
virtual ssize_t streamRemaining() override
{
return peekPointer < 0 ? string->length() : string->length() - peekPointer;
}
// calling setConsume() will consume bytes as the stream is read
// (enabled by default)
void setConsume()
{
peekPointer = -1;
}
// Reading this stream will mark the string as read without consuming
// (not enabled by default)
// Calling resetPointer() resets the read state and allows rereading.
void resetPointer(int pointer = 0)
{
peekPointer = pointer;
}
protected:
String* string;
int peekPointer; // -1:String is consumed / >=0:resettable pointer
};
#endif /* STREAMSTRING_H_ */
// StreamString is a S2Stream holding the String
class StreamString: public String, public S2Stream
{
protected:
void resetpp()
{
if (peekPointer > 0)
{
peekPointer = 0;
}
}
public:
StreamString(StreamString&& bro): String(bro), S2Stream(this) { }
StreamString(const StreamString& bro): String(bro), S2Stream(this) { }
// duplicate String contructors and operator=:
StreamString(const char* text = nullptr): String(text), S2Stream(this) { }
StreamString(const String& string): String(string), S2Stream(this) { }
StreamString(const __FlashStringHelper *str): String(str), S2Stream(this) { }
StreamString(String&& string): String(string), S2Stream(this) { }
StreamString(StringSumHelper&& sum): String(sum), S2Stream(this) { }
explicit StreamString(char c): String(c), S2Stream(this) { }
explicit StreamString(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { }
explicit StreamString(int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
explicit StreamString(unsigned int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
explicit StreamString(long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
explicit StreamString(unsigned long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
explicit StreamString(float f, unsigned char decimalPlaces = 2): String(f, decimalPlaces), S2Stream(this) { }
explicit StreamString(double d, unsigned char decimalPlaces = 2): String(d, decimalPlaces), S2Stream(this) { }
StreamString& operator= (const StreamString& rhs)
{
String::operator=(rhs);
resetpp();
return *this;
}
StreamString& operator= (const String& rhs)
{
String::operator=(rhs);
resetpp();
return *this;
}
StreamString& operator= (const char* cstr)
{
String::operator=(cstr);
resetpp();
return *this;
}
StreamString& operator= (const __FlashStringHelper* str)
{
String::operator=(str);
resetpp();
return *this;
}
StreamString& operator= (String&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
StreamString& operator= (StringSumHelper&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
};
#endif // __STREAMSTRING_H

View File

@ -22,6 +22,13 @@
#include "debug.h"
#include "osapi.h"
#ifdef DEBUG_ESP_CORE
void __iamslow(const char* what)
{
DEBUGV("%s should be overridden for better efficiency\r\n", what);
}
#endif
IRAM_ATTR
void hexdump(const void *mem, uint32_t len, uint8_t cols)
{

View File

@ -26,6 +26,20 @@ void __unhandled_exception(const char *str) __attribute__((noreturn));
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)
#ifdef DEBUG_ESP_CORE
extern void __iamslow(const char* what);
#define IAMSLOW() \
do { \
static bool once = false; \
if (!once) { \
once = true; \
__iamslow((PGM_P)FPSTR(__FUNCTION__)); \
} \
} while (0)
#else
#define IAMSLOW() do { (void)0; } while (0)
#endif
#ifdef __cplusplus
}
#endif

44
cores/esp8266/esp_priv.h Normal file
View File

@ -0,0 +1,44 @@
/*
esp_priv.h - private esp8266 helpers
Copyright (c) 2020 esp8266/Arduino community. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __ESP_PRIV
#define __ESP_PRIV
#if defined(CORE_MOCK)
constexpr bool __byteAddressable(const void* addr)
{
(void)addr;
return true;
}
#else // on hardware
#include <sys/config.h>
// returns true when addr can be used without "pgm_" functions or non32xfer service
constexpr bool __byteAddressable(const void* addr)
{
return addr < (const void*)(XCHAL_DATARAM0_VADDR + XCHAL_DATARAM0_SIZE);
}
#endif // on hardware
#endif // __ESP_PRIV

View File

@ -388,10 +388,10 @@ public:
return result;
}
size_t read(uint8_t* buf, size_t size) override
int read(uint8_t* buf, size_t size) override
{
CHECKFD();
auto result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size);
int result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size);
if (result < 0) {
DEBUGV("SPIFFS_read rc=%d\r\n", result);
return 0;

View File

@ -240,6 +240,41 @@ uart_peek_char(uart_t* uart)
return ret;
}
// return number of byte accessible by uart_peek_buffer()
size_t uart_peek_available (uart_t* uart)
{
// path for further optimization:
// - return already copied buffer pointer (= older data)
// - or return fifo when buffer is empty but then any move from fifo to
// buffer should be blocked until peek_consume is called
ETS_UART_INTR_DISABLE();
uart_rx_copy_fifo_to_buffer_unsafe(uart);
auto rpos = uart->rx_buffer->rpos;
auto wpos = uart->rx_buffer->wpos;
ETS_UART_INTR_ENABLE();
if(wpos < rpos)
return uart->rx_buffer->size - rpos;
return wpos - rpos;
}
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() between peekBuffer() and peekConsume()
const char* uart_peek_buffer (uart_t* uart)
{
return (const char*)&uart->rx_buffer->buffer[uart->rx_buffer->rpos];
}
// consume bytes after use (see uart_peek_buffer)
void uart_peek_consume (uart_t* uart, size_t consume)
{
ETS_UART_INTR_DISABLE();
uart->rx_buffer->rpos += consume;
if (uart->rx_buffer->rpos >= uart->rx_buffer->size)
uart->rx_buffer->rpos -= uart->rx_buffer->size;
ETS_UART_INTR_ENABLE();
}
int
uart_read_char(uart_t* uart)
{

View File

@ -147,6 +147,16 @@ int uart_get_debug();
void uart_start_detect_baudrate(int uart_nr);
int uart_detect_baudrate(int uart_nr);
// return number of byte accessible by peekBuffer()
size_t uart_peek_available (uart_t* uart);
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() before calling peekConsume()
const char* uart_peek_buffer (uart_t* uart);
// consume bytes after use (see peekBuffer)
void uart_peek_consume (uart_t* uart, size_t consume);
uint8_t uart_get_bit_length(const int uart_nr);
#if defined (__cplusplus)