12 Commits

Author SHA1 Message Date
Kuba Szczodrzyński
5c46939556 [release] v0.2.0
Some checks failed
PlatformIO Publish / publish (push) Has been cancelled
2022-04-30 19:11:29 +02:00
Kuba Szczodrzyński
222d58e973 [core] Add WebServer library from ESP32 2022-04-30 19:04:46 +02:00
Kuba Szczodrzyński
38343112a5 [core] Add WiFiServer(addr, port) constructor 2022-04-30 19:03:37 +02:00
Kuba Szczodrzyński
770a7bc4fa [core] Fix GDB init commands 2022-04-30 19:03:02 +02:00
Kuba Szczodrzyński
d6695f127d [core] Add FS API from ESP32 2022-04-30 19:02:23 +02:00
Kuba Szczodrzyński
35e5fc5173 [realtek-ambz] Fix WiFiClient copy assignment 2022-04-30 19:00:17 +02:00
Kuba Szczodrzyński
b58ea46c22 [docs] Fix debugging.md case 2022-04-29 23:19:42 +02:00
Kuba Szczodrzyński
470eb64051 [docs] Migrate to mkdocs 2022-04-29 23:16:32 +02:00
Kuba Szczodrzyński
6192e9be72 [docs] Describe libraries 2022-04-29 21:32:00 +02:00
Kuba Szczodrzyński
b518451888 [realtek-ambz] Fix enabling WiFi before scanning 2022-04-29 21:31:47 +02:00
Kuba Szczodrzyński
7b5dcdf07e [core] Add HTTPClient and WiFiMulti libs 2022-04-29 21:31:29 +02:00
Kuba Szczodrzyński
515d47f055 [core] Add MQTT library compatibility 2022-04-29 19:19:49 +02:00
57 changed files with 5457 additions and 69 deletions

21
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Deploy docs on GitHub Pages
on:
push:
branches:
- master
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: mkdocs.yml
EXTRA_PACKAGES: build-base
REQUIREMENTS: docs/requirements.txt

View File

@@ -1,6 +1,6 @@
# LibreTuya
<div align="center">
<div align="center" markdown>
[![Discord](https://img.shields.io/discord/967863521511608370?color=%235865F2&label=Discord&logo=discord&logoColor=white)](https://discord.gg/SyGCB9Xwtf)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/kuba2k2/platform/libretuya.svg)](https://registry.platformio.org/platforms/kuba2k2/libretuya)
@@ -25,7 +25,7 @@ LibreTuya also provides a common interface for all platform implementations. The
1. [Install PlatformIO](https://platformio.org/platformio-ide)
2. `platformio platform install libretuya`
3. Create a project, build it and upload!
4. See the [docs](docs/README.md) for any questions/problems.
4. See the [docs](https://kuba2k2.github.io/libretuya/) for any questions/problems.
## Board List
@@ -133,12 +133,14 @@ Platform name | Supported MCU(s)
### Realtek Ameba
The logic behind naming of Realtek chips and their series took me some time to figure out:
- RTL8xxxA - Ameba1/Ameba Series
- RTL8xxxB - AmebaZ Series
- RTL8xxxC - AmebaZ2/ZII Series
- RTL8xxxD - AmebaD Series
As such, there are numerous CPUs with the same numbers but different series, which makes them require different code and SDKs.
- [RTL8195AM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8195am)
- RTL8710AF (found in amb1_arduino)
- [RTL8711AM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8711am)
@@ -171,27 +173,38 @@ SoftwareSerial | ❌
SPI | ❌
Wire | ❌
**OTHER LIBRARIES** |
Wi-Fi Station | ✔️
Wi-Fi Access Point | ✔️
Wi-Fi Events | ❌
Wi-Fi STA/AP/Mixed | ✔️
Wi-Fi Client (SSL) | ✔️ (❌)
Wi-Fi Server | ✔️
Wi-Fi Events | ❌
IPv6 | ❌
HTTP Client (SSL) | ✔️ (❌)
HTTP Server | ✔️
NVS / Preferences | ❌
SPIFFS | ❌
BLE | -
HTTP | ❌
NTP | ❌
OTA | ❌
MDNS | ❌
MQTT |
MQTT |
SD | ❌
Legend:
Symbols:
- ✔️ working
- ❗ broken
- ✅ tested, external library
- ❓ untested
- ❗ broken
- ❌ not implemented (yet?)
- \- not applicable
Names:
- Core functions - stuff like delay(), millis(), yield(), etc.
- **CORE LIBRARIES** - included normally in all Arduino cores
- **OTHER LIBRARIES** - included in ESP32 core or downloadable
## License
See [LICENSE](LICENSE). Project is licensed under MIT License.

View File

@@ -0,0 +1,228 @@
/*
FS.cpp - file system wrapper
Copyright (c) 2015 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
*/
#include "FS.h"
using namespace fs;
size_t File::write(uint8_t c) {
if (!*this) {
return 0;
}
return _p->write(&c, 1);
}
time_t File::getLastWrite() {
if (!*this) {
return 0;
}
return _p->getLastWrite();
}
size_t File::write(const uint8_t *buf, size_t size) {
if (!*this) {
return 0;
}
return _p->write(buf, size);
}
int File::available() {
if (!*this) {
return false;
}
return _p->size() - _p->position();
}
int File::read() {
if (!*this) {
return -1;
}
uint8_t result;
if (_p->read(&result, 1) != 1) {
return -1;
}
return result;
}
size_t File::read(uint8_t *buf, size_t size) {
if (!*this) {
return -1;
}
return _p->read(buf, size);
}
int File::peek() {
if (!*this) {
return -1;
}
size_t curPos = _p->position();
int result = read();
seek(curPos, SeekSet);
return result;
}
void File::flush() {
if (!*this) {
return;
}
_p->flush();
}
bool File::seek(uint32_t pos, SeekMode mode) {
if (!*this) {
return false;
}
return _p->seek(pos, mode);
}
size_t File::position() const {
if (!*this) {
return 0;
}
return _p->position();
}
size_t File::size() const {
if (!*this) {
return 0;
}
return _p->size();
}
bool File::setBufferSize(size_t size) {
if (!*this) {
return 0;
}
return _p->setBufferSize(size);
}
void File::close() {
if (_p) {
_p->close();
_p = nullptr;
}
}
File::operator bool() const {
return _p != nullptr && *_p != false;
}
const char *File::path() const {
if (!*this) {
return nullptr;
}
return _p->path();
}
const char *File::name() const {
if (!*this) {
return nullptr;
}
return _p->name();
}
// to implement
boolean File::isDirectory(void) {
if (!*this) {
return false;
}
return _p->isDirectory();
}
File File::openNextFile(const char *mode) {
if (!*this) {
return File();
}
return _p->openNextFile(mode);
}
void File::rewindDirectory(void) {
if (!*this) {
return;
}
_p->rewindDirectory();
}
File FS::open(const String &path, const char *mode, const bool create) {
return open(path.c_str(), mode, create);
}
File FS::open(const char *path, const char *mode, const bool create) {
if (!_impl) {
return File();
}
return File(_impl->open(path, mode, create));
}
bool FS::exists(const char *path) {
if (!_impl) {
return false;
}
return _impl->exists(path);
}
bool FS::exists(const String &path) {
return exists(path.c_str());
}
bool FS::remove(const char *path) {
if (!_impl) {
return false;
}
return _impl->remove(path);
}
bool FS::remove(const String &path) {
return remove(path.c_str());
}
bool FS::rename(const char *pathFrom, const char *pathTo) {
if (!_impl) {
return false;
}
return _impl->rename(pathFrom, pathTo);
}
bool FS::rename(const String &pathFrom, const String &pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
bool FS::mkdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->mkdir(path);
}
bool FS::mkdir(const String &path) {
return mkdir(path.c_str());
}
bool FS::rmdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->rmdir(path);
}
bool FS::rmdir(const String &path) {
return rmdir(path.c_str());
}

152
arduino/libretuya/api/FS.h Normal file
View File

@@ -0,0 +1,152 @@
/*
FS.h - file system wrapper
Copyright (c) 2015 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
*/
#pragma once
#include <Arduino.h>
#include <memory>
namespace fs {
#define FILE_READ "r"
#define FILE_WRITE "w"
#define FILE_APPEND "a"
class File;
class FileImpl;
typedef std::shared_ptr<FileImpl> FileImplPtr;
class FSImpl;
typedef std::shared_ptr<FSImpl> FSImplPtr;
enum SeekMode { SeekSet = 0, SeekCur = 1, SeekEnd = 2 };
class File : public Stream {
public:
File(FileImplPtr p = FileImplPtr()) : _p(p) {
_timeout = 0;
}
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t read(uint8_t *buf, size_t size);
size_t readBytes(char *buffer, size_t length) {
return read((uint8_t *)buffer, length);
}
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
bool setBufferSize(size_t size);
void close();
operator bool() const;
time_t getLastWrite();
const char *path() const;
const char *name() const;
boolean isDirectory(void);
File openNextFile(const char *mode = FILE_READ);
void rewindDirectory(void);
protected:
FileImplPtr _p;
};
class FileImpl {
public:
virtual ~FileImpl() {}
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual size_t read(uint8_t *buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual bool setBufferSize(size_t size) = 0;
virtual void close() = 0;
virtual time_t getLastWrite() = 0;
virtual const char *path() const = 0;
virtual const char *name() const = 0;
virtual boolean isDirectory(void) = 0;
virtual FileImplPtr openNextFile(const char *mode) = 0;
virtual void rewindDirectory(void) = 0;
virtual operator bool() = 0;
};
class FS {
public:
FS(FSImplPtr impl) : _impl(impl) {}
File open(const char *path, const char *mode = FILE_READ, const bool create = false);
File open(const String &path, const char *mode = FILE_READ, const bool create = false);
bool exists(const char *path);
bool exists(const String &path);
bool remove(const char *path);
bool remove(const String &path);
bool rename(const char *pathFrom, const char *pathTo);
bool rename(const String &pathFrom, const String &pathTo);
bool mkdir(const char *path);
bool mkdir(const String &path);
bool rmdir(const char *path);
bool rmdir(const String &path);
protected:
FSImplPtr _impl;
};
class FSImpl {
public:
FSImpl() {}
virtual ~FSImpl() {}
virtual FileImplPtr open(const char *path, const char *mode, const bool create) = 0;
virtual bool exists(const char *path) = 0;
virtual bool rename(const char *pathFrom, const char *pathTo) = 0;
virtual bool remove(const char *path) = 0;
virtual bool mkdir(const char *path) = 0;
virtual bool rmdir(const char *path) = 0;
};
} // namespace fs
#ifndef FS_NO_GLOBALS
using fs::File;
using fs::FS;
using fs::SeekCur;
using fs::SeekEnd;
using fs::SeekMode;
using fs::SeekSet;
#endif // FS_NO_GLOBALS

View File

@@ -0,0 +1,9 @@
#include "LibreTuyaAPI.h"
__weak char *strdup(const char *s) {
size_t len = strlen(s) + 1;
void *newp = malloc(len);
if (newp == NULL)
return NULL;
return (char *)memcpy(newp, s, len);
}

View File

@@ -34,3 +34,9 @@ extern "C" {
__LINE__, \
"LibreTuya v" LT_VERSION_STR " on " LT_BOARD_STR ", compiled at " __DATE__ " " __TIME__ \
)
extern char *strdup(const char *);
// ArduinCore-API doesn't define these anymore
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define PGM_VOID_P const void *

View File

@@ -35,12 +35,15 @@ class IWiFiClient : public Client {
virtual size_t write(Stream &stream) = 0;
size_t write_P(PGM_P buffer, size_t size) {
return write((const uint8_t *)buffer, size);
}
virtual int fd() const = 0;
virtual int socket() = 0;
virtual int setTimeout(uint32_t seconds) = 0;
virtual IWiFiClient &operator=(const IWiFiClient &other) = 0;
virtual bool operator==(const IWiFiClient &other) const = 0;
bool operator==(const IWiFiClient &other) const;
operator bool() {
return connected();

View File

@@ -34,6 +34,8 @@ class IWiFiServer : public Print { // arduino::Server is useless anyway
IWiFiServer(uint16_t port = 80, uint8_t maxClients = 4) {}
IWiFiServer(const IPAddress &addr, uint16_t port = 80, uint8_t maxClients = 4) {}
~IWiFiServer() {
stop();
}

View File

@@ -49,17 +49,42 @@ void lt_log(const uint8_t level, const char *format, ...);
#define LT_F(...)
#endif
// ESP32 compat
#define log_printf(...) LT_I(__VA_ARGS__)
#define log_v(...) LT_V(__VA_ARGS__)
#define log_d(...) LT_D(__VA_ARGS__)
#define log_i(...) LT_I(__VA_ARGS__)
#define log_w(...) LT_W(__VA_ARGS__)
#define log_e(...) LT_E(__VA_ARGS__)
#define log_n(...) LT_E(__VA_ARGS__)
#define isr_log_v(...) LT_V(__VA_ARGS__)
#define isr_log_d(...) LT_D(__VA_ARGS__)
#define isr_log_i(...) LT_I(__VA_ARGS__)
#define isr_log_w(...) LT_W(__VA_ARGS__)
#define isr_log_e(...) LT_E(__VA_ARGS__)
#define isr_log_n(...) LT_E(__VA_ARGS__)
#define ESP_LOGV(...) LT_V(__VA_ARGS__)
#define ESP_LOGD(...) LT_D(__VA_ARGS__)
#define ESP_LOGI(...) LT_I(__VA_ARGS__)
#define ESP_LOGW(...) LT_W(__VA_ARGS__)
#define ESP_LOGE(...) LT_E(__VA_ARGS__)
#define ESP_EARLY_LOGV(...) LT_V(__VA_ARGS__)
#define ESP_EARLY_LOGD(...) LT_D(__VA_ARGS__)
#define ESP_EARLY_LOGI(...) LT_I(__VA_ARGS__)
#define ESP_EARLY_LOGW(...) LT_W(__VA_ARGS__)
#define ESP_EARLY_LOGE(...) LT_E(__VA_ARGS__)
#define LT_T_MOD(module, ...) \
do { \
if (module) { \
LT_T(__VA_ARGS__) \
LT_T(__VA_ARGS__); \
} \
} while (0)
#define LT_D_MOD(module, ...) \
do { \
if (module) { \
LT_D(__VA_ARGS__) \
LT_D(__VA_ARGS__); \
} \
} while (0)

View File

@@ -0,0 +1,3 @@
#pragma once
#include <api/FS.h>

View File

@@ -0,0 +1 @@
// nop

View File

@@ -0,0 +1,3 @@
#pragma once
#include <api/deprecated-avr-comp/avr/pgmspace.h>

View File

@@ -0,0 +1 @@
// nop

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
/**
* HTTPClient.h
*
* Created on: 02.11.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the HTTPClient for Arduino.
* Port to ESP32 by Evandro Luis Copercini (2017),
* changed fingerprints to CA verification.
*
* 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 HTTPClient_H_
#define HTTPClient_H_
#ifndef HTTPCLIENT_1_1_COMPATIBLE
#define HTTPCLIENT_1_1_COMPATIBLE
#endif
#include <Arduino.h>
#include <WiFiClient.h>
// #include <WiFiClientSecure.h>
#include <memory>
/// Cookie jar support
#include <vector>
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
/// HTTP client errors
#define HTTPC_ERROR_CONNECTION_REFUSED (-1)
#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
#define HTTPC_ERROR_NOT_CONNECTED (-4)
#define HTTPC_ERROR_CONNECTION_LOST (-5)
#define HTTPC_ERROR_NO_STREAM (-6)
#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
#define HTTPC_ERROR_TOO_LESS_RAM (-8)
#define HTTPC_ERROR_ENCODING (-9)
#define HTTPC_ERROR_STREAM_WRITE (-10)
#define HTTPC_ERROR_READ_TIMEOUT (-11)
/// size for the stream handling
#define HTTP_TCP_BUFFER_SIZE (1460)
/// HTTP codes see RFC7231
typedef enum {
HTTP_CODE_CONTINUE = 100,
HTTP_CODE_SWITCHING_PROTOCOLS = 101,
HTTP_CODE_PROCESSING = 102,
HTTP_CODE_OK = 200,
HTTP_CODE_CREATED = 201,
HTTP_CODE_ACCEPTED = 202,
HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_CODE_NO_CONTENT = 204,
HTTP_CODE_RESET_CONTENT = 205,
HTTP_CODE_PARTIAL_CONTENT = 206,
HTTP_CODE_MULTI_STATUS = 207,
HTTP_CODE_ALREADY_REPORTED = 208,
HTTP_CODE_IM_USED = 226,
HTTP_CODE_MULTIPLE_CHOICES = 300,
HTTP_CODE_MOVED_PERMANENTLY = 301,
HTTP_CODE_FOUND = 302,
HTTP_CODE_SEE_OTHER = 303,
HTTP_CODE_NOT_MODIFIED = 304,
HTTP_CODE_USE_PROXY = 305,
HTTP_CODE_TEMPORARY_REDIRECT = 307,
HTTP_CODE_PERMANENT_REDIRECT = 308,
HTTP_CODE_BAD_REQUEST = 400,
HTTP_CODE_UNAUTHORIZED = 401,
HTTP_CODE_PAYMENT_REQUIRED = 402,
HTTP_CODE_FORBIDDEN = 403,
HTTP_CODE_NOT_FOUND = 404,
HTTP_CODE_METHOD_NOT_ALLOWED = 405,
HTTP_CODE_NOT_ACCEPTABLE = 406,
HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
HTTP_CODE_REQUEST_TIMEOUT = 408,
HTTP_CODE_CONFLICT = 409,
HTTP_CODE_GONE = 410,
HTTP_CODE_LENGTH_REQUIRED = 411,
HTTP_CODE_PRECONDITION_FAILED = 412,
HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
HTTP_CODE_URI_TOO_LONG = 414,
HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
HTTP_CODE_EXPECTATION_FAILED = 417,
HTTP_CODE_MISDIRECTED_REQUEST = 421,
HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
HTTP_CODE_LOCKED = 423,
HTTP_CODE_FAILED_DEPENDENCY = 424,
HTTP_CODE_UPGRADE_REQUIRED = 426,
HTTP_CODE_PRECONDITION_REQUIRED = 428,
HTTP_CODE_TOO_MANY_REQUESTS = 429,
HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
HTTP_CODE_NOT_IMPLEMENTED = 501,
HTTP_CODE_BAD_GATEWAY = 502,
HTTP_CODE_SERVICE_UNAVAILABLE = 503,
HTTP_CODE_GATEWAY_TIMEOUT = 504,
HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_CODE_INSUFFICIENT_STORAGE = 507,
HTTP_CODE_LOOP_DETECTED = 508,
HTTP_CODE_NOT_EXTENDED = 510,
HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
} t_http_codes;
typedef enum { HTTPC_TE_IDENTITY, HTTPC_TE_CHUNKED } transferEncoding_t;
/**
* redirection follow mode.
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
* GET or HEAD methods will be redirected (using the same method),
* since the RFC requires end-user confirmation in other cases.
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
* regardless of a used method. New request will use the same method,
* and they will include the same body data and the same headers.
* In the sense of the RFC, it's just like every redirection is confirmed.
*/
typedef enum {
HTTPC_DISABLE_FOLLOW_REDIRECTS,
HTTPC_STRICT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
#endif
// cookie jar support
typedef struct {
String host; // host which tries to set the cookie
time_t date; // timestamp of the response that set the cookie
String name;
String value;
String domain;
String path = "";
struct {
time_t date = 0;
bool valid = false;
} expires;
struct {
time_t duration = 0;
bool valid = false;
} max_age;
bool http_only = false;
bool secure = false;
} Cookie;
typedef std::vector<Cookie> CookieJar;
class HTTPClient {
public:
HTTPClient();
~HTTPClient();
/*
* 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);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool begin(String url);
bool begin(String url, const char *CAcert);
bool begin(String host, uint16_t port, String uri = "/");
bool begin(String host, uint16_t port, String uri, const char *CAcert);
bool begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key);
#endif
void end(void);
bool connected(void);
void setReuse(bool reuse); /// keep-alive
void setUserAgent(const String &userAgent);
void setAuthorization(const char *user, const char *password);
void setAuthorization(const char *auth);
void setAuthorizationType(const char *authType);
void setConnectTimeout(int32_t connectTimeout);
void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String &url);
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int PATCH(uint8_t *payload, size_t size);
int PATCH(String payload);
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 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);
void addHeader(const String &name, const String &value, bool first = false, bool replace = true);
/// Response handling
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount);
String header(const char *name); // get request header value by name
String header(size_t i); // get request header value by number
String headerName(size_t i); // get request header name by number
int headers(); // get header count
bool hasHeader(const char *name); // check if header exists
int getSize(void);
const String &getLocation(void);
WiFiClient &getStream(void);
WiFiClient *getStreamPtr(void);
int writeToStream(Stream *stream);
// String getString(void);
static String errorToString(int error);
/// Cookie jar support
void setCookieJar(CookieJar *cookieJar);
void resetCookieJar();
void clearAllCookies();
protected:
struct RequestArgument {
String key;
String value;
};
bool beginInternal(String url, const char *expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);
bool connect(void);
bool sendHeader(const char *type);
int handleHeaderResponse();
int writeToStreamDataBlock(Stream *stream, int len);
/// Cookie jar support
void setCookie(String date, String headerValue);
bool generateCookieString(String *cookieString);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
TransportTraitsPtr _transportTraits;
std::unique_ptr<WiFiClient> _tcpDeprecated;
#endif
WiFiClient *_client = nullptr;
/// request handling
String _host;
uint16_t _port = 0;
int32_t _connectTimeout = -1;
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
bool _useHTTP10 = false;
bool _secure = false;
String _uri;
String _protocol;
String _headers;
String _userAgent = "ESP32HTTPClient";
String _base64Authorization;
String _authorizationType = "Basic";
/// Response handling
RequestArgument *_currentHeaders = nullptr;
size_t _headerKeysCount = 0;
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
/// Cookie jar support
CookieJar *_cookieJar = nullptr;
};
#endif /* HTTPClient_H_ */

View File

@@ -0,0 +1,197 @@
#include <stdlib.h>
#include <langinfo.h>
#include <time.h>
#include <ctype.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
char *strptime(const char *restrict s, const char *restrict f, struct tm *restrict tm)
{
int i, w, neg, adj, min, range, *dest, dummy;
const char *ex;
size_t len;
int want_century = 0, century = 0;
while (*f) {
if (*f != '%') {
if (isspace(*f)) for (; *s && isspace(*s); s++);
else if (*s != *f) return 0;
else s++;
f++;
continue;
}
f++;
if (*f == '+') f++;
if (isdigit(*f)) w=strtoul(f, (void *)&f, 10);
else w=-1;
adj=0;
switch (*f++) {
case 'a': case 'A':
dest = &tm->tm_wday;
min = ABDAY_1;
range = 7;
goto symbolic_range;
case 'b': case 'B': case 'h':
dest = &tm->tm_mon;
min = ABMON_1;
range = 12;
goto symbolic_range;
case 'c':
s = strptime(s, nl_langinfo(D_T_FMT), tm);
if (!s) return 0;
break;
case 'C':
dest = &century;
if (w<0) w=2;
want_century |= 2;
goto numeric_digits;
case 'd': case 'e':
dest = &tm->tm_mday;
min = 1;
range = 31;
goto numeric_range;
case 'D':
s = strptime(s, "%m/%d/%y", tm);
if (!s) return 0;
break;
case 'H':
dest = &tm->tm_hour;
min = 0;
range = 24;
goto numeric_range;
case 'I':
dest = &tm->tm_hour;
min = 1;
range = 12;
goto numeric_range;
case 'j':
dest = &tm->tm_yday;
min = 1;
range = 366;
goto numeric_range;
case 'm':
dest = &tm->tm_mon;
min = 1;
range = 12;
adj = 1;
goto numeric_range;
case 'M':
dest = &tm->tm_min;
min = 0;
range = 60;
goto numeric_range;
case 'n': case 't':
for (; *s && isspace(*s); s++);
break;
case 'p':
ex = nl_langinfo(AM_STR);
len = strlen(ex);
if (!strncasecmp(s, ex, len)) {
tm->tm_hour %= 12;
break;
}
ex = nl_langinfo(PM_STR);
len = strlen(ex);
if (!strncasecmp(s, ex, len)) {
tm->tm_hour %= 12;
tm->tm_hour += 12;
break;
}
return 0;
case 'r':
s = strptime(s, nl_langinfo(T_FMT_AMPM), tm);
if (!s) return 0;
break;
case 'R':
s = strptime(s, "%H:%M", tm);
if (!s) return 0;
break;
case 'S':
dest = &tm->tm_sec;
min = 0;
range = 61;
goto numeric_range;
case 'T':
s = strptime(s, "%H:%M:%S", tm);
if (!s) return 0;
break;
case 'U':
case 'W':
/* Throw away result, for now. (FIXME?) */
dest = &dummy;
min = 0;
range = 54;
goto numeric_range;
case 'w':
dest = &tm->tm_wday;
min = 0;
range = 7;
goto numeric_range;
case 'x':
s = strptime(s, nl_langinfo(D_FMT), tm);
if (!s) return 0;
break;
case 'X':
s = strptime(s, nl_langinfo(T_FMT), tm);
if (!s) return 0;
break;
case 'y':
dest = &tm->tm_year;
w = 2;
want_century |= 1;
goto numeric_digits;
case 'Y':
dest = &tm->tm_year;
if (w<0) w=4;
adj = 1900;
want_century = 0;
goto numeric_digits;
case '%':
if (*s++ != '%') return 0;
break;
default:
return 0;
numeric_range:
if (!isdigit(*s)) return 0;
*dest = 0;
for (i=1; i<=min+range && isdigit(*s); i*=10)
*dest = *dest * 10 + *s++ - '0';
if (*dest - min >= (unsigned)range) return 0;
*dest -= adj;
switch((char *)dest - (char *)tm) {
case offsetof(struct tm, tm_yday):
;
}
goto update;
numeric_digits:
neg = 0;
if (*s == '+') s++;
else if (*s == '-') neg=1, s++;
if (!isdigit(*s)) return 0;
for (*dest=i=0; i<w && isdigit(*s); i++)
*dest = *dest * 10 + *s++ - '0';
if (neg) *dest = -*dest;
*dest -= adj;
goto update;
symbolic_range:
for (i=2*range-1; i>=0; i--) {
ex = nl_langinfo(min+i);
len = strlen(ex);
if (strncasecmp(s, ex, len)) continue;
s += len;
*dest = i % range;
break;
}
if (i<0) return 0;
goto update;
update:
//FIXME
;
}
}
if (want_century) {
if (want_century & 2) tm->tm_year += century * 100 - 1900;
else if (tm->tm_year <= 68) tm->tm_year += 100;
}
return (char *)s;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <time.h>
extern char *strptime(const char *buf, const char *fmt, struct tm *tm);

View File

@@ -0,0 +1,56 @@
#pragma once
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M - SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE)
enum http_method {
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
typedef enum http_method HTTPMethod;
#define HTTP_ANY (HTTPMethod)(255)

View File

@@ -0,0 +1,605 @@
/*
Parsing.cpp - HTTP request parsing.
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/mimetable.h"
#ifndef WEBSERVER_MAX_POST_ARGS
#define WEBSERVER_MAX_POST_ARGS 32
#endif
#define __STR(a) #a
#define _STR(a) __STR(a)
const char *_http_method_str[] = {
#define XX(num, name, string) _STR(name),
HTTP_METHOD_MAP(XX)
#undef XX
};
static const char Content_Type[] PROGMEM = "Content-Type";
static const char filename[] PROGMEM = "filename";
static char *readBytesWithTimeout(WiFiClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
char *buf = nullptr;
dataLength = 0;
while (dataLength < maxLength) {
int tries = timeout_ms;
size_t newLength;
while (!(newLength = client.available()) && tries--)
delay(1);
if (!newLength) {
break;
}
if (!buf) {
buf = (char *)malloc(newLength + 1);
if (!buf) {
return nullptr;
}
} else {
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
if (!newBuf) {
free(buf);
return nullptr;
}
buf = newBuf;
}
client.readBytes(buf + dataLength, newLength);
dataLength += newLength;
buf[dataLength] = '\0';
}
return buf;
}
bool WebServer::_parseRequest(WiFiClient &client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
// reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value = String();
}
// First line of HTTP request looks like "GET /path HTTP/1.1"
// Retrieve the "/path" part by finding the spaces
int addr_start = req.indexOf(' ');
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
log_e("Invalid request: %s", req.c_str());
return false;
}
String methodStr = req.substring(0, addr_start);
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
int hasSearch = url.indexOf('?');
if (hasSearch != -1) {
searchStr = url.substring(hasSearch + 1);
url = url.substring(0, hasSearch);
}
_currentUri = url;
_chunked = false;
HTTPMethod method = HTTP_ANY;
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
for (size_t i = 0; i < num_methods; i++) {
if (methodStr == _http_method_str[i]) {
method = (HTTPMethod)i;
break;
}
}
if (method == HTTP_ANY) {
log_e("Unknown HTTP Method: %s", methodStr.c_str());
return false;
}
_currentMethod = method;
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
// attach handler
RequestHandler *handler;
for (handler = _firstHandler; handler; handler = handler->next()) {
if (handler->canHandle(_currentMethod, _currentUri))
break;
}
_currentHandler = handler;
String formData;
// below is needed only when POST type request
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
String boundaryStr;
String headerName;
String headerValue;
bool isForm = false;
bool isEncoded = false;
uint32_t contentLength = 0;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
using namespace mime;
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
isForm = false;
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
isForm = false;
isEncoded = true;
} else if (headerValue.startsWith(F("multipart/"))) {
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
boundaryStr.replace("\"", "");
isForm = true;
}
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
contentLength = headerValue.toInt();
} else if (headerName.equalsIgnoreCase(F("Host"))) {
_hostHeader = headerValue;
}
}
if (!isForm) {
size_t plainLength;
char *plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
}
if (contentLength > 0) {
if (isEncoded) {
// url encoded form
if (searchStr != "")
searchStr += '&';
searchStr += plainBuf;
}
_parseArguments(searchStr);
if (!isEncoded) {
// plain post json or other data
RequestArgument &arg = _currentArgs[_currentArgCount++];
arg.key = F("plain");
arg.value = String(plainBuf);
}
log_v("Plain: %s", plainBuf);
free(plainBuf);
} else {
// No content - but we can still have arguments in the URL.
_parseArguments(searchStr);
}
}
if (isForm) {
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
}
}
} else {
String headerName;
String headerValue;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase("Host")) {
_hostHeader = headerValue;
}
}
_parseArguments(searchStr);
}
client.flush();
log_v("Request: %s", url.c_str());
log_v(" Arguments: %s", searchStr.c_str());
return true;
}
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
for (int i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value = headerValue;
return true;
}
}
return false;
}
void WebServer::_parseArguments(String data) {
log_v("args: %s", data.c_str());
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
_currentArgs = new RequestArgument[1];
return;
}
_currentArgCount = 1;
for (int i = 0; i < (int)data.length();) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
log_v("args count: %d", _currentArgCount);
_currentArgs = new RequestArgument[_currentArgCount + 1];
int pos = 0;
int iarg;
for (iarg = 0; iarg < _currentArgCount;) {
int equal_sign_index = data.indexOf('=', pos);
int next_arg_index = data.indexOf('&', pos);
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
log_e("arg missing value: %d", iarg);
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
continue;
}
RequestArgument &arg = _currentArgs[iarg];
arg.key = urlDecode(data.substring(pos, equal_sign_index));
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
++iarg;
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
}
_currentArgCount = iarg;
log_v("args count: %d", _currentArgCount);
}
void WebServer::_uploadWriteByte(uint8_t b) {
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->currentSize = 0;
}
_currentUpload->buf[_currentUpload->currentSize++] = b;
}
int WebServer::_uploadReadByte(WiFiClient &client) {
int res = client.read();
if (res < 0) {
// keep trying until you either read a valid byte or timeout
unsigned long startMillis = millis();
long timeoutIntervalMillis = client.getTimeout();
boolean timedOut = false;
for (;;) {
if (!client.connected())
return -1;
// loosely modeled after blinkWithoutDelay pattern
while (!timedOut && !client.available() && client.connected()) {
delay(2);
timedOut = millis() - startMillis >= timeoutIntervalMillis;
}
res = client.read();
if (res >= 0) {
return res; // exit on a valid read
}
// NOTE: it is possible to get here and have all of the following
// assertions hold true
//
// -- client.available() > 0
// -- client.connected == true
// -- res == -1
//
// a simple retry strategy overcomes this which is to say the
// assertion is not permanent, but the reason that this works
// is elusive, and possibly indicative of a more subtle underlying
// issue
timedOut = millis() - startMillis >= timeoutIntervalMillis;
if (timedOut) {
return res; // exit on a timeout
}
}
}
return res;
}
bool WebServer::_parseForm(WiFiClient &client, String boundary, uint32_t len) {
(void)len;
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
String line;
int retry = 0;
do {
line = client.readStringUntil('\r');
++retry;
} while (line.length() == 0 && retry < 3);
client.readStringUntil('\n');
// start reading the form
if (line == ("--" + boundary)) {
if (_postArgs)
delete[] _postArgs;
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
_postArgsLen = 0;
while (1) {
String argName;
String argValue;
String argType;
String argFilename;
bool argIsFile = false;
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
int nameStart = line.indexOf('=');
if (nameStart != -1) {
argName = line.substring(nameStart + 2);
nameStart = argName.indexOf('=');
if (nameStart == -1) {
argName = argName.substring(0, argName.length() - 1);
} else {
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
argName = argName.substring(0, argName.indexOf('"'));
argIsFile = true;
log_v("PostArg FileName: %s", argFilename.c_str());
// use GET to set the filename if uploading using blob
if (argFilename == F("blob") && hasArg(FPSTR(filename)))
argFilename = arg(FPSTR(filename));
}
log_v("PostArg Name: %s", argName.c_str());
using namespace mime;
argType = FPSTR(mimeTable[txt].mimeType);
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
argType = line.substring(line.indexOf(':') + 2);
// skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
log_v("PostArg Type: %s", argType.c_str());
if (!argIsFile) {
while (1) {
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--" + boundary))
break;
if (argValue.length() > 0)
argValue += "\n";
argValue += line;
}
log_v("PostArg Value: %s", argValue.c_str());
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = argName;
arg.value = argValue;
if (line == ("--" + boundary + "--")) {
log_v("Done Parsing POST");
break;
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
return false;
}
} else {
_currentUpload.reset(new HTTPUpload());
_currentUpload->status = UPLOAD_FILE_START;
_currentUpload->name = argName;
_currentUpload->filename = argFilename;
_currentUpload->type = argType;
_currentUpload->totalSize = 0;
_currentUpload->currentSize = 0;
log_v(
"Start File: %s Type: %s",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str()
);
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->status = UPLOAD_FILE_WRITE;
int argByte = _uploadReadByte(client);
readfile:
while (argByte != 0x0D) {
if (argByte < 0)
return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if (argByte == 0x0A) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
uint32_t i = 0;
while (i < boundary.length()) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte == 0x0D) {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t j = 0;
while (j < i) {
_uploadWriteByte(endBuf[j++]);
}
goto readfile;
}
endBuf[i++] = (uint8_t)argByte;
}
if (strstr((const char *)endBuf, boundary.c_str()) != NULL) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->status = UPLOAD_FILE_END;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
log_v(
"End File: %s Type: %s Size: %d",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str(),
_currentUpload->totalSize
);
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if (line == "--") {
log_v("Done Parsing POST");
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t i = 0;
while (i < boundary.length()) {
_uploadWriteByte(endBuf[i++]);
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
}
}
}
}
int iarg;
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)
? (WEBSERVER_MAX_POST_ARGS - _postArgsLen)
: _currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++) {
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = new RequestArgument[_postArgsLen];
for (iarg = 0; iarg < _postArgsLen; iarg++) {
RequestArgument &arg = _currentArgs[iarg];
arg.key = _postArgs[iarg].key;
arg.value = _postArgs[iarg].value;
}
_currentArgCount = iarg;
if (_postArgs) {
delete[] _postArgs;
_postArgs = nullptr;
_postArgsLen = 0;
}
return true;
}
log_e("Error: line: %s", line.c_str());
return false;
}
String WebServer::urlDecode(const String &text) {
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
while (i < len) {
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len)) {
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
} else {
if (encodedChar == '+') {
decodedChar = ' ';
} else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
}
return decoded;
}
bool WebServer::_parseFormUploadAborted() {
_currentUpload->status = UPLOAD_FILE_ABORTED;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
return false;
}

View File

@@ -0,0 +1,29 @@
#pragma once
#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 void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
return _uri == requestUri;
}
};

View File

@@ -0,0 +1,717 @@
/*
WebServer.cpp - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include "FS.h"
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/RequestHandlersImpl.h"
// #include "mbedtls/md5.h"
#include <libb64/cencode.h>
static const char AUTHORIZATION_HEADER[] = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
static const char WWW_Authenticate[] = "WWW-Authenticate";
static const char Content_Length[] = "Content-Length";
WebServer::WebServer(IPAddress addr, int port)
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(addr=%s, port=%d)", addr.toString().c_str(), port);
}
WebServer::WebServer(int port)
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(port=%d)", port);
}
WebServer::~WebServer() {
_server.close();
if (_currentHeaders)
delete[] _currentHeaders;
RequestHandler *handler = _firstHandler;
while (handler) {
RequestHandler *next = handler->next();
delete handler;
handler = next;
}
}
void WebServer::begin() {
close();
_server.begin();
_server.setNoDelay(true);
}
void WebServer::begin(uint16_t port) {
close();
_server.begin(port);
_server.setNoDelay(true);
}
String WebServer::_extractParam(String &authReq, const String &param, const char delimit) {
int _begin = authReq.indexOf(param);
if (_begin == -1)
return "";
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
}
static String md5str(String &in) {
/* char out[33] = {0};
mbedtls_md5_context _ctx;
uint8_t i;
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL)
return String(out);
memset(_buf, 0x00, 16);
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, (const uint8_t *)in.c_str(), in.length());
mbedtls_md5_finish_ret(&_ctx, _buf);
for (i = 0; i < 16; i++) {
sprintf(out + (i * 2), "%02x", _buf[i]);
}
out[32] = 0;
free(_buf);
return String(out); */
return "";
}
bool WebServer::authenticate(const char *username, const char *password) {
if (hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
if (authReq.startsWith(F("Basic"))) {
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username) + strlen(password) + 1;
char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) {
authReq = "";
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) {
authReq = "";
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)) {
authReq = "";
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
} else if (authReq.startsWith(F("Digest"))) {
authReq = authReq.substring(7);
log_v("%s", authReq.c_str());
String _username = _extractParam(authReq, F("username=\""), '\"');
if (!_username.length() || _username != String(username)) {
authReq = "";
return false;
}
// extracting required parameters for RFC 2069 simpler Digest
String _realm = _extractParam(authReq, F("realm=\""), '\"');
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
String _uri = _extractParam(authReq, F("uri=\""), '\"');
String _response = _extractParam(authReq, F("response=\""), '\"');
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) ||
(!_opaque.length())) {
authReq = "";
return false;
}
if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
authReq = "";
return false;
}
// parameters for the RFC 2617 newer Digest
String _nc, _cnonce;
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_nc = _extractParam(authReq, F("nc="), ',');
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
}
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
log_v("Hash of user:realm:pass=%s", _H1.c_str());
String _H2 = "";
if (_currentMethod == HTTP_GET) {
_H2 = md5str(String(F("GET:")) + _uri);
} else if (_currentMethod == HTTP_POST) {
_H2 = md5str(String(F("POST:")) + _uri);
} else if (_currentMethod == HTTP_PUT) {
_H2 = md5str(String(F("PUT:")) + _uri);
} else if (_currentMethod == HTTP_DELETE) {
_H2 = md5str(String(F("DELETE:")) + _uri);
} else {
_H2 = md5str(String(F("GET:")) + _uri);
}
log_v("Hash of GET:uri=%s", _H2.c_str());
String _responsecheck = "";
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
} else {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
}
log_v("The Proper response=%s", _responsecheck.c_str());
if (_response == _responsecheck) {
authReq = "";
return true;
}
}
authReq = "";
}
return false;
}
String WebServer::_getRandomHexString() {
char buffer[33]; // buffer to hold 32 Hex Digit + /0
int i;
for (i = 0; i < 4; i++) {
sprintf(buffer + (i * 8), "%08x", rand());
}
return String(buffer);
}
void WebServer::requestAuthentication(HTTPAuthMethod mode, const char *realm, const String &authFailMsg) {
if (realm == NULL) {
_srealm = String(F("Login Required"));
} else {
_srealm = String(realm);
}
if (mode == BASIC_AUTH) {
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
} 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("\""))
);
}
using namespace mime;
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
}
void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void WebServer::addHandler(RequestHandler *handler) {
_addRequestHandler(handler);
}
void WebServer::_addRequestHandler(RequestHandler *handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
} else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void WebServer::serveStatic(const char *uri, FS &fs, const char *path, const char *cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {
WiFiClient client = _server.available();
if (!client) {
if (_nullDelay) {
delay(1);
}
return;
}
log_v("New client: client.localIP()=%s", client.localIP().toString().c_str());
_currentClient = client;
_currentStatus = HC_WAIT_READ;
_statusChange = millis();
}
bool keepCurrentClient = false;
bool callYield = false;
if (_currentClient.connected()) {
switch (_currentStatus) {
case HC_NONE:
// No-op to avoid C++ compiler warning
break;
case HC_WAIT_READ:
// Wait for data from client to become available
if (_currentClient.available()) {
if (_parseRequest(_currentClient)) {
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
// it must be divided by 1000
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
// Fix for issue with Chrome based browsers:
// https://github.com/espressif/arduino-esp32/issues/3652
// if (_currentClient.connected()) {
// _currentStatus = HC_WAIT_CLOSE;
// _statusChange = millis();
// keepCurrentClient = true;
// }
}
} else { // !_currentClient.available()
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
keepCurrentClient = true;
}
callYield = true;
}
break;
case HC_WAIT_CLOSE:
// Wait for client to close the connection
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
keepCurrentClient = true;
callYield = true;
}
}
}
if (!keepCurrentClient) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
_currentUpload.reset();
}
if (callYield) {
yield();
}
}
void WebServer::close() {
_server.close();
_currentStatus = HC_NONE;
if (!_headerKeysCount)
collectHeaders(0, 0);
}
void WebServer::stop() {
close();
}
void WebServer::sendHeader(const String &name, const String &value, bool first) {
String headerLine = name;
headerLine += F(": ");
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
} else {
_responseHeaders += headerLine;
}
}
void WebServer::setContentLength(const size_t contentLength) {
_contentLength = contentLength;
}
void WebServer::enableDelay(boolean value) {
_nullDelay = value;
}
void WebServer::enableCORS(boolean value) {
_corsEnabled = value;
}
void WebServer::enableCrossOrigin(boolean value) {
enableCORS(value);
}
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
response += String(code);
response += ' ';
response += _responseCodeToString(code);
response += "\r\n";
using namespace mime;
if (!content_type)
content_type = mimeTable[html].mimeType;
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
} else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion) { // HTTP/1.1 or above client
// let's do chunked
_chunked = true;
sendHeader(String(F("Accept-Ranges")), String(F("none")));
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
}
if (_corsEnabled) {
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
}
sendHeader(String(F("Connection")), String(F("close")));
response += _responseHeaders;
response += "\r\n";
_responseHeaders = "";
}
void WebServer::send(int code, const char *content_type, const String &content) {
String header;
// Can we asume the following?
// if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClientWrite(header.c_str(), header.length());
if (content.length())
sendContent(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
_currentClientWrite(header.c_str(), header.length());
sendContent_P(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void WebServer::send(int code, char *content_type, const String &content) {
send(code, (const char *)content_type, content);
}
void WebServer::send(int code, const String &content_type, const String &content) {
send(code, (const char *)content_type.c_str(), content);
}
void WebServer::sendContent(const String &content) {
sendContent(content.c_str(), content.length());
}
void WebServer::sendContent(const char *content, size_t contentLength) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", contentLength, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite(content, contentLength);
if (_chunked) {
_currentClient.write(footer, 2);
if (contentLength == 0) {
_chunked = false;
}
}
}
void WebServer::sendContent_P(PGM_P content) {
sendContent_P(content, strlen_P(content));
}
void WebServer::sendContent_P(PGM_P content, size_t size) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", size, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite_P(content, size);
if (_chunked) {
_currentClient.write(footer, 2);
if (size == 0) {
_chunked = false;
}
}
}
void WebServer::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType) {
using namespace mime;
setContentLength(fileSize);
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
contentType != String(FPSTR(mimeTable[none].mimeType))) {
sendHeader(F("Content-Encoding"), F("gzip"));
}
send(200, contentType, "");
}
String WebServer::pathArg(unsigned int i) {
if (_currentHandler != nullptr)
return _currentHandler->pathArg(i);
return "";
}
String WebServer::arg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return _postArgs[j].value;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return _currentArgs[i].value;
}
return "";
}
String WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return "";
}
String WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return "";
}
int WebServer::args() {
return _currentArgCount;
}
bool WebServer::hasArg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return true;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String WebServer::header(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
}
return "";
}
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 1;
if (_currentHeaders)
delete[] _currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
for (int i = 1; i < _headerKeysCount; i++) {
_currentHeaders[i].key = headerKeys[i - 1];
}
}
String WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return "";
}
String WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return "";
}
int WebServer::headers() {
return _headerKeysCount;
}
bool WebServer::hasHeader(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String WebServer::hostHeader() {
return _hostHeader;
}
void WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler) {
log_e("request handler not found");
} else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
if (!handled) {
log_e("request handler failed to handle request");
}
}
if (!handled && _notFoundHandler) {
_notFoundHandler();
handled = true;
}
if (!handled) {
using namespace mime;
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
handled = true;
}
if (handled) {
_finalizeResponse();
}
_currentUri = "";
}
void WebServer::_finalizeResponse() {
if (_chunked) {
sendContent("");
}
}
String WebServer::_responseCodeToString(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 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("");
}
}

View File

@@ -0,0 +1,224 @@
/*
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#pragma once
#include "HTTP_Method.h"
#include "Uri.h"
#include <WiFi.h>
#include <functional>
#include <memory>
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
#ifndef HTTP_UPLOAD_BUFLEN
#define HTTP_UPLOAD_BUFLEN 1436
#endif
#define HTTP_MAX_DATA_WAIT 5000 // ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 5000 // ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 // ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 // ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t)-1)
#define CONTENT_LENGTH_NOT_SET ((size_t)-2)
class WebServer;
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // file size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
#include "detail/RequestHandler.h"
namespace fs {
class FS;
}
class WebServer {
public:
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
virtual ~WebServer();
virtual void begin();
virtual void begin(uint16_t port);
virtual void handleClient();
virtual void close();
void stop();
bool authenticate(const char *username, const char *password);
void requestAuthentication(
HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String("")
);
typedef std::function<void(void)> THandlerFunction;
void on(const Uri &uri, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); // ufn handles file uploads
void addHandler(RequestHandler *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
void onFileUpload(THandlerFunction ufn); // handle file uploads
String uri() {
return _currentUri;
}
HTTPMethod method() {
return _currentMethod;
}
virtual WiFiClient client() {
return _currentClient;
}
HTTPUpload &upload() {
return *_currentUpload;
}
String pathArg(unsigned int i); // get request path argument by number
String arg(String name); // get request argument value by name
String arg(int i); // get request argument value by number
String argName(int i); // get request argument name by number
int args(); // get arguments count
bool hasArg(String name); // check if argument exists
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
String header(String name); // get request header value by name
String header(int i); // get request header value by number
String headerName(int i); // get request header name by number
int headers(); // get header count
bool hasHeader(String name); // check if header exists
String hostHeader(); // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char *content_type = NULL, const String &content = String(""));
void send(int code, char *content_type, const String &content);
void send(int code, const String &content_type, const String &content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void enableDelay(boolean value);
void enableCORS(boolean value = true);
void enableCrossOrigin(boolean value = true);
void setContentLength(const size_t contentLength);
void sendHeader(const String &name, const String &value, bool first = false);
void sendContent(const String &content);
void sendContent(const char *content, size_t contentLength);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
static String urlDecode(const String &text);
template <typename T>
size_t streamFile(T &file, const String &contentType) {
_streamFileCore(file.size(), file.name(), contentType);
return _currentClient.write(file);
}
protected:
virtual size_t _currentClientWrite(const char *b, size_t l) {
return _currentClient.write(b, l);
}
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
return _currentClient.write_P(b, l);
}
void _addRequestHandler(RequestHandler *handler);
void _handleRequest();
void _finalizeResponse();
bool _parseRequest(WiFiClient &client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient &client, String boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
int _uploadReadByte(WiFiClient &client);
void _prepareHeader(String &response, int code, const char *content_type, size_t contentLength);
bool _collectHeader(const char *headerName, const char *headerValue);
void _streamFileCore(const size_t fileSize, const String &fileName, const String &contentType);
String _getRandomHexString();
// for extracting Auth parameters
String _extractParam(String &authReq, const String &param, const char delimit = '"');
struct RequestArgument {
String key;
String value;
};
boolean _corsEnabled;
WiFiServer _server;
WiFiClient _currentClient;
HTTPMethod _currentMethod;
String _currentUri;
uint8_t _currentVersion;
HTTPClientStatus _currentStatus;
unsigned long _statusChange;
boolean _nullDelay;
RequestHandler *_currentHandler;
RequestHandler *_firstHandler;
RequestHandler *_lastHandler;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount;
RequestArgument *_currentArgs;
int _postArgsLen;
RequestArgument *_postArgs;
std::unique_ptr<HTTPUpload> _currentUpload;
int _headerKeysCount;
RequestArgument *_currentHeaders;
size_t _contentLength;
String _responseHeaders;
String _hostHeader;
bool _chunked;
String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls
};

View File

@@ -0,0 +1,53 @@
#pragma once
#include <assert.h>
#include <vector>
class RequestHandler {
public:
virtual ~RequestHandler() {}
virtual bool canHandle(HTTPMethod method, String uri) {
(void)method;
(void)uri;
return false;
}
virtual bool canUpload(String uri) {
(void)uri;
return false;
}
virtual bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) {
(void)server;
(void)requestMethod;
(void)requestUri;
return false;
}
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload) {
(void)server;
(void)requestUri;
(void)upload;
}
RequestHandler *next() {
return _next;
}
void next(RequestHandler *r) {
_next = r;
}
private:
RequestHandler *_next = nullptr;
protected:
std::vector<String> pathArgs;
public:
const String &pathArg(unsigned int i) {
assert(i < pathArgs.size());
return pathArgs[i];
}
};

View File

@@ -0,0 +1,149 @@
#pragma once
#include "RequestHandler.h"
#include "Uri.h"
#include "WString.h"
#include "mimetable.h"
using namespace mime;
class FunctionRequestHandler : public RequestHandler {
public:
FunctionRequestHandler(
WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method
)
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
_uri->initPathArgs(pathArgs);
}
~FunctionRequestHandler() {
delete _uri;
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (_method != HTTP_ANY && _method != requestMethod)
return false;
return _uri->canHandle(requestUri, pathArgs);
}
bool canUpload(String requestUri) override {
if (!_ufn || !canHandle(HTTP_POST, requestUri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
(void)server;
if (!canHandle(requestMethod, requestUri))
return false;
_fn();
return true;
}
void upload(WebServer &server, String requestUri, HTTPUpload &upload) override {
(void)server;
(void)upload;
if (canUpload(requestUri))
_ufn();
}
protected:
WebServer::THandlerFunction _fn;
WebServer::THandlerFunction _ufn;
Uri *_uri;
HTTPMethod _method;
};
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
File f = fs.open(path);
_isFile = (f && (!f.isDirectory()));
log_v(
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n",
path,
uri,
_isFile,
cache_header ? cache_header : ""
); // issue 5506 - cache_header can be nullptr
_baseUriLength = _uri.length();
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
String path(_path);
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/"))
requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
}
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
String contentType = getContentType(path);
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via
// content encoding when a non compressed is asked for if you point the the path to gzip you will serve the gzip
// as content type "application/x-gzip", not text or javascript etc...
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if (_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = _fs.open(path, "r");
if (!f || !f.available())
return false;
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
return true;
}
static String getContentType(const String &path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
return String(buff);
}
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
bool _isFile;
size_t _baseUriLength;
};

View File

@@ -0,0 +1,33 @@
#include "mimetable.h"
#include "pgmspace.h"
namespace mime {
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
const Entry mimeTable[maxType] = {
{".html", "text/html" },
{".htm", "text/html" },
{".css", "text/css" },
{".txt", "text/plain" },
{".js", "application/javascript" },
{".json", "application/json" },
{".png", "image/png" },
{".gif", "image/gif" },
{".jpg", "image/jpeg" },
{".ico", "image/x-icon" },
{".svg", "image/svg+xml" },
{".ttf", "application/x-font-ttf" },
{".otf", "application/x-font-opentype" },
{".woff", "application/font-woff" },
{".woff2", "application/font-woff2" },
{".eot", "application/vnd.ms-fontobject"},
{".sfnt", "application/font-sfnt" },
{".xml", "text/xml" },
{".pdf", "application/pdf" },
{".zip", "application/zip" },
{".gz", "application/x-gzip" },
{".appcache", "text/cache-manifest" },
{"", "application/octet-stream" }
};
} // namespace mime

View File

@@ -0,0 +1,38 @@
#pragma once
namespace mime {
enum type {
html,
htm,
css,
txt,
js,
json,
png,
gif,
jpg,
ico,
svg,
ttf,
otf,
woff,
woff2,
eot,
sfnt,
xml,
pdf,
zip,
gz,
appcache,
none,
maxType
};
struct Entry {
const char endsWith[16];
const char mimeType[32];
};
extern const Entry mimeTable[maxType];
} // namespace mime

View File

@@ -0,0 +1,61 @@
#pragma once
#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);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
int numParams = 0, start = 0;
do {
start = _uri.indexOf("{}", start);
if (start > 0) {
numParams++;
start += 2;
}
} while (start > 0);
pathArgs.resize(numParams);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
size_t uriLength = _uri.length();
unsigned int pathArgIndex = 0;
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[pathArgIndex] = requestUri.substring(requestUriIndex);
return pathArgs[pathArgIndex].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[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
requestUriIndex = (unsigned int)uriIndex;
}
pathArgIndex++;
}
return requestUriIndex >= requestUri.length();
}
};

View File

@@ -0,0 +1,19 @@
#pragma once
#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;
}
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "Uri.h"
#include <regex>
class UriRegex : public Uri {
public:
explicit UriRegex(const char *uri) : Uri(uri){};
explicit UriRegex(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriRegex(_uri);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
std::regex rgx((_uri + "|").c_str());
std::smatch matches;
std::string s{""};
std::regex_search(s, matches, rgx);
pathArgs.resize(matches.size() - 1);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
unsigned int pathArgIndex = 0;
std::regex rgx(_uri.c_str());
std::smatch matches;
std::string s(requestUri.c_str());
if (std::regex_search(s, matches, rgx)) {
for (size_t i = 1; i < matches.size(); ++i) { // skip first
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
pathArgIndex++;
}
return true;
}
return false;
}
};

View File

@@ -0,0 +1,238 @@
/**
*
* @file WiFiMulti.cpp
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "WiFiMulti.h"
#include <Arduino.h>
#include <limits.h>
#include <string.h>
WiFiMulti::WiFiMulti() {}
WiFiMulti::~WiFiMulti() {
for (uint32_t i = 0; i < APlist.size(); i++) {
WifiAPlist_t entry = APlist[i];
if (entry.ssid) {
free(entry.ssid);
}
if (entry.passphrase) {
free(entry.passphrase);
}
}
APlist.clear();
}
bool WiFiMulti::addAP(const char *ssid, const char *passphrase) {
WifiAPlist_t newAP;
if (!ssid || *ssid == 0x00 || strlen(ssid) > 31) {
// fail SSID too long or missing!
LT_E("SSID missing or too long");
return false;
}
if (passphrase && strlen(passphrase) > 64) {
// fail passphrase too long!
LT_E("Passphrase too long");
return false;
}
newAP.ssid = strdup(ssid);
if (!newAP.ssid) {
LT_E("Fail newAP.ssid == 0");
return false;
}
if (passphrase && *passphrase != 0x00) {
newAP.passphrase = strdup(passphrase);
if (!newAP.passphrase) {
LT_E("Fail newAP.passphrase == 0");
free(newAP.ssid);
return false;
}
} else {
newAP.passphrase = NULL;
}
APlist.push_back(newAP);
LT_V("Add SSID: %s", newAP.ssid);
return true;
}
uint8_t WiFiMulti::run(uint32_t connectTimeout) {
int8_t scanResult;
uint8_t status = WiFi.status();
if (status == WL_CONNECTED) {
for (uint32_t x = 0; x < APlist.size(); x++) {
if (WiFi.SSID() == APlist[x].ssid) {
return status;
}
}
WiFi.disconnect(false);
delay(10);
status = WiFi.status();
}
scanResult = WiFi.scanNetworks();
if (scanResult == WIFI_SCAN_RUNNING) {
// scan is running
return WL_NO_SSID_AVAIL;
} else if (scanResult >= 0) {
// scan done analyze
WifiAPlist_t bestNetwork{NULL, NULL};
int bestNetworkDb = INT_MIN;
uint8_t bestBSSID[6];
int32_t bestChannel = 0;
LT_I("Scan finished");
if (scanResult == 0) {
LT_I("No networks found");
} else {
LT_I("%d networks found", scanResult);
for (int8_t i = 0; i < scanResult; ++i) {
String ssid_scan;
int32_t rssi_scan;
WiFiAuthMode sec_scan;
uint8_t *BSSID_scan;
int32_t chan_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
bool known = false;
for (uint32_t x = APlist.size(); x > 0; x--) {
WifiAPlist_t entry = APlist[x - 1];
if (ssid_scan == entry.ssid) { // SSID match
known = true;
if (rssi_scan > bestNetworkDb) { // best network
if (sec_scan == WIFI_AUTH_OPEN ||
entry.passphrase) { // check for passphrase if not open wlan
bestNetworkDb = rssi_scan;
bestChannel = chan_scan;
memcpy((void *)&bestNetwork, (void *)&entry, sizeof(bestNetwork));
memcpy((void *)&bestBSSID, (void *)BSSID_scan, sizeof(bestBSSID));
}
}
break;
}
}
if (known) {
LT_D(
" ---> %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
} else {
LT_D(
" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
}
}
}
// clean up ram
WiFi.scanDelete();
if (bestNetwork.ssid) {
LT_I(
"Connecting to BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)",
bestBSSID[0],
bestBSSID[1],
bestBSSID[2],
bestBSSID[3],
bestBSSID[4],
bestBSSID[5],
bestNetwork.ssid,
bestChannel,
bestNetworkDb
);
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
status = WiFi.status();
auto startTime = millis();
// wait for connection, fail, or timeout
while (status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED &&
(millis() - startTime) <= connectTimeout) {
delay(10);
status = WiFi.status();
}
switch (status) {
case WL_CONNECTED:
LT_I("Connecting done");
LT_D("SSID: %s", WiFi.SSID().c_str());
LT_D("IP: %s", WiFi.localIP().toString().c_str());
LT_D("MAC: %s", WiFi.BSSIDstr().c_str());
LT_D("Channel: %d", WiFi.channel());
break;
case WL_NO_SSID_AVAIL:
LT_E("Connecting failed; AP not found");
break;
case WL_CONNECT_FAILED:
LT_E("Connecting failed");
break;
default:
LT_E("Connecting failed (%d)", status);
break;
}
} else {
LT_E("No matching network found!");
}
} else {
// start scan
LT_V("Delete old wifi config...");
WiFi.disconnect();
LT_D("Start scan");
// scan wifi async mode
WiFi.scanNetworks(true);
}
return status;
}

View File

@@ -0,0 +1,47 @@
/**
*
* @file ESP8266WiFiMulti.h
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#pragma once
#include "WiFi.h"
#include <vector>
typedef struct {
char *ssid;
char *passphrase;
} WifiAPlist_t;
class WiFiMulti {
public:
WiFiMulti();
~WiFiMulti();
bool addAP(const char *ssid, const char *passphrase = NULL);
uint8_t run(uint32_t connectTimeout = 10000);
private:
std::vector<WifiAPlist_t> APlist;
};

View File

@@ -0,0 +1,64 @@
/**
* base64.cpp
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP31B core for Arduino.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "Arduino.h"
extern "C" {
#include "libb64/cdecode.h"
#include "libb64/cencode.h"
}
#include "base64.h"
/**
* convert input data to base64
* @param data const uint8_t *
* @param length size_t
* @return String
*/
String base64::encode(const uint8_t * data, size_t length)
{
size_t size = base64_encode_expected_len(length) + 1;
char * buffer = (char *) malloc(size);
if(buffer) {
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
len = base64_encode_blockend((buffer + len), &_state);
String base64 = String(buffer);
free(buffer);
return base64;
}
return String("-FAIL-");
}
/**
* convert input data to base64
* @param text const String&
* @return String
*/
String base64::encode(const String& text)
{
return base64::encode((uint8_t *) text.c_str(), text.length());
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
class base64 {
public:
static String encode(const uint8_t *data, size_t length);
static String encode(const String &text);
};

View File

@@ -0,0 +1,7 @@
libb64: Base64 Encoding/Decoding Routines
======================================
Authors:
-------
Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com

View File

@@ -0,0 +1,29 @@
Copyright-Only Dedication (based on United States law)
or Public Domain Certification
The person or persons who have associated work with this document (the
"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of
his knowledge, the work of authorship identified is in the public domain of the
country from which the work is published, or (b) hereby dedicates whatever
copyright the dedicators holds in the work of authorship identified below (the
"Work") to the public domain. A certifier, moreover, dedicates any copyright
interest he may have in the associated work, and for these purposes, is
described as a "dedicator" below.
A certifier has taken reasonable steps to verify the copyright status of this
work. Certifier recognizes that his good faith efforts may not shield him from
liability if in fact the work certified is not in the public domain.
Dedicator makes this dedication for the benefit of the public at large and to
the detriment of the Dedicator's heirs and successors. Dedicator intends this
dedication to be an overt act of relinquishment in perpetuity of all present
and future rights under copyright law, whether vested or contingent, in the
Work. Dedicator understands that such relinquishment of all rights includes
the relinquishment of all rights to enforce (by lawsuit or otherwise) those
copyrights in the Work.
Dedicator recognizes that, once placed in the public domain, the Work may be
freely reproduced, distributed, transmitted, used, modified, built upon, or
otherwise exploited by anyone for any purpose, commercial or non-commercial,
and in any way, including by methods that have not yet been invented or
conceived.

View File

@@ -0,0 +1,99 @@
/*
cdecoder.c - c source to a base64 decoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cdecode.h"
#include <stdint.h>
static int base64_decode_value_signed(int8_t value_in){
static const int8_t decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
static const int8_t decoding_size = sizeof(decoding);
value_in -= 43;
if (value_in < 0 || value_in >= decoding_size) return -1;
return decoding[(int)value_in];
}
void base64_init_decodestate(base64_decodestate* state_in){
state_in->step = step_a;
state_in->plainchar = 0;
}
static int base64_decode_block_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out, base64_decodestate* state_in){
const int8_t* codechar = code_in;
int8_t* plainchar = plaintext_out;
int8_t fragment;
*plainchar = state_in->plainchar;
switch (state_in->step){
while (1){
case step_a:
do {
if (codechar == code_in+length_in){
state_in->step = step_a;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar = (fragment & 0x03f) << 2;
case step_b:
do {
if (codechar == code_in+length_in){
state_in->step = step_b;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x030) >> 4;
*plainchar = (fragment & 0x00f) << 4;
case step_c:
do {
if (codechar == code_in+length_in){
state_in->step = step_c;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03c) >> 2;
*plainchar = (fragment & 0x003) << 6;
case step_d:
do {
if (codechar == code_in+length_in){
state_in->step = step_d;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03f);
}
}
/* control should not reach here */
return plainchar - plaintext_out;
}
static int base64_decode_chars_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out){
base64_decodestate _state;
base64_init_decodestate(&_state);
int len = base64_decode_block_signed(code_in, length_in, plaintext_out, &_state);
if(len > 0) plaintext_out[len] = 0;
return len;
}
int base64_decode_value(char value_in){
return base64_decode_value_signed(*((int8_t *) &value_in));
}
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in){
return base64_decode_block_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out, state_in);
}
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out){
return base64_decode_chars_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out);
}

View File

@@ -0,0 +1,38 @@
/*
cdecode.h - c header for a base64 decoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CDECODE_H
#define BASE64_CDECODE_H
#define base64_decode_expected_len(n) ((n * 3) / 4)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_a, step_b, step_c, step_d
} base64_decodestep;
typedef struct {
base64_decodestep step;
char plainchar;
} base64_decodestate;
void base64_init_decodestate(base64_decodestate* state_in);
int base64_decode_value(char value_in);
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CDECODE_H */

View File

@@ -0,0 +1,102 @@
/*
cencoder.c - c source to a base64 encoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cencode.h"
void base64_init_encodestate(base64_encodestate* state_in)
{
state_in->step = step_A;
state_in->result = 0;
}
char base64_encode_value(char value_in)
{
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (value_in > 63) {
return '=';
}
return encoding[(int)value_in];
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step) {
while (1) {
case step_A:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_A;
return codechar - code_out;
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
case step_B:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_B;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
case step_C:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_C;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
}
}
/* control should not reach here */
return codechar - code_out;
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
char* codechar = code_out;
switch (state_in->step) {
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
*codechar = 0x00;
return codechar - code_out;
}
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out)
{
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block(plaintext_in, length_in, code_out, &_state);
return len + base64_encode_blockend((code_out + len), &_state);
}

View File

@@ -0,0 +1,41 @@
/*
cencode.h - c header for a base64 encoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CENCODE_H
#define BASE64_CENCODE_H
#define base64_encode_expected_len(n) ((((4 * n) / 3) + 3) & ~3)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_A, step_B, step_C
} base64_encodestep;
typedef struct {
base64_encodestep step;
char result;
int stepcount;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CENCODE_H */

View File

@@ -1,19 +1,30 @@
#include "WiFiClient.h"
#include "WiFiPriv.h"
class SocketHandle {
public:
int fd;
SocketHandle(int fd) : fd(fd) {}
~SocketHandle() {
lwip_close(fd);
}
};
WiFiClient::WiFiClient() {
LT_V_WC("WiFiClient()");
_sock = -1;
_connected = false;
_sock = NULL;
_rxBuffer = NULL;
_timeout = WIFI_CLIENT_CONNECT_TIMEOUT;
}
WiFiClient::WiFiClient(int sock) {
LT_V_WC("WiFiClient(%d)", sock);
_sock = sock;
_connected = true;
_rxBuffer = new LwIPRxBuffer(sock);
_sock = std::make_shared<SocketHandle>(sock);
_rxBuffer = std::make_shared<LwIPRxBuffer>(sock);
_timeout = WIFI_CLIENT_CONNECT_TIMEOUT;
}
@@ -22,17 +33,16 @@ WiFiClient::~WiFiClient() {
stop();
}
WiFiClient &WiFiClient::operator=(const IWiFiClient &other) {
WiFiClient &WiFiClient::operator=(const WiFiClient &other) {
stop();
WiFiClient *oth = (WiFiClient *)&other;
_sock = oth->_sock;
_connected = oth->_connected;
_rxBuffer = oth->_rxBuffer;
_connected = other._connected;
_sock = other._sock;
_rxBuffer = other._rxBuffer;
return *this;
}
bool WiFiClient::operator==(const IWiFiClient &other) const {
return _sock == other.fd() && remoteIP() == other.remoteIP() && remotePort() == other.remotePort();
bool IWiFiClient::operator==(const IWiFiClient &other) const {
return fd() == other.fd() && remoteIP() == other.remoteIP() && remotePort() == other.remotePort();
}
int WiFiClient::connect(IPAddress ip, uint16_t port) {
@@ -107,12 +117,9 @@ int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) {
lwip_fcntl(sock, F_SETFL, lwip_fcntl(sock, F_GETFL, 0) & ~O_NONBLOCK);
if (_sock != -1)
lwip_close(_sock);
_sock = sock;
_connected = true;
free(_rxBuffer);
_rxBuffer = new LwIPRxBuffer(_sock);
_sock = std::make_shared<SocketHandle>(sock);
_rxBuffer = std::make_shared<LwIPRxBuffer>(sock);
return 1;
}
@@ -149,18 +156,18 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size) {
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(_sock, &fdset);
FD_SET(fd(), &fdset);
tv.tv_sec = 0;
tv.tv_usec = WIFI_CLIENT_SELECT_TIMEOUT * 1000;
retry--;
if (lwip_select(_sock + 1, NULL, &fdset, NULL, &tv) < 0) {
if (lwip_select(fd() + 1, NULL, &fdset, NULL, &tv) < 0) {
LT_W("Select failed; errno=%d", errno);
return 0;
}
if (FD_ISSET(_sock, &fdset)) {
int res = lwip_send(_sock, buf, size, MSG_DONTWAIT);
if (FD_ISSET(fd(), &fdset)) {
int res = lwip_send(fd(), buf, size, MSG_DONTWAIT);
if (res > 0) {
written += res;
if (res >= size) {
@@ -184,7 +191,7 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size) {
}
int WiFiClient::available() {
if (!_connected)
if (!_connected || !_rxBuffer)
return 0;
int res = _rxBuffer->available();
if (_rxBuffer->failed()) {
@@ -194,17 +201,19 @@ int WiFiClient::available() {
}
int WiFiClient::fd() const {
return _sock;
if (!_sock)
return -1;
return _sock->fd;
}
int WiFiClient::socket() {
return _sock;
return fd();
}
int WiFiClient::setTimeout(uint32_t seconds) {
Client::setTimeout(seconds * 1000);
lwip_setsockopt(_sock, SOL_SOCKET, SO_RCVTIMEO, &_timeout, sizeof(_timeout));
return lwip_setsockopt(_sock, SOL_SOCKET, SO_SNDTIMEO, &_timeout, sizeof(_timeout));
lwip_setsockopt(fd(), SOL_SOCKET, SO_RCVTIMEO, &_timeout, sizeof(_timeout));
return lwip_setsockopt(fd(), SOL_SOCKET, SO_SNDTIMEO, &_timeout, sizeof(_timeout));
}
int WiFiClient::read() {
@@ -248,7 +257,7 @@ void WiFiClient::flush() {
if (!buf)
return;
while (len) {
res = lwip_recv(_sock, buf, LWIP_MIN(len, WIFI_CLIENT_FLUSH_BUF_SIZE), MSG_DONTWAIT);
res = lwip_recv(fd(), buf, LWIP_MIN(len, WIFI_CLIENT_FLUSH_BUF_SIZE), MSG_DONTWAIT);
if (res < 0) {
stop();
break;
@@ -260,18 +269,15 @@ void WiFiClient::flush() {
void WiFiClient::stop() {
LT_V_WC("stop()");
if (_sock != -1)
lwip_close(_sock);
_sock = -1;
_connected = false;
delete _rxBuffer;
_rxBuffer = NULL;
_sock = NULL;
_rxBuffer = NULL;
}
uint8_t WiFiClient::connected() {
if (_connected) {
uint8_t dummy;
if (lwip_recv(_sock, &dummy, 0, MSG_DONTWAIT) <= 0) {
if (lwip_recv(fd(), &dummy, 0, MSG_DONTWAIT) <= 0) {
switch (errno) {
case EWOULDBLOCK:
case ENOENT: // caused by vfs
@@ -313,7 +319,7 @@ uint16_t __attribute__((noinline)) getport(int sock, int (*func)(int, struct soc
}
IPAddress WiFiClient::remoteIP() const {
return getaddr(_sock, lwip_getpeername);
return getaddr(fd(), lwip_getpeername);
}
IPAddress WiFiClient::remoteIP(int fd) const {
@@ -321,7 +327,7 @@ IPAddress WiFiClient::remoteIP(int fd) const {
}
uint16_t WiFiClient::remotePort() const {
return getport(_sock, lwip_getpeername);
return getport(fd(), lwip_getpeername);
}
uint16_t WiFiClient::remotePort(int fd) const {
@@ -329,7 +335,7 @@ uint16_t WiFiClient::remotePort(int fd) const {
}
IPAddress WiFiClient::localIP() const {
return getaddr(_sock, lwip_getsockname);
return getaddr(fd(), lwip_getsockname);
}
IPAddress WiFiClient::localIP(int fd) const {
@@ -337,7 +343,7 @@ IPAddress WiFiClient::localIP(int fd) const {
}
uint16_t WiFiClient::localPort() const {
return getport(_sock, lwip_getsockname);
return getport(fd(), lwip_getsockname);
}
uint16_t WiFiClient::localPort(int fd) const {

View File

@@ -2,12 +2,15 @@
#include <api/LwIPRxBuffer.h>
#include <api/WiFiClient.h>
#include <memory>
class SocketHandle;
class WiFiClient : public IWiFiClient {
private:
int _sock;
bool _connected;
LwIPRxBuffer *_rxBuffer;
std::shared_ptr<SocketHandle> _sock;
std::shared_ptr<LwIPRxBuffer> _rxBuffer;
public:
WiFiClient();
@@ -35,9 +38,7 @@ class WiFiClient : public IWiFiClient {
void stop();
uint8_t connected();
WiFiClient &operator=(const IWiFiClient &other);
bool operator==(const IWiFiClient &other) const;
WiFiClient &operator=(const WiFiClient &other);
IPAddress remoteIP() const;
IPAddress remoteIP(int sock) const;
@@ -47,4 +48,6 @@ class WiFiClient : public IWiFiClient {
IPAddress localIP(int sock) const;
uint16_t localPort() const;
uint16_t localPort(int sock) const;
using Print::write;
};

View File

@@ -34,14 +34,19 @@ rtw_result_t WiFiClass::scanHandler(rtw_scan_handler_result_t *result) {
int16_t WiFiClass::scanNetworks(bool async, bool showHidden, bool passive, uint32_t maxMsPerChannel, uint8_t channel) {
if (_scanning)
return WIFI_SCAN_RUNNING;
if (wifi_mode == WIFI_MODE_NULL)
enableSTA(true);
scanDelete();
LT_I("Starting WiFi scan");
if (wifi_scan_networks(scanHandler, this) != RTW_SUCCESS)
return WIFI_SCAN_FAILED;
_scanning = true;
if (!async) {
LT_I("Waiting for results");
xSemaphoreTake(_scanSem, 1); // reset the semaphore quickly
xSemaphoreTake(_scanSem, pdMS_TO_TICKS(maxMsPerChannel * 20));
return _netCount;

View File

@@ -1,8 +1,9 @@
#include "WiFiServer.h"
#include "WiFiPriv.h"
WiFiServer::WiFiServer(uint16_t port, uint8_t maxClients)
: _sock(-1), _sockAccepted(-1), _port(port), _maxClients(maxClients), _active(false), _noDelay(false) {}
WiFiServer::WiFiServer(uint32_t addr, uint16_t port, uint8_t maxClients)
: _sock(-1), _sockAccepted(-1), _addr(addr), _port(port), _maxClients(maxClients), _active(false), _noDelay(false) {
}
WiFiServer::operator bool() {
return _active;
@@ -25,7 +26,7 @@ bool WiFiServer::begin(uint16_t port, bool reuseAddr) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_addr.s_addr = _addr;
addr.sin_port = htons(_port);
if (lwip_bind(_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
@@ -38,7 +39,8 @@ bool WiFiServer::begin(uint16_t port, bool reuseAddr) {
return false;
}
LT_I("Server running on :%hu", _port);
uint8_t *addrB = (uint8_t *)&_addr;
LT_I("Server running on %hhu.%hhu.%hhu.%hhu:%hu", addrB[0], addrB[1], addrB[2], addrB[3], _port);
lwip_fcntl(_sock, F_SETFL, O_NONBLOCK);
_active = true;

View File

@@ -8,13 +8,22 @@ class WiFiServer : public IWiFiServer<WiFiClient> {
private:
int _sock;
int _sockAccepted;
uint32_t _addr;
uint16_t _port;
uint8_t _maxClients;
bool _active;
bool _noDelay = false;
private:
WiFiServer(uint32_t addr, uint16_t port = 80, uint8_t maxClients = 4);
public:
WiFiServer(uint16_t port = 80, uint8_t maxClients = 4);
WiFiServer(uint16_t port = 80, uint8_t maxClients = 4) : WiFiServer((uint32_t)0, port, maxClients) {}
WiFiServer(int port = 80, uint8_t maxClients = 4) : WiFiServer((uint32_t)0, port, maxClients) {}
WiFiServer(const IPAddress &addr, uint16_t port = 80, uint8_t maxClients = 4)
: WiFiServer((uint32_t)addr, port, maxClients) {}
operator bool();

View File

@@ -19,7 +19,7 @@
],
"openocd_config": "amebaz.cfg",
"gdb_init": [
"mem 0x8000000 0x8010000 ro"
"mem 0x8000000 0x8200000 ro"
]
},
"flash": {

View File

@@ -2,7 +2,7 @@
[Product page](https://developer.tuya.com/en/docs/iot/wifiwr1module?id=K9605tc0k90t3)
- [Debugging](../../docs/platform/realtek/Debugging.md)
- [Debugging](../../docs/platform/realtek/debugging.md)
Parameter | Value
-------------|-------------------------

View File

@@ -16,8 +16,13 @@ env.Prepend(
CPPPATH=[
# fmt: off
join(API_DIR),
join(API_DIR, "api", "deprecated"),
join(LT_API_DIR),
join(LT_API_DIR, "compat"),
join(LT_API_DIR, "libraries", "base64"),
join(LT_API_DIR, "libraries", "HTTPClient"),
join(LT_API_DIR, "libraries", "WebServer"),
join(LT_API_DIR, "libraries", "WiFiMulti"),
# fmt: on
],
CPPDEFINES=[
@@ -34,9 +39,8 @@ sources_api = [
"+<" + API_DIR + "/api/Print.cpp>",
"+<" + API_DIR + "/api/Stream.cpp>",
"+<" + API_DIR + "/api/String.cpp>",
"+<" + LT_API_DIR + "/api/IPv6Address.cpp>",
"+<" + LT_API_DIR + "/api/lt_logger.c>",
"+<" + LT_API_DIR + "/api/LwIPRxBuffer.cpp>",
"+<" + LT_API_DIR + "/api/*.c*>",
"+<" + LT_API_DIR + "/libraries/**/*.c*>",
# fmt: on
]

View File

@@ -1,3 +0,0 @@
# LibreTuya documentation
TODO

View File

@@ -1,6 +1,6 @@
# LibreTuya API Configuration
Note: see [LibreTuyaConfig.h](../../arduino/libretuya/api/LibreTuyaConfig.h) for most options and their defaults.
Note: see [LibreTuyaConfig.h](../arduino/libretuya/api/LibreTuyaConfig.h) for most options and their defaults.
All options are configurable via C++ defines in PlatformIO project file. For example:
```ini

15
docs/libs-3rd-party.md Normal file
View File

@@ -0,0 +1,15 @@
# Libraries
A page outlining 3-rd party libraries compatible with LibreTuya.
To use some (most? (all?)) of these, a flag in `platformio.ini` is required to disable compatibility checks (because most libs are meant for ESP32/Arduino official framework):
```ini
[env:my_board]
lib_compat_mode = off
```
## MQTT
Tested with `realtek-ambz`.
```ini
lib_deps = 256dpi/MQTT@^2.5.0
```

25
docs/libs-built-in.md Normal file
View File

@@ -0,0 +1,25 @@
# Built-in libraries
(in alphabetical order)
## base64
- [Source](https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/base64.cpp): ESP32 Arduino Core
- [API](../arduino/libretuya/libraries/base64/base64.h)
Helper base64 encoder used in some libs taken from ESP32.
## HTTPClient
- [Source](https://github.com/espressif/arduino-esp32/tree/master/libraries/HTTPClient): ESP32 Arduino Core
- [API](../arduino/libretuya/libraries/HTTPClient/HTTPClient.h)
- [Examples](https://github.com/espressif/arduino-esp32/tree/master/libraries/HTTPClient/examples)
HTTP(S) client. Some reference may be found here: [https://links2004.github.io/Arduino/dd/d8d/class_h_t_t_p_client.html](https://links2004.github.io/Arduino/dd/d8d/class_h_t_t_p_client.html).
## WiFiMulti
- [Source](https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/src): ESP32 Arduino Core
- [API](../arduino/libretuya/libraries/WiFiMulti/WiFiMulti.h)
- [Docs](https://docs.espressif.com/projects/arduino-esp32/en/latest/api/wifi.html#wifimulti)
- Examples:
- [WiFiMulti](https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiMulti/WiFiMulti.ino)
- [WiFiClientBasic](https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiClientBasic/WiFiClientBasic.ino)
Class for selecting best available AP from a list of several ones.

View File

@@ -71,7 +71,6 @@ To disable auto reset before and after debugging:
```ini
[env:my_board]
debug_init_cmds =
mem 0x8000000 0x8010000 ro
target extended-remote $DEBUG_PORT ; remove this line if you're debugging locally
$INIT_BREAK
; monitor reset halt
@@ -82,7 +81,7 @@ debug_init_cmds =
## Technical details
GDB is first configured with `mem 0x8000000 0x8010000 ro` in order to mark flash section as read-only. This makes GDB use hardware breakpoints, as software BPs don't work on these boards.
GDB is first configured with `mem 0x8000000 0x8200000 ro` in order to mark flash memory as read-only. This makes GDB use hardware breakpoints, as software BPs don't work on these boards.
## More powerful playground

1
docs/requirements.txt Normal file
View File

@@ -0,0 +1 @@
mkdocs-same-dir

23
mkdocs.yml Normal file
View File

@@ -0,0 +1,23 @@
site_name: LibreTuya
docs_dir: .
site_url: https://kuba2k2.github.io/libretuya/
repo_url: https://github.com/kuba2k2/libretuya
theme:
name: material
plugins:
- same-dir
markdown_extensions:
- md_in_html
nav:
- "Home": "README.md"
- "Configuration": "docs/config.md"
- "Libraries":
- "Built-in": "docs/libs-built-in.md"
- "Third party": "docs/libs-3rd-party.md"
- "Platforms":
- "Realtek AmebaZ Series":
- "Boards":
- "WR3": "boards/wr3/README.md"
- "Debugging": "docs/platform/realtek/debugging.md"
- "Exception decoder": "docs/platform/realtek/exception-decoder.md"

View File

@@ -6,7 +6,7 @@
"type": "git",
"url": "https://github.com/kuba2k2/platformio-libretuya"
},
"version": "0.1.0",
"version": "0.2.0",
"frameworks": {
"realtek-ambz-sdk": {
"tilte": "Realtek AmebaZ - SDK",

View File

@@ -112,6 +112,7 @@ class LibretuyaPlatform(PlatformBase):
if "custom" not in debug["tools"]:
debug["tools"]["custom"] = {}
init = debug.get("gdb_init", [])
init += ["set mem inaccessible-by-default off"]
for link in protocols:
if link == "openocd":