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

Merge branch 'master' into wifi_mesh_update_2.2

This commit is contained in:
aerlon
2020-03-05 15:44:58 +01:00
committed by GitHub
225 changed files with 7196 additions and 2762 deletions

View File

@@ -168,7 +168,7 @@ int ArduinoOTAClass::parseInt(){
}
String ArduinoOTAClass::readStringUntil(char end){
String res = "";
String res;
int value;
while(true){
value = _udp_ota->read();
@@ -231,7 +231,7 @@ void ArduinoOTAClass::_onRx(){
return;
}
String challenge = _password + ":" + String(_nonce) + ":" + cnonce;
String challenge = _password + ':' + String(_nonce) + ':' + cnonce;
MD5Builder _challengemd5;
_challengemd5.begin();
_challengemd5.add(challenge);
@@ -303,7 +303,7 @@ void ArduinoOTAClass::_runUpdate() {
client.setNoDelay(true);
uint32_t written, total = 0;
while (!Update.isFinished() && client.connected()) {
while (!Update.isFinished() && (client.connected() || client.available())) {
int waited = 1000;
while (!client.available() && waited--)
delay(1);

View File

@@ -119,7 +119,7 @@ void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
query, queryLength);
// If we have no domain name configured, just return an error
if (_domainName == "")
if (_domainName.isEmpty())
return replyWithError(dnsHeader, _errorReplyCode,
query, queryLength);

View File

@@ -0,0 +1,84 @@
/**
PostHTTPClient.ino
Created on: 21.11.2016
*/
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#define USE_SERIAL Serial
/* this can be run with an emulated server on host:
cd esp8266-core-root-dir
cd tests/host
make ../../libraries/ESP8266WebServer/examples/PostServer/PostServer
bin/PostServer/PostServer
then put your PC's IP address in SERVER_IP below, port 9080 (instead of default 80):
*/
//#define SERVER_IP "10.0.1.7:9080" // PC address with emulation on host
#define SERVER_IP "192.168.1.42"
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
void setup() {
USE_SERIAL.begin(115200);
USE_SERIAL.println();
USE_SERIAL.println();
USE_SERIAL.println();
WiFi.begin(STASSID, STAPSK);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
USE_SERIAL.print(".");
}
USE_SERIAL.println("");
USE_SERIAL.print("Connected! IP address: ");
USE_SERIAL.println(WiFi.localIP());
}
void loop() {
// wait for WiFi connection
if ((WiFi.status() == WL_CONNECTED)) {
WiFiClient client;
HTTPClient http;
USE_SERIAL.print("[HTTP] begin...\n");
// configure traged server and url
http.begin(client, "http://" SERVER_IP "/postplain/"); //HTTP
http.addHeader("Content-Type", "application/json");
USE_SERIAL.print("[HTTP] POST...\n");
// start connection and send HTTP header and body
int httpCode = http.POST("{\"hello\":\"world\"}");
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
USE_SERIAL.printf("[HTTP] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
const String& payload = http.getString();
USE_SERIAL.println("received payload:\n<<");
USE_SERIAL.println(payload);
USE_SERIAL.println(">>");
}
} else {
USE_SERIAL.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
delay(10000);
}

View File

@@ -137,9 +137,9 @@ void HTTPClient::clear()
{
_returnCode = 0;
_size = -1;
_headers = "";
_headers.clear();
_location.clear();
_payload.reset();
_location = "";
}
@@ -150,7 +150,7 @@ void HTTPClient::clear()
* @param https bool
* @return success bool
*/
bool HTTPClient::begin(WiFiClient &client, String url) {
bool HTTPClient::begin(WiFiClient &client, const String& url) {
#if HTTPCLIENT_1_1_COMPATIBLE
if(_tcpDeprecated) {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] mix up of new and deprecated api\n");
@@ -188,7 +188,7 @@ bool HTTPClient::begin(WiFiClient &client, String url) {
* @param https bool
* @return success bool
*/
bool HTTPClient::begin(WiFiClient &client, String host, uint16_t port, String uri, bool https)
bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https)
{
#if HTTPCLIENT_1_1_COMPATIBLE
if(_tcpDeprecated) {
@@ -281,8 +281,10 @@ bool HTTPClient::begin(String url)
}
#endif // HTTPCLIENT_1_1_COMPATIBLE
bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol)
{
String url(__url);
DEBUG_HTTPCLIENT("[HTTP-Client][begin] url: %s\n", url.c_str());
clear();
@@ -317,7 +319,7 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
// auth info
String auth = host.substring(0, index);
host.remove(0, index + 1); // remove auth part including @
_base64Authorization = base64::encode(auth);
_base64Authorization = base64::encode(auth, false /* doNewLines */);
}
// get port
@@ -500,9 +502,9 @@ void HTTPClient::setAuthorization(const char * user, const char * password)
{
if(user && password) {
String auth = user;
auth += ":";
auth += ':';
auth += password;
_base64Authorization = base64::encode(auth);
_base64Authorization = base64::encode(auth, false /* doNewLines */);
}
}
@@ -514,6 +516,7 @@ void HTTPClient::setAuthorization(const char * auth)
{
if(auth) {
_base64Authorization = auth;
_base64Authorization.replace(String('\n'), emptyString);
}
}
@@ -533,7 +536,7 @@ void HTTPClient::setTimeout(uint16_t timeout)
* set the URL to a new value. Handy for following redirects.
* @param url
*/
bool HTTPClient::setURL(String url)
bool HTTPClient::setURL(const String& url)
{
// if the new location is only a path then only update the URI
if (url && url[0] == '/') {
@@ -542,13 +545,13 @@ bool HTTPClient::setURL(String url)
return true;
}
if (!url.startsWith(_protocol + ":")) {
if (!url.startsWith(_protocol + ':')) {
DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
return false;
}
// disconnect but preserve _client
// disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
_canReuse = false;
disconnect(true);
clear();
return beginInternal(url, nullptr);
}
@@ -587,16 +590,16 @@ int HTTPClient::GET()
/**
* sends a post request to the server
* @param payload uint8_t *
* @param payload const uint8_t *
* @param size size_t
* @return http code
*/
int HTTPClient::POST(uint8_t * payload, size_t size)
int HTTPClient::POST(const uint8_t* payload, size_t size)
{
return sendRequest("POST", payload, size);
}
int HTTPClient::POST(String payload)
int HTTPClient::POST(const String& payload)
{
return POST((uint8_t *) payload.c_str(), payload.length());
}
@@ -607,26 +610,26 @@ int HTTPClient::POST(String payload)
* @param size size_t
* @return http code
*/
int HTTPClient::PUT(uint8_t * payload, size_t size) {
int HTTPClient::PUT(const uint8_t* payload, size_t size) {
return sendRequest("PUT", payload, size);
}
int HTTPClient::PUT(String payload) {
return PUT((uint8_t *) payload.c_str(), payload.length());
int HTTPClient::PUT(const String& payload) {
return PUT((const uint8_t *) payload.c_str(), payload.length());
}
/**
* sends a patch request to the server
* @param payload uint8_t *
* @param payload const uint8_t *
* @param size size_t
* @return http code
*/
int HTTPClient::PATCH(uint8_t * payload, size_t size) {
int HTTPClient::PATCH(const uint8_t * payload, size_t size) {
return sendRequest("PATCH", payload, size);
}
int HTTPClient::PATCH(String payload) {
return PATCH((uint8_t *) payload.c_str(), payload.length());
int HTTPClient::PATCH(const String& payload) {
return PATCH((const uint8_t *) payload.c_str(), payload.length());
}
/**
@@ -635,19 +638,19 @@ int HTTPClient::PATCH(String payload) {
* @param payload String data for the message body
* @return
*/
int HTTPClient::sendRequest(const char * type, String payload)
int HTTPClient::sendRequest(const char * type, const String& payload)
{
return sendRequest(type, (uint8_t *) payload.c_str(), payload.length());
return sendRequest(type, (const uint8_t *) payload.c_str(), payload.length());
}
/**
* sendRequest
* @param type const char * "GET", "POST", ....
* @param payload uint8_t * data for the message body if null not send
* @param size size_t size for the message body if 0 not send
* @param type const char * "GET", "POST", ....
* @param payload const uint8_t * data for the message body if null not send
* @param size size_t size for the message body if 0 not send
* @return -1 if no info or > 0 when Content-Length is set by server
*/
int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t size)
{
bool redirect = false;
int code = 0;
@@ -655,7 +658,7 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
// wipe out any existing headers from previous request
for(size_t i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].value.length() > 0) {
_currentHeaders[i].value = "";
_currentHeaders[i].value.clear();
}
}
@@ -675,9 +678,21 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
}
// send Payload if needed
if(payload && size > 0) {
if(_client->write(&payload[0], size) != size) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
if (payload && size > 0) {
size_t bytesWritten = 0;
const uint8_t *p = payload;
size_t originalSize = size;
while (bytesWritten < originalSize) {
int written;
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
written = _client->write(p + bytesWritten, towrite);
if (written < 0) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else if (written == 0) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
bytesWritten += written;
size -= written;
}
}
@@ -745,7 +760,7 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
}
if(size > 0) {
addHeader("Content-Length", String(size));
addHeader(F("Content-Length"), String(size));
}
// send Header
@@ -1063,12 +1078,14 @@ String HTTPClient::errorToString(int error)
void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace)
{
// not allow set of Header handled by code
if(!name.equalsIgnoreCase(F("Connection")) &&
!name.equalsIgnoreCase(F("User-Agent")) &&
!name.equalsIgnoreCase(F("Host")) &&
!(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())){
if (!name.equalsIgnoreCase(F("Connection")) &&
!name.equalsIgnoreCase(F("User-Agent")) &&
!name.equalsIgnoreCase(F("Host")) &&
!(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())) {
String headerLine = name;
String headerLine;
headerLine.reserve(name.length() + value.length() + 4);
headerLine += name;
headerLine += ": ";
if (replace) {
@@ -1081,13 +1098,12 @@ void HTTPClient::addHeader(const String& name, const String& value, bool first,
headerLine += value;
headerLine += "\r\n";
if(first) {
if (first) {
_headers = headerLine + _headers;
} else {
_headers += headerLine;
}
}
}
void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
@@ -1212,42 +1228,50 @@ bool HTTPClient::sendHeader(const char * type)
return false;
}
String header = String(type) + " " + (_uri.length() ? _uri : F("/")) + F(" HTTP/1.");
String header;
// 128: Arbitrarily chosen to have enough buffer space for avoiding internal reallocations
header.reserve(_headers.length() + _uri.length() +
_base64Authorization.length() + _host.length() + _userAgent.length() + 128);
header += type;
header += ' ';
if (_uri.length()) {
header += _uri;
} else {
header += '/';
}
header += F(" HTTP/1.");
if(_useHTTP10) {
header += "0";
header += '0';
} else {
header += "1";
header += '1';
}
header += String(F("\r\nHost: ")) + _host;
header += F("\r\nHost: ");
header += _host;
if (_port != 80 && _port != 443)
{
header += ':';
header += String(_port);
}
header += String(F("\r\nUser-Agent: ")) + _userAgent +
F("\r\nConnection: ");
header += F("\r\nUser-Agent: ");
header += _userAgent;
if(_reuse) {
header += F("keep-alive");
} else {
header += F("close");
if (!_useHTTP10) {
header += F("\r\nAccept-Encoding: identity;q=1,chunked;q=0.1,*;q=0");
}
if (_base64Authorization.length()) {
header += F("\r\nAuthorization: Basic ");
header += _base64Authorization;
}
header += F("\r\nConnection: ");
header += _reuse ? F("keep-alive") : F("close");
header += "\r\n";
if(!_useHTTP10) {
header += F("Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n");
}
if(_base64Authorization.length()) {
_base64Authorization.replace("\n", "");
header += F("Authorization: Basic ");
header += _base64Authorization;
header += "\r\n";
}
header += _headers + "\r\n";
header += _headers;
header += "\r\n";
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
@@ -1278,20 +1302,23 @@ int HTTPClient::handleHeaderResponse()
size_t len = _client->available();
if(len > 0) {
String headerLine = _client->readStringUntil('\n');
headerLine.trim(); // remove \r
lastDataTime = millis();
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());
if(headerLine.startsWith("HTTP/1.")) {
if(_canReuse) {
if (headerLine.startsWith(F("HTTP/1."))) {
if (_canReuse) {
_canReuse = (headerLine[sizeof "HTTP/1." - 1] != '0');
}
_returnCode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt();
} else if(headerLine.indexOf(':')) {
String headerName = headerLine.substring(0, headerLine.indexOf(':'));
String headerValue = headerLine.substring(headerLine.indexOf(':') + 1);
continue;
}
int headerSeparator = headerLine.indexOf(':');
if (headerSeparator > 0) {
String headerName = headerLine.substring(0, headerSeparator);
String headerValue = headerLine.substring(headerSeparator + 1);
headerValue.trim();
if(headerName.equalsIgnoreCase(F("Content-Length"))) {
@@ -1299,7 +1326,8 @@ int HTTPClient::handleHeaderResponse()
}
if(_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
if(headerValue.indexOf("close") >= 0 && headerValue.indexOf("keep-alive") < 0) {
if (headerValue.indexOf(F("close")) >= 0 &&
headerValue.indexOf(F("keep-alive")) < 0) {
_canReuse = false;
}
}
@@ -1312,20 +1340,24 @@ int HTTPClient::handleHeaderResponse()
_location = headerValue;
}
for(size_t i = 0; i < _headerKeysCount; i++) {
if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
if (_currentHeaders[i].value != "") {
for (size_t i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
if (!_currentHeaders[i].value.isEmpty()) {
// Existing value, append this one with a comma
_currentHeaders[i].value += "," + headerValue;
_currentHeaders[i].value += ',';
_currentHeaders[i].value += headerValue;
} else {
_currentHeaders[i].value = headerValue;
}
break; // We found a match, stop looking
}
}
continue;
}
if(headerLine == "") {
headerLine.trim(); // remove \r
if (headerLine.isEmpty()) {
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode);
if(_size > 0) {

View File

@@ -147,8 +147,8 @@ public:
* Since both begin() functions take a reference to client as a parameter, you need to
* ensure the client object lives the entire time of the HTTPClient
*/
bool begin(WiFiClient &client, String url);
bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);
bool begin(WiFiClient &client, const String& url);
bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);
#if HTTPCLIENT_1_1_COMPATIBLE
// Plain HTTP connection, unencrypted
@@ -175,20 +175,20 @@ public:
void setTimeout(uint16_t timeout);
void setFollowRedirects(bool follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(String url); // handy for handling redirects
bool setURL(const String& url); // handy for handling redirects
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int POST(uint8_t * payload, size_t size);
int POST(String payload);
int PUT(uint8_t * payload, size_t size);
int PUT(String payload);
int PATCH(uint8_t * payload, size_t size);
int PATCH(String payload);
int sendRequest(const char * type, String payload);
int sendRequest(const char * type, uint8_t * payload = NULL, size_t size = 0);
int sendRequest(const char * type, Stream * stream, size_t size = 0);
int POST(const uint8_t* payload, size_t size);
int POST(const String& payload);
int PUT(const uint8_t* payload, size_t size);
int PUT(const String& payload);
int PATCH(const uint8_t* payload, size_t size);
int PATCH(const String& payload);
int sendRequest(const char* type, const String& payload);
int sendRequest(const char* type, const uint8_t* payload = NULL, size_t size = 0);
int sendRequest(const char* type, Stream * stream, size_t size = 0);
void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
@@ -216,7 +216,7 @@ protected:
String value;
};
bool beginInternal(String url, const char* expectedProtocol);
bool beginInternal(const String& url, const char* expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);

View File

@@ -5,7 +5,6 @@
#include <WiFiUdp.h>
#include <flash_hal.h>
#include <FS.h>
#include <LittleFS.h>
#include "StreamString.h"
#include "ESP8266HTTPUpdateServer.h"
@@ -22,12 +21,12 @@ static const char serverIndex[] PROGMEM =
<body>
<form method='POST' action='' enctype='multipart/form-data'>
Firmware:<br>
<input type='file' accept='.bin' name='firmware'>
<input type='file' accept='.bin,.bin.gz' name='firmware'>
<input type='submit' value='Update Firmware'>
</form>
<form method='POST' action='' enctype='multipart/form-data'>
FileSystem:<br>
<input type='file' accept='.bin' name='filesystem'>
<input type='file' accept='.bin,.bin.gz' name='filesystem'>
<input type='submit' value='Update FileSystem'>
</form>
</body>
@@ -78,7 +77,7 @@ void ESP8266HTTPUpdateServerTemplate<ServerType>::setup(ESP8266WebServerTemplate
HTTPUpload& upload = _server->upload();
if(upload.status == UPLOAD_FILE_START){
_updaterError = String();
_updaterError.clear();
if (_serial_output)
Serial.setDebugOutput(true);
@@ -94,8 +93,7 @@ void ESP8266HTTPUpdateServerTemplate<ServerType>::setup(ESP8266WebServerTemplate
Serial.printf("Update: %s\n", upload.filename.c_str());
if (upload.name == "filesystem") {
size_t fsSize = ((size_t) &_FS_end - (size_t) &_FS_start);
SPIFFS.end();
LittleFS.end();
close_all_fs();
if (!Update.begin(fsSize, U_FS)){//start with max available size
if (_serial_output) Update.printError(Serial);
}

View File

@@ -69,11 +69,11 @@ static const char _ssdp_notify_template[] PROGMEM =
static const char _ssdp_packet_template[] PROGMEM =
"%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL
"CACHE-CONTROL: max-age=%u\r\n" // _interval
"SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber
"USN: %s\r\n" // _uuid
"%s: %s\r\n" // "NT" or "ST", _deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
"LOCATION: http://%s:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
"\r\n";
static const char _ssdp_schema_template[] PROGMEM =
@@ -88,7 +88,7 @@ static const char _ssdp_schema_template[] PROGMEM =
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/</URLBase>" // WiFi.localIP(), _port
"<URLBase>http://%s:%u/</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
@@ -130,6 +130,7 @@ SSDPClass::SSDPClass() :
_timer(0),
_port(80),
_ttl(SSDP_MULTICAST_TTL),
_interval(SSDP_INTERVAL),
_respondToAddr(0,0,0,0),
_respondToPort(0),
_pending(false),
@@ -241,13 +242,13 @@ void SSDPClass::_send(ssdp_method_t method) {
int len = snprintf_P(buffer, sizeof(buffer),
_ssdp_packet_template,
valueBuffer,
SSDP_INTERVAL,
_interval,
_modelName,
_modelNumber,
_uuid,
(method == NONE) ? "ST" : "NT",
(_st_is_uuid) ? _uuid : _deviceType,
ip[0], ip[1], ip[2], ip[3], _port, _schemaURL
ip.toString().c_str(), _port, _schemaURL
);
_server->append(buffer, len);
@@ -276,12 +277,12 @@ void SSDPClass::_send(ssdp_method_t method) {
_server->send(remoteAddr, remotePort);
}
void SSDPClass::schema(WiFiClient client) {
void SSDPClass::schema(Print &client) const {
IPAddress ip = WiFi.localIP();
char buffer[strlen_P(_ssdp_schema_template) + 1];
strcpy_P(buffer, _ssdp_schema_template);
client.printf(buffer,
ip[0], ip[1], ip[2], ip[3], _port,
ip.toString().c_str(), _port,
_deviceType,
_friendlyName,
_presentationURL,
@@ -428,7 +429,7 @@ void SSDPClass::_update() {
if (_pending && (millis() - _process_time) > _delay) {
_pending = false; _delay = 0;
_send(NONE);
} else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){
} else if(_notify_time == 0 || (millis() - _notify_time) > (_interval * 1000L)){
_notify_time = millis();
_st_is_uuid = false;
_send(NOTIFY);
@@ -493,11 +494,14 @@ void SSDPClass::setManufacturerURL(const char *url) {
strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL));
}
void SSDPClass::setTTL(const uint8_t ttl) {
_ttl = ttl;
}
void SSDPClass::setInterval(uint32_t interval) {
_interval = interval;
}
void SSDPClass::_onTimerStatic(SSDPClass* self) {
self->_update();
}

View File

@@ -62,7 +62,8 @@ class SSDPClass{
~SSDPClass();
bool begin();
void end();
void schema(WiFiClient client);
void schema(WiFiClient client) const { schema((Print&)std::ref(client)); }
void schema(Print &print) const;
void setDeviceType(const String& deviceType) { setDeviceType(deviceType.c_str()); }
void setDeviceType(const char *deviceType);
@@ -70,13 +71,13 @@ class SSDPClass{
void setUUID(const String& uuid) { setUUID(uuid.c_str()); }
void setUUID(const char *uuid);
void setName(const String& name) { setName(name.c_str()); }
void setName(const String& name) { setName(name.c_str()); }
void setName(const char *name);
void setURL(const String& url) { setURL(url.c_str()); }
void setURL(const char *url);
void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); }
void setSchemaURL(const char *url);
void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); }
void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); }
void setSerialNumber(const char *serialNumber);
void setSerialNumber(const uint32_t serialNumber);
void setModelName(const String& name) { setModelName(name.c_str()); }
@@ -91,6 +92,7 @@ class SSDPClass{
void setManufacturerURL(const char *url);
void setHTTPPort(uint16_t port);
void setTTL(uint8_t ttl);
void setInterval(uint32_t interval);
protected:
void _send(ssdp_method_t method);
@@ -103,6 +105,7 @@ class SSDPClass{
SSDPTimer* _timer;
uint16_t _port;
uint8_t _ttl;
uint32_t _interval;
IPAddress _respondToAddr;
uint16_t _respondToPort;

View File

@@ -96,7 +96,7 @@ Getting information about request arguments
int args();
bool hasArg();
``arg`` - get request argument value
``arg`` - get request argument value, use ``arg("plain")`` to get POST body
``argName`` - get request argument name

View File

@@ -94,6 +94,25 @@ void handleNotFound() {
digitalWrite(led, 0);
}
void drawGraph() {
String out;
out.reserve(2600);
char temp[70];
out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
out += "<g stroke=\"black\">\n";
int y = rand() % 130;
for (int x = 10; x < 390; x += 10) {
int y2 = rand() % 130;
sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
out += temp;
y = y2;
}
out += "</g>\n</svg>\n";
server.send(200, "image/svg+xml", out);
}
void setup(void) {
pinMode(led, OUTPUT);
digitalWrite(led, 0);
@@ -133,20 +152,3 @@ void loop(void) {
MDNS.update();
}
void drawGraph() {
String out = "";
char temp[100];
out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
out += "<g stroke=\"black\">\n";
int y = rand() % 130;
for (int x = 10; x < 390; x += 10) {
int y2 = rand() % 130;
sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
out += temp;
y = y2;
}
out += "</g>\n</svg>\n";
server.send(200, "image/svg+xml", out);
}

View File

@@ -122,7 +122,7 @@ void handleFileUpload() {
}
DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
fsUploadFile = filesystem->open(filename, "w");
filename = String();
filename.clear();
} else if (upload.status == UPLOAD_FILE_WRITE) {
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
if (fsUploadFile) {
@@ -150,7 +150,7 @@ void handleFileDelete() {
}
filesystem->remove(path);
server.send(200, "text/plain", "");
path = String();
path.clear();
}
void handleFileCreate() {
@@ -172,7 +172,7 @@ void handleFileCreate() {
return server.send(500, "text/plain", "CREATE FAILED");
}
server.send(200, "text/plain", "");
path = String();
path.clear();
}
void handleFileList() {
@@ -184,7 +184,7 @@ void handleFileList() {
String path = server.arg("dir");
DBG_OUTPUT_PORT.println("handleFileList: " + path);
Dir dir = filesystem->openDir(path);
path = String();
path.clear();
String output = "[";
while (dir.next()) {
@@ -275,13 +275,13 @@ void setup(void) {
//get heap status, analog input value and all GPIO statuses in one json call
server.on("/all", HTTP_GET, []() {
String json = "{";
String json('{');
json += "\"heap\":" + String(ESP.getFreeHeap());
json += ", \"analog\":" + String(analogRead(A0));
json += ", \"gpio\":" + String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
json += "}";
server.send(200, "text/json", json);
json = String();
json.clear();
});
server.begin();
DBG_OUTPUT_PORT.println("HTTP server started");

View File

@@ -67,6 +67,23 @@ void setup(void) {
server.send(200, "text/plain", "this works as well");
});
server.on("/gif", []() {
static const uint8_t gif[] PROGMEM = {
0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x01,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x19, 0x8c, 0x8f, 0xa9, 0xcb, 0x9d,
0x00, 0x5f, 0x74, 0xb4, 0x56, 0xb0, 0xb0, 0xd2, 0xf2, 0x35, 0x1e, 0x4c,
0x0c, 0x24, 0x5a, 0xe6, 0x89, 0xa6, 0x4d, 0x01, 0x00, 0x3b
};
char gif_colored[sizeof(gif)];
memcpy_P(gif_colored, gif, sizeof(gif));
// Set the background to a random set of colors
gif_colored[16] = millis() % 256;
gif_colored[17] = millis() % 256;
gif_colored[18] = millis() % 256;
server.send(200, "image/gif", gif_colored, sizeof(gif_colored));
});
server.onNotFound(handleNotFound);
server.begin();

View File

@@ -0,0 +1,61 @@
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <uri/UriBraces.h>
#include <uri/UriRegex.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *password = STAPSK;
ESP8266WebServer server(80);
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
server.on(F("/"), []() {
server.send(200, "text/plain", "hello from esp8266!");
});
server.on(UriBraces("/users/{}"), []() {
String user = server.pathArg(0);
server.send(200, "text/plain", "User: '" + user + "'");
});
server.on(UriRegex("^\\/users\\/([0-9]+)\\/devices\\/([0-9]+)$"), []() {
String user = server.pathArg(0);
String device = server.pathArg(1);
server.send(200, "text/plain", "User: '" + user + "' and Device: '" + device + "'");
});
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
}

View File

@@ -0,0 +1,126 @@
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
ESP8266WebServer server(80);
const int led = LED_BUILTIN;
const String postForms = "<html>\
<head>\
<title>ESP8266 Web Server POST handling</title>\
<style>\
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
</style>\
</head>\
<body>\
<h1>POST plain text to /postplain/</h1><br>\
<form method=\"post\" enctype=\"text/plain\" action=\"/postplain/\">\
<input type=\"text\" name=\'{\"hello\": \"world\", \"trash\": \"\' value=\'\"}\'><br>\
<input type=\"submit\" value=\"Submit\">\
</form>\
<h1>POST form data to /postform/</h1><br>\
<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postform/\">\
<input type=\"text\" name=\"hello\" value=\"world\"><br>\
<input type=\"submit\" value=\"Submit\">\
</form>\
</body>\
</html>";
void handleRoot() {
digitalWrite(led, 1);
server.send(200, "text/html", postForms);
digitalWrite(led, 0);
}
void handlePlain() {
if (server.method() != HTTP_POST) {
digitalWrite(led, 1);
server.send(405, "text/plain", "Method Not Allowed");
digitalWrite(led, 0);
} else {
digitalWrite(led, 1);
server.send(200, "text/plain", "POST body was:\n" + server.arg("plain"));
digitalWrite(led, 0);
}
}
void handleForm() {
if (server.method() != HTTP_POST) {
digitalWrite(led, 1);
server.send(405, "text/plain", "Method Not Allowed");
digitalWrite(led, 0);
} else {
digitalWrite(led, 1);
String message = "POST form was:\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(200, "text/plain", message);
digitalWrite(led, 0);
}
}
void handleNotFound() {
digitalWrite(led, 1);
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
digitalWrite(led, 0);
}
void setup(void) {
pinMode(led, OUTPUT);
digitalWrite(led, 0);
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
server.on("/", handleRoot);
server.on("/postplain/", handlePlain);
server.on("/postform/", handleForm);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
}

View File

@@ -209,7 +209,7 @@ void printDirectory() {
return returnFail("BAD PATH");
}
File dir = SD.open((char *)path.c_str());
path = String();
path.clear();
if (!dir.isDirectory()) {
dir.close();
return returnFail("NOT DIR");
@@ -241,6 +241,7 @@ void printDirectory() {
entry.close();
}
server.sendContent("]");
server.sendContent(""); // Terminate the HTTP chunked transmission with a 0-length chunk
dir.close();
}

View File

@@ -252,28 +252,28 @@ void ESP8266WebServerTemplate<ServerType>::requestAuthentication(HTTPAuthMethod
_srealm = String(realm);
}
if(mode == BASIC_AUTH) {
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String('\"'));
} else {
_snonce=_getRandomHexString();
_sopaque=_getRandomHexString();
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\"")));
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String('\"'));
}
using namespace mime;
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::on(const String &uri, ESP8266WebServerTemplate<ServerType>::THandlerFunction handler) {
void ESP8266WebServerTemplate<ServerType>::on(const Uri &uri, ESP8266WebServerTemplate<ServerType>::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::on(const String &uri, HTTPMethod method, ESP8266WebServerTemplate<ServerType>::THandlerFunction fn) {
void ESP8266WebServerTemplate<ServerType>::on(const Uri &uri, HTTPMethod method, ESP8266WebServerTemplate<ServerType>::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::on(const String &uri, HTTPMethod method, ESP8266WebServerTemplate<ServerType>::THandlerFunction fn, ESP8266WebServerTemplate<ServerType>::THandlerFunction ufn) {
void ESP8266WebServerTemplate<ServerType>::on(const Uri &uri, HTTPMethod method, ESP8266WebServerTemplate<ServerType>::THandlerFunction fn, ESP8266WebServerTemplate<ServerType>::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler<ServerType>(fn, ufn, uri, method));
}
@@ -319,7 +319,7 @@ void ESP8266WebServerTemplate<ServerType>::handleClient() {
bool keepCurrentClient = false;
bool callYield = false;
if (_currentClient.connected()) {
if (_currentClient.connected() || _currentClient.available()) {
switch (_currentStatus) {
case HC_NONE:
// No-op to avoid C++ compiler warning
@@ -464,8 +464,10 @@ void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type,
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if (contentLength) {
sendContent_P(content, contentLength);
}
}
template <typename ServerType>
@@ -524,13 +526,13 @@ String ESP8266WebServerTemplate<ServerType>::credentialHash(const String& userna
{
MD5Builder md5;
md5.begin();
md5.add(username + ":" + realm + ":" + password); // md5 of the user:realm:password
md5.add(username + ':' + realm + ':' + password); // md5 of the user:realm:password
md5.calculate();
return md5.toString();
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType)
void ESP8266WebServerTemplate<ServerType>::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType)
{
using namespace mime;
setContentLength(fileSize);
@@ -542,9 +544,15 @@ void ESP8266WebServerTemplate<ServerType>::_streamFileCore(const size_t fileSize
send(200, contentType, emptyString);
}
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::pathArg(unsigned int i) const {
if (_currentHandler != nullptr)
return _currentHandler->pathArg(i);
return emptyString;
}
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::arg(String name) const {
const String& ESP8266WebServerTemplate<ServerType>::arg(const String& name) const {
for (int j = 0; j < _postArgsLen; ++j) {
if ( _postArgs[j].key == name )
return _postArgs[j].value;
@@ -590,7 +598,7 @@ bool ESP8266WebServerTemplate<ServerType>::hasArg(const String& name) const {
template <typename ServerType>
const String& ESP8266WebServerTemplate<ServerType>::header(String name) const {
const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) const {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
@@ -630,7 +638,7 @@ int ESP8266WebServerTemplate<ServerType>::headers() const {
}
template <typename ServerType>
bool ESP8266WebServerTemplate<ServerType>::hasHeader(String name) const {
bool ESP8266WebServerTemplate<ServerType>::hasHeader(const String& name) const {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
@@ -693,49 +701,136 @@ void ESP8266WebServerTemplate<ServerType>::_finalizeResponse() {
}
template <typename ServerType>
const String ESP8266WebServerTemplate<ServerType>::responseCodeToString(const int code) {
switch (code) {
case 100: return F("Continue");
case 101: return F("Switching Protocols");
case 200: return F("OK");
case 201: return F("Created");
case 202: return F("Accepted");
case 203: return F("Non-Authoritative Information");
case 204: return F("No Content");
case 205: return F("Reset Content");
case 206: return F("Partial Content");
case 300: return F("Multiple Choices");
case 301: return F("Moved Permanently");
case 302: return F("Found");
case 303: return F("See Other");
case 304: return F("Not Modified");
case 305: return F("Use Proxy");
case 307: return F("Temporary Redirect");
case 400: return F("Bad Request");
case 401: return F("Unauthorized");
case 402: return F("Payment Required");
case 403: return F("Forbidden");
case 404: return F("Not Found");
case 405: return F("Method Not Allowed");
case 406: return F("Not Acceptable");
case 407: return F("Proxy Authentication Required");
case 408: return F("Request Time-out");
case 409: return F("Conflict");
case 410: return F("Gone");
case 411: return F("Length Required");
case 412: return F("Precondition Failed");
case 413: return F("Request Entity Too Large");
case 414: return F("Request-URI Too Large");
case 415: return F("Unsupported Media Type");
case 416: return F("Requested range not satisfiable");
case 417: return F("Expectation Failed");
case 418: return F("I'm a teapot");
case 500: return F("Internal Server Error");
case 501: return F("Not Implemented");
case 502: return F("Bad Gateway");
case 503: return F("Service Unavailable");
case 504: return F("Gateway Time-out");
case 505: return F("HTTP Version not supported");
default: return F("");
}
String ESP8266WebServerTemplate<ServerType>::responseCodeToString(const int code) {
// By first determining the pointer to the flash stored string in the switch
// statement and then doing String(FlashStringHelper) return reduces the total code
// size of this function by over 50%.
const __FlashStringHelper *r;
switch (code)
{
case 100:
r = F("Continue");
break;
case 101:
r = F("Switching Protocols");
break;
case 200:
r = F("OK");
break;
case 201:
r = F("Created");
break;
case 202:
r = F("Accepted");
break;
case 203:
r = F("Non-Authoritative Information");
break;
case 204:
r = F("No Content");
break;
case 205:
r = F("Reset Content");
break;
case 206:
r = F("Partial Content");
break;
case 300:
r = F("Multiple Choices");
break;
case 301:
r = F("Moved Permanently");
break;
case 302:
r = F("Found");
break;
case 303:
r = F("See Other");
break;
case 304:
r = F("Not Modified");
break;
case 305:
r = F("Use Proxy");
break;
case 307:
r = F("Temporary Redirect");
break;
case 400:
r = F("Bad Request");
break;
case 401:
r = F("Unauthorized");
break;
case 402:
r = F("Payment Required");
break;
case 403:
r = F("Forbidden");
break;
case 404:
r = F("Not Found");
break;
case 405:
r = F("Method Not Allowed");
break;
case 406:
r = F("Not Acceptable");
break;
case 407:
r = F("Proxy Authentication Required");
break;
case 408:
r = F("Request Timeout");
break;
case 409:
r = F("Conflict");
break;
case 410:
r = F("Gone");
break;
case 411:
r = F("Length Required");
break;
case 412:
r = F("Precondition Failed");
break;
case 413:
r = F("Request Entity Too Large");
break;
case 414:
r = F("URI Too Long");
break;
case 415:
r = F("Unsupported Media Type");
break;
case 416:
r = F("Range not satisfiable");
break;
case 417:
r = F("Expectation Failed");
break;
case 500:
r = F("Internal Server Error");
break;
case 501:
r = F("Not Implemented");
break;
case 502:
r = F("Bad Gateway");
break;
case 503:
r = F("Service Unavailable");
break;
case 504:
r = F("Gateway Timeout");
break;
case 505:
r = F("HTTP Version not supported");
break;
default:
r = F("");
break;
}
return String(r);
}

View File

@@ -29,6 +29,7 @@
#include <ESP8266WiFi.h>
#include <FS.h>
#include "detail/mimetable.h"
#include "Uri.h"
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
@@ -91,9 +92,9 @@ public:
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
typedef std::function<void(void)> THandlerFunction;
void on(const String &uri, THandlerFunction handler);
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void on(const Uri &uri, THandlerFunction handler);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void addHandler(RequestHandlerType* handler);
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
void onNotFound(THandlerFunction fn); //called when handler is not assigned
@@ -107,17 +108,18 @@ public:
// Allows setting server options (i.e. SSL keys) by the instantiator
ServerType &getServer() { return _server; }
const String& arg(String name) const; // get request argument value by name
const String& pathArg(unsigned int i) const; // get request path argument by number
const String& arg(const String& name) const; // get request argument value by name
const String& arg(int i) const; // get request argument value by number
const String& argName(int i) const; // get request argument name by number
int args() const; // get arguments count
bool hasArg(const String& name) const; // check if argument exists
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
const String& header(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& headerName(int i) const; // get request header name by number
int headers() const; // get header count
bool hasHeader(String name) const; // check if header exists
bool hasHeader(const String& name) const; // check if header exists
const String& hostHeader() const; // get request host header if available or empty String if not
// send response to the client
@@ -127,6 +129,15 @@ public:
void send(int code, const char* content_type = NULL, const String& content = String(""));
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send(int code, const char *content_type, const char *content) {
send_P(code, content_type, content);
}
void send(int code, const char *content_type, const char *content, size_t content_length) {
send_P(code, content_type, content, content_length);
}
void send(int code, const char *content_type, const uint8_t *content, size_t content_length) {
send_P(code, content_type, (const char *)content, content_length);
}
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);
@@ -142,13 +153,25 @@ public:
static String urlDecode(const String& text);
// Handle a GET request by sending a response header and stream file content to response body
template<typename T>
size_t streamFile(T &file, const String& contentType) {
_streamFileCore(file.size(), file.name(), contentType);
return _currentClient.write(file);
return streamFile(file, contentType, HTTP_GET);
}
static const String responseCodeToString(const int code);
// Implement GET and HEAD requests for files.
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t streamFile(T &file, const String& contentType, HTTPMethod requestMethod) {
size_t contentLength = 0;
_streamFileCore(file.size(), file.name(), contentType);
if (requestMethod == HTTP_GET) {
contentLength = _currentClient.write(file);
}
return contentLength;
}
static String responseCodeToString(const int code);
protected:
void _addRequestHandler(RequestHandlerType* handler);

View File

@@ -89,7 +89,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
String searchStr;
int hasSearch = url.indexOf('?');
if (hasSearch != -1){
searchStr = url.substring(hasSearch + 1);
@@ -144,7 +144,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
if (req.isEmpty()) break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
@@ -222,7 +222,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
if (req.isEmpty()) break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
@@ -452,7 +452,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--"+boundary)) break;
if (argValue.length() > 0) argValue += "\n";
if (argValue.length() > 0) argValue += '\n';
argValue += line;
}
#ifdef DEBUG_ESP_HTTP_SERVER
@@ -600,7 +600,7 @@ readfile:
template <typename ServerType>
String ESP8266WebServerTemplate<ServerType>::urlDecode(const String& text)
{
String decoded = "";
String decoded;
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;

View File

@@ -0,0 +1,27 @@
#ifndef URI_H
#define URI_H
#include <Arduino.h>
#include <vector>
class Uri {
protected:
const String _uri;
public:
Uri(const char *uri) : _uri(uri) {}
Uri(const String &uri) : _uri(uri) {}
Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {}
virtual ~Uri() {}
virtual Uri* clone() const {
return new Uri(_uri);
};
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
return _uri == requestUri;
}
};
#endif

View File

@@ -2,6 +2,8 @@
#define REQUESTHANDLER_H
#include <ESP8266WebServer.h>
#include <vector>
#include <assert.h>
template<typename ServerType>
class RequestHandler {
@@ -18,6 +20,15 @@ public:
private:
RequestHandler<ServerType>* _next = nullptr;
protected:
std::vector<String> pathArgs;
public:
const String& pathArg(unsigned int i) {
assert(i < pathArgs.size());
return pathArgs[i];
}
};
#endif //REQUESTHANDLER_H

View File

@@ -5,6 +5,7 @@
#include "RequestHandler.h"
#include "mimetable.h"
#include "WString.h"
#include "Uri.h"
using namespace mime;
@@ -12,22 +13,23 @@ template<typename ServerType>
class FunctionRequestHandler : public RequestHandler<ServerType> {
using WebServerType = ESP8266WebServerTemplate<ServerType>;
public:
FunctionRequestHandler(typename WebServerType::THandlerFunction fn, typename WebServerType::THandlerFunction ufn, const String &uri, HTTPMethod method)
FunctionRequestHandler(typename WebServerType::THandlerFunction fn, typename WebServerType::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
: _fn(fn)
, _ufn(ufn)
, _uri(uri)
, _uri(uri.clone())
, _method(method)
{
}
~FunctionRequestHandler() {
delete _uri;
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (_method != HTTP_ANY && _method != requestMethod)
return false;
if (requestUri != _uri)
return false;
return true;
return _uri->canHandle(requestUri, RequestHandler<ServerType>::pathArgs);
}
bool canUpload(String requestUri) override {
@@ -56,7 +58,7 @@ public:
protected:
typename WebServerType::THandlerFunction _fn;
typename WebServerType::THandlerFunction _ufn;
String _uri;
Uri *_uri;
HTTPMethod _method;
};
@@ -70,13 +72,21 @@ public:
, _path(path)
, _cache_header(cache_header)
{
_isFile = fs.exists(path);
if (fs.exists(path)) {
File file = fs.open(path, "r");
_isFile = file && file.isFile();
file.close();
}
else {
_isFile = false;
}
DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
_baseUriLength = _uri.length();
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD))
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
@@ -96,11 +106,17 @@ public:
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/"))
if (requestUri.endsWith("/"))
requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
// If neither <blah> nor <blah>.gz exist, and <blah> is a file.htm, try it with file.html instead
// For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz
if (!_fs.exists(path) && !_fs.exists(path + ".gz") && path.endsWith(".htm")) {
path += "l";
}
}
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
@@ -118,10 +134,15 @@ public:
if (!f)
return false;
if (!f.isFile()) {
f.close();
return false;
}
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
server.streamFile(f, contentType, requestMethod);
return true;
}

View File

@@ -0,0 +1,54 @@
#ifndef URI_BRACES_H
#define URI_BRACES_H
#include "Uri.h"
class UriBraces : public Uri {
public:
explicit UriBraces(const char *uri) : Uri(uri) {};
explicit UriBraces(const String &uri) : Uri(uri) {};
Uri* clone() const override final {
return new UriBraces(_uri);
};
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
pathArgs.clear();
size_t uriLength = _uri.length();
unsigned int requestUriIndex = 0;
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
char uriChar = _uri[i];
char requestUriChar = requestUri[requestUriIndex];
if (uriChar == requestUriChar)
continue;
if (uriChar != '{')
return false;
i += 2; // index of char after '}'
if (i >= uriLength) {
// there is no char after '}'
pathArgs.push_back(requestUri.substring(requestUriIndex));
return pathArgs.back().indexOf("/") == -1; // path argument may not contain a '/'
}
else
{
char charEnd = _uri[i];
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
if (uriIndex < 0)
return false;
pathArgs.push_back(requestUri.substring(requestUriIndex, uriIndex));
requestUriIndex = (unsigned int) uriIndex;
}
}
return requestUriIndex >= requestUri.length();
}
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef URI_GLOB_H
#define URI_GLOB_H
#include "Uri.h"
#include <fnmatch.h>
class UriGlob : public Uri {
public:
explicit UriGlob(const char *uri) : Uri(uri) {};
explicit UriGlob(const String &uri) : Uri(uri) {};
Uri* clone() const override final {
return new UriGlob(_uri);
};
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
}
};
#endif

View File

@@ -0,0 +1,54 @@
#ifndef URI_REGEX_H
#define URI_REGEX_H
#include "Uri.h"
#include <regex.h>
#include <assert.h>
#ifndef REGEX_MAX_GROUPS
#define REGEX_MAX_GROUPS 10
#endif
class UriRegex : public Uri {
private:
regex_t _regexCompiled;
public:
explicit UriRegex(const char *uri) : Uri(uri) {
assert(regcomp(&_regexCompiled, uri, REG_EXTENDED) == 0);
};
explicit UriRegex(const String &uri) : UriRegex(uri.c_str()) {};
~UriRegex() {
regfree(&_regexCompiled);
}
Uri* clone() const override final {
return new UriRegex(_uri);
};
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
regmatch_t groupArray[REGEX_MAX_GROUPS];
if (regexec(&_regexCompiled, requestUri.c_str(), REGEX_MAX_GROUPS, groupArray, 0) == 0) {
// matches
pathArgs.clear();
unsigned int g = 1;
for (; g < REGEX_MAX_GROUPS; g++) {
if (groupArray[g].rm_so == (long int)-1)
break; // No more groups
pathArgs.push_back(requestUri.substring(groupArray[g].rm_so, groupArray[g].rm_eo));
}
return true;
}
return false;
}
};
#endif

View File

@@ -6,6 +6,7 @@
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <StackThunk.h>
#include <time.h>
#ifndef STASSID
@@ -81,7 +82,8 @@ void fetchURL(BearSSL::WiFiClientSecure *client, const char *host, const uint16_
}
client->stop();
uint32_t freeStackEnd = ESP.getFreeContStack();
Serial.printf("\nCONT stack used: %d\n-------\n\n", freeStackStart - freeStackEnd);
Serial.printf("\nCONT stack used: %d\n", freeStackStart - freeStackEnd);
Serial.printf("BSSL stack used: %d\n-------\n\n", stack_thunk_get_max_usage());
}
void fetchNoConfig() {

View File

@@ -85,8 +85,8 @@ void setup() {
Serial.swap();
// Hardware serial is now on RX:GPIO13 TX:GPIO15
// use SoftwareSerial on regular RX(3)/TX(1) for logging
logger = new SoftwareSerial();
logger->begin(BAUD_LOGGER, 3, 1);
logger = new SoftwareSerial(3, 1);
logger->begin(BAUD_LOGGER);
logger->enableIntTx(false);
logger->println("\n\nUsing SoftwareSerial for logging");
#else

View File

@@ -82,6 +82,10 @@ int CertStore::initCertStore(FS &fs, const char *indexFileName, const char *data
_fs = &fs;
// In case initCertStore called multiple times, don't leak old filenames
free(_indexName);
free(_dataName);
// No strdup_P, so manually do it
_indexName = (char *)malloc(strlen_P(indexFileName) + 1);
_dataName = (char *)malloc(strlen_P(dataFileName) + 1);

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,10 @@ bool ESP8266WiFiMulti::addAP(const char* ssid, const char *passphrase) {
return APlistAdd(ssid, passphrase);
}
void ESP8266WiFiMulti::cleanAPlist(void) {
APlistClean();
}
bool ESP8266WiFiMulti::existsAP(const char* ssid, const char *passphrase) {
return APlistExists(ssid, passphrase);
}

View File

@@ -57,6 +57,8 @@ class ESP8266WiFiMulti {
wl_status_t run(void);
void cleanAPlist(void);
private:
WifiAPlist APlist;
bool APlistAdd(const char* ssid, const char *passphrase = NULL);

View File

@@ -71,7 +71,7 @@ bool ESP8266WiFiSTAClass::beginWPSConfig(void) {
}
esp_yield();
// will return here when wifi_wps_status_cb fires
// will resume when wifi_wps_status_cb fires
return true;
}
@@ -107,5 +107,5 @@ void wifi_wps_status_cb(wps_cb_status status) {
}
// TODO user function to get status
esp_schedule(); // resume the beginWPSConfig function
esp_schedule(); // resume beginWPSConfig
}

View File

@@ -98,7 +98,7 @@ int8_t ESP8266WiFiScanClass::scanNetworks(bool async, bool show_hidden, uint8 ch
return WIFI_SCAN_RUNNING;
}
esp_yield();
esp_yield(); // will resume when _scanDone fires
return ESP8266WiFiScanClass::_scanCount;
} else {
return WIFI_SCAN_FAILED;
@@ -323,7 +323,7 @@ void ESP8266WiFiScanClass::_scanDone(void* result, int status) {
ESP8266WiFiScanClass::_scanComplete = true;
if(!ESP8266WiFiScanClass::_scanAsync) {
esp_schedule();
esp_schedule(); // resume scanNetworks
} else if (ESP8266WiFiScanClass::_onComplete) {
ESP8266WiFiScanClass::_onComplete(ESP8266WiFiScanClass::_scanCount);
ESP8266WiFiScanClass::_onComplete = nullptr;

View File

@@ -346,7 +346,7 @@ uint8_t WiFiClient::status()
WiFiClient::operator bool()
{
return connected();
return available() || connected();
}
IPAddress WiFiClient::remoteIP()

View File

@@ -35,12 +35,17 @@ extern "C" {
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip/inet.h"
#include "lwip/init.h" // LWIP_VERSION_
#include <include/ClientContext.h>
#ifndef MAX_PENDING_CLIENTS_PER_PORT
#define MAX_PENDING_CLIENTS_PER_PORT 5
#endif
WiFiServer::WiFiServer(const IPAddress& addr, uint16_t port)
: _port(port)
, _addr(addr)
, _pcb(nullptr)
, _listen_pcb(nullptr)
, _unclaimed(nullptr)
, _discarded(nullptr)
{
@@ -49,7 +54,7 @@ WiFiServer::WiFiServer(const IPAddress& addr, uint16_t port)
WiFiServer::WiFiServer(uint16_t port)
: _port(port)
, _addr(IP_ANY_TYPE)
, _pcb(nullptr)
, _listen_pcb(nullptr)
, _unclaimed(nullptr)
, _discarded(nullptr)
{
@@ -60,9 +65,14 @@ void WiFiServer::begin() {
}
void WiFiServer::begin(uint16_t port) {
return begin(port, MAX_PENDING_CLIENTS_PER_PORT);
}
void WiFiServer::begin(uint16_t port, uint8_t backlog) {
close();
if (!backlog)
return;
_port = port;
err_t err;
tcp_pcb* pcb = tcp_new();
if (!pcb)
return;
@@ -70,19 +80,23 @@ void WiFiServer::begin(uint16_t port) {
pcb->so_options |= SOF_REUSEADDR;
// (IPAddress _addr) operator-converted to (const ip_addr_t*)
err = tcp_bind(pcb, _addr, _port);
if (err != ERR_OK) {
if (tcp_bind(pcb, _addr, _port) != ERR_OK) {
tcp_close(pcb);
return;
}
#if LWIP_VERSION_MAJOR == 1
tcp_pcb* listen_pcb = tcp_listen(pcb);
#else
tcp_pcb* listen_pcb = tcp_listen_with_backlog(pcb, backlog);
#endif
if (!listen_pcb) {
tcp_close(pcb);
return;
}
_pcb = listen_pcb;
_listen_pcb = listen_pcb;
_port = _listen_pcb->local_port;
tcp_accept(listen_pcb, &WiFiServer::_s_accept);
tcp_arg(listen_pcb, (void*) this);
}
@@ -110,9 +124,15 @@ WiFiClient WiFiServer::available(byte* status) {
(void) status;
if (_unclaimed) {
WiFiClient result(_unclaimed);
#if LWIP_VERSION_MAJOR != 1
// pcb can be null when peer has already closed the connection
if (_unclaimed->getPCB())
// give permission to lwIP to accept one more peer
tcp_backlog_accepted(_unclaimed->getPCB());
#endif
_unclaimed = _unclaimed->next();
result.setNoDelay(getNoDelay());
DEBUGV("WS:av\r\n");
DEBUGV("WS:av status=%d WCav=%d\r\n", result.status(), result.available());
return result;
}
@@ -121,17 +141,21 @@ WiFiClient WiFiServer::available(byte* status) {
}
uint8_t WiFiServer::status() {
if (!_pcb)
if (!_listen_pcb)
return CLOSED;
return _pcb->state;
return _listen_pcb->state;
}
uint16_t WiFiServer::port() const {
return _port;
}
void WiFiServer::close() {
if (!_pcb) {
if (!_listen_pcb) {
return;
}
tcp_close(_pcb);
_pcb = nullptr;
tcp_close(_listen_pcb);
_listen_pcb = nullptr;
}
void WiFiServer::stop() {
@@ -164,9 +188,28 @@ T* slist_append_tail(T* head, T* item) {
long WiFiServer::_accept(tcp_pcb* apcb, long err) {
(void) err;
DEBUGV("WS:ac\r\n");
// always accept new PCB so incoming data can be stored in our buffers even before
// user calls ::available()
ClientContext* client = new ClientContext(apcb, &WiFiServer::_s_discard, this);
#if LWIP_VERSION_MAJOR == 1
tcp_accepted(_listen_pcb);
#else
// backlog doc:
// http://lwip.100.n7.nabble.com/Problem-re-opening-listening-pbc-tt32484.html#a32494
// https://www.nongnu.org/lwip/2_1_x/group__tcp__raw.html#gaeff14f321d1eecd0431611f382fcd338
// increase lwIP's backlog
tcp_backlog_delayed(apcb);
#endif
_unclaimed = slist_append_tail(_unclaimed, client);
tcp_accepted(_pcb);
return ERR_OK;
}

View File

@@ -31,6 +31,37 @@ extern "C" {
#include "Server.h"
#include "IPAddress.h"
// lwIP-v2 backlog facility allows to keep memory safe by limiting the
// maximum number of incoming *pending clients*. Default number of possibly
// simultaneously pending clients is defined in WiFiServer.cpp
// (MAX_PENDING_CLIENTS_PER_PORT=5). User can overide it at runtime from
// sketch:
// WiFiServer::begin(port, max-simultaneous-pending-clients);
//
// An "incoming pending" client is a new incoming TCP connection trying to
// reach the TCP server. It is "pending" until lwIP acknowledges it and
// "accepted / no more pending" when user calls WiFiServer::available().
//
// Before the backlog feature or with lwIP-v1.4, there was no pending
// connections: They were immediately accepted and filling RAM.
//
// Several pending clients can appear during the time when one client is
// served by a long not-async service like ESP8266WebServer. During that
// time WiFiServer::available() cannot be called.
//
// Note: This *does not limit* the number of *simultaneously accepted
// clients*. Such limit management is left to the user.
//
// Thus, when the maximum number of pending connections is reached, new
// connections are delayed.
// By "delayed", it is meant that WiFiServer(lwIP) will not answer to the
// SYN packet until there is room for a new one: The TCP server on that port
// will be mute. The TCP client will regularly try to connect until success
// or a timeout occurs (72s on windows).
//
// When user calls WiFiServer::available(), the tcp server stops muting and
// answers to newcomers (until the "backlog" pending list is full again).
class ClientContext;
class WiFiClient;
@@ -39,7 +70,7 @@ class WiFiServer : public Server {
protected:
uint16_t _port;
IPAddress _addr;
tcp_pcb* _pcb;
tcp_pcb* _listen_pcb;
ClientContext* _unclaimed;
ClientContext* _discarded;
@@ -53,11 +84,13 @@ public:
bool hasClient();
void begin();
void begin(uint16_t port);
void begin(uint16_t port, uint8_t backlog);
void setNoDelay(bool nodelay);
bool getNoDelay();
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buf, size_t size);
uint8_t status();
uint16_t port() const;
void close();
void stop();

View File

@@ -40,17 +40,22 @@ public:
_pcb(pcb), _rx_buf(0), _rx_buf_offset(0), _discard_cb(discard_cb), _discard_cb_arg(discard_cb_arg), _refcnt(0), _next(0),
_sync(::getDefaultPrivateGlobalSyncValue())
{
tcp_setprio(pcb, TCP_PRIO_MIN);
tcp_arg(pcb, this);
tcp_recv(pcb, &_s_recv);
tcp_sent(pcb, &_s_acked);
tcp_err(pcb, &_s_error);
tcp_poll(pcb, &_s_poll, 1);
tcp_setprio(_pcb, TCP_PRIO_MIN);
tcp_arg(_pcb, this);
tcp_recv(_pcb, &_s_recv);
tcp_sent(_pcb, &_s_acked);
tcp_err(_pcb, &_s_error);
tcp_poll(_pcb, &_s_poll, 1);
// keep-alive not enabled by default
//keepAlive();
}
tcp_pcb* getPCB ()
{
return _pcb;
}
err_t abort()
{
if(_pcb) {
@@ -130,10 +135,10 @@ public:
}
_connect_pending = true;
_op_start_time = millis();
// Following delay will be interrupted by connect callback
for (decltype(_timeout_ms) i = 0; _connect_pending && i < _timeout_ms; i++) {
// Give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
delay(1);
// will resume on timeout or when _connected or _notify_error fires
}
_connect_pending = false;
if (!_pcb) {
@@ -291,6 +296,7 @@ public:
void discard_received()
{
DEBUGV(":dsrcv %d\n", _rx_buf? _rx_buf->tot_len: 0);
if(!_rx_buf) {
return;
}
@@ -349,7 +355,8 @@ public:
uint8_t state() const
{
if(!_pcb) {
if(!_pcb || _pcb->state == CLOSE_WAIT || _pcb->state == CLOSING) {
// CLOSED for WiFIClient::status() means nothing more can be written
return CLOSED;
}
@@ -435,7 +442,7 @@ protected:
if (_connect_pending || _send_waiting) {
_send_waiting = false;
_connect_pending = false;
esp_schedule(); // break current delay()
esp_schedule(); // break delay in connect or _write_from_source
}
}
@@ -461,10 +468,11 @@ protected:
}
_send_waiting = true;
// Following delay will be interrupted by on next received ack
for (decltype(_timeout_ms) i = 0; _send_waiting && i < _timeout_ms; i++) {
// Give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
delay(1);
// will resume on timeout or when _write_some_from_cb or _notify_error fires
}
_send_waiting = false;
} while(true);
@@ -536,7 +544,7 @@ protected:
{
if (_send_waiting) {
_send_waiting = false;
esp_schedule(); // break current delay()
esp_schedule(); // break delay in _write_from_source
}
}
@@ -575,11 +583,23 @@ protected:
{
(void) pcb;
(void) err;
if(pb == 0) { // connection closed
DEBUGV(":rcl\r\n");
if(pb == 0) {
// connection closed by peer
DEBUGV(":rcl pb=%p sz=%d\r\n", _rx_buf, _rx_buf? _rx_buf->tot_len: -1);
_notify_error();
abort();
return ERR_ABRT;
if (_rx_buf && _rx_buf->tot_len)
{
// there is still something to read
return ERR_OK;
}
else
{
// nothing in receive buffer,
// peer closed = nothing can be written:
// closing in the legacy way
abort();
return ERR_ABRT;
}
}
if(_rx_buf) {
@@ -612,7 +632,7 @@ protected:
assert(pcb == _pcb);
if (_connect_pending) {
_connect_pending = false;
esp_schedule(); // break current delay()
esp_schedule(); // break delay in connect
}
return ERR_OK;
}

View File

@@ -47,6 +47,7 @@ public:
, _rx_buf(0)
, _first_buf_taken(false)
, _rx_buf_offset(0)
, _rx_buf_size(0)
, _refcnt(0)
, _tx_buf_head(0)
, _tx_buf_cur(0)
@@ -74,6 +75,7 @@ public:
pbuf_free(_rx_buf);
_rx_buf = 0;
_rx_buf_offset = 0;
_rx_buf_size = 0;
}
}
@@ -167,6 +169,26 @@ public:
#endif // !LWIP_IPV6
/*
* Add a netif (by its index) as the multicast interface
*/
void setMulticastInterface(netif* p_pNetIf)
{
#if LWIP_VERSION_MAJOR == 1
udp_set_multicast_netif_addr(_pcb, (p_pNetIf ? p_pNetIf->ip_addr : ip_addr_any));
#else
udp_set_multicast_netif_index(_pcb, (p_pNetIf ? netif_get_index(p_pNetIf) : NETIF_NO_INDEX));
#endif
}
/*
* Allow access to pcb to change eg. options
*/
udp_pcb* pcb(void)
{
return _pcb;
}
void setMulticastTTL(int ttl)
{
#ifdef LWIP_MAYBE_XCC
@@ -182,12 +204,36 @@ public:
_on_rx = handler;
}
#ifdef DEBUG_ESP_CORE
// this helper is ready to be used when debugging UDP
void printChain (const pbuf* pb, const char* msg, size_t n) const
{
// printf the pb pbuf chain, bufferred and all at once
char buf[128];
int l = snprintf(buf, sizeof(buf), "UDP: %s %u: ", msg, n);
while (pb)
{
l += snprintf(&buf[l], sizeof(buf) -l, "%p(H=%d,%d<=%d)-",
pb, pb->flags == PBUF_HELPER_FLAG, pb->len, pb->tot_len);
pb = pb->next;
}
l += snprintf(&buf[l], sizeof(buf) - l, "(end)");
DEBUGV("%s\n", buf);
}
#else
void printChain (const pbuf* pb, const char* msg) const
{
(void)pb;
(void)msg;
}
#endif
size_t getSize() const
{
if (!_rx_buf)
return 0;
return _rx_buf->len - _rx_buf_offset;
return _rx_buf_size - _rx_buf_offset;
}
size_t tell() const
@@ -202,7 +248,12 @@ public:
}
bool isValidOffset(const size_t pos) const {
return (pos <= _rx_buf->len);
return (pos <= _rx_buf_size);
}
netif* getInputNetif() const
{
return _currentAddr.input_netif;
}
CONST IPAddress& getRemoteAddress() CONST
@@ -237,46 +288,57 @@ public:
return true;
}
// We have interleaved informations on addresses within received pbuf chain:
// (before ipv6 code we had: (data-pbuf) -> (data-pbuf) -> (data-pbuf) -> ... in the receiving order)
// Now: (address-info-pbuf -> chained-data-pbuf [-> chained-data-pbuf...]) ->
// (chained-address-info-pbuf -> chained-data-pbuf [-> chained...]) -> ...
// _rx_buf is currently adressing a data pbuf,
// in this function it is going to be discarded.
auto deleteme = _rx_buf;
_rx_buf = _rx_buf->next;
// forward in the chain until next address-info pbuf or end of chain
while(_rx_buf && _rx_buf->flags != PBUF_HELPER_FLAG)
_rx_buf = _rx_buf->next;
if (_rx_buf)
{
if (_rx_buf->flags == PBUF_HELPER_FLAG)
{
// we have interleaved informations on addresses within reception pbuf chain:
// before: (data-pbuf) -> (data-pbuf) -> (data-pbuf) -> ... in the receiving order
// now: (address-info-pbuf -> data-pbuf) -> (address-info-pbuf -> data-pbuf) -> ...
assert(_rx_buf->flags == PBUF_HELPER_FLAG);
// so the first rx_buf contains an address helper,
// copy it to "current address"
auto helper = (AddrHelper*)PBUF_ALIGNER(_rx_buf->payload);
_currentAddr = *helper;
// copy address helper to "current address"
auto helper = (AddrHelper*)PBUF_ALIGNER(_rx_buf->payload);
_currentAddr = *helper;
// destroy the helper in the about-to-be-released pbuf
helper->~AddrHelper();
// destroy the helper in the about-to-be-released pbuf
helper->~AddrHelper();
// forward in rx_buf list, next one is effective data
// current (not ref'ed) one will be pbuf_free'd with deleteme
_rx_buf = _rx_buf->next;
}
// forward in rx_buf list, next one is effective data
// current (not ref'ed) one will be pbuf_free'd
// with the 'deleteme' pointer above
_rx_buf = _rx_buf->next;
// this rx_buf is not nullptr by construction,
assert(_rx_buf);
// ref'ing it to prevent release from the below pbuf_free(deleteme)
// (ref counter prevents release and will be decreased by pbuf_free)
pbuf_ref(_rx_buf);
}
// release in chain previous data, and if any:
// current helper, but not start of current data
pbuf_free(deleteme);
_rx_buf_offset = 0;
_rx_buf_size = _processSize(_rx_buf);
return _rx_buf != nullptr;
}
int read()
{
if (!_rx_buf || _rx_buf_offset >= _rx_buf->len)
if (!_rx_buf || _rx_buf_offset >= _rx_buf_size)
return -1;
char c = reinterpret_cast<char*>(_rx_buf->payload)[_rx_buf_offset];
char c = pbuf_get_at(_rx_buf, _rx_buf_offset);
_consume(1);
return c;
}
@@ -286,11 +348,17 @@ public:
if (!_rx_buf)
return 0;
size_t max_size = _rx_buf->len - _rx_buf_offset;
size_t max_size = _rx_buf_size - _rx_buf_offset;
size = (size < max_size) ? size : max_size;
DEBUGV(":urd %d, %d, %d\r\n", size, _rx_buf->len, _rx_buf_offset);
DEBUGV(":urd %d, %d, %d\r\n", size, _rx_buf_size, _rx_buf_offset);
void* buf = pbuf_get_contiguous(_rx_buf, dst, size, size, _rx_buf_offset);
if(!buf)
return 0;
if(buf != dst)
memcpy(dst, buf, size);
memcpy(dst, reinterpret_cast<char*>(_rx_buf->payload) + _rx_buf_offset, size);
_consume(size);
return size;
@@ -298,10 +366,10 @@ public:
int peek() const
{
if (!_rx_buf || _rx_buf_offset == _rx_buf->len)
if (!_rx_buf || _rx_buf_offset == _rx_buf_size)
return -1;
return reinterpret_cast<char*>(_rx_buf->payload)[_rx_buf_offset];
return pbuf_get_at(_rx_buf, _rx_buf_offset);
}
void flush()
@@ -310,7 +378,7 @@ public:
if (!_rx_buf)
return;
_consume(_rx_buf->len - _rx_buf_offset);
_consume(_rx_buf_size - _rx_buf_offset);
}
size_t append(const char* data, size_t size)
@@ -394,6 +462,14 @@ public:
private:
size_t _processSize (const pbuf* pb)
{
size_t ret = 0;
for (; pb && pb->flags != PBUF_HELPER_FLAG; pb = pb->next)
ret += pb->len;
return ret;
}
void _reserve(size_t size)
{
const size_t pbuf_unit_size = 128;
@@ -431,8 +507,8 @@ private:
void _consume(size_t size)
{
_rx_buf_offset += size;
if (_rx_buf_offset > _rx_buf->len) {
_rx_buf_offset = _rx_buf->len;
if (_rx_buf_offset > _rx_buf_size) {
_rx_buf_offset = _rx_buf_size;
}
}
@@ -440,11 +516,27 @@ private:
const ip_addr_t *srcaddr, u16_t srcport)
{
(void) upcb;
// check receive pbuf chain depth
// optimization path: cache the pbuf chain length
{
pbuf* p;
int count = 0;
for (p = _rx_buf; p && ++count < rxBufMaxDepth*2; p = p->next);
if (p)
{
// pbuf chain too deep, dropping
pbuf_free(pb);
DEBUGV(":udr\r\n");
return;
}
}
#if LWIP_VERSION_MAJOR == 1
#define TEMPDSTADDR (&current_iphdr_dest)
#define TEMPINPUTNETIF (current_netif)
#else
#define TEMPDSTADDR (ip_current_dest_addr())
#define TEMPINPUTNETIF (ip_current_input_netif())
#endif
// chain this helper pbuf first
@@ -472,7 +564,7 @@ private:
return;
}
// construct in place
new(PBUF_ALIGNER(pb_helper->payload)) AddrHelper(srcaddr, TEMPDSTADDR, srcport);
new(PBUF_ALIGNER(pb_helper->payload)) AddrHelper(srcaddr, TEMPDSTADDR, srcport, TEMPINPUTNETIF);
pb_helper->flags = PBUF_HELPER_FLAG; // mark helper pbuf
// chain it
pbuf_cat(_rx_buf, pb_helper);
@@ -486,11 +578,13 @@ private:
_currentAddr.srcaddr = srcaddr;
_currentAddr.dstaddr = TEMPDSTADDR;
_currentAddr.srcport = srcport;
_currentAddr.input_netif = TEMPINPUTNETIF;
DEBUGV(":urn %d\r\n", pb->tot_len);
_first_buf_taken = false;
_rx_buf = pb;
_rx_buf_offset = 0;
_rx_buf_size = pb->tot_len;
}
if (_on_rx) {
@@ -498,6 +592,7 @@ private:
}
#undef TEMPDSTADDR
#undef TEMPINPUTNETIF
}
@@ -508,11 +603,96 @@ private:
reinterpret_cast<UdpContext*>(arg)->_recv(upcb, p, srcaddr, srcport);
}
#if LWIP_VERSION_MAJOR == 1
/*
* Code in this conditional block is copied/backported verbatim from
* LwIP 2.1.2 to provide pbuf_get_contiguous.
*/
static const struct pbuf *
pbuf_skip_const(const struct pbuf *in, u16_t in_offset, u16_t *out_offset)
{
u16_t offset_left = in_offset;
const struct pbuf *pbuf_it = in;
/* get the correct pbuf */
while ((pbuf_it != NULL) && (pbuf_it->len <= offset_left)) {
offset_left = (u16_t)(offset_left - pbuf_it->len);
pbuf_it = pbuf_it->next;
}
if (out_offset != NULL) {
*out_offset = offset_left;
}
return pbuf_it;
}
u16_t
pbuf_copy_partial(const struct pbuf *buf, void *dataptr, u16_t len, u16_t offset)
{
const struct pbuf *p;
u16_t left = 0;
u16_t buf_copy_len;
u16_t copied_total = 0;
LWIP_ERROR("pbuf_copy_partial: invalid buf", (buf != NULL), return 0;);
LWIP_ERROR("pbuf_copy_partial: invalid dataptr", (dataptr != NULL), return 0;);
/* Note some systems use byte copy if dataptr or one of the pbuf payload pointers are unaligned. */
for (p = buf; len != 0 && p != NULL; p = p->next) {
if ((offset != 0) && (offset >= p->len)) {
/* don't copy from this buffer -> on to the next */
offset = (u16_t)(offset - p->len);
} else {
/* copy from this buffer. maybe only partially. */
buf_copy_len = (u16_t)(p->len - offset);
if (buf_copy_len > len) {
buf_copy_len = len;
}
/* copy the necessary parts of the buffer */
MEMCPY(&((char *)dataptr)[left], &((char *)p->payload)[offset], buf_copy_len);
copied_total = (u16_t)(copied_total + buf_copy_len);
left = (u16_t)(left + buf_copy_len);
len = (u16_t)(len - buf_copy_len);
offset = 0;
}
}
return copied_total;
}
void *
pbuf_get_contiguous(const struct pbuf *p, void *buffer, size_t bufsize, u16_t len, u16_t offset)
{
const struct pbuf *q;
u16_t out_offset;
LWIP_ERROR("pbuf_get_contiguous: invalid buf", (p != NULL), return NULL;);
LWIP_ERROR("pbuf_get_contiguous: invalid dataptr", (buffer != NULL), return NULL;);
LWIP_ERROR("pbuf_get_contiguous: invalid dataptr", (bufsize >= len), return NULL;);
q = pbuf_skip_const(p, offset, &out_offset);
if (q != NULL) {
if (q->len >= (out_offset + len)) {
/* all data in this pbuf, return zero-copy */
return (u8_t *)q->payload + out_offset;
}
/* need to copy */
if (pbuf_copy_partial(q, buffer, len, out_offset) != len) {
/* copying failed: pbuf is too short */
return NULL;
}
return buffer;
}
/* pbuf is too short (offset does not fit in) */
return NULL;
}
#endif
private:
udp_pcb* _pcb;
pbuf* _rx_buf;
bool _first_buf_taken;
size_t _rx_buf_offset;
size_t _rx_buf_size;
int _refcnt;
pbuf* _tx_buf_head;
pbuf* _tx_buf_cur;
@@ -525,12 +705,17 @@ private:
{
IPAddress srcaddr, dstaddr;
int16_t srcport;
netif* input_netif;
AddrHelper() { }
AddrHelper(const ip_addr_t* src, const ip_addr_t* dst, uint16_t srcport):
srcaddr(src), dstaddr(dst), srcport(srcport) { }
AddrHelper(const ip_addr_t* src, const ip_addr_t* dst, uint16_t srcport, netif* input_netif):
srcaddr(src), dstaddr(dst), srcport(srcport), input_netif(input_netif) { }
};
AddrHelper _currentAddr;
// rx pbuf depth barrier (counter of buffered UDP received packets)
// keep it small
static constexpr int rxBufMaxDepth = 4;
};

View File

@@ -43,6 +43,23 @@ void setup() {
}
void update_started() {
USE_SERIAL.println("CALLBACK: HTTP update process started");
}
void update_finished() {
USE_SERIAL.println("CALLBACK: HTTP update process finished");
}
void update_progress(int cur, int total) {
USE_SERIAL.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total);
}
void update_error(int err) {
USE_SERIAL.printf("CALLBACK: HTTP update fatal error code %d\n", err);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
@@ -57,6 +74,12 @@ void loop() {
// value is used to put the LED on. If the LED is on with HIGH, that value should be passed
ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW);
// Add optional callback notifiers
ESPhttpUpdate.onStart(update_started);
ESPhttpUpdate.onEnd(update_finished);
ESPhttpUpdate.onProgress(update_progress);
ESPhttpUpdate.onError(update_error);
t_httpUpdate_return ret = ESPhttpUpdate.update(client, "http://server/file.bin");
// Or:
//t_httpUpdate_return ret = ESPhttpUpdate.update(client, "server", 80, "file.bin");

72
libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp Normal file → Executable file
View File

@@ -294,7 +294,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
if(code <= 0) {
DEBUG_HTTP_UPDATE("[httpUpdate] HTTP error: %s\n", http.errorToString(code).c_str());
_lastError = code;
_setLastError(code);
http.end();
return HTTP_UPDATE_FAILED;
}
@@ -334,15 +334,21 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
}
}
if(!startUpdate) {
_lastError = HTTP_UE_TOO_LESS_SPACE;
if (!startUpdate) {
_setLastError(HTTP_UE_TOO_LESS_SPACE);
ret = HTTP_UPDATE_FAILED;
} else {
// Warn main app we're starting up...
if (_cbStart) {
_cbStart();
}
WiFiClient * tcp = http.getStreamPtr();
WiFiUDP::stopAll();
WiFiClient::stopAllExcept(tcp);
if (_closeConnectionsOnUpdate) {
WiFiUDP::stopAll();
WiFiClient::stopAllExcept(tcp);
}
delay(100);
@@ -360,34 +366,40 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
uint8_t buf[4];
if(tcp->peekBytes(&buf[0], 4) != 4) {
DEBUG_HTTP_UPDATE("[httpUpdate] peekBytes magic header failed\n");
_lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
_setLastError(HTTP_UE_BIN_VERIFY_HEADER_FAILED);
http.end();
return HTTP_UPDATE_FAILED;
}
// check for valid first magic byte
if(buf[0] != 0xE9) {
if(buf[0] != 0xE9 && buf[0] != 0x1f) {
DEBUG_HTTP_UPDATE("[httpUpdate] Magic header does not start with 0xE9\n");
_lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
_setLastError(HTTP_UE_BIN_VERIFY_HEADER_FAILED);
http.end();
return HTTP_UPDATE_FAILED;
}
uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);
if (buf[0] == 0xe9) {
uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);
// check if new bin fits to SPI flash
if(bin_flash_size > ESP.getFlashChipRealSize()) {
DEBUG_HTTP_UPDATE("[httpUpdate] New binary does not fit SPI Flash size\n");
_lastError = HTTP_UE_BIN_FOR_WRONG_FLASH;
http.end();
return HTTP_UPDATE_FAILED;
// check if new bin fits to SPI flash
if(bin_flash_size > ESP.getFlashChipRealSize()) {
DEBUG_HTTP_UPDATE("[httpUpdate] New binary does not fit SPI Flash size\n");
_setLastError(HTTP_UE_BIN_FOR_WRONG_FLASH);
http.end();
return HTTP_UPDATE_FAILED;
}
}
}
if(runUpdate(*tcp, len, http.header("x-MD5"), command)) {
ret = HTTP_UPDATE_OK;
DEBUG_HTTP_UPDATE("[httpUpdate] Update ok\n");
http.end();
// Warn main app we're all done
if (_cbEnd) {
_cbEnd();
}
#ifdef ATOMIC_FS_UPDATE
if(_rebootOnUpdate) {
@@ -403,7 +415,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
}
}
} else {
_lastError = HTTP_UE_SERVER_NOT_REPORT_SIZE;
_setLastError(HTTP_UE_SERVER_NOT_REPORT_SIZE);
ret = HTTP_UPDATE_FAILED;
DEBUG_HTTP_UPDATE("[httpUpdate] Content-Length was 0 or wasn't set by Server?!\n");
}
@@ -413,15 +425,15 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
ret = HTTP_UPDATE_NO_UPDATES;
break;
case HTTP_CODE_NOT_FOUND:
_lastError = HTTP_UE_SERVER_FILE_NOT_FOUND;
_setLastError(HTTP_UE_SERVER_FILE_NOT_FOUND);
ret = HTTP_UPDATE_FAILED;
break;
case HTTP_CODE_FORBIDDEN:
_lastError = HTTP_UE_SERVER_FORBIDDEN;
_setLastError(HTTP_UE_SERVER_FORBIDDEN);
ret = HTTP_UPDATE_FAILED;
break;
default:
_lastError = HTTP_UE_SERVER_WRONG_HTTP_CODE;
_setLastError(HTTP_UE_SERVER_WRONG_HTTP_CODE);
ret = HTTP_UPDATE_FAILED;
DEBUG_HTTP_UPDATE("[httpUpdate] HTTP Code is (%d)\n", code);
//http.writeToStream(&Serial1);
@@ -439,37 +451,49 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
* @param md5 String
* @return true if Update ok
*/
bool ESP8266HTTPUpdate::runUpdate(Stream& in, uint32_t size, String md5, int command)
bool ESP8266HTTPUpdate::runUpdate(Stream& in, uint32_t size, const String& md5, int command)
{
StreamString error;
if (_cbProgress) {
Update.onProgress(_cbProgress);
}
if(!Update.begin(size, command, _ledPin, _ledOn)) {
_lastError = Update.getError();
_setLastError(Update.getError());
Update.printError(error);
error.trim(); // remove line ending
DEBUG_HTTP_UPDATE("[httpUpdate] Update.begin failed! (%s)\n", error.c_str());
return false;
}
if (_cbProgress) {
_cbProgress(0, size);
}
if(md5.length()) {
if(!Update.setMD5(md5.c_str())) {
_lastError = HTTP_UE_SERVER_FAULTY_MD5;
_setLastError(HTTP_UE_SERVER_FAULTY_MD5);
DEBUG_HTTP_UPDATE("[httpUpdate] Update.setMD5 failed! (%s)\n", md5.c_str());
return false;
}
}
if(Update.writeStream(in) != size) {
_lastError = Update.getError();
_setLastError(Update.getError());
Update.printError(error);
error.trim(); // remove line ending
DEBUG_HTTP_UPDATE("[httpUpdate] Update.writeStream failed! (%s)\n", error.c_str());
return false;
}
if (_cbProgress) {
_cbProgress(size, size);
}
if(!Update.end()) {
_lastError = Update.getError();
_setLastError(Update.getError());
Update.printError(error);
error.trim(); // remove line ending
DEBUG_HTTP_UPDATE("[httpUpdate] Update.end failed! (%s)\n", error.c_str());

48
libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h Normal file → Executable file
View File

@@ -47,14 +47,15 @@
#endif
/// note we use HTTP client errors too so we start at 100
#define HTTP_UE_TOO_LESS_SPACE (-100)
#define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101)
#define HTTP_UE_SERVER_FILE_NOT_FOUND (-102)
#define HTTP_UE_SERVER_FORBIDDEN (-103)
#define HTTP_UE_SERVER_WRONG_HTTP_CODE (-104)
#define HTTP_UE_SERVER_FAULTY_MD5 (-105)
#define HTTP_UE_BIN_VERIFY_HEADER_FAILED (-106)
#define HTTP_UE_BIN_FOR_WRONG_FLASH (-107)
//TODO - in v3.0.0 make this an enum
constexpr int HTTP_UE_TOO_LESS_SPACE = (-100);
constexpr int HTTP_UE_SERVER_NOT_REPORT_SIZE = (-101);
constexpr int HTTP_UE_SERVER_FILE_NOT_FOUND = (-102);
constexpr int HTTP_UE_SERVER_FORBIDDEN = (-103);
constexpr int HTTP_UE_SERVER_WRONG_HTTP_CODE = (-104);
constexpr int HTTP_UE_SERVER_FAULTY_MD5 = (-105);
constexpr int HTTP_UE_BIN_VERIFY_HEADER_FAILED = (-106);
constexpr int HTTP_UE_BIN_FOR_WRONG_FLASH = (-107);
enum HTTPUpdateResult {
HTTP_UPDATE_FAILED,
@@ -64,6 +65,11 @@ enum HTTPUpdateResult {
typedef HTTPUpdateResult t_httpUpdate_return; // backward compatibility
using HTTPUpdateStartCB = std::function<void()>;
using HTTPUpdateEndCB = std::function<void()>;
using HTTPUpdateErrorCB = std::function<void(int)>;
using HTTPUpdateProgressCB = std::function<void(int, int)>;
class ESP8266HTTPUpdate
{
public:
@@ -81,6 +87,11 @@ public:
_followRedirects = follow;
}
void closeConnectionsOnUpdate(bool sever)
{
_closeConnectionsOnUpdate = sever;
}
void setLedPin(int ledPin = -1, uint8_t ledOn = HIGH)
{
_ledPin = ledPin;
@@ -124,20 +135,39 @@ public:
#endif
t_httpUpdate_return updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion = "");
// Notification callbacks
void onStart(HTTPUpdateStartCB cbOnStart) { _cbStart = cbOnStart; }
void onEnd(HTTPUpdateEndCB cbOnEnd) { _cbEnd = cbOnEnd; }
void onError(HTTPUpdateErrorCB cbOnError) { _cbError = cbOnError; }
void onProgress(HTTPUpdateProgressCB cbOnProgress) { _cbProgress = cbOnProgress; }
int getLastError(void);
String getLastErrorString(void);
protected:
t_httpUpdate_return handleUpdate(HTTPClient& http, const String& currentVersion, bool spiffs = false);
bool runUpdate(Stream& in, uint32_t size, String md5, int command = U_FLASH);
bool runUpdate(Stream& in, uint32_t size, const String& md5, int command = U_FLASH);
// Set the error and potentially use a CB to notify the application
void _setLastError(int err) {
_lastError = err;
if (_cbError) {
_cbError(err);
}
}
int _lastError;
bool _rebootOnUpdate = true;
bool _closeConnectionsOnUpdate = true;
private:
int _httpClientTimeout;
bool _followRedirects;
// Callbacks
HTTPUpdateStartCB _cbStart;
HTTPUpdateEndCB _cbEnd;
HTTPUpdateErrorCB _cbError;
HTTPUpdateProgressCB _cbProgress;
int _ledPin;
uint8_t _ledOn;
};

View File

@@ -277,7 +277,7 @@ bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *valu
return false; //max txt record size
}
MDNSTxt *newtxt = new MDNSTxt;
newtxt->_txt = String(key) + "=" + String(value);
newtxt->_txt = String(key) + '=' + String(value);
newtxt->_next = 0;
if (servicePtr->_txts == 0) //no services have been added
{

View File

@@ -114,33 +114,20 @@ bool MDNSResponder::begin(const char* p_pcHostname, const IPAddress& p_IPAddress
IPAddress sta = WiFi.localIP();
IPAddress ap = WiFi.softAPIP();
if (!sta.isSet() && !ap.isSet())
if (sta.isSet())
{
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] internal interfaces (STA, AP) are not set (none was specified)\n")));
return false;
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] STA interface selected\n")));
ipAddress = sta;
}
if (ap.isSet())
else if (ap.isSet())
{
if (sta.isSet())
{
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] default interface AP selected over STA (none was specified)\n")));
}
else
{
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] default interface AP selected\n")));
}
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] AP interface selected\n")));
ipAddress = ap;
}
else
{
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] default interface STA selected (none was specified)\n")));
ipAddress = sta;
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] standard interfaces are not up, please specify one in ::begin()\n")));
return false;
}
// continue to ensure interface is UP
@@ -215,18 +202,26 @@ bool MDNSResponder::begin(const char* p_pcHostname, const IPAddress& p_IPAddress
*/
bool MDNSResponder::close(void)
{
bool bResult = false;
m_GotIPHandler.reset(); // reset WiFi event callbacks.
m_DisconnectedHandler.reset();
if (0 != m_pUDPContext)
{
m_GotIPHandler.reset(); // reset WiFi event callbacks.
m_DisconnectedHandler.reset();
_announce(false, true);
_resetProbeStatus(false); // Stop probing
_announce(false, true);
_resetProbeStatus(false); // Stop probing
_releaseServiceQueries();
_releaseUDPContext();
_releaseHostname();
_releaseServiceQueries();
_releaseUDPContext();
_releaseHostname();
return true;
bResult = true;
}
else
{
DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] close: Ignoring call to close!\n")););
}
return bResult;
}
/*
@@ -280,7 +275,7 @@ bool MDNSResponder::setHostname(const char* p_pcHostname)
/*
MDNSResponder::setHostname (LEGACY)
*/
bool MDNSResponder::setHostname(String p_strHostname)
bool MDNSResponder::setHostname(const String& p_strHostname)
{
return setHostname(p_strHostname.c_str());
@@ -369,8 +364,8 @@ bool MDNSResponder::removeService(const char* p_pcName,
/*
MDNSResponder::addService (LEGACY)
*/
bool MDNSResponder::addService(String p_strService,
String p_strProtocol,
bool MDNSResponder::addService(const String& p_strService,
const String& p_strProtocol,
uint16_t p_u16Port)
{
@@ -595,10 +590,10 @@ bool MDNSResponder::addServiceTxt(const char* p_pcService,
/*
MDNSResponder::addServiceTxt (LEGACY)
*/
bool MDNSResponder::addServiceTxt(String p_strService,
String p_strProtocol,
String p_strKey,
String p_strValue)
bool MDNSResponder::addServiceTxt(const String& p_strService,
const String& p_strProtocol,
const String& p_strKey,
const String& p_strValue)
{
return (0 != _addServiceTxt(_findService(m_pcHostname, p_strService.c_str(), p_strProtocol.c_str()), p_strKey.c_str(), p_strValue.c_str(), false));
@@ -826,8 +821,8 @@ bool MDNSResponder::removeQuery(void)
/*
MDNSResponder::queryService (LEGACY)
*/
uint32_t MDNSResponder::queryService(String p_strService,
String p_strProtocol)
uint32_t MDNSResponder::queryService(const String& p_strService,
const String& p_strProtocol)
{
return queryService(p_strService.c_str(), p_strProtocol.c_str());

View File

@@ -192,7 +192,7 @@ public:
// Change hostname (probing is restarted)
bool setHostname(const char* p_pcHostname);
// for compatibility...
bool setHostname(String p_strHostname);
bool setHostname(const String& p_strHostname);
/**
hMDNSService (opaque handle to access the service)
@@ -213,8 +213,8 @@ public:
const char* p_pcServiceName,
const char* p_pcProtocol);
// for compatibility...
bool addService(String p_strServiceName,
String p_strProtocol,
bool addService(const String& p_strServiceName,
const String& p_strProtocol,
uint16_t p_u16Port);
@@ -276,10 +276,10 @@ public:
const char* p_pcProtocol,
const char* p_pcKey,
const char* p_pcValue);
bool addServiceTxt(String p_strService,
String p_strProtocol,
String p_strKey,
String p_strValue);
bool addServiceTxt(const String& p_strService,
const String& p_strProtocol,
const String& p_strKey,
const String& p_strValue);
/**
MDNSDynamicServiceTxtCallbackFn
@@ -331,8 +331,8 @@ public:
const uint16_t p_u16Timeout = MDNS_QUERYSERVICES_WAIT_TIME);
bool removeQuery(void);
// for compatibility...
uint32_t queryService(String p_strService,
String p_strProtocol);
uint32_t queryService(const String& p_strService,
const String& p_strProtocol);
const char* answerHostname(const uint32_t p_u32AnswerIndex);
IPAddress answerIP(const uint32_t p_u32AnswerIndex);

View File

@@ -0,0 +1,13 @@
#include <GDBStub.h>
void setup() {
Serial.begin(115200);
gdbstub_init();
Serial.printf("Starting...\n");
}
void loop() {
static uint32_t cnt = 0;
Serial.printf("%d\n", cnt++);
delay(100);
}

View File

@@ -67,7 +67,6 @@ OS-less SDK defines. Defines some headers for things that aren't in the include
the xthal stack frame struct.
*/
#include "osapi.h"
#include "user_interface.h"
void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame));

View File

@@ -0,0 +1,189 @@
/* Example showing timestamp support in LittleFS */
/* Released into the public domain. */
/* Earle F. Philhower, III <earlephilhower@yahoo.com> */
#include <FS.h>
#include <LittleFS.h>
#include <time.h>
#include <ESP8266WiFi.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
long timezone = 2;
byte daysavetime = 1;
bool getLocalTime(struct tm * info, uint32_t ms) {
uint32_t count = ms / 10;
time_t now;
time(&now);
localtime_r(&now, info);
if (info->tm_year > (2016 - 1900)) {
return true;
}
while (count--) {
delay(10);
time(&now);
localtime_r(&now, info);
if (info->tm_year > (2016 - 1900)) {
return true;
}
}
return false;
}
void listDir(const char * dirname) {
Serial.printf("Listing directory: %s\n", dirname);
Dir root = LittleFS.openDir(dirname);
while (root.next()) {
File file = root.openFile("r");
Serial.print(" FILE: ");
Serial.print(root.fileName());
Serial.print(" SIZE: ");
Serial.print(file.size());
time_t cr = file.getCreationTime();
time_t lw = file.getLastWrite();
file.close();
struct tm * tmstruct = localtime(&cr);
Serial.printf(" CREATION: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
tmstruct = localtime(&lw);
Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
}
}
void readFile(const char * path) {
Serial.printf("Reading file: %s\n", path);
File file = LittleFS.open(path, "r");
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
void writeFile(const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = LittleFS.open(path, "w");
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
delay(2000); // Make sure the CREATE and LASTWRITE times are different
file.close();
}
void appendFile(const char * path, const char * message) {
Serial.printf("Appending to file: %s\n", path);
File file = LittleFS.open(path, "a");
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
void renameFile(const char * path1, const char * path2) {
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (LittleFS.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(const char * path) {
Serial.printf("Deleting file: %s\n", path);
if (LittleFS.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void setup() {
Serial.begin(115200);
// We start by connecting to a WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("Contacting Time Server");
configTime(3600 * timezone, daysavetime * 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
struct tm tmstruct ;
delay(2000);
tmstruct.tm_year = 0;
getLocalTime(&tmstruct, 5000);
Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
Serial.println("");
Serial.println("Formatting LittleFS filesystem");
LittleFS.format();
Serial.println("Mount LittleFS");
if (!LittleFS.begin()) {
Serial.println("LittleFS mount failed");
return;
}
listDir("/");
deleteFile("/hello.txt");
writeFile("/hello.txt", "Hello ");
appendFile("/hello.txt", "World!\n");
listDir("/");
Serial.println("The timestamp should be valid above");
Serial.println("Now unmount and remount and perform the same operation.");
Serial.println("Timestamp should be valid, data should be good.");
LittleFS.end();
Serial.println("Now mount it");
if (!LittleFS.begin()) {
Serial.println("LittleFS mount failed");
return;
}
readFile("/hello.txt");
listDir("/");
}
void loop() { }

View File

@@ -52,7 +52,7 @@ FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode a
int flags = _getFlags(openMode, accessMode);
auto fd = std::make_shared<lfs_file_t>();
if ((openMode && OM_CREATE) && strchr(path, '/')) {
if ((openMode & OM_CREATE) && strchr(path, '/')) {
// For file creation, silently make subdirs as needed. If any fail,
// it will be caught by the real file open later on
char *pathStr = strdup(path);
@@ -68,13 +68,26 @@ FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode a
}
free(pathStr);
}
time_t creation = 0;
if (timeCallback && (openMode & OM_CREATE)) {
// O_CREATE means we *may* make the file, but not if it already exists.
// See if it exists, and only if not update the creation time
int rc = lfs_file_open(&_lfs, fd.get(), path, LFS_O_RDONLY);
if (rc == 0) {
lfs_file_close(&_lfs, fd.get()); // It exists, don't update create time
} else {
creation = timeCallback(); // File didn't exist or otherwise, so we're going to create this time
}
}
int rc = lfs_file_open(&_lfs, fd.get(), path, flags);
if (rc == LFS_ERR_ISDIR) {
// To support the SD.openNextFile, a null FD indicates to the LittleFSFile this is just
// a directory whose name we are carrying around but which cannot be read or written
return std::make_shared<LittleFSFileImpl>(this, path, nullptr);
return std::make_shared<LittleFSFileImpl>(this, path, nullptr, flags, creation);
} else if (rc == 0) {
return std::make_shared<LittleFSFileImpl>(this, path, fd);
return std::make_shared<LittleFSFileImpl>(this, path, fd, flags, creation);
} else {
DEBUGV("LittleFSDirImpl::openFile: rc=%d fd=%p path=`%s` openMode=%d accessMode=%d err=%d\n",
rc, fd.get(), path, openMode, accessMode, rc);
@@ -190,6 +203,14 @@ int LittleFSImpl::lfs_flash_sync(const struct lfs_config *c) {
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS)
FS LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(FS_PHYS_ADDR, FS_PHYS_SIZE, FS_PHYS_PAGE, FS_PHYS_BLOCK, FS_MAX_OPEN_FILES)));
extern "C" void littlefs_request_end(void)
{
// override default weak function
//ets_printf("debug: not weak littlefs end\n");
LittleFS.end();
}
#endif
#endif // !CORE_MOCK

View File

@@ -47,11 +47,8 @@ class LittleFSDirImpl;
class LittleFSConfig : public FSConfig
{
public:
LittleFSConfig(bool autoFormat = true) {
_type = LittleFSConfig::fsid::FSId;
_autoFormat = autoFormat;
}
enum fsid { FSId = 0x4c495454 };
static constexpr uint32_t FSId = 0x4c495454;
LittleFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
};
class LittleFSImpl : public FSImpl
@@ -92,7 +89,7 @@ public:
DirImplPtr openDir(const char *path) override;
bool exists(const char* path) override {
if ( !_mounted || !path || !path[0] ) {
if (!_mounted || !path || !path[0]) {
return false;
}
lfs_info info;
@@ -101,7 +98,7 @@ public:
}
bool rename(const char* pathFrom, const char* pathTo) override {
if (!_mounted || !pathFrom || !pathFrom[0] || !pathTo || !pathTo[0]) {
if (!_mounted || !pathFrom || !pathFrom[0] || !pathTo || !pathTo[0]) {
return false;
}
int rc = lfs_rename(&_lfs, pathFrom, pathTo);
@@ -176,7 +173,7 @@ public:
}
bool setConfig(const FSConfig &cfg) override {
if ((cfg._type != LittleFSConfig::fsid::FSId) || _mounted) {
if ((cfg._type != LittleFSConfig::FSId) || _mounted) {
return false;
}
_cfg = *static_cast<const LittleFSConfig *>(&cfg);
@@ -326,7 +323,7 @@ protected:
class LittleFSFileImpl : public FileImpl
{
public:
LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr<lfs_file_t> fd) : _fs(fs), _fd(fd), _opened(true) {
LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr<lfs_file_t> fd, int flags, time_t creation) : _fs(fs), _fd(fd), _opened(true), _flags(flags), _creation(creation) {
_name = std::shared_ptr<char>(new char[strlen(name) + 1], std::default_delete<char[]>());
strcpy(_name.get(), name);
}
@@ -422,9 +419,44 @@ public:
lfs_file_close(_fs->getFS(), _getFD());
_opened = false;
DEBUGV("lfs_file_close: fd=%p\n", _getFD());
if (timeCallback && (_flags & LFS_O_WRONLY)) {
// If the file opened with O_CREAT, write the creation time attribute
if (_creation) {
int rc = lfs_setattr(_fs->getFS(), _name.get(), 'c', (const void *)&_creation, sizeof(_creation));
if (rc < 0) {
DEBUGV("Unable to set creation time on '%s' to %d\n", _name.get(), _creation);
}
}
// Add metadata with last write time
time_t now = timeCallback();
int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now));
if (rc < 0) {
DEBUGV("Unable to set last write time on '%s' to %d\n", _name.get(), now);
}
}
}
}
time_t getLastWrite() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 't', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
time_t getCreationTime() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 'c', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
const char* name() const override {
if (!_opened) {
return nullptr;
@@ -468,6 +500,8 @@ protected:
std::shared_ptr<lfs_file_t> _fd;
std::shared_ptr<char> _name;
bool _opened;
int _flags;
time_t _creation;
};
class LittleFSDirImpl : public DirImpl
@@ -496,13 +530,9 @@ public:
int nameLen = 3; // Slashes, terminator
nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
nameLen += strlen(_dirent.name);
char *tmpName = (char*)malloc(nameLen);
if (!tmpName) {
return FileImplPtr();
}
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
char tmpName[nameLen];
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
auto ret = _fs->open((const char *)tmpName, openMode, accessMode);
free(tmpName);
return ret;
}
@@ -520,6 +550,15 @@ public:
return _dirent.size;
}
time_t fileTime() override {
return (time_t)_getAttr4('t');
}
time_t fileCreationTime() override {
return (time_t)_getAttr4('c');
}
bool isFile() const override {
return _valid && (_dirent.type == LFS_TYPE_REG);
}
@@ -531,6 +570,10 @@ public:
bool rewind() override {
_valid = false;
int rc = lfs_dir_rewind(_fs->getFS(), _getDir());
// Skip the . and .. entries
lfs_info dirent;
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
return (rc == 0);
}
@@ -551,6 +594,22 @@ protected:
return _dir.get();
}
uint32_t _getAttr4(char attr) {
if (!_valid) {
return 0;
}
int nameLen = 3; // Slashes, terminator
nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
nameLen += strlen(_dirent.name);
char tmpName[nameLen];
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
time_t ftime = 0;
int rc = lfs_getattr(_fs->getFS(), tmpName, attr, (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
return ftime;
}
String _pattern;
LittleFSImpl *_fs;
std::shared_ptr<lfs_dir_t> _dir;

View File

@@ -28,14 +28,11 @@ File root;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.begin(115200);
Serial.print("Initializing SD card...");
if (!SD.begin(4)) {
if (!SD.begin(SS)) {
Serial.println("initialization failed!");
return;
}
@@ -70,11 +67,14 @@ void printDirectory(File dir, int numTabs) {
} else {
// files have sizes, directories do not
Serial.print("\t\t");
Serial.println(entry.size(), DEC);
Serial.print(entry.size(), DEC);
time_t cr = entry.getCreationTime();
time_t lw = entry.getLastWrite();
struct tm * tmstruct = localtime(&cr);
Serial.printf("\tCREATION: %d-%02d-%02d %02d:%02d:%02d", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
tmstruct = localtime(&lw);
Serial.printf("\tLAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
}
entry.close();
}
}

View File

@@ -3,3 +3,5 @@
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
SDClass SD;
#endif
void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr;

View File

@@ -29,6 +29,7 @@
#undef FILE_WRITE
#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND)
class SDClass {
public:
boolean begin(uint8_t csPin, SPISettings cfg = SPI_HALF_SPEED) {
@@ -137,6 +138,17 @@ public:
return ((uint64_t)clusterSize() * (uint64_t)totalClusters());
}
void setTimeCallback(time_t (*cb)(void)) {
SDFS.setTimeCallback(cb);
}
// Wrapper to allow obsolete datetimecallback use, silently convert to time_t in wrappertimecb
void dateTimeCallback(void (*cb)(uint16_t*, uint16_t*)) {
extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
__SD__userDateTimeCB = cb;
SDFS.setTimeCallback(wrapperTimeCB);
}
private:
const char *getMode(uint8_t mode) {
bool read = (mode & sdfat::O_READ) ? true : false;
@@ -150,8 +162,46 @@ private:
else { return "r"; }
}
static time_t wrapperTimeCB(void) {
extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
if (__SD__userDateTimeCB) {
uint16_t d, t;
__SD__userDateTimeCB(&d, &t);
return sdfs::SDFSImpl::FatToTimeT(d, t);
}
return time(nullptr);
}
};
// Expose FatStructs.h helpers for MSDOS date/time for use with dateTimeCallback
static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
return (year - 1980) << 9 | month << 5 | day;
}
static inline uint16_t FAT_YEAR(uint16_t fatDate) {
return 1980 + (fatDate >> 9);
}
static inline uint8_t FAT_MONTH(uint16_t fatDate) {
return (fatDate >> 5) & 0XF;
}
static inline uint8_t FAT_DAY(uint16_t fatDate) {
return fatDate & 0X1F;
}
static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
return hour << 11 | minute << 5 | second >> 1;
}
static inline uint8_t FAT_HOUR(uint16_t fatTime) {
return fatTime >> 11;
}
static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
return (fatTime >> 5) & 0X3F;
}
static inline uint8_t FAT_SECOND(uint16_t fatTime) {
return 2*(fatTime & 0X1F);
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
extern SDClass SD;
#endif

View File

@@ -45,22 +45,9 @@ class SDFSDirImpl;
class SDFSConfig : public FSConfig
{
public:
SDFSConfig() {
_type = SDFSConfig::fsid::FSId;
_autoFormat = false;
_csPin = 4;
_spiSettings = SD_SCK_MHZ(10);
_part = 0;
}
SDFSConfig(uint8_t csPin, SPISettings spi) {
_type = SDFSConfig::fsid::FSId;
_autoFormat = false;
_csPin = csPin;
_spiSettings = spi;
_part = 0;
}
static constexpr uint32_t FSId = 0x53444653;
enum fsid { FSId = 0x53444653 };
SDFSConfig(uint8_t csPin = 4, SPISettings spi = SD_SCK_MHZ(10)) : FSConfig(FSId, false), _csPin(csPin), _part(0), _spiSettings(spi) { }
SDFSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
@@ -152,7 +139,7 @@ public:
bool setConfig(const FSConfig &cfg) override
{
if ((cfg._type != SDFSConfig::fsid::FSId) || _mounted) {
if ((cfg._type != SDFSConfig::FSId) || _mounted) {
DEBUGV("SDFS::setConfig: invalid config or already mounted\n");
return false;
}
@@ -169,6 +156,7 @@ public:
format();
_mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings);
}
sdfat::SdFile::dateTimeCallback(dateTimeCB);
return _mounted;
}
@@ -203,6 +191,31 @@ public:
return (clusterSize() * totalClusters());
}
// Helper function, takes FAT and makes standard time_t
static time_t FatToTimeT(uint16_t d, uint16_t t) {
struct tm tiempo;
memset(&tiempo, 0, sizeof(tiempo));
tiempo.tm_sec = (((int)t) << 1) & 0x3e;
tiempo.tm_min = (((int)t) >> 5) & 0x3f;
tiempo.tm_hour = (((int)t) >> 11) & 0x1f;
tiempo.tm_mday = (int)(d & 0x1f);
tiempo.tm_mon = ((int)(d >> 5) & 0x0f) - 1;
tiempo.tm_year = ((int)(d >> 9) & 0x7f) + 80;
tiempo.tm_isdst = -1;
return mktime(&tiempo);
}
// Because SdFat has a single, global setting for this we can only use a
// static member of our class to return the time/date. However, since
// this is static, we can't see the time callback variable. Punt for now,
// using time(NULL) as the best we can do.
static void dateTimeCB(uint16_t *dosYear, uint16_t *dosTime) {
time_t now = time(nullptr);
struct tm *tiempo = localtime(&now);
*dosYear = ((tiempo->tm_year - 80) << 9) | ((tiempo->tm_mon + 1) << 5) | tiempo->tm_mday;
*dosTime = (tiempo->tm_hour << 11) | (tiempo->tm_min << 5) | tiempo->tm_sec;
}
protected:
friend class SDFileImpl;
friend class SDFSDirImpl;
@@ -212,6 +225,7 @@ protected:
return &_fs;
}
static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) {
uint8_t mode = 0;
if (openMode & OM_CREATE) {
@@ -350,6 +364,29 @@ public:
return _opened ? _fd->isDirectory() : false;
}
time_t getLastWrite() override {
time_t ftime = 0;
if (_opened && _fd) {
sdfat::dir_t tmp;
if (_fd.get()->dirEntry(&tmp)) {
ftime = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime);
}
}
return ftime;
}
time_t getCreationTime() override {
time_t ftime = 0;
if (_opened && _fd) {
sdfat::dir_t tmp;
if (_fd.get()->dirEntry(&tmp)) {
ftime = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime);
}
}
return ftime;
}
protected:
SDFSImpl* _fs;
@@ -404,6 +441,24 @@ public:
return _size;
}
time_t fileTime() override
{
if (!_valid) {
return 0;
}
return _time;
}
time_t fileCreationTime() override
{
if (!_valid) {
return 0;
}
return _creation;
}
bool isFile() const override
{
return _valid ? _isFile : false;
@@ -425,6 +480,14 @@ public:
_size = file.fileSize();
_isFile = file.isFile();
_isDirectory = file.isDirectory();
sdfat::dir_t tmp;
if (file.dirEntry(&tmp)) {
_time = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime);
_creation = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime);
} else {
_time = 0;
_creation = 0;
}
file.getName(_lfn, sizeof(_lfn));
file.close();
} else {
@@ -447,6 +510,8 @@ protected:
std::shared_ptr<sdfat::File> _dir;
bool _valid;
char _lfn[64];
time_t _time;
time_t _creation;
std::shared_ptr<char> _dirPath;
uint32_t _size;
bool _isFile;

View File

@@ -63,14 +63,17 @@ void SPISlaveClass::_s_status_tx(void *arg)
{
reinterpret_cast<SPISlaveClass*>(arg)->_status_tx();
}
void SPISlaveClass::begin()
void SPISlaveClass::begin() //backwards compatibility
{
begin(4);
}
void SPISlaveClass::begin(uint8_t statusLength)
{
hspi_slave_onData(&_s_data_rx);
hspi_slave_onDataSent(&_s_data_tx);
hspi_slave_onStatus(&_s_status_rx);
hspi_slave_onStatusSent(&_s_status_tx);
hspi_slave_begin(4, this);
hspi_slave_begin(statusLength, this);
}
void SPISlaveClass::end()
{

View File

@@ -52,6 +52,7 @@ public:
{}
~SPISlaveClass() {}
void begin();
void begin(uint8_t statusLength);
void end();
void setData(uint8_t * data, size_t len);
void setData(const char * data)

View File

@@ -72,11 +72,10 @@ void ICACHE_RAM_ATTR _hspi_slave_isr_handler(void *arg)
void hspi_slave_begin(uint8_t status_len, void * arg)
{
status_len &= 7;
if(status_len > 4) {
status_len = 4; //max 32 bits
}
if(status_len == 0) {
else if(status_len == 0) {
status_len = 1; //min 8 bits
}
@@ -85,7 +84,13 @@ void hspi_slave_begin(uint8_t status_len, void * arg)
pinMode(MISO, SPECIAL);
pinMode(MOSI, SPECIAL);
SPI1S = SPISE | SPISBE | 0x3E0; // SPI_SLAVE_REG
SPI1S = SPISE | SPISBE | SPISTRIE | SPISWBIE | SPISRSIE | SPISWSIE | SPISRBIE; //(0x63E0)
//setting config bits in SPI_SLAVE_REG, defined in "esp8266_peri.h" :
//SPISE - spi slave enable
//SPISBE - allows work (read/write) with buffer, without this only? status available
//SPISTRIE - enables TRANS?? interrupt
//other SPISxxIE - enables corresponding interrupts (read(R)/write(W) status(S) and buffer(B))
SPI1U = SPIUMISOH | SPIUCOMMAND | SPIUSSE; // SPI_USER_REG
SPI1CLK = 0;
SPI1U2 = (7 << SPILCOMMAND); // SPI_USER2_REG

View File

@@ -50,6 +50,7 @@
#define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached
#define REFRESH_INTERVAL 20000 // minumim time to refresh servos in microseconds
#define MAX_SERVOS 12
#if !defined(ESP8266)

View File

@@ -1,129 +0,0 @@
/*
Ticker.h - esp8266 library that calls functions periodically
Copyright (c) 2014 Ivan Grokhotkov. 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 TICKER_H
#define TICKER_H
#include <functional>
#include <Schedule.h>
#include <ets_sys.h>
class Ticker
{
public:
Ticker();
~Ticker();
typedef void (*callback_with_arg_t)(void *);
typedef std::function<void(void)> callback_function_t;
void attach_scheduled(float seconds, callback_function_t callback)
{
attach(seconds, [callback]() { schedule_function(callback); });
}
void attach(float seconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_s(seconds, true, _static_callback, this);
}
void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{
attach_ms(milliseconds, [callback]() { schedule_function(callback); });
}
void attach_ms(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(milliseconds, true, _static_callback, this);
}
template<typename TArg>
void attach(float seconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
// C-cast serves two purposes:
// static_cast for smaller integer types,
// reinterpret_cast + const_cast for pointer types
_attach_s(seconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void *>(arg));
}
template<typename TArg>
void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(milliseconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void *>(arg));
}
void once_scheduled(float seconds, callback_function_t callback)
{
once(seconds, [callback]() { schedule_function(callback); });
}
void once(float seconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_s(seconds, false, _static_callback, this);
}
void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{
once_ms(milliseconds, [callback]() { schedule_function(callback); });
}
void once_ms(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(milliseconds, false, _static_callback, this);
}
template<typename TArg>
void once(float seconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_s(seconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void *>(arg));
}
template<typename TArg>
void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(milliseconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void *>(arg));
}
void detach();
bool active() const;
protected:
void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg);
static void _static_callback(void* arg);
ETSTimer* _timer;
callback_function_t _callback_function = nullptr;
private:
void _attach_s(float seconds, bool repeat, callback_with_arg_t callback, void* arg);
ETSTimer _etsTimer;
};
#endif//TICKER_H

View File

@@ -13,9 +13,17 @@
#include <Ticker.h>
Ticker tickerSetHigh;
Ticker tickerSetAnalog;
Ticker tickerSetLow;
Ticker tickerSetHigh;
Ticker tickerSetChar;
void setPinLow() {
digitalWrite(LED_BUILTIN, 0);
}
void setPinHigh() {
digitalWrite(LED_BUILTIN, 1);
}
void setPin(int state) {
digitalWrite(LED_BUILTIN, state);
@@ -27,14 +35,15 @@ void setPinChar(char state) {
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(1, LOW);
// every 25 ms, call setPin(0)
tickerSetLow.attach_ms(25, setPin, 0);
// every 25 ms, call setPinLow()
tickerSetLow.attach_ms(25, setPinLow);
// every 26 ms, call setPinChar(1)
tickerSetHigh.attach_ms(26, setPinChar, (char)1);
// every 26 ms, call setPinHigh()
tickerSetHigh.attach_ms(26, setPinHigh);
// every 54 ms, call setPinChar(1)
tickerSetChar.attach_ms(26, setPinChar, (char)1);
}
void loop() {

View File

@@ -1,11 +1,9 @@
#######################################
# Syntax Coloring Map For Wire
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
Ticker KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
@@ -16,14 +14,3 @@ once KEYWORD2
once_ms KEYWORD2
detach KEYWORD2
active KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
Ticker KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -25,60 +25,47 @@
#include "Ticker.h"
namespace
{
constexpr int ONCE = 0;
constexpr int REPEAT = 1;
}
Ticker::Ticker()
: _timer(nullptr)
{
}
: _timer(nullptr) {}
Ticker::~Ticker()
{
detach();
}
void Ticker::_attach_s(float seconds, bool repeat, callback_with_arg_t callback, void* arg)
{
_attach_ms(1000 * seconds, repeat, callback, arg);
detach();
}
void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg)
{
if (_timer)
{
os_timer_disarm(_timer);
}
else
{
_timer = &_etsTimer;
}
if (_timer)
{
os_timer_disarm(_timer);
}
else
{
_timer = &_etsTimer;
}
os_timer_setfn(_timer, callback, arg);
os_timer_arm(_timer, milliseconds, (repeat) ? REPEAT : ONCE);
os_timer_setfn(_timer, callback, arg);
os_timer_arm(_timer, milliseconds, repeat);
}
void Ticker::detach()
{
if (!_timer)
return;
if (!_timer)
return;
os_timer_disarm(_timer);
_timer = nullptr;
_callback_function = nullptr;
os_timer_disarm(_timer);
_timer = nullptr;
_callback_function = nullptr;
}
bool Ticker::active() const
{
return _timer;
return _timer;
}
void Ticker::_static_callback(void* arg)
{
Ticker* _this = reinterpret_cast<Ticker*>(arg);
if (_this && _this->_callback_function)
_this->_callback_function();
Ticker* _this = reinterpret_cast<Ticker*>(arg);
if (_this && _this->_callback_function)
_this->_callback_function();
}

View File

@@ -0,0 +1,133 @@
/*
Ticker.h - esp8266 library that calls functions periodically
Copyright (c) 2014 Ivan Grokhotkov. 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 TICKER_H
#define TICKER_H
#include <functional>
#include <Schedule.h>
#include <ets_sys.h>
class Ticker
{
public:
Ticker();
~Ticker();
typedef void (*callback_with_arg_t)(void*);
typedef std::function<void(void)> callback_function_t;
void attach_scheduled(float seconds, callback_function_t callback)
{
_callback_function = [callback]() { schedule_function(callback); };
_attach_ms(1000UL * seconds, true);
}
void attach(float seconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(1000UL * seconds, true);
}
void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = [callback]() { schedule_function(callback); };
_attach_ms(milliseconds, true);
}
void attach_ms(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(milliseconds, true);
}
template<typename TArg>
void attach(float seconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(1000UL * seconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
}
template<typename TArg>
void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(milliseconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
}
void once_scheduled(float seconds, callback_function_t callback)
{
_callback_function = [callback]() { schedule_function(callback); };
_attach_ms(1000UL * seconds, false);
}
void once(float seconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(1000UL * seconds, false);
}
void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = [callback]() { schedule_function(callback); };
_attach_ms(milliseconds, false);
}
void once_ms(uint32_t milliseconds, callback_function_t callback)
{
_callback_function = std::move(callback);
_attach_ms(milliseconds, false);
}
template<typename TArg>
void once(float seconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(1000UL * seconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
}
template<typename TArg>
void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg)
{
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(milliseconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
}
void detach();
bool active() const;
protected:
static void _static_callback(void* arg);
void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg);
void _attach_ms(uint32_t milliseconds, bool repeat)
{
_attach_ms(milliseconds, repeat, _static_callback, this);
}
ETSTimer* _timer;
callback_function_t _callback_function = nullptr;
private:
ETSTimer _etsTimer;
};
#endif //TICKER_H

View File

@@ -0,0 +1,64 @@
#include <Arduino.h>
#include <Ticker.h>
#include "CallBackList.h"
using namespace experimental::CBListImplentation;
class exampleClass {
public:
exampleClass() {};
using exCallBack = std::function<void(int)>;
using exHandler = CallBackList<exCallBack>::CallBackHandler;
CallBackList<exCallBack> myHandlers;
exHandler setHandler(exCallBack cb) {
return myHandlers.add(cb);
}
void removeHandler(exHandler hnd) {
myHandlers.remove(hnd);
}
void trigger(int t) {
myHandlers.execute(t);
}
};
exampleClass myExample;
void cb1(int in) {
Serial.printf("Callback 1, in = %d\n", in);
}
void cb2(int in) {
Serial.printf("Callback 2, in = %d\n", in);
}
void cb3(int in, int s) {
Serial.printf("Callback 3, in = %d, s = %d\n", in, s);
}
Ticker tk, tk2, tk3;
exampleClass::exHandler e1 = myExample.setHandler(cb1);
exampleClass::exHandler e2 = myExample.setHandler(cb2);
exampleClass::exHandler e3 = myExample.setHandler(std::bind(cb3, std::placeholders::_1, 10));
void setup() {
Serial.begin(115200);
tk.attach_ms(2000, []() {
Serial.printf("trigger %d\n", (uint32_t)millis());
myExample.trigger(millis());
});
tk2.once_ms(10000, []() {
myExample.removeHandler(e2);
});
tk3.once_ms(20000, []() {
e3.reset();
});
}
void loop() {
}

View File

@@ -0,0 +1,45 @@
/*
Demonstrate CRC check passing and failing by simulating a bit flip in flash.
WARNING!!! You would never want to actually do this in a real application!
Released to the Public Domain by Earle F. Philhower, III <earlephilhower@yahoo.com>
*/
extern "C" {
#include "spi_flash.h"
}
// Artificially create a space in PROGMEM that fills multipe sectors so
// we can corrupt one without crashing the system
const int corruptme[SPI_FLASH_SEC_SIZE * 4] PROGMEM = { 0 };
void setup() {
Serial.begin(115200);
Serial.printf("Starting\n");
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
Serial.printf("...Corrupting a portion of flash in the array...\n");
uint32_t ptr = (uint32_t)corruptme;
// Find a page aligned spot inside the array
ptr += 2 * SPI_FLASH_SEC_SIZE;
ptr &= ~(SPI_FLASH_SEC_SIZE - 1); // Sectoralign
uint32_t sector = ((((uint32_t)ptr - 0x40200000) / SPI_FLASH_SEC_SIZE));
// Create a sector with 1 bit set (i.e. fake corruption)
uint32_t *space = (uint32_t*)calloc(SPI_FLASH_SEC_SIZE, 1);
space[42] = 64;
// Write it into flash at the spot in question
spi_flash_erase_sector(sector);
spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE);
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
Serial.printf("...Correcting the flash...\n");
memset(space, 0, SPI_FLASH_SEC_SIZE);
spi_flash_erase_sector(sector);
spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE);
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
}
void loop() {
}

View File

@@ -0,0 +1,439 @@
/* This example demonstrates the different low-power modes of the ESP8266
The initial setup was a WeMos D1 Mini with 3.3V connected to the 3V3 pin through a meter
so that it bypassed the on-board voltage regulator and USB chip. There's still about
0.3 mA worth of leakage amperage due to the unpowered chips. These tests should work with
any module, although on-board components will affect the actual current measurement.
While the modem is turned on the amperage is > 67 mA or changing with a minimum value.
To verify the 20 uA Deep Sleep amperage the voltage regulator and USB chip were removed.
This test series requires an active WiFi connection to illustrate two tests. If you
have problems with WiFi, uncomment the #define DEBUG for additional WiFi error messages.
The test requires a pushbutton switch connected between D3 and GND to advance the tests.
You'll also need to connect D0/GPIO16 to RST for the Deep Sleep tests. If you forget to
connect D0 to RST it will hang after the first Deep Sleep test. D0 is driven high during
Deep Sleep, so you should use a Schottky diode between D0 and RST if you want to use a
reset switch; connect the anode of the diode to RST, and the cathode to D0.
Additionally, you can connect an LED from any free pin through a 1K ohm resistor to the
3.3V supply, though preferably not the 3V3 pin on the module or it adds to the measured
amperage. When the LED blinks you can proceed to the next test. When the LED is lit
continuously it's connecting WiFi, and when it's off the CPU is asleep. The LED blinks
slowly when the tests are complete. Test progress can also be shown on the serial monitor.
WiFi connections will be made over twice as fast if you can use a static IP address.
This example 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 example 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 example; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include <ESP8266WiFi.h>
#include <coredecls.h> // crc32()
#include <PolledTimeout.h>
#include <include/WiFiState.h> // WiFiState structure details
//#define DEBUG // prints WiFi connection info to serial, uncomment if you want WiFi messages
#ifdef DEBUG
#define DEBUG_PRINTLN(x) Serial.println(x)
#define DEBUG_PRINT(x) Serial.print(x)
#else
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT(x)
#endif
#define WAKE_UP_PIN 0 // D3/GPIO0, can also force a serial flash upload with RESET
// you can use any GPIO for WAKE_UP_PIN except for D0/GPIO16 as it doesn't support interrupts
// uncomment one of the two lines below for your LED connection (optional)
#define LED 5 // D1/GPIO5 external LED for modules with built-in LEDs so it doesn't add amperage
//#define LED 2 // D4/GPIO2 LED for ESP-01,07 modules; D4 is LED_BUILTIN on most other modules
// you can use LED_BUILTIN, but it adds to the measured amperage by 0.3mA to 6mA.
ADC_MODE(ADC_VCC); // allows you to monitor the internal VCC level; it varies with WiFi load
// don't connect anything to the analog input pin(s)!
// enter your WiFi configuration below
const char* AP_SSID = "SSID"; // your router's SSID here
const char* AP_PASS = "password"; // your router's password here
IPAddress staticIP(0, 0, 0, 0); // parameters below are for your static IP address, if used
IPAddress gateway(0, 0, 0, 0);
IPAddress subnet(0, 0, 0, 0);
IPAddress dns1(0, 0, 0, 0);
IPAddress dns2(0, 0, 0, 0);
uint32_t timeout = 30E3; // 30 second timeout on the WiFi connection
//#define TESTPOINT // used to track the timing of several test cycles (optional)
#ifdef TESTPOINT
#define testPointPin 4 // D2/GPIO4, you can use any pin that supports interrupts
#define testPoint_HIGH digitalWrite(testPointPin, HIGH)
#define testPoint_LOW digitalWrite(testPointPin, LOW)
#else
#define testPoint_HIGH
#define testPoint_LOW
#endif
// This structure is stored in RTC memory to save the WiFi state and reset count (number of Deep Sleeps),
// and it reconnects twice as fast as the first connection; it's used several places in this demo
struct nv_s {
WiFiState wss; // core's WiFi save state
struct {
uint32_t crc32;
uint32_t rstCount; // stores the Deep Sleep reset count
// you can add anything else here that you want to save, must be 4-byte aligned
} rtcData;
};
static nv_s* nv = (nv_s*)RTC_USER_MEM; // user RTC RAM area
uint32_t resetCount = 0; // keeps track of the number of Deep Sleep tests / resets
const uint32_t blinkDelay = 100; // fast blink rate for the LED when waiting for the user
esp8266::polledTimeout::periodicMs blinkLED(blinkDelay); // LED blink delay without delay()
esp8266::polledTimeout::oneShotMs altDelay(blinkDelay); // tight loop to simulate user code
esp8266::polledTimeout::oneShotMs wifiTimeout(timeout); // 30 second timeout on WiFi connection
// use fully qualified type and avoid importing all ::esp8266 namespace to the global namespace
void wakeupCallback() { // unlike ISRs, you can do a print() from a callback function
testPoint_LOW; // testPoint tracks latency from WAKE_UP_PIN LOW to testPoint LOW
printMillis(); // show time difference across sleep; millis is wrong as the CPU eventually stops
Serial.println(F("Woke from Light Sleep - this is the callback"));
}
void setup() {
#ifdef TESTPOINT
pinMode(testPointPin, OUTPUT); // test point for Light Sleep and Deep Sleep tests
testPoint_LOW; // Deep Sleep reset doesn't clear GPIOs, testPoint LOW shows boot time
#endif
pinMode(LED, OUTPUT); // activity and status indicator
digitalWrite(LED, LOW); // turn on the LED
pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, interrupt for Forced Light Sleep
Serial.begin(115200);
Serial.println();
Serial.print(F("\nReset reason = "));
String resetCause = ESP.getResetReason();
Serial.println(resetCause);
resetCount = 0;
if ((resetCause == "External System") || (resetCause == "Power on")) {
Serial.println(F("I'm awake and starting the Low Power tests"));
}
// Read previous resets (Deep Sleeps) from RTC memory, if any
uint32_t crcOfData = crc32((uint8_t*) &nv->rtcData.rstCount, sizeof(nv->rtcData.rstCount));
if ((crcOfData = nv->rtcData.crc32) && (resetCause == "Deep-Sleep Wake")) {
resetCount = nv->rtcData.rstCount; // read the previous reset count
resetCount++;
}
nv->rtcData.rstCount = resetCount; // update the reset count & CRC
updateRTCcrc();
if (resetCount == 1) { // show that millis() is cleared across the Deep Sleep reset
printMillis();
}
} // end of setup()
void loop() {
if (resetCount == 0) { // if first loop() since power on or external reset
runTest1();
runTest2();
runTest3();
runTest4();
runTest5();
runTest6();
runTest7(); // first Deep Sleep test, all these end with a RESET
}
if (resetCount < 4) {
initWiFi(); // optional re-init of WiFi for the Deep Sleep tests
}
if (resetCount == 1) {
runTest8();
} else if (resetCount == 2) {
runTest9();
} else if (resetCount == 3) {
runTest10();
} else if (resetCount == 4) {
resetTests();
}
} //end of loop()
void runTest1() {
Serial.println(F("\n1st test - running with WiFi unconfigured"));
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay);
}
void runTest2() {
Serial.println(F("\n2nd test - Automatic Modem Sleep"));
Serial.println(F("connecting WiFi, please wait until the LED blinks"));
initWiFi();
if (WiFi.localIP()) { // won't go into Automatic Sleep without an active WiFi connection
Serial.println(F("The amperage will drop in 7 seconds."));
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(true, 90); /* This is using a special feature: below 100 mS blink delay,
the LED blink delay is padding 100 mS time with 'program cycles' to fill the 100 mS.
At 90 mS delay, 90% of the blink time is delay(), and 10% is 'your program running'.
Below 90% you'll see a difference in the average amperage: less delay() = more amperage.
At 100 mS and above it's essentially all delay() time. On an oscilloscope you'll see the
time between beacons at > 67 mA more often with less delay() percentage. You can change
the '90' mS to other values to see the effect it has on Automatic Modem Sleep. */
} else {
Serial.println(F("no WiFi connection, test skipped"));
}
}
void runTest3() {
Serial.println(F("\n3rd test - Forced Modem Sleep"));
WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // shut the modem down and save the WiFi state for faster reconnection
// WiFi.forceSleepBegin(delay_in_uS); // alternate method of Forced Modem Sleep for an optional timed shutdown,
// with WiFi.forceSleepBegin(0xFFFFFFF); the modem sleeps until you wake it, with values <= 0xFFFFFFE it's timed
// delay(10); // it doesn't always go to sleep unless you delay(10); yield() wasn't reliable
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(true, 99); /* Using the same < 100 mS feature. If you drop the delay below 100, you
will see the effect of program time vs. delay() time on minimum amperage. Above ~ 97 (97% of the
time in delay) there is little change in amperage, so you need to spend maximum time in delay()
to get minimum amperage.*/
}
void runTest4() {
Serial.println(F("\n4th test - Automatic Light Sleep"));
Serial.println(F("reconnecting WiFi with forceSleepWake"));
Serial.println(F("Automatic Light Sleep begins after WiFi connects (LED blinks)"));
// on successive loops after power-on, WiFi shows 'connected' several seconds before Sleep happens
// and WiFi reconnects after the forceSleepWake more quickly
digitalWrite(LED, LOW); // visual cue that we're reconnecting WiFi
uint32_t wifiBegin = millis();
WiFi.forceSleepWake(); // reconnect with previous STA mode and connection settings
WiFi.setSleepMode(WIFI_LIGHT_SLEEP, 3); // Automatic Light Sleep, DTIM listen interval = 3
// at higher DTIM intervals you'll have a hard time establishing and maintaining a connection
wifiTimeout.reset(timeout);
while (((!WiFi.localIP()) || (WiFi.status() != WL_CONNECTED)) && (!wifiTimeout)) {
yield();
}
if ((WiFi.status() == WL_CONNECTED) && WiFi.localIP()) {
// won't go into Automatic Sleep without an active WiFi connection
float reConn = (millis() - wifiBegin);
Serial.print(F("WiFi connect time = "));
Serial.printf("%1.2f seconds\n", reConn / 1000);
readVoltage(); // read internal VCC
Serial.println(F("long press of the switch to continue"));
waitPushbutton(true, 350); /* Below 100 mS delay it only goes into 'Automatic Modem Sleep',
and below ~ 350 mS delay() the 'Automatic Light Sleep' is less frequent. Above 500 mS
delay() doesn't make significant improvement in power savings. */
} else {
Serial.println(F("no WiFi connection, test skipped"));
}
}
void runTest5() {
Serial.println(F("\n5th test - Timed Light Sleep, wake in 10 seconds"));
Serial.println(F("Press the button when you're ready to proceed"));
waitPushbutton(true, blinkDelay);
WiFi.mode(WIFI_OFF); // you must turn the modem off; using disconnect won't work
readVoltage(); // read internal VCC
printMillis(); // show millis() across sleep, including Serial.flush()
digitalWrite(LED, HIGH); // turn the LED off so they know the CPU isn't running
testPoint_HIGH; // testPoint LOW in callback tracks delay from testPoint HIGH to LOW
extern os_timer_t *timer_list;
timer_list = nullptr; // stop (but don't disable) the 4 OS timers
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKE_UP_PIN), GPIO_PIN_INTR_LOLEVEL); // GPIO wakeup (optional)
// only LOLEVEL or HILEVEL interrupts work, no edge, that's an SDK or CPU limitation
wifi_fpm_set_wakeup_cb(wakeupCallback); // set wakeup callback
// the callback is optional, but without it the modem will wake in 10 seconds then delay(10 seconds)
// with the callback the sleep time is only 10 seconds total, no extra delay() afterward
wifi_fpm_open();
wifi_fpm_do_sleep(10E6); // Sleep range = 10000 ~ 268,435,454 uS (0xFFFFFFE, 2^28-1)
delay(10e3 + 1); // delay needs to be 1 mS longer than sleep or it only goes into Modem Sleep
Serial.println(F("Woke up!")); // the interrupt callback hits before this is executed
}
void runTest6() {
Serial.println(F("\n6th test - Forced Light Sleep, wake with GPIO interrupt"));
Serial.flush();
WiFi.mode(WIFI_OFF); // you must turn the modem off; using disconnect won't work
digitalWrite(LED, HIGH); // turn the LED off so they know the CPU isn't running
readVoltage(); // read internal VCC
Serial.println(F("CPU going to sleep, pull WAKE_UP_PIN low to wake it (press the switch)"));
printMillis(); // show millis() across sleep, including Serial.flush()
testPoint_HIGH; // testPoint tracks latency from WAKE_UP_PIN LOW to testPoint LOW in callback
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKE_UP_PIN), GPIO_PIN_INTR_LOLEVEL);
// only LOLEVEL or HILEVEL interrupts work, no edge, that's an SDK or CPU limitation
wifi_fpm_set_wakeup_cb(wakeupCallback); // Set wakeup callback (optional)
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF); // only 0xFFFFFFF, any other value and it won't disconnect the RTC timer
delay(10); // it goes to sleep during this delay() and waits for an interrupt
Serial.println(F("Woke up!")); // the interrupt callback hits before this is executed*/
}
void runTest7() {
Serial.println(F("\n7th test - Deep Sleep for 10 seconds, reset and wake with RF_DEFAULT"));
initWiFi(); // initialize WiFi since we turned it off in the last test
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
while (!digitalRead(WAKE_UP_PIN)) { // wait for them to release the switch from the previous test
delay(10);
}
delay(50); // debounce time for the switch, pushbutton released
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
digitalWrite(LED, LOW); // turn the LED on, at least briefly
//WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // Forced Modem Sleep for a more Instant Deep Sleep,
// and no extended RFCAL as it goes into Deep Sleep
Serial.println(F("going into Deep Sleep now..."));
printMillis(); // show time difference across sleep
testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup()
ESP.deepSleep(10E6, WAKE_RF_DEFAULT); // good night! D0 fires a reset in 10 seconds...
// if you do ESP.deepSleep(0, mode); it needs a RESET to come out of sleep (RTC is disconnected)
// maximum timed Deep Sleep interval ~ 3 to 4 hours depending on the RTC timer, see the README
// the 2 uA GPIO amperage during Deep Sleep can't drive the LED so it's not lit now, although
// depending on the LED used, you might see it very dimly lit in a dark room during this test
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
void runTest8() {
Serial.println(F("\n8th test - in RF_DEFAULT, Deep Sleep for 10 seconds, reset and wake with RFCAL"));
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
//WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // Forced Modem Sleep for a more Instant Deep Sleep,
// and no extended RFCAL as it goes into Deep Sleep
Serial.println(F("going into Deep Sleep now..."));
Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message
testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup()
ESP.deepSleep(10E6, WAKE_RFCAL); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
void runTest9() {
Serial.println(F("\n9th test - in RFCAL, Deep Sleep Instant for 10 seconds, reset and wake with NO_RFCAL"));
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // Forced Modem Sleep for a more Instant Deep Sleep
Serial.println(F("going into Deep Sleep now..."));
Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message
testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup()
ESP.deepSleepInstant(10E6, WAKE_NO_RFCAL); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
void runTest10() {
Serial.println(F("\n10th test - in NO_RFCAL, Deep Sleep Instant for 10 seconds, reset and wake with RF_DISABLED"));
readVoltage(); // read internal VCC
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
//WiFi.mode(WIFI_SHUTDOWN); // Forced Modem Sleep for a more Instant Deep Sleep
Serial.println(F("going into Deep Sleep now..."));
Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message
testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup()
ESP.deepSleepInstant(10E6, WAKE_RF_DISABLED); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
void resetTests() {
readVoltage(); // read internal VCC
Serial.println(F("\nTests completed, in RF_DISABLED, press the switch to do an ESP.restart()"));
memset(&nv->wss, 0, sizeof(nv->wss) * 2); // wipe saved WiFi states, comment this if you want to keep them
waitPushbutton(false, 1000);
ESP.restart();
}
void waitPushbutton(bool usesDelay, unsigned int delayTime) { // loop until they press the switch
// note: 2 different modes, as 3 of the power saving modes need a delay() to activate fully
if (!usesDelay) { // quick interception of pushbutton press, no delay() used
blinkLED.reset(delayTime);
while (digitalRead(WAKE_UP_PIN)) { // wait for a pushbutton press
if (blinkLED) {
digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED
}
yield(); // this would be a good place for ArduinoOTA.handle();
}
} else { // long delay() for the 3 modes that need it, but it misses quick switch presses
while (digitalRead(WAKE_UP_PIN)) { // wait for a pushbutton press
digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED
delay(delayTime); // another good place for ArduinoOTA.handle();
if (delayTime < 100) {
altDelay.reset(100 - delayTime); // pad the time < 100 mS with some real CPU cycles
while (!altDelay) { // this simulates 'your program running', not delay() time
}
}
}
}
delay(50); // debounce time for the switch, pushbutton pressed
while (!digitalRead(WAKE_UP_PIN)) { // now wait for them to release the pushbutton
delay(10);
}
delay(50); // debounce time for the switch, pushbutton released
}
void readVoltage() { // read internal VCC
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.2f volts\n", volts / 1000);
}
void printMillis() {
Serial.print(F("millis() = ")); // show that millis() isn't correct across most Sleep modes
Serial.println(millis());
Serial.flush(); // needs a Serial.flush() else it may not print the whole message before sleeping
}
void updateRTCcrc() { // updates the reset count CRC
nv->rtcData.crc32 = crc32((uint8_t*) &nv->rtcData.rstCount, sizeof(nv->rtcData.rstCount));
}
void initWiFi() {
digitalWrite(LED, LOW); // give a visual indication that we're alive but busy with WiFi
uint32_t wifiBegin = millis(); // how long does it take to connect
if ((crc32((uint8_t*) &nv->rtcData.rstCount + 1, sizeof(nv->wss)) && !WiFi.shutdownValidCRC(&nv->wss))) {
// if good copy of wss, overwrite invalid (primary) copy
memcpy((uint32_t*) &nv->wss, (uint32_t*) &nv->rtcData.rstCount + 1, sizeof(nv->wss));
}
if (WiFi.shutdownValidCRC(&nv->wss)) { // if we have a valid WiFi saved state
memcpy((uint32_t*) &nv->rtcData.rstCount + 1, (uint32_t*) &nv->wss, sizeof(nv->wss)); // save a copy of it
Serial.println(F("resuming WiFi"));
}
if (!(WiFi.mode(WIFI_RESUME, &nv->wss))) { // couldn't resume, or no valid saved WiFi state yet
/* Explicitly set the ESP8266 as a WiFi-client (STAtion mode), otherwise by default it
would try to act as both a client and an access-point and could cause network issues
with other WiFi devices on your network. */
WiFi.persistent(false); // don't store the connection each time to save wear on the flash
WiFi.mode(WIFI_STA);
WiFi.setOutputPower(10); // reduce RF output power, increase if it won't connect
WiFi.config(staticIP, gateway, subnet); // if using static IP, enter parameters at the top
WiFi.begin(AP_SSID, AP_PASS);
Serial.print(F("connecting to WiFi "));
Serial.println(AP_SSID);
DEBUG_PRINT(F("my MAC: "));
DEBUG_PRINTLN(WiFi.macAddress());
}
wifiTimeout.reset(timeout);
while (((!WiFi.localIP()) || (WiFi.status() != WL_CONNECTED)) && (!wifiTimeout)) {
yield();
}
if ((WiFi.status() == WL_CONNECTED) && WiFi.localIP()) {
DEBUG_PRINTLN(F("WiFi connected"));
Serial.print(F("WiFi connect time = "));
float reConn = (millis() - wifiBegin);
Serial.printf("%1.2f seconds\n", reConn / 1000);
DEBUG_PRINT(F("WiFi Gateway IP: "));
DEBUG_PRINTLN(WiFi.gatewayIP());
DEBUG_PRINT(F("my IP address: "));
DEBUG_PRINTLN(WiFi.localIP());
} else {
Serial.println(F("WiFi timed out and didn't connect"));
}
WiFi.setAutoReconnect(true);
}

View File

@@ -0,0 +1,125 @@
# <center>Low-Power Demo</center>
There is a lot of confusion, out-of-date information, and poor or non-working examples of how to use the 5 basic low-power modes of the ESP8266. This demo code shows you how to use them reliably. If you're here for very low power, then the 2 Light Sleep modes and Deep Sleep are what you want.
The two relevant reference manuals from Espressif are the [Low-Power Solutions](https://www.espressif.com/sites/default/files/documentation/9b-esp8266-low_power_solutions__en.pdf) and the [Non-OS SDK API Reference](https://www.espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf). There is more information in the two PDFs than is presented here, so you'll want both of them for your reference.
The table below is an expanded version of Table 1.1 from the Low-Power Solutions PDF. The amperages listed are absolute minimums, and most people will not get that low with typical hardware and programs.
| item | Automatic Modem Sleep | Forced Modem Sleep | Automatic Light Sleep | Timed / Forced Light Sleep | Forced Deep Sleep |
|:---------------------:|:---------------------:|:------------------:|:---------------------:|:------------------:|:------------------:|
| WiFi connectivity | Connected | Disconnected | Connected | Disconnected | Disconnected |
| GPIO state | Unchanged | Unchanged | Unchanged | Unchanged | Low amperage (2 uA) |
| WiFi | ON | OFF | ON | OFF | OFF |
| System Clock | ON | ON | CYCLING | OFF | OFF |
| RTC | ON | ON | ON | ON (1) | ON (2) |
| CPU | ON | ON | ON | ON | OFF |
| Substrate Amperage | 15 mA | 15 mA | 1-15 mA (3) | 0.4 mA | 20 uA |
| Avg Amperage DTIM = 1 | 16.2 mA | | (1.8 mA) | | |
| Avg Amperage DTIM = 3 | 15.4 mA | | (0.9 mA) | | |
| Avg Amperage DTIM = 10 | 15.2 mA | | (0.55 mA) | | |
Notes:
(1) setting a sleep time of 0xFFFFFFF for Light Sleep disconnects the RTC, requiring a GPIO interrupt to wake the CPU
(2) setting a sleep time of 0 for Deep Sleep disconnects the RTC, requiring an external RESET to wake the CPU
(3) minimum amperage was never measured less than ~ 1 mA and is frequently 15 mA between TIM beacons
The Average Amperage with different DTIM settings is unverified, and will likely be higher in a real-world environment. All of the amperages listed in this README are for the ESP8266 chip only, compiled for 80 MHz CPU Frequency as 160 MHz uses even more power. Modules that have voltage regulators, USB chips, LEDs or other hardware will draw additional amperage.
---
## Basic Tests in the Demo
1. Unconfigured modem
2. Automatic Modem Sleep
3. Forced Modem Sleep
4. Automatic Light Sleep
5. Timed Light Sleep - stop the CPU for (x microseconds)
6. Forced Light Sleep, wake with GPIO interrupt
7. Deep Sleep for 10 seconds, wake with default modem power settings
8. Deep Sleep for 10 seconds, wake with RFCAL
9. Deep Sleep Instant for 10 seconds, wake with NO_RFCAL
10. Deep Sleep Instant for 10 seconds, wake with RF_DISABLED
---
### Test 1 - Unconfigured modem
This is typical for programs that don't use WiFi, and is a high continuous drain of at least 67 mA. This isn't a test as much as setting a baseline or reference point for comparing the power savings. You can stop during any test while the CPU is halted or the LED is blinking to measure the amperage.
### Test 2 - Automatic Modem Sleep
This is the default power saving mode when you have an active WiFi connection. You don't need to add anything to your code to get this mode. The only time the modem sleeps is when your program spends time in delay() frequently in STA mode. Any delay() time works as long as it happens frequently. The test is doing **delay(100)** to get the modem to sleep. While in delay() your sketch isn't doing anything worthwhile. Amperage during Automatic Modem Sleep is 15 mA minimum. Without a delay() the amperage is > 67 mA with brief spikes > 250-350 mA as transmissions occur. When the WiFi has traffic (even a couple of pings), the modem can turn on for over 2 seconds continuous at 67 mA, and it may stay on for a second after the traffic. In a high traffic environment you won't get any power savings with either of the 2 Automatic modes. Automatic Modem Sleep turns on 7-8 seconds after an active connection is established.
### Test 3 - Forced Modem Sleep
Turns off the modem (losing the connection), and reducing the amperage by > 50 mA. This test uses a WiFi library function and saves the WiFi connection state for faster reconnection later in the tests. Forced Modem Sleep is good if there is a long interval with no expected WiFi traffic, as you can do other things while only drawing 15 to 20 mA. The longer you spend in delay(), the closer the amperage approaches 15 mA. The test loops on delay(100) until you press the button. The CPU will be drawing 15 to 16 mA during the looped delay(), and 19-20 mA without a delay(). Doing WiFi.forceSleepWake() to wake the modem later can take twice as long as a re-initialize of WiFi.
### Test 4 - Automatic Light Sleep
Like Automatic Modem Sleep, with similar restrictions. Once configured it's immediately active when a connection is established. During periods of long delay() the amperage can drop to ~ 2 mA average. In a network with sparse traffic you might get something near 2-5 mA average. The LED blinks more slowly during this test as it's doing delay(350) to get the modem to sleep. With delay() times shorter than the TIM beacon interval (100 mS beacons for these tests) the modem only goes into Automatic Modem Sleep, and with a longer delay() it will go more fully into Automatic Light Sleep. Although the CPU clock is cycling on and off to achieve low power, millis() and micros() are correct. You can't stop OS_timers to get the low amperage recorded by Espressif as WiFi needs system timers running.
### Test 5 - Timed Light Sleep
Similar to timed Deep Sleep, but it wakes with an interrupt at the next line in your code and continues. The chip sleeps at 0.4 mA amperage until it is woken by the RTC timer. If you have a design that needs to be woken more often than every 2 seconds then you should consider using Timed Light Sleep. For sleep periods longer than 2 seconds, Deep Sleep will be more energy efficient. The chip wakes after an interrupt in about 3 to 5.5 mS (regardless of CPU speed), but WiFi was turned off to enter timed Light Sleep so you will need to re-initialize it if you are using WiFi. Any timers (including OS_timers and PWM) will keep the chip from going into timed Light Sleep, and it will fall through to the next line in your code if any timers are running. If you do a print() before Sleep, be sure to do Serial.flush() to stop the UART. The interrupt callback is recommended, and you can set a optional GPIO interrupt to wake the CPU as well as the RTC timer.
### Test 6 - Forced Light Sleep, wake with GPIO interrupt
Similar to ESP.deepSleep(0). The chip sleeps at 0.4 mA amperage until it is woken by an external interrupt. The only allowed interrupts are high level and low level; edge interrupts won't work. If you have a design that needs to be woken more often than every 2 seconds then you should consider using Forced Light Sleep. For sleep periods longer than 2 seconds, Deep Sleep will be more energy efficient. The chip wakes after an interrupt in about 3 to 5.5 mS (regardless of CPU speed), but WiFi was turned off to enter Forced Light Sleep so you will need to re-initialize it if you are using WiFi. Any user timers (including PWM) will keep the chip from going fully into Forced Light Sleep, and amperage will be ~ 2 mA with timers enabled.
### Test 7 - Deep Sleep, wake with RF_DEFAULT
In Deep Sleep almost everything is turned off, and the chip draws ~ 20 uA. If you have D0/GPIO16 connected to RST, you can use the RTC timer to wake the chip up at a timed interval. You can also wake it solely with an external RESET with ESP.deepSleep(0, wake option), which disconnects the timer. Waking with RF_DEFAULT means it will do an RFCAL if it needs to. Doing **ESP.deepSleep(time)** without the mode variable uses this wake mode. These first two Deep Sleep tests use the standard Deep Sleep function, so the WiFi connection is closed and the modem turned off, which takes up to 270 mS before Deep Sleep begins. Deep Sleep ends with a RESET, and the boot time after that is ~ 130 mS. Any Deep Sleep less than 2 seconds is wasting energy due to the modem shut-off and boot times, and Forced Light Sleep will be a better choice as it recovers in < 5.5 mS from the previous program state. The Deep Sleep tests will not go into Automatic Modem Sleep because delay() is not used.
Note that a RESET during Deep Sleep (either external or from D0/GPIO16) does not clear the GPIO pins; some of them hold their previous state. It's unknown how much else survives a reset, as it's not well documented.
### Test 8 - Deep Sleep, wake with RFCAL
Identical to the test above, but the modem always does an RF power calibration when booting. In normal use, most people would do WAKE_RF_DEFAULT instead to avoid the extra RFCAL power burst coming out of Deep Sleep if it's not needed. Note that most of the time both of these modes (WAKE_RF_DEFAULT and WAKE_RFCAL) do a 100 mS long RFCAL *before* going into Deep Sleep (the RFCAL after Deep Sleep is much shorter). If the modem is shut down, this long RFCAL doesn't happen.
### Test 9 - Deep Sleep Instant, wake with NO_RFCAL
This variation doesn't do an RF calibration on return, so power requirements will be slightly less. Additionally, frequently it immediately goes into Deep Sleep without turning off the modem (that's the INSTANT part). There's another bug in SDK 2, and the SDK functions the WiFi-class calls occasionally do a modem shut-down before Deep Sleep; it's not always Instant. When it doesn't do the modem shut-down it saves an extra 270 mS of power. With the modem turned off (Forced Modem Sleep) you **always** get an instant Deep Sleep; doing WiFi.mode(WIFI_OFF) doesn't help, as the SDK still spends 270 mS of time shutting the modem down before going into Deep Sleep.
### Test 10 - Deep Sleep Instant, wake with RF_DISABLED
This last variation also uses Deep Sleep Instant, but it wakes up with the modem disabled so amperage after Deep Sleep is only 15 mA. Each of the 4 WAKE modes has their own use, depending on what you need.
---
All of the Deep Sleep modes end with a RESET, so you must re-initialize nearly everything. You can store *some* information in the RTC memory to survive a Deep Sleep reset, which was done in this demo to illustrate the RTC memory. See the **RTCUserMemory** and **WiFiShutdown** examples for more on this feature.
Since SDK 2.1 the maximum Deep Sleep time has changed. The old maximum was based on uint32_t(micros) or ~71.58 minutes (2^32-1 microseconds). The new maximum is calculated from the RTC clock, which drifts with temperature from 5 to 7 timer ticks per microsecond, and will return a different number each time you read **system_rtc_clock_cali_proc()**. Depending on CPU and temperature, you will get between 3 and 4 hours maximum Deep Sleep. You can read the current theoretical maximum with **uint64_t deepSleepMax = ESP.deepSleepMax();** although you should set your time a little less than that due to the RTC drift. If you go over the maximum time when you finally enter Deep Sleep, it disconnects the timer and won't wake without an external RESET.
If you need a longer sleep time than 3 hours, you can pass zero as the time variable to Deep Sleep and it disconnects the RTC. The only way to wake it at that point is an external RESET; D0 can't do it. Both Forced Light Sleep and Deep Sleep(0) are woken by an external signal, so short delays are more efficient with Forced Light Sleep, and longer delays are more energy efficient with Deep Sleep.
---
### Lower Power without the WiFi library (Forced Modem Sleep):
If all you want to do is reduce power for a sketch that doesn't need WiFi, add these SDK 2 functions to your code:
At the top of your program, add:
```c
#include <user_interface.h>
```
and in setup() add:
```c
wifi_set_opmode_current(NULL_MODE); // set Wi-Fi working mode to unconfigured, don't save to flash
wifi_fpm_set_sleep_type(MODEM_SLEEP_T); // set the sleep type to modem sleep
wifi_fpm_open(); // enable Forced Modem Sleep
wifi_fpm_do_sleep(0xFFFFFFF); // force the modem to enter sleep mode
delay(10); // without a minimum of delay(1) here it doesn't reliably enter sleep
```
This code allows you to shut down the modem *without* loading the WiFi library, dropping your amperage by > 50 mA, or ~ 1/4th of the initial power. It doesn't time out at 268 seconds, as you might think from the (0xFFFFFFF). If you put a delay value smaller than 0xFFFFFFF it will end Modem Sleep at the number of uS you have entered: 10E6 would be 10 seconds of Modem Sleep. The Forced Modem Sleep test does the same thing with a WiFi library call that encapsulates something similar to the code above.
If you want to reduce the start-up power even more, see https://github.com/esp8266/Arduino/issues/6642#issuecomment-578462867
You can also use the Deep Sleep modes without loading the WiFi library, as Deep Sleep use ESP API functions. The Deep Sleep tests above turn the WiFi on to show you the differences after the 4 reset modes. but WiFi is not required for Deep Sleep.

View File

@@ -85,6 +85,16 @@ static bool time_machine_running = false;
// return 15000; // 15s
//}
#define PTM(w) \
Serial.print(" " #w "="); \
Serial.print(tm->tm_##w);
void printTm(const char* what, const tm* tm) {
Serial.print(what);
PTM(isdst); PTM(yday); PTM(wday);
PTM(year); PTM(mon); PTM(mday);
PTM(hour); PTM(min); PTM(sec);
}
void showTime() {
gettimeofday(&tv, nullptr);
@@ -124,13 +134,14 @@ void showTime() {
Serial.println((uint32_t)now);
// timezone and demo in the future
Serial.printf("timezone: %s\n", MYTZ);
Serial.printf("timezone: %s\n", getenv("TZ"));
// human readable
Serial.print("ctime: ");
Serial.print(ctime(&now));
#if LWIP_VERSION_MAJOR > 1
// LwIP v2 is able to list more details about the currently configured SNTP servers
for (int i = 0; i < SNTP_MAX_SERVERS; i++) {
IPAddress sntp = *sntp_getserver(i);
@@ -152,18 +163,6 @@ void showTime() {
Serial.println();
}
#define PTM(w) \
Serial.print(" " #w "="); \
Serial.print(tm->tm_##w);
void printTm(const char* what, const tm* tm) {
Serial.print(what);
PTM(isdst); PTM(yday); PTM(wday);
PTM(year); PTM(mon); PTM(mday);
PTM(hour); PTM(min); PTM(sec);
}
void time_is_set_scheduled() {
// everything is allowed in this function
@@ -205,8 +204,7 @@ void setup() {
// it will be used until NTP server will send us real current time
time_t rtc = RTC_UTC_TEST;
timeval tv = { rtc, 0 };
timezone tz = { 0, 0 };
settimeofday(&tv, &tz);
settimeofday(&tv, nullptr);
// install callback - called when settimeofday is called (by SNTP or us)
// once enabled (by DHCP), SNTP is updated every hour
@@ -214,8 +212,18 @@ void setup() {
// NTP servers may be overriden by your DHCP server for a more local one
// (see below)
// ----> Here is the ONLY ONE LINE needed in your sketch
configTime(MYTZ, "pool.ntp.org");
// Here is the ONLY ONE LINE needed in your sketch <----
// pick a value from TZ.h (search for this file in your filesystem) for MYTZ
// former configTime is still valid, here is the call for 7 hours to the west
// with an enabled 30mn DST
//configTime(7 * 3600, 3600 / 2, "pool.ntp.org");
// OPTIONAL: disable obtaining SNTP servers from DHCP
//sntp_servermode_dhcp(0); // 0: disable obtaining SNTP servers from DHCP (enabled by default)

View File

@@ -72,8 +72,8 @@ void setup() {
// using HardwareSerial0 pins,
// so we can still log to the regular usbserial chips
SoftwareSerial* ss = new SoftwareSerial;
ss->begin(SSBAUD, 3, 1);
SoftwareSerial* ss = new SoftwareSerial(3, 1);
ss->begin(SSBAUD);
ss->enableIntTx(false);
logger = ss;
logger->println();