1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-05-12 14:41:29 +03:00

Merge branch 'master' into master

This commit is contained in:
Earle F. Philhower, III 2021-05-15 11:16:02 -07:00 committed by GitHub
commit dc44227eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
216 changed files with 19077 additions and 2024 deletions
boards.txt
cores/esp8266
doc
libraries

File diff suppressed because it is too large Load Diff

@ -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;

@ -29,7 +29,6 @@
#include "coredecls.h"
#include "umm_malloc/umm_malloc.h"
// #include "core_esp8266_vm.h"
#include <pgmspace.h>
#include "reboot_uart_dwnld.h"
@ -526,7 +525,7 @@ bool EspClass::eraseConfig(void) {
return true;
}
uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) const
uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes)
{
/**
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
@ -576,7 +575,7 @@ uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) co
return resultArray;
}
uint32_t EspClass::random() const
uint32_t EspClass::random()
{
union { uint32_t b32; uint8_t b8[4]; } result;
random(result.b8, 4);
@ -984,22 +983,11 @@ String EspClass::getSketchMD5()
return result;
}
void EspClass::enableVM()
{
#ifdef UMM_HEAP_EXTERNAL
if (!vmEnabled)
install_vm_exception_handler();
vmEnabled = true;
#endif
}
void EspClass::setExternalHeap()
{
#ifdef UMM_HEAP_EXTERNAL
if (vmEnabled) {
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
panic();
}
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
panic();
}
#endif
}
@ -1016,10 +1004,8 @@ void EspClass::setIramHeap()
void EspClass::setDramHeap()
{
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
if (vmEnabled) {
if (!umm_push_heap(UMM_HEAP_DRAM)) {
panic();
}
if (!umm_push_heap(UMM_HEAP_DRAM)) {
panic();
}
#elif defined(UMM_HEAP_IRAM)
if (!umm_push_heap(UMM_HEAP_DRAM)) {
@ -1031,10 +1017,8 @@ void EspClass::setDramHeap()
void EspClass::resetHeap()
{
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
if (vmEnabled) {
if (!umm_pop_heap()) {
panic();
}
if (!umm_pop_heap()) {
panic();
}
#elif defined(UMM_HEAP_IRAM)
if (!umm_pop_heap()) {

@ -87,75 +87,75 @@ typedef enum {
class EspClass {
public:
// TODO: figure out how to set WDT timeout
void wdtEnable(uint32_t timeout_ms = 0);
static void wdtEnable(uint32_t timeout_ms = 0);
// note: setting the timeout value is not implemented at the moment
void wdtEnable(WDTO_t timeout_ms = WDTO_0MS);
static void wdtEnable(WDTO_t timeout_ms = WDTO_0MS);
void wdtDisable();
void wdtFeed();
static void wdtDisable();
static void wdtFeed();
void deepSleep(uint64_t time_us, RFMode mode = RF_DEFAULT);
void deepSleepInstant(uint64_t time_us, RFMode mode = RF_DEFAULT);
uint64_t deepSleepMax();
static void deepSleep(uint64_t time_us, RFMode mode = RF_DEFAULT);
static void deepSleepInstant(uint64_t time_us, RFMode mode = RF_DEFAULT);
static uint64_t deepSleepMax();
bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size);
bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size);
static bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size);
static bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size);
void reset();
void restart();
static void reset();
static void restart();
/**
* @brief When calling this method the ESP8266 reboots into the UART download mode without
* the need of any external wiring. This is the same mode which can also be entered by
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
*/
[[noreturn]] void rebootIntoUartDownloadMode();
[[noreturn]] static void rebootIntoUartDownloadMode();
uint16_t getVcc();
uint32_t getChipId();
static uint16_t getVcc();
static uint32_t getChipId();
uint32_t getFreeHeap();
uint16_t getMaxFreeBlockSize();
uint8_t getHeapFragmentation(); // in %
void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr);
static uint32_t getFreeHeap();
static uint16_t getMaxFreeBlockSize();
static uint8_t getHeapFragmentation(); // in %
static void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr);
uint32_t getFreeContStack();
void resetFreeContStack();
static uint32_t getFreeContStack();
static void resetFreeContStack();
const char * getSdkVersion();
String getCoreVersion();
String getFullVersion();
static const char * getSdkVersion();
static String getCoreVersion();
static String getFullVersion();
uint8_t getBootVersion();
uint8_t getBootMode();
static uint8_t getBootVersion();
static uint8_t getBootMode();
#if defined(F_CPU) || defined(CORE_MOCK)
constexpr
#endif
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
static inline uint8_t getCpuFreqMHz() __attribute__((always_inline))
{
return esp_get_cpu_freq_mhz();
}
uint32_t getFlashChipId();
uint8_t getFlashChipVendorId();
static uint32_t getFlashChipId();
static uint8_t getFlashChipVendorId();
//gets the actual chip size based on the flash id
uint32_t getFlashChipRealSize();
static uint32_t getFlashChipRealSize();
//gets the size of the flash as set by the compiler
uint32_t getFlashChipSize();
uint32_t getFlashChipSpeed();
FlashMode_t getFlashChipMode();
uint32_t getFlashChipSizeByChipId();
static uint32_t getFlashChipSize();
static uint32_t getFlashChipSpeed();
static FlashMode_t getFlashChipMode();
static uint32_t getFlashChipSizeByChipId();
uint32_t magicFlashChipSize(uint8_t byte);
uint32_t magicFlashChipSpeed(uint8_t byte);
FlashMode_t magicFlashChipMode(uint8_t byte);
static uint32_t magicFlashChipSize(uint8_t byte);
static uint32_t magicFlashChipSpeed(uint8_t byte);
static FlashMode_t magicFlashChipMode(uint8_t byte);
bool checkFlashConfig(bool needsEquals = false);
static bool checkFlashConfig(bool needsEquals = false);
bool checkFlashCRC();
static bool checkFlashCRC();
bool flashEraseSector(uint32_t sector);
static bool flashEraseSector(uint32_t sector);
/**
* @brief Write @a size bytes from @a data to flash at @a address
* This overload requires @a data and @a size to be always 4 byte aligned and
@ -168,7 +168,7 @@ class EspClass {
* @retval true success
* @retval false failure to write to flash or incorrect alignment of params
*/
bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
static bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
/**
* @brief Write @a size bytes from @a data to flash at @a address
* This overload handles all misalignment cases
@ -177,7 +177,7 @@ class EspClass {
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
* @return bool result of operation
*/
bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
static bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
/**
* @brief Read @a size bytes to @a data to flash at @a address
* This overload requires @a data and @a size to be 4 byte aligned
@ -188,7 +188,7 @@ class EspClass {
* @retval true success
* @retval false failure to read from flash or incorrect alignment of params
*/
bool flashRead(uint32_t address, uint32_t *data, size_t size);
static bool flashRead(uint32_t address, uint32_t *data, size_t size);
/**
* @brief Read @a size bytes to @a data to flash at @a address
* This overload handles all misalignment cases
@ -197,58 +197,51 @@ class EspClass {
* @param size amount of data, passing not multiple of 4 will cause additional read
* @return bool result of operation
*/
bool flashRead(uint32_t address, uint8_t *data, size_t size);
static bool flashRead(uint32_t address, uint8_t *data, size_t size);
uint32_t getSketchSize();
String getSketchMD5();
uint32_t getFreeSketchSpace();
bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true);
static uint32_t getSketchSize();
static String getSketchMD5();
static uint32_t getFreeSketchSpace();
static bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true);
String getResetReason();
String getResetInfo();
struct rst_info * getResetInfoPtr();
static String getResetReason();
static String getResetInfo();
static struct rst_info * getResetInfoPtr();
bool eraseConfig();
static bool eraseConfig();
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
uint32_t random() const;
static uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes);
static uint32_t random();
#if !defined(CORE_MOCK)
inline uint32_t getCycleCount() __attribute__((always_inline))
static inline uint32_t getCycleCount() __attribute__((always_inline))
{
return esp_get_cycle_count();
}
#else
uint32_t getCycleCount();
static uint32_t getCycleCount();
#endif // !defined(CORE_MOCK)
/**
* @brief Installs VM exception handler to support External memory (Experimental)
*
* @param none
* @return none
*/
void enableVM();
/**
* @brief Push current Heap selection and set Heap selection to DRAM.
*
* @param none
* @return none
*/
void setDramHeap();
static void setDramHeap();
/**
* @brief Push current Heap selection and set Heap selection to IRAM.
*
* @param none
* @return none
*/
void setIramHeap();
static void setIramHeap();
/**
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
*
* @param none
* @return none
*/
void setExternalHeap();
static void setExternalHeap();
/**
* @brief Restores Heap selection back to value present when
* setDramHeap, setIramHeap, or setExternalHeap was called.
@ -256,11 +249,8 @@ class EspClass {
* @param none
* @return none
*/
void resetHeap();
static void resetHeap();
private:
#ifdef UMM_HEAP_EXTERNAL
bool vmEnabled = false;
#endif
/**
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
*
@ -271,7 +261,7 @@ class EspClass {
* @retval true success
* @retval false failed to read/write or invalid args
*/
bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
static bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
/**
* @brief Write up to @a size bytes from @a data to flash at @a address
* This function takes case of unaligned memory acces by copying @a data to a temporary buffer,
@ -282,7 +272,7 @@ class EspClass {
* @param size amount of data
* @return size_t amount of data written, 0 on failure
*/
size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
static size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
/**
* @brief Splits up to 4 bytes into 4 byte blocks and writes them to flash
* We need this since spi_flash_write cannot handle writing over a page boundary with unaligned offset
@ -293,7 +283,7 @@ class EspClass {
* @param size amount of data, must be < 4
* @return bool result of operation
*/
bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
static bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
};
extern EspClass ESP;

@ -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;

@ -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;

@ -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;

@ -10,7 +10,7 @@ typedef void (*voidFuncPtrArg)(void*);
extern "C" void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtr userFunc, void*fp, int mode, bool functional);
void ICACHE_RAM_ATTR interruptFunctional(void* arg)
void IRAM_ATTR interruptFunctional(void* arg)
{
ArgStructure* localArg = (ArgStructure*)arg;
if (localArg->functionInfo->reqScheduledFunction)

@ -101,31 +101,31 @@ public:
return uart_get_rx_buffer_size(_uart);
}
void swap()
bool swap()
{
swap(1);
return swap(1);
}
void swap(uint8_t tx_pin) //toggle between use of GPIO13/GPIO15 or GPIO3/GPIO(1/2) as RX and TX
bool swap(uint8_t tx_pin) //toggle between use of GPIO13/GPIO15 or GPIO3/GPIO(1/2) as RX and TX
{
uart_swap(_uart, tx_pin);
return uart_swap(_uart, tx_pin);
}
/*
* Toggle between use of GPIO1 and GPIO2 as TX on UART 0.
* Note: UART 1 can't be used if GPIO2 is used with UART 0!
*/
void set_tx(uint8_t tx_pin)
bool set_tx(uint8_t tx_pin)
{
uart_set_tx(_uart, tx_pin);
return uart_set_tx(_uart, tx_pin);
}
/*
* UART 0 possible options are (1, 3), (2, 3) or (15, 13)
* UART 1 allows only TX on 2 if UART 0 is not (2, 3)
*/
void pins(uint8_t tx, uint8_t rx)
bool pins(uint8_t tx, uint8_t rx)
{
uart_set_pins(_uart, tx, rx);
return uart_set_pins(_uart, tx, rx);
}
int available(void) override;
@ -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
{

@ -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--) {

@ -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);

@ -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;
}

@ -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

250
cores/esp8266/StreamDev.h Normal file

@ -0,0 +1,250 @@
/*
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
{
// valid with dram, iram and flash
return _peekPointer < _size ? pgm_read_byte(&_buffer[_peekPointer++]) : -1;
}
virtual int peek() override
{
// valid with dram, iram and flash
return _peekPointer < _size ? pgm_read_byte(&_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

@ -0,0 +1,356 @@
/*
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;
timedOut.reset(); // something has been written
}
if (foundChar)
{
peekConsume(1);
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;
timedOut.reset(); // something has been written
}
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;
}
timedOut.reset(); // something has been written
}
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

@ -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() {
}

@ -1,39 +1,285 @@
/**
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) { }
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;
}
};
#endif // __STREAMSTRING_H

@ -1,6 +1,7 @@
#include "Updater.h"
#include "eboot_command.h"
#include <esp8266_peri.h>
#include <PolledTimeout.h>
#include "StackThunk.h"
//#define DEBUG_UPDATER Serial
@ -476,7 +477,7 @@ bool UpdaterClass::_verifyEnd() {
return false;
}
size_t UpdaterClass::writeStream(Stream &data) {
size_t UpdaterClass::writeStream(Stream &data, uint16_t streamTimeout) {
size_t written = 0;
size_t toRead = 0;
if(hasError() || !isRunning())
@ -489,6 +490,7 @@ size_t UpdaterClass::writeStream(Stream &data) {
_reset();
return 0;
}
esp8266::polledTimeout::oneShotMs timeOut(streamTimeout);
if (_progress_callback) {
_progress_callback(0, _size);
}
@ -506,13 +508,15 @@ size_t UpdaterClass::writeStream(Stream &data) {
}
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) { //Timeout
delay(100);
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) { //Timeout
_currentAddress = (_startAddress + _size);
_setError(UPDATE_ERROR_STREAM);
return written;
}
if (timeOut) {
_currentAddress = (_startAddress + _size);
_setError(UPDATE_ERROR_STREAM);
_reset();
return written;
}
delay(100);
} else {
timeOut.reset();
}
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // Switch LED off

@ -83,7 +83,7 @@ class UpdaterClass {
Should be equal to the remaining bytes when called
Usable for slow streams like Serial
*/
size_t writeStream(Stream &data);
size_t writeStream(Stream &data, uint16_t streamTimeout = 60000);
/*
If all bytes are written

@ -21,10 +21,16 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "Arduino.h"
#include "WString.h"
#include "stdlib_noniso.h"
#define OOM_STRING_BORDER_DISPLAY 10
#define OOM_STRING_THRESHOLD_REALLOC_WARN 128
#define __STRHELPER(x) #x
#define STR(x) __STRHELPER(x) // stringifier
/*********************************************/
/* Constructors */
/*********************************************/
@ -50,11 +56,6 @@ String::String(String &&rval) noexcept {
move(rval);
}
String::String(StringSumHelper &&rval) noexcept {
init();
move(rval);
}
String::String(unsigned char value, unsigned char base) {
init();
char buf[1 + 8 * sizeof(unsigned char)];
@ -178,6 +179,14 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
}
// Fallthrough to normal allocator
size_t newSize = (maxStrLen + 16) & (~0xf);
#ifdef DEBUG_ESP_OOM
if (!isSSO() && capacity() >= OOM_STRING_THRESHOLD_REALLOC_WARN && maxStrLen > capacity()) {
// warn when badly re-allocating
DEBUGV("[offending String op %d->%d ('%." STR(OOM_STRING_BORDER_DISPLAY) "s ... %." STR(OOM_STRING_BORDER_DISPLAY) "s')]\n",
len(), maxStrLen, c_str(),
len() > OOM_STRING_BORDER_DISPLAY? c_str() + std::max((int)len() - OOM_STRING_BORDER_DISPLAY, OOM_STRING_BORDER_DISPLAY): "");
}
#endif
// Make sure we can fit newsize in the buffer
if (newSize > CAPACITY_MAX) {
return 0;
@ -376,98 +385,92 @@ unsigned char String::concat(const __FlashStringHelper *str) {
}
/*********************************************/
/* Concatenate */
/* Insert */
/*********************************************/
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs.buffer(), rhs.len()))
a.invalidate();
return a;
String &String::insert(size_t position, const char *other, size_t other_length) {
if (position > length())
return *this;
auto len = length();
auto total = len + other_length;
if (!reserve(total))
return *this;
auto left = len - position;
setLen(total);
auto *start = wbuffer() + position;
memmove(start + other_length, start, left);
memmove_P(start, other, other_length);
wbuffer()[total] = '\0';
return *this;
}
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!cstr || !a.concat(cstr, strlen(cstr)))
a.invalidate();
return a;
String &String::insert(size_t position, const __FlashStringHelper *other) {
auto *p = reinterpret_cast<const char*>(other);
return insert(position, p, strlen_P(p));
}
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(c))
a.invalidate();
return a;
String &String::insert(size_t position, char other) {
char tmp[2] { other, '\0' };
return insert(position, tmp, 1);
}
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, const char *other) {
return insert(position, other, strlen(other));
}
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, const String &other) {
return insert(position, other.c_str(), other.length());
}
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(const String &lhs, String &&rhs) {
String res;
auto total = lhs.length() + rhs.length();
if (rhs.capacity() > total) {
rhs.insert(0, lhs);
res = std::move(rhs);
} else {
res.reserve(total);
res += lhs;
res += rhs;
rhs.invalidate();
}
return res;
}
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(String &&lhs, String &&rhs) {
String res;
auto total = lhs.length() + rhs.length();
if ((total > lhs.capacity()) && (total < rhs.capacity())) {
rhs.insert(0, lhs);
res = std::move(rhs);
} else {
lhs += rhs;
rhs.invalidate();
res = std::move(lhs);
}
return res;
}
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(char lhs, const String &rhs) {
String res;
res.reserve(rhs.length() + 1);
res += lhs;
res += rhs;
return res;
}
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs))
a.invalidate();
return a;
String operator +(const char *lhs, const String &rhs) {
String res;
res.reserve(strlen_P(lhs) + rhs.length());
res += lhs;
res += rhs;
return res;
}
/*********************************************/

@ -23,14 +23,15 @@
#define String_class_h
#ifdef __cplusplus
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pgmspace.h>
// An inherited class for holding the result of a concatenation. These
// result objects are assumed to be writable by subsequent concatenations.
class StringSumHelper;
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cctype>
#include <utility>
#include <type_traits>
// an abstract class used as a means to proide a unique pointer type
// but really has no body
@ -38,6 +39,10 @@ class __FlashStringHelper;
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal)))
// support libraries that expect this name to be available
// replace with `using StringSumHelper = String;` in case something wants this constructible
class StringSumHelper;
// The string class
class String {
// use a function pointer to allow for "if (s)" without the
@ -60,7 +65,6 @@ class String {
String(const String &str);
String(const __FlashStringHelper *str);
String(String &&rval) noexcept;
String(StringSumHelper &&rval) noexcept;
explicit String(char c) {
sso.buff[0] = c;
sso.buff[1] = 0;
@ -104,8 +108,10 @@ class String {
String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept;
String &operator =(StringSumHelper &&rval) noexcept {
return operator =((String &&)rval);
String &operator =(char c) {
char buffer[2] { c, '\0' };
*this = buffer;
return *this;
}
// concatenate (works w/ built-in types)
@ -130,72 +136,11 @@ class String {
// if there's not enough memory for the concatenated value, the string
// will be left unchanged (but this isn't signalled in any way)
String &operator +=(const String &rhs) {
template <typename T>
String &operator +=(const T &rhs) {
concat(rhs);
return *this;
}
String &operator +=(const char *cstr) {
concat(cstr);
return *this;
}
String &operator +=(char c) {
concat(c);
return *this;
}
String &operator +=(unsigned char num) {
concat(num);
return *this;
}
String &operator +=(int num) {
concat(num);
return *this;
}
String &operator +=(unsigned int num) {
concat(num);
return *this;
}
String &operator +=(long num) {
concat(num);
return *this;
}
String &operator +=(unsigned long num) {
concat(num);
return *this;
}
String &operator +=(long long num) {
concat(num);
return *this;
}
String &operator +=(unsigned long long num) {
concat(num);
return *this;
}
String &operator +=(float num) {
concat(num);
return *this;
}
String &operator +=(double num) {
concat(num);
return *this;
}
String &operator +=(const __FlashStringHelper *str) {
concat(str);
return *this;
}
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
// comparison (only works w/ Strings and "strings")
operator StringIfHelperType() const {
@ -338,6 +283,14 @@ class String {
const char *buffer() const { return wbuffer(); }
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
// concatenation is done via non-member functions
// make sure we still have access to internal methods, since we optimize based on capacity of both sides and want to manipulate internal buffers directly
friend String operator +(const String &lhs, String &&rhs);
friend String operator +(String &&lhs, String &&rhs);
friend String operator +(char lhs, String &&rhs);
friend String operator +(const char *lhs, String &&rhs);
friend String operator +(const __FlashStringHelper *lhs, String &&rhs);
protected:
void init(void) __attribute__((always_inline)) {
sso.buff[0] = 0;
@ -359,54 +312,81 @@ class String {
void invalidate(void);
unsigned char changeBuffer(unsigned int maxStrLen);
// copy and move
// copy or insert at a specific position
String &copy(const char *cstr, unsigned int length);
String &copy(const __FlashStringHelper *pstr, unsigned int length);
String &insert(size_t position, char);
String &insert(size_t position, const char *);
String &insert(size_t position, const __FlashStringHelper *);
String &insert(size_t position, const char *, size_t length);
String &insert(size_t position, const String &);
// rvalue helper
void move(String &rhs) noexcept;
};
class StringSumHelper: public String {
public:
StringSumHelper(const String &s) :
String(s) {
}
StringSumHelper(const char *p) :
String(p) {
}
StringSumHelper(char c) :
String(c) {
}
StringSumHelper(unsigned char num) :
String(num) {
}
StringSumHelper(int num) :
String(num) {
}
StringSumHelper(unsigned int num) :
String(num) {
}
StringSumHelper(long num) :
String(num) {
}
StringSumHelper(unsigned long num) :
String(num) {
}
StringSumHelper(long long num) :
String(num) {
}
StringSumHelper(unsigned long long num) :
String(num) {
}
StringSumHelper(float num) :
String(num) {
}
StringSumHelper(double num) :
String(num) {
}
StringSumHelper(const __FlashStringHelper *s) :
String(s) {
}
};
// concatenation (note that it's done using non-method operators to handle both possible type refs)
inline String operator +(const String &lhs, const String &rhs) {
String res;
res.reserve(lhs.length() + rhs.length());
res += lhs;
res += rhs;
return res;
}
inline String operator +(String &&lhs, const String &rhs) {
lhs += rhs;
return std::move(lhs);
}
String operator +(const String &lhs, String &&rhs);
String operator +(String &&lhs, String &&rhs);
template <typename T,
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
inline String operator +(const String &lhs, const T &value) {
String res(lhs);
res += value;
return res;
}
template <typename T,
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
inline String operator +(String &&lhs, const T &value) {
lhs += value;
return std::move(lhs);
}
// `String(char)` is explicit, but we used to have StringSumHelper silently allowing the following:
// `String x; x = 'a' + String('b') + 'c';`
// For comparison, `std::string(char)` does not exist. However, we are allowed to chain `char` as both lhs and rhs
String operator +(char lhs, const String &rhs);
inline String operator +(char lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
// both `char*` and `__FlashStringHelper*` are implicitly converted into `String()`, calling the `operator+(const String& ...);`
// however, here we:
// - do an automatic `reserve(total length)` for the resulting string
// - possibly do rhs.insert(0, ...), when &&rhs capacity could fit both
String operator +(const char *lhs, const String &rhs);
inline String operator +(const char *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
inline String operator +(const __FlashStringHelper *lhs, const String &rhs) {
return reinterpret_cast<const char*>(lhs) + rhs;
}
inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
extern const String emptyString;

@ -33,11 +33,23 @@ public:
// NOTE: The default behaviour of backend (lib64)
// is to add a newline every 72 (encoded) characters output.
// This may 'break' longer uris and json variables
static String encode(const uint8_t * data, size_t length, bool doNewLines = true);
static String inline encode(const String& text, bool doNewLines = true)
static String encode(const uint8_t * data, size_t length, bool doNewLines);
static inline String encode(const String& text, bool doNewLines)
{
return encode( (const uint8_t *) text.c_str(), text.length(), doNewLines );
}
// esp32 compat:
static inline String encode(const uint8_t * data, size_t length)
{
return encode(data, length, false);
}
static inline String encode(const String& text)
{
return encode(text, false);
}
private:
};

@ -67,7 +67,7 @@ size_t cbuf::resize(size_t newSize) {
return _size;
}
size_t ICACHE_RAM_ATTR cbuf::available() const {
size_t IRAM_ATTR cbuf::available() const {
if(_end >= _begin) {
return _end - _begin;
}
@ -108,7 +108,7 @@ size_t cbuf::peek(char *dst, size_t size) {
return size_read;
}
int ICACHE_RAM_ATTR cbuf::read() {
int IRAM_ATTR cbuf::read() {
if(empty())
return -1;
@ -133,7 +133,7 @@ size_t cbuf::read(char* dst, size_t size) {
return size_read;
}
size_t ICACHE_RAM_ATTR cbuf::write(char c) {
size_t IRAM_ATTR cbuf::write(char c) {
if(full())
return 0;

@ -42,7 +42,7 @@ void cont_init(cont_t* cont) {
}
}
int ICACHE_RAM_ATTR cont_check(cont_t* cont) {
int IRAM_ATTR cont_check(cont_t* cont) {
if(cont->stack_guard1 != CONT_STACKGUARD || cont->stack_guard2 != CONT_STACKGUARD) return 1;
return 0;
@ -62,7 +62,7 @@ int cont_get_free_stack(cont_t* cont) {
return freeWords * 4;
}
bool ICACHE_RAM_ATTR cont_can_yield(cont_t* cont) {
bool IRAM_ATTR cont_can_yield(cont_t* cont) {
return !ETS_INTR_WITHINISR() &&
cont->pc_ret != 0 && cont->pc_yield == 0;
}

@ -27,6 +27,13 @@ extern "C" void call_user_start();
/* this is the default NONOS-SDK user's heap location */
static cont_t g_cont __attribute__ ((aligned (16)));
#if defined(DEBUG_ESP_HWDT_NOEXTRA4K) || defined(DEBUG_ESP_HWDT)
extern "C" cont_t * IRAM_ATTR get_noextra4k_g_pcont(void)
{
return &g_cont;
}
#else
extern "C" void app_entry_redefinable(void)
{
g_pcont = &g_cont;
@ -34,3 +41,4 @@ extern "C" void app_entry_redefinable(void)
/* Call the entry point of the SDK code. */
call_user_start();
}
#endif

@ -31,6 +31,7 @@
#define CORE_HAS_UMM
#define WIFI_HAS_EVENT_CALLBACK
#define WIFI_IS_OFF_AT_BOOT
#include <stdlib.h> // malloc()
#include <stddef.h> // size_t
@ -121,6 +122,9 @@ inline int esp_get_cpu_freq_mhz()
}
#endif
// Call this function in your setup() to cause the phase locked version of the generator to
// be linked in automatically. Otherwise, the default PWM locked version will be used.
void enablePhaseLockedWaveform(void);
#ifdef __cplusplus
}

@ -24,7 +24,7 @@
#include "osapi.h"
#include "ets_sys.h"
#include "i2s_reg.h"
#include "i2s.h"
#include "core_esp8266_i2s.h"
extern "C" {
@ -61,7 +61,7 @@ typedef struct i2s_state {
uint32_t * curr_slc_buf; // Current buffer for writing
uint32_t curr_slc_buf_pos; // Position in the current buffer
void (*callback) (void);
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
// Callback function should be defined as 'void IRAM_ATTR function_name()',
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
// function, use it to set flags and process later.
bool driveClocks;
@ -139,7 +139,7 @@ uint16_t i2s_rx_available(){
}
// Pop the top off of the queue and return it
static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
static uint32_t * IRAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
uint8_t i;
uint32_t *item = ch->slc_queue[0];
ch->slc_queue_len--;
@ -150,7 +150,7 @@ static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
}
// Append an item to the end of the queue from receive
static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) {
static void IRAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) {
// Shift everything up, except for the one corresponding to this item
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
if (ch->slc_queue[i] != item) {
@ -164,7 +164,7 @@ static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t
}
}
static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
static void IRAM_ATTR i2s_slc_isr(void) {
ETS_SLC_INTR_DISABLE();
uint32_t slc_intr_status = SLCIS;
SLCIC = 0xFFFFFFFF;
@ -194,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
}
void i2s_set_callback(void (*callback) (void)) {
tx->callback = callback;
if (tx) tx->callback = callback;
}
void i2s_rx_set_callback(void (*callback) (void)) {
rx->callback = callback;
if (rx) rx->callback = callback;
}
static bool _alloc_channel(i2s_state_t *ch) {
@ -343,7 +343,7 @@ bool i2s_write_lr(int16_t left, int16_t right){
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
static uint16_t _i2s_write_buffer(const int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
uint16_t frames_written=0;
while(frame_count>0) {
@ -401,13 +401,13 @@ static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mo
return frames_written;
}
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
if (!rx) {

@ -0,0 +1,81 @@
/*
i2s.h - Software I2S library for esp8266
Copyright (c) 2015 Hristo Gochkov. 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
*/
#ifndef I2S_h
#define I2S_h
#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1
/*
How does this work? Basically, to get sound, you need to:
- Connect an I2S codec to the I2S pins on the ESP.
- Start up a thread that's going to do the sound output
- Call i2s_set_bits() if you want to enable 24-bit mode
- Call i2s_begin()
- Call i2s_set_rate() with the sample rate you want.
- Generate sound and call i2s_write_sample() with 32-bit samples.
The 32bit samples basically are 2 16-bit signed values (the analog values for
the left and right channel) concatenated as (Rout<<16)+Lout
i2s_write_sample will block when you're sending data too quickly, so you can just
generate and push data as fast as you can and i2s_write_sample will regulate the
speed.
*/
#ifdef __cplusplus
extern "C" {
#endif
bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin.
// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the
// hardware shifts starting at bit 31, not bit 23.
void i2s_begin(); // Enable TX only, for compatibility
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks);
void i2s_end();
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
float i2s_get_real_rate();//The actual Sample Rate on output
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
bool i2s_is_empty();//returns true if DMA is empty (underflow)
bool i2s_rx_is_full();
bool i2s_rx_is_empty();
uint16_t i2s_available();// returns the number of samples than can be written before blocking
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
void i2s_set_callback(void (*callback) (void));
void i2s_rx_set_callback(void (*callback) (void));
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count);
#ifdef __cplusplus
}
#endif
#endif

@ -35,9 +35,10 @@ extern "C" {
#include <core_version.h>
#include "gdb_hooks.h"
#include "flash_quirks.h"
#include "hwdt_app_entry.h"
#include <umm_malloc/umm_malloc.h>
#include <core_esp8266_non32xfer.h>
#include "core_esp8266_vm.h"
#define LOOP_TASK_PRIORITY 1
#define LOOP_QUEUE_SIZE 1
@ -331,7 +332,18 @@ extern "C" void app_entry (void)
extern "C" void preinit (void) __attribute__((weak));
extern "C" void preinit (void)
{
/* do nothing by default */
/* does nothing, kept for backward compatibility */
}
extern "C" void __disableWiFiAtBootTime (void) __attribute__((weak));
extern "C" void __disableWiFiAtBootTime (void)
{
// Starting from arduino core v3: wifi is disabled at boot time
// WiFi.begin() or WiFi.softAP() will wake WiFi up
wifi_set_opmode_current(0/*WIFI_OFF*/);
wifi_fpm_set_sleep_type(MODEM_SLEEP_T);
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF);
}
extern "C" void user_init(void) {
@ -348,13 +360,23 @@ extern "C" void user_init(void) {
cont_init(g_pcont);
#if defined(DEBUG_ESP_HWDT) || defined(DEBUG_ESP_HWDT_NOEXTRA4K)
debug_hwdt_init();
#endif
#if defined(UMM_HEAP_EXTERNAL)
install_vm_exception_handler();
#endif
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
install_non32xfer_exception_handler();
#endif
#if defined(MMU_IRAM_HEAP)
umm_init_iram();
#endif
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
__disableWiFiAtBootTime(); // default weak function disables WiFi
ets_task(loop_task,
LOOP_TASK_PRIORITY, s_loop_queue,

@ -64,51 +64,10 @@ static
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
{
do {
/*
In adapting the public domain version, a crash would come or go away with
the slightest unrelated changes elsewhere in the function. Observed that
register a15 was used for epc1, then clobbered by `rsr.` I now believe a
"&" on the output register would have resolved the problem.
However, I have refactored the Extended ASM to reduce and consolidate
register usage and corrected the issue.
The positioning of the Extended ASM block (as early as possible in the
compiled function) is in part controlled by the immediate need for
output variable `insn`. This placement aids in getting excvaddr read as
early as possible.
*/
uint32_t insn, excvaddr;
#if 1
{
uint32_t tmp;
__asm__ (
"rsr.excvaddr %[vaddr]\n\t" /* Read faulting address as early as possible */
"movi.n %[tmp], ~3\n\t" /* prepare a mask for the EPC */
"and %[tmp], %[tmp], %[epc]\n\t" /* apply mask for 32-bit aligned base */
"ssa8l %[epc]\n\t" /* set up shift register for src op */
"l32i %[insn], %[tmp], 0\n\t" /* load part 1 */
"l32i %[tmp], %[tmp], 4\n\t" /* load part 2 */
"src %[insn], %[tmp], %[insn]\n\t" /* right shift to get faulting instruction */
: [vaddr]"=&r"(excvaddr), [insn]"=&r"(insn), [tmp]"=&r"(tmp)
: [epc]"r"(ef->epc) :);
}
#else
{
__asm__ __volatile__ ("rsr.excvaddr %0;" : "=r"(excvaddr):: "memory");
/*
"C" reference code for the ASM to document intent.
May also prove useful when issolating possible issues with Extended ASM,
optimizations, new compilers, etc.
*/
uint32_t epc = ef->epc;
uint32_t *pWord = (uint32_t *)(epc & ~3);
uint64_t big_word = ((uint64_t)pWord[1] << 32) | pWord[0];
uint32_t pos = (epc & 3) * 8;
insn = (uint32_t)(big_word >>= pos);
}
#endif
/* Extract instruction and faulting data address */
__EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn);
uint32_t what = insn & LOAD_MASK;
uint32_t valmask = 0;

@ -7,6 +7,54 @@ extern "C" {
extern void install_non32xfer_exception_handler();
/*
In adapting the public domain version, a crash would come or go away with
the slightest unrelated changes elsewhere in the function. Observed that
register a15 was used for epc1, then clobbered by `rsr.` I now believe a
"&" on the output register would have resolved the problem.
However, I have refactored the Extended ASM to reduce and consolidate
register usage and corrected the issue.
The positioning of the Extended ASM block (as early as possible in the
compiled function) is in part controlled by the immediate need for
output variable `insn`. This placement aids in getting excvaddr read as
early as possible.
*/
#if 0
{
__asm__ __volatile__ ("rsr.excvaddr %0;" : "=r"(excvaddr):: "memory");
/*
"C" reference code for the ASM to document intent.
May also prove useful when issolating possible issues with Extended ASM,
optimizations, new compilers, etc.
*/
uint32_t epc = ef->epc;
uint32_t *pWord = (uint32_t *)(epc & ~3);
uint64_t big_word = ((uint64_t)pWord[1] << 32) | pWord[0];
uint32_t pos = (epc & 3) * 8;
insn = (uint32_t)(big_word >>= pos);
}
#endif
#define __EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn) \
{ \
uint32_t tmp; \
__asm__ ( \
"rsr.excvaddr %[vaddr]\n\t" /* Read faulting address as early as possible */ \
"movi.n %[tmp], ~3\n\t" /* prepare a mask for the EPC */ \
"and %[tmp], %[tmp], %[epc]\n\t" /* apply mask for 32-bit aligned base */ \
"ssa8l %[epc]\n\t" /* set up shift register for src op */ \
"l32i %[insn], %[tmp], 0\n\t" /* load part 1 */ \
"l32i %[tmp], %[tmp], 4\n\t" /* load part 2 */ \
"src %[insn], %[tmp], %[insn]\n\t" /* right shift to get faulting instruction */ \
: [vaddr]"=&r"(excvaddr), [insn]"=&r"(insn), [tmp]"=&r"(tmp) \
: [epc]"r"(ef->epc) :); \
}
#ifdef __cplusplus
}
#endif

@ -300,10 +300,10 @@ static const uint8_t ICACHE_FLASH_ATTR phy_init_data[128] =
static bool spoof_init_data = false;
extern int __real_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
extern int ICACHE_RAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
extern int __get_adc_mode();
extern int ICACHE_RAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size)
extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size)
{
if (!spoof_init_data || size != 128) {
return __real_spi_flash_read(addr, dst, size);
@ -354,6 +354,6 @@ void user_rf_pre_init()
}
void ICACHE_RAM_ATTR user_spi_flash_dio_to_qio_pre_init() {}
void IRAM_ATTR user_spi_flash_dio_to_qio_pre_init() {}
};

@ -103,23 +103,23 @@ private:
ETSTimer timer;
// Event/IRQ callbacks, so they can't use "this" and need to be static
static void ICACHE_RAM_ATTR onSclChange(void);
static void ICACHE_RAM_ATTR onSdaChange(void);
static void IRAM_ATTR onSclChange(void);
static void IRAM_ATTR onSdaChange(void);
static void eventTask(ETSEvent *e);
static void ICACHE_RAM_ATTR onTimer(void *unused);
static void IRAM_ATTR onTimer(void *unused);
// Allow not linking in the slave code if there is no call to setAddress
bool _slaveEnabled = false;
// Internal use functions
void ICACHE_RAM_ATTR busywait(unsigned int v);
void IRAM_ATTR busywait(unsigned int v);
bool write_start(void);
bool write_stop(void);
bool write_bit(bool bit);
bool read_bit(void);
bool write_byte(unsigned char byte);
unsigned char read_byte(bool nack);
void ICACHE_RAM_ATTR onTwipEvent(uint8_t status);
void IRAM_ATTR onTwipEvent(uint8_t status);
// Handle the case where a slave needs to stretch the clock with a time-limited busy wait
inline void WAIT_CLOCK_STRETCH()
@ -149,8 +149,8 @@ public:
uint8_t transmit(const uint8_t* data, uint8_t length);
void attachSlaveRxEvent(void (*function)(uint8_t*, size_t));
void attachSlaveTxEvent(void (*function)(void));
void ICACHE_RAM_ATTR reply(uint8_t ack);
void ICACHE_RAM_ATTR releaseBus(void);
void IRAM_ATTR reply(uint8_t ack);
void IRAM_ATTR releaseBus(void);
void enableSlave();
};
@ -229,7 +229,7 @@ void Twi::enableSlave()
}
}
void ICACHE_RAM_ATTR Twi::busywait(unsigned int v)
void IRAM_ATTR Twi::busywait(unsigned int v)
{
unsigned int i;
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
@ -472,9 +472,9 @@ void Twi::attachSlaveTxEvent(void (*function)(void))
}
// DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into
// parts and the ICACHE_RAM_ATTR isn't propagated correctly to all parts, which of course causes crashes.
// parts and the IRAM_ATTR isn't propagated correctly to all parts, which of course causes crashes.
// TODO: test with gcc 9.x and if it still fails, disable optimization with -fdisable-ipa-fnsplit
void ICACHE_RAM_ATTR Twi::reply(uint8_t ack)
void IRAM_ATTR Twi::reply(uint8_t ack)
{
// transmit master read ready signal, with or without ack
if (ack)
@ -492,7 +492,7 @@ void ICACHE_RAM_ATTR Twi::reply(uint8_t ack)
}
void ICACHE_RAM_ATTR Twi::releaseBus(void)
void IRAM_ATTR Twi::releaseBus(void)
{
// release bus
//TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
@ -505,7 +505,7 @@ void ICACHE_RAM_ATTR Twi::releaseBus(void)
}
void ICACHE_RAM_ATTR Twi::onTwipEvent(uint8_t status)
void IRAM_ATTR Twi::onTwipEvent(uint8_t status)
{
twip_status = status;
switch (status)
@ -612,7 +612,7 @@ void ICACHE_RAM_ATTR Twi::onTwipEvent(uint8_t status)
}
}
void ICACHE_RAM_ATTR Twi::onTimer(void *unused)
void IRAM_ATTR Twi::onTimer(void *unused)
{
(void)unused;
twi.releaseBus();
@ -662,7 +662,7 @@ void Twi::eventTask(ETSEvent *e)
// Shorthand for if the state is any of the or'd bitmask x
#define IFSTATE(x) if (twip_state_mask & (x))
void ICACHE_RAM_ATTR Twi::onSclChange(void)
void IRAM_ATTR Twi::onSclChange(void)
{
unsigned int sda;
unsigned int scl;
@ -860,7 +860,7 @@ void ICACHE_RAM_ATTR Twi::onSclChange(void)
}
}
void ICACHE_RAM_ATTR Twi::onSdaChange(void)
void IRAM_ATTR Twi::onSdaChange(void)
{
unsigned int sda;
unsigned int scl;

@ -31,7 +31,7 @@ extern "C" {
static volatile timercallback timer1_user_cb = NULL;
void ICACHE_RAM_ATTR timer1_isr_handler(void *para, void *frame) {
void IRAM_ATTR timer1_isr_handler(void *para, void *frame) {
(void) para;
(void) frame;
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
@ -45,32 +45,32 @@ void ICACHE_RAM_ATTR timer1_isr_handler(void *para, void *frame) {
}
}
void ICACHE_RAM_ATTR timer1_isr_init(){
void IRAM_ATTR timer1_isr_init(){
ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL);
}
void ICACHE_RAM_ATTR timer1_attachInterrupt(timercallback userFunc) {
void IRAM_ATTR timer1_attachInterrupt(timercallback userFunc) {
timer1_user_cb = userFunc;
ETS_FRC1_INTR_ENABLE();
}
void ICACHE_RAM_ATTR timer1_detachInterrupt() {
void IRAM_ATTR timer1_detachInterrupt() {
timer1_user_cb = 0;
TEIE &= ~TEIE1;//edge int disable
ETS_FRC1_INTR_DISABLE();
}
void ICACHE_RAM_ATTR timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload){
void IRAM_ATTR timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload){
T1C = (1 << TCTE) | ((divider & 3) << TCPD) | ((int_type & 1) << TCIT) | ((reload & 1) << TCAR);
T1I = 0;
}
void ICACHE_RAM_ATTR timer1_write(uint32_t ticks){
void IRAM_ATTR timer1_write(uint32_t ticks){
T1L = ((ticks)& 0x7FFFFF);
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
}
void ICACHE_RAM_ATTR timer1_disable(){
void IRAM_ATTR timer1_disable(){
T1C = 0;
T1I = 0;
}
@ -80,7 +80,7 @@ void ICACHE_RAM_ATTR timer1_disable(){
static volatile timercallback timer0_user_cb = NULL;
void ICACHE_RAM_ATTR timer0_isr_handler(void *para, void *frame) {
void IRAM_ATTR timer0_isr_handler(void *para, void *frame) {
(void) para;
(void) frame;
if (timer0_user_cb) {
@ -92,16 +92,16 @@ void ICACHE_RAM_ATTR timer0_isr_handler(void *para, void *frame) {
}
}
void ICACHE_RAM_ATTR timer0_isr_init(){
void IRAM_ATTR timer0_isr_init(){
ETS_CCOMPARE0_INTR_ATTACH(timer0_isr_handler, NULL);
}
void ICACHE_RAM_ATTR timer0_attachInterrupt(timercallback userFunc) {
void IRAM_ATTR timer0_attachInterrupt(timercallback userFunc) {
timer0_user_cb = userFunc;
ETS_CCOMPARE0_ENABLE();
}
void ICACHE_RAM_ATTR timer0_detachInterrupt() {
void IRAM_ATTR timer0_detachInterrupt() {
timer0_user_cb = NULL;
ETS_CCOMPARE0_DISABLE();
}

@ -0,0 +1,399 @@
/*
core_esp8266_vm - Implements logic to enable external SRAM/PSRAM to be used
as if it were on-chip memory by code.
Copyright (c) 2020 Earle F. Philhower, III All rights 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
The original exception handler idea was taken from @pvvx's public domain
misaligned-flash-read exception handler, available here:
https://github.com/pvvx/esp8266web/blob/master/app/sdklib/system/app_main.c
Theory of Operation:
The Xtensa core generates a hardware exception (unrelated to C++ exceptions)
when an address that's defined as invalid for load or store. The XTOS ROM
routines capture the machine state and call a standard C exception handler
routine (or the default one which resets the system).
We hook into this exception callback and decode the EXCVADDR (the address
being accessed) and use the exception PC to read out the faulting
instruction. We decode that instruction and simulate it's behavior
(i.e. either loading or storing some data to a register/external memory)
and then return to the calling application.
We use the hardware SPI interface to talk to an external SRAM/PSRAM, and
implement a simple cache to minimize the amount of times we actually need
to go out over the (slow) SPI bus. The SPI is set up in a DIO mode which
uses no more pins than normal SPI, but provides for ~2X faster transfers.
NOTE: This works fine for processor accesses, but cannot be used by any
of the peripherals' DMA. For that, we'd need a real MMU.
Hardware Configuration (make sure you have 3.3V compatible SRAMs):
* SPI interfaced byte-addressible SRAM/PSRAM: 24LC1024 or smaller
CS -> GPIO15
SCK -> GPIO14
MOSI -> GPIO13
MISO -> GPIO12
(note these are GPIO numbers, not the Arduion Dxx ones. Refer to your
ESP8266 board schematic for the mapping of GPIO to pin.)
* Higher density PSRAM (ESP-PSRAM64H/etc.) works as well, but may be too
large to effectively use with UMM. Only 256K is available vial malloc,
but addresses above 256K do work and can be used for fixed buffers.
*/
#ifdef MMU_EXTERNAL_HEAP
#include <Arduino.h>
#include <esp8266_undocumented.h>
#include "esp8266_peri.h"
#include "core_esp8266_vm.h"
#include "core_esp8266_non32xfer.h"
#include "umm_malloc/umm_malloc.h"
extern "C" {
#define SHORT_MASK 0x000008u
#define LOAD_MASK 0x00f00fu
#define L8UI_MATCH 0x000002u
#define L16UI_MATCH 0x001002u
#define L16SI_MATCH 0x009002u
#define L16_MASK 0x001000u
#define SIGNED_MASK 0x008000u
#define L32IN_MATCH 0x000008u
#define L32I_MATCH 0x002002u
#define L32R_MATCH 0x000001u
#define L32_MASK 0x002009u
#define STORE_MASK 0x00f00fu
#define S8I_MATCH 0x004002u
#define S16I_MATCH 0x005002u
#define S16_MASK 0x001000u
#define S32I_MATCH 0x006002u
#define S32IN_MATCH 0x000009u
#define S32_MASK 0x002001u
#define EXCCAUSE_LOAD_PROHIBITED 28 // Cache Attribute does not allow Load
#define EXCCAUSE_STORE_PROHIBITED 29 // Cache Attribute does not allow Store
#define EXCCAUSE_STORE_MASK 1 // Fast way of deciding if it's a ld or s that faulted
// MINI SPI implementation inlined to have max performance and minimum code
// bloat. Can't include a library (SPI) in the core, anyway.
// Place in a struct so hopefully compiler will generate smaller, base+offset
// based code to access it
typedef struct {
volatile uint32_t spi_cmd; // The SPI can change this behind our backs, so volatile!
uint32_t spi_addr;
uint32_t spi_ctrl;
uint32_t spi_ctrl1; // undocumented? Not shown in the reg map
uint32_t spi_rd_status;
uint32_t spi_ctrl2;
uint32_t spi_clock;
uint32_t spi_user;
uint32_t spi_user1;
uint32_t spi_user2;
uint32_t spi_wr_status;
uint32_t spi_pin;
uint32_t spi_slave;
uint32_t spi_slave1;
uint32_t spi_slave2;
uint32_t spi_slave3;
uint32_t spi_w[16]; // NOTE: You need a memory barrier before reading these after a read xaction
uint32_t spi_ext3;
} spi_regs;
// The standard HSPI bus pins are used
constexpr uint8_t cs = 15;
constexpr uint8_t miso = 12;
constexpr uint8_t mosi = 13;
constexpr uint8_t sck = 14;
#define DECLARE_SPI1 spi_regs *spi1 = (spi_regs*)&SPI1CMD
typedef enum { spi_5mhz = 0x001c1001, spi_10mhz = 0x000c1001, spi_20mhz = 0x00041001, spi_30mhz = 0x00002001, spi_40mhz = 0x00001001 } spi_clocking;
typedef enum { sio = 0, dio = 1 } iotype;
#if MMU_EXTERNAL_HEAP > 128
constexpr uint32_t spi_clkval = spi_40mhz;
constexpr iotype hspi_mode = sio;
#else
constexpr uint32_t spi_clkval = spi_20mhz;
constexpr iotype hspi_mode = dio;
#endif
constexpr int read_delay = (hspi_mode == dio) ? 4-1 : 0;
constexpr int cache_ways = 4; // N-way, fully associative cache
constexpr int cache_words = 16; // Must be 16 words or smaller to fit in SPI buffer
static struct cache_line {
int32_t addr; // Address, lower bits masked off
int dirty; // Needs writeback
struct cache_line *next; // We'll keep linked list in MRU order
union {
uint32_t w[cache_words];
uint16_t s[cache_words * 2];
uint8_t b[cache_words * 4];
};
} __vm_cache_line[cache_ways];
static struct cache_line *__vm_cache; // Always points to MRU (hence the line being read/written)
constexpr int addrmask = ~(sizeof(__vm_cache[0].w)-1); // Helper to mask off bits present in cache entry
static void spi_init(spi_regs *spi1)
{
pinMode(sck, SPECIAL);
pinMode(miso, SPECIAL);
pinMode(mosi, SPECIAL);
pinMode(cs, SPECIAL);
spi1->spi_cmd = 0;
GPMUX &= ~(1 << 9);
spi1->spi_clock = spi_clkval;
spi1->spi_ctrl = 0 ; // MSB first + plain SPI mode
spi1->spi_ctrl1 = 0; // undocumented, clear for safety?
spi1->spi_ctrl2 = 0; // No add'l delays on signals
spi1->spi_user2 = 0; // No insn or insn_bits to set
}
// Note: GCC optimization -O2 and -O3 tried and returned *slower* code than the default
// The SPI hardware cannot make the "command" portion dual or quad, only the addr and data
// So using the command portion of the cycle will not work. Comcatenate the address
// and command into a single 32-bit chunk "address" which will be sent across both bits.
inline IRAM_ATTR void spi_writetransaction(spi_regs *spi1, int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual)
{
// Ensure no writes are still ongoing
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
spi1->spi_addr = addr;
spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | (data_bits ? SPIUMOSI : 0) | (dual ? SPIUFWDIO : 0);
spi1->spi_user1 = (addr_bits << 26) | (data_bits << 17) | dummy_bits;
// No need to set spi_user2, insn field never used
__asm ( "" ::: "memory" );
spi1->spi_cmd = SPIBUSY;
// The write may continue on in the background, letting core do useful work instead of waiting, unless we're in cacheless mode
if (cache_ways == 0) {
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
}
}
inline IRAM_ATTR uint32_t spi_readtransaction(spi_regs *spi1, int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual)
{
// Ensure no writes are still ongoing
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
spi1->spi_addr = addr;
spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | SPIUMISO | (dual ? SPIUFWDIO : 0);
spi1->spi_user1 = (addr_bits << 26) | (data_bits << 8) | dummy_bits;
// No need to set spi_user2, insn field never used
__asm ( "" ::: "memory" );
spi1->spi_cmd = SPIBUSY;
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
__asm ( "" ::: "memory" );
return spi1->spi_w[0];
}
static inline IRAM_ATTR void cache_flushrefill(spi_regs *spi1, int addr)
{
addr &= addrmask;
struct cache_line *way = __vm_cache;
if (__vm_cache->addr == addr) return; // Fast case, it already is the MRU
struct cache_line *last = way;
way = way->next;
for (auto i = 1; i < cache_ways; i++) {
if (way->addr == addr) {
last->next = way->next;
way->next = __vm_cache;
__vm_cache = way;
return;
} else {
last = way;
way = way->next;
}
}
// At this point we know the line is not in the cache and way points to the LRU.
// We allow reads to go before writes since the write can happen in the background.
// We need to keep the data to be written back since it will be overwritten with read data
uint32_t wb[cache_words];
if (last->dirty) {
memcpy(wb, last->w, sizeof(last->w));
}
// Update MRU info, list
last->next = __vm_cache;
__vm_cache = last;
// Do the actual read
spi_readtransaction(spi1, (0x03 << 24) | addr, 32-1, read_delay, sizeof(last->w) * 8 - 1, hspi_mode);
memcpy(last->w, spi1->spi_w, sizeof(last->w));
// We fire a background writeback now, if needed
if (last->dirty) {
memcpy(spi1->spi_w, wb, sizeof(wb));
spi_writetransaction(spi1, (0x02 << 24) | last->addr, 32-1, 0, sizeof(last->w) * 8 - 1, hspi_mode);
last->dirty = 0;
}
// Update the addr at this point since we no longer need the old one
last->addr = addr;
}
static inline IRAM_ATTR void spi_ramwrite(spi_regs *spi1, int addr, int data_bits, uint32_t val)
{
if (cache_ways == 0) {
spi1->spi_w[0] = val;
spi_writetransaction(spi1, (0x02<<24) | addr, 32-1, 0, data_bits, hspi_mode);
} else {
cache_flushrefill(spi1, addr);
__vm_cache->dirty = 1;
addr -= __vm_cache->addr;
switch (data_bits) {
case 31: __vm_cache->w[addr >> 2] = val; break;
case 7: __vm_cache->b[addr] = val; break;
default: __vm_cache->s[addr >> 1] = val; break;
}
}
}
static inline IRAM_ATTR uint32_t spi_ramread(spi_regs *spi1, int addr, int data_bits)
{
if (cache_ways == 0) {
spi1->spi_w[0] = 0;
return spi_readtransaction(spi1, (0x03 << 24) | addr, 32-1, read_delay, data_bits, hspi_mode);
} else {
cache_flushrefill(spi1, addr);
addr -= __vm_cache->addr;
switch (data_bits) {
case 31: return __vm_cache->w[addr >> 2];
case 7: return __vm_cache->b[addr];
default: return __vm_cache->s[addr >> 1];
}
}
}
static void (*__old_handler)(struct __exception_frame *ef, int cause);
static IRAM_ATTR void loadstore_exception_handler(struct __exception_frame *ef, int cause)
{
uint32_t excvaddr;
uint32_t insn;
/* Extract instruction and faulting data address */
__EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn);
// Check that we're really accessing VM and not some other illegal range
if ((excvaddr >> 28) != 1) {
// Reinstall the old handler, and retry the instruction to keep us out of the stack dump
_xtos_set_exception_handler(EXCCAUSE_LOAD_PROHIBITED, __old_handler);
_xtos_set_exception_handler(EXCCAUSE_STORE_PROHIBITED, __old_handler);
return;
}
DECLARE_SPI1;
ef->epc += (insn & SHORT_MASK) ? 2 : 3; // resume at following instruction
int regno = (insn & 0x0000f0u) >> 4;
if (regno != 0) --regno; // account for skipped a1 in exception_frame
if (cause & EXCCAUSE_STORE_MASK) {
uint32_t val = ef->a_reg[regno];
uint32_t what = insn & STORE_MASK;
if (what == S8I_MATCH) {
spi_ramwrite(spi1, excvaddr & 0x1ffff, 8-1, val);
} else if (what == S16I_MATCH) {
spi_ramwrite(spi1, excvaddr & 0x1ffff, 16-1, val);
} else {
spi_ramwrite(spi1, excvaddr & 0x1ffff, 32-1, val);
}
} else {
if (insn & L32_MASK) {
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 32-1);
} else if (insn & L16_MASK) {
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 16-1);
if ((insn & SIGNED_MASK ) && (ef->a_reg[regno] & 0x8000))
ef->a_reg[regno] |= 0xffff0000;
} else {
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 8-1);
}
}
}
void install_vm_exception_handler()
{
__old_handler = _xtos_set_exception_handler(EXCCAUSE_LOAD_PROHIBITED, loadstore_exception_handler);
_xtos_set_exception_handler(EXCCAUSE_STORE_PROHIBITED, loadstore_exception_handler);
DECLARE_SPI1;
// Manually reset chip from DIO to SIO mode (HW SPI has issues with <8 bits/clocks total output)
digitalWrite(cs, HIGH);
digitalWrite(mosi, HIGH);
digitalWrite(miso, HIGH);
digitalWrite(sck, LOW);
pinMode(cs, OUTPUT);
pinMode(miso, OUTPUT);
pinMode(mosi, OUTPUT);
pinMode(sck, OUTPUT);
digitalWrite(cs, LOW);
for (int i = 0; i < 4; i++) {
digitalWrite(sck, HIGH);
digitalWrite(sck, LOW);
}
digitalWrite(cs, HIGH);
// Set up the SPI regs
spi_init(spi1);
// Enable streaming read/write mode
spi1->spi_w[0] = 0x40;
spi_writetransaction(spi1, 0x01<<24, 8-1, 0, 8-1, sio);
if (hspi_mode == dio) {
// Ramp up to DIO mode
spi_writetransaction(spi1, 0x3b<<24, 8-1, 0, 0, sio);
spi1->spi_ctrl |= SPICDIO | SPICFASTRD;
}
// Bring cache structures to baseline
if (cache_ways > 0) {
for (auto i = 0; i < cache_ways; i++) {
__vm_cache_line[i].addr = -1; // Invalid, bits set in lower region so will never match
__vm_cache_line[i].next = &__vm_cache_line[i+1];
}
__vm_cache = &__vm_cache_line[0];
__vm_cache_line[cache_ways - 1].next = NULL;
}
// Hook into memory manager
umm_init_vm( (void *)0x10000000, MMU_EXTERNAL_HEAP * 1024);
}
};
#endif

@ -0,0 +1,11 @@
#ifdef __cplusplus
extern "C" {
#endif
extern void install_vm_exception_handler();
#ifdef __cplusplus
};
#endif

@ -72,11 +72,6 @@
extern "C" {
#endif
// Call this function in your setup() to cause the phase locked version of the generator to
// be linked in automatically. Otherwise, the default PWM locked version will be used.
void enablePhaseLockedWaveform(void);
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
// full period.
@ -112,7 +107,7 @@ int stopWaveform(uint8_t pin);
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
// Make sure the CB function has the IRAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());

@ -111,7 +111,7 @@ namespace {
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static IRAM_ATTR void timer1Interrupt();
// Non-speed critical bits
#pragma GCC optimize ("Os")
@ -125,7 +125,7 @@ static void initTimer() {
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
}
static void ICACHE_RAM_ATTR deinitTimer() {
static void IRAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
@ -218,7 +218,7 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc
}
// Stops a waveform on a pin
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!waveform.timer1Running) {
return false;
@ -252,7 +252,7 @@ ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
if (ISCPUFREQ160MHZ) {
return isCPU2X ? ccys : (ccys >> 1);
}
@ -261,7 +261,7 @@ static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool i
}
}
static ICACHE_RAM_ATTR void timer1Interrupt() {
static IRAM_ATTR void timer1Interrupt() {
const uint32_t isrStartCcy = ESP.getCycleCount();
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
const bool isCPU2X = CPU2X & 1;

@ -93,7 +93,7 @@ static WVFState wvfState;
#pragma GCC optimize ("Os")
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static IRAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static __attribute__((noinline)) void initTimer() {
@ -107,7 +107,7 @@ static __attribute__((noinline)) void initTimer() {
}
}
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
static IRAM_ATTR void forceTimerInterrupt() {
if (T1L > microsecondsToClockCycles(10)) {
T1L = microsecondsToClockCycles(10);
}
@ -144,7 +144,7 @@ static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
// If there are no more scheduled activities, shut down Timer 1.
// Otherwise, do nothing.
static ICACHE_RAM_ATTR void disableIdleTimer() {
static IRAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
@ -155,7 +155,7 @@ static ICACHE_RAM_ATTR void disableIdleTimer() {
// Notify the NMI that a new PWM state is available through the mailbox.
// Wait for mailbox to be emptied (either busy or delay() as needed)
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
p->pwmUpdate = nullptr;
pwmState.pwmUpdate = p;
MEMBARRIER();
@ -237,7 +237,7 @@ static void _cleanAndRemovePWM(PWMState *p, int pin) {
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
ICACHE_RAM_ATTR bool _stopPWM_weak(uint8_t pin) {
IRAM_ATTR bool _stopPWM_weak(uint8_t pin) {
if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active
}
@ -356,7 +356,7 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t
(void) phaseOffsetUS;
(void) autoPwm;
if ((pin > 16) || isFlashInterfacePin(pin)) {
if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) {
return false;
}
Waveform *wave = &wvfState.waveform[pin];
@ -430,7 +430,7 @@ void setTimer1Callback(uint32_t (*fn)()) {
// Stops a waveform on a pin
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
@ -454,7 +454,7 @@ ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
return true;
}
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
ICACHE_RAM_ATTR int stopWaveform(uint8_t pin) {
IRAM_ATTR int stopWaveform(uint8_t pin) {
return stopWaveform_bound(pin);
}
@ -464,14 +464,14 @@ ICACHE_RAM_ATTR int stopWaveform(uint8_t pin) {
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
static inline IRAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Find the earliest cycle as compared to right now
static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
static inline IRAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
uint32_t now = GetCycleCountIRQ();
int32_t da = a - now;
int32_t db = b - now;
@ -496,7 +496,7 @@ static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
#define MINIRQTIME microsecondsToClockCycles(4)
static ICACHE_RAM_ATTR void timer1Interrupt() {
static IRAM_ATTR void timer1Interrupt() {
// Flag if the core is at 160 MHz, for use by adjust()
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;

@ -149,7 +149,7 @@ void micros_overflow_tick(void* arg) {
//
// Reference function: corrected millis(), 64-bit arithmetic,
// truncated to 32-bits by return
// unsigned long ICACHE_RAM_ATTR millis_corr_DEBUG( void )
// unsigned long IRAM_ATTR millis_corr_DEBUG( void )
// {
// // Get usec system time, usec overflow conter
// ......
@ -163,7 +163,7 @@ void micros_overflow_tick(void* arg) {
#define MAGIC_1E3_wLO 0x4bc6a7f0 // LS part
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
unsigned long ICACHE_RAM_ATTR millis()
unsigned long IRAM_ATTR millis()
{
union {
uint64_t q; // Accumulator, 64-bit, little endian
@ -194,18 +194,18 @@ unsigned long ICACHE_RAM_ATTR millis()
} //millis
unsigned long ICACHE_RAM_ATTR micros() {
unsigned long IRAM_ATTR micros() {
return system_get_time();
}
uint64_t ICACHE_RAM_ATTR micros64() {
uint64_t IRAM_ATTR micros64() {
uint32_t low32_us = system_get_time();
uint32_t high32_us = micros_overflow_count + ((low32_us < micros_at_last_overflow_tick) ? 1 : 0);
uint64_t duration64_us = (uint64_t)high32_us << 32 | low32_us;
return duration64_us;
}
void ICACHE_RAM_ATTR delayMicroseconds(unsigned int us) {
void IRAM_ATTR delayMicroseconds(unsigned int us) {
os_delay_us(us);
}

@ -81,7 +81,7 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
}
}
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
extern void IRAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
_stopPWM(pin); // and any analogWrites (PWM)
if(pin < 16){
@ -93,7 +93,7 @@ extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
}
}
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
extern int IRAM_ATTR __digitalRead(uint8_t pin) {
if(pin < 16){
return GPIP(pin);
} else if(pin == 16){
@ -131,7 +131,7 @@ typedef struct {
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
static uint32_t interrupt_reg = 0;
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
void IRAM_ATTR interrupt_handler(void *arg, void *frame)
{
(void) arg;
(void) frame;
@ -218,7 +218,7 @@ extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
}
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
extern void IRAM_ATTR __detachInterrupt(uint8_t pin) {
if (pin < 16)
{
ETS_GPIO_INTR_DISABLE();

@ -18,6 +18,7 @@ void esp_schedule();
void tune_timeshift64 (uint64_t now_us);
void disable_extra4k_at_link_time (void) __attribute__((noinline));
bool sntp_set_timezone_in_seconds(int32_t timezone);
void __disableWiFiAtBootTime (void) __attribute__((noinline));
void __real_system_restart_local() __attribute__((noreturn));
uint32_t sqrt32 (uint32_t n);
@ -34,6 +35,6 @@ using TrivialCB = std::function<void()>;
void settimeofday_cb (const BoolCB& cb);
void settimeofday_cb (const TrivialCB& cb);
#endif
#endif // __cplusplus
#endif // __COREDECLS_H

@ -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)
{

@ -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

@ -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

@ -40,7 +40,7 @@
*
*/
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP) || defined(NEW_EXC_C_WRAPPER)
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP) || defined(NEW_EXC_C_WRAPPER) || defined(MMU_EXTERNAL_HEAP)
/*
* The original module source code came from:

@ -26,7 +26,7 @@
same stub can be used for gdb_present. */
extern "C" {
static bool ICACHE_RAM_ATTR __gdb_no_op()
static bool IRAM_ATTR __gdb_no_op()
{
return false;
}

@ -164,7 +164,7 @@ void* _calloc_r(struct _reent* unused, size_t count, size_t size)
#define DEBUG_HEAP_PRINTF ets_uart_printf
void ICACHE_RAM_ATTR print_loc(size_t size, const char* file, int line)
void IRAM_ATTR print_loc(size_t size, const char* file, int line)
{
(void)size;
(void)line;
@ -175,8 +175,8 @@ void ICACHE_RAM_ATTR print_loc(size_t size, const char* file, int line)
if (inISR && (uint32_t)file >= 0x40200000) {
DEBUG_HEAP_PRINTF("File: %p", file);
} else if (!inISR && (uint32_t)file >= 0x40200000) {
char buf[ets_strlen(file) + 1] __attribute__((aligned(4)));
ets_strcpy(buf, file);
char buf[strlen_P(file) + 1];
strcpy_P(buf, file);
DEBUG_HEAP_PRINTF(buf);
} else {
DEBUG_HEAP_PRINTF(file);
@ -186,7 +186,7 @@ void ICACHE_RAM_ATTR print_loc(size_t size, const char* file, int line)
}
}
void ICACHE_RAM_ATTR print_oom_size(size_t size)
void IRAM_ATTR print_oom_size(size_t size)
{
(void)size;
if (system_get_os_print()) {
@ -232,7 +232,7 @@ void ICACHE_RAM_ATTR print_oom_size(size_t size)
For malloc(), calloc(), and zalloc() Full Posion Check is done 1st since
these functions do not modify an existing allocation.
*/
void* ICACHE_RAM_ATTR malloc(size_t size)
void* IRAM_ATTR malloc(size_t size)
{
INTEGRITY_CHECK__ABORT();
POISON_CHECK__ABORT();
@ -242,7 +242,7 @@ void* ICACHE_RAM_ATTR malloc(size_t size)
return ret;
}
void* ICACHE_RAM_ATTR calloc(size_t count, size_t size)
void* IRAM_ATTR calloc(size_t count, size_t size)
{
INTEGRITY_CHECK__ABORT();
POISON_CHECK__ABORT();
@ -252,7 +252,7 @@ void* ICACHE_RAM_ATTR calloc(size_t count, size_t size)
return ret;
}
void* ICACHE_RAM_ATTR realloc(void* ptr, size_t size)
void* IRAM_ATTR realloc(void* ptr, size_t size)
{
INTEGRITY_CHECK__ABORT();
void* ret = UMM_REALLOC_FL(ptr, size, NULL, 0);
@ -262,7 +262,7 @@ void* ICACHE_RAM_ATTR realloc(void* ptr, size_t size)
return ret;
}
void ICACHE_RAM_ATTR free(void* p)
void IRAM_ATTR free(void* p)
{
INTEGRITY_CHECK__ABORT();
UMM_FREE_FL(p, NULL, 0);
@ -271,7 +271,7 @@ void ICACHE_RAM_ATTR free(void* p)
#endif
STATIC_ALWAYS_INLINE
void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line)
void* IRAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line)
{
INTEGRITY_CHECK__PANIC_FL(file, line);
POISON_CHECK__PANIC_FL(file, line);
@ -282,7 +282,7 @@ void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line)
}
STATIC_ALWAYS_INLINE
void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line)
void* IRAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line)
{
INTEGRITY_CHECK__PANIC_FL(file, line);
POISON_CHECK__PANIC_FL(file, line);
@ -293,7 +293,7 @@ void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* f
}
STATIC_ALWAYS_INLINE
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line)
void* IRAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line)
{
INTEGRITY_CHECK__PANIC_FL(file, line);
void* ret = UMM_REALLOC_FL(ptr, size, file, line);
@ -304,7 +304,7 @@ void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* fil
}
STATIC_ALWAYS_INLINE
void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line)
void* IRAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line)
{
INTEGRITY_CHECK__PANIC_FL(file, line);
POISON_CHECK__PANIC_FL(file, line);
@ -315,14 +315,14 @@ void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line)
}
STATIC_ALWAYS_INLINE
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line)
void IRAM_ATTR heap_vPortFree(void *ptr, const char* file, int line)
{
INTEGRITY_CHECK__PANIC_FL(file, line);
UMM_FREE_FL(ptr, file, line);
POISON_CHECK__PANIC_FL(file, line);
}
size_t ICACHE_RAM_ATTR xPortWantedSizeAlign(size_t size)
size_t IRAM_ATTR xPortWantedSizeAlign(size_t size)
{
return (size + 3) & ~((size_t) 3);
}
@ -338,31 +338,31 @@ void system_show_malloc(void)
malloc calls pvPortMalloc, ... we can leverage that for this solution.
Force pvPortMalloc, ... APIs to serve DRAM only.
*/
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
void* IRAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
{
HeapSelectDram ephemeral;
return heap_pvPortMalloc(size, file, line);;
}
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
void* IRAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
{
HeapSelectDram ephemeral;
return heap_pvPortCalloc(count, size, file, line);
}
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
void* IRAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
{
HeapSelectDram ephemeral;
return heap_pvPortRealloc(ptr, size, file, line);
}
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
void* IRAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
{
HeapSelectDram ephemeral;
return heap_pvPortZalloc(size, file, line);
}
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line)
void IRAM_ATTR vPortFree(void *ptr, const char* file, int line)
{
#if defined(DEBUG_ESP_OOM) || defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE) || defined(UMM_INTEGRITY_CHECK)
// This is only needed for debug checks to ensure they are performed in

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
#if !defined(HWDT_STACK_DUMP_H) || defined(HWDT_VERIFY_HWDT_INFO)
#define HWDT_STACK_DUMP_H
typedef struct hwdt_info_ {
uint32_t rom;
uint32_t sys;
uint32_t cont;
uint32_t bearssl;
uint32_t rom_api_reason;
uint32_t rtc_sys_reason;
uint32_t reset_reason;
uint32_t cont_integrity;
bool g_pcont_valid;
} hwdt_info_t;
extern "C" void debug_hwdt_init(void);
extern uint32_t *g_rom_stack;
extern hwdt_info_t hwdt_info;
#endif

@ -1,81 +1,12 @@
/*
i2s.h - Software I2S library for esp8266
// This include file is a hack to ensure backward compatibility with
// pre 3.0.0 versions of the core. There was a *lowercase* "i2s.h"
// header which was in this directory, now renamed to "core_esp82i66s.h"
// But, the I2S class has a header, "I2S.h" in uppercase. On Linux
// the two names are different, but on Windows it's case-insensitive
// so the names conflict.
//
// Avoid the issue by preserving the old i2s.h file and have it redirect
// to I2S.h which will give the ESP8266-specific functions as well as
// the generic I2S class.
Copyright (c) 2015 Hristo Gochkov. 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
*/
#ifndef I2S_h
#define I2S_h
#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1
/*
How does this work? Basically, to get sound, you need to:
- Connect an I2S codec to the I2S pins on the ESP.
- Start up a thread that's going to do the sound output
- Call i2s_set_bits() if you want to enable 24-bit mode
- Call i2s_begin()
- Call i2s_set_rate() with the sample rate you want.
- Generate sound and call i2s_write_sample() with 32-bit samples.
The 32bit samples basically are 2 16-bit signed values (the analog values for
the left and right channel) concatenated as (Rout<<16)+Lout
i2s_write_sample will block when you're sending data too quickly, so you can just
generate and push data as fast as you can and i2s_write_sample will regulate the
speed.
*/
#ifdef __cplusplus
extern "C" {
#endif
bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin.
// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the
// hardware shifts starting at bit 31, not bit 23.
void i2s_begin(); // Enable TX only, for compatibility
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks);
void i2s_end();
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
float i2s_get_real_rate();//The actual Sample Rate on output
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
bool i2s_is_empty();//returns true if DMA is empty (underflow)
bool i2s_rx_is_full();
bool i2s_rx_is_empty();
uint16_t i2s_available();// returns the number of samples than can be written before blocking
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
void i2s_set_callback(void (*callback) (void));
void i2s_rx_set_callback(void (*callback) (void));
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count);
#ifdef __cplusplus
}
#endif
#endif
#include "../../libraries/I2S/src/I2S.h"

@ -47,27 +47,27 @@
extern "C" {
int ICACHE_RAM_ATTR _open_r (struct _reent* unused, const char *ptr, int mode) {
int IRAM_ATTR _open_r (struct _reent* unused, const char *ptr, int mode) {
(void)unused;
(void)ptr;
(void)mode;
return 0;
}
int ICACHE_RAM_ATTR _close_r(struct _reent* unused, int file) {
int IRAM_ATTR _close_r(struct _reent* unused, int file) {
(void)unused;
(void)file;
return 0;
}
int ICACHE_RAM_ATTR _fstat_r(struct _reent* unused, int file, struct stat *st) {
int IRAM_ATTR _fstat_r(struct _reent* unused, int file, struct stat *st) {
(void)unused;
(void)file;
st->st_mode = S_IFCHR;
return 0;
}
int ICACHE_RAM_ATTR _lseek_r(struct _reent* unused, int file, int ptr, int dir) {
int IRAM_ATTR _lseek_r(struct _reent* unused, int file, int ptr, int dir) {
(void)unused;
(void)file;
(void)ptr;
@ -75,7 +75,7 @@ int ICACHE_RAM_ATTR _lseek_r(struct _reent* unused, int file, int ptr, int dir)
return 0;
}
int ICACHE_RAM_ATTR _read_r(struct _reent* unused, int file, char *ptr, int len) {
int IRAM_ATTR _read_r(struct _reent* unused, int file, char *ptr, int len) {
(void)unused;
(void)file;
(void)ptr;
@ -83,7 +83,7 @@ int ICACHE_RAM_ATTR _read_r(struct _reent* unused, int file, char *ptr, int len)
return 0;
}
int ICACHE_RAM_ATTR _write_r(struct _reent* r, int file, char *ptr, int len) {
int IRAM_ATTR _write_r(struct _reent* r, int file, char *ptr, int len) {
(void) r;
int pos = len;
if (file == STDOUT_FILENO) {
@ -95,9 +95,9 @@ int ICACHE_RAM_ATTR _write_r(struct _reent* r, int file, char *ptr, int len) {
return len;
}
int ICACHE_RAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) __attribute__((weak));
int IRAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) __attribute__((weak));
int ICACHE_RAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) {
int IRAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) {
(void) r;
if (file->_file == STDOUT_FILENO) {
ets_putc(c);
@ -106,7 +106,7 @@ int ICACHE_RAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) {
return EOF;
}
int ICACHE_RAM_ATTR puts(const char * str) {
int IRAM_ATTR puts(const char * str) {
char c;
while((c = *str) != 0) {
ets_putc(c);
@ -117,7 +117,7 @@ int ICACHE_RAM_ATTR puts(const char * str) {
}
#undef putchar
int ICACHE_RAM_ATTR putchar(int c) {
int IRAM_ATTR putchar(int c) {
ets_putc(c);
return c;
}

@ -52,7 +52,7 @@ static inline void __wsr_vecbase(uint32_t vector_base) {
asm volatile("wsr.vecbase %0" :: "r" (vector_base));
}
[[noreturn]] void ICACHE_RAM_ATTR esp8266UartDownloadMode()
[[noreturn]] void IRAM_ATTR esp8266UartDownloadMode()
{
/* reverse engineered from system_restart_core() */
/* Before disabling instruction cache and restoring instruction RAM to a

@ -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;

@ -115,7 +115,7 @@ struct uart_
// called by ISR
inline size_t ICACHE_RAM_ATTR
inline size_t IRAM_ATTR
uart_rx_fifo_available(const int uart_nr)
{
return (USS(uart_nr) >> USRXC) & 0xFF;
@ -144,7 +144,7 @@ uart_rx_available_unsafe(uart_t* uart)
// Copy all the rx fifo bytes that fit into the rx buffer
// called by ISR
inline void ICACHE_RAM_ATTR
inline void IRAM_ATTR
uart_rx_copy_fifo_to_buffer_unsafe(uart_t* uart)
{
struct uart_rx_buffer_ *rx_buffer = uart->rx_buffer;
@ -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)
{
@ -289,7 +324,7 @@ uart_read(uart_t* uart, char* userbuffer, size_t usersize)
// instead of the uart_isr...uart_rx_copy_fifo_to_buffer_unsafe()
// Since we've already read the bytes from the FIFO, can't use that
// function directly and need to implement it bytewise here
static void ICACHE_RAM_ATTR uart_isr_handle_data(void* arg, uint8_t data)
static void IRAM_ATTR uart_isr_handle_data(void* arg, uint8_t data)
{
uart_t* uart = (uart_t*)arg;
if(uart == NULL || !uart->rx_enabled) {
@ -370,7 +405,7 @@ uart_get_rx_buffer_size(uart_t* uart)
}
// The default ISR handler called when GDB is not enabled
void ICACHE_RAM_ATTR
void IRAM_ATTR
uart_isr(void * arg, void * frame)
{
(void) frame;
@ -725,11 +760,11 @@ uart_uninit(uart_t* uart)
free(uart);
}
void
bool
uart_swap(uart_t* uart, int tx_pin)
{
if(uart == NULL)
return;
return false;
switch(uart->uart_nr)
{
@ -740,19 +775,17 @@ uart_swap(uart_t* uart, int tx_pin)
{
pinMode(uart->tx_pin, INPUT);
uart->tx_pin = 15;
pinMode(uart->tx_pin, FUNCTION_4);
}
if(uart->rx_enabled) //RX
{
pinMode(uart->rx_pin, INPUT);
uart->rx_pin = 13;
pinMode(uart->rx_pin, FUNCTION_4);
}
if(uart->tx_enabled)
pinMode(uart->tx_pin, FUNCTION_4); //TX
if(uart->rx_enabled)
pinMode(uart->rx_pin, FUNCTION_4); //RX
IOSWAP |= (1 << IOSWAPU0);
return true;
}
else
{
@ -760,19 +793,17 @@ uart_swap(uart_t* uart, int tx_pin)
{
pinMode(uart->tx_pin, INPUT);
uart->tx_pin = (tx_pin == 2)?2:1;
pinMode(uart->tx_pin, (tx_pin == 2)?FUNCTION_4:SPECIAL);
}
if(uart->rx_enabled) //RX
{
pinMode(uart->rx_pin, INPUT);
uart->rx_pin = 3;
pinMode(3, SPECIAL);
}
if(uart->tx_enabled)
pinMode(uart->tx_pin, (tx_pin == 2)?FUNCTION_4:SPECIAL); //TX
if(uart->rx_enabled)
pinMode(3, SPECIAL); //RX
IOSWAP &= ~(1 << IOSWAPU0);
return true;
}
break;
case UART1:
@ -781,30 +812,30 @@ uart_swap(uart_t* uart, int tx_pin)
default:
break;
}
return false;
}
void
bool
uart_set_tx(uart_t* uart, int tx_pin)
{
if(uart == NULL)
return;
return false;
switch(uart->uart_nr)
{
case UART0:
if(uart->tx_enabled)
{
if (uart->tx_pin == 1 && tx_pin == 2)
if (uart->tx_pin == tx_pin)
{
pinMode(uart->tx_pin, INPUT);
uart->tx_pin = 2;
pinMode(uart->tx_pin, FUNCTION_4);
return true;
}
else if (uart->tx_pin == 2 && tx_pin != 2)
else if (tx_pin == 1 || tx_pin == 2)
{
pinMode(uart->tx_pin, INPUT);
uart->tx_pin = 1;
pinMode(uart->tx_pin, SPECIAL);
uart->tx_pin = tx_pin;
pinMode(uart->tx_pin, tx_pin == 1 ? SPECIAL : FUNCTION_4);
return true;
}
}
@ -815,33 +846,54 @@ uart_set_tx(uart_t* uart, int tx_pin)
default:
break;
}
return false;
}
void
bool
uart_set_pins(uart_t* uart, int tx, int rx)
{
if(uart == NULL)
return;
return false;
if(uart->uart_nr == UART0) // Only UART0 allows pin changes
if(uart->uart_nr != UART0) // Only UART0 allows pin changes
return false;
if(uart->tx_enabled && uart->tx_pin != tx)
{
if(uart->tx_enabled && uart->tx_pin != tx)
if( rx == 13 && tx == 15)
{
if( rx == 13 && tx == 15)
if (!uart_swap(uart, 15))
return false;
}
else if (rx == 3 && (tx == 1 || tx == 2))
{
if (uart->rx_pin != rx)
{
uart_swap(uart, 15);
if (!uart_swap(uart, tx))
return false;
}
else if (rx == 3 && (tx == 1 || tx == 2))
else
{
if (uart->rx_pin != rx)
uart_swap(uart, tx);
else
uart_set_tx(uart, tx);
if (!uart_set_tx(uart, tx))
return false;
}
}
if(uart->rx_enabled && uart->rx_pin != rx && rx == 13 && tx == 15)
uart_swap(uart, 15);
else
return false;
}
if (uart->rx_enabled && uart->rx_pin != rx)
{
if (rx == 13 && tx == 15)
{
if (!uart_swap(uart, 15))
return false;
}
else
return false;
}
return true;
}

@ -116,9 +116,9 @@ typedef struct uart_ uart_t;
uart_t* uart_init(int uart_nr, int baudrate, int config, int mode, int tx_pin, size_t rx_size, bool invert);
void uart_uninit(uart_t* uart);
void uart_swap(uart_t* uart, int tx_pin);
void uart_set_tx(uart_t* uart, int tx_pin);
void uart_set_pins(uart_t* uart, int tx, int rx);
bool uart_swap(uart_t* uart, int tx_pin);
bool uart_set_tx(uart_t* uart, int tx_pin);
bool uart_set_pins(uart_t* uart, int tx, int rx);
bool uart_tx_enabled(uart_t* uart);
bool uart_rx_enabled(uart_t* uart);
@ -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)

@ -204,13 +204,8 @@ void umm_print_stats(int force) {
#endif
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
/*
To use ets_strlen() and ets_strcpy() safely with PROGMEM, flash storage,
the PROGMEM address must be word (4 bytes) aligned. The destination
address for ets_memcpy must also be word-aligned.
*/
char ram_buf[ets_strlen(fmt) + 1] __attribute__((aligned(4)));
ets_strcpy(ram_buf, fmt);
char ram_buf[strlen_P(fmt) + 1];
strcpy_P(ram_buf, fmt);
va_list argPtr;
va_start(argPtr, fmt);
int result = ets_vprintf(ets_uart_putc1, ram_buf, argPtr);

@ -48,8 +48,7 @@ void ICACHE_FLASH_ATTR umm_print_stats(int force);
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR4(fmt), ##__VA_ARGS__)
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR(fmt), ##__VA_ARGS__)
typedef struct umm_block_t umm_block;

@ -42,7 +42,11 @@ extern "C" {
#undef UMM_HEAP_IRAM
#endif
// #define UMM_HEAP_EXTERNAL
#if defined(MMU_EXTERNAL_HEAP)
#define UMM_HEAP_EXTERNAL
#else
#undef UMM_HEAP_EXTERNAL
#endif
/*
* Assign IDs to active Heaps and tally. DRAM is always active.
@ -793,11 +797,11 @@ extern "C" {
#include <pgmspace.h>
// Reuse pvPort* calls, since they already support passing location information.
// Specificly the debug version (heap_...) that does not force DRAM heap.
void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line);
void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line);
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line);
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
void* IRAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line);
void* IRAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line);
void* IRAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
void* IRAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line);
void IRAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
#define malloc(s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortMalloc(s, mem_debug_file, __LINE__); })
#define calloc(n,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortCalloc(n, s, mem_debug_file, __LINE__); })
@ -811,10 +815,10 @@ void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
#elif defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
#include <pgmspace.h>
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
void* IRAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortRealloc(p, s, mem_debug_file, __LINE__); })
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
void IRAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
//C - to be discussed
/*
Problem, I would like to report the file and line number with the umm poison

@ -344,6 +344,14 @@ LOLIN(WEMOS) D1 R2 & mini
Product page: https://www.wemos.cc/
LOLIN(WEMOS) D1 mini (clone)
----------------------------
Clone variant of the LOLIN(WEMOS) D1 mini board,
with enabled flash-mode menu, DOUT selected by default.
Product page of the preferred official board: https://www.wemos.cc/
LOLIN(WEMOS) D1 mini Pro
------------------------

@ -20,7 +20,7 @@ BearSSL doesn't perform memory allocations at runtime, but it does require alloc
. A per-application secondary stack
. A per-connection TLS receive/transmit buffer plus overhead
The per-application secondary stack is approximately 5.6KB in size and is used for temporary variables during BearSSL processing. Only one stack is required, and it will be allocated whenever any `BearSSL::WiFiClientSecure` or `BearSSL::WiFiServerSecure` are instantiated. So, in the case of a global client or server, the memory will be allocated before `setup()` is called.
The per-application secondary stack is approximately 6KB in size and is used for temporary variables during BearSSL processing. Only one stack is required, and it will be allocated whenever any `BearSSL::WiFiClientSecure` or `BearSSL::WiFiServerSecure` are instantiated. So, in the case of a global client or server, the memory will be allocated before `setup()` is called.
The per-connection buffers are approximately 22KB in size, but in certain circumstances it can be reduced dramatically by using MFLN or limiting message sizes. See the `MLFN section <#mfln-or-maximum-fragment-length-negotiation-saving-ram>`__ below for more information.
@ -219,3 +219,13 @@ setCiphersLessSecure()
^^^^^^^^^^^^^^^^^^^^^^
Helper function which essentially limits BearSSL to less secure ciphers than it would natively choose, but they may be helpful and faster if your server depended on specific crypto options.
Limiting TLS(SSL) Versions
~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, BearSSL will connect with TLS 1.0, TLS 1.1, or TLS 1.2 protocols (depending on the request of the remote side). If you want to limit to a subset, use the following call:
setSSLVersion(uint32_t min, uint32_t max)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Valid values for min and max are `BR_TLS10`, `BR_TLS11`, `BR_TLS12`. Min and max may be set to the same value if only a single TLS version is desired.

@ -42,6 +42,35 @@ persistent
WiFi.persistent(persistent)
Starting from version 3 of this core, **persistence is disabled by default
and WiFi does not start automatically at boot** (see PR `#7902 <https://github.com/esp8266/Arduino/pull/7902>`__).
Previously, SDK was automatically starting WiFi at boot. This was probably
intended for the Espressif AT FW which is interactive and preserves WiFi
state accross reboots. This behavior is generally irrelevant with the
Arduino API because sketches start with ``WiFi.begin()`` or
``WiFi.softAP()``.
This change is harmless with standard sketches: Calls to ``WiFi.mode()`` do
enable radio as usual. It also smooths current spikes at boot and decreases
DHCP stress.
Legacy behavior can be restored by calling ``enableWiFiAtBootTime()`` from
anywhere in the code (it is a weak void function intended to play with the
linker).
.. code:: cpp
#include <ESP8266WiFi.h>
void setup () {
#ifdef WIFI_IS_OFF_AT_BOOT
enableWiFiAtBootTime(); // can be called from anywhere with the same effect
#endif
....
}
When legacy behavior is restored thanks to this call,
ESP8266 is able to reconnect to the last used WiFi network or establishes the same Access Point upon power up or reset.
By default, these settings are written to specific sectors of flash memory every time they are changed in ``WiFi.begin(ssid, passphrase)`` or ``WiFi.softAP(ssid, passphrase, channel)``, and when ``WiFi.disconnect`` or ``WiFi.softAPdisconnect`` is invoked.
Frequently calling these functions could cause wear on the flash memory (see issue `#1054 <https://github.com/esp8266/Arduino/issues/1054>`__).
@ -51,9 +80,6 @@ Once ``WiFi.persistent(false)`` is called, ``WiFi.begin``, ``WiFi.disconnect``,
mode
~~~~
Regular WiFi modes
__________________
.. code:: cpp
bool mode(WiFiMode_t m)
@ -65,25 +91,6 @@ Switches to one of the regular WiFi modes, where ``m`` is one of:
- ``WIFI_AP``: switch to `Access Point (AP) <readme.rst#soft-access-point>`__ mode.
- ``WIFI_AP_STA``: enable both Station (STA) and Access Point (AP) mode.
Pseudo-modes
____________
.. code:: cpp
bool mode(WiFiMode_t m, WiFiState* state)
Used with the following pseudo-modes, where ``m`` is one of:
- ``WIFI_SHUTDOWN``: Fills in the provided ``WiFiState`` structure, switches to ``WIFI_OFF`` mode and puts WiFi into forced sleep, preserving energy.
- ``WIFI_RESUME``: Turns WiFi on and tries to re-establish the WiFi connection stored in the ``WiFiState`` structure.
These modes are used in low-power scenarios, e.g. where ESP.deepSleep is used between actions to preserve battery power.
It is the user's responsibility to preserve the WiFiState between ``WIFI_SHUTDOWN`` and ``WIFI_RESUME``, e.g. by storing it
in RTC user data and/or flash memory.
There is an example sketch `WiFiShutdown.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino>`__ available in the examples folder of the ESP8266WiFi library.
getMode
~~~~~~~
@ -170,6 +177,41 @@ getPhyMode
Gets the WiFi radio phy mode that is currently set.
forceSleepBegin
~~~~~~~~~~~~~~~
.. code:: cpp
bool forceSleepBegin (uint32 sleepUs=0)
Saves the currently set WiFi mode and starts forced modem sleep for the specified time (us)
forceSleepWake
~~~~~~~~~~~~~~
.. code:: cpp
bool forceSleepWake ()
Called after `forceSleepBegin()`. Restores the previous WiFi mode and attempts reconnection when STA was active.
shutdown and resumeFromShutdown
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
bool shutdown (WiFiState& state)
bool shutdown (WiFiState& state, uint32 sleepUs)
bool resumeFromShutdown (WiFiState& state)
bool shutdownValidCRC (const WiFiState& state)
Stores the STA interface IP configuration in the specified ``state`` struct and calls ``forceSleepBegin(sleepUs)``.
Restores STA interface configuration from the ``state`` and calls ``forceSleepWake()``.
These methods are intended to be used in low-power scenarios, e.g. where ESP.deepSleep is used between actions to preserve battery power. It is the user's responsibility to preserve the WiFiState between ``shutdown()`` and ``resumeFromShutdown()`` by storing it in the RTC user data and/or flash memory.
See `WiFiShutdown.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino>`__ for an example of usage.
Other Function Calls
~~~~~~~~~~~~~~~~~~~~
@ -179,8 +221,6 @@ Other Function Calls
WiFiSleepType_t getSleepMode ()
bool enableSTA (bool enable)
bool enableAP (bool enable)
bool forceSleepBegin (uint32 sleepUs=0)
bool forceSleepWake ()
int hostByName (const char *aHostname, IPAddress &aResult)
appeared with SDK pre-V3:

@ -7,7 +7,7 @@ ESP8266 is all about Wi-Fi. If you are eager to connect your new ESP8266 module
Introduction
------------
The `Wi-Fi library for ESP8266 <https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi>`__ has been developed based on `ESP8266 SDK <https://bbs.espressif.com/viewtopic.php?f=51&t=1023>`__, using the naming conventions and overall functionality philosophy of the `Arduino WiFi library <https://www.arduino.cc/en/Reference/WiFi>`__. Over time, the wealth of Wi-Fi features ported from ESP8266 SDK to `esp8266 /
The `Wi-Fi library for ESP8266 <https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi>`__ has been developed based on `ESP8266 SDK <https://github.com/espressif/ESP8266_NONOS_SDK>`__, using the naming conventions and overall functionality philosophy of the `Arduino WiFi library <https://www.arduino.cc/en/Reference/WiFi>`__. Over time, the wealth of Wi-Fi features ported from ESP8266 SDK to `esp8266 /
Arduino <https://github.com/esp8266/Arduino>`__ outgrew `Arduino WiFi library <https://www.arduino.cc/en/Reference/WiFi>`__ and it became apparent that we would need to provide separate documentation on what is new and extra.
This documentation will walk you through several classes, methods and properties of the `ESP8266WiFi <https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi>`__ library. If you are new to C++ and Arduino, don't worry. We will start from general concepts and then move to detailed description of members of each particular class including usage examples.
@ -164,6 +164,7 @@ WiFi Multi
Example:
.. code:: cpp
#include <ESP8266WiFiMulti.h>
ESP8266WiFiMulti wifiMulti;

@ -41,9 +41,8 @@ following three things right: 1. Module is provided with enough power,
2. GPIO0, GPIO15 and CH\_PD are connected using pull up / pull down
resistors, 3. Module is put into boot loader mode.
For specific details please refer to section on `Generic ESP8266
modules <../boards.rst#generic-esp8266-modules>`__. Example modules
without USB to serial converter on board are shown below.
For specific details please refer to section on `Generic ESP8266 module <../boards.rst#generic-esp8266-module>`__.
Example modules without USB to serial converter on board are shown below.
.. figure:: pictures/a01-example-boards-without-usb.png
:alt: Example ESP8266 modules without USB to serial converter

@ -247,8 +247,8 @@ Interrupt Service Routines
cache may kick in for that code. However, the cache currently can't be used
during hardware interrupts. That means that, if you use a hardware ISR, such as
attachInterrupt(gpio, myISR, CHANGE) for a GPIO change, the ISR must have the
ICACHE_RAM_ATTR attribute declared. Not only that, but the entire function tree
called from the ISR must also have the ICACHE_RAM_ATTR declared.
IRAM_ATTR attribute declared. Not only that, but the entire function tree
called from the ISR must also have the IRAM_ATTR declared.
Be aware that every function that has this attribute reduces available memory.
In addition, it is not possible to execute delay() or yield() from an ISR,

@ -48,7 +48,7 @@ follows:
Error compiling for board Generic ESP8266 Module.
Below is an example messages for
`WeMos <../boards.rst#wemos-d1-r2-mini>`__:
`WeMos <../boards.rst#lolin-wemos-d1-r2-mini>`__:
::

@ -177,3 +177,12 @@ will need to implement an additional (short) deep sleep using
``WAKE_RF_DEFAULT``.
Ref. `#3072 <https://github.com/esp8266/Arduino/issues/3072>`__
My WiFi was previously automatically connected right after booting, but isn't anymore
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This was WiFi persistence. Starting from version 3 of this core, WiFi is
indeed off at boot and is powered on only when starting to be used with the
regular API.
Read more at `former WiFi persistent mode <../esp8266wifi/generic-class.rst#persistent>`__.

@ -474,8 +474,8 @@ Performs the same operation as ``info`` but allows for reporting greater than
4GB for filesystem size/used/etc. Should be used with the SD and SDFS
filesystems since most SD cards today are greater than 4GB in size.
setTimeCallback(time_t (*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
setTimeCallback(time_t (\*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
@ -574,8 +574,8 @@ rewind
Resets the internal pointer to the start of the directory.
setTimeCallback(time_t (*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
setTimeCallback(time_t (\*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the time callback for any files accessed from this Dir object via openNextFile.
Note that the SD and SDFS filesystems only support a filesystem-wide callback and
@ -693,7 +693,7 @@ Close the file. No other operations should be performed on *File* object
after ``close`` function was called.
openNextFile (compatibiity method, not recommended for new code)
~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
@ -705,7 +705,7 @@ Opens the next file in the directory pointed to by the File. Only valid
when ``File.isDirectory() == true``.
rewindDirectory (compatibiity method, not recommended for new code)
~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
@ -718,8 +718,8 @@ rewindDirectory (compatibiity method, not recommended for new code)
Resets the ``openNextFile`` pointer to the top of the directory. Only
valid when ``File.isDirectory() == true``.
setTimeCallback(time_t (*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
setTimeCallback(time_t (\*cb)(void))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the time callback for this specific file. Note that the SD and
SDFS filesystems only support a filesystem-wide callback and calls to

@ -87,7 +87,7 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab
``ESP.getHeapFragmentation()`` returns the fragmentation metric (0% is clean, more than ~50% is not harmless)
``ESP.getMaxFreeBlockSize()`` returns the largest contiguous free RAM block in the heap, useful for checking heap fragmentation. **NOTE:** Maximum ``malloc()``able block will be smaller due to memory manager overheads.
``ESP.getMaxFreeBlockSize()`` returns the largest contiguous free RAM block in the heap, useful for checking heap fragmentation. **NOTE:** Maximum ``malloc()`` -able block will be smaller due to memory manager overheads.
``ESP.getChipId()`` returns the ESP8266 chip ID as a 32-bit integer.

@ -233,5 +233,3 @@ address range of IRAM or DRAM.
uint8_t mmu_set_uint8(void *p8, const uint8_t val);
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val);
int16_t mmu_set_int16(int16_t *p16, const int16_t val);
::

@ -161,7 +161,7 @@ If signing is desired, sign the gzip compressed file *after* compression.
Updating apps in the field to support compression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have applications deployed in the field and wish to update them to support compressed OTA uploads, you will need to first recompile the application, then _upload the uncompressed `.bin` file once_. Attempting to upload a `gzip` compressed binary to a legacy app will result in the Updater rejecting the upload as it does not understand the `gzip` format. After this initial upload, which will include the new bootloader and `Updater` class with compression support, compressed updates can then be used.
If you have applications deployed in the field and wish to update them to support compressed OTA uploads, you will need to first recompile the application, then _upload the uncompressed `.bin` file once. Attempting to upload a `gzip` compressed binary to a legacy app will result in the Updater rejecting the upload as it does not understand the `gzip` format. After this initial upload, which will include the new bootloader and `Updater` class with compression support, compressed updates can then be used.
Safety

@ -9,13 +9,13 @@ and have several limitations:
* Interrupt callback functions must be in IRAM, because the flash may be
in the middle of other operations when they occur. Do this by adding
the ``ICACHE_RAM_ATTR`` attribute on the function definition. If this
the ``IRAM_ATTR`` attribute on the function definition. If this
attribute is not present, the sketch will crash when it attempts to
``attachInterrupt`` with an error message.
.. code:: cpp
ICACHE_RAM_ATTR void gpio_change_handler(void *data) {...
IRAM_ATTR void gpio_change_handler(void *data) {...
* Interrupts must not call ``delay()`` or ``yield()``, or call any routines
which internally use ``delay()`` or ``yield()`` either.
@ -69,17 +69,22 @@ Pin interrupts are supported through ``attachInterrupt``,
``detachInterrupt`` functions. Interrupts may be attached to any GPIO
pin, except GPIO16. Standard Arduino interrupt types are supported:
``CHANGE``, ``RISING``, ``FALLING``. ISRs need to have
``ICACHE_RAM_ATTR`` before the function definition.
``IRAM_ATTR`` before the function definition.
Analog input
------------
**NOTE:**
Calling ``analogRead()`` too frequently causes WiFi to stop working. When
WiFi is under operation, ``analogRead()`` result may be cached for at least
5ms between effective calls.
ESP8266 has a single ADC channel available to users. It may be used
either to read voltage at ADC pin, or to read module supply voltage
(VCC).
To read external voltage applied to ADC pin, use ``analogRead(A0)``.
Input voltage range of bare ESP8266 is 0 — 1.0V, however some many
Input voltage range of bare ESP8266 is 0 — 1.0V, however some
boards may implement voltage dividers. To be on the safe side, <1.0V
can be tested. If e.g. 0.5V delivers values around ~512, then maximum
voltage is very likely to be 1.0V and 3.3V may harm the ESP8266.
@ -326,3 +331,196 @@ C++
This assures correct behavior, including handling of all subobjects, which guarantees stability.
History: `#6269 <https://github.com/esp8266/Arduino/issues/6269>`__ `#6309 <https://github.com/esp8266/Arduino/pull/6309>`__ `#6312 <https://github.com/esp8266/Arduino/pull/6312>`__
Streams
-------
Arduino API
Stream is one of the core classes in the Arduino API. Wire, serial, network and
filesystems are streams, from which data are read or written.
Making a transfer with streams is quite common, like for example the
historical WiFiSerial sketch:
.. code:: cpp
//check clients for data
//get data from the telnet client and push it to the UART
while (serverClient.available()) {
Serial.write(serverClient.read());
}
//check UART for data
if (Serial.available()) {
size_t len = Serial.available();
uint8_t sbuf[len];
Serial.readBytes(sbuf, len);
//push UART data to all connected telnet clients
if (serverClient && serverClient.connected()) {
serverClient.write(sbuf, len);
}
}
One will notice that in the network to serial direction, data are transfered
byte by byte while data are available. In the other direction, a temporary
buffer is created on stack, filled with available serial data, then
transferred to network.
The ``readBytes(buffer, length)`` method includes a timeout to ensure that
all required bytes are received. The ``write(buffer, length)`` (inherited
from ``Print::``) function is also usually blocking until the full buffer is
transmitted. Both functions return the number of transmitted bytes.
That's the way the Stream class works and is commonly used.
Classes derived from ``Stream::`` also usually introduce the ``read(buffer,
len)`` method, which is similar to ``readBytes(buffer, len)`` without
timeout: the returned value can be less than the requested size, so special
care must be taken with this function, introduced in the Arduino
``Client::`` class (cf. AVR reference implementation).
This function has also been introduced in other classes
that don't derive from ``Client::``, e.g. ``HardwareSerial::``.
Stream extensions
Stream extensions are designed to be compatible with Arduino API, and
offer additional methods to make transfers more efficient and easier to
use.
The serial to network transfer above can be written like this:
.. code:: cpp
serverClient.sendAvailable(Serial); // chunk by chunk
Serial.sendAvailable(serverClient); // chunk by chunk
An echo service can be written like this:
.. code:: cpp
serverClient.sendAvailable(serverClient); // tcp echo service
Serial.sendAvailable(Serial); // serial software loopback
Beside reducing coding time, these methods optimize transfers by avoiding
buffer copies when possible.
- User facing API: ``Stream::send()``
The goal of streams is to transfer data between producers and consumers,
like the telnet/serial example above. Four methods are provided, all of
them return the number of transmitted bytes:
- ``Stream::sendSize(dest, size [, timeout])``
This method waits up to the given or default timeout to transfer
``size`` bytes to the the ``dest`` Stream.
- ``Stream::sendUntil(dest, delim [, timeout])``
This method waits up to the given or default timeout to transfer data
until the character ``delim`` is met.
Note: The delimiter is read but not transferred (like ``readBytesUntil``)
- ``Stream::sendAvailable(dest)``
This method transfers all already available data to the destination.
There is no timeout and the returned value is 0 when there is nothing
to transfer or no room in the destination.
- ``Stream::sendAll(dest [, timeout])``
This method waits up to the given or default timeout to transfer all
available data. It is useful when source is able to tell that no more
data will be available for this call, or when destination can tell
that it will no be able to receive anymore.
For example, a source String will not grow during the transfer, or a
particular network connection supposed to send a fixed amount of data
before closing. ``::sendAll()`` will receive all bytes. Timeout is
useful when destination needs processing time (e.g. network or serial
input buffer full = please wait a bit).
- String, flash strings helpers
Two additional classes are provided.
- ``StreamConstPtr::`` is designed to hold a constant buffer (in ram or flash).
With this class, a ``Stream::`` can be made from ``const char*``,
``F("some words in flash")`` or ``PROGMEM`` strings. This class makes
no copy, even with data in flash. For flash content, byte-by-byte
transfers is a consequence when "memcpy_P" cannot be used. Other
contents can be transferred at once when possible.
.. code:: cpp
StreamConstPtr css(F("my long css data")); // CSS data not copied to RAM
server.sendAll(css);
- ``S2Stream::`` is designed to make a ``Stream::`` out of a ``String::`` without copy.
.. code:: cpp
String helloString("hello");
S2Stream hello(helloString);
hello.reset(0); // prevents ::read() to consume the string
hello.sendAll(Serial); // shows "hello"
hello.sendAll(Serial); // shows nothing, content has already been read
hello.reset(); // reset content pointer
hello.sendAll(Serial); // shows "hello"
hello.reset(3); // reset content pointer to a specific position
hello.sendAll(Serial); // shows "lo"
hello.setConsume(); // ::read() will consume, this is the default
Serial.println(helloString.length()); // shows 5
hello.sendAll(Serial); // shows "hello"
Serial.println(helloString.length()); // shows 0, string is consumed
``StreamString::`` derives from ``S2Stream``
.. code:: cpp
StreamString contentStream;
client.sendSize(contentStream, SOME_SIZE); // receives at most SOME_SIZE bytes
// equivalent to:
String content;
S2Stream contentStream(content);
client.sendSize(contentStream, SOME_SIZE); // receives at most SOME_SIZE bytes
// content has the data
- Internal Stream API: ``peekBuffer``
Here is the method list and their significations. They are currently
implemented in ``HardwareSerial``, ``WiFiClient`` and
``WiFiClientSecure``.
- ``virtual bool hasPeekBufferAPI ()`` returns ``true`` when the API is present in the class
- ``virtual size_t peekAvailable ()`` returns the number of reachable bytes
- ``virtual const char* peekBuffer ()`` returns the pointer to these bytes
This API requires that any kind of ``"read"`` function must not be called after ``peekBuffer()``
and until ``peekConsume()`` is called.
- ``virtual void peekConsume (size_t consume)`` tells to discard that number of bytes
- ``virtual bool inputCanTimeout ()``
A ``StringStream`` will return false. A closed network connection returns false.
This function allows ``Stream::sendAll()`` to return earlier.
- ``virtual bool outputCanTimeout ()``
A closed network connection returns false.
This function allows ``Stream::sendAll()`` to return earlier.
- ``virtual ssize_t streamRemaining()``
It returns -1 when stream remaining size is unknown, depending on implementation
(string size, file size..).

@ -110,7 +110,7 @@ void EEPROMClass::write(int const address, uint8_t const value) {
return;
}
if(!_data) {
DEBUGV("EEPROMClass::read without ::begin\n");
DEBUGV("EEPROMClass::write without ::begin\n");
return;
}

@ -44,6 +44,8 @@ void loop() {
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
client->setFingerprint(fingerprint);
// Or, if you happy to ignore the SSL certificate, then use the following line instead:
// client->setInsecure();
HTTPClient https;

@ -25,9 +25,22 @@
#include "ESP8266HTTPClient.h"
#include <ESP8266WiFi.h>
#include <StreamString.h>
#include <StreamDev.h>
#include <base64.h>
static int StreamReportToHttpClientReport (Stream::Report streamSendError)
{
switch (streamSendError)
{
case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
case Stream::Report::Success: return 0;
}
return 0; // never reached, keep gcc quiet
}
/**
* constructor
*/
@ -429,24 +442,9 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
// send Payload if needed
if (payload && size > 0) {
size_t bytesWritten = 0;
const uint8_t *p = payload;
size_t originalSize = size;
while (bytesWritten < originalSize) {
int written;
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
written = _client->write(p + bytesWritten, towrite);
if (written < 0) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else if (written == 0) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
bytesWritten += written;
size -= written;
}
}
// transfer all of it, with send-timeout
if (size && StreamConstPtr(payload, size).sendAll(_client) != size)
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
// handle Server Response (Header)
code = handleHeaderResponse();
@ -545,111 +543,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
int buff_size = HTTP_TCP_BUFFER_SIZE;
int len = size;
int bytesWritten = 0;
if(len == 0) {
len = -1;
}
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
buff_size = len;
}
// create buffer for read
uint8_t * buff = (uint8_t *) malloc(buff_size);
if(buff) {
// read all data from stream and send it to server
while(connected() && (stream->available() > 0) && (len > 0 || len == -1)) {
// get available data size
int sizeAvailable = stream->available();
if(sizeAvailable) {
int readBytes = sizeAvailable;
// read only the asked bytes
if(len > 0 && readBytes > len) {
readBytes = len;
}
// not read more the buffer can handle
if(readBytes > buff_size) {
readBytes = buff_size;
}
// read data
int bytesRead = stream->readBytes(buff, readBytes);
// write it to Stream
int bytesWrite = _client->write((const uint8_t *) buff, bytesRead);
bytesWritten += bytesWrite;
// are all Bytes a writen to stream ?
if(bytesWrite != bytesRead) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d retry...\n", bytesRead, bytesWrite);
// check for write error
if(_client->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
//reset write error for retry
_client->clearWriteError();
}
// some time for the stream
delay(1);
int leftBytes = (readBytes - bytesWrite);
// retry to send the missed bytes
bytesWrite = _client->write((const uint8_t *) (buff + bytesWrite), leftBytes);
bytesWritten += bytesWrite;
if(bytesWrite != leftBytes) {
// failed again
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", leftBytes, bytesWrite);
free(buff);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
}
// check for write error
if(_client->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
free(buff);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
// count bytes to read left
if(len > 0) {
len -= readBytes;
}
delay(0);
} else {
delay(1);
}
}
free(buff);
if(size && (int) size != bytesWritten) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload bytesWritten %d and size %zd mismatch!.\n", bytesWritten, size);
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] ERROR SEND PAYLOAD FAILED!");
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload written: %d\n", bytesWritten);
}
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
return returnError(HTTPC_ERROR_TOO_LESS_RAM);
// transfer all of it, with timeout
size_t transferred = stream->sendSize(_client, size);
if (transferred != size)
{
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
// handle Server Response (Header)
@ -725,13 +624,13 @@ int HTTPClient::writeToStream(Stream * stream)
int ret = 0;
if(_transferEncoding == HTTPC_TE_IDENTITY) {
if(len > 0 || len == -1) {
ret = writeToStreamDataBlock(stream, len);
// len < 0: transfer all of it, with timeout
// len >= 0: max:len, with timeout
ret = _client->sendSize(stream, len);
// have we an error?
if(ret < 0) {
return returnError(ret);
}
// do we have an error?
if(_client->getLastSendReport() != Stream::Report::Success) {
return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
}
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
int size = 0;
@ -754,11 +653,11 @@ int HTTPClient::writeToStream(Stream * stream)
// data left?
if(len > 0) {
int r = writeToStreamDataBlock(stream, len);
if(r < 0) {
// error in writeToStreamDataBlock
return returnError(r);
}
// read len bytes with timeout
int r = _client->sendSize(stream, len);
if (_client->getLastSendReport() != Stream::Report::Success)
// not all data transferred
return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
ret += r;
} else {
@ -948,9 +847,7 @@ bool HTTPClient::connect(void)
{
if(_reuse && _canReuse && connected()) {
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
while(_client->available() > 0) {
_client->read();
}
_client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
return true;
}
@ -1032,7 +929,8 @@ bool HTTPClient::sendHeader(const char * type)
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
return (_client->write((const uint8_t *) header.c_str(), header.length()) == header.length());
// transfer all of it, with timeout
return StreamConstPtr(header).sendAll(_client) == header.length();
}
/**
@ -1150,116 +1048,6 @@ int HTTPClient::handleHeaderResponse()
return HTTPC_ERROR_CONNECTION_LOST;
}
/**
* write one Data Block to Stream
* @param stream Stream *
* @param size int
* @return < 0 = error >= 0 = size written
*/
int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
{
int buff_size = HTTP_TCP_BUFFER_SIZE;
int len = size; // left size to read
int bytesWritten = 0;
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
buff_size = len;
}
// create buffer for read
uint8_t * buff = (uint8_t *) malloc(buff_size);
if(!buff) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
return HTTPC_ERROR_TOO_LESS_RAM;
}
// read all data from server
while(connected() && (len > 0 || len == -1))
{
int readBytes = len;
// not read more the buffer can handle
if(readBytes > buff_size) {
readBytes = buff_size;
}
// len == -1 or len > what is available, read only what is available
int av = _client->available();
if (readBytes < 0 || readBytes > av) {
readBytes = av;
}
// read data
int bytesRead = _client->readBytes(buff, readBytes);
if (!bytesRead)
{
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] input stream timeout\n");
free(buff);
return HTTPC_ERROR_READ_TIMEOUT;
}
// write it to Stream
int bytesWrite = stream->write(buff, bytesRead);
bytesWritten += bytesWrite;
// are all Bytes a writen to stream ?
if(bytesWrite != bytesRead) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d retry...\n", bytesRead, bytesWrite);
// check for write error
if(stream->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
//reset write error for retry
stream->clearWriteError();
}
// some time for the stream
delay(1);
int leftBytes = (bytesRead - bytesWrite);
// retry to send the missed bytes
bytesWrite = stream->write((buff + bytesWrite), leftBytes);
bytesWritten += bytesWrite;
if(bytesWrite != leftBytes) {
// failed again
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d failed.\n", leftBytes, bytesWrite);
free(buff);
return HTTPC_ERROR_STREAM_WRITE;
}
}
// check for write error
if(stream->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
free(buff);
return HTTPC_ERROR_STREAM_WRITE;
}
// count bytes to read left
if(len > 0) {
len -= bytesRead;
}
delay(0);
}
free(buff);
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] end of chunk or data (transferred: %d).\n", bytesWritten);
if((size > 0) && (size != bytesWritten)) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] transferred size %d and request size %d mismatch!.\n", bytesWritten, size);
return HTTPC_ERROR_STREAM_WRITE;
}
return bytesWritten;
}
/**
* called to handle error return, may disconnect the connection if still exists
* @param error

@ -28,7 +28,7 @@
#include <memory>
#include <Arduino.h>
#include <StreamString.h>
#include <WiFiClient.h>
#ifdef DEBUG_ESP_HTTP_CLIENT
@ -148,8 +148,6 @@ typedef enum {
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
class StreamString;
class HTTPClient
{
public:

@ -115,9 +115,9 @@ void setup(void) {
// swallow the exact amount matching the full request+content,
// hence the tcp connection cannot be handled anymore by the
// webserver.
#ifdef STREAMTO_API
#ifdef STREAMSEND_API
// we are lucky
client->toWithTimeout(Serial, 500);
client->sendAll(Serial, 500);
#else
auto last = millis();
while ((millis() - last) < 500) {

@ -123,11 +123,8 @@ void setup(void) {
});
server.onNotFound(handleNotFound);
//here the list of headers to be recorded
const char * headerkeys[] = {"User-Agent", "Cookie"} ;
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
//ask server to track these headers
server.collectHeaders(headerkeys, headerkeyssize);
server.collectHeaders("User-Agent", "Cookie");
server.begin();
Serial.println("HTTP server started");
}

@ -28,6 +28,7 @@
#include "FS.h"
#include "base64.h"
#include "detail/RequestHandlersImpl.h"
#include <StreamDev.h>
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
@ -372,7 +373,7 @@ void ESP8266WebServerTemplate<ServerType>::close() {
_server.close();
_currentStatus = HC_NONE;
if(!_headerKeysCount)
collectHeaders(0, 0);
collectHeaders();
}
template <typename ServerType>
@ -440,72 +441,69 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int
_responseHeaders = "";
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) {
String header;
// Can we asume the following?
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if(content.length())
sendContent(content);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if (contentLength) {
sendContent_P(content);
}
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if (contentLength) {
sendContent_P(content, contentLength);
}
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, char* content_type, const String& content) {
send(code, (const char*)content_type, content);
return send(code, (const char*)content_type, content);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) {
return send(code, content_type, content.c_str(), content.length());
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const String& content_type, const String& content) {
send(code, (const char*)content_type.c_str(), content);
return send(code, (const char*)content_type.c_str(), content);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) {
if (_currentMethod == HTTP_HEAD) return;
const char * footer = "\r\n";
size_t len = content.length();
StreamConstPtr ref(content.c_str(), content.length());
sendContent(&ref);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, Stream* stream, size_t content_length /*= 0*/) {
String header;
if (content_length == 0)
content_length = std::max((ssize_t)0, stream->streamRemaining());
_prepareHeader(header, code, content_type, content_length);
size_t sent = StreamConstPtr(header).sendAll(&_currentClient);
if (sent != header.length())
DBGWS("HTTPServer: error: sent %zd on %u bytes\n", sent, header.length());
if (content_length)
return sendContent(stream, content_length);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) {
StreamConstPtr ref(content, strlen_P(content));
return send(code, String(content_type).c_str(), &ref);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
StreamConstPtr ref(content, contentLength);
return send(code, String(content_type).c_str(), &ref);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::sendContent(Stream* content, ssize_t content_length /* = 0*/) {
if (_currentMethod == HTTP_HEAD)
return;
if (content_length <= 0)
content_length = std::max((ssize_t)0, content->streamRemaining());
if(_chunked) {
char chunkSize[11];
sprintf(chunkSize, "%zx\r\n", len);
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
_currentClient.printf("%zx\r\n", content_length);
}
_currentClient.write((const uint8_t *)content.c_str(), len);
if(_chunked){
_currentClient.write((const uint8_t *)footer, 2);
if (len == 0) {
ssize_t sent = content->sendSize(&_currentClient, content_length);
if (sent != content_length)
{
DBGWS("HTTPServer: error: short send after timeout (%d<%d)\n", sent, content_length);
}
if(_chunked) {
_currentClient.printf_P(PSTR("\r\n"));
if (content_length == 0) {
_chunked = false;
}
}
@ -518,19 +516,8 @@ void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content) {
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) {
const char * footer = "\r\n";
if(_chunked) {
char chunkSize[11];
sprintf(chunkSize, "%zx\r\n", size);
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
}
_currentClient.write_P(content, size);
if(_chunked){
_currentClient.write((const uint8_t *)footer, 2);
if (size == 0) {
_chunked = false;
}
}
StreamConstPtr ptr(content, size);
return sendContent(&ptr, size);
}
template <typename ServerType>
@ -557,7 +544,7 @@ void ESP8266WebServerTemplate<ServerType>::_streamFileCore(const size_t fileSize
}
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::pathArg(unsigned int i) const {
const String& ESP8266WebServerTemplate<ServerType>::pathArg(unsigned int i) const {
if (_currentHandler != nullptr)
return _currentHandler->pathArg(i);
return emptyString;
@ -608,7 +595,6 @@ bool ESP8266WebServerTemplate<ServerType>::hasArg(const String& name) const {
return false;
}
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) const {
for (int i = 0; i < _headerKeysCount; ++i) {
@ -618,21 +604,30 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
return emptyString;
}
template<typename ServerType>
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 2;
if (_currentHeaders){
if (_currentHeaders)
delete[] _currentHeaders;
}
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders = new RequestArgument[_headerKeysCount = headerKeysCount + 2];
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
for (int i = 2; i < _headerKeysCount; i++){
_currentHeaders[i].key = headerKeys[i-2];
_currentHeaders[i].key = headerKeys[i - 2];
}
}
template <typename ServerType>
template <typename... Args>
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const Args&... args) {
if (_currentHeaders)
delete[] _currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount = sizeof...(args) + 2] {
{ .key = FPSTR(AUTHORIZATION_HEADER), .value = emptyString },
{ .key = FPSTR(ETAG_HEADER), .value = emptyString },
{ .key = args, .value = emptyString } ...
};
}
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::header(int i) const {
if (i < _headerKeysCount)
@ -694,7 +689,7 @@ void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
}
if (!handled) {
using namespace mime;
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
send(404, FPSTR(mimeTable[html].mimeType), String(F("Not found: ")) + _currentUri);
handled = true;
}
if (handled) {

@ -138,6 +138,8 @@ public:
int args() const; // get arguments count
bool hasArg(const String& name) const; // check if argument exists
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
template<typename... Args>
void collectHeaders(const Args&... args); // set the request headers to collect (variadic template version)
const String& header(const String& name) const; // get request header value by name
const String& header(int i) const; // get request header value by number
const String& headerName(int i) const; // get request header name by number
@ -149,7 +151,7 @@ public:
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char* content_type = NULL, const String& content = String(""));
void send(int code, const char* content_type = NULL, const String& content = emptyString);
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send(int code, const char *content_type, const char *content) {
@ -164,14 +166,23 @@ public:
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void send(int code, const char* content_type, Stream* stream, size_t content_length = 0);
void send(int code, const char* content_type, Stream& stream, size_t content_length = 0);
void setContentLength(const size_t contentLength);
void sendHeader(const String& name, const String& value, bool first = false);
void sendContent(const String& content);
void sendContent(String& content) {
sendContent((const String&)content);
}
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
void sendContent(const char *content) { sendContent_P(content); }
void sendContent(const char *content, size_t size) { sendContent_P(content, size); }
void sendContent(Stream* content, ssize_t content_length = 0);
void sendContent(Stream& content, ssize_t content_length = 0) { sendContent(&content, content_length); }
bool chunkedResponseModeStart_P (int code, PGM_P content_type) {
if (_currentVersion == 0)
// no chunk mode in HTTP/1.0
@ -215,11 +226,35 @@ public:
size_t contentLength = 0;
_streamFileCore(file.size(), file.name(), contentType);
if (requestMethod == HTTP_GET) {
contentLength = _currentClient.write(file);
contentLength = file.sendAll(_currentClient);
}
return contentLength;
}
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T &aStream, const String& contentType, HTTPMethod requestMethod, ssize_t size) {
setContentLength(size);
send(200, contentType, emptyString);
if (requestMethod == HTTP_GET)
size = aStream.sendSize(_currentClient, size);
return size;
}
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T& aStream, const String& contentType, HTTPMethod requestMethod = HTTP_GET) {
ssize_t size = aStream.size();
if (size < 0)
{
send(500, F("text/html"), F("input stream: undetermined size"));
return 0;
}
return stream(aStream, contentType, requestMethod, size);
}
static String responseCodeToString(const int code);
void addHook (HookFunction hook) {

@ -37,22 +37,8 @@ namespace esp8266webserver {
template <typename ServerType>
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms)
{
if (!data.reserve(maxLength + 1))
return false;
data[0] = 0; // data.clear()??
while (data.length() < maxLength) {
int tries = timeout_ms;
size_t avail;
while (!(avail = client.available()) && tries--)
delay(1);
if (!avail)
break;
if (data.length() + avail > maxLength)
avail = maxLength - data.length();
while (avail--)
data += (char)client.read();
}
return data.length() == maxLength;
S2Stream dataStream(data);
return client.sendSize(dataStream, maxLength, timeout_ms) == maxLength;
}
template <typename ServerType>

@ -1,59 +0,0 @@
#include <ESP8266WiFi.h>
#include <AddrList.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
// preinit() is called before system startup
// from nonos-sdk's user entry point user_init()
void preinit() {
// Global WiFi constructors are not called yet
// (global class instances like WiFi, Serial... are not yet initialized)..
// No global object methods or C++ exceptions can be called in here!
//The below is a static class method, which is similar to a function, so it's ok.
ESP8266WiFiClass::preinitWiFiOff();
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println("sleeping 5s");
// during this period, a simple amp meter shows
// an average of 20mA with a Wemos D1 mini
// a DSO is needed to check #2111
delay(5000);
Serial.println("waking WiFi up, sleeping 5s");
WiFi.forceSleepWake();
// amp meter raises to 75mA
delay(5000);
Serial.println("connecting to AP " STASSID);
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
for (bool configured = false; !configured;) {
for (auto addr : addrList)
if ((configured = !addr.isLocal() && addr.ifnumber() == STATION_IF)) {
Serial.printf("STA: IF='%s' hostname='%s' addr= %s\n",
addr.ifname().c_str(),
addr.ifhostname(),
addr.toString().c_str());
break;
}
Serial.print('.');
delay(500);
}
// amp meter cycles within 75-80 mA
}
void loop() {
}

@ -28,27 +28,33 @@ const int httpsPort = 443;
// DigiCert High Assurance EV Root CA
const char trustRoot[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
MIIE6zCCBHGgAwIBAgIQAtX25VXj+RoJlA3D2bWkgzAKBggqhkjOPQQDAzBWMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjEwMzA0MDAw
MDAwWhcNMjIwMzA5MjM1OTU5WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHVi
LCBJbmMuMRUwEwYDVQQDDAwqLmdpdGh1Yi5jb20wWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAQf8SePhtD7JeGm0YuTQ4HihyeENuvsNFdYPPIxIx6Lj9iOu2ECkgy4
52UR+mhIF24OvPizDveyCFOqmG/MI7kwo4IDDTCCAwkwHwYDVR0jBBgwFoAUCrwI
KReMpTlteg7OM8cus+37w3owHQYDVR0OBBYEFP5TUYtiCp+N3FISu3CqxMlJhdG1
MCMGA1UdEQQcMBqCDCouZ2l0aHViLmNvbYIKZ2l0aHViLmNvbTAOBgNVHQ8BAf8E
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGXBgNVHR8EgY8w
gYwwRKBCoECGPmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5
YnJpZEVDQ1NIQTM4NDIwMjBDQTEuY3JsMESgQqBAhj5odHRwOi8vY3JsNC5kaWdp
Y2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0ExLmNybDA+
BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRp
Z2ljZXJ0LmNvbS9DUFMwgYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQcwAYYYaHR0
cDovL29jc3AuZGlnaWNlcnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0
cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0Ex
LmNydDAMBgNVHRMBAf8EAjAAMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAKXm+
8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF3/bWc4AAABAMARjBEAiBm
IdofaKj+XfeISM/2tjap1nQY1afFSBAcdw/YtgjmSQIgMqWoDyfO66suyk2VFcld
1C+WHUNGvXsCRPof5HG5QQgAdgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNc
buIPAgAAAXf9tZ0CAAAEAwBHMEUCIQCJzwZRfAvv0izotFx2KE0sgV8O+NfuHUpa
1866RqKEtwIgc65P+xToSqPbp/J1gSFBJgySI/a1YoB+3p8xXTYaDsAwCgYIKoZI
zj0EAwMDaAAwZQIxAL8fIlMNWdeKHalpm9z+ksCuYT4tSN1ubXeNvDywr56me+yT
+fr42MnEcBdUtLOVOAIwPNC9fAJjyHHTL2vaRW1JRnrovLKDQVbZpZNIZnlY3WFu
kmxiBWDOpyfJrG9vQ25K
-----END CERTIFICATE-----
)EOF";
X509List cert(trustRoot);

@ -0,0 +1,156 @@
/*
WiFiEcho - Echo server
released to public domain
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <PolledTimeout.h>
#include <algorithm> // std::min
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
constexpr int port = 23;
WiFiServer server(port);
WiFiClient client;
constexpr size_t sizes [] = { 0, 512, 384, 256, 128, 64, 16, 8, 4 };
constexpr uint32_t breathMs = 200;
esp8266::polledTimeout::oneShotFastMs enoughMs(breathMs);
esp8266::polledTimeout::periodicFastMs test(2000);
int t = 1; // test (1, 2 or 3, see below)
int s = 0; // sizes[] index
void setup() {
Serial.begin(115200);
Serial.println(ESP.getFullVersion());
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
Serial.print("\nConnecting to ");
Serial.println(STASSID);
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(500);
}
Serial.println();
Serial.print("connected, address=");
Serial.println(WiFi.localIP());
server.begin();
MDNS.begin("echo23");
Serial.printf("Ready!\n"
"- Use 'telnet/nc echo23.local %d' to try echo\n\n"
"- Use 'python3 echo-client.py' bandwidth meter to compare transfer APIs\n\n"
" and try typing 1, 1, 1, 2, 2, 2, 3, 3, 3 on console during transfers\n\n",
port);
}
void loop() {
MDNS.update();
static uint32_t tot = 0;
static uint32_t cnt = 0;
if (test && cnt) {
Serial.printf("measured-block-size=%u min-free-stack=%u", tot / cnt, ESP.getFreeContStack());
if (t == 2 && sizes[s]) {
Serial.printf(" (blocks: at most %d bytes)", sizes[s]);
}
if (t == 3 && sizes[s]) {
Serial.printf(" (blocks: exactly %d bytes)", sizes[s]);
}
if (t == 3 && !sizes[s]) {
Serial.printf(" (blocks: any size)");
}
Serial.printf("\n");
}
//check if there are any new clients
if (server.hasClient()) {
client = server.available();
Serial.println("New client");
}
if (Serial.available()) {
s = (s + 1) % (sizeof(sizes) / sizeof(sizes[0]));
switch (Serial.read()) {
case '1': if (t != 1) s = 0; t = 1; Serial.println("byte-by-byte (watch then press 2, 3 or 4)"); break;
case '2': if (t != 2) s = 1; t = 2; Serial.printf("through buffer (watch then press 2 again, or 1, 3 or 4)\n"); break;
case '3': if (t != 3) s = 0; t = 3; Serial.printf("direct access (sendAvailable - watch then press 3 again, or 1, 2 or 4)\n"); break;
case '4': t = 4; Serial.printf("direct access (sendAll - close peer to stop, then press 1, 2 or 3 before restarting peer)\n"); break;
}
tot = cnt = 0;
ESP.resetFreeContStack();
}
enoughMs.reset(breathMs);
if (t == 1) {
// byte by byte
while (client.available() && client.availableForWrite() && !enoughMs) {
// working char by char is not efficient
client.write(client.read());
cnt++;
tot += 1;
}
}
else if (t == 2) {
// block by block through a local buffer (2 copies)
while (client.available() && client.availableForWrite() && !enoughMs) {
size_t maxTo = std::min(client.available(), client.availableForWrite());
maxTo = std::min(maxTo, sizes[s]);
uint8_t buf[maxTo];
size_t tcp_got = client.read(buf, maxTo);
size_t tcp_sent = client.write(buf, tcp_got);
if (tcp_sent != maxTo) {
Serial.printf("len mismatch: available:%zd tcp-read:%zd serial-write:%zd\n", maxTo, tcp_got, tcp_sent);
}
tot += tcp_sent;
cnt++;
}
}
else if (t == 3) {
// stream to print, possibly with only one copy
if (sizes[s]) {
tot += client.sendSize(&client, sizes[s]);
} else {
tot += client.sendAvailable(&client);
}
cnt++;
switch (client.getLastSendReport()) {
case Stream::Report::Success: break;
case Stream::Report::TimedOut: Serial.println("Stream::send: timeout"); break;
case Stream::Report::ReadError: Serial.println("Stream::send: read error"); break;
case Stream::Report::WriteError: Serial.println("Stream::send: write error"); break;
case Stream::Report::ShortOperation: Serial.println("Stream::send: short transfer"); break;
}
}
else if (t == 4) {
// stream to print, possibly with only one copy
tot += client.sendAll(&client); // this one might not exit until peer close
cnt++;
switch (client.getLastSendReport()) {
case Stream::Report::Success: break;
case Stream::Report::TimedOut: Serial.println("Stream::send: timeout"); break;
case Stream::Report::ReadError: Serial.println("Stream::send: read error"); break;
case Stream::Report::WriteError: Serial.println("Stream::send: write error"); break;
case Stream::Report::ShortOperation: Serial.println("Stream::send: short transfer"); break;
}
}
}

@ -0,0 +1,49 @@
#!/usr/bin/env python3
import os
import asyncio
# 512 bytes
message = bytearray(512);
bufsize=len(message)
print('message len=', bufsize)
global recv
recv = 0
async def tcp_echo_open (ip, port):
return await asyncio.open_connection(ip, port)
async def tcp_echo_sender(message, writer):
print('Writer started')
while True:
writer.write(message)
await writer.drain()
async def tcp_echo_receiver(message, reader):
global recv
print('Reader started')
while True:
data = ''.encode('utf8')
while len(data) < bufsize:
data += await reader.read(bufsize - len(data))
recv += len(data);
if data != message:
print('error')
async def tcp_stat():
global recv
dur = 0
loopsec = 2
while True:
last = recv
await asyncio.sleep(loopsec) # drifting
dur += loopsec
print('BW=', (recv - last) * 2 * 8 / 1024 / loopsec, 'Kibits/s avg=', recv * 2 * 8 / 1024 / dur)
loop = asyncio.get_event_loop()
reader, writer = loop.run_until_complete(tcp_echo_open('echo23.local', 23))
loop.create_task(tcp_echo_receiver(message, reader))
loop.create_task(tcp_echo_sender(message, writer))
loop.create_task(tcp_stat())
loop.run_forever()

@ -1,5 +1,5 @@
// Demonstrate the use of WiFi.mode(WIFI_SHUTDOWN)/WiFi.mode(WIFI_RESUME)
// Demonstrate the use of WiFi.shutdown() and WiFi.resumeFromShutdown()
// Released to public domain
// Current on WEMOS D1 mini (including: LDO, usbserial chip):
@ -24,11 +24,6 @@ WiFiState state;
const char* ssid = STASSID;
const char* password = STAPSK;
void preinit(void) {
// Make sure, wifi stays off after boot.
ESP8266WiFiClass::preinitWiFiOff();
}
void setup() {
Serial.begin(74880);
//Serial.setDebugOutput(true); // If you need debug output
@ -44,7 +39,7 @@ void setup() {
ESP.rtcUserMemoryRead(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
unsigned long start = millis();
if (!WiFi.mode(WIFI_RESUME, &state)
if (!WiFi.resumeFromShutdown(state)
|| (WiFi.waitForConnectResult(10000) != WL_CONNECTED)) {
Serial.println("Cannot resume WiFi connection, connecting via begin...");
WiFi.persistent(false);
@ -68,7 +63,7 @@ void setup() {
// Here you can do whatever you need to do that needs a WiFi connection.
// ---
WiFi.mode(WIFI_SHUTDOWN, &state);
WiFi.shutdown(state);
ESP.rtcUserMemoryWrite(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
// ---
@ -82,4 +77,4 @@ void setup() {
void loop() {
// Nothing to do here.
}
}

@ -56,6 +56,8 @@ enableSTA KEYWORD2
enableAP KEYWORD2
forceSleepBegin KEYWORD2
forceSleepWake KEYWORD2
shutdown KEYWORD2
resumeFromShutdown KEYWORD2
#ESP8266WiFi
printDiag KEYWORD2
@ -156,6 +158,7 @@ loadCertificate KEYWORD2
loadPrivateKey KEYWORD2
loadCACert KEYWORD2
allowSelfSignedCerts KEYWORD2
setSSLVersion KEYWORD2
#WiFiServer
hasClient KEYWORD2

@ -125,7 +125,7 @@ int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char *
uint8_t fileHeader[60];
// 0..15 = filename in ASCII
// 48...57 = length in decimal ASCII
uint32_t length;
int32_t length;
if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) {
break;
}
@ -201,7 +201,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
free(der);
return nullptr;
}
if (data.read((uint8_t *)der, ci.length) != ci.length) {
if (data.read(der, ci.length) != (int)ci.length) {
free(der);
return nullptr;
}

@ -53,6 +53,7 @@ extern "C" {
#define DEBUG_WIFI(...) do { (void)0; } while (0)
#endif
extern "C" void enableWiFiAtBootTime (void) __attribute__((noinline));
class ESP8266WiFiClass : public ESP8266WiFiGenericClass, public ESP8266WiFiSTAClass, public ESP8266WiFiScanClass, public ESP8266WiFiAPClass {
public:

@ -83,7 +83,7 @@ struct WiFiEventHandlerOpaque
static std::list<WiFiEventHandler> sCbEventList;
bool ESP8266WiFiGenericClass::_persistent = true;
bool ESP8266WiFiGenericClass::_persistent = false;
WiFiMode_t ESP8266WiFiGenericClass::_forceSleepLastMode = WIFI_OFF;
ESP8266WiFiGenericClass::ESP8266WiFiGenericClass()
@ -105,7 +105,7 @@ WiFiEventHandler ESP8266WiFiGenericClass::onStationModeConnected(std::function<v
WiFiEventHandler handler = std::make_shared<WiFiEventHandlerOpaque>(WIFI_EVENT_STAMODE_CONNECTED, [f](System_Event_t* e) {
auto& src = e->event_info.connected;
WiFiEventStationModeConnected dst;
dst.ssid = String(reinterpret_cast<char*>(src.ssid));
dst.ssid.concat(reinterpret_cast<char*>(src.ssid), src.ssid_len);
memcpy(dst.bssid, src.bssid, 6);
dst.channel = src.channel;
f(dst);
@ -119,7 +119,7 @@ WiFiEventHandler ESP8266WiFiGenericClass::onStationModeDisconnected(std::functio
WiFiEventHandler handler = std::make_shared<WiFiEventHandlerOpaque>(WIFI_EVENT_STAMODE_DISCONNECTED, [f](System_Event_t* e){
auto& src = e->event_info.disconnected;
WiFiEventStationModeDisconnected dst;
dst.ssid = String(reinterpret_cast<char*>(src.ssid));
dst.ssid.concat(reinterpret_cast<char*>(src.ssid), src.ssid_len);
memcpy(dst.bssid, src.bssid, 6);
dst.reason = static_cast<WiFiDisconnectReason>(src.reason);
f(dst);
@ -401,27 +401,10 @@ bool ESP8266WiFiGenericClass::getPersistent(){
* set new mode
* @param m WiFiMode_t
*/
bool ESP8266WiFiGenericClass::mode(WiFiMode_t m, WiFiState* state) {
if (m == WIFI_SHUTDOWN) {
return shutdown(0, state);
}
else if (m == WIFI_RESUME) {
return resumeFromShutdown(state);
}
else if (m & ~(WIFI_STA | WIFI_AP))
bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) {
if (m & ~(WIFI_STA | WIFI_AP)) {
// any other bits than legacy disallowed
return false;
// m is now WIFI_STA, WIFI_AP or WIFI_AP_STA
if (state)
{
DEBUG_WIFI("core: state is useless without SHUTDOWN or RESUME\n");
}
if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) {
// wifi may have been put asleep by ESP8266WiFiGenericClass::preinitWiFiOff
wifi_fpm_do_wakeup();
wifi_fpm_close();
}
if(_persistent){
@ -432,6 +415,12 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m, WiFiState* state) {
return true;
}
if (m != WIFI_OFF && wifi_fpm_get_sleep_type() != NONE_SLEEP_T) {
// wifi starts asleep by default
wifi_fpm_do_wakeup();
wifi_fpm_close();
}
bool ret = false;
ETS_UART_INTR_DISABLE();
if(_persistent) {
@ -719,37 +708,37 @@ void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *ca
esp_schedule(); // break delay in hostByName
}
uint32_t ESP8266WiFiGenericClass::shutdownCRC (const WiFiState* state)
uint32_t ESP8266WiFiGenericClass::shutdownCRC (const WiFiState& state)
{
return state? crc32(&state->state, sizeof(state->state)): 0;
return crc32(&state.state, sizeof(state.state));
}
bool ESP8266WiFiGenericClass::shutdownValidCRC (const WiFiState* state)
bool ESP8266WiFiGenericClass::shutdownValidCRC (const WiFiState& state)
{
return state && (crc32(&state->state, sizeof(state->state)) == state->crc);
return crc32(&state.state, sizeof(state.state)) == state.crc;
}
bool ESP8266WiFiGenericClass::shutdown (uint32 sleepUs, WiFiState* state)
bool ESP8266WiFiGenericClass::shutdown (WiFiState& state, uint32 sleepUs)
{
bool persistent = _persistent;
WiFiMode_t before_off_mode = getMode();
if ((before_off_mode & WIFI_STA) && state)
if (before_off_mode & WIFI_STA)
{
bool ret = wifi_get_ip_info(STATION_IF, &state->state.ip);
bool ret = wifi_get_ip_info(STATION_IF, &state.state.ip);
if (!ret)
{
DEBUG_WIFI("core: error with wifi_get_ip_info(STATION_IF)\n");
return false;
}
memset(state->state.fwconfig.bssid, 0xff, 6);
ret = wifi_station_get_config(&state->state.fwconfig);
memset(state.state.fwconfig.bssid, 0xff, 6);
ret = wifi_station_get_config(&state.state.fwconfig);
if (!ret)
{
DEBUG_WIFI("core: error with wifi_station_get_config\n");
return false;
}
state->state.channel = wifi_get_channel();
state.state.channel = wifi_get_channel();
}
// disable persistence in FW so in case of power failure
@ -766,57 +755,63 @@ bool ESP8266WiFiGenericClass::shutdown (uint32 sleepUs, WiFiState* state)
}
// WiFi is now in force-sleep mode
// finish filling state and process crc
if (state)
state.state.persistent = persistent;
state.state.mode = before_off_mode;
uint8_t i = 0;
for (auto& ntp: state.state.ntp)
{
// finish filling state and process crc
state->state.persistent = persistent;
state->state.mode = before_off_mode;
uint8_t i = 0;
for (auto& ntp: state->state.ntp)
{
ntp = *sntp_getserver(i++);
}
i = 0;
for (auto& dns: state->state.dns)
dns = WiFi.dnsIP(i++);
state->crc = shutdownCRC(state);
DEBUG_WIFI("core: state is saved\n");
ntp = *sntp_getserver(i++);
}
i = 0;
for (auto& dns: state.state.dns)
{
dns = WiFi.dnsIP(i++);
}
state.crc = shutdownCRC(state);
DEBUG_WIFI("core: state is saved\n");
return true;
}
bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState* state)
bool ESP8266WiFiGenericClass::shutdown (WiFiState& state) {
return shutdown(state, 0);
}
bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState& state)
{
if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) {
wifi_fpm_do_wakeup();
wifi_fpm_close();
}
if (!state || shutdownCRC(state) != state->crc)
if (shutdownCRC(state) != state.crc)
{
DEBUG_WIFI("core: resume: no state or bad crc\n");
DEBUG_WIFI("core: resume: bad crc\n");
return false;
}
persistent(state->state.persistent);
persistent(state.state.persistent);
if (!mode(state->state.mode))
if (!mode(state.state.mode))
{
DEBUG_WIFI("core: resume: can't set wifi mode to %d\n", state->state.mode);
DEBUG_WIFI("core: resume: can't set wifi mode to %d\n", state.state.mode);
return false;
}
if (state->state.mode & WIFI_STA)
if (state.state.mode & WIFI_STA)
{
IPAddress local(state->state.ip.ip);
IPAddress local(state.state.ip.ip);
if (local)
{
DEBUG_WIFI("core: resume: static address '%s'\n", local.toString().c_str());
WiFi.config(state->state.ip.ip, state->state.ip.gw, state->state.ip.netmask, state->state.dns[0], state->state.dns[1]);
WiFi.config(state.state.ip.ip, state.state.ip.gw, state.state.ip.netmask, state.state.dns[0], state.state.dns[1]);
uint8_t i = 0;
for (const auto& ntp: state->state.ntp)
for (const auto& ntp: state.state.ntp)
{
IPAddress ip(ntp);
if (ip.isSet())
@ -826,10 +821,23 @@ bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState* state)
}
}
}
auto beginResult = WiFi.begin((const char*)state->state.fwconfig.ssid,
(const char*)state->state.fwconfig.password,
state->state.channel,
state->state.fwconfig.bssid,
String ssid;
{
const char* ptr = reinterpret_cast<const char*>(state.state.fwconfig.ssid);
ssid.concat(ptr, strnlen(ptr, sizeof(station_config::ssid)));
}
String pass;
{
const char* ptr = reinterpret_cast<const char*>(state.state.fwconfig.password);
pass.concat(ptr, strnlen(ptr, sizeof(station_config::password)));
}
auto beginResult = WiFi.begin(ssid.c_str(),
pass.c_str(),
state.state.channel,
state.state.fwconfig.bssid,
true);
if (beginResult == WL_CONNECT_FAILED)
{
@ -843,37 +851,19 @@ bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState* state)
}
}
if (state->state.mode & WIFI_AP)
if (state.state.mode & WIFI_AP)
{
DEBUG_WIFI("core: resume AP mode TODO\n");
return false;
}
// success, invalidate saved state
state->crc++;
state.crc++;
return true;
}
//meant to be called from user-defined ::preinit()
void ESP8266WiFiGenericClass::preinitWiFiOff () {
// https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391
// WiFi.persistent(false);
// WiFi.mode(WIFI_OFF);
// WiFi.forceSleepBegin();
//WiFi.mode(WIFI_OFF) equivalent:
// datasheet:
// Set Wi-Fi working mode to Station mode, SoftAP
// or Station + SoftAP, and do not update flash
// (not persistent)
wifi_set_opmode_current(WIFI_OFF);
//WiFi.forceSleepBegin(/*default*/0) equivalent:
// sleep forever until wifi_fpm_do_wakeup() is called
wifi_fpm_set_sleep_type(MODEM_SLEEP_T);
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF);
// use WiFi.forceSleepWake() to wake WiFi up
// It was meant to be called from user-defined ::preinit()
// It is now deprecated by enableWiFiAtBootTime() and __disableWiFiAtBootTime()
}

@ -77,6 +77,39 @@ class ESP8266WiFiGenericClass {
uint8_t channel(void);
bool setSleepMode(WiFiSleepType_t type, uint8_t listenInterval = 0);
/**
* Set modem sleep mode (ESP32 compatibility)
* @param enable true to enable
* @return true if succeeded
*/
bool setSleep(bool enable)
{
if (enable)
{
return setSleepMode(WIFI_MODEM_SLEEP);
}
else
{
return setSleepMode(WIFI_NONE_SLEEP);
}
}
/**
* Set sleep mode (ESP32 compatibility)
* @param mode wifi_ps_type_t
* @return true if succeeded
*/
bool setSleep(wifi_ps_type_t mode)
{
return setSleepMode((WiFiSleepType_t)mode);
}
/**
* Get current sleep state (ESP32 compatibility)
* @return true if modem sleep is enabled
*/
bool getSleep()
{
return getSleepMode() == WIFI_MODEM_SLEEP;
}
WiFiSleepType_t getSleepMode();
uint8_t getListenInterval ();
@ -87,9 +120,9 @@ class ESP8266WiFiGenericClass {
void setOutputPower(float dBm);
void persistent(bool persistent);
static void persistent(bool persistent);
bool mode(WiFiMode_t, WiFiState* state = nullptr);
bool mode(WiFiMode_t);
WiFiMode_t getMode();
bool enableSTA(bool enable);
@ -98,21 +131,23 @@ class ESP8266WiFiGenericClass {
bool forceSleepBegin(uint32 sleepUs = 0);
bool forceSleepWake();
static uint32_t shutdownCRC (const WiFiState* state);
static bool shutdownValidCRC (const WiFiState* state);
static void preinitWiFiOff (); //meant to be called in user-defined preinit()
// wrappers around mode() and forceSleepBegin/Wake
// - sleepUs is WiFi.forceSleepBegin() parameter, 0 means forever
// - saveState is the user's state to hold configuration on restore
bool shutdown(WiFiState& stateSave);
bool shutdown(WiFiState& stateSave, uint32 sleepUs);
bool resumeFromShutdown(WiFiState& savedState);
static bool shutdownValidCRC (const WiFiState& state);
static void preinitWiFiOff () __attribute__((deprecated("WiFi is off by default at boot, use enableWiFiAtBoot() for legacy behavior")));
protected:
static bool _persistent;
static WiFiMode_t _forceSleepLastMode;
static void _eventCallback(void *event);
static uint32_t shutdownCRC (const WiFiState& state);
// called by WiFi.mode(SHUTDOWN/RESTORE, state)
// - sleepUs is WiFi.forceSleepBegin() parameter, 0 = forever
// - saveState is the user's state to hold configuration on restore
bool shutdown (uint32 sleepUs = 0, WiFiState* stateSave = nullptr);
bool resumeFromShutdown (WiFiState* savedState = nullptr);
static void _eventCallback(void *event);
// ----------------------------------------------------------------------------------------------
// ------------------------------------ Generic Network function --------------------------------

@ -504,6 +504,18 @@ IPAddress ESP8266WiFiSTAClass::dnsIP(uint8_t dns_no) {
return IPAddress(dns_getserver(dns_no));
}
/**
* Get the broadcast ip address.
* @return IPAddress Bradcast IP
*/
IPAddress ESP8266WiFiSTAClass::broadcastIP()
{
struct ip_info ip;
wifi_get_ip_info(STATION_IF, &ip);
return IPAddress(ip.ip.addr | ~(ip.netmask.addr));
}
/**
* Return Connection status.
* @return one of the value defined in wl_status_t

@ -70,6 +70,7 @@ class ESP8266WiFiSTAClass: public LwipIntf {
IPAddress gatewayIP();
IPAddress dnsIP(uint8_t dns_no = 0);
IPAddress broadcastIP();
// STA WiFi info
wl_status_t status();
String SSID() const;

@ -34,8 +34,7 @@
typedef enum WiFiMode
{
WIFI_OFF = 0, WIFI_STA = 1, WIFI_AP = 2, WIFI_AP_STA = 3,
/* these two pseudo modes are experimental: */ WIFI_SHUTDOWN = 4, WIFI_RESUME = 8
WIFI_OFF = 0, WIFI_STA = 1, WIFI_AP = 2, WIFI_AP_STA = 3
} WiFiMode_t;
typedef enum WiFiPhyMode
@ -48,6 +47,13 @@ typedef enum WiFiSleepType
WIFI_NONE_SLEEP = 0, WIFI_LIGHT_SLEEP = 1, WIFI_MODEM_SLEEP = 2
} WiFiSleepType_t;
// ESP32 compatibility
typedef enum wifi_ps_type
{
WIFI_PS_NONE = WIFI_NONE_SLEEP,
WIFI_PS_MIN_MODEM = WIFI_MODEM_SLEEP,
WIFI_PS_MAX_MODEM = WIFI_LIGHT_SLEEP,
} wifi_ps_type_t;
typedef enum WiFiEvent
{

@ -40,6 +40,7 @@ extern "C"
#include "lwip/netif.h"
#include <include/ClientContext.h>
#include "c_types.h"
#include <StreamDev.h>
uint16_t WiFiClient::_localPort = 0;
@ -212,23 +213,19 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size)
return 0;
}
_client->setTimeout(_timeout);
return _client->write(buf, size);
}
size_t WiFiClient::write(Stream& stream, size_t unused)
{
(void) unused;
return WiFiClient::write(stream);
return _client->write((const char*)buf, size);
}
size_t WiFiClient::write(Stream& stream)
{
// (this method is deprecated)
if (!_client || !stream.available())
{
return 0;
}
_client->setTimeout(_timeout);
return _client->write(stream);
// core up to 2.7.4 was equivalent to this
return stream.sendAll(this);
}
size_t WiFiClient::write_P(PGM_P buf, size_t size)
@ -238,13 +235,14 @@ size_t WiFiClient::write_P(PGM_P buf, size_t size)
return 0;
}
_client->setTimeout(_timeout);
return _client->write_P(buf, size);
StreamConstPtr nopeek(buf, size);
return nopeek.sendAll(this);
}
int WiFiClient::available()
{
if (!_client)
return false;
return 0;
int result = _client->getSize();
@ -262,10 +260,14 @@ int WiFiClient::read()
return _client->read();
}
int WiFiClient::read(uint8_t* buf, size_t size)
{
return (int) _client->read(reinterpret_cast<char*>(buf), size);
return (int)_client->read((char*)buf, size);
}
int WiFiClient::read(char* buf, size_t size)
{
return (int)_client->read(buf, size);
}
int WiFiClient::peek()
@ -304,7 +306,7 @@ bool WiFiClient::flush(unsigned int maxWaitMs)
if (maxWaitMs == 0)
maxWaitMs = WIFICLIENT_MAX_FLUSH_WAIT_MS;
return _client->wait_until_sent(maxWaitMs);
return _client->wait_until_acked(maxWaitMs);
}
bool WiFiClient::stop(unsigned int maxWaitMs)
@ -412,3 +414,28 @@ uint8_t WiFiClient::getKeepAliveCount () const
{
return _client->getKeepAliveCount();
}
bool WiFiClient::hasPeekBufferAPI () const
{
return true;
}
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* WiFiClient::peekBuffer ()
{
return _client? _client->peekBuffer(): nullptr;
}
// return number of byte accessible by peekBuffer()
size_t WiFiClient::peekAvailable ()
{
return _client? _client->peekAvailable(): 0;
}
// consume bytes after use (see peekBuffer)
void WiFiClient::peekConsume (size_t consume)
{
if (_client)
_client->peekConsume(consume);
}

@ -59,14 +59,13 @@ public:
virtual size_t write(uint8_t) override;
virtual size_t write(const uint8_t *buf, size_t size) override;
virtual size_t write_P(PGM_P buf, size_t size);
size_t write(Stream& stream);
// This one is deprecated, use write(Stream& instead)
size_t write(Stream& stream, size_t unitSize) __attribute__ ((deprecated));
size_t write(Stream& stream) [[ deprecated("use stream.sendHow(client...)") ]];
virtual int available() override;
virtual int read() override;
virtual int read(uint8_t *buf, size_t size) override;
virtual int read(uint8_t* buf, size_t size) override;
int read(char* buf, size_t size);
virtual int peek() override;
virtual size_t peekBytes(uint8_t *buffer, size_t length);
size_t peekBytes(char *buffer, size_t length) {
@ -120,6 +119,22 @@ public:
bool getSync() const;
void setSync(bool sync);
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override;
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override;
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override;
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override;
virtual bool outputCanTimeout () override { return connected(); }
virtual bool inputCanTimeout () override { return connected(); }
protected:
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);

@ -93,6 +93,8 @@ void WiFiClientSecureCtx::_clear() {
_session = nullptr;
_cipher_list = nullptr;
_cipher_cnt = 0;
_tls_min = BR_TLS10;
_tls_max = BR_TLS12;
}
void WiFiClientSecureCtx::_clearAuthenticationSettings() {
@ -125,7 +127,7 @@ WiFiClientSecureCtx::~WiFiClientSecureCtx() {
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
const X509List *chain, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta) {
const X509List *client_CA_ta, int tls_min, int tls_max) {
_clear();
_clearAuthenticationSettings();
stack_thunk_add_ref();
@ -133,6 +135,8 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
_iobuf_out_size = iobuf_out_size;
_client = client;
_client->ref();
_tls_min = tls_min;
_tls_max = tls_max;
if (!_connectSSLServerRSA(chain, sk, cache, client_CA_ta)) {
_client->unref();
_client = nullptr;
@ -144,7 +148,7 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
const X509List *chain,
unsigned cert_issuer_key_type, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta) {
const X509List *client_CA_ta, int tls_min, int tls_max) {
_clear();
_clearAuthenticationSettings();
stack_thunk_add_ref();
@ -152,6 +156,8 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
_iobuf_out_size = iobuf_out_size;
_client = client;
_client->ref();
_tls_min = tls_min;
_tls_max = tls_max;
if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, cache, client_CA_ta)) {
_client->unref();
_client = nullptr;
@ -257,6 +263,27 @@ uint8_t WiFiClientSecureCtx::connected() {
return false;
}
int WiFiClientSecureCtx::availableForWrite () {
// code taken from ::_write()
if (!connected() || !_handshake_done) {
return 0;
}
// Get BearSSL to a state where we can send
if (_run_until(BR_SSL_SENDAPP) < 0) {
return 0;
}
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
size_t sendapp_len;
(void)br_ssl_engine_sendapp_buf(_eng, &sendapp_len);
// We want to call br_ssl_engine_sendapp_ack(0) but 0 is forbidden (bssl doc).
// After checking br_ssl_engine_sendapp_buf() src code,
// it seems that it is OK to not call ack when the buffer is left untouched.
//forbidden: br_ssl_engine_sendapp_ack(_eng, 0);
return (int)sendapp_len;
}
return 0;
}
size_t WiFiClientSecureCtx::_write(const uint8_t *buf, size_t size, bool pmem) {
size_t sent_bytes = 0;
@ -306,28 +333,12 @@ size_t WiFiClientSecureCtx::write_P(PGM_P buf, size_t size) {
return _write((const uint8_t *)buf, size, true);
}
// We have to manually read and send individual chunks.
size_t WiFiClientSecureCtx::write(Stream& stream) {
size_t totalSent = 0;
size_t countRead;
size_t countSent;
if (!connected() || !_handshake_done) {
DEBUG_BSSL("write: Connect/handshake not completed yet\n");
return 0;
}
do {
uint8_t temp[256]; // Temporary chunk size same as ClientContext
countSent = 0;
countRead = stream.readBytes(temp, sizeof(temp));
if (countRead) {
countSent = _write((const uint8_t*)temp, countRead, true);
totalSent += countSent;
}
yield(); // Feed the WDT
} while ((countSent == countRead) && (countSent > 0));
return totalSent;
return stream.sendAll(this);
}
int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) {
@ -362,6 +373,22 @@ int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) {
return 0; // If we're connected, no error but no read.
}
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* WiFiClientSecureCtx::peekBuffer ()
{
return (const char*)_recvapp_buf;
}
// consume bytes after use (see peekBuffer)
void WiFiClientSecureCtx::peekConsume (size_t consume)
{
// according to WiFiClientSecureCtx::read:
br_ssl_engine_recvapp_ack(_eng, consume);
_recvapp_buf = nullptr;
_recvapp_len = 0;
}
int WiFiClientSecureCtx::read() {
uint8_t c;
if (1 == read(&c, 1)) {
@ -989,6 +1016,17 @@ bool WiFiClientSecureCtx::setCiphers(const std::vector<uint16_t>& list) {
return setCiphers(&list[0], list.size());
}
bool WiFiClientSecureCtx::setSSLVersion(uint32_t min, uint32_t max) {
if ( ((min != BR_TLS10) && (min != BR_TLS11) && (min != BR_TLS12)) ||
((max != BR_TLS10) && (max != BR_TLS11) && (max != BR_TLS12)) ||
(max < min) ) {
return false; // Invalid options
}
_tls_min = min;
_tls_max = max;
return true;
}
// Installs the appropriate X509 cert validation method for a client connection
bool WiFiClientSecureCtx::_installClientX509Validator() {
if (_use_insecure || _use_fingerprint || _use_self_signed) {
@ -1094,6 +1132,7 @@ bool WiFiClientSecureCtx::_connectSSL(const char* hostName) {
return false;
}
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
br_ssl_engine_set_versions(_eng, _tls_min, _tls_max);
// Apply any client certificates, if supplied.
if (_sk && _sk->isRSA()) {
@ -1208,6 +1247,7 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain,
sk ? sk->getRSA() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
br_rsa_private_get_default(), br_rsa_pkcs1_sign_get_default());
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
br_ssl_engine_set_versions(_eng, _tls_min, _tls_max);
if (cache != nullptr)
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
@ -1254,6 +1294,7 @@ bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain,
sk ? sk->getEC() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
cert_issuer_key_type, br_ssl_engine_get_ec(_eng), br_ecdsa_i15_sign_asn1);
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
br_ssl_engine_set_versions(_eng, _tls_min, _tls_max);
if (cache != nullptr)
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
@ -1271,6 +1312,7 @@ bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain,
(void) chain;
(void) cert_issuer_key_type;
(void) sk;
(void) cache;
(void) client_CA_ta;
DEBUG_BSSL("_connectSSLServerEC: Attempting to use EC cert in minimal cipher mode (no EC)\n");
return false;

@ -48,6 +48,7 @@ class WiFiClientSecureCtx : public WiFiClient {
size_t write_P(PGM_P buf, size_t size) override;
size_t write(Stream& stream); // Note this is not virtual
int read(uint8_t *buf, size_t size) override;
int read(char *buf, size_t size) { return read((uint8_t*)buf, size); }
int available() override;
int read() override;
int peek() override;
@ -57,6 +58,8 @@ class WiFiClientSecureCtx : public WiFiClient {
void flush() override { (void)flush(0); }
void stop() override { (void)stop(0); }
int availableForWrite() override;
// Allow sessions to be saved/restored automatically to a memory area
void setSession(Session *session) { _session = session; }
@ -120,6 +123,23 @@ class WiFiClientSecureCtx : public WiFiClient {
bool setCiphers(const std::vector<uint16_t>& list);
bool setCiphersLessSecure(); // Only use the limited set of RSA ciphers without EC
// Limit the TLS versions BearSSL will connect with. Default is
// BR_TLS10...BR_TLS12
bool setSSLVersion(uint32_t min = BR_TLS10, uint32_t max = BR_TLS12);
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override { return true; }
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override { return WiFiClientSecureCtx::available(); }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override;
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override;
protected:
bool _connectSSL(const char *hostName); // Do initial SSL handshake
@ -161,6 +181,10 @@ class WiFiClientSecureCtx : public WiFiClient {
std::shared_ptr<uint16_t> _cipher_list;
uint8_t _cipher_cnt;
// TLS ciphers allowed
uint32_t _tls_min;
uint32_t _tls_max;
unsigned char *_recvapp_buf;
size_t _recvapp_len;
@ -180,10 +204,10 @@ class WiFiClientSecureCtx : public WiFiClient {
friend class WiFiClientSecure; // access to private context constructors
WiFiClientSecureCtx(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta);
const X509List *client_CA_ta, int tls_min, int tls_max);
WiFiClientSecureCtx(ClientContext* client, const X509List *chain, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta);
const X509List *client_CA_ta, int tls_min, int tls_max);
// RSA keyed server
bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk,
@ -227,6 +251,7 @@ class WiFiClientSecure : public WiFiClient {
size_t write(Stream& stream) /* Note this is not virtual */ { return _ctx->write(stream); }
int read(uint8_t *buf, size_t size) override { return _ctx->read(buf, size); }
int available() override { return _ctx->available(); }
int availableForWrite() override { return _ctx->availableForWrite(); }
int read() override { return _ctx->read(); }
int peek() override { return _ctx->peek(); }
size_t peekBytes(uint8_t *buffer, size_t length) override { return _ctx->peekBytes(buffer, length); }
@ -282,11 +307,28 @@ class WiFiClientSecure : public WiFiClient {
bool setCiphers(const std::vector<uint16_t> list) { return _ctx->setCiphers(list); }
bool setCiphersLessSecure() { return _ctx->setCiphersLessSecure(); } // Only use the limited set of RSA ciphers without EC
// Limit the TLS versions BearSSL will connect with. Default is
// BR_TLS10...BR_TLS12. Allowed values are: BR_TLS10, BR_TLS11, BR_TLS12
bool setSSLVersion(uint32_t min = BR_TLS10, uint32_t max = BR_TLS12) { return _ctx->setSSLVersion(min, max); };
// Check for Maximum Fragment Length support for given len before connection (possibly insecure)
static bool probeMaxFragmentLength(IPAddress ip, uint16_t port, uint16_t len);
static bool probeMaxFragmentLength(const char *hostname, uint16_t port, uint16_t len);
static bool probeMaxFragmentLength(const String& host, uint16_t port, uint16_t len);
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override { return true; }
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override { return _ctx->available(); }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override { return _ctx->peekBuffer(); }
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override { return _ctx->peekConsume(consume); }
private:
std::shared_ptr<WiFiClientSecureCtx> _ctx;
@ -294,14 +336,14 @@ class WiFiClientSecure : public WiFiClient {
friend class WiFiServerSecure; // Server needs to access these constructors
WiFiClientSecure(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta):
_ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
const X509List *client_CA_ta, int tls_min, int tls_max):
_ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta, tls_min, tls_max)) {
}
WiFiClientSecure(ClientContext* client, const X509List *chain, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta):
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
const X509List *client_CA_ta, int tls_min, int tls_max):
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta, tls_min, tls_max)) {
}
}; // class WiFiClientSecure

@ -79,13 +79,13 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) {
(void) status; // Unused
if (_unclaimed) {
if (_sk && _sk->isRSA()) {
WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta);
WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta, _tls_min, _tls_max);
_unclaimed = _unclaimed->next();
result.setNoDelay(_noDelay);
DEBUGV("WS:av\r\n");
return result;
} else if (_sk && _sk->isEC()) {
WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta);
WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta, _tls_min, _tls_max);
_unclaimed = _unclaimed->next();
result.setNoDelay(_noDelay);
DEBUGV("WS:av\r\n");
@ -101,4 +101,15 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) {
return WiFiClientSecure();
}
bool WiFiServerSecure::setSSLVersion(uint32_t min, uint32_t max) {
if ( ((min != BR_TLS10) && (min != BR_TLS11) && (min != BR_TLS12)) ||
((max != BR_TLS10) && (max != BR_TLS11) && (max != BR_TLS12)) ||
(max < min) ) {
return false; // Invalid options
}
_tls_min = min;
_tls_max = max;
return true;
}
};

@ -60,6 +60,10 @@ class WiFiServerSecure : public WiFiServer {
_client_CA_ta = client_CA_ta;
}
// Limit the TLS versions BearSSL will connect with. Default is
// BR_TLS10...BR_TLS12
bool setSSLVersion(uint32_t min = BR_TLS10, uint32_t max = BR_TLS12);
// If awaiting connection available and authenticated (i.e. client cert), return it.
WiFiClientSecure available(uint8_t* status = NULL);
@ -76,6 +80,9 @@ class WiFiServerSecure : public WiFiServer {
const X509List *_client_CA_ta = nullptr;
ServerSessions *_cache = nullptr;
// TLS ciphers allowed
uint32_t _tls_min = BR_TLS10;
uint32_t _tls_max = BR_TLS12;
};
};

Some files were not shown because too many files have changed in this diff Show More