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:
commit
dc44227eda
boards.txt
cores/esp8266
Client.hEsp.cppEsp.hFS.cppFS.hFSImpl.hFunctionalInterrupt.cppHardwareSerial.hPrint.cppPrint.hStream.cppStream.hStreamDev.hStreamSend.cppStreamString.cppStreamString.hUpdater.cppUpdater.hWString.cppWString.hbase64.hcbuf.cppcont_util.cppcore_esp8266_app_entry_noextra4k.cppcore_esp8266_features.hcore_esp8266_i2s.cppcore_esp8266_i2s.hcore_esp8266_main.cppcore_esp8266_non32xfer.cppcore_esp8266_non32xfer.hcore_esp8266_phy.cppcore_esp8266_si2c.cppcore_esp8266_timer.cppcore_esp8266_vm.cppcore_esp8266_vm.hcore_esp8266_waveform.hcore_esp8266_waveform_phase.cppcore_esp8266_waveform_pwm.cppcore_esp8266_wiring.cppcore_esp8266_wiring_digital.cppcoredecls.hdebug.cppdebug.hesp_priv.hexc-sethandler.cppgdb_hooks.cppheap.cpphwdt_app_entry.cpphwdt_app_entry.hi2s.hlibc_replacements.cppreboot_uart_dwnld.cppspiffs_api.huart.cppuart.h
umm_malloc
doc
libraries
EEPROM
ESP8266HTTPClient
ESP8266WebServer
examples
src
ESP8266WiFi
792
boards.txt
792
boards.txt
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
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
|
356
cores/esp8266/StreamSend.cpp
Normal file
356
cores/esp8266/StreamSend.cpp
Normal file
@ -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 ©(const char *cstr, unsigned int length);
|
||||
String ©(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) {
|
||||
|
81
cores/esp8266/core_esp8266_i2s.h
Normal file
81
cores/esp8266/core_esp8266_i2s.h
Normal file
@ -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();
|
||||
}
|
||||
|
399
cores/esp8266/core_esp8266_vm.cpp
Normal file
399
cores/esp8266/core_esp8266_vm.cpp
Normal file
@ -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
|
11
cores/esp8266/core_esp8266_vm.h
Normal file
11
cores/esp8266/core_esp8266_vm.h
Normal file
@ -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
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
|
||||
|
1242
cores/esp8266/hwdt_app_entry.cpp
Normal file
1242
cores/esp8266/hwdt_app_entry.cpp
Normal file
File diff suppressed because it is too large
Load Diff
21
cores/esp8266/hwdt_app_entry.h
Normal file
21
cores/esp8266/hwdt_app_entry.h
Normal file
@ -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);
|
||||
|
156
libraries/ESP8266WiFi/examples/WiFiEcho/WiFiEcho.ino
Normal file
156
libraries/ESP8266WiFi/examples/WiFiEcho/WiFiEcho.ino
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
49
libraries/ESP8266WiFi/examples/WiFiEcho/echo-client.py
Executable file
49
libraries/ESP8266WiFi/examples/WiFiEcho/echo-client.py
Executable file
@ -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
Loading…
x
Reference in New Issue
Block a user