mirror of
https://github.com/esp8266/Arduino.git
synced 2025-07-07 06:01:35 +03:00
Merge branch 'master' into master
This commit is contained in:
792
boards.txt
792
boards.txt
File diff suppressed because it is too large
Load Diff
@ -28,13 +28,13 @@ class Client: public Stream {
|
|||||||
public:
|
public:
|
||||||
virtual int connect(IPAddress ip, uint16_t port) = 0;
|
virtual int connect(IPAddress ip, uint16_t port) = 0;
|
||||||
virtual int connect(const char *host, 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(uint8_t) override = 0;
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) =0;
|
virtual size_t write(const uint8_t *buf, size_t size) override = 0;
|
||||||
virtual int available() = 0;
|
virtual int available() override = 0;
|
||||||
virtual int read() = 0;
|
virtual int read() override = 0;
|
||||||
virtual int read(uint8_t *buf, size_t size) = 0;
|
virtual int read(uint8_t *buf, size_t size) override = 0;
|
||||||
virtual int peek() = 0;
|
virtual int peek() override = 0;
|
||||||
virtual void flush() = 0;
|
virtual void flush() override = 0;
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
virtual uint8_t connected() = 0;
|
virtual uint8_t connected() = 0;
|
||||||
virtual operator bool() = 0;
|
virtual operator bool() = 0;
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
|
|
||||||
#include "coredecls.h"
|
#include "coredecls.h"
|
||||||
#include "umm_malloc/umm_malloc.h"
|
#include "umm_malloc/umm_malloc.h"
|
||||||
// #include "core_esp8266_vm.h"
|
|
||||||
#include <pgmspace.h>
|
#include <pgmspace.h>
|
||||||
#include "reboot_uart_dwnld.h"
|
#include "reboot_uart_dwnld.h"
|
||||||
|
|
||||||
@ -526,7 +525,7 @@ bool EspClass::eraseConfig(void) {
|
|||||||
return true;
|
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):
|
* 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;
|
return resultArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t EspClass::random() const
|
uint32_t EspClass::random()
|
||||||
{
|
{
|
||||||
union { uint32_t b32; uint8_t b8[4]; } result;
|
union { uint32_t b32; uint8_t b8[4]; } result;
|
||||||
random(result.b8, 4);
|
random(result.b8, 4);
|
||||||
@ -984,23 +983,12 @@ String EspClass::getSketchMD5()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EspClass::enableVM()
|
|
||||||
{
|
|
||||||
#ifdef UMM_HEAP_EXTERNAL
|
|
||||||
if (!vmEnabled)
|
|
||||||
install_vm_exception_handler();
|
|
||||||
vmEnabled = true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void EspClass::setExternalHeap()
|
void EspClass::setExternalHeap()
|
||||||
{
|
{
|
||||||
#ifdef UMM_HEAP_EXTERNAL
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
if (vmEnabled) {
|
|
||||||
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
|
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1016,11 +1004,9 @@ void EspClass::setIramHeap()
|
|||||||
void EspClass::setDramHeap()
|
void EspClass::setDramHeap()
|
||||||
{
|
{
|
||||||
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||||
if (vmEnabled) {
|
|
||||||
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#elif defined(UMM_HEAP_IRAM)
|
#elif defined(UMM_HEAP_IRAM)
|
||||||
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||||
panic();
|
panic();
|
||||||
@ -1031,11 +1017,9 @@ void EspClass::setDramHeap()
|
|||||||
void EspClass::resetHeap()
|
void EspClass::resetHeap()
|
||||||
{
|
{
|
||||||
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||||
if (vmEnabled) {
|
|
||||||
if (!umm_pop_heap()) {
|
if (!umm_pop_heap()) {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#elif defined(UMM_HEAP_IRAM)
|
#elif defined(UMM_HEAP_IRAM)
|
||||||
if (!umm_pop_heap()) {
|
if (!umm_pop_heap()) {
|
||||||
panic();
|
panic();
|
||||||
|
@ -87,75 +87,75 @@ typedef enum {
|
|||||||
class EspClass {
|
class EspClass {
|
||||||
public:
|
public:
|
||||||
// TODO: figure out how to set WDT timeout
|
// 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
|
// 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();
|
static void wdtDisable();
|
||||||
void wdtFeed();
|
static void wdtFeed();
|
||||||
|
|
||||||
void deepSleep(uint64_t time_us, RFMode mode = RF_DEFAULT);
|
static void deepSleep(uint64_t time_us, RFMode mode = RF_DEFAULT);
|
||||||
void deepSleepInstant(uint64_t time_us, RFMode mode = RF_DEFAULT);
|
static void deepSleepInstant(uint64_t time_us, RFMode mode = RF_DEFAULT);
|
||||||
uint64_t deepSleepMax();
|
static uint64_t deepSleepMax();
|
||||||
|
|
||||||
bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size);
|
static bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size);
|
||||||
bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size);
|
static bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
|
||||||
void reset();
|
static void reset();
|
||||||
void restart();
|
static void restart();
|
||||||
/**
|
/**
|
||||||
* @brief When calling this method the ESP8266 reboots into the UART download mode without
|
* @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
|
* 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.
|
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
|
||||||
*/
|
*/
|
||||||
[[noreturn]] void rebootIntoUartDownloadMode();
|
[[noreturn]] static void rebootIntoUartDownloadMode();
|
||||||
|
|
||||||
uint16_t getVcc();
|
static uint16_t getVcc();
|
||||||
uint32_t getChipId();
|
static uint32_t getChipId();
|
||||||
|
|
||||||
uint32_t getFreeHeap();
|
static uint32_t getFreeHeap();
|
||||||
uint16_t getMaxFreeBlockSize();
|
static uint16_t getMaxFreeBlockSize();
|
||||||
uint8_t getHeapFragmentation(); // in %
|
static uint8_t getHeapFragmentation(); // in %
|
||||||
void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr);
|
static void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr);
|
||||||
|
|
||||||
uint32_t getFreeContStack();
|
static uint32_t getFreeContStack();
|
||||||
void resetFreeContStack();
|
static void resetFreeContStack();
|
||||||
|
|
||||||
const char * getSdkVersion();
|
static const char * getSdkVersion();
|
||||||
String getCoreVersion();
|
static String getCoreVersion();
|
||||||
String getFullVersion();
|
static String getFullVersion();
|
||||||
|
|
||||||
uint8_t getBootVersion();
|
static uint8_t getBootVersion();
|
||||||
uint8_t getBootMode();
|
static uint8_t getBootMode();
|
||||||
|
|
||||||
#if defined(F_CPU) || defined(CORE_MOCK)
|
#if defined(F_CPU) || defined(CORE_MOCK)
|
||||||
constexpr
|
constexpr
|
||||||
#endif
|
#endif
|
||||||
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
|
static inline uint8_t getCpuFreqMHz() __attribute__((always_inline))
|
||||||
{
|
{
|
||||||
return esp_get_cpu_freq_mhz();
|
return esp_get_cpu_freq_mhz();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getFlashChipId();
|
static uint32_t getFlashChipId();
|
||||||
uint8_t getFlashChipVendorId();
|
static uint8_t getFlashChipVendorId();
|
||||||
|
|
||||||
//gets the actual chip size based on the flash id
|
//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
|
//gets the size of the flash as set by the compiler
|
||||||
uint32_t getFlashChipSize();
|
static uint32_t getFlashChipSize();
|
||||||
uint32_t getFlashChipSpeed();
|
static uint32_t getFlashChipSpeed();
|
||||||
FlashMode_t getFlashChipMode();
|
static FlashMode_t getFlashChipMode();
|
||||||
uint32_t getFlashChipSizeByChipId();
|
static uint32_t getFlashChipSizeByChipId();
|
||||||
|
|
||||||
uint32_t magicFlashChipSize(uint8_t byte);
|
static uint32_t magicFlashChipSize(uint8_t byte);
|
||||||
uint32_t magicFlashChipSpeed(uint8_t byte);
|
static uint32_t magicFlashChipSpeed(uint8_t byte);
|
||||||
FlashMode_t magicFlashChipMode(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
|
* @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
|
* 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 true success
|
||||||
* @retval false failure to write to flash or incorrect alignment of params
|
* @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
|
* @brief Write @a size bytes from @a data to flash at @a address
|
||||||
* This overload handles all misalignment cases
|
* 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
|
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
|
||||||
* @return bool result of operation
|
* @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
|
* @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
|
* This overload requires @a data and @a size to be 4 byte aligned
|
||||||
@ -188,7 +188,7 @@ class EspClass {
|
|||||||
* @retval true success
|
* @retval true success
|
||||||
* @retval false failure to read from flash or incorrect alignment of params
|
* @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
|
* @brief Read @a size bytes to @a data to flash at @a address
|
||||||
* This overload handles all misalignment cases
|
* 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
|
* @param size amount of data, passing not multiple of 4 will cause additional read
|
||||||
* @return bool result of operation
|
* @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();
|
static uint32_t getSketchSize();
|
||||||
String getSketchMD5();
|
static String getSketchMD5();
|
||||||
uint32_t getFreeSketchSpace();
|
static uint32_t getFreeSketchSpace();
|
||||||
bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true);
|
static bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true);
|
||||||
|
|
||||||
String getResetReason();
|
static String getResetReason();
|
||||||
String getResetInfo();
|
static String getResetInfo();
|
||||||
struct rst_info * getResetInfoPtr();
|
static struct rst_info * getResetInfoPtr();
|
||||||
|
|
||||||
bool eraseConfig();
|
static bool eraseConfig();
|
||||||
|
|
||||||
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
|
static uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes);
|
||||||
uint32_t random() const;
|
static uint32_t random();
|
||||||
|
|
||||||
#if !defined(CORE_MOCK)
|
#if !defined(CORE_MOCK)
|
||||||
inline uint32_t getCycleCount() __attribute__((always_inline))
|
static inline uint32_t getCycleCount() __attribute__((always_inline))
|
||||||
{
|
{
|
||||||
return esp_get_cycle_count();
|
return esp_get_cycle_count();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
uint32_t getCycleCount();
|
static uint32_t getCycleCount();
|
||||||
#endif // !defined(CORE_MOCK)
|
#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.
|
* @brief Push current Heap selection and set Heap selection to DRAM.
|
||||||
*
|
*
|
||||||
* @param none
|
* @param none
|
||||||
* @return none
|
* @return none
|
||||||
*/
|
*/
|
||||||
void setDramHeap();
|
static void setDramHeap();
|
||||||
/**
|
/**
|
||||||
* @brief Push current Heap selection and set Heap selection to IRAM.
|
* @brief Push current Heap selection and set Heap selection to IRAM.
|
||||||
*
|
*
|
||||||
* @param none
|
* @param none
|
||||||
* @return none
|
* @return none
|
||||||
*/
|
*/
|
||||||
void setIramHeap();
|
static void setIramHeap();
|
||||||
/**
|
/**
|
||||||
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
|
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
|
||||||
*
|
*
|
||||||
* @param none
|
* @param none
|
||||||
* @return none
|
* @return none
|
||||||
*/
|
*/
|
||||||
void setExternalHeap();
|
static void setExternalHeap();
|
||||||
/**
|
/**
|
||||||
* @brief Restores Heap selection back to value present when
|
* @brief Restores Heap selection back to value present when
|
||||||
* setDramHeap, setIramHeap, or setExternalHeap was called.
|
* setDramHeap, setIramHeap, or setExternalHeap was called.
|
||||||
@ -256,11 +249,8 @@ class EspClass {
|
|||||||
* @param none
|
* @param none
|
||||||
* @return none
|
* @return none
|
||||||
*/
|
*/
|
||||||
void resetHeap();
|
static void resetHeap();
|
||||||
private:
|
private:
|
||||||
#ifdef UMM_HEAP_EXTERNAL
|
|
||||||
bool vmEnabled = false;
|
|
||||||
#endif
|
|
||||||
/**
|
/**
|
||||||
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
||||||
*
|
*
|
||||||
@ -271,7 +261,7 @@ class EspClass {
|
|||||||
* @retval true success
|
* @retval true success
|
||||||
* @retval false failed to read/write or invalid args
|
* @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
|
* @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,
|
* 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
|
* @param size amount of data
|
||||||
* @return size_t amount of data written, 0 on failure
|
* @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
|
* @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
|
* 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
|
* @param size amount of data, must be < 4
|
||||||
* @return bool result of operation
|
* @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;
|
extern EspClass ESP;
|
||||||
|
@ -66,7 +66,7 @@ int File::read() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t File::read(uint8_t* buf, size_t size) {
|
int File::read(uint8_t* buf, size_t size) {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -67,13 +67,14 @@ public:
|
|||||||
size_t readBytes(char *buffer, size_t length) override {
|
size_t readBytes(char *buffer, size_t length) override {
|
||||||
return read((uint8_t*)buffer, length);
|
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, SeekMode mode);
|
||||||
bool seek(uint32_t pos) {
|
bool seek(uint32_t pos) {
|
||||||
return seek(pos, SeekSet);
|
return seek(pos, SeekSet);
|
||||||
}
|
}
|
||||||
size_t position() const;
|
size_t position() const;
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
|
virtual ssize_t streamRemaining() override { return (ssize_t)size() - (ssize_t)position(); }
|
||||||
void close();
|
void close();
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
const char* name() const;
|
const char* name() const;
|
||||||
@ -84,6 +85,7 @@ public:
|
|||||||
bool isDirectory() const;
|
bool isDirectory() const;
|
||||||
|
|
||||||
// Arduino "class SD" methods for compatibility
|
// Arduino "class SD" methods for compatibility
|
||||||
|
//TODO use stream::send / check read(buf,size) result
|
||||||
template<typename T> size_t write(T &src){
|
template<typename T> size_t write(T &src){
|
||||||
uint8_t obuf[256];
|
uint8_t obuf[256];
|
||||||
size_t doneLen = 0;
|
size_t doneLen = 0;
|
||||||
|
@ -30,7 +30,7 @@ class FileImpl {
|
|||||||
public:
|
public:
|
||||||
virtual ~FileImpl() { }
|
virtual ~FileImpl() { }
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) = 0;
|
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 void flush() = 0;
|
||||||
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
||||||
virtual size_t position() const = 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);
|
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;
|
ArgStructure* localArg = (ArgStructure*)arg;
|
||||||
if (localArg->functionInfo->reqScheduledFunction)
|
if (localArg->functionInfo->reqScheduledFunction)
|
||||||
|
@ -101,31 +101,31 @@ public:
|
|||||||
return uart_get_rx_buffer_size(_uart);
|
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.
|
* 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!
|
* 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 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)
|
* 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;
|
int available(void) override;
|
||||||
@ -135,16 +135,45 @@ public:
|
|||||||
// return -1 when data is unvailable (arduino api)
|
// return -1 when data is unvailable (arduino api)
|
||||||
return uart_peek_char(_uart);
|
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
|
int read(void) override
|
||||||
{
|
{
|
||||||
// return -1 when data is unvailable (arduino api)
|
// return -1 when data is unvailable (arduino api)
|
||||||
return uart_read_char(_uart);
|
return uart_read_char(_uart);
|
||||||
}
|
}
|
||||||
// ::read(buffer, size): same as readBytes without timeout
|
// ::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);
|
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(char* buffer, size_t size) override;
|
||||||
size_t readBytes(uint8_t* buffer, size_t size) override
|
size_t readBytes(uint8_t* buffer, size_t size) override
|
||||||
{
|
{
|
||||||
|
@ -33,15 +33,7 @@
|
|||||||
|
|
||||||
/* default implementation: may be overridden */
|
/* default implementation: may be overridden */
|
||||||
size_t Print::write(const uint8_t *buffer, size_t size) {
|
size_t Print::write(const uint8_t *buffer, size_t size) {
|
||||||
|
IAMSLOW();
|
||||||
#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
|
|
||||||
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
while (size--) {
|
while (size--) {
|
||||||
|
@ -111,6 +111,10 @@ class Print {
|
|||||||
size_t println(void);
|
size_t println(void);
|
||||||
|
|
||||||
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
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);
|
template<> size_t Print::printNumber(double number, uint8_t digits);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
|
||||||
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
|
#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
|
#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.
|
// the buffer is NOT null terminated.
|
||||||
//
|
//
|
||||||
size_t Stream::readBytes(char *buffer, size_t length) {
|
size_t Stream::readBytes(char *buffer, size_t length) {
|
||||||
|
IAMSLOW();
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
while(count < length) {
|
while(count < length) {
|
||||||
int c = timedRead();
|
int c = timedRead();
|
||||||
@ -258,3 +261,20 @@ String Stream::readStringUntil(char terminator) {
|
|||||||
}
|
}
|
||||||
return ret;
|
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
|
#ifndef Stream_h
|
||||||
#define Stream_h
|
#define Stream_h
|
||||||
|
|
||||||
|
#include <debug.h>
|
||||||
#include <inttypes.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() parseInt()
|
||||||
#define getInt(skipChar) parseInt(skipchar)
|
#define getInt(skipChar) parseInt(skipchar)
|
||||||
@ -35,6 +38,15 @@
|
|||||||
readBytesBetween( pre_string, terminator, buffer, length)
|
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 {
|
class Stream: public Print {
|
||||||
protected:
|
protected:
|
||||||
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
|
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
|
// parsing methods
|
||||||
|
|
||||||
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
|
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(const char *target); // reads data from the stream until the target string is found
|
||||||
bool find(uint8_t *target) {
|
bool find(uint8_t *target) {
|
||||||
@ -102,12 +115,114 @@ class Stream: public Print {
|
|||||||
virtual String readString();
|
virtual String readString();
|
||||||
String readStringUntil(char terminator);
|
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:
|
protected:
|
||||||
long parseInt(char skipChar); // as above but the given skipChar is ignored
|
size_t sendGeneric (Print* to,
|
||||||
// as above but the given skipChar is ignored
|
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
|
// 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
|
#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,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
StreamString.h
|
StreamString.h
|
||||||
|
|
||||||
Copyright (c) 2015 Markus Sattler. All rights reserved.
|
Copyright (c) 2020 D. Gauchard. All rights reserved.
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
@ -20,20 +20,266 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef STREAMSTRING_H_
|
#ifndef __STREAMSTRING_H
|
||||||
#define 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:
|
public:
|
||||||
size_t write(const uint8_t *buffer, size_t size) override;
|
|
||||||
size_t write(uint8_t data) override;
|
|
||||||
|
|
||||||
int available() override;
|
S2Stream(String& string, int peekPointer = -1):
|
||||||
int read() override;
|
string(&string), peekPointer(peekPointer)
|
||||||
int peek() override;
|
{
|
||||||
void flush() override;
|
}
|
||||||
|
|
||||||
|
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 "Updater.h"
|
||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
#include <esp8266_peri.h>
|
#include <esp8266_peri.h>
|
||||||
|
#include <PolledTimeout.h>
|
||||||
#include "StackThunk.h"
|
#include "StackThunk.h"
|
||||||
|
|
||||||
//#define DEBUG_UPDATER Serial
|
//#define DEBUG_UPDATER Serial
|
||||||
@ -476,7 +477,7 @@ bool UpdaterClass::_verifyEnd() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t UpdaterClass::writeStream(Stream &data) {
|
size_t UpdaterClass::writeStream(Stream &data, uint16_t streamTimeout) {
|
||||||
size_t written = 0;
|
size_t written = 0;
|
||||||
size_t toRead = 0;
|
size_t toRead = 0;
|
||||||
if(hasError() || !isRunning())
|
if(hasError() || !isRunning())
|
||||||
@ -489,6 +490,7 @@ size_t UpdaterClass::writeStream(Stream &data) {
|
|||||||
_reset();
|
_reset();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
esp8266::polledTimeout::oneShotMs timeOut(streamTimeout);
|
||||||
if (_progress_callback) {
|
if (_progress_callback) {
|
||||||
_progress_callback(0, _size);
|
_progress_callback(0, _size);
|
||||||
}
|
}
|
||||||
@ -506,13 +508,15 @@ size_t UpdaterClass::writeStream(Stream &data) {
|
|||||||
}
|
}
|
||||||
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
|
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
|
||||||
if(toRead == 0) { //Timeout
|
if(toRead == 0) { //Timeout
|
||||||
delay(100);
|
if (timeOut) {
|
||||||
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
|
|
||||||
if(toRead == 0) { //Timeout
|
|
||||||
_currentAddress = (_startAddress + _size);
|
_currentAddress = (_startAddress + _size);
|
||||||
_setError(UPDATE_ERROR_STREAM);
|
_setError(UPDATE_ERROR_STREAM);
|
||||||
|
_reset();
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
delay(100);
|
||||||
|
} else {
|
||||||
|
timeOut.reset();
|
||||||
}
|
}
|
||||||
if(_ledPin != -1) {
|
if(_ledPin != -1) {
|
||||||
digitalWrite(_ledPin, !_ledOn); // Switch LED off
|
digitalWrite(_ledPin, !_ledOn); // Switch LED off
|
||||||
|
@ -83,7 +83,7 @@ class UpdaterClass {
|
|||||||
Should be equal to the remaining bytes when called
|
Should be equal to the remaining bytes when called
|
||||||
Usable for slow streams like Serial
|
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
|
If all bytes are written
|
||||||
|
@ -21,10 +21,16 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include "Arduino.h"
|
||||||
#include "WString.h"
|
#include "WString.h"
|
||||||
#include "stdlib_noniso.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 */
|
/* Constructors */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
@ -50,11 +56,6 @@ String::String(String &&rval) noexcept {
|
|||||||
move(rval);
|
move(rval);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(StringSumHelper &&rval) noexcept {
|
|
||||||
init();
|
|
||||||
move(rval);
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(unsigned char value, unsigned char base) {
|
String::String(unsigned char value, unsigned char base) {
|
||||||
init();
|
init();
|
||||||
char buf[1 + 8 * sizeof(unsigned char)];
|
char buf[1 + 8 * sizeof(unsigned char)];
|
||||||
@ -178,6 +179,14 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
|||||||
}
|
}
|
||||||
// Fallthrough to normal allocator
|
// Fallthrough to normal allocator
|
||||||
size_t newSize = (maxStrLen + 16) & (~0xf);
|
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
|
// Make sure we can fit newsize in the buffer
|
||||||
if (newSize > CAPACITY_MAX) {
|
if (newSize > CAPACITY_MAX) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -376,98 +385,92 @@ unsigned char String::concat(const __FlashStringHelper *str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
/* Concatenate */
|
/* Insert */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
|
String &String::insert(size_t position, const char *other, size_t other_length) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
if (position > length())
|
||||||
if (!a.concat(rhs.buffer(), rhs.len()))
|
return *this;
|
||||||
a.invalidate();
|
|
||||||
return a;
|
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) {
|
String &String::insert(size_t position, const __FlashStringHelper *other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
auto *p = reinterpret_cast<const char*>(other);
|
||||||
if (!cstr || !a.concat(cstr, strlen(cstr)))
|
return insert(position, p, strlen_P(p));
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
|
String &String::insert(size_t position, char other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
char tmp[2] { other, '\0' };
|
||||||
if (!a.concat(c))
|
return insert(position, tmp, 1);
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
|
String &String::insert(size_t position, const char *other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
return insert(position, other, strlen(other));
|
||||||
if (!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
|
String &String::insert(size_t position, const String &other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
return insert(position, other.c_str(), other.length());
|
||||||
if (!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
|
String operator +(const String &lhs, String &&rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
String res;
|
||||||
if (!a.concat(num))
|
auto total = lhs.length() + rhs.length();
|
||||||
a.invalidate();
|
if (rhs.capacity() > total) {
|
||||||
return a;
|
rhs.insert(0, lhs);
|
||||||
|
res = std::move(rhs);
|
||||||
|
} else {
|
||||||
|
res.reserve(total);
|
||||||
|
res += lhs;
|
||||||
|
res += rhs;
|
||||||
|
rhs.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
|
return res;
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
|
||||||
if (!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
|
String operator +(String &&lhs, String &&rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
String res;
|
||||||
if (!a.concat(num))
|
auto total = lhs.length() + rhs.length();
|
||||||
a.invalidate();
|
if ((total > lhs.capacity()) && (total < rhs.capacity())) {
|
||||||
return a;
|
rhs.insert(0, lhs);
|
||||||
|
res = std::move(rhs);
|
||||||
|
} else {
|
||||||
|
lhs += rhs;
|
||||||
|
rhs.invalidate();
|
||||||
|
res = std::move(lhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
|
return res;
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
|
||||||
if (!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
|
String operator +(char lhs, const String &rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
String res;
|
||||||
if (!a.concat(num))
|
res.reserve(rhs.length() + 1);
|
||||||
a.invalidate();
|
res += lhs;
|
||||||
return a;
|
res += rhs;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
|
String operator +(const char *lhs, const String &rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
String res;
|
||||||
if (!a.concat(num))
|
res.reserve(strlen_P(lhs) + rhs.length());
|
||||||
a.invalidate();
|
res += lhs;
|
||||||
return a;
|
res += rhs;
|
||||||
}
|
return res;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
|
@ -23,14 +23,15 @@
|
|||||||
#define String_class_h
|
#define String_class_h
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <pgmspace.h>
|
#include <pgmspace.h>
|
||||||
|
|
||||||
// An inherited class for holding the result of a concatenation. These
|
#include <cstdlib>
|
||||||
// result objects are assumed to be writable by subsequent concatenations.
|
#include <cstdint>
|
||||||
class StringSumHelper;
|
#include <cstring>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
// an abstract class used as a means to proide a unique pointer type
|
// an abstract class used as a means to proide a unique pointer type
|
||||||
// but really has no body
|
// but really has no body
|
||||||
@ -38,6 +39,10 @@ class __FlashStringHelper;
|
|||||||
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
|
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
|
||||||
#define F(string_literal) (FPSTR(PSTR(string_literal)))
|
#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
|
// The string class
|
||||||
class String {
|
class String {
|
||||||
// use a function pointer to allow for "if (s)" without the
|
// use a function pointer to allow for "if (s)" without the
|
||||||
@ -60,7 +65,6 @@ class String {
|
|||||||
String(const String &str);
|
String(const String &str);
|
||||||
String(const __FlashStringHelper *str);
|
String(const __FlashStringHelper *str);
|
||||||
String(String &&rval) noexcept;
|
String(String &&rval) noexcept;
|
||||||
String(StringSumHelper &&rval) noexcept;
|
|
||||||
explicit String(char c) {
|
explicit String(char c) {
|
||||||
sso.buff[0] = c;
|
sso.buff[0] = c;
|
||||||
sso.buff[1] = 0;
|
sso.buff[1] = 0;
|
||||||
@ -104,8 +108,10 @@ class String {
|
|||||||
String &operator =(const char *cstr);
|
String &operator =(const char *cstr);
|
||||||
String &operator =(const __FlashStringHelper *str);
|
String &operator =(const __FlashStringHelper *str);
|
||||||
String &operator =(String &&rval) noexcept;
|
String &operator =(String &&rval) noexcept;
|
||||||
String &operator =(StringSumHelper &&rval) noexcept {
|
String &operator =(char c) {
|
||||||
return operator =((String &&)rval);
|
char buffer[2] { c, '\0' };
|
||||||
|
*this = buffer;
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// concatenate (works w/ built-in types)
|
// concatenate (works w/ built-in types)
|
||||||
@ -130,72 +136,11 @@ class String {
|
|||||||
|
|
||||||
// if there's not enough memory for the concatenated value, the 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)
|
// 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);
|
concat(rhs);
|
||||||
return *this;
|
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")
|
// comparison (only works w/ Strings and "strings")
|
||||||
operator StringIfHelperType() const {
|
operator StringIfHelperType() const {
|
||||||
@ -338,6 +283,14 @@ class String {
|
|||||||
const char *buffer() const { return wbuffer(); }
|
const char *buffer() const { return wbuffer(); }
|
||||||
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
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:
|
protected:
|
||||||
void init(void) __attribute__((always_inline)) {
|
void init(void) __attribute__((always_inline)) {
|
||||||
sso.buff[0] = 0;
|
sso.buff[0] = 0;
|
||||||
@ -359,54 +312,81 @@ class String {
|
|||||||
void invalidate(void);
|
void invalidate(void);
|
||||||
unsigned char changeBuffer(unsigned int maxStrLen);
|
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 char *cstr, unsigned int length);
|
||||||
String ©(const __FlashStringHelper *pstr, 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;
|
void move(String &rhs) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringSumHelper: public String {
|
// concatenation (note that it's done using non-method operators to handle both possible type refs)
|
||||||
public:
|
|
||||||
StringSumHelper(const String &s) :
|
inline String operator +(const String &lhs, const String &rhs) {
|
||||||
String(s) {
|
String res;
|
||||||
|
res.reserve(lhs.length() + rhs.length());
|
||||||
|
res += lhs;
|
||||||
|
res += rhs;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
StringSumHelper(const char *p) :
|
|
||||||
String(p) {
|
inline String operator +(String &&lhs, const String &rhs) {
|
||||||
|
lhs += rhs;
|
||||||
|
return std::move(lhs);
|
||||||
}
|
}
|
||||||
StringSumHelper(char c) :
|
|
||||||
String(c) {
|
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;
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned char num) :
|
|
||||||
String(num) {
|
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);
|
||||||
}
|
}
|
||||||
StringSumHelper(int num) :
|
|
||||||
String(num) {
|
// `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));
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned int num) :
|
|
||||||
String(num) {
|
// 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));
|
||||||
}
|
}
|
||||||
StringSumHelper(long num) :
|
|
||||||
String(num) {
|
inline String operator +(const __FlashStringHelper *lhs, const String &rhs) {
|
||||||
|
return reinterpret_cast<const char*>(lhs) + rhs;
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned long num) :
|
|
||||||
String(num) {
|
inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
|
||||||
|
return std::move(rhs.insert(0, lhs));
|
||||||
}
|
}
|
||||||
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) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const String emptyString;
|
extern const String emptyString;
|
||||||
|
|
||||||
|
@ -33,11 +33,23 @@ public:
|
|||||||
// NOTE: The default behaviour of backend (lib64)
|
// NOTE: The default behaviour of backend (lib64)
|
||||||
// is to add a newline every 72 (encoded) characters output.
|
// is to add a newline every 72 (encoded) characters output.
|
||||||
// This may 'break' longer uris and json variables
|
// This may 'break' longer uris and json variables
|
||||||
static String encode(const uint8_t * data, size_t length, bool doNewLines = true);
|
static String encode(const uint8_t * data, size_t length, bool doNewLines);
|
||||||
static String inline encode(const String& text, bool doNewLines = true)
|
static inline String encode(const String& text, bool doNewLines)
|
||||||
{
|
{
|
||||||
return encode( (const uint8_t *) text.c_str(), text.length(), 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:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ size_t cbuf::resize(size_t newSize) {
|
|||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ICACHE_RAM_ATTR cbuf::available() const {
|
size_t IRAM_ATTR cbuf::available() const {
|
||||||
if(_end >= _begin) {
|
if(_end >= _begin) {
|
||||||
return _end - _begin;
|
return _end - _begin;
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ size_t cbuf::peek(char *dst, size_t size) {
|
|||||||
return size_read;
|
return size_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ICACHE_RAM_ATTR cbuf::read() {
|
int IRAM_ATTR cbuf::read() {
|
||||||
if(empty())
|
if(empty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ size_t cbuf::read(char* dst, size_t size) {
|
|||||||
return size_read;
|
return size_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ICACHE_RAM_ATTR cbuf::write(char c) {
|
size_t IRAM_ATTR cbuf::write(char c) {
|
||||||
if(full())
|
if(full())
|
||||||
return 0;
|
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;
|
if(cont->stack_guard1 != CONT_STACKGUARD || cont->stack_guard2 != CONT_STACKGUARD) return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -62,7 +62,7 @@ int cont_get_free_stack(cont_t* cont) {
|
|||||||
return freeWords * 4;
|
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() &&
|
return !ETS_INTR_WITHINISR() &&
|
||||||
cont->pc_ret != 0 && cont->pc_yield == 0;
|
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 */
|
/* this is the default NONOS-SDK user's heap location */
|
||||||
static cont_t g_cont __attribute__ ((aligned (16)));
|
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)
|
extern "C" void app_entry_redefinable(void)
|
||||||
{
|
{
|
||||||
g_pcont = &g_cont;
|
g_pcont = &g_cont;
|
||||||
@ -34,3 +41,4 @@ extern "C" void app_entry_redefinable(void)
|
|||||||
/* Call the entry point of the SDK code. */
|
/* Call the entry point of the SDK code. */
|
||||||
call_user_start();
|
call_user_start();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#define CORE_HAS_UMM
|
#define CORE_HAS_UMM
|
||||||
|
|
||||||
#define WIFI_HAS_EVENT_CALLBACK
|
#define WIFI_HAS_EVENT_CALLBACK
|
||||||
|
#define WIFI_IS_OFF_AT_BOOT
|
||||||
|
|
||||||
#include <stdlib.h> // malloc()
|
#include <stdlib.h> // malloc()
|
||||||
#include <stddef.h> // size_t
|
#include <stddef.h> // size_t
|
||||||
@ -121,6 +122,9 @@ inline int esp_get_cpu_freq_mhz()
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include "osapi.h"
|
#include "osapi.h"
|
||||||
#include "ets_sys.h"
|
#include "ets_sys.h"
|
||||||
#include "i2s_reg.h"
|
#include "i2s_reg.h"
|
||||||
#include "i2s.h"
|
#include "core_esp8266_i2s.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ typedef struct i2s_state {
|
|||||||
uint32_t * curr_slc_buf; // Current buffer for writing
|
uint32_t * curr_slc_buf; // Current buffer for writing
|
||||||
uint32_t curr_slc_buf_pos; // Position in the current buffer
|
uint32_t curr_slc_buf_pos; // Position in the current buffer
|
||||||
void (*callback) (void);
|
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
|
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
||||||
// function, use it to set flags and process later.
|
// function, use it to set flags and process later.
|
||||||
bool driveClocks;
|
bool driveClocks;
|
||||||
@ -139,7 +139,7 @@ uint16_t i2s_rx_available(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pop the top off of the queue and return it
|
// 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;
|
uint8_t i;
|
||||||
uint32_t *item = ch->slc_queue[0];
|
uint32_t *item = ch->slc_queue[0];
|
||||||
ch->slc_queue_len--;
|
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
|
// 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
|
// Shift everything up, except for the one corresponding to this item
|
||||||
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
|
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
|
||||||
if (ch->slc_queue[i] != item) {
|
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();
|
ETS_SLC_INTR_DISABLE();
|
||||||
uint32_t slc_intr_status = SLCIS;
|
uint32_t slc_intr_status = SLCIS;
|
||||||
SLCIC = 0xFFFFFFFF;
|
SLCIC = 0xFFFFFFFF;
|
||||||
@ -194,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void i2s_set_callback(void (*callback) (void)) {
|
void i2s_set_callback(void (*callback) (void)) {
|
||||||
tx->callback = callback;
|
if (tx) tx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_rx_set_callback(void (*callback) (void)) {
|
void i2s_rx_set_callback(void (*callback) (void)) {
|
||||||
rx->callback = callback;
|
if (rx) rx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _alloc_channel(i2s_state_t *ch) {
|
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
|
// 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.
|
// 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;
|
uint16_t frames_written=0;
|
||||||
|
|
||||||
while(frame_count>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;
|
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) {
|
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
|
||||||
if (!rx) {
|
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 <core_version.h>
|
||||||
#include "gdb_hooks.h"
|
#include "gdb_hooks.h"
|
||||||
#include "flash_quirks.h"
|
#include "flash_quirks.h"
|
||||||
|
#include "hwdt_app_entry.h"
|
||||||
#include <umm_malloc/umm_malloc.h>
|
#include <umm_malloc/umm_malloc.h>
|
||||||
#include <core_esp8266_non32xfer.h>
|
#include <core_esp8266_non32xfer.h>
|
||||||
|
#include "core_esp8266_vm.h"
|
||||||
|
|
||||||
#define LOOP_TASK_PRIORITY 1
|
#define LOOP_TASK_PRIORITY 1
|
||||||
#define LOOP_QUEUE_SIZE 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) __attribute__((weak));
|
||||||
extern "C" void preinit (void)
|
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) {
|
extern "C" void user_init(void) {
|
||||||
@ -348,13 +360,23 @@ extern "C" void user_init(void) {
|
|||||||
|
|
||||||
cont_init(g_pcont);
|
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)
|
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
|
||||||
install_non32xfer_exception_handler();
|
install_non32xfer_exception_handler();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(MMU_IRAM_HEAP)
|
#if defined(MMU_IRAM_HEAP)
|
||||||
umm_init_iram();
|
umm_init_iram();
|
||||||
#endif
|
#endif
|
||||||
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
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,
|
ets_task(loop_task,
|
||||||
LOOP_TASK_PRIORITY, s_loop_queue,
|
LOOP_TASK_PRIORITY, s_loop_queue,
|
||||||
|
@ -64,51 +64,10 @@ static
|
|||||||
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
|
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
|
||||||
{
|
{
|
||||||
do {
|
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;
|
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
|
/* Extract instruction and faulting data address */
|
||||||
{
|
__EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn);
|
||||||
__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
|
|
||||||
|
|
||||||
uint32_t what = insn & LOAD_MASK;
|
uint32_t what = insn & LOAD_MASK;
|
||||||
uint32_t valmask = 0;
|
uint32_t valmask = 0;
|
||||||
|
@ -7,6 +7,54 @@ extern "C" {
|
|||||||
|
|
||||||
extern void install_non32xfer_exception_handler();
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -300,10 +300,10 @@ static const uint8_t ICACHE_FLASH_ATTR phy_init_data[128] =
|
|||||||
static bool spoof_init_data = false;
|
static bool spoof_init_data = false;
|
||||||
|
|
||||||
extern int __real_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
|
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 __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) {
|
if (!spoof_init_data || size != 128) {
|
||||||
return __real_spi_flash_read(addr, dst, size);
|
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;
|
ETSTimer timer;
|
||||||
|
|
||||||
// Event/IRQ callbacks, so they can't use "this" and need to be static
|
// Event/IRQ callbacks, so they can't use "this" and need to be static
|
||||||
static void ICACHE_RAM_ATTR onSclChange(void);
|
static void IRAM_ATTR onSclChange(void);
|
||||||
static void ICACHE_RAM_ATTR onSdaChange(void);
|
static void IRAM_ATTR onSdaChange(void);
|
||||||
static void eventTask(ETSEvent *e);
|
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
|
// Allow not linking in the slave code if there is no call to setAddress
|
||||||
bool _slaveEnabled = false;
|
bool _slaveEnabled = false;
|
||||||
|
|
||||||
// Internal use functions
|
// Internal use functions
|
||||||
void ICACHE_RAM_ATTR busywait(unsigned int v);
|
void IRAM_ATTR busywait(unsigned int v);
|
||||||
bool write_start(void);
|
bool write_start(void);
|
||||||
bool write_stop(void);
|
bool write_stop(void);
|
||||||
bool write_bit(bool bit);
|
bool write_bit(bool bit);
|
||||||
bool read_bit(void);
|
bool read_bit(void);
|
||||||
bool write_byte(unsigned char byte);
|
bool write_byte(unsigned char byte);
|
||||||
unsigned char read_byte(bool nack);
|
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
|
// Handle the case where a slave needs to stretch the clock with a time-limited busy wait
|
||||||
inline void WAIT_CLOCK_STRETCH()
|
inline void WAIT_CLOCK_STRETCH()
|
||||||
@ -149,8 +149,8 @@ public:
|
|||||||
uint8_t transmit(const uint8_t* data, uint8_t length);
|
uint8_t transmit(const uint8_t* data, uint8_t length);
|
||||||
void attachSlaveRxEvent(void (*function)(uint8_t*, size_t));
|
void attachSlaveRxEvent(void (*function)(uint8_t*, size_t));
|
||||||
void attachSlaveTxEvent(void (*function)(void));
|
void attachSlaveTxEvent(void (*function)(void));
|
||||||
void ICACHE_RAM_ATTR reply(uint8_t ack);
|
void IRAM_ATTR reply(uint8_t ack);
|
||||||
void ICACHE_RAM_ATTR releaseBus(void);
|
void IRAM_ATTR releaseBus(void);
|
||||||
void enableSlave();
|
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;
|
unsigned int i;
|
||||||
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
|
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
|
// 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
|
// 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
|
// transmit master read ready signal, with or without ack
|
||||||
if (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
|
// release bus
|
||||||
//TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
|
//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;
|
twip_status = status;
|
||||||
switch (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;
|
(void)unused;
|
||||||
twi.releaseBus();
|
twi.releaseBus();
|
||||||
@ -662,7 +662,7 @@ void Twi::eventTask(ETSEvent *e)
|
|||||||
// Shorthand for if the state is any of the or'd bitmask x
|
// Shorthand for if the state is any of the or'd bitmask x
|
||||||
#define IFSTATE(x) if (twip_state_mask & (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 sda;
|
||||||
unsigned int scl;
|
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 sda;
|
||||||
unsigned int scl;
|
unsigned int scl;
|
||||||
|
@ -31,7 +31,7 @@ extern "C" {
|
|||||||
|
|
||||||
static volatile timercallback timer1_user_cb = NULL;
|
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) para;
|
||||||
(void) frame;
|
(void) frame;
|
||||||
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
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);
|
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;
|
timer1_user_cb = userFunc;
|
||||||
ETS_FRC1_INTR_ENABLE();
|
ETS_FRC1_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_detachInterrupt() {
|
void IRAM_ATTR timer1_detachInterrupt() {
|
||||||
timer1_user_cb = 0;
|
timer1_user_cb = 0;
|
||||||
TEIE &= ~TEIE1;//edge int disable
|
TEIE &= ~TEIE1;//edge int disable
|
||||||
ETS_FRC1_INTR_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);
|
T1C = (1 << TCTE) | ((divider & 3) << TCPD) | ((int_type & 1) << TCIT) | ((reload & 1) << TCAR);
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_write(uint32_t ticks){
|
void IRAM_ATTR timer1_write(uint32_t ticks){
|
||||||
T1L = ((ticks)& 0x7FFFFF);
|
T1L = ((ticks)& 0x7FFFFF);
|
||||||
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
|
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_disable(){
|
void IRAM_ATTR timer1_disable(){
|
||||||
T1C = 0;
|
T1C = 0;
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ void ICACHE_RAM_ATTR timer1_disable(){
|
|||||||
|
|
||||||
static volatile timercallback timer0_user_cb = NULL;
|
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) para;
|
||||||
(void) frame;
|
(void) frame;
|
||||||
if (timer0_user_cb) {
|
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);
|
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;
|
timer0_user_cb = userFunc;
|
||||||
ETS_CCOMPARE0_ENABLE();
|
ETS_CCOMPARE0_ENABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_detachInterrupt() {
|
void IRAM_ATTR timer0_detachInterrupt() {
|
||||||
timer0_user_cb = NULL;
|
timer0_user_cb = NULL;
|
||||||
ETS_CCOMPARE0_DISABLE();
|
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" {
|
extern "C" {
|
||||||
#endif
|
#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.
|
// 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
|
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||||
// full period.
|
// full period.
|
||||||
@ -112,7 +107,7 @@ int stopWaveform(uint8_t pin);
|
|||||||
// to determine whether or not to perform an operation.
|
// to determine whether or not to perform an operation.
|
||||||
// Pass in NULL to disable the callback and, if no other waveforms being
|
// Pass in NULL to disable the callback and, if no other waveforms being
|
||||||
// generated, stop the timer as well.
|
// 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)());
|
void setTimer1Callback(uint32_t (*fn)());
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interrupt on/off control
|
// Interrupt on/off control
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
static IRAM_ATTR void timer1Interrupt();
|
||||||
|
|
||||||
// Non-speed critical bits
|
// Non-speed critical bits
|
||||||
#pragma GCC optimize ("Os")
|
#pragma GCC optimize ("Os")
|
||||||
@ -125,7 +125,7 @@ static void initTimer() {
|
|||||||
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
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);
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||||
timer1_disable();
|
timer1_disable();
|
||||||
timer1_isr_init();
|
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
|
// 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
|
// Can't possibly need to stop anything if there is no timer active
|
||||||
if (!waveform.timer1Running) {
|
if (!waveform.timer1Running) {
|
||||||
return false;
|
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.
|
// 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.
|
// 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) {
|
if (ISCPUFREQ160MHZ) {
|
||||||
return isCPU2X ? ccys : (ccys >> 1);
|
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();
|
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||||
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||||
const bool isCPU2X = CPU2X & 1;
|
const bool isCPU2X = CPU2X & 1;
|
||||||
|
@ -93,7 +93,7 @@ static WVFState wvfState;
|
|||||||
#pragma GCC optimize ("Os")
|
#pragma GCC optimize ("Os")
|
||||||
|
|
||||||
// Interrupt on/off control
|
// Interrupt on/off control
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
static IRAM_ATTR void timer1Interrupt();
|
||||||
static bool timerRunning = false;
|
static bool timerRunning = false;
|
||||||
|
|
||||||
static __attribute__((noinline)) void initTimer() {
|
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)) {
|
if (T1L > microsecondsToClockCycles(10)) {
|
||||||
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.
|
// If there are no more scheduled activities, shut down Timer 1.
|
||||||
// Otherwise, do nothing.
|
// Otherwise, do nothing.
|
||||||
static ICACHE_RAM_ATTR void disableIdleTimer() {
|
static IRAM_ATTR void disableIdleTimer() {
|
||||||
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||||
timer1_disable();
|
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.
|
// 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)
|
// 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;
|
p->pwmUpdate = nullptr;
|
||||||
pwmState.pwmUpdate = p;
|
pwmState.pwmUpdate = p;
|
||||||
MEMBARRIER();
|
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%))
|
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
|
||||||
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
|
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)) {
|
if (!((1<<pin) & pwmState.mask)) {
|
||||||
return false; // Pin not actually active
|
return false; // Pin not actually active
|
||||||
}
|
}
|
||||||
@ -356,7 +356,7 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t
|
|||||||
(void) phaseOffsetUS;
|
(void) phaseOffsetUS;
|
||||||
(void) autoPwm;
|
(void) autoPwm;
|
||||||
|
|
||||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Waveform *wave = &wvfState.waveform[pin];
|
Waveform *wave = &wvfState.waveform[pin];
|
||||||
@ -430,7 +430,7 @@ void setTimer1Callback(uint32_t (*fn)()) {
|
|||||||
|
|
||||||
// Stops a waveform on a pin
|
// Stops a waveform on a pin
|
||||||
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
|
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
|
// Can't possibly need to stop anything if there is no timer active
|
||||||
if (!timerRunning) {
|
if (!timerRunning) {
|
||||||
return false;
|
return false;
|
||||||
@ -454,7 +454,7 @@ ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
|
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);
|
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
|
// Normally would not want two copies like this, but due to different
|
||||||
// optimization levels the inline attribute gets lost if we try the
|
// optimization levels the inline attribute gets lost if we try the
|
||||||
// other version.
|
// other version.
|
||||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
static inline IRAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||||
uint32_t ccount;
|
uint32_t ccount;
|
||||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||||
return ccount;
|
return ccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the earliest cycle as compared to right now
|
// 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();
|
uint32_t now = GetCycleCountIRQ();
|
||||||
int32_t da = a - now;
|
int32_t da = a - now;
|
||||||
int32_t db = b - 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
|
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
|
||||||
#define MINIRQTIME microsecondsToClockCycles(4)
|
#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()
|
// Flag if the core is at 160 MHz, for use by adjust()
|
||||||
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
|
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,
|
// Reference function: corrected millis(), 64-bit arithmetic,
|
||||||
// truncated to 32-bits by return
|
// 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
|
// // 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_wLO 0x4bc6a7f0 // LS part
|
||||||
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
|
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
|
||||||
|
|
||||||
unsigned long ICACHE_RAM_ATTR millis()
|
unsigned long IRAM_ATTR millis()
|
||||||
{
|
{
|
||||||
union {
|
union {
|
||||||
uint64_t q; // Accumulator, 64-bit, little endian
|
uint64_t q; // Accumulator, 64-bit, little endian
|
||||||
@ -194,18 +194,18 @@ unsigned long ICACHE_RAM_ATTR millis()
|
|||||||
|
|
||||||
} //millis
|
} //millis
|
||||||
|
|
||||||
unsigned long ICACHE_RAM_ATTR micros() {
|
unsigned long IRAM_ATTR micros() {
|
||||||
return system_get_time();
|
return system_get_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ICACHE_RAM_ATTR micros64() {
|
uint64_t IRAM_ATTR micros64() {
|
||||||
uint32_t low32_us = system_get_time();
|
uint32_t low32_us = system_get_time();
|
||||||
uint32_t high32_us = micros_overflow_count + ((low32_us < micros_at_last_overflow_tick) ? 1 : 0);
|
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;
|
uint64_t duration64_us = (uint64_t)high32_us << 32 | low32_us;
|
||||||
return duration64_us;
|
return duration64_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR delayMicroseconds(unsigned int us) {
|
void IRAM_ATTR delayMicroseconds(unsigned int us) {
|
||||||
os_delay_us(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
|
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
|
||||||
_stopPWM(pin); // and any analogWrites (PWM)
|
_stopPWM(pin); // and any analogWrites (PWM)
|
||||||
if(pin < 16){
|
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){
|
if(pin < 16){
|
||||||
return GPIP(pin);
|
return GPIP(pin);
|
||||||
} else if(pin == 16){
|
} else if(pin == 16){
|
||||||
@ -131,7 +131,7 @@ typedef struct {
|
|||||||
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
||||||
static uint32_t interrupt_reg = 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) arg;
|
||||||
(void) frame;
|
(void) frame;
|
||||||
@ -218,7 +218,7 @@ extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg
|
|||||||
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
|
__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)
|
if (pin < 16)
|
||||||
{
|
{
|
||||||
ETS_GPIO_INTR_DISABLE();
|
ETS_GPIO_INTR_DISABLE();
|
||||||
|
@ -18,6 +18,7 @@ void esp_schedule();
|
|||||||
void tune_timeshift64 (uint64_t now_us);
|
void tune_timeshift64 (uint64_t now_us);
|
||||||
void disable_extra4k_at_link_time (void) __attribute__((noinline));
|
void disable_extra4k_at_link_time (void) __attribute__((noinline));
|
||||||
bool sntp_set_timezone_in_seconds(int32_t timezone);
|
bool sntp_set_timezone_in_seconds(int32_t timezone);
|
||||||
|
void __disableWiFiAtBootTime (void) __attribute__((noinline));
|
||||||
void __real_system_restart_local() __attribute__((noreturn));
|
void __real_system_restart_local() __attribute__((noreturn));
|
||||||
|
|
||||||
uint32_t sqrt32 (uint32_t n);
|
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 BoolCB& cb);
|
||||||
void settimeofday_cb (const TrivialCB& cb);
|
void settimeofday_cb (const TrivialCB& cb);
|
||||||
|
|
||||||
#endif
|
#endif // __cplusplus
|
||||||
|
|
||||||
#endif // __COREDECLS_H
|
#endif // __COREDECLS_H
|
||||||
|
@ -22,6 +22,13 @@
|
|||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "osapi.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
|
IRAM_ATTR
|
||||||
void hexdump(const void *mem, uint32_t len, uint8_t cols)
|
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));
|
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
|
||||||
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)
|
#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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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:
|
* The original module source code came from:
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
same stub can be used for gdb_present. */
|
same stub can be used for gdb_present. */
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static bool ICACHE_RAM_ATTR __gdb_no_op()
|
static bool IRAM_ATTR __gdb_no_op()
|
||||||
{
|
{
|
||||||
return false;
|
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
|
#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)size;
|
||||||
(void)line;
|
(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) {
|
if (inISR && (uint32_t)file >= 0x40200000) {
|
||||||
DEBUG_HEAP_PRINTF("File: %p", file);
|
DEBUG_HEAP_PRINTF("File: %p", file);
|
||||||
} else if (!inISR && (uint32_t)file >= 0x40200000) {
|
} else if (!inISR && (uint32_t)file >= 0x40200000) {
|
||||||
char buf[ets_strlen(file) + 1] __attribute__((aligned(4)));
|
char buf[strlen_P(file) + 1];
|
||||||
ets_strcpy(buf, file);
|
strcpy_P(buf, file);
|
||||||
DEBUG_HEAP_PRINTF(buf);
|
DEBUG_HEAP_PRINTF(buf);
|
||||||
} else {
|
} else {
|
||||||
DEBUG_HEAP_PRINTF(file);
|
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;
|
(void)size;
|
||||||
if (system_get_os_print()) {
|
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
|
For malloc(), calloc(), and zalloc() Full Posion Check is done 1st since
|
||||||
these functions do not modify an existing allocation.
|
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();
|
INTEGRITY_CHECK__ABORT();
|
||||||
POISON_CHECK__ABORT();
|
POISON_CHECK__ABORT();
|
||||||
@ -242,7 +242,7 @@ void* ICACHE_RAM_ATTR malloc(size_t size)
|
|||||||
return ret;
|
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();
|
INTEGRITY_CHECK__ABORT();
|
||||||
POISON_CHECK__ABORT();
|
POISON_CHECK__ABORT();
|
||||||
@ -252,7 +252,7 @@ void* ICACHE_RAM_ATTR calloc(size_t count, size_t size)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ICACHE_RAM_ATTR realloc(void* ptr, size_t size)
|
void* IRAM_ATTR realloc(void* ptr, size_t size)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__ABORT();
|
INTEGRITY_CHECK__ABORT();
|
||||||
void* ret = UMM_REALLOC_FL(ptr, size, NULL, 0);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR free(void* p)
|
void IRAM_ATTR free(void* p)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__ABORT();
|
INTEGRITY_CHECK__ABORT();
|
||||||
UMM_FREE_FL(p, NULL, 0);
|
UMM_FREE_FL(p, NULL, 0);
|
||||||
@ -271,7 +271,7 @@ void ICACHE_RAM_ATTR free(void* p)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
STATIC_ALWAYS_INLINE
|
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);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_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
|
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);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_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
|
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);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
void* ret = UMM_REALLOC_FL(ptr, size, 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
|
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);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_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
|
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);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
UMM_FREE_FL(ptr, file, line);
|
UMM_FREE_FL(ptr, file, line);
|
||||||
POISON_CHECK__PANIC_FL(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);
|
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.
|
malloc calls pvPortMalloc, ... we can leverage that for this solution.
|
||||||
Force pvPortMalloc, ... APIs to serve DRAM only.
|
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;
|
HeapSelectDram ephemeral;
|
||||||
return heap_pvPortMalloc(size, file, line);;
|
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;
|
HeapSelectDram ephemeral;
|
||||||
return heap_pvPortCalloc(count, size, file, line);
|
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;
|
HeapSelectDram ephemeral;
|
||||||
return heap_pvPortRealloc(ptr, size, file, line);
|
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;
|
HeapSelectDram ephemeral;
|
||||||
return heap_pvPortZalloc(size, file, line);
|
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)
|
#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
|
// 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 @@
|
|||||||
/*
|
// This include file is a hack to ensure backward compatibility with
|
||||||
i2s.h - Software I2S library for esp8266
|
// 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.
|
#include "../../libraries/I2S/src/I2S.h"
|
||||||
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
|
|
||||||
|
@ -47,27 +47,27 @@
|
|||||||
|
|
||||||
extern "C" {
|
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)unused;
|
||||||
(void)ptr;
|
(void)ptr;
|
||||||
(void)mode;
|
(void)mode;
|
||||||
return 0;
|
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)unused;
|
||||||
(void)file;
|
(void)file;
|
||||||
return 0;
|
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)unused;
|
||||||
(void)file;
|
(void)file;
|
||||||
st->st_mode = S_IFCHR;
|
st->st_mode = S_IFCHR;
|
||||||
return 0;
|
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)unused;
|
||||||
(void)file;
|
(void)file;
|
||||||
(void)ptr;
|
(void)ptr;
|
||||||
@ -75,7 +75,7 @@ int ICACHE_RAM_ATTR _lseek_r(struct _reent* unused, int file, int ptr, int dir)
|
|||||||
return 0;
|
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)unused;
|
||||||
(void)file;
|
(void)file;
|
||||||
(void)ptr;
|
(void)ptr;
|
||||||
@ -83,7 +83,7 @@ int ICACHE_RAM_ATTR _read_r(struct _reent* unused, int file, char *ptr, int len)
|
|||||||
return 0;
|
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;
|
(void) r;
|
||||||
int pos = len;
|
int pos = len;
|
||||||
if (file == STDOUT_FILENO) {
|
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;
|
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;
|
(void) r;
|
||||||
if (file->_file == STDOUT_FILENO) {
|
if (file->_file == STDOUT_FILENO) {
|
||||||
ets_putc(c);
|
ets_putc(c);
|
||||||
@ -106,7 +106,7 @@ int ICACHE_RAM_ATTR _putc_r(struct _reent* r, int c, FILE* file) {
|
|||||||
return EOF;
|
return EOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ICACHE_RAM_ATTR puts(const char * str) {
|
int IRAM_ATTR puts(const char * str) {
|
||||||
char c;
|
char c;
|
||||||
while((c = *str) != 0) {
|
while((c = *str) != 0) {
|
||||||
ets_putc(c);
|
ets_putc(c);
|
||||||
@ -117,7 +117,7 @@ int ICACHE_RAM_ATTR puts(const char * str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#undef putchar
|
#undef putchar
|
||||||
int ICACHE_RAM_ATTR putchar(int c) {
|
int IRAM_ATTR putchar(int c) {
|
||||||
ets_putc(c);
|
ets_putc(c);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ static inline void __wsr_vecbase(uint32_t vector_base) {
|
|||||||
asm volatile("wsr.vecbase %0" :: "r" (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() */
|
/* reverse engineered from system_restart_core() */
|
||||||
/* Before disabling instruction cache and restoring instruction RAM to a
|
/* Before disabling instruction cache and restoring instruction RAM to a
|
||||||
|
@ -388,10 +388,10 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read(uint8_t* buf, size_t size) override
|
int read(uint8_t* buf, size_t size) override
|
||||||
{
|
{
|
||||||
CHECKFD();
|
CHECKFD();
|
||||||
auto result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size);
|
int result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
DEBUGV("SPIFFS_read rc=%d\r\n", result);
|
DEBUGV("SPIFFS_read rc=%d\r\n", result);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -115,7 +115,7 @@ struct uart_
|
|||||||
|
|
||||||
|
|
||||||
// called by ISR
|
// called by ISR
|
||||||
inline size_t ICACHE_RAM_ATTR
|
inline size_t IRAM_ATTR
|
||||||
uart_rx_fifo_available(const int uart_nr)
|
uart_rx_fifo_available(const int uart_nr)
|
||||||
{
|
{
|
||||||
return (USS(uart_nr) >> USRXC) & 0xFF;
|
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
|
// Copy all the rx fifo bytes that fit into the rx buffer
|
||||||
// called by ISR
|
// called by ISR
|
||||||
inline void ICACHE_RAM_ATTR
|
inline void IRAM_ATTR
|
||||||
uart_rx_copy_fifo_to_buffer_unsafe(uart_t* uart)
|
uart_rx_copy_fifo_to_buffer_unsafe(uart_t* uart)
|
||||||
{
|
{
|
||||||
struct uart_rx_buffer_ *rx_buffer = uart->rx_buffer;
|
struct uart_rx_buffer_ *rx_buffer = uart->rx_buffer;
|
||||||
@ -240,6 +240,41 @@ uart_peek_char(uart_t* uart)
|
|||||||
return ret;
|
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
|
int
|
||||||
uart_read_char(uart_t* uart)
|
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()
|
// 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
|
// Since we've already read the bytes from the FIFO, can't use that
|
||||||
// function directly and need to implement it bytewise here
|
// 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;
|
uart_t* uart = (uart_t*)arg;
|
||||||
if(uart == NULL || !uart->rx_enabled) {
|
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
|
// The default ISR handler called when GDB is not enabled
|
||||||
void ICACHE_RAM_ATTR
|
void IRAM_ATTR
|
||||||
uart_isr(void * arg, void * frame)
|
uart_isr(void * arg, void * frame)
|
||||||
{
|
{
|
||||||
(void) frame;
|
(void) frame;
|
||||||
@ -725,11 +760,11 @@ uart_uninit(uart_t* uart)
|
|||||||
free(uart);
|
free(uart);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
uart_swap(uart_t* uart, int tx_pin)
|
uart_swap(uart_t* uart, int tx_pin)
|
||||||
{
|
{
|
||||||
if(uart == NULL)
|
if(uart == NULL)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
switch(uart->uart_nr)
|
switch(uart->uart_nr)
|
||||||
{
|
{
|
||||||
@ -740,19 +775,17 @@ uart_swap(uart_t* uart, int tx_pin)
|
|||||||
{
|
{
|
||||||
pinMode(uart->tx_pin, INPUT);
|
pinMode(uart->tx_pin, INPUT);
|
||||||
uart->tx_pin = 15;
|
uart->tx_pin = 15;
|
||||||
|
pinMode(uart->tx_pin, FUNCTION_4);
|
||||||
}
|
}
|
||||||
if(uart->rx_enabled) //RX
|
if(uart->rx_enabled) //RX
|
||||||
{
|
{
|
||||||
pinMode(uart->rx_pin, INPUT);
|
pinMode(uart->rx_pin, INPUT);
|
||||||
uart->rx_pin = 13;
|
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);
|
IOSWAP |= (1 << IOSWAPU0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -760,19 +793,17 @@ uart_swap(uart_t* uart, int tx_pin)
|
|||||||
{
|
{
|
||||||
pinMode(uart->tx_pin, INPUT);
|
pinMode(uart->tx_pin, INPUT);
|
||||||
uart->tx_pin = (tx_pin == 2)?2:1;
|
uart->tx_pin = (tx_pin == 2)?2:1;
|
||||||
|
pinMode(uart->tx_pin, (tx_pin == 2)?FUNCTION_4:SPECIAL);
|
||||||
}
|
}
|
||||||
if(uart->rx_enabled) //RX
|
if(uart->rx_enabled) //RX
|
||||||
{
|
{
|
||||||
pinMode(uart->rx_pin, INPUT);
|
pinMode(uart->rx_pin, INPUT);
|
||||||
uart->rx_pin = 3;
|
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);
|
IOSWAP &= ~(1 << IOSWAPU0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UART1:
|
case UART1:
|
||||||
@ -781,30 +812,30 @@ uart_swap(uart_t* uart, int tx_pin)
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
uart_set_tx(uart_t* uart, int tx_pin)
|
uart_set_tx(uart_t* uart, int tx_pin)
|
||||||
{
|
{
|
||||||
if(uart == NULL)
|
if(uart == NULL)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
switch(uart->uart_nr)
|
switch(uart->uart_nr)
|
||||||
{
|
{
|
||||||
case UART0:
|
case UART0:
|
||||||
if(uart->tx_enabled)
|
if(uart->tx_enabled)
|
||||||
{
|
{
|
||||||
if (uart->tx_pin == 1 && tx_pin == 2)
|
if (uart->tx_pin == tx_pin)
|
||||||
{
|
{
|
||||||
pinMode(uart->tx_pin, INPUT);
|
return true;
|
||||||
uart->tx_pin = 2;
|
|
||||||
pinMode(uart->tx_pin, FUNCTION_4);
|
|
||||||
}
|
}
|
||||||
else if (uart->tx_pin == 2 && tx_pin != 2)
|
else if (tx_pin == 1 || tx_pin == 2)
|
||||||
{
|
{
|
||||||
pinMode(uart->tx_pin, INPUT);
|
pinMode(uart->tx_pin, INPUT);
|
||||||
uart->tx_pin = 1;
|
uart->tx_pin = tx_pin;
|
||||||
pinMode(uart->tx_pin, SPECIAL);
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
uart_set_pins(uart_t* uart, int tx, int rx)
|
uart_set_pins(uart_t* uart, int tx, int rx)
|
||||||
{
|
{
|
||||||
if(uart == NULL)
|
if(uart == NULL)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
if(uart->uart_nr != UART0) // Only UART0 allows pin changes
|
||||||
|
return false;
|
||||||
|
|
||||||
if(uart->uart_nr == UART0) // Only UART0 allows pin changes
|
|
||||||
{
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
uart_swap(uart, 15);
|
if (!uart_swap(uart, 15))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else if (rx == 3 && (tx == 1 || tx == 2))
|
else if (rx == 3 && (tx == 1 || tx == 2))
|
||||||
{
|
{
|
||||||
if (uart->rx_pin != rx)
|
if (uart->rx_pin != rx)
|
||||||
uart_swap(uart, tx);
|
{
|
||||||
|
if (!uart_swap(uart, tx))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
else
|
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)
|
else
|
||||||
uart_swap(uart, 15);
|
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);
|
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_uninit(uart_t* uart);
|
||||||
|
|
||||||
void uart_swap(uart_t* uart, int tx_pin);
|
bool uart_swap(uart_t* uart, int tx_pin);
|
||||||
void uart_set_tx(uart_t* uart, int tx_pin);
|
bool uart_set_tx(uart_t* uart, int tx_pin);
|
||||||
void uart_set_pins(uart_t* uart, int tx, int rx);
|
bool uart_set_pins(uart_t* uart, int tx, int rx);
|
||||||
bool uart_tx_enabled(uart_t* uart);
|
bool uart_tx_enabled(uart_t* uart);
|
||||||
bool uart_rx_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);
|
void uart_start_detect_baudrate(int uart_nr);
|
||||||
int uart_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);
|
uint8_t uart_get_bit_length(const int uart_nr);
|
||||||
|
|
||||||
#if defined (__cplusplus)
|
#if defined (__cplusplus)
|
||||||
|
@ -204,13 +204,8 @@ void umm_print_stats(int force) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
|
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
|
||||||
/*
|
char ram_buf[strlen_P(fmt) + 1];
|
||||||
To use ets_strlen() and ets_strcpy() safely with PROGMEM, flash storage,
|
strcpy_P(ram_buf, fmt);
|
||||||
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);
|
|
||||||
va_list argPtr;
|
va_list argPtr;
|
||||||
va_start(argPtr, fmt);
|
va_start(argPtr, fmt);
|
||||||
int result = ets_vprintf(ets_uart_putc1, ram_buf, argPtr);
|
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)));
|
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__)
|
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR(fmt), ##__VA_ARGS__)
|
||||||
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct umm_block_t umm_block;
|
typedef struct umm_block_t umm_block;
|
||||||
|
@ -42,7 +42,11 @@ extern "C" {
|
|||||||
#undef UMM_HEAP_IRAM
|
#undef UMM_HEAP_IRAM
|
||||||
#endif
|
#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.
|
* Assign IDs to active Heaps and tally. DRAM is always active.
|
||||||
@ -793,11 +797,11 @@ extern "C" {
|
|||||||
#include <pgmspace.h>
|
#include <pgmspace.h>
|
||||||
// Reuse pvPort* calls, since they already support passing location information.
|
// Reuse pvPort* calls, since they already support passing location information.
|
||||||
// Specificly the debug version (heap_...) that does not force DRAM heap.
|
// 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* IRAM_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* IRAM_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* IRAM_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* IRAM_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_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 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__); })
|
#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)
|
#elif defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
|
||||||
#include <pgmspace.h>
|
#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__); })
|
#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
|
//C - to be discussed
|
||||||
/*
|
/*
|
||||||
Problem, I would like to report the file and line number with the umm poison
|
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/
|
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
|
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-application secondary stack
|
||||||
. A per-connection TLS receive/transmit buffer plus overhead
|
. 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.
|
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.
|
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)
|
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.
|
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.
|
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>`__).
|
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
|
mode
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
Regular WiFi modes
|
|
||||||
__________________
|
|
||||||
|
|
||||||
.. code:: cpp
|
.. code:: cpp
|
||||||
|
|
||||||
bool mode(WiFiMode_t m)
|
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``: switch to `Access Point (AP) <readme.rst#soft-access-point>`__ mode.
|
||||||
- ``WIFI_AP_STA``: enable both Station (STA) and Access Point (AP) 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
|
getMode
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@ -170,6 +177,41 @@ getPhyMode
|
|||||||
|
|
||||||
Gets the WiFi radio phy mode that is currently set.
|
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
|
Other Function Calls
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -179,8 +221,6 @@ Other Function Calls
|
|||||||
WiFiSleepType_t getSleepMode ()
|
WiFiSleepType_t getSleepMode ()
|
||||||
bool enableSTA (bool enable)
|
bool enableSTA (bool enable)
|
||||||
bool enableAP (bool enable)
|
bool enableAP (bool enable)
|
||||||
bool forceSleepBegin (uint32 sleepUs=0)
|
|
||||||
bool forceSleepWake ()
|
|
||||||
int hostByName (const char *aHostname, IPAddress &aResult)
|
int hostByName (const char *aHostname, IPAddress &aResult)
|
||||||
|
|
||||||
appeared with SDK pre-V3:
|
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
|
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.
|
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.
|
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:
|
Example:
|
||||||
|
|
||||||
.. code:: cpp
|
.. code:: cpp
|
||||||
|
|
||||||
#include <ESP8266WiFiMulti.h>
|
#include <ESP8266WiFiMulti.h>
|
||||||
|
|
||||||
ESP8266WiFiMulti wifiMulti;
|
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
|
2. GPIO0, GPIO15 and CH\_PD are connected using pull up / pull down
|
||||||
resistors, 3. Module is put into boot loader mode.
|
resistors, 3. Module is put into boot loader mode.
|
||||||
|
|
||||||
For specific details please refer to section on `Generic ESP8266
|
For specific details please refer to section on `Generic ESP8266 module <../boards.rst#generic-esp8266-module>`__.
|
||||||
modules <../boards.rst#generic-esp8266-modules>`__. Example modules
|
Example modules without USB to serial converter on board are shown below.
|
||||||
without USB to serial converter on board are shown below.
|
|
||||||
|
|
||||||
.. figure:: pictures/a01-example-boards-without-usb.png
|
.. figure:: pictures/a01-example-boards-without-usb.png
|
||||||
:alt: Example ESP8266 modules without USB to serial converter
|
: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
|
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
|
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
|
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
|
IRAM_ATTR attribute declared. Not only that, but the entire function tree
|
||||||
called from the ISR must also have the ICACHE_RAM_ATTR declared.
|
called from the ISR must also have the IRAM_ATTR declared.
|
||||||
Be aware that every function that has this attribute reduces available memory.
|
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,
|
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.
|
Error compiling for board Generic ESP8266 Module.
|
||||||
|
|
||||||
Below is an example messages for
|
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``.
|
``WAKE_RF_DEFAULT``.
|
||||||
|
|
||||||
Ref. `#3072 <https://github.com/esp8266/Arduino/issues/3072>`__
|
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
|
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.
|
filesystems since most SD cards today are greater than 4GB in size.
|
||||||
|
|
||||||
setTimeCallback(time_t (*cb)(void))
|
setTimeCallback(time_t (\*cb)(void))
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code:: cpp
|
.. code:: cpp
|
||||||
|
|
||||||
@ -574,8 +574,8 @@ rewind
|
|||||||
|
|
||||||
Resets the internal pointer to the start of the directory.
|
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.
|
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
|
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.
|
after ``close`` function was called.
|
||||||
|
|
||||||
openNextFile (compatibiity method, not recommended for new code)
|
openNextFile (compatibiity method, not recommended for new code)
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code:: cpp
|
.. code:: cpp
|
||||||
|
|
||||||
@ -705,7 +705,7 @@ Opens the next file in the directory pointed to by the File. Only valid
|
|||||||
when ``File.isDirectory() == true``.
|
when ``File.isDirectory() == true``.
|
||||||
|
|
||||||
rewindDirectory (compatibiity method, not recommended for new code)
|
rewindDirectory (compatibiity method, not recommended for new code)
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code:: cpp
|
.. 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
|
Resets the ``openNextFile`` pointer to the top of the directory. Only
|
||||||
valid when ``File.isDirectory() == true``.
|
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
|
Sets the time callback for this specific file. Note that the SD and
|
||||||
SDFS filesystems only support a filesystem-wide callback and calls to
|
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.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.
|
``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);
|
uint8_t mmu_set_uint8(void *p8, const uint8_t val);
|
||||||
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_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);
|
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
|
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
|
Safety
|
||||||
|
@ -9,13 +9,13 @@ and have several limitations:
|
|||||||
|
|
||||||
* Interrupt callback functions must be in IRAM, because the flash may be
|
* 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
|
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
|
attribute is not present, the sketch will crash when it attempts to
|
||||||
``attachInterrupt`` with an error message.
|
``attachInterrupt`` with an error message.
|
||||||
|
|
||||||
.. code:: cpp
|
.. 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
|
* Interrupts must not call ``delay()`` or ``yield()``, or call any routines
|
||||||
which internally use ``delay()`` or ``yield()`` either.
|
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
|
``detachInterrupt`` functions. Interrupts may be attached to any GPIO
|
||||||
pin, except GPIO16. Standard Arduino interrupt types are supported:
|
pin, except GPIO16. Standard Arduino interrupt types are supported:
|
||||||
``CHANGE``, ``RISING``, ``FALLING``. ISRs need to have
|
``CHANGE``, ``RISING``, ``FALLING``. ISRs need to have
|
||||||
``ICACHE_RAM_ATTR`` before the function definition.
|
``IRAM_ATTR`` before the function definition.
|
||||||
|
|
||||||
Analog input
|
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
|
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
|
either to read voltage at ADC pin, or to read module supply voltage
|
||||||
(VCC).
|
(VCC).
|
||||||
|
|
||||||
To read external voltage applied to ADC pin, use ``analogRead(A0)``.
|
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
|
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
|
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.
|
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.
|
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>`__
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if(!_data) {
|
if(!_data) {
|
||||||
DEBUGV("EEPROMClass::read without ::begin\n");
|
DEBUGV("EEPROMClass::write without ::begin\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ void loop() {
|
|||||||
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
|
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
|
||||||
|
|
||||||
client->setFingerprint(fingerprint);
|
client->setFingerprint(fingerprint);
|
||||||
|
// Or, if you happy to ignore the SSL certificate, then use the following line instead:
|
||||||
|
// client->setInsecure();
|
||||||
|
|
||||||
HTTPClient https;
|
HTTPClient https;
|
||||||
|
|
||||||
|
@ -25,9 +25,22 @@
|
|||||||
|
|
||||||
#include "ESP8266HTTPClient.h"
|
#include "ESP8266HTTPClient.h"
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <StreamString.h>
|
#include <StreamDev.h>
|
||||||
#include <base64.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
|
* 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);
|
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send Payload if needed
|
// transfer all of it, with send-timeout
|
||||||
if (payload && size > 0) {
|
if (size && StreamConstPtr(payload, size).sendAll(_client) != size)
|
||||||
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);
|
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||||
} else if (written == 0) {
|
|
||||||
return returnError(HTTPC_ERROR_CONNECTION_LOST);
|
|
||||||
}
|
|
||||||
bytesWritten += written;
|
|
||||||
size -= written;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle Server Response (Header)
|
// handle Server Response (Header)
|
||||||
code = handleHeaderResponse();
|
code = handleHeaderResponse();
|
||||||
@ -545,112 +543,13 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
|
|||||||
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
int buff_size = HTTP_TCP_BUFFER_SIZE;
|
// transfer all of it, with timeout
|
||||||
|
size_t transferred = stream->sendSize(_client, size);
|
||||||
int len = size;
|
if (transferred != size)
|
||||||
int bytesWritten = 0;
|
{
|
||||||
|
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
|
||||||
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);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle Server Response (Header)
|
// handle Server Response (Header)
|
||||||
return returnError(handleHeaderResponse());
|
return returnError(handleHeaderResponse());
|
||||||
@ -725,13 +624,13 @@ int HTTPClient::writeToStream(Stream * stream)
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if(_transferEncoding == HTTPC_TE_IDENTITY) {
|
if(_transferEncoding == HTTPC_TE_IDENTITY) {
|
||||||
if(len > 0 || len == -1) {
|
// len < 0: transfer all of it, with timeout
|
||||||
ret = writeToStreamDataBlock(stream, len);
|
// len >= 0: max:len, with timeout
|
||||||
|
ret = _client->sendSize(stream, len);
|
||||||
|
|
||||||
// have we an error?
|
// do we have an error?
|
||||||
if(ret < 0) {
|
if(_client->getLastSendReport() != Stream::Report::Success) {
|
||||||
return returnError(ret);
|
return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
|
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
|
||||||
int size = 0;
|
int size = 0;
|
||||||
@ -754,11 +653,11 @@ int HTTPClient::writeToStream(Stream * stream)
|
|||||||
|
|
||||||
// data left?
|
// data left?
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
int r = writeToStreamDataBlock(stream, len);
|
// read len bytes with timeout
|
||||||
if(r < 0) {
|
int r = _client->sendSize(stream, len);
|
||||||
// error in writeToStreamDataBlock
|
if (_client->getLastSendReport() != Stream::Report::Success)
|
||||||
return returnError(r);
|
// not all data transferred
|
||||||
}
|
return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
|
||||||
ret += r;
|
ret += r;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -948,9 +847,7 @@ bool HTTPClient::connect(void)
|
|||||||
{
|
{
|
||||||
if(_reuse && _canReuse && connected()) {
|
if(_reuse && _canReuse && connected()) {
|
||||||
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
|
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
|
||||||
while(_client->available() > 0) {
|
_client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
|
||||||
_client->read();
|
|
||||||
}
|
|
||||||
return true;
|
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());
|
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;
|
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
|
* called to handle error return, may disconnect the connection if still exists
|
||||||
* @param error
|
* @param error
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <StreamString.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_CLIENT
|
#ifdef DEBUG_ESP_HTTP_CLIENT
|
||||||
@ -148,8 +148,6 @@ typedef enum {
|
|||||||
class TransportTraits;
|
class TransportTraits;
|
||||||
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
|
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
|
||||||
|
|
||||||
class StreamString;
|
|
||||||
|
|
||||||
class HTTPClient
|
class HTTPClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -115,9 +115,9 @@ void setup(void) {
|
|||||||
// swallow the exact amount matching the full request+content,
|
// swallow the exact amount matching the full request+content,
|
||||||
// hence the tcp connection cannot be handled anymore by the
|
// hence the tcp connection cannot be handled anymore by the
|
||||||
// webserver.
|
// webserver.
|
||||||
#ifdef STREAMTO_API
|
#ifdef STREAMSEND_API
|
||||||
// we are lucky
|
// we are lucky
|
||||||
client->toWithTimeout(Serial, 500);
|
client->sendAll(Serial, 500);
|
||||||
#else
|
#else
|
||||||
auto last = millis();
|
auto last = millis();
|
||||||
while ((millis() - last) < 500) {
|
while ((millis() - last) < 500) {
|
||||||
|
@ -123,11 +123,8 @@ void setup(void) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.onNotFound(handleNotFound);
|
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
|
//ask server to track these headers
|
||||||
server.collectHeaders(headerkeys, headerkeyssize);
|
server.collectHeaders("User-Agent", "Cookie");
|
||||||
server.begin();
|
server.begin();
|
||||||
Serial.println("HTTP server started");
|
Serial.println("HTTP server started");
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
#include "detail/RequestHandlersImpl.h"
|
#include "detail/RequestHandlersImpl.h"
|
||||||
|
#include <StreamDev.h>
|
||||||
|
|
||||||
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
|
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
|
||||||
static const char qop_auth[] PROGMEM = "qop=auth";
|
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||||
@ -372,7 +373,7 @@ void ESP8266WebServerTemplate<ServerType>::close() {
|
|||||||
_server.close();
|
_server.close();
|
||||||
_currentStatus = HC_NONE;
|
_currentStatus = HC_NONE;
|
||||||
if(!_headerKeysCount)
|
if(!_headerKeysCount)
|
||||||
collectHeaders(0, 0);
|
collectHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
@ -440,72 +441,69 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int
|
|||||||
_responseHeaders = "";
|
_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>
|
template <typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::send(int code, char* content_type, const String& content) {
|
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>
|
template <typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::send(int code, const String& content_type, const String& content) {
|
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>
|
template <typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) {
|
void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) {
|
||||||
if (_currentMethod == HTTP_HEAD) return;
|
StreamConstPtr ref(content.c_str(), content.length());
|
||||||
const char * footer = "\r\n";
|
sendContent(&ref);
|
||||||
size_t len = content.length();
|
|
||||||
if(_chunked) {
|
|
||||||
char chunkSize[11];
|
|
||||||
sprintf(chunkSize, "%zx\r\n", len);
|
|
||||||
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
|
|
||||||
}
|
}
|
||||||
_currentClient.write((const uint8_t *)content.c_str(), len);
|
|
||||||
|
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) {
|
if(_chunked) {
|
||||||
_currentClient.write((const uint8_t *)footer, 2);
|
_currentClient.printf("%zx\r\n", content_length);
|
||||||
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;
|
_chunked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,19 +516,8 @@ void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content) {
|
|||||||
|
|
||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) {
|
void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) {
|
||||||
const char * footer = "\r\n";
|
StreamConstPtr ptr(content, size);
|
||||||
if(_chunked) {
|
return sendContent(&ptr, size);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
@ -608,7 +595,6 @@ bool ESP8266WebServerTemplate<ServerType>::hasArg(const String& name) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) const {
|
const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) const {
|
||||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
@ -618,14 +604,11 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
|
|||||||
return emptyString;
|
return emptyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template<typename ServerType>
|
template<typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||||
_headerKeysCount = headerKeysCount + 2;
|
if (_currentHeaders)
|
||||||
if (_currentHeaders){
|
|
||||||
delete[] _currentHeaders;
|
delete[] _currentHeaders;
|
||||||
}
|
_currentHeaders = new RequestArgument[_headerKeysCount = headerKeysCount + 2];
|
||||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
|
||||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||||
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
|
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
|
||||||
for (int i = 2; i < _headerKeysCount; i++){
|
for (int i = 2; i < _headerKeysCount; i++){
|
||||||
@ -633,6 +616,18 @@ void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
template <typename ServerType>
|
||||||
const String& ESP8266WebServerTemplate<ServerType>::header(int i) const {
|
const String& ESP8266WebServerTemplate<ServerType>::header(int i) const {
|
||||||
if (i < _headerKeysCount)
|
if (i < _headerKeysCount)
|
||||||
@ -694,7 +689,7 @@ void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
|
|||||||
}
|
}
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
using namespace mime;
|
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;
|
handled = true;
|
||||||
}
|
}
|
||||||
if (handled) {
|
if (handled) {
|
||||||
|
@ -138,6 +138,8 @@ public:
|
|||||||
int args() const; // get arguments count
|
int args() const; // get arguments count
|
||||||
bool hasArg(const String& name) const; // check if argument exists
|
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
|
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(const String& name) const; // get request header value by name
|
||||||
const String& header(int i) const; // get request header value by number
|
const String& header(int i) const; // get request header value by number
|
||||||
const String& headerName(int i) const; // get request header name 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
|
// code - HTTP response code, can be 200 or 404
|
||||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||||
// content - actual content body
|
// 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, char* content_type, const String& content);
|
||||||
void send(int code, const String& 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) {
|
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);
|
||||||
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
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 setContentLength(const size_t contentLength);
|
||||||
void sendHeader(const String& name, const String& value, bool first = false);
|
void sendHeader(const String& name, const String& value, bool first = false);
|
||||||
void sendContent(const String& content);
|
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);
|
||||||
void sendContent_P(PGM_P content, size_t size);
|
void sendContent_P(PGM_P content, size_t size);
|
||||||
void sendContent(const char *content) { sendContent_P(content); }
|
void sendContent(const char *content) { sendContent_P(content); }
|
||||||
void sendContent(const char *content, size_t size) { sendContent_P(content, size); }
|
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) {
|
bool chunkedResponseModeStart_P (int code, PGM_P content_type) {
|
||||||
if (_currentVersion == 0)
|
if (_currentVersion == 0)
|
||||||
// no chunk mode in HTTP/1.0
|
// no chunk mode in HTTP/1.0
|
||||||
@ -215,11 +226,35 @@ public:
|
|||||||
size_t contentLength = 0;
|
size_t contentLength = 0;
|
||||||
_streamFileCore(file.size(), file.name(), contentType);
|
_streamFileCore(file.size(), file.name(), contentType);
|
||||||
if (requestMethod == HTTP_GET) {
|
if (requestMethod == HTTP_GET) {
|
||||||
contentLength = _currentClient.write(file);
|
contentLength = file.sendAll(_currentClient);
|
||||||
}
|
}
|
||||||
return contentLength;
|
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);
|
static String responseCodeToString(const int code);
|
||||||
|
|
||||||
void addHook (HookFunction hook) {
|
void addHook (HookFunction hook) {
|
||||||
|
@ -37,22 +37,8 @@ namespace esp8266webserver {
|
|||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms)
|
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms)
|
||||||
{
|
{
|
||||||
if (!data.reserve(maxLength + 1))
|
S2Stream dataStream(data);
|
||||||
return false;
|
return client.sendSize(dataStream, maxLength, timeout_ms) == maxLength;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ServerType>
|
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
|
// DigiCert High Assurance EV Root CA
|
||||||
const char trustRoot[] PROGMEM = R"EOF(
|
const char trustRoot[] PROGMEM = R"EOF(
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
|
MIIE6zCCBHGgAwIBAgIQAtX25VXj+RoJlA3D2bWkgzAKBggqhkjOPQQDAzBWMQsw
|
||||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
|
||||||
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
|
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjEwMzA0MDAw
|
||||||
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
|
MDAwWhcNMjIwMzA5MjM1OTU5WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
|
||||||
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
|
aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHVi
|
||||||
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
|
LCBJbmMuMRUwEwYDVQQDDAwqLmdpdGh1Yi5jb20wWTATBgcqhkjOPQIBBggqhkjO
|
||||||
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
|
PQMBBwNCAAQf8SePhtD7JeGm0YuTQ4HihyeENuvsNFdYPPIxIx6Lj9iOu2ECkgy4
|
||||||
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
|
52UR+mhIF24OvPizDveyCFOqmG/MI7kwo4IDDTCCAwkwHwYDVR0jBBgwFoAUCrwI
|
||||||
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
|
KReMpTlteg7OM8cus+37w3owHQYDVR0OBBYEFP5TUYtiCp+N3FISu3CqxMlJhdG1
|
||||||
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
|
MCMGA1UdEQQcMBqCDCouZ2l0aHViLmNvbYIKZ2l0aHViLmNvbTAOBgNVHQ8BAf8E
|
||||||
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
|
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGXBgNVHR8EgY8w
|
||||||
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
|
gYwwRKBCoECGPmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5
|
||||||
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
|
YnJpZEVDQ1NIQTM4NDIwMjBDQTEuY3JsMESgQqBAhj5odHRwOi8vY3JsNC5kaWdp
|
||||||
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
|
Y2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0ExLmNybDA+
|
||||||
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
|
BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRp
|
||||||
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
|
Z2ljZXJ0LmNvbS9DUFMwgYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQcwAYYYaHR0
|
||||||
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
|
cDovL29jc3AuZGlnaWNlcnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0
|
||||||
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
|
cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0Ex
|
||||||
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
|
LmNydDAMBgNVHRMBAf8EAjAAMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAKXm+
|
||||||
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
|
8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF3/bWc4AAABAMARjBEAiBm
|
||||||
+OkuE6N36B9K
|
IdofaKj+XfeISM/2tjap1nQY1afFSBAcdw/YtgjmSQIgMqWoDyfO66suyk2VFcld
|
||||||
|
1C+WHUNGvXsCRPof5HG5QQgAdgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNc
|
||||||
|
buIPAgAAAXf9tZ0CAAAEAwBHMEUCIQCJzwZRfAvv0izotFx2KE0sgV8O+NfuHUpa
|
||||||
|
1866RqKEtwIgc65P+xToSqPbp/J1gSFBJgySI/a1YoB+3p8xXTYaDsAwCgYIKoZI
|
||||||
|
zj0EAwMDaAAwZQIxAL8fIlMNWdeKHalpm9z+ksCuYT4tSN1ubXeNvDywr56me+yT
|
||||||
|
+fr42MnEcBdUtLOVOAIwPNC9fAJjyHHTL2vaRW1JRnrovLKDQVbZpZNIZnlY3WFu
|
||||||
|
kmxiBWDOpyfJrG9vQ25K
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
)EOF";
|
)EOF";
|
||||||
X509List cert(trustRoot);
|
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
|
// Released to public domain
|
||||||
|
|
||||||
// Current on WEMOS D1 mini (including: LDO, usbserial chip):
|
// Current on WEMOS D1 mini (including: LDO, usbserial chip):
|
||||||
@ -24,11 +24,6 @@ WiFiState state;
|
|||||||
const char* ssid = STASSID;
|
const char* ssid = STASSID;
|
||||||
const char* password = STAPSK;
|
const char* password = STAPSK;
|
||||||
|
|
||||||
void preinit(void) {
|
|
||||||
// Make sure, wifi stays off after boot.
|
|
||||||
ESP8266WiFiClass::preinitWiFiOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(74880);
|
Serial.begin(74880);
|
||||||
//Serial.setDebugOutput(true); // If you need debug output
|
//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));
|
ESP.rtcUserMemoryRead(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
|
||||||
unsigned long start = millis();
|
unsigned long start = millis();
|
||||||
|
|
||||||
if (!WiFi.mode(WIFI_RESUME, &state)
|
if (!WiFi.resumeFromShutdown(state)
|
||||||
|| (WiFi.waitForConnectResult(10000) != WL_CONNECTED)) {
|
|| (WiFi.waitForConnectResult(10000) != WL_CONNECTED)) {
|
||||||
Serial.println("Cannot resume WiFi connection, connecting via begin...");
|
Serial.println("Cannot resume WiFi connection, connecting via begin...");
|
||||||
WiFi.persistent(false);
|
WiFi.persistent(false);
|
||||||
@ -68,7 +63,7 @@ void setup() {
|
|||||||
// Here you can do whatever you need to do that needs a WiFi connection.
|
// 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));
|
ESP.rtcUserMemoryWrite(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
@ -56,6 +56,8 @@ enableSTA KEYWORD2
|
|||||||
enableAP KEYWORD2
|
enableAP KEYWORD2
|
||||||
forceSleepBegin KEYWORD2
|
forceSleepBegin KEYWORD2
|
||||||
forceSleepWake KEYWORD2
|
forceSleepWake KEYWORD2
|
||||||
|
shutdown KEYWORD2
|
||||||
|
resumeFromShutdown KEYWORD2
|
||||||
|
|
||||||
#ESP8266WiFi
|
#ESP8266WiFi
|
||||||
printDiag KEYWORD2
|
printDiag KEYWORD2
|
||||||
@ -156,6 +158,7 @@ loadCertificate KEYWORD2
|
|||||||
loadPrivateKey KEYWORD2
|
loadPrivateKey KEYWORD2
|
||||||
loadCACert KEYWORD2
|
loadCACert KEYWORD2
|
||||||
allowSelfSignedCerts KEYWORD2
|
allowSelfSignedCerts KEYWORD2
|
||||||
|
setSSLVersion KEYWORD2
|
||||||
|
|
||||||
#WiFiServer
|
#WiFiServer
|
||||||
hasClient KEYWORD2
|
hasClient KEYWORD2
|
||||||
|
@ -125,7 +125,7 @@ int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char *
|
|||||||
uint8_t fileHeader[60];
|
uint8_t fileHeader[60];
|
||||||
// 0..15 = filename in ASCII
|
// 0..15 = filename in ASCII
|
||||||
// 48...57 = length in decimal ASCII
|
// 48...57 = length in decimal ASCII
|
||||||
uint32_t length;
|
int32_t length;
|
||||||
if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) {
|
if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -201,7 +201,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
|
|||||||
free(der);
|
free(der);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (data.read((uint8_t *)der, ci.length) != ci.length) {
|
if (data.read(der, ci.length) != (int)ci.length) {
|
||||||
free(der);
|
free(der);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ extern "C" {
|
|||||||
#define DEBUG_WIFI(...) do { (void)0; } while (0)
|
#define DEBUG_WIFI(...) do { (void)0; } while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern "C" void enableWiFiAtBootTime (void) __attribute__((noinline));
|
||||||
|
|
||||||
class ESP8266WiFiClass : public ESP8266WiFiGenericClass, public ESP8266WiFiSTAClass, public ESP8266WiFiScanClass, public ESP8266WiFiAPClass {
|
class ESP8266WiFiClass : public ESP8266WiFiGenericClass, public ESP8266WiFiSTAClass, public ESP8266WiFiScanClass, public ESP8266WiFiAPClass {
|
||||||
public:
|
public:
|
||||||
|
@ -83,7 +83,7 @@ struct WiFiEventHandlerOpaque
|
|||||||
|
|
||||||
static std::list<WiFiEventHandler> sCbEventList;
|
static std::list<WiFiEventHandler> sCbEventList;
|
||||||
|
|
||||||
bool ESP8266WiFiGenericClass::_persistent = true;
|
bool ESP8266WiFiGenericClass::_persistent = false;
|
||||||
WiFiMode_t ESP8266WiFiGenericClass::_forceSleepLastMode = WIFI_OFF;
|
WiFiMode_t ESP8266WiFiGenericClass::_forceSleepLastMode = WIFI_OFF;
|
||||||
|
|
||||||
ESP8266WiFiGenericClass::ESP8266WiFiGenericClass()
|
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) {
|
WiFiEventHandler handler = std::make_shared<WiFiEventHandlerOpaque>(WIFI_EVENT_STAMODE_CONNECTED, [f](System_Event_t* e) {
|
||||||
auto& src = e->event_info.connected;
|
auto& src = e->event_info.connected;
|
||||||
WiFiEventStationModeConnected dst;
|
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);
|
memcpy(dst.bssid, src.bssid, 6);
|
||||||
dst.channel = src.channel;
|
dst.channel = src.channel;
|
||||||
f(dst);
|
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){
|
WiFiEventHandler handler = std::make_shared<WiFiEventHandlerOpaque>(WIFI_EVENT_STAMODE_DISCONNECTED, [f](System_Event_t* e){
|
||||||
auto& src = e->event_info.disconnected;
|
auto& src = e->event_info.disconnected;
|
||||||
WiFiEventStationModeDisconnected dst;
|
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);
|
memcpy(dst.bssid, src.bssid, 6);
|
||||||
dst.reason = static_cast<WiFiDisconnectReason>(src.reason);
|
dst.reason = static_cast<WiFiDisconnectReason>(src.reason);
|
||||||
f(dst);
|
f(dst);
|
||||||
@ -401,27 +401,10 @@ bool ESP8266WiFiGenericClass::getPersistent(){
|
|||||||
* set new mode
|
* set new mode
|
||||||
* @param m WiFiMode_t
|
* @param m WiFiMode_t
|
||||||
*/
|
*/
|
||||||
bool ESP8266WiFiGenericClass::mode(WiFiMode_t m, WiFiState* state) {
|
bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) {
|
||||||
if (m == WIFI_SHUTDOWN) {
|
if (m & ~(WIFI_STA | WIFI_AP)) {
|
||||||
return shutdown(0, state);
|
|
||||||
}
|
|
||||||
else if (m == WIFI_RESUME) {
|
|
||||||
return resumeFromShutdown(state);
|
|
||||||
}
|
|
||||||
else if (m & ~(WIFI_STA | WIFI_AP))
|
|
||||||
// any other bits than legacy disallowed
|
// any other bits than legacy disallowed
|
||||||
return false;
|
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){
|
if(_persistent){
|
||||||
@ -432,6 +415,12 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m, WiFiState* state) {
|
|||||||
return true;
|
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;
|
bool ret = false;
|
||||||
ETS_UART_INTR_DISABLE();
|
ETS_UART_INTR_DISABLE();
|
||||||
if(_persistent) {
|
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
|
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;
|
bool persistent = _persistent;
|
||||||
WiFiMode_t before_off_mode = getMode();
|
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)
|
if (!ret)
|
||||||
{
|
{
|
||||||
DEBUG_WIFI("core: error with wifi_get_ip_info(STATION_IF)\n");
|
DEBUG_WIFI("core: error with wifi_get_ip_info(STATION_IF)\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memset(state->state.fwconfig.bssid, 0xff, 6);
|
memset(state.state.fwconfig.bssid, 0xff, 6);
|
||||||
ret = wifi_station_get_config(&state->state.fwconfig);
|
ret = wifi_station_get_config(&state.state.fwconfig);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
{
|
{
|
||||||
DEBUG_WIFI("core: error with wifi_station_get_config\n");
|
DEBUG_WIFI("core: error with wifi_station_get_config\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state->state.channel = wifi_get_channel();
|
state.state.channel = wifi_get_channel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable persistence in FW so in case of power failure
|
// 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
|
// WiFi is now in force-sleep mode
|
||||||
|
|
||||||
if (state)
|
|
||||||
{
|
|
||||||
// finish filling state and process crc
|
// finish filling state and process crc
|
||||||
|
|
||||||
state->state.persistent = persistent;
|
state.state.persistent = persistent;
|
||||||
state->state.mode = before_off_mode;
|
state.state.mode = before_off_mode;
|
||||||
|
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
for (auto& ntp: state->state.ntp)
|
for (auto& ntp: state.state.ntp)
|
||||||
{
|
{
|
||||||
ntp = *sntp_getserver(i++);
|
ntp = *sntp_getserver(i++);
|
||||||
}
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
for (auto& dns: state->state.dns)
|
|
||||||
|
for (auto& dns: state.state.dns)
|
||||||
|
{
|
||||||
dns = WiFi.dnsIP(i++);
|
dns = WiFi.dnsIP(i++);
|
||||||
state->crc = shutdownCRC(state);
|
|
||||||
DEBUG_WIFI("core: state is saved\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.crc = shutdownCRC(state);
|
||||||
|
DEBUG_WIFI("core: state is saved\n");
|
||||||
|
|
||||||
return true;
|
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) {
|
if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) {
|
||||||
wifi_fpm_do_wakeup();
|
wifi_fpm_do_wakeup();
|
||||||
wifi_fpm_close();
|
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;
|
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;
|
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)
|
if (local)
|
||||||
{
|
{
|
||||||
DEBUG_WIFI("core: resume: static address '%s'\n", local.toString().c_str());
|
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;
|
uint8_t i = 0;
|
||||||
for (const auto& ntp: state->state.ntp)
|
for (const auto& ntp: state.state.ntp)
|
||||||
{
|
{
|
||||||
IPAddress ip(ntp);
|
IPAddress ip(ntp);
|
||||||
if (ip.isSet())
|
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,
|
String ssid;
|
||||||
state->state.channel,
|
{
|
||||||
state->state.fwconfig.bssid,
|
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);
|
true);
|
||||||
if (beginResult == WL_CONNECT_FAILED)
|
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");
|
DEBUG_WIFI("core: resume AP mode TODO\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// success, invalidate saved state
|
// success, invalidate saved state
|
||||||
state->crc++;
|
state.crc++;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//meant to be called from user-defined ::preinit()
|
|
||||||
void ESP8266WiFiGenericClass::preinitWiFiOff () {
|
void ESP8266WiFiGenericClass::preinitWiFiOff () {
|
||||||
// https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391
|
// It was meant to be called from user-defined ::preinit()
|
||||||
// WiFi.persistent(false);
|
// It is now deprecated by enableWiFiAtBootTime() and __disableWiFiAtBootTime()
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,39 @@ class ESP8266WiFiGenericClass {
|
|||||||
uint8_t channel(void);
|
uint8_t channel(void);
|
||||||
|
|
||||||
bool setSleepMode(WiFiSleepType_t type, uint8_t listenInterval = 0);
|
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();
|
WiFiSleepType_t getSleepMode();
|
||||||
uint8_t getListenInterval ();
|
uint8_t getListenInterval ();
|
||||||
@ -87,9 +120,9 @@ class ESP8266WiFiGenericClass {
|
|||||||
|
|
||||||
void setOutputPower(float dBm);
|
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();
|
WiFiMode_t getMode();
|
||||||
|
|
||||||
bool enableSTA(bool enable);
|
bool enableSTA(bool enable);
|
||||||
@ -98,21 +131,23 @@ class ESP8266WiFiGenericClass {
|
|||||||
bool forceSleepBegin(uint32 sleepUs = 0);
|
bool forceSleepBegin(uint32 sleepUs = 0);
|
||||||
bool forceSleepWake();
|
bool forceSleepWake();
|
||||||
|
|
||||||
static uint32_t shutdownCRC (const WiFiState* state);
|
// wrappers around mode() and forceSleepBegin/Wake
|
||||||
static bool shutdownValidCRC (const WiFiState* state);
|
// - sleepUs is WiFi.forceSleepBegin() parameter, 0 means forever
|
||||||
static void preinitWiFiOff (); //meant to be called in user-defined preinit()
|
// - 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:
|
protected:
|
||||||
static bool _persistent;
|
static bool _persistent;
|
||||||
static WiFiMode_t _forceSleepLastMode;
|
static WiFiMode_t _forceSleepLastMode;
|
||||||
|
|
||||||
static void _eventCallback(void *event);
|
static uint32_t shutdownCRC (const WiFiState& state);
|
||||||
|
|
||||||
// called by WiFi.mode(SHUTDOWN/RESTORE, state)
|
static void _eventCallback(void *event);
|
||||||
// - 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);
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------
|
||||||
// ------------------------------------ Generic Network function --------------------------------
|
// ------------------------------------ Generic Network function --------------------------------
|
||||||
|
@ -504,6 +504,18 @@ IPAddress ESP8266WiFiSTAClass::dnsIP(uint8_t dns_no) {
|
|||||||
return IPAddress(dns_getserver(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 Connection status.
|
||||||
* @return one of the value defined in wl_status_t
|
* @return one of the value defined in wl_status_t
|
||||||
|
@ -70,6 +70,7 @@ class ESP8266WiFiSTAClass: public LwipIntf {
|
|||||||
IPAddress gatewayIP();
|
IPAddress gatewayIP();
|
||||||
IPAddress dnsIP(uint8_t dns_no = 0);
|
IPAddress dnsIP(uint8_t dns_no = 0);
|
||||||
|
|
||||||
|
IPAddress broadcastIP();
|
||||||
// STA WiFi info
|
// STA WiFi info
|
||||||
wl_status_t status();
|
wl_status_t status();
|
||||||
String SSID() const;
|
String SSID() const;
|
||||||
|
@ -34,8 +34,7 @@
|
|||||||
|
|
||||||
typedef enum WiFiMode
|
typedef enum WiFiMode
|
||||||
{
|
{
|
||||||
WIFI_OFF = 0, WIFI_STA = 1, WIFI_AP = 2, WIFI_AP_STA = 3,
|
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
|
|
||||||
} WiFiMode_t;
|
} WiFiMode_t;
|
||||||
|
|
||||||
typedef enum WiFiPhyMode
|
typedef enum WiFiPhyMode
|
||||||
@ -48,6 +47,13 @@ typedef enum WiFiSleepType
|
|||||||
WIFI_NONE_SLEEP = 0, WIFI_LIGHT_SLEEP = 1, WIFI_MODEM_SLEEP = 2
|
WIFI_NONE_SLEEP = 0, WIFI_LIGHT_SLEEP = 1, WIFI_MODEM_SLEEP = 2
|
||||||
} WiFiSleepType_t;
|
} 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
|
typedef enum WiFiEvent
|
||||||
{
|
{
|
||||||
|
@ -40,6 +40,7 @@ extern "C"
|
|||||||
#include "lwip/netif.h"
|
#include "lwip/netif.h"
|
||||||
#include <include/ClientContext.h>
|
#include <include/ClientContext.h>
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
|
#include <StreamDev.h>
|
||||||
|
|
||||||
uint16_t WiFiClient::_localPort = 0;
|
uint16_t WiFiClient::_localPort = 0;
|
||||||
|
|
||||||
@ -212,23 +213,19 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
_client->setTimeout(_timeout);
|
_client->setTimeout(_timeout);
|
||||||
return _client->write(buf, size);
|
return _client->write((const char*)buf, size);
|
||||||
}
|
|
||||||
|
|
||||||
size_t WiFiClient::write(Stream& stream, size_t unused)
|
|
||||||
{
|
|
||||||
(void) unused;
|
|
||||||
return WiFiClient::write(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WiFiClient::write(Stream& stream)
|
size_t WiFiClient::write(Stream& stream)
|
||||||
{
|
{
|
||||||
|
// (this method is deprecated)
|
||||||
|
|
||||||
if (!_client || !stream.available())
|
if (!_client || !stream.available())
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
_client->setTimeout(_timeout);
|
// core up to 2.7.4 was equivalent to this
|
||||||
return _client->write(stream);
|
return stream.sendAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WiFiClient::write_P(PGM_P buf, size_t size)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
_client->setTimeout(_timeout);
|
_client->setTimeout(_timeout);
|
||||||
return _client->write_P(buf, size);
|
StreamConstPtr nopeek(buf, size);
|
||||||
|
return nopeek.sendAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
int WiFiClient::available()
|
int WiFiClient::available()
|
||||||
{
|
{
|
||||||
if (!_client)
|
if (!_client)
|
||||||
return false;
|
return 0;
|
||||||
|
|
||||||
int result = _client->getSize();
|
int result = _client->getSize();
|
||||||
|
|
||||||
@ -262,10 +260,14 @@ int WiFiClient::read()
|
|||||||
return _client->read();
|
return _client->read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int WiFiClient::read(uint8_t* buf, size_t size)
|
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()
|
int WiFiClient::peek()
|
||||||
@ -304,7 +306,7 @@ bool WiFiClient::flush(unsigned int maxWaitMs)
|
|||||||
|
|
||||||
if (maxWaitMs == 0)
|
if (maxWaitMs == 0)
|
||||||
maxWaitMs = WIFICLIENT_MAX_FLUSH_WAIT_MS;
|
maxWaitMs = WIFICLIENT_MAX_FLUSH_WAIT_MS;
|
||||||
return _client->wait_until_sent(maxWaitMs);
|
return _client->wait_until_acked(maxWaitMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WiFiClient::stop(unsigned int maxWaitMs)
|
bool WiFiClient::stop(unsigned int maxWaitMs)
|
||||||
@ -412,3 +414,28 @@ uint8_t WiFiClient::getKeepAliveCount () const
|
|||||||
{
|
{
|
||||||
return _client->getKeepAliveCount();
|
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(uint8_t) override;
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) override;
|
virtual size_t write(const uint8_t *buf, size_t size) override;
|
||||||
virtual size_t write_P(PGM_P buf, size_t size);
|
virtual size_t write_P(PGM_P buf, size_t size);
|
||||||
size_t write(Stream& stream);
|
size_t write(Stream& stream) [[ deprecated("use stream.sendHow(client...)") ]];
|
||||||
|
|
||||||
// This one is deprecated, use write(Stream& instead)
|
|
||||||
size_t write(Stream& stream, size_t unitSize) __attribute__ ((deprecated));
|
|
||||||
|
|
||||||
virtual int available() override;
|
virtual int available() override;
|
||||||
virtual int read() 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 int peek() override;
|
||||||
virtual size_t peekBytes(uint8_t *buffer, size_t length);
|
virtual size_t peekBytes(uint8_t *buffer, size_t length);
|
||||||
size_t peekBytes(char *buffer, size_t length) {
|
size_t peekBytes(char *buffer, size_t length) {
|
||||||
@ -120,6 +119,22 @@ public:
|
|||||||
bool getSync() const;
|
bool getSync() const;
|
||||||
void setSync(bool sync);
|
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:
|
protected:
|
||||||
|
|
||||||
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
|
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
|
||||||
|
@ -93,6 +93,8 @@ void WiFiClientSecureCtx::_clear() {
|
|||||||
_session = nullptr;
|
_session = nullptr;
|
||||||
_cipher_list = nullptr;
|
_cipher_list = nullptr;
|
||||||
_cipher_cnt = 0;
|
_cipher_cnt = 0;
|
||||||
|
_tls_min = BR_TLS10;
|
||||||
|
_tls_max = BR_TLS12;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiClientSecureCtx::_clearAuthenticationSettings() {
|
void WiFiClientSecureCtx::_clearAuthenticationSettings() {
|
||||||
@ -125,7 +127,7 @@ WiFiClientSecureCtx::~WiFiClientSecureCtx() {
|
|||||||
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
|
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
|
||||||
const X509List *chain, const PrivateKey *sk,
|
const X509List *chain, const PrivateKey *sk,
|
||||||
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
|
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();
|
_clear();
|
||||||
_clearAuthenticationSettings();
|
_clearAuthenticationSettings();
|
||||||
stack_thunk_add_ref();
|
stack_thunk_add_ref();
|
||||||
@ -133,6 +135,8 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
|
|||||||
_iobuf_out_size = iobuf_out_size;
|
_iobuf_out_size = iobuf_out_size;
|
||||||
_client = client;
|
_client = client;
|
||||||
_client->ref();
|
_client->ref();
|
||||||
|
_tls_min = tls_min;
|
||||||
|
_tls_max = tls_max;
|
||||||
if (!_connectSSLServerRSA(chain, sk, cache, client_CA_ta)) {
|
if (!_connectSSLServerRSA(chain, sk, cache, client_CA_ta)) {
|
||||||
_client->unref();
|
_client->unref();
|
||||||
_client = nullptr;
|
_client = nullptr;
|
||||||
@ -144,7 +148,7 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
|
|||||||
const X509List *chain,
|
const X509List *chain,
|
||||||
unsigned cert_issuer_key_type, const PrivateKey *sk,
|
unsigned cert_issuer_key_type, const PrivateKey *sk,
|
||||||
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
|
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();
|
_clear();
|
||||||
_clearAuthenticationSettings();
|
_clearAuthenticationSettings();
|
||||||
stack_thunk_add_ref();
|
stack_thunk_add_ref();
|
||||||
@ -152,6 +156,8 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
|
|||||||
_iobuf_out_size = iobuf_out_size;
|
_iobuf_out_size = iobuf_out_size;
|
||||||
_client = client;
|
_client = client;
|
||||||
_client->ref();
|
_client->ref();
|
||||||
|
_tls_min = tls_min;
|
||||||
|
_tls_max = tls_max;
|
||||||
if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, cache, client_CA_ta)) {
|
if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, cache, client_CA_ta)) {
|
||||||
_client->unref();
|
_client->unref();
|
||||||
_client = nullptr;
|
_client = nullptr;
|
||||||
@ -257,6 +263,27 @@ uint8_t WiFiClientSecureCtx::connected() {
|
|||||||
return false;
|
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 WiFiClientSecureCtx::_write(const uint8_t *buf, size_t size, bool pmem) {
|
||||||
size_t sent_bytes = 0;
|
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);
|
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 WiFiClientSecureCtx::write(Stream& stream) {
|
||||||
size_t totalSent = 0;
|
|
||||||
size_t countRead;
|
|
||||||
size_t countSent;
|
|
||||||
|
|
||||||
if (!connected() || !_handshake_done) {
|
if (!connected() || !_handshake_done) {
|
||||||
DEBUG_BSSL("write: Connect/handshake not completed yet\n");
|
DEBUG_BSSL("write: Connect/handshake not completed yet\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return stream.sendAll(this);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) {
|
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 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() {
|
int WiFiClientSecureCtx::read() {
|
||||||
uint8_t c;
|
uint8_t c;
|
||||||
if (1 == read(&c, 1)) {
|
if (1 == read(&c, 1)) {
|
||||||
@ -989,6 +1016,17 @@ bool WiFiClientSecureCtx::setCiphers(const std::vector<uint16_t>& list) {
|
|||||||
return setCiphers(&list[0], list.size());
|
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
|
// Installs the appropriate X509 cert validation method for a client connection
|
||||||
bool WiFiClientSecureCtx::_installClientX509Validator() {
|
bool WiFiClientSecureCtx::_installClientX509Validator() {
|
||||||
if (_use_insecure || _use_fingerprint || _use_self_signed) {
|
if (_use_insecure || _use_fingerprint || _use_self_signed) {
|
||||||
@ -1094,6 +1132,7 @@ bool WiFiClientSecureCtx::_connectSSL(const char* hostName) {
|
|||||||
return false;
|
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_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.
|
// Apply any client certificates, if supplied.
|
||||||
if (_sk && _sk->isRSA()) {
|
if (_sk && _sk->isRSA()) {
|
||||||
@ -1208,6 +1247,7 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain,
|
|||||||
sk ? sk->getRSA() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
|
sk ? sk->getRSA() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
|
||||||
br_rsa_private_get_default(), br_rsa_pkcs1_sign_get_default());
|
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_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)
|
if (cache != nullptr)
|
||||||
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
|
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
|
||||||
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
|
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,
|
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);
|
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_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)
|
if (cache != nullptr)
|
||||||
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
|
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
|
||||||
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
|
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
|
||||||
@ -1271,6 +1312,7 @@ bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain,
|
|||||||
(void) chain;
|
(void) chain;
|
||||||
(void) cert_issuer_key_type;
|
(void) cert_issuer_key_type;
|
||||||
(void) sk;
|
(void) sk;
|
||||||
|
(void) cache;
|
||||||
(void) client_CA_ta;
|
(void) client_CA_ta;
|
||||||
DEBUG_BSSL("_connectSSLServerEC: Attempting to use EC cert in minimal cipher mode (no EC)\n");
|
DEBUG_BSSL("_connectSSLServerEC: Attempting to use EC cert in minimal cipher mode (no EC)\n");
|
||||||
return false;
|
return false;
|
||||||
|
@ -48,6 +48,7 @@ class WiFiClientSecureCtx : public WiFiClient {
|
|||||||
size_t write_P(PGM_P buf, size_t size) override;
|
size_t write_P(PGM_P buf, size_t size) override;
|
||||||
size_t write(Stream& stream); // Note this is not virtual
|
size_t write(Stream& stream); // Note this is not virtual
|
||||||
int read(uint8_t *buf, size_t size) override;
|
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 available() override;
|
||||||
int read() override;
|
int read() override;
|
||||||
int peek() override;
|
int peek() override;
|
||||||
@ -57,6 +58,8 @@ class WiFiClientSecureCtx : public WiFiClient {
|
|||||||
void flush() override { (void)flush(0); }
|
void flush() override { (void)flush(0); }
|
||||||
void stop() override { (void)stop(0); }
|
void stop() override { (void)stop(0); }
|
||||||
|
|
||||||
|
int availableForWrite() override;
|
||||||
|
|
||||||
// Allow sessions to be saved/restored automatically to a memory area
|
// Allow sessions to be saved/restored automatically to a memory area
|
||||||
void setSession(Session *session) { _session = session; }
|
void setSession(Session *session) { _session = session; }
|
||||||
|
|
||||||
@ -120,6 +123,23 @@ class WiFiClientSecureCtx : public WiFiClient {
|
|||||||
bool setCiphers(const std::vector<uint16_t>& list);
|
bool setCiphers(const std::vector<uint16_t>& list);
|
||||||
bool setCiphersLessSecure(); // Only use the limited set of RSA ciphers without EC
|
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:
|
protected:
|
||||||
bool _connectSSL(const char *hostName); // Do initial SSL handshake
|
bool _connectSSL(const char *hostName); // Do initial SSL handshake
|
||||||
|
|
||||||
@ -161,6 +181,10 @@ class WiFiClientSecureCtx : public WiFiClient {
|
|||||||
std::shared_ptr<uint16_t> _cipher_list;
|
std::shared_ptr<uint16_t> _cipher_list;
|
||||||
uint8_t _cipher_cnt;
|
uint8_t _cipher_cnt;
|
||||||
|
|
||||||
|
// TLS ciphers allowed
|
||||||
|
uint32_t _tls_min;
|
||||||
|
uint32_t _tls_max;
|
||||||
|
|
||||||
unsigned char *_recvapp_buf;
|
unsigned char *_recvapp_buf;
|
||||||
size_t _recvapp_len;
|
size_t _recvapp_len;
|
||||||
|
|
||||||
@ -180,10 +204,10 @@ class WiFiClientSecureCtx : public WiFiClient {
|
|||||||
friend class WiFiClientSecure; // access to private context constructors
|
friend class WiFiClientSecure; // access to private context constructors
|
||||||
WiFiClientSecureCtx(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
|
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 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,
|
WiFiClientSecureCtx(ClientContext* client, const X509List *chain, const PrivateKey *sk,
|
||||||
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
|
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
|
// RSA keyed server
|
||||||
bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk,
|
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); }
|
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 read(uint8_t *buf, size_t size) override { return _ctx->read(buf, size); }
|
||||||
int available() override { return _ctx->available(); }
|
int available() override { return _ctx->available(); }
|
||||||
|
int availableForWrite() override { return _ctx->availableForWrite(); }
|
||||||
int read() override { return _ctx->read(); }
|
int read() override { return _ctx->read(); }
|
||||||
int peek() override { return _ctx->peek(); }
|
int peek() override { return _ctx->peek(); }
|
||||||
size_t peekBytes(uint8_t *buffer, size_t length) override { return _ctx->peekBytes(buffer, length); }
|
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 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
|
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)
|
// 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(IPAddress ip, uint16_t port, uint16_t len);
|
||||||
static bool probeMaxFragmentLength(const char *hostname, 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);
|
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:
|
private:
|
||||||
std::shared_ptr<WiFiClientSecureCtx> _ctx;
|
std::shared_ptr<WiFiClientSecureCtx> _ctx;
|
||||||
|
|
||||||
@ -294,14 +336,14 @@ class WiFiClientSecure : public WiFiClient {
|
|||||||
friend class WiFiServerSecure; // Server needs to access these constructors
|
friend class WiFiServerSecure; // Server needs to access these constructors
|
||||||
WiFiClientSecure(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
|
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 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):
|
||||||
_ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
|
_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,
|
WiFiClientSecure(ClientContext* client, const X509List *chain, const PrivateKey *sk,
|
||||||
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
|
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):
|
||||||
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
|
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta, tls_min, tls_max)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}; // class WiFiClientSecure
|
}; // class WiFiClientSecure
|
||||||
|
@ -79,13 +79,13 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) {
|
|||||||
(void) status; // Unused
|
(void) status; // Unused
|
||||||
if (_unclaimed) {
|
if (_unclaimed) {
|
||||||
if (_sk && _sk->isRSA()) {
|
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();
|
_unclaimed = _unclaimed->next();
|
||||||
result.setNoDelay(_noDelay);
|
result.setNoDelay(_noDelay);
|
||||||
DEBUGV("WS:av\r\n");
|
DEBUGV("WS:av\r\n");
|
||||||
return result;
|
return result;
|
||||||
} else if (_sk && _sk->isEC()) {
|
} 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();
|
_unclaimed = _unclaimed->next();
|
||||||
result.setNoDelay(_noDelay);
|
result.setNoDelay(_noDelay);
|
||||||
DEBUGV("WS:av\r\n");
|
DEBUGV("WS:av\r\n");
|
||||||
@ -101,4 +101,15 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) {
|
|||||||
return WiFiClientSecure();
|
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;
|
_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.
|
// If awaiting connection available and authenticated (i.e. client cert), return it.
|
||||||
WiFiClientSecure available(uint8_t* status = NULL);
|
WiFiClientSecure available(uint8_t* status = NULL);
|
||||||
|
|
||||||
@ -76,6 +80,9 @@ class WiFiServerSecure : public WiFiServer {
|
|||||||
const X509List *_client_CA_ta = nullptr;
|
const X509List *_client_CA_ta = nullptr;
|
||||||
ServerSessions *_cache = 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
Reference in New Issue
Block a user