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:
@@ -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);
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
|
Submodule libraries/ESP8266SdFat updated: af4ed0c5ec...b240d2231a
@@ -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
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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");
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
126
libraries/ESP8266WebServer/examples/PostServer/PostServer.ino
Normal file
126
libraries/ESP8266WebServer/examples/PostServer/PostServer.ino
Normal 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();
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
27
libraries/ESP8266WebServer/src/Uri.h
Normal file
27
libraries/ESP8266WebServer/src/Uri.h
Normal 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
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
54
libraries/ESP8266WebServer/src/uri/UriBraces.h
Normal file
54
libraries/ESP8266WebServer/src/uri/UriBraces.h
Normal 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
|
22
libraries/ESP8266WebServer/src/uri/UriGlob.h
Normal file
22
libraries/ESP8266WebServer/src/uri/UriGlob.h
Normal 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
|
54
libraries/ESP8266WebServer/src/uri/UriRegex.h
Normal file
54
libraries/ESP8266WebServer/src/uri/UriRegex.h
Normal 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
|
@@ -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() {
|
||||
|
@@ -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
|
||||
|
@@ -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
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -346,7 +346,7 @@ uint8_t WiFiClient::status()
|
||||
|
||||
WiFiClient::operator bool()
|
||||
{
|
||||
return connected();
|
||||
return available() || connected();
|
||||
}
|
||||
|
||||
IPAddress WiFiClient::remoteIP()
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 (¤t_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;
|
||||
};
|
||||
|
||||
|
||||
|
@@ -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
72
libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
Normal file → Executable 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
48
libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h
Normal file → Executable 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;
|
||||
};
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
|
13
libraries/GDBStub/examples/gdbstub_example.ino
Normal file
13
libraries/GDBStub/examples/gdbstub_example.ino
Normal 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);
|
||||
}
|
@@ -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));
|
||||
|
||||
|
@@ -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() { }
|
||||
|
Submodule libraries/LittleFS/lib/littlefs updated: abd90cb84c...ce2c01f098
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -3,3 +3,5 @@
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
||||
SDClass SD;
|
||||
#endif
|
||||
|
||||
void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
Submodule libraries/SoftwareSerial updated: 8f85d64900...94b2388a47
@@ -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
|
@@ -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() {
|
||||
|
@@ -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)
|
||||
#######################################
|
||||
|
||||
|
@@ -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();
|
||||
}
|
133
libraries/Ticker/src/Ticker.h
Normal file
133
libraries/Ticker/src/Ticker.h
Normal 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
|
64
libraries/esp8266/examples/CallBackList/CallBackGeneric.ino
Normal file
64
libraries/esp8266/examples/CallBackList/CallBackGeneric.ino
Normal 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() {
|
||||
}
|
45
libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino
Normal file
45
libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino
Normal 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() {
|
||||
}
|
439
libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino
Normal file
439
libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino
Normal 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);
|
||||
}
|
125
libraries/esp8266/examples/LowPowerDemo/README.md
Normal file
125
libraries/esp8266/examples/LowPowerDemo/README.md
Normal 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.
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user