mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
merge #2108
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
Version 7.2.1
|
||||
|
||||
From https://github.com/bblanchon/ArduinoJson/releases
|
||||
|
||||
MIT License (MIT)
|
||||
|
||||
Copyright © 2014-2024, Benoit BLANCHON
|
||||
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,14 +0,0 @@
|
||||
# AsyncTCP
|
||||
|
||||

|
||||
A fork of the [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) library by [@me-no-dev](https://github.com/me-no-dev) for [ESPHome](https://esphome.io).
|
||||
|
||||
### Async TCP Library for ESP32 Arduino
|
||||
|
||||
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
|
||||
|
||||
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||
|
||||
## AsyncClient and AsyncServer
|
||||
|
||||
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
Asynchronous TCP library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ASYNCTCP_H_
|
||||
#define ASYNCTCP_H_
|
||||
|
||||
#define ASYNCTCP_VERSION "3.2.14"
|
||||
#define ASYNCTCP_VERSION_MAJOR 3
|
||||
#define ASYNCTCP_VERSION_MINOR 2
|
||||
#define ASYNCTCP_VERSION_REVISION 14
|
||||
#define ASYNCTCP_FORK_mathieucarbou
|
||||
|
||||
#include "IPAddress.h"
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
#include "IPv6Address.h"
|
||||
#endif
|
||||
#include <functional>
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/ip6_addr.h"
|
||||
|
||||
#ifndef LIBRETINY
|
||||
#include "sdkconfig.h"
|
||||
extern "C" {
|
||||
#include "freertos/semphr.h"
|
||||
#include "lwip/pbuf.h"
|
||||
}
|
||||
#else
|
||||
extern "C" {
|
||||
#include <semphr.h>
|
||||
#include <lwip/pbuf.h>
|
||||
}
|
||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||
#define CONFIG_ASYNC_TCP_USE_WDT 0
|
||||
#endif
|
||||
|
||||
//If core is not defined, then we are running in Arduino or PIO
|
||||
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||
// Note default was 1 and previously set to 0 for EMS-ESP
|
||||
#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event
|
||||
#endif
|
||||
|
||||
// EMS-ESP: stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k
|
||||
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
|
||||
#define CONFIG_ASYNC_TCP_STACK_SIZE 6144
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_ASYNC_TCP_PRIORITY
|
||||
#define CONFIG_ASYNC_TCP_PRIORITY 5
|
||||
#endif
|
||||
|
||||
// EMS-ESP: maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177
|
||||
#ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE
|
||||
#define CONFIG_ASYNC_TCP_QUEUE_SIZE 32
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
|
||||
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
|
||||
#endif
|
||||
|
||||
class AsyncClient;
|
||||
|
||||
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
|
||||
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
|
||||
|
||||
typedef std::function<void(void *, AsyncClient *)> AcConnectHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, size_t len, uint32_t time)> AcAckHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, int8_t error)> AcErrorHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, void * data, size_t len)> AcDataHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, struct pbuf * pb)> AcPacketHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, uint32_t time)> AcTimeoutHandler;
|
||||
|
||||
struct tcp_pcb;
|
||||
struct ip_addr;
|
||||
|
||||
class AsyncClient {
|
||||
public:
|
||||
AsyncClient(tcp_pcb * pcb = 0);
|
||||
~AsyncClient();
|
||||
|
||||
AsyncClient & operator=(const AsyncClient & other);
|
||||
AsyncClient & operator+=(const AsyncClient & other);
|
||||
|
||||
bool operator==(const AsyncClient & other);
|
||||
|
||||
bool operator!=(const AsyncClient & other) {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool connect(const IPAddress & ip, uint16_t port);
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
bool connect(const IPv6Address & ip, uint16_t port);
|
||||
#endif
|
||||
bool connect(const char * host, uint16_t port);
|
||||
void close(bool now = false);
|
||||
void stop();
|
||||
int8_t abort();
|
||||
bool free();
|
||||
|
||||
bool canSend(); //ack is not pending
|
||||
size_t space(); //space available in the TCP window
|
||||
size_t add(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //add for sending
|
||||
bool send(); //send all data added with the method above
|
||||
|
||||
//write equals add()+send()
|
||||
size_t write(const char * data);
|
||||
size_t write(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
|
||||
|
||||
uint8_t state();
|
||||
bool connecting();
|
||||
bool connected();
|
||||
bool disconnecting();
|
||||
bool disconnected();
|
||||
bool freeable(); //disconnected or disconnecting
|
||||
|
||||
uint16_t getMss();
|
||||
|
||||
uint32_t getRxTimeout();
|
||||
void setRxTimeout(uint32_t timeout); //no RX data timeout for the connection in seconds
|
||||
|
||||
uint32_t getAckTimeout();
|
||||
void setAckTimeout(uint32_t timeout); //no ACK timeout for the last sent packet in milliseconds
|
||||
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
|
||||
void setKeepAlive(uint32_t ms, uint8_t cnt);
|
||||
|
||||
uint32_t getRemoteAddress();
|
||||
uint16_t getRemotePort();
|
||||
uint32_t getLocalAddress();
|
||||
uint16_t getLocalPort();
|
||||
#if LWIP_IPV6
|
||||
ip6_addr_t getRemoteAddress6();
|
||||
ip6_addr_t getLocalAddress6();
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
IPv6Address remoteIP6();
|
||||
IPv6Address localIP6();
|
||||
#else
|
||||
IPAddress remoteIP6();
|
||||
IPAddress localIP6();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//compatibility
|
||||
IPAddress remoteIP();
|
||||
uint16_t remotePort();
|
||||
IPAddress localIP();
|
||||
uint16_t localPort();
|
||||
|
||||
void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect
|
||||
void onDisconnect(AcConnectHandler cb, void * arg = 0); //disconnected
|
||||
void onAck(AcAckHandler cb, void * arg = 0); //ack received
|
||||
void onError(AcErrorHandler cb, void * arg = 0); //unsuccessful connect or error
|
||||
void onData(AcDataHandler cb, void * arg = 0); //data received (called if onPacket is not used)
|
||||
void onPacket(AcPacketHandler cb, void * arg = 0); //data received
|
||||
void onTimeout(AcTimeoutHandler cb, void * arg = 0); //ack timeout
|
||||
void onPoll(AcConnectHandler cb, void * arg = 0); //every 125ms when connected
|
||||
|
||||
void ackPacket(struct pbuf * pb); //ack pbuf from onPacket
|
||||
size_t ack(size_t len); //ack data that you have not acked using the method below
|
||||
void ackLater() {
|
||||
_ack_pcb = false;
|
||||
} //will not ack the current packet. Call from onData
|
||||
|
||||
const char * errorToString(int8_t error);
|
||||
const char * stateToString();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_poll(void * arg, struct tcp_pcb * tpcb);
|
||||
static int8_t _s_recv(void * arg, struct tcp_pcb * tpcb, struct pbuf * pb, int8_t err);
|
||||
static int8_t _s_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
||||
static int8_t _s_lwip_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
||||
static void _s_error(void * arg, int8_t err);
|
||||
static int8_t _s_sent(void * arg, struct tcp_pcb * tpcb, uint16_t len);
|
||||
static int8_t _s_connected(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
||||
static void _s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
|
||||
|
||||
int8_t _recv(tcp_pcb * pcb, pbuf * pb, int8_t err);
|
||||
tcp_pcb * pcb() {
|
||||
return _pcb;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _connect(ip_addr_t addr, uint16_t port);
|
||||
|
||||
tcp_pcb * _pcb;
|
||||
int8_t _closed_slot;
|
||||
|
||||
AcConnectHandler _connect_cb;
|
||||
void * _connect_cb_arg;
|
||||
AcConnectHandler _discard_cb;
|
||||
void * _discard_cb_arg;
|
||||
AcAckHandler _sent_cb;
|
||||
void * _sent_cb_arg;
|
||||
AcErrorHandler _error_cb;
|
||||
void * _error_cb_arg;
|
||||
AcDataHandler _recv_cb;
|
||||
void * _recv_cb_arg;
|
||||
AcPacketHandler _pb_cb;
|
||||
void * _pb_cb_arg;
|
||||
AcTimeoutHandler _timeout_cb;
|
||||
void * _timeout_cb_arg;
|
||||
AcConnectHandler _poll_cb;
|
||||
void * _poll_cb_arg;
|
||||
|
||||
bool _ack_pcb;
|
||||
uint32_t _tx_last_packet;
|
||||
uint32_t _rx_ack_len;
|
||||
uint32_t _rx_last_packet;
|
||||
uint32_t _rx_timeout;
|
||||
uint32_t _rx_last_ack;
|
||||
uint32_t _ack_timeout;
|
||||
uint16_t _connect_port;
|
||||
|
||||
int8_t _close();
|
||||
void _free_closed_slot();
|
||||
bool _allocate_closed_slot();
|
||||
int8_t _connected(tcp_pcb * pcb, int8_t err);
|
||||
void _error(int8_t err);
|
||||
int8_t _poll(tcp_pcb * pcb);
|
||||
int8_t _sent(tcp_pcb * pcb, uint16_t len);
|
||||
int8_t _fin(tcp_pcb * pcb, int8_t err);
|
||||
int8_t _lwip_fin(tcp_pcb * pcb, int8_t err);
|
||||
void _dns_found(struct ip_addr * ipaddr);
|
||||
|
||||
public:
|
||||
AsyncClient * prev;
|
||||
AsyncClient * next;
|
||||
};
|
||||
|
||||
class AsyncServer {
|
||||
public:
|
||||
AsyncServer(IPAddress addr, uint16_t port);
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
AsyncServer(IPv6Address addr, uint16_t port);
|
||||
#endif
|
||||
AsyncServer(uint16_t port);
|
||||
~AsyncServer();
|
||||
void onClient(AcConnectHandler cb, void * arg);
|
||||
void begin();
|
||||
void end();
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
uint8_t status();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_accept(void * arg, tcp_pcb * newpcb, int8_t err);
|
||||
static int8_t _s_accepted(void * arg, AsyncClient * client);
|
||||
|
||||
protected:
|
||||
uint16_t _port;
|
||||
bool _bind4 = false;
|
||||
bool _bind6 = false;
|
||||
IPAddress _addr;
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
IPv6Address _addr6;
|
||||
#endif
|
||||
bool _noDelay;
|
||||
tcp_pcb * _pcb;
|
||||
AcConnectHandler _connect_cb;
|
||||
void * _connect_cb_arg;
|
||||
|
||||
int8_t _accept(tcp_pcb * newpcb, int8_t err);
|
||||
int8_t _accepted(AsyncClient * client);
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCTCP_H_ */
|
||||
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,75 +0,0 @@
|
||||
# ESP Async WebServer
|
||||
|
||||
[](https://opensource.org/license/lgpl-3-0/)
|
||||
[](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
|
||||
[](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
|
||||
|
||||
Async Web Server for ESP31B
|
||||
|
||||
This is using <https://github.com/mathieucarbou/ESPAsyncWebServer>
|
||||
|
||||
This fork is based on <https://github.com/yubox-node-org/ESPAsyncWebServer> and includes all the concurrency fixes.
|
||||
|
||||
## Changes
|
||||
|
||||
- SPIFFSEditor is removed
|
||||
- Arduino Json 7 compatibility
|
||||
- Deployed in PlatformIO registry and Arduino IDE library manager
|
||||
- CI
|
||||
- Only supports ESP32
|
||||
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
|
||||
|
||||
## Documentation
|
||||
|
||||
Usage and API stays the same as the original library.
|
||||
Please look at the original libraries for more examples and documentation.
|
||||
|
||||
[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer)
|
||||
|
||||
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
|
||||
|
||||
The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr<std::vector<uint8_t>>` for WebSocket.
|
||||
|
||||
This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class.
|
||||
So you have the choice of which API to use.
|
||||
I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient.
|
||||
|
||||
Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized:
|
||||
|
||||
```cpp
|
||||
void send(JsonDocument& doc) {
|
||||
const size_t len = measureJson(doc);
|
||||
|
||||
// original API from me-no-dev
|
||||
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->get(), len);
|
||||
_ws->textAll(buffer);
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example for serializing a Json document in a more optimized way, and compatible with both forks:
|
||||
|
||||
```cpp
|
||||
void send(JsonDocument& doc) {
|
||||
const size_t len = measureJson(doc);
|
||||
|
||||
#if defined(ASYNCWEBSERVER_FORK_mathieucarbou)
|
||||
|
||||
// this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project
|
||||
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->data(), len);
|
||||
_ws->textAll(std::move(buffer));
|
||||
|
||||
#else
|
||||
|
||||
// original API from me-no-dev
|
||||
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->get(), len);
|
||||
_ws->textAll(buffer);
|
||||
|
||||
#endif
|
||||
}
|
||||
```
|
||||
@@ -1,405 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. 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
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
#include "AsyncEventSource.h"
|
||||
|
||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
String ev = "";
|
||||
|
||||
if(reconnect){
|
||||
ev += "retry: ";
|
||||
ev += String(reconnect);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(id){
|
||||
ev += "id: ";
|
||||
ev += String(id);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(event != NULL){
|
||||
ev += "event: ";
|
||||
ev += String(event);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(message != NULL){
|
||||
size_t messageLen = strlen(message);
|
||||
char * lineStart = (char *)message;
|
||||
char * lineEnd;
|
||||
do {
|
||||
char * nextN = strchr(lineStart, '\n');
|
||||
char * nextR = strchr(lineStart, '\r');
|
||||
if(nextN == NULL && nextR == NULL){
|
||||
size_t llen = ((char *)message + messageLen) - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += "data: ";
|
||||
ev += ldata;
|
||||
ev += "\r\n\r\n";
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = (char *)message + messageLen;
|
||||
} else {
|
||||
char * nextLine = NULL;
|
||||
if(nextN != NULL && nextR != NULL){
|
||||
if(nextR < nextN){
|
||||
lineEnd = nextR;
|
||||
if(nextN == (nextR + 1))
|
||||
nextLine = nextN + 1;
|
||||
else
|
||||
nextLine = nextR + 1;
|
||||
} else {
|
||||
lineEnd = nextN;
|
||||
if(nextR == (nextN + 1))
|
||||
nextLine = nextR + 1;
|
||||
else
|
||||
nextLine = nextN + 1;
|
||||
}
|
||||
} else if(nextN != NULL){
|
||||
lineEnd = nextN;
|
||||
nextLine = nextN + 1;
|
||||
} else {
|
||||
lineEnd = nextR;
|
||||
nextLine = nextR + 1;
|
||||
}
|
||||
|
||||
size_t llen = lineEnd - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += "data: ";
|
||||
ev += ldata;
|
||||
ev += "\r\n";
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = nextLine;
|
||||
if(lineStart == ((char *)message + messageLen))
|
||||
ev += "\r\n";
|
||||
}
|
||||
} while(lineStart < ((char *)message + messageLen));
|
||||
}
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
// Message
|
||||
|
||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
||||
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
||||
{
|
||||
_data = (uint8_t*)malloc(_len+1);
|
||||
if(_data == nullptr){
|
||||
_len = 0;
|
||||
} else {
|
||||
memcpy(_data, data, len);
|
||||
_data[_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
||||
if(_data != NULL)
|
||||
free(_data);
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::ack(size_t len) {
|
||||
// If the whole message is now acked...
|
||||
if(_acked + len > _len){
|
||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||
const size_t extra = _acked + len - _len;
|
||||
_acked = _len;
|
||||
return extra;
|
||||
}
|
||||
// Return that no extra bytes left.
|
||||
_acked += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::write_buffer(AsyncClient *client) {
|
||||
if (!client->canSend())
|
||||
return 0;
|
||||
const size_t len = _len - _sent;
|
||||
if(client->space() < len){
|
||||
return 0;
|
||||
}
|
||||
size_t sent = client->add((const char *)_data, len);
|
||||
_sent += sent;
|
||||
return sent;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
||||
size_t sent = write_buffer(client);
|
||||
client->send();
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
||||
{
|
||||
_client = request->client();
|
||||
_server = server;
|
||||
_lastId = 0;
|
||||
if(request->hasHeader("Last-Event-ID"))
|
||||
_lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());
|
||||
|
||||
_client->setRxTimeout(0);
|
||||
_client->onError(NULL, NULL);
|
||||
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
||||
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||
_client->onData(NULL, NULL);
|
||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||
|
||||
_server->_addClient(this);
|
||||
delete request;
|
||||
|
||||
_client->setNoDelay(true);
|
||||
}
|
||||
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
||||
_messageQueue.free();
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
||||
if(dataMessage == NULL)
|
||||
return;
|
||||
if(!connected()){
|
||||
delete dataMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
||||
ets_printf("AsyncEventSourceClient: ERROR: Queue is full, communications too slow, dropping event");
|
||||
delete dataMessage;
|
||||
} else {
|
||||
_messageQueue.add(dataMessage);
|
||||
}
|
||||
if(_client->canSend())
|
||||
_runQueue();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
||||
_runQueue();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onPoll(){
|
||||
if(!_messageQueue.isEmpty()){
|
||||
_runQueue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
||||
_client->close(true);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onDisconnect(){
|
||||
_client = NULL;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::close(){
|
||||
if(_client != NULL)
|
||||
_client->close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::write(const char * message, size_t len){
|
||||
_queueMessage(new AsyncEventSourceMessage(message, len));
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_runQueue(){
|
||||
#if defined(ESP32)
|
||||
if(!this->_messageQueue_mutex.try_lock()) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if(this->_messageQueue_processing){
|
||||
return;
|
||||
}
|
||||
this->_messageQueue_processing = true;
|
||||
#endif // ESP32
|
||||
|
||||
size_t total_bytes_written = 0;
|
||||
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
|
||||
{
|
||||
if(!(*i)->sent()) {
|
||||
size_t bytes_written = (*i)->write_buffer(_client);
|
||||
total_bytes_written += bytes_written;
|
||||
if(bytes_written == 0)
|
||||
break;
|
||||
// todo: there is a further optimization to write a partial event to squeeze the last few bytes into the outgoing tcp send buffer, in
|
||||
// fact all of this code is already set up to do so, it's only write_buffer that needs to be updated to allow it instead of
|
||||
// returning zero when the full event won't fit into what's left of the buffer
|
||||
// todo: windows is taking 40-50ms to send an ack back while it waits for more data which won't come since this code must wait for ack first
|
||||
// due to system resource limitations - if the dashboard javascript just sends a single byte back per event received (which this
|
||||
// code would of course throw away as meaningless) then windows (or whatever other host runs the webbrower) will piggyback an ack
|
||||
// onto that outgoing packet for us, reducing roundtrip ack latency and potentially as much as trippling throughput again
|
||||
// (measured: ESP-01: 20ms to send another packet after ack received, windows: 40-50ms to ack after receiving a packet)
|
||||
}
|
||||
}
|
||||
if(total_bytes_written > 0)
|
||||
_client->send();
|
||||
|
||||
size_t len = total_bytes_written;
|
||||
while(len && !_messageQueue.isEmpty()){
|
||||
len = _messageQueue.front()->ack(len);
|
||||
if(_messageQueue.front()->finished()){
|
||||
_messageQueue.remove(_messageQueue.front());
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
this->_messageQueue_mutex.unlock();
|
||||
#else
|
||||
this->_messageQueue_processing = false;
|
||||
#endif // ESP32
|
||||
}
|
||||
|
||||
|
||||
// Handler
|
||||
|
||||
AsyncEventSource::AsyncEventSource(const String& url)
|
||||
: _url(url)
|
||||
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
|
||||
, _connectcb(NULL)
|
||||
{}
|
||||
|
||||
AsyncEventSource::~AsyncEventSource(){
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
||||
/*char * temp = (char *)malloc(2054);
|
||||
if(temp != NULL){
|
||||
memset(temp+1,' ',2048);
|
||||
temp[0] = ':';
|
||||
temp[2049] = '\r';
|
||||
temp[2050] = '\n';
|
||||
temp[2051] = '\r';
|
||||
temp[2052] = '\n';
|
||||
temp[2053] = 0;
|
||||
client->write((const char *)temp, 2053);
|
||||
free(temp);
|
||||
}*/
|
||||
|
||||
_clients.add(client);
|
||||
if(_connectcb)
|
||||
_connectcb(client);
|
||||
}
|
||||
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
||||
_clients.remove(client);
|
||||
}
|
||||
|
||||
void AsyncEventSource::close(){
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected())
|
||||
c->close();
|
||||
}
|
||||
}
|
||||
|
||||
// pmb fix
|
||||
size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||
if(_clients.isEmpty())
|
||||
return 0;
|
||||
|
||||
size_t aql=0;
|
||||
uint32_t nConnectedClients=0;
|
||||
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
aql+=c->packetsWaiting();
|
||||
++nConnectedClients;
|
||||
}
|
||||
}
|
||||
// return aql / nConnectedClients;
|
||||
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
|
||||
}
|
||||
|
||||
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
|
||||
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
c->write(ev.c_str(), ev.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncEventSource::count() const {
|
||||
return _clients.count_if([](AsyncEventSourceClient *c){
|
||||
return c->connected();
|
||||
});
|
||||
}
|
||||
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||
return false;
|
||||
}
|
||||
request->addInterestingHeader("Last-Event-ID");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
request->send(new AsyncEventSourceResponse(this));
|
||||
}
|
||||
|
||||
// Response
|
||||
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
||||
_server = server;
|
||||
_code = 200;
|
||||
_contentType = "text/event-stream";
|
||||
_sendContentLength = false;
|
||||
addHeader("Cache-Control", "no-cache");
|
||||
addHeader("Connection","keep-alive");
|
||||
}
|
||||
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
||||
String out = _assembleHead(request->version());
|
||||
request->client()->write(out.c_str(), _headLength);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
||||
if(len){
|
||||
new AsyncEventSourceClient(request, _server);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. 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
|
||||
*/
|
||||
#ifndef ASYNCEVENTSOURCE_H_
|
||||
#define ASYNCEVENTSOURCE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Arduino.h>
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include <AsyncTCP.h>
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <mutex>
|
||||
#endif // ESP32
|
||||
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "AsyncWebSynchronization.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
||||
#endif
|
||||
|
||||
class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
||||
|
||||
class AsyncEventSourceMessage {
|
||||
private:
|
||||
uint8_t * _data;
|
||||
size_t _len;
|
||||
size_t _sent;
|
||||
//size_t _ack;
|
||||
size_t _acked;
|
||||
public:
|
||||
AsyncEventSourceMessage(const char * data, size_t len);
|
||||
~AsyncEventSourceMessage();
|
||||
size_t ack(size_t len);
|
||||
size_t write_buffer(AsyncClient *client);
|
||||
size_t send(AsyncClient *client);
|
||||
bool finished(){ return _acked == _len; }
|
||||
bool sent() { return _sent == _len; }
|
||||
};
|
||||
|
||||
class AsyncEventSourceClient {
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncEventSource *_server;
|
||||
uint32_t _lastId;
|
||||
#if defined(ESP32)
|
||||
std::mutex _messageQueue_mutex;
|
||||
#else
|
||||
bool _messageQueue_processing{false};
|
||||
#endif // ESP32
|
||||
LinkedList<AsyncEventSourceMessage *> _messageQueue;
|
||||
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
||||
void _runQueue();
|
||||
|
||||
public:
|
||||
|
||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
||||
~AsyncEventSourceClient();
|
||||
|
||||
AsyncClient* client(){ return _client; }
|
||||
void close();
|
||||
void write(const char * message, size_t len);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
bool connected() const { return (_client != NULL) && _client->connected(); }
|
||||
uint32_t lastId() const { return _lastId; }
|
||||
size_t packetsWaiting() const { return _messageQueue.length(); }
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
};
|
||||
|
||||
class AsyncEventSource: public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
LinkedList<AsyncEventSourceClient *> _clients;
|
||||
ArEventHandlerFunction _connectcb;
|
||||
public:
|
||||
AsyncEventSource(const String& url);
|
||||
~AsyncEventSource();
|
||||
|
||||
const char * url() const { return _url.c_str(); }
|
||||
void close();
|
||||
void onConnect(ArEventHandlerFunction cb);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
size_t count() const; //number clinets connected
|
||||
size_t avgPacketsWaiting() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _addClient(AsyncEventSourceClient * client);
|
||||
void _handleDisconnect(AsyncEventSourceClient * client);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
};
|
||||
|
||||
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncEventSource *_server;
|
||||
public:
|
||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||
@@ -1,200 +0,0 @@
|
||||
// AsyncJson.h
|
||||
|
||||
#ifndef ASYNC_JSON_H_
|
||||
#define ASYNC_JSON_H_
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <Print.h>
|
||||
|
||||
constexpr const char * JSON_MIMETYPE = "application/json";
|
||||
constexpr const char * MSGPACK_MIMETYPE = "application/msgpack";
|
||||
|
||||
class ChunkPrint : public Print {
|
||||
private:
|
||||
uint8_t * _destination;
|
||||
size_t _to_skip;
|
||||
size_t _to_write;
|
||||
size_t _pos;
|
||||
|
||||
public:
|
||||
ChunkPrint(uint8_t * destination, size_t from, size_t len)
|
||||
: _destination(destination)
|
||||
, _to_skip(from)
|
||||
, _to_write(len)
|
||||
, _pos{0} {
|
||||
}
|
||||
virtual ~ChunkPrint() {
|
||||
}
|
||||
size_t write(uint8_t c) {
|
||||
if (_to_skip > 0) {
|
||||
_to_skip--;
|
||||
return 1;
|
||||
} else if (_to_write > 0) {
|
||||
_to_write--;
|
||||
_destination[_pos++] = c;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
size_t write(const uint8_t * buffer, size_t size) {
|
||||
return this->Print::write(buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
// added msgPack by proddy for EMS-ESP
|
||||
class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||
protected:
|
||||
JsonDocument _jsonBuffer;
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
bool _isMsgPack;
|
||||
|
||||
public:
|
||||
AsyncJsonResponse(bool isArray = false, bool isMsgPack = false)
|
||||
: _isValid{false}
|
||||
, _isMsgPack{isMsgPack} {
|
||||
_code = 200;
|
||||
_contentType = (isMsgPack) ? MSGPACK_MIMETYPE : JSON_MIMETYPE;
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.add<JsonArray>();
|
||||
else
|
||||
_root = _jsonBuffer.add<JsonObject>();
|
||||
}
|
||||
|
||||
~AsyncJsonResponse() {
|
||||
}
|
||||
JsonVariant getRoot() {
|
||||
return _root;
|
||||
}
|
||||
bool _sourceValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
size_t setLength() {
|
||||
_contentLength = _isMsgPack ? measureMsgPack(_root) : measureJson(_root);
|
||||
|
||||
if (_contentLength) {
|
||||
_isValid = true;
|
||||
}
|
||||
return _contentLength;
|
||||
}
|
||||
|
||||
size_t getSize() {
|
||||
return _jsonBuffer.size();
|
||||
}
|
||||
|
||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
_isMsgPack ? serializeMsgPack(_root, dest) : serializeJson(_root, dest);
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
// class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
||||
// public:
|
||||
// PrettyAsyncJsonResponse(bool isArray = false)
|
||||
// : AsyncJsonResponse{isArray} {
|
||||
// }
|
||||
// size_t setLength() {
|
||||
// _contentLength = measureJsonPretty(_root);
|
||||
// if (_contentLength) {
|
||||
// _isValid = true;
|
||||
// }
|
||||
// return _contentLength;
|
||||
// }
|
||||
// size_t _fillBuffer(uint8_t * data, size_t len) {
|
||||
// ChunkPrint dest(data, _sentLength, len);
|
||||
// serializeJsonPretty(_root, dest);
|
||||
// return len;
|
||||
// }
|
||||
// };
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction;
|
||||
|
||||
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
const String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
size_t _maxContentLength;
|
||||
|
||||
public:
|
||||
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest)
|
||||
: _uri(uri)
|
||||
, _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH)
|
||||
, _onRequest(onRequest)
|
||||
, _maxContentLength(16384) {
|
||||
}
|
||||
|
||||
void setMethod(WebRequestMethodComposite method) {
|
||||
_method = method;
|
||||
}
|
||||
void setMaxContentLength(int maxContentLength) {
|
||||
_maxContentLength = maxContentLength;
|
||||
}
|
||||
void onRequest(ArJsonRequestHandlerFunction fn) {
|
||||
_onRequest = fn;
|
||||
}
|
||||
|
||||
|
||||
virtual bool canHandle(AsyncWebServerRequest * request) override final {
|
||||
if (!_onRequest)
|
||||
return false;
|
||||
|
||||
WebRequestMethodComposite request_method = request->method();
|
||||
|
||||
if (!(_method & request_method))
|
||||
return false;
|
||||
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
return false;
|
||||
|
||||
if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(JSON_MIMETYPE))
|
||||
return false;
|
||||
|
||||
request->addInterestingHeader("ANY");
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest * request) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
} else if (request->_tempObject != NULL) {
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
||||
} else {
|
||||
request->send(500);
|
||||
}
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final {
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
}
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() override final {
|
||||
return _onRequest ? false : true;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,398 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSOCKET_H_
|
||||
#define ASYNCWEBSOCKET_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#endif
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "AsyncWebSynchronization.h"
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||
#endif
|
||||
|
||||
class AsyncWebSocket;
|
||||
class AsyncWebSocketResponse;
|
||||
class AsyncWebSocketClient;
|
||||
class AsyncWebSocketControl;
|
||||
|
||||
typedef struct {
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
* Note: Applications will only see WS_TEXT and WS_BINARY.
|
||||
* All other types are handled by the library. */
|
||||
uint8_t message_opcode;
|
||||
/** Frame number of a fragmented message. */
|
||||
uint32_t num;
|
||||
/** Is this the last frame in a fragmented message ?*/
|
||||
uint8_t final;
|
||||
/** Is this frame masked? */
|
||||
uint8_t masked;
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
* This value is the same as message_opcode for non-fragmented
|
||||
* messages, but may also be WS_CONTINUATION in a fragmented message. */
|
||||
uint8_t opcode;
|
||||
/** Length of the current frame.
|
||||
* This equals the total length of the message if num == 0 && final == true */
|
||||
uint64_t len;
|
||||
/** Mask key */
|
||||
uint8_t mask[4];
|
||||
/** Offset of the data inside the current frame. */
|
||||
uint64_t index;
|
||||
} AwsFrameInfo;
|
||||
|
||||
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
|
||||
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
|
||||
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
|
||||
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
|
||||
|
||||
class AsyncWebSocketMessageBuffer {
|
||||
friend AsyncWebSocket;
|
||||
friend AsyncWebSocketClient;
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::vector<uint8_t>> _buffer;
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessageBuffer();
|
||||
AsyncWebSocketMessageBuffer(size_t size);
|
||||
AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
|
||||
~AsyncWebSocketMessageBuffer();
|
||||
bool reserve(size_t size);
|
||||
uint8_t * get() {
|
||||
return _buffer->data();
|
||||
}
|
||||
size_t length() const {
|
||||
return _buffer->size();
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncWebSocketMessage {
|
||||
private:
|
||||
std::shared_ptr<std::vector<uint8_t>> _WSbuffer;
|
||||
uint8_t _opcode{WS_TEXT};
|
||||
bool _mask{false};
|
||||
AwsMessageStatus _status{WS_MSG_ERROR};
|
||||
size_t _sent{};
|
||||
size_t _ack{};
|
||||
size_t _acked{};
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
|
||||
bool finished() const {
|
||||
return _status != WS_MSG_SENDING;
|
||||
}
|
||||
bool betweenFrames() const {
|
||||
return _acked == _ack;
|
||||
}
|
||||
|
||||
void ack(size_t len, uint32_t time);
|
||||
size_t send(AsyncClient * client);
|
||||
};
|
||||
|
||||
class AsyncWebSocketClient {
|
||||
private:
|
||||
AsyncClient * _client;
|
||||
AsyncWebSocket * _server;
|
||||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
|
||||
AsyncWebLock _lock;
|
||||
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
bool closeWhenFull = true;
|
||||
|
||||
uint8_t _pstate;
|
||||
AwsFrameInfo _pinfo;
|
||||
|
||||
uint32_t _lastMessageTime;
|
||||
uint32_t _keepAlivePeriod;
|
||||
|
||||
void _queueControl(uint8_t opcode, const uint8_t * data = NULL, size_t len = 0, bool mask = false);
|
||||
void _queueMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
void _runQueue();
|
||||
void _clearQueue();
|
||||
|
||||
public:
|
||||
void * _tempObject;
|
||||
|
||||
AsyncWebSocketClient(AsyncWebServerRequest * request, AsyncWebSocket * server);
|
||||
~AsyncWebSocketClient();
|
||||
|
||||
//client id increments for the given server
|
||||
uint32_t id() const {
|
||||
return _clientId;
|
||||
}
|
||||
AwsClientStatus status() const {
|
||||
return _status;
|
||||
}
|
||||
AsyncClient * client() {
|
||||
return _client;
|
||||
}
|
||||
const AsyncClient * client() const {
|
||||
return _client;
|
||||
}
|
||||
AsyncWebSocket * server() {
|
||||
return _server;
|
||||
}
|
||||
const AsyncWebSocket * server() const {
|
||||
return _server;
|
||||
}
|
||||
AwsFrameInfo const & pinfo() const {
|
||||
return _pinfo;
|
||||
}
|
||||
|
||||
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||
// and so on, causing a resource exhaustion.
|
||||
//
|
||||
// - If "false", the incoming message will be discarded if the queue is full.
|
||||
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
|
||||
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
|
||||
//
|
||||
// - In any case, when the queue is full, a message is logged.
|
||||
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
|
||||
//
|
||||
// Usage:
|
||||
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
|
||||
//
|
||||
// Use cases:,
|
||||
// - if using websocket to send logging messages, maybe some loss is acceptable.
|
||||
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
|
||||
void setCloseClientOnQueueFull(bool close) {
|
||||
closeWhenFull = close;
|
||||
}
|
||||
bool willCloseClientOnQueueFull() const {
|
||||
return closeWhenFull;
|
||||
}
|
||||
|
||||
IPAddress remoteIP() const;
|
||||
uint16_t remotePort() const;
|
||||
|
||||
bool shouldBeDeleted() const {
|
||||
return !_client;
|
||||
}
|
||||
|
||||
//control frames
|
||||
void close(uint16_t code = 0, const char * message = NULL);
|
||||
void ping(const uint8_t * data = NULL, size_t len = 0);
|
||||
|
||||
//set auto-ping period in seconds. disabled if zero (default)
|
||||
void keepAlivePeriod(uint16_t seconds) {
|
||||
_keepAlivePeriod = seconds * 1000;
|
||||
}
|
||||
uint16_t keepAlivePeriod() {
|
||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||
}
|
||||
|
||||
//data packets
|
||||
void message(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
|
||||
_queueMessage(buffer, opcode, mask);
|
||||
}
|
||||
bool queueIsFull() const;
|
||||
size_t queueLen() const;
|
||||
|
||||
size_t printf(const char * format, ...) __attribute__((format(printf, 2, 3)));
|
||||
#ifndef ESP32
|
||||
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
#endif
|
||||
|
||||
void text(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
void text(const uint8_t * message, size_t len);
|
||||
void text(const char * message, size_t len);
|
||||
void text(const char * message);
|
||||
void text(const String & message);
|
||||
void text(const __FlashStringHelper * message);
|
||||
void text(AsyncWebSocketMessageBuffer * buffer);
|
||||
|
||||
void binary(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
void binary(const uint8_t * message, size_t len);
|
||||
void binary(const char * message, size_t len);
|
||||
void binary(const char * message);
|
||||
void binary(const String & message);
|
||||
void binary(const __FlashStringHelper * message, size_t len);
|
||||
void binary(AsyncWebSocketMessageBuffer * buffer);
|
||||
|
||||
bool canSend() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void * pbuf, size_t plen);
|
||||
};
|
||||
|
||||
typedef std::function<bool(AsyncWebServerRequest * request)> AwsHandshakeHandler;
|
||||
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len)> AwsEventHandler;
|
||||
|
||||
//WebServer Handler implementation that plays the role of a socket server
|
||||
class AsyncWebSocket : public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler;
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
AsyncWebLock _lock;
|
||||
|
||||
public:
|
||||
AsyncWebSocket(const String & url);
|
||||
~AsyncWebSocket();
|
||||
const char * url() const {
|
||||
return _url.c_str();
|
||||
}
|
||||
void enable(bool e) {
|
||||
_enabled = e;
|
||||
}
|
||||
bool enabled() const {
|
||||
return _enabled;
|
||||
}
|
||||
bool availableForWriteAll();
|
||||
bool availableForWrite(uint32_t id);
|
||||
|
||||
size_t count() const;
|
||||
AsyncWebSocketClient * client(uint32_t id);
|
||||
bool hasClient(uint32_t id) {
|
||||
return client(id) != NULL;
|
||||
}
|
||||
|
||||
void close(uint32_t id, uint16_t code = 0, const char * message = NULL);
|
||||
void closeAll(uint16_t code = 0, const char * message = NULL);
|
||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||
|
||||
void ping(uint32_t id, const uint8_t * data = NULL, size_t len = 0);
|
||||
void pingAll(const uint8_t * data = NULL, size_t len = 0); // done
|
||||
|
||||
void text(uint32_t id, const uint8_t * message, size_t len);
|
||||
void text(uint32_t id, const char * message, size_t len);
|
||||
void text(uint32_t id, const char * message);
|
||||
void text(uint32_t id, const String & message);
|
||||
void text(uint32_t id, const __FlashStringHelper * message);
|
||||
void text(uint32_t id, AsyncWebSocketMessageBuffer * buffer);
|
||||
void text(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
|
||||
void textAll(const uint8_t * message, size_t len);
|
||||
void textAll(const char * message, size_t len);
|
||||
void textAll(const char * message);
|
||||
void textAll(const String & message);
|
||||
void textAll(const __FlashStringHelper * message);
|
||||
void textAll(AsyncWebSocketMessageBuffer * buffer);
|
||||
void textAll(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
|
||||
void binary(uint32_t id, const uint8_t * message, size_t len);
|
||||
void binary(uint32_t id, const char * message, size_t len);
|
||||
void binary(uint32_t id, const char * message);
|
||||
void binary(uint32_t id, const String & message);
|
||||
void binary(uint32_t id, const __FlashStringHelper * message, size_t len);
|
||||
void binary(uint32_t id, AsyncWebSocketMessageBuffer * buffer);
|
||||
void binary(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
|
||||
void binaryAll(const uint8_t * message, size_t len);
|
||||
void binaryAll(const char * message, size_t len);
|
||||
void binaryAll(const char * message);
|
||||
void binaryAll(const String & message);
|
||||
void binaryAll(const __FlashStringHelper * message, size_t len);
|
||||
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
|
||||
void binaryAll(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||
|
||||
size_t printf(uint32_t id, const char * format, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll(const char * format, ...) __attribute__((format(printf, 2, 3)));
|
||||
#ifndef ESP32
|
||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
|
||||
#endif
|
||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
//event listener
|
||||
void onEvent(AwsEventHandler handler) {
|
||||
_eventHandler = handler;
|
||||
}
|
||||
|
||||
// Handshake Handler
|
||||
void handleHandshake(AwsHandshakeHandler handler) {
|
||||
_handshakeHandler = handler;
|
||||
}
|
||||
|
||||
//system callbacks (do not call)
|
||||
uint32_t _getNextId() {
|
||||
return _cNextId++;
|
||||
}
|
||||
AsyncWebSocketClient * _newClient(AsyncWebServerRequest * request);
|
||||
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len);
|
||||
virtual bool canHandle(AsyncWebServerRequest * request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest * request) override final;
|
||||
|
||||
|
||||
// messagebuffer functions/objects.
|
||||
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
||||
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
|
||||
|
||||
const std::list<AsyncWebSocketClient> & getClients() const {
|
||||
return _clients;
|
||||
}
|
||||
};
|
||||
|
||||
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||
class AsyncWebSocketResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncWebSocket * _server;
|
||||
|
||||
public:
|
||||
AsyncWebSocketResponse(const String & key, AsyncWebSocket * server);
|
||||
void _respond(AsyncWebServerRequest * request);
|
||||
size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCWEBSOCKET_H_ */
|
||||
@@ -1,134 +0,0 @@
|
||||
#ifndef ASYNCWEBSYNCHRONIZATION_H_
|
||||
#define ASYNCWEBSYNCHRONIZATION_H_
|
||||
|
||||
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||
// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not
|
||||
// always available. According to example by Arjan Filius, changed name,
|
||||
// added unimplemented version for ESP8266
|
||||
class AsyncPlainLock
|
||||
{
|
||||
private:
|
||||
SemaphoreHandle_t _lock;
|
||||
|
||||
public:
|
||||
AsyncPlainLock() {
|
||||
_lock = xSemaphoreCreateBinary();
|
||||
// In this fails, the system is likely that much out of memory that
|
||||
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||
assert(_lock);
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
|
||||
~AsyncPlainLock() {
|
||||
vSemaphoreDelete(_lock);
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
};
|
||||
|
||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||
class AsyncWebLock
|
||||
{
|
||||
private:
|
||||
SemaphoreHandle_t _lock;
|
||||
mutable TaskHandle_t _lockedBy{};
|
||||
|
||||
public:
|
||||
AsyncWebLock()
|
||||
{
|
||||
_lock = xSemaphoreCreateBinary();
|
||||
// In this fails, the system is likely that much out of memory that
|
||||
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||
assert(_lock);
|
||||
_lockedBy = NULL;
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
|
||||
~AsyncWebLock() {
|
||||
vSemaphoreDelete(_lock);
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
const auto currentTask = xTaskGetCurrentTaskHandle();
|
||||
if (_lockedBy != currentTask) {
|
||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||
_lockedBy = currentTask;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
_lockedBy = NULL;
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// This is the 8266 version of the Sync Lock which is currently unimplemented
|
||||
class AsyncWebLock
|
||||
{
|
||||
|
||||
public:
|
||||
AsyncWebLock() {
|
||||
}
|
||||
|
||||
~AsyncWebLock() {
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
}
|
||||
};
|
||||
|
||||
// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above.
|
||||
using AsyncPlainLock = AsyncWebLock;
|
||||
|
||||
#endif
|
||||
|
||||
class AsyncWebLockGuard
|
||||
{
|
||||
private:
|
||||
const AsyncWebLock *_lock;
|
||||
|
||||
public:
|
||||
AsyncWebLockGuard(const AsyncWebLock &l) {
|
||||
if (l.lock()) {
|
||||
_lock = &l;
|
||||
} else {
|
||||
_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncWebLockGuard() {
|
||||
if (_lock) {
|
||||
_lock->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
if (_lock) {
|
||||
_lock->unlock();
|
||||
_lock = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
||||
@@ -1,616 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef _ESPAsyncWebServer_H_
|
||||
#define _ESPAsyncWebServer_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include "FS.h"
|
||||
|
||||
#include <ArduinoJson.h> // added by proddy for EMS-ESP
|
||||
|
||||
#include "StringArray.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
#define ASYNCWEBSERVER_VERSION "2.6.1"
|
||||
#define ASYNCWEBSERVER_VERSION_MAJOR 2
|
||||
#define ASYNCWEBSERVER_VERSION_MINOR 6
|
||||
#define ASYNCWEBSERVER_VERSION_REVISION 1
|
||||
#define ASYNCWEBSERVER_FORK_mathieucarbou
|
||||
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
|
||||
#else
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
|
||||
#endif
|
||||
|
||||
class AsyncWebServer;
|
||||
class AsyncWebServerRequest;
|
||||
class AsyncWebServerResponse;
|
||||
class AsyncWebHeader;
|
||||
class AsyncWebParameter;
|
||||
class AsyncWebRewrite;
|
||||
class AsyncWebHandler;
|
||||
class AsyncStaticWebHandler;
|
||||
class AsyncCallbackWebHandler;
|
||||
class AsyncResponseStream;
|
||||
|
||||
#ifndef WEBSERVER_H
|
||||
typedef enum {
|
||||
HTTP_GET = 0b00000001,
|
||||
HTTP_POST = 0b00000010,
|
||||
HTTP_DELETE = 0b00000100,
|
||||
HTTP_PUT = 0b00001000,
|
||||
HTTP_PATCH = 0b00010000,
|
||||
HTTP_HEAD = 0b00100000,
|
||||
HTTP_OPTIONS = 0b01000000,
|
||||
HTTP_ANY = 0b01111111,
|
||||
} WebRequestMethod;
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||
namespace fs {
|
||||
class FileOpenMode {
|
||||
public:
|
||||
static const char * read;
|
||||
static const char * write;
|
||||
static const char * append;
|
||||
};
|
||||
}; // namespace fs
|
||||
#else
|
||||
#include "FileOpenMode.h"
|
||||
#endif
|
||||
|
||||
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
||||
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
|
||||
|
||||
typedef uint8_t WebRequestMethodComposite;
|
||||
typedef std::function<void(void)> ArDisconnectHandler;
|
||||
|
||||
/*
|
||||
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
|
||||
* */
|
||||
|
||||
class AsyncWebParameter {
|
||||
private:
|
||||
String _name;
|
||||
String _value;
|
||||
size_t _size;
|
||||
bool _isForm;
|
||||
bool _isFile;
|
||||
|
||||
public:
|
||||
AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0)
|
||||
: _name(name)
|
||||
, _value(value)
|
||||
, _size(size)
|
||||
, _isForm(form)
|
||||
, _isFile(file) {
|
||||
}
|
||||
const String & name() const {
|
||||
return _name;
|
||||
}
|
||||
const String & value() const {
|
||||
return _value;
|
||||
}
|
||||
size_t size() const {
|
||||
return _size;
|
||||
}
|
||||
bool isPost() const {
|
||||
return _isForm;
|
||||
}
|
||||
bool isFile() const {
|
||||
return _isFile;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* HEADER :: Chainable object to hold the headers
|
||||
* */
|
||||
|
||||
class AsyncWebHeader {
|
||||
private:
|
||||
String _name;
|
||||
String _value;
|
||||
|
||||
public:
|
||||
AsyncWebHeader() = default;
|
||||
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||
|
||||
AsyncWebHeader(const String & name, const String & value)
|
||||
: _name(name)
|
||||
, _value(value) {
|
||||
}
|
||||
AsyncWebHeader(const String & data)
|
||||
: _name()
|
||||
, _value() {
|
||||
if (!data)
|
||||
return;
|
||||
int index = data.indexOf(':');
|
||||
if (index < 0)
|
||||
return;
|
||||
_name = data.substring(0, index);
|
||||
_value = data.substring(index + 2);
|
||||
}
|
||||
|
||||
AsyncWebHeader & operator=(const AsyncWebHeader &) = default;
|
||||
|
||||
const String & name() const {
|
||||
return _name;
|
||||
}
|
||||
const String & value() const {
|
||||
return _value;
|
||||
}
|
||||
String toString() const {
|
||||
return _name + F(": ") + _value + F("\r\n");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
|
||||
* */
|
||||
|
||||
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
|
||||
|
||||
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
|
||||
typedef std::function<String(const String &)> AwsTemplateProcessor;
|
||||
|
||||
class AsyncWebServerRequest {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
|
||||
private:
|
||||
AsyncClient * _client;
|
||||
AsyncWebServer * _server;
|
||||
AsyncWebHandler * _handler;
|
||||
AsyncWebServerResponse * _response;
|
||||
std::vector<String> _interestingHeaders;
|
||||
ArDisconnectHandler _onDisconnectfn;
|
||||
|
||||
String _temp;
|
||||
uint8_t _parseState;
|
||||
|
||||
uint8_t _version;
|
||||
WebRequestMethodComposite _method;
|
||||
String _url;
|
||||
String _host;
|
||||
String _contentType;
|
||||
String _boundary;
|
||||
String _authorization;
|
||||
RequestedConnectionType _reqconntype;
|
||||
void _removeNotInterestingHeaders();
|
||||
bool _isDigest;
|
||||
bool _isMultipart;
|
||||
bool _isPlainPost;
|
||||
bool _expectingContinue;
|
||||
size_t _contentLength;
|
||||
size_t _parsedLength;
|
||||
|
||||
std::list<AsyncWebHeader> _headers;
|
||||
LinkedList<AsyncWebParameter *> _params;
|
||||
std::vector<String> _pathParams;
|
||||
|
||||
uint8_t _multiParseState;
|
||||
uint8_t _boundaryPosition;
|
||||
size_t _itemStartIndex;
|
||||
size_t _itemSize;
|
||||
String _itemName;
|
||||
String _itemFilename;
|
||||
String _itemType;
|
||||
String _itemValue;
|
||||
uint8_t * _itemBuffer;
|
||||
size_t _itemBufferIndex;
|
||||
bool _itemIsFile;
|
||||
|
||||
void _onPoll();
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t error);
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void * buf, size_t len);
|
||||
|
||||
void _addParam(AsyncWebParameter *);
|
||||
void _addPathParam(const char * param);
|
||||
|
||||
bool _parseReqHead();
|
||||
bool _parseReqHeader();
|
||||
void _parseLine();
|
||||
void _parsePlainPostChar(uint8_t data);
|
||||
void _parseMultipartPostByte(uint8_t data, bool last);
|
||||
void _addGetParams(const String & params);
|
||||
|
||||
void _handleUploadStart();
|
||||
void _handleUploadByte(uint8_t data, bool last);
|
||||
void _handleUploadEnd();
|
||||
|
||||
public:
|
||||
File _tempFile;
|
||||
void * _tempObject;
|
||||
|
||||
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
|
||||
~AsyncWebServerRequest();
|
||||
|
||||
AsyncClient * client() {
|
||||
return _client;
|
||||
}
|
||||
uint8_t version() const {
|
||||
return _version;
|
||||
}
|
||||
WebRequestMethodComposite method() const {
|
||||
return _method;
|
||||
}
|
||||
const String & url() const {
|
||||
return _url;
|
||||
}
|
||||
const String & host() const {
|
||||
return _host;
|
||||
}
|
||||
const String & contentType() const {
|
||||
return _contentType;
|
||||
}
|
||||
size_t contentLength() const {
|
||||
return _contentLength;
|
||||
}
|
||||
bool multipart() const {
|
||||
return _isMultipart;
|
||||
}
|
||||
const __FlashStringHelper * methodToString() const;
|
||||
const __FlashStringHelper * requestedConnTypeToString() const;
|
||||
RequestedConnectionType requestedConnType() const {
|
||||
return _reqconntype;
|
||||
}
|
||||
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
|
||||
void onDisconnect(ArDisconnectHandler fn);
|
||||
|
||||
//hash is the string representation of:
|
||||
// base64(user:pass) for basic or
|
||||
// user:realm:md5(user:realm:pass) for digest
|
||||
bool authenticate(const char * hash);
|
||||
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
|
||||
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
|
||||
|
||||
void setHandler(AsyncWebHandler * handler) {
|
||||
_handler = handler;
|
||||
}
|
||||
void addInterestingHeader(const String & name);
|
||||
|
||||
void redirect(const String & url);
|
||||
|
||||
void send(AsyncWebServerResponse * response);
|
||||
void send(int code, const String & contentType = String(), const String & content = String());
|
||||
void send(FS & fs, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
void send(File content, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
void send(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
void send(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
void sendChunked(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
void send_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
void send_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
|
||||
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String());
|
||||
AsyncWebServerResponse *
|
||||
beginResponse(FS & fs, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse *
|
||||
beginResponse(File content, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncWebServerResponse * beginChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncResponseStream * beginResponseStream(const String & contentType, size_t bufferSize = 1460);
|
||||
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse * beginResponse(const String & contentType, const uint8_t * content, size_t len); // added by proddy for EMS-ESP
|
||||
|
||||
size_t headers() const; // get header count
|
||||
bool hasHeader(const String & name) const; // check if header exists
|
||||
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
||||
|
||||
AsyncWebHeader * getHeader(const String & name);
|
||||
const AsyncWebHeader * getHeader(const String & name) const;
|
||||
AsyncWebHeader * getHeader(const __FlashStringHelper * data);
|
||||
const AsyncWebHeader * getHeader(const __FlashStringHelper * data) const;
|
||||
AsyncWebHeader * getHeader(size_t num);
|
||||
const AsyncWebHeader * getHeader(size_t num) const;
|
||||
|
||||
size_t params() const; // get arguments count
|
||||
bool hasParam(const String & name, bool post = false, bool file = false) const;
|
||||
bool hasParam(const __FlashStringHelper * data, bool post = false, bool file = false) const;
|
||||
|
||||
AsyncWebParameter * getParam(const String & name, bool post = false, bool file = false) const;
|
||||
AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
||||
AsyncWebParameter * getParam(size_t num) const;
|
||||
|
||||
size_t args() const {
|
||||
return params();
|
||||
} // get arguments count
|
||||
const String & arg(const String & name) const; // get request argument value by name
|
||||
const String & arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
||||
const String & arg(size_t i) const; // get request argument value by number
|
||||
const String & argName(size_t i) const; // get request argument name by number
|
||||
bool hasArg(const char * name) const; // check if argument exists
|
||||
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
||||
|
||||
const String & ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
||||
|
||||
const String & header(const char * name) const; // get request header value by name
|
||||
const String & header(const __FlashStringHelper * data) const; // get request header value by F(name)
|
||||
const String & header(size_t i) const; // get request header value by number
|
||||
const String & headerName(size_t i) const; // get request header name by number
|
||||
String urlDecode(const String & text) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
||||
* */
|
||||
|
||||
typedef std::function<bool(AsyncWebServerRequest * request)> ArRequestFilterFunction;
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest * request);
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest * request);
|
||||
|
||||
/*
|
||||
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||
* */
|
||||
|
||||
class AsyncWebRewrite {
|
||||
protected:
|
||||
String _from;
|
||||
String _toUrl;
|
||||
String _params;
|
||||
ArRequestFilterFunction _filter;
|
||||
|
||||
public:
|
||||
AsyncWebRewrite(const char * from, const char * to)
|
||||
: _from(from)
|
||||
, _toUrl(to)
|
||||
, _params(String())
|
||||
, _filter(NULL) {
|
||||
int index = _toUrl.indexOf('?');
|
||||
if (index > 0) {
|
||||
_params = _toUrl.substring(index + 1);
|
||||
_toUrl = _toUrl.substring(0, index);
|
||||
}
|
||||
}
|
||||
virtual ~AsyncWebRewrite() {
|
||||
}
|
||||
AsyncWebRewrite & setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
bool filter(AsyncWebServerRequest * request) const {
|
||||
return _filter == NULL || _filter(request);
|
||||
}
|
||||
const String & from(void) const {
|
||||
return _from;
|
||||
}
|
||||
const String & toUrl(void) const {
|
||||
return _toUrl;
|
||||
}
|
||||
const String & params(void) const {
|
||||
return _params;
|
||||
}
|
||||
virtual bool match(AsyncWebServerRequest * request) {
|
||||
return from() == request->url() && filter(request);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* HANDLER :: One instance can be attached to any Request (done by the Server)
|
||||
* */
|
||||
|
||||
class AsyncWebHandler {
|
||||
protected:
|
||||
ArRequestFilterFunction _filter;
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
public:
|
||||
AsyncWebHandler()
|
||||
: _username("")
|
||||
, _password("") {
|
||||
}
|
||||
AsyncWebHandler & setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
AsyncWebHandler & setAuthentication(const String & username, const String & password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return *this;
|
||||
};
|
||||
AsyncWebHandler & setAuthentication(const char * username, const char * password) {
|
||||
_username = String(username);
|
||||
_password = String(password);
|
||||
return *this;
|
||||
};
|
||||
bool filter(AsyncWebServerRequest * request) {
|
||||
return _filter == NULL || _filter(request);
|
||||
}
|
||||
virtual ~AsyncWebHandler() {
|
||||
}
|
||||
virtual bool canHandle(AsyncWebServerRequest * request __attribute__((unused))) {
|
||||
return false;
|
||||
}
|
||||
virtual void handleRequest(AsyncWebServerRequest * request __attribute__((unused))) {
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest * request __attribute__((unused)),
|
||||
const String & filename __attribute__((unused)),
|
||||
size_t index __attribute__((unused)),
|
||||
uint8_t * data __attribute__((unused)),
|
||||
size_t len __attribute__((unused)),
|
||||
bool final __attribute__((unused))) {
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest * request __attribute__((unused)),
|
||||
uint8_t * data __attribute__((unused)),
|
||||
size_t len __attribute__((unused)),
|
||||
size_t index __attribute__((unused)),
|
||||
size_t total __attribute__((unused))) {
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* RESPONSE :: One instance is created for each Request (attached by the Handler)
|
||||
* */
|
||||
|
||||
typedef enum { RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED } WebResponseState;
|
||||
|
||||
class AsyncWebServerResponse {
|
||||
protected:
|
||||
int _code;
|
||||
std::list<AsyncWebHeader> _headers;
|
||||
String _contentType;
|
||||
size_t _contentLength;
|
||||
bool _sendContentLength;
|
||||
bool _chunked;
|
||||
size_t _headLength;
|
||||
size_t _sentLength;
|
||||
size_t _ackedLength;
|
||||
size_t _writtenLength;
|
||||
WebResponseState _state;
|
||||
const char * _responseCodeToString(int code);
|
||||
|
||||
public:
|
||||
static const __FlashStringHelper * responseCodeToString(int code);
|
||||
|
||||
public:
|
||||
AsyncWebServerResponse();
|
||||
virtual ~AsyncWebServerResponse();
|
||||
virtual void setCode(int code);
|
||||
virtual void setContentLength(size_t len);
|
||||
virtual void setContentType(const String & type);
|
||||
virtual void addHeader(const String & name, const String & value);
|
||||
virtual String _assembleHead(uint8_t version);
|
||||
virtual bool _started() const;
|
||||
virtual bool _finished() const;
|
||||
virtual bool _failed() const;
|
||||
virtual bool _sourceValid() const;
|
||||
virtual void _respond(AsyncWebServerRequest * request);
|
||||
virtual size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
|
||||
};
|
||||
|
||||
/*
|
||||
* SERVER :: One instance
|
||||
* */
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction; // added by proddy for EMS-ESP
|
||||
|
||||
class AsyncWebServer {
|
||||
protected:
|
||||
AsyncServer _server;
|
||||
LinkedList<AsyncWebRewrite *> _rewrites;
|
||||
LinkedList<AsyncWebHandler *> _handlers;
|
||||
AsyncCallbackWebHandler * _catchAllHandler;
|
||||
|
||||
public:
|
||||
AsyncWebServer(uint16_t port);
|
||||
~AsyncWebServer();
|
||||
|
||||
void begin();
|
||||
void end();
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void onSslFileRequest(AcSSlFileHandler cb, void * arg);
|
||||
void beginSecure(const char * cert, const char * private_key_file, const char * password);
|
||||
#endif
|
||||
|
||||
AsyncWebRewrite & addRewrite(AsyncWebRewrite * rewrite);
|
||||
bool removeRewrite(AsyncWebRewrite * rewrite);
|
||||
AsyncWebRewrite & rewrite(const char * from, const char * to);
|
||||
|
||||
AsyncWebHandler & addHandler(AsyncWebHandler * handler);
|
||||
bool removeHandler(AsyncWebHandler * handler);
|
||||
|
||||
AsyncCallbackWebHandler & on(const char * uri, ArRequestHandlerFunction onRequest);
|
||||
AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
|
||||
AsyncCallbackWebHandler & on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
|
||||
AsyncCallbackWebHandler &
|
||||
on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
|
||||
|
||||
void on(const char * uri, ArJsonRequestHandlerFunction onRequest); // added by proddy for EMS-ESP
|
||||
|
||||
AsyncStaticWebHandler & serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL);
|
||||
|
||||
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
||||
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
||||
|
||||
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
||||
|
||||
void _handleDisconnect(AsyncWebServerRequest * request);
|
||||
void _attachHandler(AsyncWebServerRequest * request);
|
||||
void _rewriteRequest(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
class DefaultHeaders {
|
||||
using headers_t = std::list<AsyncWebHeader>;
|
||||
headers_t _headers;
|
||||
|
||||
public:
|
||||
DefaultHeaders() = default;
|
||||
|
||||
using ConstIterator = headers_t::const_iterator;
|
||||
|
||||
void addHeader(const String & name, const String & value) {
|
||||
_headers.emplace_back(name, value);
|
||||
}
|
||||
|
||||
ConstIterator begin() const {
|
||||
return _headers.begin();
|
||||
}
|
||||
ConstIterator end() const {
|
||||
return _headers.end();
|
||||
}
|
||||
|
||||
DefaultHeaders(DefaultHeaders const &) = delete;
|
||||
DefaultHeaders & operator=(DefaultHeaders const &) = delete;
|
||||
|
||||
static DefaultHeaders & Instance() {
|
||||
static DefaultHeaders instance;
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
#include "WebResponseImpl.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
#include "AsyncWebSocket.h"
|
||||
#include "AsyncEventSource.h"
|
||||
|
||||
#endif /* _AsyncWebServer_H_ */
|
||||
@@ -1,2 +0,0 @@
|
||||
// to please Arduino Lint
|
||||
#include "ESPAsyncWebServer.h"
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef STRINGARRAY_H_
|
||||
#define STRINGARRAY_H_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "WString.h"
|
||||
|
||||
template <typename T>
|
||||
class LinkedListNode {
|
||||
T _value;
|
||||
public:
|
||||
LinkedListNode<T>* next;
|
||||
LinkedListNode(const T val): _value(val), next(nullptr) {}
|
||||
~LinkedListNode(){}
|
||||
const T& value() const { return _value; };
|
||||
T& value(){ return _value; }
|
||||
};
|
||||
|
||||
template <typename T, template<typename> class Item = LinkedListNode>
|
||||
class LinkedList {
|
||||
public:
|
||||
typedef Item<T> ItemType;
|
||||
typedef std::function<void(const T&)> OnRemove;
|
||||
typedef std::function<bool(const T&)> Predicate;
|
||||
private:
|
||||
ItemType* _root;
|
||||
OnRemove _onRemove;
|
||||
|
||||
class Iterator {
|
||||
ItemType* _node;
|
||||
public:
|
||||
Iterator(ItemType* current = nullptr) : _node(current) {}
|
||||
Iterator(const Iterator& i) : _node(i._node) {}
|
||||
Iterator& operator ++() { _node = _node->next; return *this; }
|
||||
bool operator != (const Iterator& i) const { return _node != i._node; }
|
||||
const T& operator * () const { return _node->value(); }
|
||||
const T* operator -> () const { return &_node->value(); }
|
||||
};
|
||||
|
||||
public:
|
||||
typedef const Iterator ConstIterator;
|
||||
ConstIterator begin() const { return ConstIterator(_root); }
|
||||
ConstIterator end() const { return ConstIterator(nullptr); }
|
||||
|
||||
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
|
||||
~LinkedList(){}
|
||||
void add(const T& t){
|
||||
auto it = new ItemType(t);
|
||||
if(!_root){
|
||||
_root = it;
|
||||
} else {
|
||||
auto i = _root;
|
||||
while(i->next) i = i->next;
|
||||
i->next = it;
|
||||
}
|
||||
}
|
||||
T& front() const {
|
||||
return _root->value();
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return _root == nullptr;
|
||||
}
|
||||
size_t length() const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
i++;
|
||||
it = it->next;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
size_t count_if(Predicate predicate) const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
if (!predicate){
|
||||
i++;
|
||||
}
|
||||
else if (predicate(it->value())) {
|
||||
i++;
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
const T* nth(size_t N) const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
if(i++ == N)
|
||||
return &(it->value());
|
||||
it = it->next;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool remove(const T& t){
|
||||
auto it = _root;
|
||||
auto pit = _root;
|
||||
while(it){
|
||||
if(it->value() == t){
|
||||
if(it == _root){
|
||||
_root = _root->next;
|
||||
} else {
|
||||
pit->next = it->next;
|
||||
}
|
||||
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
|
||||
delete it;
|
||||
return true;
|
||||
}
|
||||
pit = it;
|
||||
it = it->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool remove_first(Predicate predicate){
|
||||
auto it = _root;
|
||||
auto pit = _root;
|
||||
while(it){
|
||||
if(predicate(it->value())){
|
||||
if(it == _root){
|
||||
_root = _root->next;
|
||||
} else {
|
||||
pit->next = it->next;
|
||||
}
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
delete it;
|
||||
return true;
|
||||
}
|
||||
pit = it;
|
||||
it = it->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void free(){
|
||||
while(_root != nullptr){
|
||||
auto it = _root;
|
||||
_root = _root->next;
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
delete it;
|
||||
}
|
||||
_root = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* STRINGARRAY_H_ */
|
||||
@@ -1,249 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "WebAuthentication.h"
|
||||
#include <libb64/cencode.h>
|
||||
#ifdef ESP32
|
||||
#include <MD5Builder.h>
|
||||
#else
|
||||
#include "md5.h"
|
||||
#endif
|
||||
|
||||
|
||||
// Basic Auth hash = base64("username:password")
|
||||
|
||||
bool checkBasicAuthentication(const char * hash, const char * username, const char * password) {
|
||||
if (username == NULL || password == NULL || hash == NULL)
|
||||
return false;
|
||||
|
||||
size_t toencodeLen = strlen(username) + strlen(password) + 1;
|
||||
size_t encodedLen = base64_encode_expected_len(toencodeLen);
|
||||
if (strlen(hash) != encodedLen)
|
||||
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (strlen(hash) != encodedLen)
|
||||
#else
|
||||
if (strlen(hash) != encodedLen - 1)
|
||||
#endif
|
||||
return false;
|
||||
|
||||
char * toencode = new char[toencodeLen + 1];
|
||||
if (toencode == NULL) {
|
||||
return false;
|
||||
}
|
||||
char * encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
if (encoded == NULL) {
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf_P(toencode, PSTR("%s:%s"), username, password);
|
||||
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) {
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool getMD5(uint8_t * data, uint16_t len, char * output) { //33 bytes or more
|
||||
#ifdef ESP32
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(data, len);
|
||||
md5.calculate();
|
||||
md5.getChars(output);
|
||||
#else
|
||||
md5_context_t _ctx;
|
||||
|
||||
uint8_t * _buf = (uint8_t *)malloc(16);
|
||||
if (_buf == NULL)
|
||||
return false;
|
||||
memset(_buf, 0x00, 16);
|
||||
|
||||
MD5Init(&_ctx);
|
||||
MD5Update(&_ctx, data, len);
|
||||
MD5Final(_buf, &_ctx);
|
||||
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
|
||||
}
|
||||
|
||||
free(_buf);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static String genRandomMD5() {
|
||||
#ifdef ESP8266
|
||||
uint32_t r = RANDOM_REG32;
|
||||
#else
|
||||
uint32_t r = rand();
|
||||
#endif
|
||||
char * out = (char *)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t *)(&r), 4, out))
|
||||
return emptyString;
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
static String stringMD5(const String & in) {
|
||||
char * out = (char *)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out))
|
||||
return emptyString;
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
String generateDigestHash(const char * username, const char * password, const char * realm) {
|
||||
if (username == NULL || password == NULL || realm == NULL) {
|
||||
return emptyString;
|
||||
}
|
||||
char * out = (char *)malloc(33);
|
||||
String res = String(username);
|
||||
res += ':';
|
||||
res.concat(realm);
|
||||
res += ':';
|
||||
String in = res;
|
||||
in.concat(password);
|
||||
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out))
|
||||
return emptyString;
|
||||
res.concat(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
String requestDigestAuthentication(const char * realm) {
|
||||
String header = F("realm=\"");
|
||||
if (realm == NULL)
|
||||
header.concat(F("asyncesp"));
|
||||
else
|
||||
header.concat(realm);
|
||||
header.concat(F("\", qop=\"auth\", nonce=\""));
|
||||
header.concat(genRandomMD5());
|
||||
header.concat(F("\", opaque=\""));
|
||||
header.concat(genRandomMD5());
|
||||
header += '"';
|
||||
return header;
|
||||
}
|
||||
|
||||
bool checkDigestAuthentication(const char * header,
|
||||
const __FlashStringHelper * method,
|
||||
const char * username,
|
||||
const char * password,
|
||||
const char * realm,
|
||||
bool passwordIsHash,
|
||||
const char * nonce,
|
||||
const char * opaque,
|
||||
const char * uri) {
|
||||
if (username == NULL || password == NULL || header == NULL || method == NULL) {
|
||||
//os_printf("AUTH FAIL: missing requred fields\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
String myHeader = String(header);
|
||||
int nextBreak = myHeader.indexOf(',');
|
||||
if (nextBreak < 0) {
|
||||
//os_printf("AUTH FAIL: no variables\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
String myUsername = String();
|
||||
String myRealm = String();
|
||||
String myNonce = String();
|
||||
String myUri = String();
|
||||
String myResponse = String();
|
||||
String myQop = String();
|
||||
String myNc = String();
|
||||
String myCnonce = String();
|
||||
|
||||
myHeader += F(", ");
|
||||
do {
|
||||
String avLine = myHeader.substring(0, nextBreak);
|
||||
avLine.trim();
|
||||
myHeader = myHeader.substring(nextBreak + 1);
|
||||
nextBreak = myHeader.indexOf(',');
|
||||
|
||||
int eqSign = avLine.indexOf('=');
|
||||
if (eqSign < 0) {
|
||||
//os_printf("AUTH FAIL: no = sign\n");
|
||||
return false;
|
||||
}
|
||||
String varName = avLine.substring(0, eqSign);
|
||||
avLine = avLine.substring(eqSign + 1);
|
||||
if (avLine.startsWith(String('"'))) {
|
||||
avLine = avLine.substring(1, avLine.length() - 1);
|
||||
}
|
||||
|
||||
if (varName.equals(F("username"))) {
|
||||
if (!avLine.equals(username)) {
|
||||
//os_printf("AUTH FAIL: username\n");
|
||||
return false;
|
||||
}
|
||||
myUsername = avLine;
|
||||
} else if (varName.equals(F("realm"))) {
|
||||
if (realm != NULL && !avLine.equals(realm)) {
|
||||
//os_printf("AUTH FAIL: realm\n");
|
||||
return false;
|
||||
}
|
||||
myRealm = avLine;
|
||||
} else if (varName.equals(F("nonce"))) {
|
||||
if (nonce != NULL && !avLine.equals(nonce)) {
|
||||
//os_printf("AUTH FAIL: nonce\n");
|
||||
return false;
|
||||
}
|
||||
myNonce = avLine;
|
||||
} else if (varName.equals(F("opaque"))) {
|
||||
if (opaque != NULL && !avLine.equals(opaque)) {
|
||||
//os_printf("AUTH FAIL: opaque\n");
|
||||
return false;
|
||||
}
|
||||
} else if (varName.equals(F("uri"))) {
|
||||
if (uri != NULL && !avLine.equals(uri)) {
|
||||
//os_printf("AUTH FAIL: uri\n");
|
||||
return false;
|
||||
}
|
||||
myUri = avLine;
|
||||
} else if (varName.equals(F("response"))) {
|
||||
myResponse = avLine;
|
||||
} else if (varName.equals(F("qop"))) {
|
||||
myQop = avLine;
|
||||
} else if (varName.equals(F("nc"))) {
|
||||
myNc = avLine;
|
||||
} else if (varName.equals(F("cnonce"))) {
|
||||
myCnonce = avLine;
|
||||
}
|
||||
} while (nextBreak > 0);
|
||||
|
||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
|
||||
String ha2 = String(method) + ':' + myUri;
|
||||
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
|
||||
|
||||
if (myResponse.equals(stringMD5(response))) {
|
||||
//os_printf("AUTH SUCCESS\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
//os_printf("AUTH FAIL: password\n");
|
||||
return false;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef WEB_AUTHENTICATION_H_
|
||||
#define WEB_AUTHENTICATION_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
|
||||
String requestDigestAuthentication(const char * realm);
|
||||
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
|
||||
|
||||
//for storing hashed versions on the device that can be authenticated against
|
||||
String generateDigestHash(const char * username, const char * password, const char * realm);
|
||||
|
||||
#endif
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
|
||||
#define ASYNCWEBSERVERHANDLERIMPL_H_
|
||||
|
||||
#include <string>
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
#include "stddef.h"
|
||||
#include <time.h>
|
||||
|
||||
class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
private:
|
||||
bool _getFile(AsyncWebServerRequest *request);
|
||||
bool _fileExists(AsyncWebServerRequest *request, const String& path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _default_file;
|
||||
String _cache_control;
|
||||
String _last_modified;
|
||||
AwsTemplateProcessor _callback;
|
||||
bool _isDir;
|
||||
bool _gzipFirst;
|
||||
uint8_t _gzipStats;
|
||||
public:
|
||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
AsyncStaticWebHandler& setIsDir(bool isDir);
|
||||
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
||||
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
||||
#ifdef ESP8266
|
||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
||||
#endif
|
||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
||||
};
|
||||
|
||||
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArRequestHandlerFunction _onRequest;
|
||||
ArUploadHandlerFunction _onUpload;
|
||||
ArBodyHandlerFunction _onBody;
|
||||
bool _isRegex;
|
||||
public:
|
||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||
void setUri(const String& uri){
|
||||
_uri = uri;
|
||||
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
||||
}
|
||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
||||
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
|
||||
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
|
||||
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
|
||||
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
||||
|
||||
if(!_onRequest)
|
||||
return false;
|
||||
|
||||
if(!(_method & request->method()))
|
||||
return false;
|
||||
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
if (_isRegex) {
|
||||
std::regex pattern(_uri.c_str());
|
||||
std::smatch matches;
|
||||
std::string s(request->url().c_str());
|
||||
if(std::regex_search(s, matches, pattern)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
||||
request->_addPathParam(matches[i].str().c_str());
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (_uri.length() && _uri.startsWith("/*.")) {
|
||||
String uriTemplate = String (_uri);
|
||||
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
|
||||
if (!request->url().endsWith(uriTemplate))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
if (_uri.length() && _uri.endsWith("*")) {
|
||||
String uriTemplate = String(_uri);
|
||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
||||
if (!request->url().startsWith(uriTemplate))
|
||||
return false;
|
||||
}
|
||||
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
||||
return false;
|
||||
|
||||
request->addInterestingHeader("ANY");
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onRequest)
|
||||
_onRequest(request);
|
||||
else
|
||||
request->send(500);
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onUpload)
|
||||
_onUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onBody)
|
||||
_onBody(request, data, len, index, total);
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
||||
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr)
|
||||
{
|
||||
// Ensure leading '/'
|
||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri;
|
||||
if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path;
|
||||
|
||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||
_isDir = _path[_path.length()-1] == '/';
|
||||
|
||||
// Remove the trailing '/' so we can handle default file
|
||||
// Notice that root will be "" not "/"
|
||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
||||
|
||||
// Reset stats
|
||||
_gzipFirst = false;
|
||||
_gzipStats = 0xF8;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
|
||||
_isDir = isDir;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
|
||||
_default_file = String(filename);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
|
||||
_cache_control = String(cache_control);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
|
||||
_last_modified = last_modified;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
|
||||
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
||||
char format[strlen_P(formatP) + 1];
|
||||
strcpy_P(format, formatP);
|
||||
|
||||
char result[30];
|
||||
strftime(result, sizeof(result), format, last_modified);
|
||||
return setLastModified((const char *)result);
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
|
||||
return setLastModified((struct tm *)gmtime(&last_modified));
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
|
||||
time_t last_modified;
|
||||
if(time(&last_modified) == 0) //time is not yet set
|
||||
return *this;
|
||||
return setLastModified(last_modified);
|
||||
}
|
||||
#endif
|
||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
||||
if(request->method() != HTTP_GET
|
||||
|| !request->url().startsWith(_uri)
|
||||
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
|
||||
){
|
||||
return false;
|
||||
}
|
||||
if (_getFile(request)) {
|
||||
// We interested in "If-Modified-Since" header to check if file was modified
|
||||
if (_last_modified.length())
|
||||
request->addInterestingHeader(F("If-Modified-Since"));
|
||||
|
||||
if(_cache_control.length())
|
||||
request->addInterestingHeader(F("If-None-Match"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
||||
{
|
||||
// Remove the found uri
|
||||
String path = request->url().substring(_uri.length());
|
||||
|
||||
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
||||
|
||||
path = _path + path;
|
||||
|
||||
// Do we have a file or .gz file
|
||||
if (!canSkipFileCheck && _fileExists(request, path))
|
||||
return true;
|
||||
|
||||
// Can't handle if not default file
|
||||
if (_default_file.length() == 0)
|
||||
return false;
|
||||
|
||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||
if (path.length() == 0 || path[path.length()-1] != '/')
|
||||
path += String('/');
|
||||
path += _default_file;
|
||||
|
||||
return _fileExists(request, path);
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||
#else
|
||||
#define FILE_IS_REAL(f) (f == true)
|
||||
#endif
|
||||
|
||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
|
||||
{
|
||||
bool fileFound = false;
|
||||
bool gzipFound = false;
|
||||
|
||||
String gzip = path + F(".gz");
|
||||
|
||||
if (_gzipFirst) {
|
||||
if (_fs.exists(gzip)) {
|
||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
if (!gzipFound){
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
if (!fileFound){
|
||||
if (_fs.exists(gzip)) {
|
||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool found = fileFound || gzipFound;
|
||||
|
||||
if (found) {
|
||||
// Extract the file name from the path and keep it in _tempObject
|
||||
size_t pathLen = path.length();
|
||||
char * _tempPath = (char*)malloc(pathLen+1);
|
||||
snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str());
|
||||
request->_tempObject = (void*)_tempPath;
|
||||
|
||||
// Calculate gzip statistic
|
||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
|
||||
{
|
||||
uint8_t w = value;
|
||||
uint8_t n;
|
||||
for (n=0; w!=0; n++) w&=w-1;
|
||||
return n;
|
||||
}
|
||||
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
||||
{
|
||||
// Get the filename from request->_tempObject and free it
|
||||
String filename = String((char*)request->_tempObject);
|
||||
free(request->_tempObject);
|
||||
request->_tempObject = NULL;
|
||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
|
||||
if (request->_tempFile == true) {
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
if (lw) setLastModified(gmtime(&lw));
|
||||
String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size
|
||||
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
|
||||
request->_tempFile.close();
|
||||
request->send(304); // Not modified
|
||||
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
|
||||
request->_tempFile.close();
|
||||
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
||||
response->addHeader(F("Cache-Control"), _cache_control);
|
||||
response->addHeader(F("ETag"), etag);
|
||||
request->send(response);
|
||||
} else {
|
||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||
if (_last_modified.length())
|
||||
response->addHeader(F("Last-Modified"), _last_modified);
|
||||
if (_cache_control.length()){
|
||||
response->addHeader(F("Cache-Control"), _cache_control);
|
||||
response->addHeader(F("ETag"), etag);
|
||||
}
|
||||
request->send(response);
|
||||
}
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
|
||||
#ifdef Arduino_h
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
||||
|
||||
class AsyncBasicResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
|
||||
public:
|
||||
AsyncBasicResponse(int code, const String & contentType = String(), const String & content = String());
|
||||
void _respond(AsyncWebServerRequest * request);
|
||||
size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncAbstractResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _head;
|
||||
// Data is inserted into cache at begin().
|
||||
// This is inefficient with vector, but if we use some other container,
|
||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
||||
// so by gaining performance in one place, we'll lose it in another.
|
||||
std::vector<uint8_t> _cache;
|
||||
size_t _readDataFromCacheOrContent(uint8_t * data, const size_t len);
|
||||
size_t _fillBufferAndProcessTemplates(uint8_t * buf, size_t maxLen);
|
||||
|
||||
protected:
|
||||
AwsTemplateProcessor _callback;
|
||||
|
||||
public:
|
||||
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
|
||||
void _respond(AsyncWebServerRequest * request);
|
||||
size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const {
|
||||
return false;
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef TEMPLATE_PLACEHOLDER
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#endif
|
||||
|
||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||
class AsyncFileResponse : public AsyncAbstractResponse {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
private:
|
||||
File _content;
|
||||
String _path;
|
||||
void _setContentType(const String & path);
|
||||
|
||||
public:
|
||||
AsyncFileResponse(FS & fs, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncFileResponse(File content, const String & path, const String & contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
~AsyncFileResponse();
|
||||
bool _sourceValid() const {
|
||||
return !!(_content);
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncStreamResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
Stream * _content;
|
||||
|
||||
public:
|
||||
AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
bool _sourceValid() const {
|
||||
return !!(_content);
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncCallbackResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
bool _sourceValid() const {
|
||||
return !!(_content);
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncChunkedResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
bool _sourceValid() const {
|
||||
return !!(_content);
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncProgmemResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
const uint8_t * _content;
|
||||
size_t _readLength;
|
||||
|
||||
public:
|
||||
AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
bool _sourceValid() const {
|
||||
return true;
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class cbuf;
|
||||
|
||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||
private:
|
||||
std::unique_ptr<cbuf> _content;
|
||||
|
||||
public:
|
||||
AsyncResponseStream(const String & contentType, size_t bufferSize);
|
||||
~AsyncResponseStream();
|
||||
bool _sourceValid() const {
|
||||
return (_state < RESPONSE_END);
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t * buf, size_t maxLen) override;
|
||||
size_t write(const uint8_t * data, size_t len);
|
||||
size_t write(uint8_t data);
|
||||
using Print::write;
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
|
||||
@@ -1,781 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebResponseImpl.h"
|
||||
#include "cbuf.h"
|
||||
|
||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
||||
void * memchr(void * ptr, int ch, size_t count) {
|
||||
unsigned char * p = static_cast<unsigned char *>(ptr);
|
||||
while (count--)
|
||||
if (*p++ == static_cast<unsigned char>(ch))
|
||||
return --p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abstract Response
|
||||
* */
|
||||
const char * AsyncWebServerResponse::_responseCodeToString(int code) {
|
||||
return reinterpret_cast<const char *>(responseCodeToString(code));
|
||||
}
|
||||
|
||||
const __FlashStringHelper * AsyncWebServerResponse::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("");
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse::AsyncWebServerResponse()
|
||||
: _code(0)
|
||||
, _contentType()
|
||||
, _contentLength(0)
|
||||
, _sendContentLength(true)
|
||||
, _chunked(false)
|
||||
, _headLength(0)
|
||||
, _sentLength(0)
|
||||
, _ackedLength(0)
|
||||
, _writtenLength(0)
|
||||
, _state(RESPONSE_SETUP) {
|
||||
for (const auto & header : DefaultHeaders::Instance()) {
|
||||
_headers.emplace_back(header);
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse::~AsyncWebServerResponse() = default;
|
||||
|
||||
void AsyncWebServerResponse::setCode(int code) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_code = code;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentLength(size_t len) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_contentLength = len;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentType(const String & type) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_contentType = type;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::addHeader(const String & name, const String & value) {
|
||||
_headers.emplace_back(name, value);
|
||||
}
|
||||
|
||||
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
|
||||
if (version) {
|
||||
addHeader(F("Accept-Ranges"), F("none"));
|
||||
if (_chunked)
|
||||
addHeader(F("Transfer-Encoding"), F("chunked"));
|
||||
}
|
||||
String out = String();
|
||||
int bufSize = 300;
|
||||
char buf[bufSize];
|
||||
|
||||
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
|
||||
out.concat(buf);
|
||||
|
||||
if (_sendContentLength) {
|
||||
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
|
||||
out.concat(buf);
|
||||
}
|
||||
if (_contentType.length()) {
|
||||
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
|
||||
out.concat(buf);
|
||||
}
|
||||
|
||||
for (const auto & header : _headers) {
|
||||
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
|
||||
out.concat(buf);
|
||||
}
|
||||
_headers.clear();
|
||||
|
||||
out.concat(F("\r\n"));
|
||||
_headLength = out.length();
|
||||
return out;
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::_started() const {
|
||||
return _state > RESPONSE_SETUP;
|
||||
}
|
||||
bool AsyncWebServerResponse::_finished() const {
|
||||
return _state > RESPONSE_WAIT_ACK;
|
||||
}
|
||||
bool AsyncWebServerResponse::_failed() const {
|
||||
return _state == RESPONSE_FAILED;
|
||||
}
|
||||
bool AsyncWebServerResponse::_sourceValid() const {
|
||||
return false;
|
||||
}
|
||||
void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) {
|
||||
_state = RESPONSE_END;
|
||||
request->client()->close();
|
||||
}
|
||||
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
||||
(void)request;
|
||||
(void)len;
|
||||
(void)time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* String/Code Response
|
||||
* */
|
||||
AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
if (_content.length()) {
|
||||
_contentLength = _content.length();
|
||||
if (!_contentType.length())
|
||||
_contentType = F("text/plain");
|
||||
}
|
||||
addHeader(F("Connection"), F("close"));
|
||||
}
|
||||
|
||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) {
|
||||
_state = RESPONSE_HEADERS;
|
||||
String out = _assembleHead(request->version());
|
||||
size_t outLen = out.length();
|
||||
size_t space = request->client()->space();
|
||||
if (!_contentLength && space >= outLen) {
|
||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
} else if (_contentLength && space >= outLen + _contentLength) {
|
||||
out += _content;
|
||||
outLen += _contentLength;
|
||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
} else if (space && space < outLen) {
|
||||
String partial = out.substring(0, space);
|
||||
_content = out.substring(space) + _content;
|
||||
_contentLength += outLen - space;
|
||||
_writtenLength += request->client()->write(partial.c_str(), partial.length());
|
||||
_state = RESPONSE_CONTENT;
|
||||
} else if (space > outLen && space < (outLen + _contentLength)) {
|
||||
size_t shift = space - outLen;
|
||||
outLen += shift;
|
||||
_sentLength += shift;
|
||||
out += _content.substring(0, shift);
|
||||
_content = _content.substring(shift);
|
||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||
_state = RESPONSE_CONTENT;
|
||||
} else {
|
||||
_content = out + _content;
|
||||
_contentLength += outLen;
|
||||
_state = RESPONSE_CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
_ackedLength += len;
|
||||
if (_state == RESPONSE_CONTENT) {
|
||||
size_t available = _contentLength - _sentLength;
|
||||
size_t space = request->client()->space();
|
||||
//we can fit in this packet
|
||||
if (space > available) {
|
||||
_writtenLength += request->client()->write(_content.c_str(), available);
|
||||
_content = String();
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
return available;
|
||||
}
|
||||
//send some data, the rest on ack
|
||||
String out = _content.substring(0, space);
|
||||
_content = _content.substring(space);
|
||||
_sentLength += space;
|
||||
_writtenLength += request->client()->write(out.c_str(), space);
|
||||
return space;
|
||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
||||
if (_ackedLength >= _writtenLength) {
|
||||
_state = RESPONSE_END;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abstract Response
|
||||
* */
|
||||
|
||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback)
|
||||
: _callback(callback) {
|
||||
// In case of template processing, we're unable to determine real response size
|
||||
if (callback) {
|
||||
_contentLength = 0;
|
||||
_sendContentLength = false;
|
||||
_chunked = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) {
|
||||
addHeader(F("Connection"), F("close"));
|
||||
_head = _assembleHead(request->version());
|
||||
_state = RESPONSE_HEADERS;
|
||||
_ack(request, 0, 0);
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
if (!_sourceValid()) {
|
||||
_state = RESPONSE_FAILED;
|
||||
request->client()->close();
|
||||
return 0;
|
||||
}
|
||||
_ackedLength += len;
|
||||
size_t space = request->client()->space();
|
||||
|
||||
size_t headLen = _head.length();
|
||||
if (_state == RESPONSE_HEADERS) {
|
||||
if (space >= headLen) {
|
||||
_state = RESPONSE_CONTENT;
|
||||
space -= headLen;
|
||||
} else {
|
||||
String out = _head.substring(0, space);
|
||||
_head = _head.substring(space);
|
||||
_writtenLength += request->client()->write(out.c_str(), out.length());
|
||||
return out.length();
|
||||
}
|
||||
}
|
||||
|
||||
if (_state == RESPONSE_CONTENT) {
|
||||
size_t outLen;
|
||||
if (_chunked) {
|
||||
if (space <= 8) {
|
||||
return 0;
|
||||
}
|
||||
outLen = space;
|
||||
} else if (!_sendContentLength) {
|
||||
outLen = space;
|
||||
} else {
|
||||
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
|
||||
}
|
||||
|
||||
uint8_t * buf = (uint8_t *)malloc(outLen + headLen);
|
||||
if (!buf) {
|
||||
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (headLen) {
|
||||
memcpy(buf, _head.c_str(), _head.length());
|
||||
}
|
||||
|
||||
size_t readLen = 0;
|
||||
|
||||
if (_chunked) {
|
||||
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
|
||||
// See RFC2616 sections 2, 3.6.1.
|
||||
readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
|
||||
if (readLen == RESPONSE_TRY_AGAIN) {
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
outLen = sprintf_P((char *)buf + headLen, PSTR("%x"), readLen) + headLen;
|
||||
while (outLen < headLen + 4)
|
||||
buf[outLen++] = ' ';
|
||||
buf[outLen++] = '\r';
|
||||
buf[outLen++] = '\n';
|
||||
outLen += readLen;
|
||||
buf[outLen++] = '\r';
|
||||
buf[outLen++] = '\n';
|
||||
} else {
|
||||
readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
|
||||
if (readLen == RESPONSE_TRY_AGAIN) {
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
outLen = readLen + headLen;
|
||||
}
|
||||
|
||||
if (headLen) {
|
||||
_head = String();
|
||||
}
|
||||
|
||||
if (outLen) {
|
||||
_writtenLength += request->client()->write((const char *)buf, outLen);
|
||||
}
|
||||
|
||||
if (_chunked) {
|
||||
_sentLength += readLen;
|
||||
} else {
|
||||
_sentLength += outLen - headLen;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
return outLen;
|
||||
|
||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
||||
if (!_sendContentLength || _ackedLength >= _writtenLength) {
|
||||
_state = RESPONSE_END;
|
||||
if (!_chunked && !_sendContentLength)
|
||||
request->client()->close(true);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) {
|
||||
// If we have something in cache, copy it to buffer
|
||||
const size_t readFromCache = std::min(len, _cache.size());
|
||||
if (readFromCache) {
|
||||
memcpy(data, _cache.data(), readFromCache);
|
||||
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
|
||||
}
|
||||
// If we need to read more...
|
||||
const size_t needFromFile = len - readFromCache;
|
||||
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
|
||||
return readFromCache + readFromContent;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) {
|
||||
if (!_callback)
|
||||
return _fillBuffer(data, len);
|
||||
|
||||
const size_t originalLen = len;
|
||||
len = _readDataFromCacheOrContent(data, len);
|
||||
// Now we've read 'len' bytes, either from cache or from file
|
||||
// Search for template placeholders
|
||||
uint8_t * pTemplateStart = data;
|
||||
while ((pTemplateStart < &data[len])
|
||||
&& (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
||||
uint8_t * pTemplateEnd =
|
||||
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
||||
// temporary buffer to hold parameter name
|
||||
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
||||
String paramName;
|
||||
// If closing placeholder is found:
|
||||
if (pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
|
||||
if (paramNameLength) {
|
||||
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
||||
buf[paramNameLength] = 0;
|
||||
paramName = String(reinterpret_cast<char *>(buf));
|
||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
||||
// remove the 2nd percent sign
|
||||
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
||||
++pTemplateStart;
|
||||
}
|
||||
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
||||
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
||||
const size_t readFromCacheOrContent =
|
||||
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
||||
if (readFromCacheOrContent) {
|
||||
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||
if (pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
*pTemplateEnd = 0;
|
||||
paramName = String(reinterpret_cast<char *>(buf));
|
||||
// Copy remaining read-ahead data into cache
|
||||
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
pTemplateEnd = &data[len - 1];
|
||||
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
||||
{
|
||||
// but first, store read file data in cache
|
||||
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
++pTemplateStart;
|
||||
}
|
||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
if (paramName.length()) {
|
||||
// call callback and replace with result.
|
||||
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
||||
// Data after pTemplateEnd may need to be moved.
|
||||
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
||||
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
||||
const String paramValue(_callback(paramName));
|
||||
const char * pvstr = paramValue.c_str();
|
||||
const unsigned int pvlen = paramValue.length();
|
||||
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
||||
// make room for param value
|
||||
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
||||
if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
|
||||
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
||||
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
||||
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
||||
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
||||
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
||||
// Move the entire data after the placeholder
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
// 3. replace placeholder with actual value
|
||||
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
||||
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
||||
if (numBytesCopied < pvlen) {
|
||||
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
|
||||
} else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
||||
// there is some free room, fill it from cache
|
||||
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
||||
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
||||
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
||||
} else { // result is copied fully; it is longer than placeholder text
|
||||
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
||||
len = std::min(len + roomTaken, originalLen);
|
||||
}
|
||||
}
|
||||
} // while(pTemplateStart)
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* File Response
|
||||
* */
|
||||
|
||||
AsyncFileResponse::~AsyncFileResponse() {
|
||||
if (_content)
|
||||
_content.close();
|
||||
}
|
||||
|
||||
void AsyncFileResponse::_setContentType(const String & path) {
|
||||
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
|
||||
extern const __FlashStringHelper * getContentType(const String & path);
|
||||
_contentType = getContentType(path);
|
||||
#else
|
||||
if (path.endsWith(F(".html")))
|
||||
_contentType = F("text/html");
|
||||
else if (path.endsWith(F(".htm")))
|
||||
_contentType = F("text/html");
|
||||
else if (path.endsWith(F(".css")))
|
||||
_contentType = F("text/css");
|
||||
else if (path.endsWith(F(".json")))
|
||||
_contentType = F("application/json");
|
||||
else if (path.endsWith(F(".js")))
|
||||
_contentType = F("application/javascript");
|
||||
else if (path.endsWith(F(".png")))
|
||||
_contentType = F("image/png");
|
||||
else if (path.endsWith(F(".gif")))
|
||||
_contentType = F("image/gif");
|
||||
else if (path.endsWith(F(".jpg")))
|
||||
_contentType = F("image/jpeg");
|
||||
else if (path.endsWith(F(".ico")))
|
||||
_contentType = F("image/x-icon");
|
||||
else if (path.endsWith(F(".svg")))
|
||||
_contentType = F("image/svg+xml");
|
||||
else if (path.endsWith(F(".eot")))
|
||||
_contentType = F("font/eot");
|
||||
else if (path.endsWith(F(".woff")))
|
||||
_contentType = F("font/woff");
|
||||
else if (path.endsWith(F(".woff2")))
|
||||
_contentType = F("font/woff2");
|
||||
else if (path.endsWith(F(".ttf")))
|
||||
_contentType = F("font/ttf");
|
||||
else if (path.endsWith(F(".xml")))
|
||||
_contentType = F("text/xml");
|
||||
else if (path.endsWith(F(".pdf")))
|
||||
_contentType = F("application/pdf");
|
||||
else if (path.endsWith(F(".zip")))
|
||||
_contentType = F("application/zip");
|
||||
else if (path.endsWith(F(".gz")))
|
||||
_contentType = F("application/x-gzip");
|
||||
else
|
||||
_contentType = F("text/plain");
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) {
|
||||
_path = _path + F(".gz");
|
||||
addHeader(F("Content-Encoding"), F("gzip"));
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (contentType.length() == 0)
|
||||
_setContentType(path);
|
||||
else
|
||||
_contentType = contentType;
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char * filename = (char *)path.c_str() + filenameStart;
|
||||
|
||||
if (download) {
|
||||
// set filename and force download
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
} else {
|
||||
// set filename and force rendering
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename);
|
||||
}
|
||||
addHeader(F("Content-Disposition"), buf);
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) {
|
||||
addHeader(F("Content-Encoding"), F("gzip"));
|
||||
_callback = nullptr; // Unable to process gzipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
_content = content;
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (contentType.length() == 0)
|
||||
_setContentType(path);
|
||||
else
|
||||
_contentType = contentType;
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char * filename = (char *)path.c_str() + filenameStart;
|
||||
|
||||
if (download) {
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
} else {
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename);
|
||||
}
|
||||
addHeader(F("Content-Disposition"), buf);
|
||||
}
|
||||
|
||||
size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) {
|
||||
return _content.read(data, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream Response
|
||||
* */
|
||||
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_content = &stream;
|
||||
_contentLength = len;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) {
|
||||
size_t available = _content->available();
|
||||
size_t outLen = (available > len) ? len : available;
|
||||
size_t i;
|
||||
for (i = 0; i < outLen; i++)
|
||||
data[i] = _content->read();
|
||||
return outLen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback Response
|
||||
* */
|
||||
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
|
||||
: AsyncAbstractResponse(templateCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = len;
|
||||
if (!len)
|
||||
_sendContentLength = false;
|
||||
_contentType = contentType;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Chunked Response
|
||||
* */
|
||||
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
|
||||
: AsyncAbstractResponse(processorCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
_sendContentLength = false;
|
||||
_chunked = true;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Progmem Response
|
||||
* */
|
||||
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
_contentLength = len;
|
||||
_readLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) {
|
||||
size_t left = _contentLength - _readLength;
|
||||
if (left > len) {
|
||||
memcpy_P(data, _content + _readLength, len);
|
||||
_readLength += len;
|
||||
return len;
|
||||
}
|
||||
memcpy_P(data, _content + _readLength, left);
|
||||
_readLength += left;
|
||||
return left;
|
||||
}
|
||||
|
||||
/*
|
||||
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
||||
* */
|
||||
|
||||
AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) {
|
||||
_code = 200;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); //std::make_unique<cbuf>(bufferSize);
|
||||
}
|
||||
|
||||
AsyncResponseStream::~AsyncResponseStream() = default;
|
||||
|
||||
size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) {
|
||||
return _content->read((char *)buf, maxLen);
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::write(const uint8_t * data, size_t len) {
|
||||
if (_started())
|
||||
return 0;
|
||||
|
||||
if (len > _content->room()) {
|
||||
size_t needed = len - _content->room();
|
||||
_content->resizeAdd(needed);
|
||||
}
|
||||
size_t written = _content->write((const char *)data, len);
|
||||
_contentLength += written;
|
||||
return written;
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::write(uint8_t data) {
|
||||
return write(&data, 1);
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
#include "AsyncJson.h"
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest * request) {
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
}
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest * request) {
|
||||
return WiFi.localIP() != request->client()->localIP();
|
||||
}
|
||||
|
||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||
const char * fs::FileOpenMode::read = "r";
|
||||
const char * fs::FileOpenMode::write = "w";
|
||||
const char * fs::FileOpenMode::append = "a";
|
||||
#endif
|
||||
|
||||
AsyncWebServer::AsyncWebServer(uint16_t port)
|
||||
: _server(port)
|
||||
, _rewrites(LinkedList<AsyncWebRewrite *>([](AsyncWebRewrite * r) { delete r; }))
|
||||
, _handlers(LinkedList<AsyncWebHandler *>([](AsyncWebHandler * h) { delete h; })) {
|
||||
_catchAllHandler = new AsyncCallbackWebHandler();
|
||||
if (_catchAllHandler == NULL)
|
||||
return;
|
||||
_server.onClient(
|
||||
[](void * s, AsyncClient * c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
c->setRxTimeout(3);
|
||||
AsyncWebServerRequest * r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
|
||||
if (r == NULL) {
|
||||
c->close(true);
|
||||
c->free();
|
||||
delete c;
|
||||
}
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
AsyncWebServer::~AsyncWebServer() {
|
||||
reset();
|
||||
end();
|
||||
if (_catchAllHandler)
|
||||
delete _catchAllHandler;
|
||||
}
|
||||
|
||||
AsyncWebRewrite & AsyncWebServer::addRewrite(AsyncWebRewrite * rewrite) {
|
||||
_rewrites.add(rewrite);
|
||||
return *rewrite;
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite * rewrite) {
|
||||
return _rewrites.remove(rewrite);
|
||||
}
|
||||
|
||||
AsyncWebRewrite & AsyncWebServer::rewrite(const char * from, const char * to) {
|
||||
return addRewrite(new AsyncWebRewrite(from, to));
|
||||
}
|
||||
|
||||
AsyncWebHandler & AsyncWebServer::addHandler(AsyncWebHandler * handler) {
|
||||
_handlers.add(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeHandler(AsyncWebHandler * handler) {
|
||||
return _handlers.remove(handler);
|
||||
}
|
||||
|
||||
void AsyncWebServer::begin() {
|
||||
_server.setNoDelay(true);
|
||||
_server.begin();
|
||||
}
|
||||
|
||||
void AsyncWebServer::end() {
|
||||
_server.end();
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void * arg) {
|
||||
_server.onSslFileRequest(cb, arg);
|
||||
}
|
||||
|
||||
void AsyncWebServer::beginSecure(const char * cert, const char * key, const char * password) {
|
||||
_server.beginSecure(cert, key, password);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest * request) {
|
||||
delete request;
|
||||
}
|
||||
|
||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest * request) {
|
||||
for (const auto & r : _rewrites) {
|
||||
if (r->match(request)) {
|
||||
request->_url = r->toUrl();
|
||||
request->_addGetParams(r->params());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest * request) {
|
||||
for (const auto & h : _handlers) {
|
||||
if (h->filter(request) && h->canHandle(request)) {
|
||||
request->setHandler(h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
request->addInterestingHeader(F("ANY"));
|
||||
request->setHandler(_catchAllHandler);
|
||||
}
|
||||
|
||||
|
||||
AsyncCallbackWebHandler & AsyncWebServer::on(const char * uri,
|
||||
WebRequestMethodComposite method,
|
||||
ArRequestHandlerFunction onRequest,
|
||||
ArUploadHandlerFunction onUpload,
|
||||
ArBodyHandlerFunction onBody) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
handler->onRequest(onRequest);
|
||||
handler->onUpload(onUpload);
|
||||
handler->onBody(onBody);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler &
|
||||
AsyncWebServer::on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
handler->onRequest(onRequest);
|
||||
handler->onUpload(onUpload);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler & AsyncWebServer::on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
handler->onRequest(onRequest);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler & AsyncWebServer::on(const char * uri, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler * handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->onRequest(onRequest);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
// added by proddy for EMS-ESP
|
||||
void AsyncWebServer::on(const char * uri, ArJsonRequestHandlerFunction onRequest) {
|
||||
auto * handler = new AsyncCallbackJsonWebHandler(uri, onRequest);
|
||||
addHandler(handler);
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler & AsyncWebServer::serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control) {
|
||||
AsyncStaticWebHandler * handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
|
||||
_catchAllHandler->onRequest(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
|
||||
_catchAllHandler->onUpload(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
|
||||
_catchAllHandler->onBody(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::reset() {
|
||||
_rewrites.free();
|
||||
_handlers.free();
|
||||
|
||||
if (_catchAllHandler != NULL) {
|
||||
_catchAllHandler->onRequest(NULL);
|
||||
_catchAllHandler->onUpload(NULL);
|
||||
_catchAllHandler->onBody(NULL);
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* FIPS-180-1 compliant SHA-1 implementation
|
||||
*
|
||||
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is part of mbed TLS (https://tls.mbed.org)
|
||||
* Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
|
||||
#include "SHA1Builder.h"
|
||||
|
||||
// 32-bit integer manipulation macros (big endian)
|
||||
|
||||
#ifndef GET_UINT32_BE
|
||||
#define GET_UINT32_BE(n, b, i) \
|
||||
{ (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
|
||||
#endif
|
||||
|
||||
#ifndef PUT_UINT32_BE
|
||||
#define PUT_UINT32_BE(n, b, i) \
|
||||
{ \
|
||||
(b)[(i)] = (uint8_t)((n) >> 24); \
|
||||
(b)[(i) + 1] = (uint8_t)((n) >> 16); \
|
||||
(b)[(i) + 2] = (uint8_t)((n) >> 8); \
|
||||
(b)[(i) + 3] = (uint8_t)((n)); \
|
||||
}
|
||||
#endif
|
||||
|
||||
// Constants
|
||||
|
||||
static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Private methods
|
||||
|
||||
void SHA1Builder::process(const uint8_t * data) {
|
||||
uint32_t temp, W[16], A, B, C, D, E;
|
||||
|
||||
GET_UINT32_BE(W[0], data, 0);
|
||||
GET_UINT32_BE(W[1], data, 4);
|
||||
GET_UINT32_BE(W[2], data, 8);
|
||||
GET_UINT32_BE(W[3], data, 12);
|
||||
GET_UINT32_BE(W[4], data, 16);
|
||||
GET_UINT32_BE(W[5], data, 20);
|
||||
GET_UINT32_BE(W[6], data, 24);
|
||||
GET_UINT32_BE(W[7], data, 28);
|
||||
GET_UINT32_BE(W[8], data, 32);
|
||||
GET_UINT32_BE(W[9], data, 36);
|
||||
GET_UINT32_BE(W[10], data, 40);
|
||||
GET_UINT32_BE(W[11], data, 44);
|
||||
GET_UINT32_BE(W[12], data, 48);
|
||||
GET_UINT32_BE(W[13], data, 52);
|
||||
GET_UINT32_BE(W[14], data, 56);
|
||||
GET_UINT32_BE(W[15], data, 60);
|
||||
|
||||
#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
|
||||
|
||||
#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
|
||||
|
||||
#define sha1_P(a, b, c, d, e, x) \
|
||||
{ \
|
||||
e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
|
||||
b = sha1_S(b, 30); \
|
||||
}
|
||||
|
||||
A = state[0];
|
||||
B = state[1];
|
||||
C = state[2];
|
||||
D = state[3];
|
||||
E = state[4];
|
||||
|
||||
#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define sha1_K 0x5A827999
|
||||
|
||||
sha1_P(A, B, C, D, E, W[0]);
|
||||
sha1_P(E, A, B, C, D, W[1]);
|
||||
sha1_P(D, E, A, B, C, W[2]);
|
||||
sha1_P(C, D, E, A, B, W[3]);
|
||||
sha1_P(B, C, D, E, A, W[4]);
|
||||
sha1_P(A, B, C, D, E, W[5]);
|
||||
sha1_P(E, A, B, C, D, W[6]);
|
||||
sha1_P(D, E, A, B, C, W[7]);
|
||||
sha1_P(C, D, E, A, B, W[8]);
|
||||
sha1_P(B, C, D, E, A, W[9]);
|
||||
sha1_P(A, B, C, D, E, W[10]);
|
||||
sha1_P(E, A, B, C, D, W[11]);
|
||||
sha1_P(D, E, A, B, C, W[12]);
|
||||
sha1_P(C, D, E, A, B, W[13]);
|
||||
sha1_P(B, C, D, E, A, W[14]);
|
||||
sha1_P(A, B, C, D, E, W[15]);
|
||||
sha1_P(E, A, B, C, D, sha1_R(16));
|
||||
sha1_P(D, E, A, B, C, sha1_R(17));
|
||||
sha1_P(C, D, E, A, B, sha1_R(18));
|
||||
sha1_P(B, C, D, E, A, sha1_R(19));
|
||||
|
||||
#undef sha1_K
|
||||
#undef sha1_F
|
||||
|
||||
#define sha1_F(x, y, z) (x ^ y ^ z)
|
||||
#define sha1_K 0x6ED9EBA1
|
||||
|
||||
sha1_P(A, B, C, D, E, sha1_R(20));
|
||||
sha1_P(E, A, B, C, D, sha1_R(21));
|
||||
sha1_P(D, E, A, B, C, sha1_R(22));
|
||||
sha1_P(C, D, E, A, B, sha1_R(23));
|
||||
sha1_P(B, C, D, E, A, sha1_R(24));
|
||||
sha1_P(A, B, C, D, E, sha1_R(25));
|
||||
sha1_P(E, A, B, C, D, sha1_R(26));
|
||||
sha1_P(D, E, A, B, C, sha1_R(27));
|
||||
sha1_P(C, D, E, A, B, sha1_R(28));
|
||||
sha1_P(B, C, D, E, A, sha1_R(29));
|
||||
sha1_P(A, B, C, D, E, sha1_R(30));
|
||||
sha1_P(E, A, B, C, D, sha1_R(31));
|
||||
sha1_P(D, E, A, B, C, sha1_R(32));
|
||||
sha1_P(C, D, E, A, B, sha1_R(33));
|
||||
sha1_P(B, C, D, E, A, sha1_R(34));
|
||||
sha1_P(A, B, C, D, E, sha1_R(35));
|
||||
sha1_P(E, A, B, C, D, sha1_R(36));
|
||||
sha1_P(D, E, A, B, C, sha1_R(37));
|
||||
sha1_P(C, D, E, A, B, sha1_R(38));
|
||||
sha1_P(B, C, D, E, A, sha1_R(39));
|
||||
|
||||
#undef sha1_K
|
||||
#undef sha1_F
|
||||
|
||||
#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
|
||||
#define sha1_K 0x8F1BBCDC
|
||||
|
||||
sha1_P(A, B, C, D, E, sha1_R(40));
|
||||
sha1_P(E, A, B, C, D, sha1_R(41));
|
||||
sha1_P(D, E, A, B, C, sha1_R(42));
|
||||
sha1_P(C, D, E, A, B, sha1_R(43));
|
||||
sha1_P(B, C, D, E, A, sha1_R(44));
|
||||
sha1_P(A, B, C, D, E, sha1_R(45));
|
||||
sha1_P(E, A, B, C, D, sha1_R(46));
|
||||
sha1_P(D, E, A, B, C, sha1_R(47));
|
||||
sha1_P(C, D, E, A, B, sha1_R(48));
|
||||
sha1_P(B, C, D, E, A, sha1_R(49));
|
||||
sha1_P(A, B, C, D, E, sha1_R(50));
|
||||
sha1_P(E, A, B, C, D, sha1_R(51));
|
||||
sha1_P(D, E, A, B, C, sha1_R(52));
|
||||
sha1_P(C, D, E, A, B, sha1_R(53));
|
||||
sha1_P(B, C, D, E, A, sha1_R(54));
|
||||
sha1_P(A, B, C, D, E, sha1_R(55));
|
||||
sha1_P(E, A, B, C, D, sha1_R(56));
|
||||
sha1_P(D, E, A, B, C, sha1_R(57));
|
||||
sha1_P(C, D, E, A, B, sha1_R(58));
|
||||
sha1_P(B, C, D, E, A, sha1_R(59));
|
||||
|
||||
#undef sha1_K
|
||||
#undef sha1_F
|
||||
|
||||
#define sha1_F(x, y, z) (x ^ y ^ z)
|
||||
#define sha1_K 0xCA62C1D6
|
||||
|
||||
sha1_P(A, B, C, D, E, sha1_R(60));
|
||||
sha1_P(E, A, B, C, D, sha1_R(61));
|
||||
sha1_P(D, E, A, B, C, sha1_R(62));
|
||||
sha1_P(C, D, E, A, B, sha1_R(63));
|
||||
sha1_P(B, C, D, E, A, sha1_R(64));
|
||||
sha1_P(A, B, C, D, E, sha1_R(65));
|
||||
sha1_P(E, A, B, C, D, sha1_R(66));
|
||||
sha1_P(D, E, A, B, C, sha1_R(67));
|
||||
sha1_P(C, D, E, A, B, sha1_R(68));
|
||||
sha1_P(B, C, D, E, A, sha1_R(69));
|
||||
sha1_P(A, B, C, D, E, sha1_R(70));
|
||||
sha1_P(E, A, B, C, D, sha1_R(71));
|
||||
sha1_P(D, E, A, B, C, sha1_R(72));
|
||||
sha1_P(C, D, E, A, B, sha1_R(73));
|
||||
sha1_P(B, C, D, E, A, sha1_R(74));
|
||||
sha1_P(A, B, C, D, E, sha1_R(75));
|
||||
sha1_P(E, A, B, C, D, sha1_R(76));
|
||||
sha1_P(D, E, A, B, C, sha1_R(77));
|
||||
sha1_P(C, D, E, A, B, sha1_R(78));
|
||||
sha1_P(B, C, D, E, A, sha1_R(79));
|
||||
|
||||
#undef sha1_K
|
||||
#undef sha1_F
|
||||
|
||||
state[0] += A;
|
||||
state[1] += B;
|
||||
state[2] += C;
|
||||
state[3] += D;
|
||||
state[4] += E;
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
void SHA1Builder::begin(void) {
|
||||
total[0] = 0;
|
||||
total[1] = 0;
|
||||
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xEFCDAB89;
|
||||
state[2] = 0x98BADCFE;
|
||||
state[3] = 0x10325476;
|
||||
state[4] = 0xC3D2E1F0;
|
||||
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
memset(hash, 0x00, sizeof(hash));
|
||||
}
|
||||
|
||||
void SHA1Builder::add(const uint8_t * data, size_t len) {
|
||||
size_t fill;
|
||||
uint32_t left;
|
||||
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
left = total[0] & 0x3F;
|
||||
fill = 64 - left;
|
||||
|
||||
total[0] += (uint32_t)len;
|
||||
total[0] &= 0xFFFFFFFF;
|
||||
|
||||
if (total[0] < (uint32_t)len) {
|
||||
total[1]++;
|
||||
}
|
||||
|
||||
if (left && len >= fill) {
|
||||
memcpy((void *)(buffer + left), data, fill);
|
||||
process(buffer);
|
||||
data += fill;
|
||||
len -= fill;
|
||||
left = 0;
|
||||
}
|
||||
|
||||
while (len >= 64) {
|
||||
process(data);
|
||||
data += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
memcpy((void *)(buffer + left), data, len);
|
||||
}
|
||||
}
|
||||
|
||||
void SHA1Builder::calculate(void) {
|
||||
uint32_t last, padn;
|
||||
uint32_t high, low;
|
||||
uint8_t msglen[8];
|
||||
|
||||
high = (total[0] >> 29) | (total[1] << 3);
|
||||
low = (total[0] << 3);
|
||||
|
||||
PUT_UINT32_BE(high, msglen, 0);
|
||||
PUT_UINT32_BE(low, msglen, 4);
|
||||
|
||||
last = total[0] & 0x3F;
|
||||
padn = (last < 56) ? (56 - last) : (120 - last);
|
||||
|
||||
add((uint8_t *)sha1_padding, padn);
|
||||
add(msglen, 8);
|
||||
|
||||
PUT_UINT32_BE(state[0], hash, 0);
|
||||
PUT_UINT32_BE(state[1], hash, 4);
|
||||
PUT_UINT32_BE(state[2], hash, 8);
|
||||
PUT_UINT32_BE(state[3], hash, 12);
|
||||
PUT_UINT32_BE(state[4], hash, 16);
|
||||
}
|
||||
|
||||
void SHA1Builder::getBytes(uint8_t * output) {
|
||||
memcpy(output, hash, SHA1_HASH_SIZE);
|
||||
}
|
||||
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SHA1Builder_h
|
||||
#define SHA1Builder_h
|
||||
|
||||
#include <Stream.h>
|
||||
#include <WString.h>
|
||||
|
||||
#define SHA1_HASH_SIZE 20
|
||||
|
||||
class SHA1Builder {
|
||||
private:
|
||||
uint32_t total[2]; /* number of bytes processed */
|
||||
uint32_t state[5]; /* intermediate digest state */
|
||||
unsigned char buffer[64]; /* data block being processed */
|
||||
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
|
||||
|
||||
void process(const uint8_t * data);
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void add(const uint8_t * data, size_t len);
|
||||
void calculate();
|
||||
void getBytes(uint8_t * output);
|
||||
};
|
||||
|
||||
#endif // SHA1Builder_h
|
||||
@@ -1,10 +0,0 @@
|
||||
name=OneWire
|
||||
version=2.3.3
|
||||
author=Jim Studt, Tom Pollard, Robin James, Glenn Trewitt, Jason Dangel, Guillermo Lovato, Paul Stoffregen, Scott Roberts, Bertrik Sikken, Mark Tillotson, Ken Butcher, Roger Clark, Love Nystrom
|
||||
maintainer=Paul Stoffregen
|
||||
sentence=Access 1-wire temperature sensors, memory and other chips.
|
||||
paragraph= Mod of Paul Stoffregen code to support ESP32
|
||||
category=Communication
|
||||
url=http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
architectures=esp8266,esp32
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For the current project.
|
||||
# This file was generated by doxygen2keywords.xsl.
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Classes and structs (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ModbusServerTCP::ClientData KEYWORD1
|
||||
CoilData KEYWORD1
|
||||
Modbus::FCT KEYWORD1
|
||||
ModbusBridge KEYWORD1
|
||||
ModbusClient KEYWORD1
|
||||
ModbusClientTCP KEYWORD1
|
||||
ModbusClientRTU KEYWORD1
|
||||
ModbusClientTCPasync KEYWORD1
|
||||
ModbusError KEYWORD1
|
||||
ModbusMessage KEYWORD1
|
||||
ModbusServer KEYWORD1
|
||||
ModbusServerTCP KEYWORD1
|
||||
ModbusServerRTU KEYWORD1
|
||||
ModbusServerTCPasync KEYWORD1
|
||||
RTUutils KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
ClientData KEYWORD2
|
||||
~ClientData KEYWORD2
|
||||
CoilData KEYWORD2
|
||||
~CoilData KEYWORD2
|
||||
coils KEYWORD2
|
||||
coilsSetON KEYWORD2
|
||||
coilsSetOFF KEYWORD2
|
||||
FCT KEYWORD2
|
||||
getType KEYWORD2
|
||||
redefineType KEYWORD2
|
||||
ModbusBridge KEYWORD2
|
||||
attachServer KEYWORD2
|
||||
addFunctionCode KEYWORD2
|
||||
denyFunctionCode KEYWORD2
|
||||
bridgeWorker KEYWORD2
|
||||
bridgeDenyWorker KEYWORD2
|
||||
onDataHandler KEYWORD2
|
||||
onErrorHandler KEYWORD2
|
||||
onResponseHandler KEYWORD2
|
||||
getMessageCount KEYWORD2
|
||||
getErrorCount KEYWORD2
|
||||
resetCounts KEYWORD2
|
||||
addRequest KEYWORD2
|
||||
syncRequest KEYWORD2
|
||||
buildErrorMsg KEYWORD2
|
||||
addRequest KEYWORD2
|
||||
ModbusClient KEYWORD2
|
||||
waitSync KEYWORD2
|
||||
ModbusClientTCPasync KEYWORD2
|
||||
setTimeout KEYWORD2
|
||||
setIdleTimeout KEYWORD2
|
||||
setMaxInflightRequests KEYWORD2
|
||||
addToQueue KEYWORD2
|
||||
ModbusError KEYWORD2
|
||||
getText KEYWORD2
|
||||
ModbusMessage KEYWORD2
|
||||
data KEYWORD2
|
||||
size KEYWORD2
|
||||
push_back KEYWORD2
|
||||
clear KEYWORD2
|
||||
resize KEYWORD2
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
append KEYWORD2
|
||||
getServerID KEYWORD2
|
||||
getFunctionCode KEYWORD2
|
||||
getError KEYWORD2
|
||||
setFunctionCode KEYWORD2
|
||||
add KEYWORD2
|
||||
get KEYWORD2
|
||||
setMessage KEYWORD2
|
||||
setError KEYWORD2
|
||||
determineFloatOrder KEYWORD2
|
||||
determineDoubleOrder KEYWORD2
|
||||
swapFloat KEYWORD2
|
||||
swapDouble KEYWORD2
|
||||
getOne KEYWORD2
|
||||
registerWorker KEYWORD2
|
||||
getWorker KEYWORD2
|
||||
unregisterWorker KEYWORD2
|
||||
isServerFor KEYWORD2
|
||||
getMessageCount KEYWORD2
|
||||
getErrorCount KEYWORD2
|
||||
resetCounts KEYWORD2
|
||||
localRequest KEYWORD2
|
||||
listServer KEYWORD2
|
||||
ModbusServer KEYWORD2
|
||||
ModbusServerTCP KEYWORD2
|
||||
activeClients KEYWORD2
|
||||
start KEYWORD2
|
||||
stop KEYWORD2
|
||||
clientAvailable KEYWORD2
|
||||
ModbusServerTCPasync KEYWORD2
|
||||
isRunning KEYWORD2
|
||||
calcCRC KEYWORD2
|
||||
validCRC KEYWORD2
|
||||
addCRC KEYWORD2
|
||||
calculateInterval KEYWORD2
|
||||
prepareHardwareSerial KEYWORD2
|
||||
RTUutils KEYWORD2
|
||||
ServerData KEYWORD2
|
||||
NIL_RESPONSE KEYWORD2
|
||||
ECHO_RESPONSE KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
DISCONNECTED LITERAL1
|
||||
CONNECTING LITERAL1
|
||||
CONNECTED LITERAL1
|
||||
ANY_FUNCTION_CODE LITERAL1
|
||||
READ_COIL LITERAL1
|
||||
READ_DISCR_INPUT LITERAL1
|
||||
READ_HOLD_REGISTER LITERAL1
|
||||
READ_INPUT_REGISTER LITERAL1
|
||||
WRITE_COIL LITERAL1
|
||||
WRITE_HOLD_REGISTER LITERAL1
|
||||
READ_EXCEPTION_SERIAL LITERAL1
|
||||
DIAGNOSTICS_SERIAL LITERAL1
|
||||
READ_COMM_CNT_SERIAL LITERAL1
|
||||
READ_COMM_LOG_SERIAL LITERAL1
|
||||
WRITE_MULT_COILS LITERAL1
|
||||
WRITE_MULT_REGISTERS LITERAL1
|
||||
REPORT_SERVER_ID_SERIAL LITERAL1
|
||||
READ_FILE_RECORD LITERAL1
|
||||
WRITE_FILE_RECORD LITERAL1
|
||||
MASK_WRITE_REGISTER LITERAL1
|
||||
R_W_MULT_REGISTERS LITERAL1
|
||||
READ_FIFO_QUEUE LITERAL1
|
||||
ENCAPSULATED_INTERFACE LITERAL1
|
||||
USER_DEFINED_41 LITERAL1
|
||||
USER_DEFINED_42 LITERAL1
|
||||
USER_DEFINED_43 LITERAL1
|
||||
USER_DEFINED_44 LITERAL1
|
||||
USER_DEFINED_45 LITERAL1
|
||||
USER_DEFINED_46 LITERAL1
|
||||
USER_DEFINED_47 LITERAL1
|
||||
USER_DEFINED_48 LITERAL1
|
||||
USER_DEFINED_64 LITERAL1
|
||||
USER_DEFINED_65 LITERAL1
|
||||
USER_DEFINED_66 LITERAL1
|
||||
USER_DEFINED_67 LITERAL1
|
||||
USER_DEFINED_68 LITERAL1
|
||||
USER_DEFINED_69 LITERAL1
|
||||
USER_DEFINED_6A LITERAL1
|
||||
USER_DEFINED_6B LITERAL1
|
||||
USER_DEFINED_6C LITERAL1
|
||||
USER_DEFINED_6D LITERAL1
|
||||
USER_DEFINED_6E LITERAL1
|
||||
SUCCESS LITERAL1
|
||||
ILLEGAL_FUNCTION LITERAL1
|
||||
ILLEGAL_DATA_ADDRESS LITERAL1
|
||||
ILLEGAL_DATA_VALUE LITERAL1
|
||||
SERVER_DEVICE_FAILURE LITERAL1
|
||||
ACKNOWLEDGE LITERAL1
|
||||
SERVER_DEVICE_BUSY LITERAL1
|
||||
NEGATIVE_ACKNOWLEDGE LITERAL1
|
||||
MEMORY_PARITY_ERROR LITERAL1
|
||||
GATEWAY_PATH_UNAVAIL LITERAL1
|
||||
GATEWAY_TARGET_NO_RESP LITERAL1
|
||||
TIMEOUT LITERAL1
|
||||
INVALID_SERVER LITERAL1
|
||||
CRC_ERROR LITERAL1
|
||||
FC_MISMATCH LITERAL1
|
||||
SERVER_ID_MISMATCH LITERAL1
|
||||
PACKET_LENGTH_ERROR LITERAL1
|
||||
PARAMETER_COUNT_ERROR LITERAL1
|
||||
PARAMETER_LIMIT_ERROR LITERAL1
|
||||
REQUEST_QUEUE_FULL LITERAL1
|
||||
ILLEGAL_IP_OR_PORT LITERAL1
|
||||
IP_CONNECTION_FAILED LITERAL1
|
||||
TCP_HEAD_MISMATCH LITERAL1
|
||||
EMPTY_MESSAGE LITERAL1
|
||||
ASCII_FRAME_ERR LITERAL1
|
||||
ASCII_CRC_ERR LITERAL1
|
||||
ASCII_INVALID_CHAR LITERAL1
|
||||
BROADCAST_ERROR LITERAL1
|
||||
UNDEFINED_ERROR LITERAL1
|
||||
FC01_TYPE LITERAL1
|
||||
FC07_TYPE LITERAL1
|
||||
FC0F_TYPE LITERAL1
|
||||
FC10_TYPE LITERAL1
|
||||
FC16_TYPE LITERAL1
|
||||
FC18_TYPE LITERAL1
|
||||
FCGENERIC LITERAL1
|
||||
FCUSER LITERAL1
|
||||
FCILLEGAL LITERAL1
|
||||
PrintOut LITERAL1
|
||||
LOG_LEVEL LITERAL1
|
||||
LOCAL_LOG_LEVEL LITERAL1
|
||||
LOG_LEVEL_NONE LITERAL1
|
||||
LOG_LEVEL_CRITICAL LITERAL1
|
||||
LOG_LEVEL_ERROR LITERAL1
|
||||
LOG_LEVEL_WARNING LITERAL1
|
||||
LOG_LEVEL_INFO LITERAL1
|
||||
LOG_LEVEL_DEBUG LITERAL1
|
||||
LOG_LEVEL_VERBOSE LITERAL1
|
||||
LL_RED LITERAL1
|
||||
LL_GREEN LITERAL1
|
||||
LL_YELLOW LITERAL1
|
||||
LL_BLUE LITERAL1
|
||||
LL_MAGENTA LITERAL1
|
||||
LL_CYAN LITERAL1
|
||||
LL_NORM LITERAL1
|
||||
LOG_HEADER LITERAL1
|
||||
LOG_LINE_C LITERAL1
|
||||
LOG_LINE_E LITERAL1
|
||||
LOG_LINE_T LITERAL1
|
||||
LOG_RAW_C LITERAL1
|
||||
LOG_RAW_E LITERAL1
|
||||
LOG_RAW_T LITERAL1
|
||||
HEX_DUMP_T LITERAL1
|
||||
LOG_N LITERAL1
|
||||
LOGRAW_N LITERAL1
|
||||
HEXDUMP_N LITERAL1
|
||||
LOG_C LITERAL1
|
||||
LOGRAW_C LITERAL1
|
||||
HEXDUMP_C LITERAL1
|
||||
LOG_E LITERAL1
|
||||
LOGRAW_E LITERAL1
|
||||
HEXDUMP_E LITERAL1
|
||||
LOG_W LITERAL1
|
||||
LOGRAW_W LITERAL1
|
||||
HEXDUMP_W LITERAL1
|
||||
LOG_I LITERAL1
|
||||
LOGRAW_I LITERAL1
|
||||
HEXDUMP_I LITERAL1
|
||||
LOG_D LITERAL1
|
||||
LOGRAW_D LITERAL1
|
||||
HEXDUMP_D LITERAL1
|
||||
LOG_V LITERAL1
|
||||
LOGRAW_V LITERAL1
|
||||
HEXDUMP_V LITERAL1
|
||||
LOCAL_LOG_LEVEL LITERAL1
|
||||
TCP_SERVER LITERAL1
|
||||
RTU_SERVER LITERAL1
|
||||
SERVER_END LITERAL1
|
||||
LOCAL_LOG_LEVEL LITERAL1
|
||||
DEFAULTTIMEOUT LITERAL1
|
||||
DEFAULTIDLETIME LITERAL1
|
||||
LOCAL_LOG_LEVEL LITERAL1
|
||||
SERVER_END LITERAL1
|
||||
SWAP_BYTES LITERAL1
|
||||
SWAP_REGISTERS LITERAL1
|
||||
SWAP_WORDS LITERAL1
|
||||
SWAP_NIBBLES LITERAL1
|
||||
LOCK_GUARD LITERAL1
|
||||
@@ -1,9 +0,0 @@
|
||||
name=eModbus
|
||||
version=1.7.2
|
||||
author=bertmelis,Miq1 <miq1@gmx.de>
|
||||
maintainer=Miq1 <miq1@gmx.de>
|
||||
sentence=eModbus provides Modbus RTU, ASCII and TCP functions for ESP32.
|
||||
paragraph=This library is non-blocking for the program using it. Modbus requests and responses will be returned to user-supplied callback functions. All Modbus function codes are supported implicitly, the codes specified by the Modbus specs are parameter-checked.
|
||||
category=Communication
|
||||
url=https://github.com/eModbus/eModbus
|
||||
architectures=esp32,FreeRTOS
|
||||
@@ -12,6 +12,7 @@ with additional changes to support EMS-ESP such as compiling with Tasmota and no
|
||||
```
|
||||
src/espMqttClient.cpp
|
||||
src/Transport/ClientSecureSync.h
|
||||
src/Config.h
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
#include "APSettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE)
|
||||
, _dnsServer(nullptr)
|
||||
, _lastManaged(0)
|
||||
, _reconfigureAp(false)
|
||||
, _connected(0) {
|
||||
addUpdateHandler([this] { reconfigureAP(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
}
|
||||
|
||||
void APSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
// disabled for delayed start, first try station mode
|
||||
// reconfigureAP();
|
||||
}
|
||||
|
||||
// wait 10 sec on STA disconnect before starting AP
|
||||
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
uint8_t was_connected = _connected;
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
_connected &= ~1;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
_connected &= ~2;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
_connected |= 1;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||
_connected |= 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// wait 10 sec before starting AP
|
||||
if (was_connected && !_connected) {
|
||||
_lastManaged = uuid::get_uptime();
|
||||
}
|
||||
}
|
||||
|
||||
void APSettingsService::reconfigureAP() {
|
||||
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
|
||||
_reconfigureAp = true;
|
||||
}
|
||||
|
||||
void APSettingsService::loop() {
|
||||
unsigned long currentMillis = uuid::get_uptime();
|
||||
unsigned long manageElapsed = static_cast<uint32_t>(currentMillis - _lastManaged);
|
||||
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
|
||||
_lastManaged = currentMillis;
|
||||
manageAP();
|
||||
}
|
||||
|
||||
handleDNS();
|
||||
}
|
||||
|
||||
void APSettingsService::manageAP() {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !_connected)) {
|
||||
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
|
||||
startAP();
|
||||
}
|
||||
} else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) && _connected && (_reconfigureAp || !WiFi.softAPgetStationNum())) {
|
||||
stopAP();
|
||||
}
|
||||
_reconfigureAp = false;
|
||||
}
|
||||
|
||||
void APSettingsService::startAP() {
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
WiFi.softAPenableIpV6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
||||
#else
|
||||
WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
||||
#endif
|
||||
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
|
||||
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
#endif
|
||||
if (!_dnsServer) {
|
||||
IPAddress apIp = WiFi.softAPIP();
|
||||
emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", apIp.toString().c_str());
|
||||
_dnsServer = new DNSServer;
|
||||
_dnsServer->start(DNS_PORT, "*", apIp);
|
||||
}
|
||||
}
|
||||
|
||||
void APSettingsService::stopAP() {
|
||||
if (_dnsServer) {
|
||||
emsesp::EMSESP::logger().info("Stopping Access Point");
|
||||
_dnsServer->stop();
|
||||
delete _dnsServer;
|
||||
_dnsServer = nullptr;
|
||||
}
|
||||
WiFi.softAPdisconnect(true);
|
||||
}
|
||||
|
||||
void APSettingsService::handleDNS() {
|
||||
if (_dnsServer) {
|
||||
_dnsServer->processNextRequest();
|
||||
}
|
||||
}
|
||||
|
||||
APNetworkStatus APSettingsService::getAPNetworkStatus() {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
|
||||
|
||||
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
||||
return APNetworkStatus::LINGERING;
|
||||
}
|
||||
|
||||
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
|
||||
}
|
||||
|
||||
|
||||
void APSettings::read(const APSettings & settings, JsonObject root) {
|
||||
root["provision_mode"] = settings.provisionMode;
|
||||
root["ssid"] = settings.ssid;
|
||||
root["password"] = settings.password;
|
||||
root["channel"] = settings.channel;
|
||||
root["ssid_hidden"] = settings.ssidHidden;
|
||||
root["max_clients"] = settings.maxClients;
|
||||
root["local_ip"] = settings.localIP.toString();
|
||||
root["gateway_ip"] = settings.gatewayIP.toString();
|
||||
root["subnet_mask"] = settings.subnetMask.toString();
|
||||
}
|
||||
|
||||
StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
|
||||
APSettings newSettings = {};
|
||||
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
|
||||
|
||||
switch (settings.provisionMode) {
|
||||
case AP_MODE_ALWAYS:
|
||||
case AP_MODE_DISCONNECTED:
|
||||
case AP_MODE_NEVER:
|
||||
break;
|
||||
default:
|
||||
newSettings.provisionMode = AP_MODE_ALWAYS;
|
||||
}
|
||||
|
||||
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
|
||||
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
|
||||
newSettings.channel = static_cast<uint8_t>(root["channel"] | FACTORY_AP_CHANNEL);
|
||||
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
|
||||
newSettings.maxClients = static_cast<uint8_t>(root["max_clients"] | FACTORY_AP_MAX_CLIENTS);
|
||||
|
||||
JsonUtils::readIP(root, "local_ip", newSettings.localIP, String(FACTORY_AP_LOCAL_IP));
|
||||
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, String(FACTORY_AP_GATEWAY_IP));
|
||||
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, String(FACTORY_AP_SUBNET_MASK));
|
||||
|
||||
if (newSettings == settings) {
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
}
|
||||
|
||||
settings = newSettings;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
#ifndef APSettingsConfig_h
|
||||
#define APSettingsConfig_h
|
||||
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#ifndef FACTORY_AP_PROVISION_MODE
|
||||
#define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_SSID
|
||||
#define FACTORY_AP_SSID "ems-esp"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_PASSWORD
|
||||
#define FACTORY_AP_PASSWORD "ems-esp-neo"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_LOCAL_IP
|
||||
#define FACTORY_AP_LOCAL_IP "192.168.4.1"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_GATEWAY_IP
|
||||
#define FACTORY_AP_GATEWAY_IP "192.168.4.1"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_SUBNET_MASK
|
||||
#define FACTORY_AP_SUBNET_MASK "255.255.255.0"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_CHANNEL
|
||||
#define FACTORY_AP_CHANNEL 1
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_SSID_HIDDEN
|
||||
#define FACTORY_AP_SSID_HIDDEN false
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_AP_MAX_CLIENTS
|
||||
#define FACTORY_AP_MAX_CLIENTS 4
|
||||
#endif
|
||||
|
||||
#define AP_SETTINGS_FILE "/config/apSettings.json"
|
||||
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
|
||||
|
||||
#define AP_MODE_ALWAYS 0
|
||||
#define AP_MODE_DISCONNECTED 1
|
||||
#define AP_MODE_NEVER 2
|
||||
|
||||
#define MANAGE_NETWORK_DELAY 10000
|
||||
#define DNS_PORT 53
|
||||
|
||||
enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING };
|
||||
|
||||
class APSettings {
|
||||
public:
|
||||
uint8_t provisionMode;
|
||||
String ssid;
|
||||
String password;
|
||||
uint8_t channel;
|
||||
bool ssidHidden;
|
||||
uint8_t maxClients;
|
||||
|
||||
IPAddress localIP;
|
||||
IPAddress gatewayIP;
|
||||
IPAddress subnetMask;
|
||||
|
||||
bool operator==(const APSettings & settings) const {
|
||||
return provisionMode == settings.provisionMode && ssid == settings.ssid && password == settings.password && channel == settings.channel
|
||||
&& ssidHidden == settings.ssidHidden && maxClients == settings.maxClients && localIP == settings.localIP && gatewayIP == settings.gatewayIP
|
||||
&& subnetMask == settings.subnetMask;
|
||||
}
|
||||
|
||||
static void read(const APSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, APSettings & settings);
|
||||
};
|
||||
|
||||
class APSettingsService : public StatefulService<APSettings> {
|
||||
public:
|
||||
APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void loop();
|
||||
APNetworkStatus getAPNetworkStatus();
|
||||
|
||||
private:
|
||||
HttpEndpoint<APSettings> _httpEndpoint;
|
||||
FSPersistence<APSettings> _fsPersistence;
|
||||
|
||||
// for the captive portal
|
||||
DNSServer * _dnsServer;
|
||||
|
||||
// for the management delay loop
|
||||
volatile unsigned long _lastManaged;
|
||||
volatile boolean _reconfigureAp;
|
||||
uint8_t _connected;
|
||||
|
||||
void reconfigureAP();
|
||||
void manageAP();
|
||||
void startAP();
|
||||
void stopAP();
|
||||
void handleDNS();
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "APStatus.h"
|
||||
|
||||
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
|
||||
: _apSettingsService(apSettingsService) {
|
||||
server->on(AP_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { apStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void APStatus::apStatus(AsyncWebServerRequest * request) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["status"] = _apSettingsService->getAPNetworkStatus();
|
||||
root["ip_address"] = WiFi.softAPIP().toString();
|
||||
root["mac_address"] = WiFi.softAPmacAddress();
|
||||
root["station_num"] = WiFi.softAPgetStationNum();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef APStatus_h
|
||||
#define APStatus_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
#include "APSettingsService.h"
|
||||
|
||||
#define AP_STATUS_SERVICE_PATH "/rest/apStatus"
|
||||
|
||||
class APStatus {
|
||||
public:
|
||||
APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService);
|
||||
|
||||
private:
|
||||
APSettingsService * _apSettingsService;
|
||||
void apStatus(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "ArduinoJsonJWT.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
ArduinoJsonJWT::ArduinoJsonJWT(String secret)
|
||||
: _secret(std::move(secret)) {
|
||||
}
|
||||
|
||||
void ArduinoJsonJWT::setSecret(String secret) {
|
||||
_secret = std::move(secret);
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::getSecret() {
|
||||
return _secret;
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::buildJWT(JsonObject payload) {
|
||||
// serialize, then encode payload
|
||||
String jwt;
|
||||
serializeJson(payload, jwt);
|
||||
jwt = encode(jwt.c_str(), static_cast<int>(jwt.length()));
|
||||
|
||||
// add the header to payload
|
||||
jwt = getJWTHeader() + '.' + jwt;
|
||||
|
||||
// add signature
|
||||
jwt += '.' + sign(jwt);
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
|
||||
// clear json document before we begin, jsonDocument wil be null on failure
|
||||
jsonDocument.clear();
|
||||
|
||||
const String & jwt_header = getJWTHeader();
|
||||
const unsigned int jwt_header_size = jwt_header.length();
|
||||
|
||||
// must have the correct header and delimiter
|
||||
if (!jwt.startsWith(jwt_header) || jwt.indexOf('.') != static_cast<int>(jwt_header_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check there is a signature delimieter
|
||||
const int signatureDelimiterIndex = jwt.lastIndexOf('.');
|
||||
if (signatureDelimiterIndex == static_cast<int>(jwt_header_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check the signature is valid
|
||||
const String signature = jwt.substring(static_cast<unsigned int>(signatureDelimiterIndex) + 1);
|
||||
jwt = jwt.substring(0, static_cast<unsigned int>(signatureDelimiterIndex));
|
||||
if (sign(jwt) != signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
// decode payload
|
||||
jwt = jwt.substring(jwt_header_size + 1);
|
||||
jwt = decode(jwt);
|
||||
|
||||
// parse payload, clearing json document after failure
|
||||
const DeserializationError error = deserializeJson(jsonDocument, jwt);
|
||||
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
|
||||
jsonDocument.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ESP32 uses mbedtls, with decent HMAC implementations supporting sha256, as well as others.
|
||||
* No need to pull in additional crypto libraries - lets use what we already have.
|
||||
*/
|
||||
String ArduinoJsonJWT::sign(String & payload) {
|
||||
std::array<unsigned char, 32> hmacResult{};
|
||||
{
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
|
||||
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
|
||||
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
|
||||
mbedtls_md_free(&ctx);
|
||||
}
|
||||
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
|
||||
// prepare encoder
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
|
||||
// prepare buffer of correct length
|
||||
const auto bufferLength = static_cast<std::size_t>(base64_encode_expected_len(inputLen)) + 1;
|
||||
auto * buffer = new char[bufferLength];
|
||||
|
||||
// encode to buffer
|
||||
int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
|
||||
len += base64_encode_blockend(&buffer[len], &_state);
|
||||
buffer[len] = '\0';
|
||||
|
||||
// convert to arduino string, freeing buffer
|
||||
auto result = String(buffer);
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
|
||||
// remove padding and convert to URL safe form
|
||||
while (result.length() > 0 && result.charAt(result.length() - 1) == '=') {
|
||||
result.remove(result.length() - 1);
|
||||
}
|
||||
result.replace('+', '-');
|
||||
result.replace('/', '_');
|
||||
|
||||
// return as string
|
||||
return result;
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::decode(String value) {
|
||||
// convert to standard base64
|
||||
value.replace('-', '+');
|
||||
value.replace('_', '/');
|
||||
|
||||
// prepare buffer of correct length
|
||||
const auto bufferLength = static_cast<std::size_t>(base64_decode_expected_len(value.length()) + 1);
|
||||
auto * buffer = new char[bufferLength];
|
||||
|
||||
// decode
|
||||
const int len = base64_decode_chars(value.c_str(), static_cast<int>(value.length()), &buffer[0]);
|
||||
buffer[len] = '\0';
|
||||
|
||||
// convert to arduino string, freeing buffer
|
||||
auto result = String(buffer);
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
|
||||
// return as string
|
||||
return result;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef ArduinoJsonJWT_H
|
||||
#define ArduinoJsonJWT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <libb64/cdecode.h>
|
||||
#include <libb64/cencode.h>
|
||||
#include <mbedtls/md.h>
|
||||
|
||||
class ArduinoJsonJWT {
|
||||
public:
|
||||
explicit ArduinoJsonJWT(String secret);
|
||||
|
||||
void setSecret(String secret);
|
||||
String getSecret();
|
||||
|
||||
String buildJWT(JsonObject payload);
|
||||
void parseJWT(String jwt, JsonDocument & jsonDocument);
|
||||
|
||||
private:
|
||||
String _secret;
|
||||
|
||||
String sign(String & value);
|
||||
|
||||
static String encode(const char * cstr, int len);
|
||||
static String decode(String value);
|
||||
|
||||
static const String & getJWTHeader() {
|
||||
static const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
return JWT_HEADER;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "AuthenticationService.h"
|
||||
|
||||
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _securityManager(securityManager) {
|
||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { verifyAuthorization(request); });
|
||||
server->on(SIGN_IN_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { signIn(request, json); });
|
||||
}
|
||||
|
||||
// Verifies that the request supplied a valid JWT.
|
||||
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest * request) {
|
||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||
request->send(authentication.authenticated ? 200 : 401);
|
||||
}
|
||||
|
||||
// Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests.
|
||||
void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
String username = json["username"];
|
||||
String password = json["password"];
|
||||
Authentication authentication = _securityManager->authenticate(username, password);
|
||||
if (authentication.authenticated) {
|
||||
User * user = authentication.user;
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(401);
|
||||
request->send(response);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef AuthenticationService_H_
|
||||
#define AuthenticationService_H_
|
||||
|
||||
#include "Features.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
|
||||
#define SIGN_IN_PATH "/rest/signIn"
|
||||
|
||||
class AuthenticationService {
|
||||
public:
|
||||
AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
SecurityManager * _securityManager;
|
||||
|
||||
void signIn(AsyncWebServerRequest * request, JsonVariant json);
|
||||
void verifyAuthorization(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,86 +0,0 @@
|
||||
#include "ESP8266React.h"
|
||||
|
||||
#include "WWWData.h" // include auto-generated static web resources
|
||||
|
||||
ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
: _securitySettingsService(server, fs)
|
||||
, _networkSettingsService(server, fs, &_securitySettingsService)
|
||||
, _wifiScanner(server, &_securitySettingsService)
|
||||
, _networkStatus(server, &_securitySettingsService)
|
||||
, _apSettingsService(server, fs, &_securitySettingsService)
|
||||
, _apStatus(server, &_securitySettingsService, &_apSettingsService)
|
||||
, _ntpSettingsService(server, fs, &_securitySettingsService)
|
||||
, _ntpStatus(server, &_securitySettingsService)
|
||||
, _uploadFileService(server, &_securitySettingsService)
|
||||
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
||||
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
||||
, _authenticationService(server, &_securitySettingsService) {
|
||||
//
|
||||
// Serve static web resources
|
||||
//
|
||||
|
||||
// Populate the last modification date based on build datetime
|
||||
static char last_modified[50];
|
||||
sprintf(last_modified, "%s %s CET", __DATE__, __TIME__);
|
||||
|
||||
WWWData::registerRoutes([server](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
|
||||
// Check if the client already has the same version and respond with a 304 (Not modified)
|
||||
if (request->header("If-Modified-Since").indexOf(last_modified) > 0) {
|
||||
return request->send(304);
|
||||
} else if (request->header("If-None-Match").equals(hash)) {
|
||||
return request->send(304);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len);
|
||||
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS
|
||||
// response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
|
||||
response->addHeader("Cache-Control", "must-revalidate"); // ensure that a client will check the server for a change
|
||||
response->addHeader("Last-Modified", last_modified);
|
||||
response->addHeader("ETag", hash);
|
||||
|
||||
request->send(response);
|
||||
};
|
||||
|
||||
server->on(uri, HTTP_GET, requestHandler);
|
||||
|
||||
// Serving non matching get requests with "/index.html"
|
||||
// OPTIONS get a straight up 200 response
|
||||
if (strncmp(uri, "/index.html", 11) == 0) {
|
||||
server->onNotFound([requestHandler](AsyncWebServerRequest * request) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
requestHandler(request);
|
||||
} else if (request->method() == HTTP_OPTIONS) {
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ESP8266React::begin() {
|
||||
_networkSettingsService.begin();
|
||||
_networkSettingsService.read([&](NetworkSettings & networkSettings) {
|
||||
DefaultHeaders & defaultHeaders = DefaultHeaders::Instance();
|
||||
if (networkSettings.enableCORS) {
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
defaultHeaders.addHeader("Server", networkSettings.hostname);
|
||||
});
|
||||
_apSettingsService.begin();
|
||||
_ntpSettingsService.begin();
|
||||
_mqttSettingsService.begin();
|
||||
_securitySettingsService.begin();
|
||||
}
|
||||
|
||||
void ESP8266React::loop() {
|
||||
_networkSettingsService.loop();
|
||||
_apSettingsService.loop();
|
||||
_mqttSettingsService.loop();
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#ifndef ESP8266React_h
|
||||
#define ESP8266React_h
|
||||
|
||||
#include "APSettingsService.h"
|
||||
#include "APStatus.h"
|
||||
#include "AuthenticationService.h"
|
||||
#include "MqttSettingsService.h"
|
||||
#include "MqttStatus.h"
|
||||
#include "NTPSettingsService.h"
|
||||
#include "NTPStatus.h"
|
||||
#include "UploadFileService.h"
|
||||
#include "SecuritySettingsService.h"
|
||||
#include "WiFiScanner.h"
|
||||
#include "NetworkSettingsService.h"
|
||||
#include "NetworkStatus.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
class ESP8266React {
|
||||
public:
|
||||
ESP8266React(AsyncWebServer * server, FS * fs);
|
||||
|
||||
void begin();
|
||||
void loop();
|
||||
|
||||
SecurityManager * getSecurityManager() {
|
||||
return &_securitySettingsService;
|
||||
}
|
||||
|
||||
StatefulService<SecuritySettings> * getSecuritySettingsService() {
|
||||
return &_securitySettingsService;
|
||||
}
|
||||
|
||||
StatefulService<NetworkSettings> * getNetworkSettingsService() {
|
||||
return &_networkSettingsService;
|
||||
}
|
||||
|
||||
StatefulService<APSettings> * getAPSettingsService() {
|
||||
return &_apSettingsService;
|
||||
}
|
||||
|
||||
StatefulService<NTPSettings> * getNTPSettingsService() {
|
||||
return &_ntpSettingsService;
|
||||
}
|
||||
|
||||
StatefulService<MqttSettings> * getMqttSettingsService() {
|
||||
return &_mqttSettingsService;
|
||||
}
|
||||
|
||||
MqttClient * getMqttClient() {
|
||||
return _mqttSettingsService.getMqttClient();
|
||||
}
|
||||
|
||||
//
|
||||
// special functions needed outside scope
|
||||
//
|
||||
|
||||
// true if AP is active
|
||||
bool apStatus() {
|
||||
return _apSettingsService.getAPNetworkStatus() == APNetworkStatus::ACTIVE;
|
||||
}
|
||||
|
||||
uint16_t getWifiReconnects() {
|
||||
return _networkSettingsService.getWifiReconnects();
|
||||
}
|
||||
|
||||
private:
|
||||
SecuritySettingsService _securitySettingsService;
|
||||
NetworkSettingsService _networkSettingsService;
|
||||
WiFiScanner _wifiScanner;
|
||||
NetworkStatus _networkStatus;
|
||||
APSettingsService _apSettingsService;
|
||||
APStatus _apStatus;
|
||||
NTPSettingsService _ntpSettingsService;
|
||||
NTPStatus _ntpStatus;
|
||||
UploadFileService _uploadFileService;
|
||||
MqttSettingsService _mqttSettingsService;
|
||||
MqttStatus _mqttStatus;
|
||||
AuthenticationService _authenticationService;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#ifndef FSPersistence_h
|
||||
#define FSPersistence_h
|
||||
|
||||
#include "StatefulService.h"
|
||||
#include "FS.h"
|
||||
|
||||
template <class T>
|
||||
class FSPersistence {
|
||||
public:
|
||||
FSPersistence(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> * statefulService, FS * fs, const char * filePath)
|
||||
: _stateReader(stateReader)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _statefulService(statefulService)
|
||||
, _fs(fs)
|
||||
, _filePath(filePath)
|
||||
, _updateHandlerId(0) {
|
||||
enableUpdateHandler();
|
||||
}
|
||||
|
||||
void readFromFS() {
|
||||
File settingsFile = _fs->open(_filePath, "r");
|
||||
|
||||
if (settingsFile) {
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
||||
JsonObject jsonObject = jsonDocument.as<JsonObject>();
|
||||
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// Serial.println();
|
||||
// Serial.printf("Reading settings from %s ", _filePath);
|
||||
// serializeJson(jsonDocument, Serial);
|
||||
// Serial.println();
|
||||
#endif
|
||||
settingsFile.close();
|
||||
return;
|
||||
}
|
||||
settingsFile.close();
|
||||
}
|
||||
|
||||
// If we reach here we have not been successful in loading the config,
|
||||
// hard-coded emergency defaults are now applied.
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// Serial.println();
|
||||
// Serial.printf("Applying defaults to %s", _filePath);
|
||||
// Serial.println();
|
||||
#endif
|
||||
applyDefaults();
|
||||
writeToFS(); // added to make sure the initial file is created
|
||||
}
|
||||
|
||||
bool writeToFS() {
|
||||
// create and populate a new json object
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject jsonObject = jsonDocument.to<JsonObject>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
|
||||
// make directories if required, for new IDF4.2 & LittleFS
|
||||
String path(_filePath);
|
||||
int index = 0;
|
||||
while ((index = path.indexOf('/', static_cast<unsigned int>(index) + 1)) != -1) {
|
||||
String segment = path.substring(0, static_cast<unsigned int>(index));
|
||||
if (!_fs->exists(segment)) {
|
||||
_fs->mkdir(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// serialize it to filesystem
|
||||
File settingsFile = _fs->open(_filePath, "w");
|
||||
|
||||
// failed to open file, return false
|
||||
if (!settingsFile || !jsonObject.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// serialize the data to the file
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// Serial.println();
|
||||
// Serial.printf("Writing settings to %s ", _filePath);
|
||||
// serializeJson(jsonDocument, Serial);
|
||||
// Serial.println();
|
||||
#endif
|
||||
serializeJson(jsonDocument, settingsFile);
|
||||
settingsFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void disableUpdateHandler() {
|
||||
if (_updateHandlerId) {
|
||||
_statefulService->removeUpdateHandler(_updateHandlerId);
|
||||
_updateHandlerId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void enableUpdateHandler() {
|
||||
if (!_updateHandlerId) {
|
||||
_updateHandlerId = _statefulService->addUpdateHandler([&] { writeToFS(); });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JsonStateReader<T> _stateReader;
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T> * _statefulService;
|
||||
FS * _fs;
|
||||
const char * _filePath;
|
||||
update_handler_id_t _updateHandlerId;
|
||||
|
||||
protected:
|
||||
// We assume the updater supplies sensible defaults if an empty object
|
||||
// is supplied, this virtual function allows that to be changed.
|
||||
virtual void applyDefaults() {
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject jsonObject = jsonDocument.as<JsonObject>();
|
||||
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef Features_h
|
||||
#define Features_h
|
||||
|
||||
// project feature on by default
|
||||
#ifndef FT_PROJECT
|
||||
#define FT_PROJECT 1
|
||||
#endif
|
||||
|
||||
// security feature on by default
|
||||
#ifndef FT_SECURITY
|
||||
#define FT_SECURITY 1
|
||||
#endif
|
||||
|
||||
// mqtt feature on by default
|
||||
#ifndef FT_MQTT
|
||||
#define FT_MQTT 1
|
||||
#endif
|
||||
|
||||
// ntp feature on by default
|
||||
#ifndef FT_NTP
|
||||
#define FT_NTP 1
|
||||
#endif
|
||||
|
||||
// upload firmware/file feature on by default
|
||||
#ifndef FT_UPLOAD_FIRMWARE
|
||||
#define FT_UPLOAD_FIRMWARE 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,70 +0,0 @@
|
||||
#ifndef HttpEndpoint_h
|
||||
#define HttpEndpoint_h
|
||||
|
||||
#include <functional>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
#include "StatefulService.h"
|
||||
|
||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
|
||||
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
|
||||
|
||||
template <class T>
|
||||
class HttpEndpoint {
|
||||
protected:
|
||||
JsonStateReader<T> _stateReader;
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T> * _statefulService;
|
||||
|
||||
public:
|
||||
HttpEndpoint(JsonStateReader<T> stateReader,
|
||||
JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> * statefulService,
|
||||
AsyncWebServer * server,
|
||||
const String & servicePath,
|
||||
SecurityManager * securityManager,
|
||||
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN)
|
||||
: _stateReader(stateReader)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _statefulService(statefulService) {
|
||||
// Create handler for both GET and POST endpoints
|
||||
server->on(servicePath.c_str(),
|
||||
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { handleRequest(request, json); },
|
||||
authenticationPredicate));
|
||||
}
|
||||
|
||||
protected:
|
||||
// for POST
|
||||
void handleRequest(AsyncWebServerRequest * request, JsonVariant json) {
|
||||
if (request->method() == HTTP_POST) {
|
||||
// Handle POST
|
||||
if (!json.is<JsonObject>()) {
|
||||
request->send(400); // error, bad request
|
||||
return;
|
||||
}
|
||||
|
||||
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(json.as<JsonObject>(), _stateUpdater);
|
||||
|
||||
if (outcome == StateUpdateResult::ERROR) {
|
||||
request->send(400); // error, bad request
|
||||
return;
|
||||
} else if (outcome == StateUpdateResult::CHANGED || outcome == StateUpdateResult::CHANGED_RESTART) {
|
||||
// persist changes
|
||||
request->onDisconnect([this] { _statefulService->callUpdateHandlers(); });
|
||||
if (outcome == StateUpdateResult::CHANGED_RESTART) {
|
||||
request->send(205); // reboot required
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot().to<JsonObject>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef IPUtils_h
|
||||
#define IPUtils_h
|
||||
|
||||
#include <IPAddress.h>
|
||||
|
||||
class IPUtils {
|
||||
public:
|
||||
static bool isSet(const IPAddress & ip) {
|
||||
return ip != getNotSetIP();
|
||||
}
|
||||
static bool isNotSet(const IPAddress & ip) {
|
||||
return ip == getNotSetIP();
|
||||
}
|
||||
|
||||
private:
|
||||
static const IPAddress & getNotSetIP() {
|
||||
static const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
|
||||
return IP_NOT_SET;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef JsonUtils_h
|
||||
#define JsonUtils_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include "IPUtils.h"
|
||||
|
||||
class JsonUtils {
|
||||
public:
|
||||
static void readIP(JsonObject root, const String & key, IPAddress & ip, const String & def) {
|
||||
IPAddress defaultIp = {};
|
||||
if (!defaultIp.fromString(def)) {
|
||||
defaultIp = INADDR_NONE;
|
||||
}
|
||||
readIP(root, key, ip, defaultIp);
|
||||
}
|
||||
static void readIP(JsonObject root, const String & key, IPAddress & ip, const IPAddress & defaultIp = INADDR_NONE) {
|
||||
if (!root[key].is<String>() || !ip.fromString(root[key].as<String>())) {
|
||||
ip = defaultIp;
|
||||
}
|
||||
}
|
||||
static void writeIP(JsonObject root, const String & key, const IPAddress & ip) {
|
||||
if (IPUtils::isSet(ip)) {
|
||||
root[key] = ip.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,403 +0,0 @@
|
||||
#include "MqttSettingsService.h"
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE)
|
||||
, _reconfigureMqtt(false)
|
||||
, _disconnectedAt(0)
|
||||
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
|
||||
, _mqttClient(nullptr) {
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
addUpdateHandler([this] { onConfigUpdated(); }, false);
|
||||
}
|
||||
|
||||
static String generateClientId() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return "ems-esp";
|
||||
#else
|
||||
return "esp32-" + String(static_cast<uint32_t>(ESP.getEfuseMac()), HEX);
|
||||
#endif
|
||||
}
|
||||
|
||||
MqttSettingsService::~MqttSettingsService() {
|
||||
delete _mqttClient;
|
||||
}
|
||||
|
||||
void MqttSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
startClient();
|
||||
}
|
||||
|
||||
void MqttSettingsService::startClient() {
|
||||
static bool isSecure = false;
|
||||
if (_mqttClient != nullptr) {
|
||||
// do we need to change the client?
|
||||
if ((isSecure && _state.enableTLS) || (!isSecure && _state.enableTLS)) {
|
||||
return;
|
||||
}
|
||||
delete _mqttClient;
|
||||
_mqttClient = nullptr;
|
||||
}
|
||||
#ifndef TASMOTA_SDK
|
||||
if (_state.enableTLS) {
|
||||
isSecure = true;
|
||||
if (emsesp::EMSESP::system_.PSram() > 0) {
|
||||
_mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::YES);
|
||||
} else {
|
||||
_mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
|
||||
}
|
||||
if (_state.rootCA == "insecure") {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
|
||||
} else {
|
||||
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
|
||||
}
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)
|
||||
->onMessage(
|
||||
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
|
||||
onMqttMessage(properties, topic, payload, len, index, total);
|
||||
});
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
isSecure = false;
|
||||
if (emsesp::EMSESP::system_.PSram() > 0) {
|
||||
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::YES);
|
||||
} else {
|
||||
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
|
||||
}
|
||||
static_cast<espMqttClient *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
|
||||
static_cast<espMqttClient *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
|
||||
static_cast<espMqttClient *>(_mqttClient)
|
||||
->onMessage(
|
||||
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
|
||||
onMqttMessage(properties, topic, payload, len, index, total);
|
||||
});
|
||||
}
|
||||
|
||||
void MqttSettingsService::loop() {
|
||||
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
||||
// reconfigure MQTT client
|
||||
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
|
||||
_reconfigureMqtt = false;
|
||||
}
|
||||
if (emsesp::EMSESP::system_.PSram() == 0) {
|
||||
_mqttClient->loop();
|
||||
}
|
||||
}
|
||||
|
||||
bool MqttSettingsService::isEnabled() {
|
||||
return _state.enabled;
|
||||
}
|
||||
|
||||
bool MqttSettingsService::isConnected() {
|
||||
return _mqttClient->connected();
|
||||
}
|
||||
|
||||
const char * MqttSettingsService::getClientId() {
|
||||
return _mqttClient->getClientId();
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttMessage(const espMqttClientTypes::MessageProperties & properties,
|
||||
const char * topic,
|
||||
const uint8_t * payload,
|
||||
size_t len,
|
||||
size_t index,
|
||||
size_t total) {
|
||||
(void)properties;
|
||||
(void)index;
|
||||
(void)total;
|
||||
emsesp::EMSESP::mqtt_.on_message(topic, payload, len);
|
||||
}
|
||||
|
||||
espMqttClientTypes::DisconnectReason MqttSettingsService::getDisconnectReason() {
|
||||
return _disconnectReason;
|
||||
}
|
||||
|
||||
MqttClient * MqttSettingsService::getMqttClient() {
|
||||
return _mqttClient;
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
|
||||
(void)sessionPresent;
|
||||
emsesp::EMSESP::mqtt_.on_connect();
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
_disconnectReason = reason;
|
||||
if (!_disconnectedAt) {
|
||||
_disconnectedAt = uuid::get_uptime();
|
||||
}
|
||||
emsesp::EMSESP::mqtt_.on_disconnect(reason);
|
||||
}
|
||||
|
||||
void MqttSettingsService::onConfigUpdated() {
|
||||
_reconfigureMqtt = true;
|
||||
_disconnectedAt = 0;
|
||||
|
||||
startClient();
|
||||
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
|
||||
}
|
||||
|
||||
void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
if (_state.enabled && !_mqttClient->connected()) {
|
||||
onConfigUpdated();
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
if (_state.enabled) {
|
||||
_mqttClient->disconnect(true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MqttSettingsService::configureMqtt() {
|
||||
// disconnect if already connected
|
||||
if (_mqttClient->connected()) {
|
||||
emsesp::EMSESP::logger().info("Disconnecting to configure");
|
||||
_mqttClient->disconnect(true);
|
||||
}
|
||||
|
||||
// only connect if WiFi is connected and MQTT is enabled
|
||||
if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) {
|
||||
// create last will topic with the base prefixed. It has to be static because the client destroys the reference
|
||||
static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH];
|
||||
if (_state.base.isEmpty()) {
|
||||
snprintf(will_topic, sizeof(will_topic), "status");
|
||||
} else {
|
||||
snprintf(will_topic, sizeof(will_topic), "%s/status", _state.base.c_str());
|
||||
}
|
||||
|
||||
_reconfigureMqtt = false;
|
||||
#ifndef TASMOTA_SDK
|
||||
if (_state.enableTLS) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
|
||||
#endif
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)
|
||||
->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
|
||||
}
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(_state.clientId.c_str());
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
|
||||
return _mqttClient->connect();
|
||||
}
|
||||
#endif
|
||||
static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
|
||||
}
|
||||
static_cast<espMqttClient *>(_mqttClient)->setClientId(_state.clientId.c_str());
|
||||
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
|
||||
return _mqttClient->connect();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
|
||||
#ifndef TASMOTA_SDK
|
||||
root["enableTLS"] = settings.enableTLS;
|
||||
root["rootCA"] = settings.rootCA;
|
||||
#endif
|
||||
root["enabled"] = settings.enabled;
|
||||
root["host"] = settings.host;
|
||||
root["port"] = settings.port;
|
||||
root["base"] = settings.base;
|
||||
root["username"] = settings.username;
|
||||
root["password"] = settings.password;
|
||||
root["client_id"] = settings.clientId;
|
||||
root["keep_alive"] = settings.keepAlive;
|
||||
root["clean_session"] = settings.cleanSession;
|
||||
root["entity_format"] = settings.entity_format;
|
||||
|
||||
root["publish_time_boiler"] = settings.publish_time_boiler;
|
||||
root["publish_time_thermostat"] = settings.publish_time_thermostat;
|
||||
root["publish_time_solar"] = settings.publish_time_solar;
|
||||
root["publish_time_mixer"] = settings.publish_time_mixer;
|
||||
root["publish_time_water"] = settings.publish_time_water;
|
||||
root["publish_time_other"] = settings.publish_time_other;
|
||||
root["publish_time_sensor"] = settings.publish_time_sensor;
|
||||
root["publish_time_heartbeat"] = settings.publish_time_heartbeat;
|
||||
root["mqtt_qos"] = settings.mqtt_qos;
|
||||
root["mqtt_retain"] = settings.mqtt_retain;
|
||||
root["ha_enabled"] = settings.ha_enabled;
|
||||
root["nested_format"] = settings.nested_format;
|
||||
root["discovery_prefix"] = settings.discovery_prefix;
|
||||
root["discovery_type"] = settings.discovery_type;
|
||||
root["publish_single"] = settings.publish_single;
|
||||
root["publish_single2cmd"] = settings.publish_single2cmd;
|
||||
root["send_response"] = settings.send_response;
|
||||
}
|
||||
|
||||
StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) {
|
||||
MqttSettings newSettings = {};
|
||||
bool changed = false;
|
||||
|
||||
#ifndef TASMOTA_SDK
|
||||
newSettings.enableTLS = root["enableTLS"];
|
||||
newSettings.rootCA = root["rootCA"] | "";
|
||||
#else
|
||||
newSettings.enableTLS = false;
|
||||
#endif
|
||||
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
|
||||
newSettings.host = root["host"] | FACTORY_MQTT_HOST;
|
||||
newSettings.port = static_cast<uint16_t>(root["port"] | FACTORY_MQTT_PORT);
|
||||
newSettings.base = root["base"] | FACTORY_MQTT_BASE;
|
||||
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
|
||||
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
|
||||
newSettings.clientId = root["client_id"] | generateClientId();
|
||||
newSettings.keepAlive = static_cast<uint16_t>(root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE);
|
||||
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
|
||||
newSettings.mqtt_qos = static_cast<uint8_t>(root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS);
|
||||
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
|
||||
|
||||
newSettings.publish_time_boiler = static_cast<uint16_t>(root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_thermostat = static_cast<uint16_t>(root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_solar = static_cast<uint16_t>(root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_mixer = static_cast<uint16_t>(root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_water = static_cast<uint16_t>(root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME_OTHER);
|
||||
newSettings.publish_time_sensor = static_cast<uint16_t>(root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_heartbeat = static_cast<uint16_t>(root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT);
|
||||
|
||||
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
|
||||
newSettings.nested_format = static_cast<uint8_t>(root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT);
|
||||
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
|
||||
newSettings.discovery_type = static_cast<uint8_t>(root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE);
|
||||
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
|
||||
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
|
||||
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
|
||||
newSettings.entity_format = static_cast<uint8_t>(root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT);
|
||||
|
||||
if (newSettings.enabled != settings.enabled) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.mqtt_qos != settings.mqtt_qos) {
|
||||
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.nested_format != settings.nested_format) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.discovery_prefix != settings.discovery_prefix) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.discovery_type != settings.discovery_type) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.entity_format != settings.entity_format) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// if both settings are stored from older version, HA has priority
|
||||
if (newSettings.ha_enabled && newSettings.publish_single) {
|
||||
newSettings.publish_single = false;
|
||||
}
|
||||
|
||||
if (newSettings.publish_single != settings.publish_single) {
|
||||
if (newSettings.publish_single) {
|
||||
newSettings.ha_enabled = false;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.publish_single2cmd != settings.publish_single2cmd) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.send_response != settings.send_response) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.ha_enabled != settings.ha_enabled) {
|
||||
emsesp::EMSESP::mqtt_.ha_enabled(newSettings.ha_enabled);
|
||||
if (newSettings.ha_enabled) {
|
||||
newSettings.publish_single = false;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.mqtt_retain != settings.mqtt_retain) {
|
||||
emsesp::EMSESP::mqtt_.set_retain(newSettings.mqtt_retain);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_boiler != settings.publish_time_boiler) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_boiler(newSettings.publish_time_boiler);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_thermostat != settings.publish_time_thermostat) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_thermostat(newSettings.publish_time_thermostat);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_solar != settings.publish_time_solar) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_solar(newSettings.publish_time_solar);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_mixer != settings.publish_time_mixer) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_mixer(newSettings.publish_time_mixer);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_water != settings.publish_time_water) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_water(newSettings.publish_time_water);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_other != settings.publish_time_other) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_other(newSettings.publish_time_other);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_sensor != settings.publish_time_sensor) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_sensor(newSettings.publish_time_sensor);
|
||||
}
|
||||
|
||||
if (newSettings.publish_time_heartbeat != settings.publish_time_heartbeat) {
|
||||
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
|
||||
}
|
||||
|
||||
#ifndef TASMOTA_SDK
|
||||
// strip down to certificate only
|
||||
newSettings.rootCA.replace("\r", "");
|
||||
newSettings.rootCA.replace("\n", "");
|
||||
newSettings.rootCA.replace("-----BEGIN CERTIFICATE-----", "");
|
||||
newSettings.rootCA.replace("-----END CERTIFICATE-----", "");
|
||||
newSettings.rootCA.replace(" ", "");
|
||||
if (newSettings.rootCA.length() == 0 && newSettings.enableTLS) {
|
||||
newSettings.rootCA = "insecure";
|
||||
}
|
||||
if (newSettings.enableTLS != settings.enableTLS || newSettings.rootCA != settings.rootCA) {
|
||||
changed = true;
|
||||
}
|
||||
#endif
|
||||
// save the new settings
|
||||
settings = newSettings;
|
||||
|
||||
if (changed) {
|
||||
emsesp::EMSESP::mqtt_.reset_mqtt();
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
#ifndef MqttSettingsService_h
|
||||
#define MqttSettingsService_h
|
||||
|
||||
#include "StatefulService.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#include <espMqttClient.h>
|
||||
|
||||
#include <uuid/common.h>
|
||||
|
||||
#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds
|
||||
|
||||
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
|
||||
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
|
||||
|
||||
#ifndef FACTORY_MQTT_ENABLED
|
||||
#define FACTORY_MQTT_ENABLED false
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_HOST
|
||||
#define FACTORY_MQTT_HOST "" // is blank
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_PORT
|
||||
#define FACTORY_MQTT_PORT 1883
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_BASE
|
||||
#define FACTORY_MQTT_BASE "ems-esp"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_USERNAME
|
||||
#define FACTORY_MQTT_USERNAME ""
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_PASSWORD
|
||||
#define FACTORY_MQTT_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_KEEP_ALIVE
|
||||
#define FACTORY_MQTT_KEEP_ALIVE 16
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_CLEAN_SESSION
|
||||
#define FACTORY_MQTT_CLEAN_SESSION false
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH
|
||||
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
|
||||
#endif
|
||||
|
||||
class MqttSettings {
|
||||
public:
|
||||
bool enabled;
|
||||
String host;
|
||||
uint16_t port;
|
||||
String rootCA;
|
||||
bool enableTLS;
|
||||
String username;
|
||||
String password;
|
||||
String clientId;
|
||||
uint16_t keepAlive;
|
||||
bool cleanSession;
|
||||
|
||||
// EMS-ESP specific
|
||||
String base;
|
||||
uint16_t publish_time_boiler;
|
||||
uint16_t publish_time_thermostat;
|
||||
uint16_t publish_time_solar;
|
||||
uint16_t publish_time_mixer;
|
||||
uint16_t publish_time_water;
|
||||
uint16_t publish_time_other;
|
||||
uint16_t publish_time_sensor;
|
||||
uint16_t publish_time_heartbeat;
|
||||
uint8_t mqtt_qos;
|
||||
bool mqtt_retain;
|
||||
bool ha_enabled;
|
||||
uint8_t nested_format;
|
||||
String discovery_prefix;
|
||||
uint8_t discovery_type;
|
||||
bool publish_single;
|
||||
bool publish_single2cmd;
|
||||
bool send_response;
|
||||
uint8_t entity_format;
|
||||
|
||||
static void read(MqttSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, MqttSettings & settings);
|
||||
};
|
||||
|
||||
class MqttSettingsService : public StatefulService<MqttSettings> {
|
||||
public:
|
||||
MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
~MqttSettingsService();
|
||||
|
||||
void begin();
|
||||
void startClient();
|
||||
void loop();
|
||||
bool isEnabled();
|
||||
bool isConnected();
|
||||
const char * getClientId();
|
||||
espMqttClientTypes::DisconnectReason getDisconnectReason();
|
||||
MqttClient * getMqttClient();
|
||||
|
||||
protected:
|
||||
void onConfigUpdated();
|
||||
|
||||
private:
|
||||
HttpEndpoint<MqttSettings> _httpEndpoint;
|
||||
FSPersistence<MqttSettings> _fsPersistence;
|
||||
|
||||
// variable to help manage connection
|
||||
bool _reconfigureMqtt;
|
||||
unsigned long _disconnectedAt;
|
||||
|
||||
// connection status
|
||||
espMqttClientTypes::DisconnectReason _disconnectReason;
|
||||
|
||||
// the MQTT client instance
|
||||
MqttClient * _mqttClient;
|
||||
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
void onMqttConnect(bool sessionPresent);
|
||||
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
|
||||
void
|
||||
onMqttMessage(const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total);
|
||||
|
||||
bool configureMqtt();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "MqttStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
|
||||
: _mqttSettingsService(mqttSettingsService) {
|
||||
server->on(MQTT_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { mqttStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["enabled"] = _mqttSettingsService->isEnabled();
|
||||
root["connected"] = _mqttSettingsService->isConnected();
|
||||
root["client_id"] = _mqttSettingsService->getClientId();
|
||||
root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason();
|
||||
|
||||
root["mqtt_queued"] = emsesp::Mqtt::publish_queued();
|
||||
root["mqtt_fails"] = emsesp::Mqtt::publish_fails();
|
||||
root["connect_count"] = emsesp::Mqtt::connect_count();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef MqttStatus_h
|
||||
#define MqttStatus_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "MqttSettingsService.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"
|
||||
|
||||
class MqttStatus {
|
||||
public:
|
||||
MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
MqttSettingsService * _mqttSettingsService;
|
||||
|
||||
void mqttStatus(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,100 +0,0 @@
|
||||
#include "NTPSettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
|
||||
, _connected(false) {
|
||||
server->on(TIME_PATH,
|
||||
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { configureTime(request, json); },
|
||||
AuthenticationPredicates::IS_ADMIN));
|
||||
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
addUpdateHandler([this] { configureNTP(); }, false);
|
||||
}
|
||||
|
||||
void NTPSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
configureNTP();
|
||||
}
|
||||
|
||||
// handles both WiFI and Ethernet
|
||||
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
if (_connected) {
|
||||
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
|
||||
_connected = false;
|
||||
configureNTP();
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
|
||||
_connected = true;
|
||||
configureNTP();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
|
||||
void NTPSettingsService::configureNTP() {
|
||||
emsesp::EMSESP::system_.ntp_connected(false);
|
||||
if (_connected && _state.enabled) {
|
||||
emsesp::EMSESP::logger().info("Starting NTP service");
|
||||
esp_sntp_set_sync_interval(3600000); // one hour
|
||||
esp_sntp_set_time_sync_notification_cb(ntp_received);
|
||||
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
|
||||
} else {
|
||||
setenv("TZ", _state.tzFormat.c_str(), 1);
|
||||
tzset();
|
||||
esp_sntp_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
struct tm tm = {};
|
||||
String timeLocal = json["local_time"];
|
||||
char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm);
|
||||
if (s != nullptr) {
|
||||
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
|
||||
time_t time = mktime(&tm);
|
||||
struct timeval now = {.tv_sec = time, .tv_usec = {}};
|
||||
settimeofday(&now, nullptr);
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(400);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void NTPSettingsService::ntp_received(struct timeval * tv) {
|
||||
(void)tv;
|
||||
// emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec);
|
||||
emsesp::EMSESP::system_.ntp_connected(true);
|
||||
}
|
||||
|
||||
void NTPSettings::read(NTPSettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["server"] = settings.server;
|
||||
root["tz_label"] = settings.tzLabel;
|
||||
root["tz_format"] = settings.tzFormat;
|
||||
}
|
||||
|
||||
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#ifndef NTPSettingsService_h
|
||||
#define NTPSettingsService_h
|
||||
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
#ifndef FACTORY_NTP_ENABLED
|
||||
#define FACTORY_NTP_ENABLED true
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_NTP_TIME_ZONE_LABEL
|
||||
#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_NTP_TIME_ZONE_FORMAT
|
||||
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_NTP_SERVER
|
||||
#define FACTORY_NTP_SERVER "time.google.com"
|
||||
#endif
|
||||
|
||||
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
|
||||
|
||||
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
|
||||
#define TIME_PATH "/rest/time"
|
||||
|
||||
class NTPSettings {
|
||||
public:
|
||||
bool enabled;
|
||||
String tzLabel;
|
||||
String tzFormat;
|
||||
String server;
|
||||
|
||||
static void read(NTPSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
|
||||
};
|
||||
|
||||
class NTPSettingsService : public StatefulService<NTPSettings> {
|
||||
public:
|
||||
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
static void ntp_received(struct timeval * tv);
|
||||
|
||||
private:
|
||||
HttpEndpoint<NTPSettings> _httpEndpoint;
|
||||
FSPersistence<NTPSettings> _fsPersistence;
|
||||
bool _connected;
|
||||
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
void configureNTP();
|
||||
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,62 +0,0 @@
|
||||
#include "NTPStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(NTP_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { ntpStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
/*
|
||||
* Formats the time using the format provided.
|
||||
*
|
||||
* Uses a 25 byte buffer, large enough to fit an ISO time string with offset.
|
||||
*/
|
||||
String formatTime(tm * time, const char * format) {
|
||||
std::array<char, 25> time_string{};
|
||||
strftime(time_string.data(), time_string.size(), format, time);
|
||||
return {time_string.data()};
|
||||
}
|
||||
|
||||
String toUTCTimeString(tm * time) {
|
||||
return formatTime(time, "%FT%TZ");
|
||||
}
|
||||
|
||||
String toLocalTimeString(tm * time) {
|
||||
return formatTime(time, "%FT%T");
|
||||
}
|
||||
|
||||
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
// grab the current instant in unix seconds
|
||||
time_t now = time(nullptr);
|
||||
|
||||
// only provide enabled/disabled status for now
|
||||
root["status"] = [] {
|
||||
if (esp_sntp_enabled()) {
|
||||
if (emsesp::EMSESP::system_.ntp_connected()) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
|
||||
// the current time in UTC
|
||||
root["utc_time"] = toUTCTimeString(gmtime(&now));
|
||||
|
||||
// local time with offset
|
||||
root["local_time"] = toLocalTimeString(localtime(&now));
|
||||
|
||||
// the sntp server name
|
||||
root["server"] = esp_sntp_getservername(0);
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#ifndef NTPStatus_h
|
||||
#define NTPStatus_h
|
||||
|
||||
#include <ctime>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <uuid/common.h>
|
||||
|
||||
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
|
||||
|
||||
class NTPStatus {
|
||||
public:
|
||||
NTPStatus(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void ntpStatus(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,466 +0,0 @@
|
||||
#include "NetworkSettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
|
||||
, _lastConnectionAttempt(0)
|
||||
, _stopping(false) {
|
||||
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
|
||||
// Eth is also bound to the WifiGeneric event handler
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
|
||||
}
|
||||
|
||||
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
|
||||
uint tmp[6];
|
||||
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
mac[i] = static_cast<uint8_t>(tmp[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkSettingsService::begin() {
|
||||
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
|
||||
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
|
||||
if (WiFi.getMode() != WIFI_OFF) {
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
||||
// Disable WiFi config persistance and auto reconnect
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
|
||||
WiFi.mode(WIFI_MODE_MAX);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14
|
||||
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
|
||||
|
||||
_fsPersistence.readFromFS();
|
||||
// reconfigureWiFiConnection();
|
||||
}
|
||||
|
||||
void NetworkSettingsService::reconfigureWiFiConnection() {
|
||||
// do not disconnect for switching to eth, restart is needed
|
||||
if (WiFi.isConnected() && _state.ssid.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// disconnect and de-configure wifi
|
||||
if (WiFi.disconnect(true)) {
|
||||
_stopping = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettingsService::loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (!_lastConnectionAttempt || static_cast<uint32_t>(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
|
||||
_lastConnectionAttempt = currentMillis;
|
||||
manageSTA();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettingsService::manageSTA() {
|
||||
// Abort if already connected, or if we have no SSID
|
||||
if (WiFi.isConnected() || _state.ssid.length() == 0) {
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
if (_state.ssid.length() == 0) {
|
||||
ETH.enableIPv6(true);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect or reconnect as required
|
||||
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
WiFi.enableIPv6(true);
|
||||
#endif
|
||||
if (_state.staticIPConfig) {
|
||||
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
|
||||
}
|
||||
WiFi.setHostname(_state.hostname.c_str()); // set hostname
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (_state.bandwidth20) {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
}
|
||||
if (_state.nosleep) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
}
|
||||
|
||||
// attempt to connect to the network
|
||||
uint8_t bssid[6];
|
||||
if (formatBssid(_state.bssid, bssid)) {
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid);
|
||||
} else {
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
|
||||
}
|
||||
|
||||
#ifdef BOARD_C3_MINI_V1
|
||||
// always hardcode Tx power for Wemos CS Mini v1
|
||||
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
|
||||
// https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#else
|
||||
if (_state.tx_power != 0) {
|
||||
// if not set to Auto (0) set the Tx power now
|
||||
if (!WiFi.setTxPower(static_cast<wifi_power_t>(_state.tx_power))) {
|
||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else { // not connected but STA-mode active => disconnect
|
||||
reconfigureWiFiConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// set the TxPower based on the RSSI (signal strength), picking the lowest value
|
||||
// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
|
||||
void NetworkSettingsService::setWiFiPowerOnRSSI() {
|
||||
// Range ESP32 : 2dBm - 20dBm
|
||||
// 802.11b - wifi1
|
||||
// 802.11a - wifi2
|
||||
// 802.11g - wifi3
|
||||
// 802.11n - wifi4
|
||||
// 802.11ac - wifi5
|
||||
// 802.11ax - wifi6
|
||||
|
||||
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
|
||||
int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold
|
||||
|
||||
// Assume AP sends with max set by ETSI standard.
|
||||
// 2.4 GHz: 100 mWatt (20 dBm)
|
||||
// US and some other countries allow 1000 mW (30 dBm)
|
||||
int rssi = WiFi.RSSI() * 10;
|
||||
int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time.
|
||||
|
||||
int min_tx_pwr = 0;
|
||||
if (newrssi < threshold) {
|
||||
min_tx_pwr = threshold - newrssi;
|
||||
}
|
||||
if (min_tx_pwr > max_tx_pwr) {
|
||||
min_tx_pwr = max_tx_pwr;
|
||||
}
|
||||
|
||||
// from WiFIGeneric.h use:
|
||||
// WIFI_POWER_19_5dBm = 78,// 19.5dBm
|
||||
// WIFI_POWER_19dBm = 76,// 19dBm
|
||||
// WIFI_POWER_18_5dBm = 74,// 18.5dBm
|
||||
// WIFI_POWER_17dBm = 68,// 17dBm
|
||||
// WIFI_POWER_15dBm = 60,// 15dBm
|
||||
// WIFI_POWER_13dBm = 52,// 13dBm
|
||||
// WIFI_POWER_11dBm = 44,// 11dBm
|
||||
// WIFI_POWER_8_5dBm = 34,// 8.5dBm
|
||||
// WIFI_POWER_7dBm = 28,// 7dBm
|
||||
// WIFI_POWER_5dBm = 20,// 5dBm
|
||||
// WIFI_POWER_2dBm = 8,// 2dBm
|
||||
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
|
||||
wifi_power_t p = WIFI_POWER_2dBm;
|
||||
if (min_tx_pwr > 185)
|
||||
p = WIFI_POWER_19_5dBm;
|
||||
else if (min_tx_pwr > 170)
|
||||
p = WIFI_POWER_18_5dBm;
|
||||
else if (min_tx_pwr > 150)
|
||||
p = WIFI_POWER_17dBm;
|
||||
else if (min_tx_pwr > 130)
|
||||
p = WIFI_POWER_15dBm;
|
||||
else if (min_tx_pwr > 110)
|
||||
p = WIFI_POWER_13dBm;
|
||||
else if (min_tx_pwr > 85)
|
||||
p = WIFI_POWER_11dBm;
|
||||
else if (min_tx_pwr > 70)
|
||||
p = WIFI_POWER_8_5dBm;
|
||||
else if (min_tx_pwr > 50)
|
||||
p = WIFI_POWER_7dBm;
|
||||
else if (min_tx_pwr > 20)
|
||||
p = WIFI_POWER_5dBm;
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold);
|
||||
#endif
|
||||
|
||||
if (!WiFi.setTxPower(p)) {
|
||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
|
||||
}
|
||||
}
|
||||
|
||||
// start the multicast UDP service so EMS-ESP is discoverable via .local
|
||||
void NetworkSettingsService::mDNS_start() const {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
MDNS.end();
|
||||
|
||||
if (_state.enableMDNS) {
|
||||
if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) {
|
||||
emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string address_s = emsesp::EMSESP::system_.hostname() + ".local";
|
||||
|
||||
MDNS.addService("http", "tcp", 80); // add our web server and rest API
|
||||
MDNS.addService("telnet", "tcp", 23); // add our telnet console
|
||||
|
||||
MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
|
||||
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
|
||||
|
||||
emsesp::EMSESP::logger().info("Starting mDNS Responder service");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const char * NetworkSettingsService::disconnectReason(uint8_t code) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (code) {
|
||||
case WIFI_REASON_UNSPECIFIED: // = 1,
|
||||
return "unspecified";
|
||||
case WIFI_REASON_AUTH_EXPIRE: // = 2,
|
||||
return "auth expire";
|
||||
case WIFI_REASON_AUTH_LEAVE: // = 3,
|
||||
return "auth leave";
|
||||
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
|
||||
return "assoc expired";
|
||||
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
|
||||
return "assoc too many";
|
||||
case WIFI_REASON_NOT_AUTHED: // = 6,
|
||||
return "not authenticated";
|
||||
case WIFI_REASON_NOT_ASSOCED: // = 7,
|
||||
return "not assoc";
|
||||
case WIFI_REASON_ASSOC_LEAVE: // = 8,
|
||||
return "assoc leave";
|
||||
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
|
||||
return "assoc not authed";
|
||||
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
|
||||
return "disassoc powerCAP bad";
|
||||
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
|
||||
return "disassoc supchan bad";
|
||||
case WIFI_REASON_IE_INVALID: // = 13,
|
||||
return "IE invalid";
|
||||
case WIFI_REASON_MIC_FAILURE: // = 14,
|
||||
return "MIC failure";
|
||||
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
|
||||
return "4way handshake timeout";
|
||||
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
|
||||
return "group key-update timeout";
|
||||
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
|
||||
return "IE in 4way differs";
|
||||
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
|
||||
return "group cipher invalid";
|
||||
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
|
||||
return "pairwise cipher invalid";
|
||||
case WIFI_REASON_AKMP_INVALID: // = 20,
|
||||
return "AKMP invalid";
|
||||
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
|
||||
return "unsupported RSN_IE version";
|
||||
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
|
||||
return "invalid RSN_IE_CAP";
|
||||
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
|
||||
return "802 X1 auth failed";
|
||||
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
|
||||
return "cipher suite rejected";
|
||||
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
|
||||
return "beacon timeout";
|
||||
case WIFI_REASON_NO_AP_FOUND: // = 201,
|
||||
return "no AP found";
|
||||
case WIFI_REASON_AUTH_FAIL: // = 202,
|
||||
return "auth fail";
|
||||
case WIFI_REASON_ASSOC_FAIL: // = 203,
|
||||
return "assoc fail";
|
||||
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
|
||||
return "handshake timeout";
|
||||
case WIFI_REASON_CONNECTION_FAIL: // 205,
|
||||
return "connection fail";
|
||||
case WIFI_REASON_AP_TSF_RESET: // 206,
|
||||
return "AP tsf reset";
|
||||
case WIFI_REASON_ROAMING: // 207,
|
||||
return "roaming";
|
||||
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208,
|
||||
return "assoc comeback time too long";
|
||||
case WIFI_REASON_SA_QUERY_TIMEOUT: // 209,
|
||||
return "sa query timeout";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
#endif
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// handles both WiFI and Ethernet
|
||||
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP:
|
||||
if (_stopping) {
|
||||
_lastConnectionAttempt = 0;
|
||||
_stopping = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
connectcount_++; // count the number of WiFi reconnects
|
||||
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
|
||||
connectcount_,
|
||||
disconnectReason(info.wifi_sta_disconnected.reason),
|
||||
info.wifi_sta_disconnected.reason); // IDF 4.0
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
char result[10];
|
||||
emsesp::EMSESP::logger().info("WiFi connected (Local IP=%s, hostname=%s, TxPower=%s dBm)",
|
||||
WiFi.localIP().toString().c_str(),
|
||||
WiFi.getHostname(),
|
||||
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
|
||||
mDNS_start();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
// configure for static IP
|
||||
if (_state.staticIPConfig) {
|
||||
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
|
||||
}
|
||||
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// prevent double calls to mDNS
|
||||
if (!emsesp::EMSESP::system_.ethernet_connected()) {
|
||||
emsesp::EMSESP::logger().info("Ethernet connected (Local IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed());
|
||||
emsesp::EMSESP::system_.ethernet_connected(true);
|
||||
mDNS_start();
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
emsesp::EMSESP::logger().warning("Ethernet disconnected. Reason: %s (%d)",
|
||||
disconnectReason(info.wifi_sta_disconnected.reason),
|
||||
info.wifi_sta_disconnected.reason);
|
||||
emsesp::EMSESP::system_.ethernet_connected(false);
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
emsesp::EMSESP::logger().info("Ethernet stopped");
|
||||
emsesp::EMSESP::system_.ethernet_connected(false);
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
// Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto)
|
||||
if (_state.tx_power == 0) {
|
||||
setWiFiPowerOnRSSI();
|
||||
}
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
WiFi.enableIpV6(); // force ipv6
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
ETH.enableIpV6(); // force ipv6
|
||||
#endif
|
||||
break;
|
||||
|
||||
// IPv6 specific - WiFi/Eth
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6: {
|
||||
#if !TASMOTA_SDK && ESP_IDF_VERSION_MAJOR < 5
|
||||
auto ip6 = IPv6Address((uint8_t *)info.got_ip6.ip6_info.ip.addr).toString();
|
||||
#else
|
||||
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
|
||||
#endif
|
||||
const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi";
|
||||
if (ip6.startsWith("fe80")) {
|
||||
emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str());
|
||||
} else if (ip6.startsWith("fd") || ip6.startsWith("fc")) {
|
||||
emsesp::EMSESP::logger().info("IPv6 (%s) ULA: %s", link, ip6.c_str());
|
||||
} else {
|
||||
emsesp::EMSESP::logger().info("IPv6 (%s) global: %s", link, ip6.c_str());
|
||||
}
|
||||
emsesp::EMSESP::system_.has_ipv6(true);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkSettings::read(NetworkSettings & settings, JsonObject root) {
|
||||
// connection settings
|
||||
root["ssid"] = settings.ssid;
|
||||
root["bssid"] = settings.bssid;
|
||||
root["password"] = settings.password;
|
||||
root["hostname"] = settings.hostname;
|
||||
root["static_ip_config"] = settings.staticIPConfig;
|
||||
root["bandwidth20"] = settings.bandwidth20;
|
||||
root["nosleep"] = settings.nosleep;
|
||||
root["enableMDNS"] = settings.enableMDNS;
|
||||
root["enableCORS"] = settings.enableCORS;
|
||||
root["CORSOrigin"] = settings.CORSOrigin;
|
||||
root["tx_power"] = settings.tx_power;
|
||||
|
||||
// extended settings
|
||||
JsonUtils::writeIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
}
|
||||
|
||||
StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & settings) {
|
||||
// keep copy of original settings
|
||||
auto enableCORS = settings.enableCORS;
|
||||
auto CORSOrigin = settings.CORSOrigin;
|
||||
auto ssid = settings.ssid;
|
||||
auto tx_power = settings.tx_power;
|
||||
|
||||
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
|
||||
settings.bssid = root["bssid"] | "";
|
||||
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
|
||||
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
|
||||
settings.staticIPConfig = root["static_ip_config"];
|
||||
settings.bandwidth20 = root["bandwidth20"];
|
||||
settings.tx_power = static_cast<uint8_t>(root["tx_power"] | 0);
|
||||
settings.nosleep = root["nosleep"] | true;
|
||||
settings.enableMDNS = root["enableMDNS"] | true;
|
||||
settings.enableCORS = root["enableCORS"];
|
||||
settings.CORSOrigin = root["CORSOrigin"] | "*";
|
||||
|
||||
// extended settings
|
||||
JsonUtils::readIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
|
||||
// Swap around the dns servers if 2 is populated but 1 is not
|
||||
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
|
||||
settings.dnsIP1 = settings.dnsIP2;
|
||||
settings.dnsIP2 = INADDR_NONE;
|
||||
}
|
||||
|
||||
// Turning off static ip config if we don't meet the minimum requirements
|
||||
// of ipAddress, gateway and subnet. This may change to static ip only
|
||||
// as sensible defaults can be assumed for gateway and subnet
|
||||
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
|
||||
settings.staticIPConfig = false;
|
||||
}
|
||||
|
||||
// see if we need to inform the user of a restart
|
||||
if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin
|
||||
|| (ssid != settings.ssid && settings.ssid.isEmpty())) {
|
||||
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#ifndef NetworkSettingsService_h
|
||||
#define NetworkSettingsService_h
|
||||
|
||||
#include "StatefulService.h"
|
||||
#include "FSPersistence.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <esp_wifi.h>
|
||||
#include <ETH.h>
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPmDNS.h>
|
||||
#endif
|
||||
|
||||
#define NETWORK_SETTINGS_FILE "/config/networkSettings.json"
|
||||
#define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings"
|
||||
#define WIFI_RECONNECTION_DELAY (1000 * 3)
|
||||
|
||||
#ifndef FACTORY_WIFI_SSID
|
||||
#define FACTORY_WIFI_SSID ""
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_WIFI_PASSWORD
|
||||
#define FACTORY_WIFI_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_WIFI_HOSTNAME
|
||||
#define FACTORY_WIFI_HOSTNAME ""
|
||||
#endif
|
||||
|
||||
// copied from Tasmota
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
#define MAX_TX_PWR_DBM_11b 195
|
||||
#define MAX_TX_PWR_DBM_54g 150
|
||||
#define MAX_TX_PWR_DBM_n 130
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -750
|
||||
#define WIFI_SENSITIVITY_n -720
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#define MAX_TX_PWR_DBM_11b 210
|
||||
#define MAX_TX_PWR_DBM_54g 190
|
||||
#define MAX_TX_PWR_DBM_n 185
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -760
|
||||
#define WIFI_SENSITIVITY_n -720
|
||||
#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3
|
||||
#define MAX_TX_PWR_DBM_11b 210
|
||||
#define MAX_TX_PWR_DBM_54g 190
|
||||
#define MAX_TX_PWR_DBM_n 185
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -760
|
||||
#define WIFI_SENSITIVITY_n -730
|
||||
#else
|
||||
#define MAX_TX_PWR_DBM_11b 195
|
||||
#define MAX_TX_PWR_DBM_54g 160
|
||||
#define MAX_TX_PWR_DBM_n 140
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -750
|
||||
#define WIFI_SENSITIVITY_n -700
|
||||
#endif
|
||||
|
||||
class NetworkSettings {
|
||||
public:
|
||||
// core wifi configuration
|
||||
String ssid;
|
||||
String bssid;
|
||||
String password;
|
||||
String hostname;
|
||||
bool staticIPConfig;
|
||||
bool bandwidth20;
|
||||
uint8_t tx_power;
|
||||
bool nosleep;
|
||||
bool enableMDNS;
|
||||
bool enableCORS;
|
||||
String CORSOrigin;
|
||||
|
||||
// optional configuration for static IP address
|
||||
IPAddress localIP;
|
||||
IPAddress gatewayIP;
|
||||
IPAddress subnetMask;
|
||||
IPAddress dnsIP1;
|
||||
IPAddress dnsIP2;
|
||||
|
||||
static void read(NetworkSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, NetworkSettings & settings);
|
||||
};
|
||||
|
||||
class NetworkSettingsService : public StatefulService<NetworkSettings> {
|
||||
public:
|
||||
NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void loop();
|
||||
|
||||
uint16_t getWifiReconnects() const {
|
||||
return connectcount_;
|
||||
}
|
||||
|
||||
private:
|
||||
HttpEndpoint<NetworkSettings> _httpEndpoint;
|
||||
FSPersistence<NetworkSettings> _fsPersistence;
|
||||
|
||||
unsigned long _lastConnectionAttempt;
|
||||
bool _stopping;
|
||||
|
||||
uint16_t connectcount_ = 0; // number of wifi reconnects
|
||||
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void mDNS_start() const;
|
||||
const char * disconnectReason(uint8_t code);
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void setWiFiPowerOnRSSI();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,92 +0,0 @@
|
||||
#include "NetworkStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
#ifdef TASMOTA_SDK
|
||||
#include "lwip/dns.h"
|
||||
#endif
|
||||
|
||||
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(NETWORK_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { networkStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected();
|
||||
wl_status_t wifi_status = WiFi.status();
|
||||
|
||||
// see if Ethernet is connected
|
||||
if (ethernet_connected) {
|
||||
root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
|
||||
root["hostname"] = ETH.getHostname();
|
||||
} else {
|
||||
root["status"] = static_cast<uint8_t>(wifi_status);
|
||||
root["hostname"] = WiFi.getHostname();
|
||||
}
|
||||
|
||||
// for both connections show ethernet
|
||||
if (ethernet_connected) {
|
||||
// Ethernet
|
||||
root["local_ip"] = ETH.localIP().toString();
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
root["local_ipv6"] = ETH.localIPv6().toString();
|
||||
#else
|
||||
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
|
||||
#endif
|
||||
root["mac_address"] = ETH.macAddress();
|
||||
root["subnet_mask"] = ETH.subnetMask().toString();
|
||||
root["gateway_ip"] = ETH.gatewayIP().toString();
|
||||
#ifdef TASMOTA_SDK
|
||||
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
|
||||
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
|
||||
#else
|
||||
IPAddress dnsIP1 = ETH.dnsIP(0);
|
||||
IPAddress dnsIP2 = ETH.dnsIP(1);
|
||||
#endif
|
||||
if (IPUtils::isSet(dnsIP1)) {
|
||||
root["dns_ip_1"] = dnsIP1.toString();
|
||||
}
|
||||
if (IPUtils::isSet(dnsIP2)) {
|
||||
root["dns_ip_2"] = dnsIP2.toString();
|
||||
}
|
||||
} else if (wifi_status == WL_CONNECTED) {
|
||||
root["local_ip"] = WiFi.localIP().toString();
|
||||
// #if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
root["local_ipv6"] = WiFi.localIPv6().toString();
|
||||
#else
|
||||
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
|
||||
#endif
|
||||
root["mac_address"] = WiFi.macAddress();
|
||||
root["rssi"] = WiFi.RSSI();
|
||||
root["ssid"] = WiFi.SSID();
|
||||
root["bssid"] = WiFi.BSSIDstr();
|
||||
root["channel"] = WiFi.channel();
|
||||
root["reconnect_count"] = emsesp::EMSESP::esp8266React.getWifiReconnects();
|
||||
root["subnet_mask"] = WiFi.subnetMask().toString();
|
||||
|
||||
if (WiFi.gatewayIP() != INADDR_NONE) {
|
||||
root["gateway_ip"] = WiFi.gatewayIP().toString();
|
||||
}
|
||||
|
||||
#ifdef TASMOTA_SDK
|
||||
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
|
||||
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
|
||||
#else
|
||||
IPAddress dnsIP1 = WiFi.dnsIP(0);
|
||||
IPAddress dnsIP2 = WiFi.dnsIP(1);
|
||||
#endif
|
||||
if (dnsIP1 != INADDR_NONE) {
|
||||
root["dns_ip_1"] = dnsIP1.toString();
|
||||
}
|
||||
if (dnsIP2 != INADDR_NONE) {
|
||||
root["dns_ip_2"] = dnsIP2.toString();
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef NetworkStatus_h
|
||||
#define NetworkStatus_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include "IPUtils.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"
|
||||
|
||||
class NetworkStatus {
|
||||
public:
|
||||
NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void networkStatus(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,98 +0,0 @@
|
||||
#ifndef SecurityManager_h
|
||||
#define SecurityManager_h
|
||||
|
||||
#include "Features.h"
|
||||
#include "ArduinoJsonJWT.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <list>
|
||||
|
||||
#define ACCESS_TOKEN_PARAMATER "access_token"
|
||||
|
||||
#define AUTHORIZATION_HEADER "Authorization"
|
||||
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
|
||||
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
|
||||
|
||||
class User {
|
||||
public:
|
||||
String username;
|
||||
String password;
|
||||
bool admin;
|
||||
|
||||
public:
|
||||
User(String username, String password, bool admin)
|
||||
: username(std::move(username))
|
||||
, password(std::move(password))
|
||||
, admin(admin) {
|
||||
}
|
||||
};
|
||||
|
||||
class Authentication {
|
||||
public:
|
||||
User * user = nullptr;
|
||||
boolean authenticated = false;
|
||||
|
||||
public:
|
||||
explicit Authentication(const User & user)
|
||||
: user(new User(user))
|
||||
, authenticated(true) {
|
||||
}
|
||||
|
||||
Authentication() = default;
|
||||
|
||||
~Authentication() {
|
||||
delete user;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<boolean(Authentication & authentication)> AuthenticationPredicate;
|
||||
|
||||
class AuthenticationPredicates {
|
||||
public:
|
||||
static bool NONE_REQUIRED(const Authentication & authentication) {
|
||||
(void)authentication;
|
||||
return true;
|
||||
};
|
||||
static bool IS_AUTHENTICATED(const Authentication & authentication) {
|
||||
return authentication.authenticated;
|
||||
};
|
||||
static bool IS_ADMIN(const Authentication & authentication) {
|
||||
return authentication.authenticated && authentication.user->admin;
|
||||
};
|
||||
};
|
||||
|
||||
class SecurityManager {
|
||||
public:
|
||||
/*
|
||||
* Authenticate, returning the user if found
|
||||
*/
|
||||
virtual Authentication authenticate(const String & username, const String & password) = 0;
|
||||
|
||||
/*
|
||||
* Generate a JWT for the user provided
|
||||
*/
|
||||
virtual String generateJWT(const User * user) = 0;
|
||||
|
||||
/*
|
||||
* Check the request header for the Authorization token
|
||||
*/
|
||||
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
|
||||
|
||||
/**
|
||||
* Filter a request with the provided predicate, only returning true if the predicate matches.
|
||||
*/
|
||||
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
|
||||
|
||||
/**
|
||||
* Wrap the provided request to provide validation against an AuthenticationPredicate.
|
||||
*/
|
||||
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
||||
|
||||
/**
|
||||
* Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
|
||||
*/
|
||||
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,123 +0,0 @@
|
||||
#include "SecuritySettingsService.h"
|
||||
|
||||
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
|
||||
: _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
|
||||
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
|
||||
, _jwtHandler(FACTORY_JWT_SECRET) {
|
||||
addUpdateHandler([this] { configureJWTHandler(); }, false);
|
||||
server->on(GENERATE_TOKEN_PATH,
|
||||
HTTP_GET,
|
||||
SecuritySettingsService::wrapRequest([this](AsyncWebServerRequest * request) { generateToken(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void SecuritySettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
configureJWTHandler();
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
|
||||
AsyncWebHeader * authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
|
||||
if (authorizationHeader) {
|
||||
String value = authorizationHeader->value();
|
||||
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
|
||||
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
|
||||
AsyncWebParameter * tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
|
||||
String value = tokenParamater->value();
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void SecuritySettingsService::configureJWTHandler() {
|
||||
_jwtHandler.setSecret(_state.jwtSecret);
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
|
||||
JsonDocument payloadDocument;
|
||||
_jwtHandler.parseJWT(jwt, payloadDocument);
|
||||
if (payloadDocument.is<JsonObject>()) {
|
||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||
String username = parsedPayload["username"];
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticate(const String & username, const String & password) {
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == username && _user.password == password) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
inline void populateJWTPayload(JsonObject payload, const User * user) {
|
||||
payload["username"] = user->username;
|
||||
payload["admin"] = user->admin;
|
||||
}
|
||||
|
||||
boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, const User * user) {
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return payload == parsedPayload;
|
||||
}
|
||||
|
||||
String SecuritySettingsService::generateJWT(const User * user) {
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return _jwtHandler.buildJWT(payload);
|
||||
}
|
||||
|
||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
|
||||
return [this, predicate](AsyncWebServerRequest * request) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
return predicate(authentication);
|
||||
};
|
||||
}
|
||||
|
||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
||||
return [this, onRequest, predicate](AsyncWebServerRequest * request) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
if (!predicate(authentication)) {
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
onRequest(request);
|
||||
};
|
||||
}
|
||||
|
||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
|
||||
return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant json) {
|
||||
Authentication authentication = authenticateRequest(request);
|
||||
if (!predicate(authentication)) {
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
onRequest(request, json);
|
||||
};
|
||||
}
|
||||
|
||||
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
|
||||
AsyncWebParameter * usernameParam = request->getParam("username");
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == usernameParam->value()) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["token"] = generateJWT(&_user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(401);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#ifndef SecuritySettingsService_h
|
||||
#define SecuritySettingsService_h
|
||||
|
||||
#include "Features.h"
|
||||
#include "SecurityManager.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#ifndef FACTORY_ADMIN_USERNAME
|
||||
#define FACTORY_ADMIN_USERNAME "admin"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_ADMIN_PASSWORD
|
||||
#define FACTORY_ADMIN_PASSWORD "admin"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_GUEST_USERNAME
|
||||
#define FACTORY_GUEST_USERNAME "guest"
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_GUEST_PASSWORD
|
||||
#define FACTORY_GUEST_PASSWORD "guest"
|
||||
#endif
|
||||
|
||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
|
||||
|
||||
#define GENERATE_TOKEN_SIZE 512
|
||||
#define GENERATE_TOKEN_PATH "/rest/generateToken"
|
||||
|
||||
class SecuritySettings {
|
||||
public:
|
||||
String jwtSecret;
|
||||
std::vector<User> users;
|
||||
|
||||
static void read(SecuritySettings & settings, JsonObject root) {
|
||||
// secret
|
||||
root["jwt_secret"] = settings.jwtSecret;
|
||||
|
||||
// users
|
||||
JsonArray users = root["users"].to<JsonArray>();
|
||||
for (const User & user : settings.users) {
|
||||
JsonObject userRoot = users.add<JsonObject>();
|
||||
userRoot["username"] = user.username;
|
||||
userRoot["password"] = user.password;
|
||||
userRoot["admin"] = user.admin;
|
||||
}
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject root, SecuritySettings & settings) {
|
||||
// secret
|
||||
settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
|
||||
|
||||
// users
|
||||
settings.users.clear();
|
||||
if (root["users"].is<JsonArray>()) {
|
||||
for (JsonVariant user : root["users"].as<JsonArray>()) {
|
||||
settings.users.emplace_back(user["username"], user["password"], user["admin"]);
|
||||
}
|
||||
} else {
|
||||
settings.users.emplace_back(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
|
||||
settings.users.emplace_back(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false);
|
||||
}
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
};
|
||||
|
||||
class SecuritySettingsService final : public StatefulService<SecuritySettings>, public SecurityManager {
|
||||
public:
|
||||
SecuritySettingsService(AsyncWebServer * server, FS * fs);
|
||||
|
||||
void begin();
|
||||
|
||||
Authentication authenticate(const String & username, const String & password) override;
|
||||
Authentication authenticateRequest(AsyncWebServerRequest * request) override;
|
||||
String generateJWT(const User * user) override;
|
||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) override;
|
||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) override;
|
||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate) override;
|
||||
|
||||
private:
|
||||
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
||||
FSPersistence<SecuritySettings> _fsPersistence;
|
||||
ArduinoJsonJWT _jwtHandler;
|
||||
|
||||
void generateToken(AsyncWebServerRequest * request);
|
||||
|
||||
void configureJWTHandler();
|
||||
|
||||
Authentication authenticateJWT(String & jwt); // Lookup the user by JWT
|
||||
|
||||
boolean validatePayload(JsonObject parsedPayload, const User * user); // Verify the payload is correct
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,3 +0,0 @@
|
||||
#include "StatefulService.h"
|
||||
|
||||
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;
|
||||
@@ -1,137 +0,0 @@
|
||||
#ifndef StatefulService_h
|
||||
#define StatefulService_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
enum class StateUpdateResult {
|
||||
CHANGED = 0, // The update changed the state and propagation should take place if required
|
||||
CHANGED_RESTART, // a restart of the device is needed
|
||||
UNCHANGED, // The state was unchanged, propagation should not take place
|
||||
ERROR // There was a problem updating the state, propagation should not take place
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject root, T & settings)>;
|
||||
|
||||
template <typename T>
|
||||
using JsonStateReader = std::function<void(T & settings, JsonObject root)>;
|
||||
|
||||
typedef size_t update_handler_id_t;
|
||||
typedef std::function<void()> StateUpdateCallback;
|
||||
|
||||
typedef struct StateUpdateHandlerInfo {
|
||||
static update_handler_id_t currentUpdatedHandlerId;
|
||||
update_handler_id_t _id;
|
||||
StateUpdateCallback _cb;
|
||||
bool _allowRemove;
|
||||
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
|
||||
: _id(++currentUpdatedHandlerId)
|
||||
, _cb(std::move(cb))
|
||||
, _allowRemove(allowRemove){};
|
||||
} StateUpdateHandlerInfo_t;
|
||||
|
||||
template <class T>
|
||||
class StatefulService {
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...)
|
||||
, _accessMutex(xSemaphoreCreateRecursiveMutex()) {
|
||||
}
|
||||
|
||||
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
|
||||
if (!cb) {
|
||||
return 0;
|
||||
}
|
||||
StateUpdateHandlerInfo_t updateHandler(std::move(cb), allowRemove);
|
||||
_updateHandlers.push_back(std::move(updateHandler));
|
||||
return updateHandler._id;
|
||||
}
|
||||
|
||||
void removeUpdateHandler(update_handler_id_t id) {
|
||||
for (auto it = _updateHandlers.begin(); it != _updateHandlers.end();) {
|
||||
auto & elem = *it;
|
||||
if (elem._allowRemove && elem._id == id) {
|
||||
it = _updateHandlers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
void read(std::function<void(T &)> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void read(JsonObject jsonObject, JsonStateReader<T> stateReader) {
|
||||
beginTransaction();
|
||||
stateReader(_state, jsonObject);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void callUpdateHandlers() {
|
||||
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
|
||||
updateHandler._cb();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
T _state;
|
||||
|
||||
inline void beginTransaction() {
|
||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
inline void endTransaction() {
|
||||
xSemaphoreGiveRecursive(_accessMutex);
|
||||
}
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
std::vector<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,167 +0,0 @@
|
||||
#include "UploadFileService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
#include <esp_app_format.h>
|
||||
|
||||
static String getFilenameExtension(const String & filename) {
|
||||
const auto pos = filename.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
return filename.substring(static_cast<unsigned int>(pos) + 1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _securityManager(securityManager)
|
||||
, _is_firmware(false)
|
||||
, _md5() {
|
||||
// upload a file via a form
|
||||
server->on(
|
||||
UPLOAD_FILE_PATH,
|
||||
HTTP_POST,
|
||||
[this](AsyncWebServerRequest * request) { uploadComplete(request); },
|
||||
[this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
|
||||
handleUpload(request, filename, index, data, len, final);
|
||||
});
|
||||
}
|
||||
|
||||
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
|
||||
// quit if not authorized
|
||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||
if (!AuthenticationPredicates::IS_ADMIN(authentication)) {
|
||||
handleError(request, 403); // send the forbidden response
|
||||
return;
|
||||
}
|
||||
|
||||
// at init
|
||||
if (!index) {
|
||||
// check details of the file, to see if its a valid bin or json file
|
||||
const String extension = getFilenameExtension(filename);
|
||||
const std::size_t filesize = request->contentLength();
|
||||
|
||||
_is_firmware = false;
|
||||
if ((extension == "bin") && (filesize > 1000000)) {
|
||||
_is_firmware = true;
|
||||
} else if (extension == "json") {
|
||||
_md5[0] = '\0'; // clear md5
|
||||
} else if (extension == "md5") {
|
||||
if (len == _md5.size() - 1) {
|
||||
std::memcpy(_md5.data(), data, _md5.size() - 1);
|
||||
_md5.back() = '\0';
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
_md5.front() = '\0';
|
||||
handleError(request, 406); // Not Acceptable - unsupported file type
|
||||
return;
|
||||
}
|
||||
|
||||
if (_is_firmware) {
|
||||
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
|
||||
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
|
||||
handleError(request, 503); // service unavailable
|
||||
return;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
|
||||
handleError(request, 503); // service unavailable
|
||||
return;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
|
||||
handleError(request, 503); // service unavailable
|
||||
return;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
|
||||
handleError(request, 503); // service unavailable
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// it's firmware - initialize the ArduinoOTA updater
|
||||
if (Update.begin(filesize - sizeof(esp_image_header_t))) {
|
||||
if (strlen(_md5.data()) == _md5.size() - 1) {
|
||||
Update.setMD5(_md5.data());
|
||||
_md5.front() = '\0';
|
||||
}
|
||||
request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up
|
||||
} else {
|
||||
handleError(request, 507); // failed to begin, send an error response Insufficient Storage
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// its a normal file, open a new temp file to write the contents too
|
||||
request->_tempFile = LittleFS.open(TEMP_FILENAME_PATH, "w");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_is_firmware) {
|
||||
if (len && len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
|
||||
handleError(request, 507); // 507-Insufficient Storage
|
||||
}
|
||||
} else if (!request->_tempObject) { // if we haven't delt with an error, continue with the firmware update
|
||||
if (Update.write(data, len) != len) {
|
||||
handleError(request, 500); // internal error, failed
|
||||
return;
|
||||
}
|
||||
if (final && !Update.end(true)) {
|
||||
handleError(request, 500); // internal error, failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
// did we just complete uploading a json file?
|
||||
if (request->_tempFile) {
|
||||
request->_tempFile.close(); // close the file handle as the upload is now done
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop. We use pending for the Web's RestartMonitor
|
||||
return;
|
||||
}
|
||||
|
||||
// check if it was a firmware upgrade
|
||||
// if no error, send the success response as a JSON
|
||||
if (_is_firmware && !request->_tempObject) {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop. We use pending for the Web's RestartMonitor
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(_md5.data()) == _md5.size() - 1) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["md5"] = _md5.data();
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
handleError(request, 500);
|
||||
}
|
||||
|
||||
void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
|
||||
// if we have had an error already, do nothing
|
||||
if (request->_tempObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send the error code to the client and record the error code in the temp object
|
||||
AsyncWebServerResponse * response = request->beginResponse(code);
|
||||
request->send(response);
|
||||
|
||||
// check for invalid extension and immediately kill the connection, which will through an error
|
||||
// that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections
|
||||
if (code == 406) {
|
||||
request->client()->close(true);
|
||||
handleEarlyDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void UploadFileService::handleEarlyDisconnect() {
|
||||
_is_firmware = false;
|
||||
Update.abort();
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef UploadFileService_h
|
||||
#define UploadFileService_h
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <LittleFS.h>
|
||||
#include <Update.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#define UPLOAD_FILE_PATH "/rest/uploadFile"
|
||||
|
||||
#define TEMP_FILENAME_PATH "/pre_load.json" // for uploaded json files, handled by System::check_restore()
|
||||
|
||||
class UploadFileService {
|
||||
public:
|
||||
UploadFileService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
SecurityManager * _securityManager;
|
||||
bool _is_firmware;
|
||||
std::array<char, 33> _md5;
|
||||
|
||||
void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
|
||||
void uploadComplete(AsyncWebServerRequest * request);
|
||||
void handleError(AsyncWebServerRequest * request, int code);
|
||||
|
||||
void handleEarlyDisconnect();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "WiFiScanner.h"
|
||||
|
||||
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
server->on(SCAN_NETWORKS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { scanNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(LIST_NETWORKS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { listNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
};
|
||||
|
||||
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
|
||||
request->send(202); // special code to indicate scan in progress
|
||||
|
||||
if (WiFi.scanComplete() != -1) {
|
||||
WiFi.scanDelete();
|
||||
WiFi.scanNetworks(true);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
|
||||
const int numNetworks = WiFi.scanComplete();
|
||||
if (numNetworks > -1) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray networks = root["networks"].to<JsonArray>();
|
||||
for (uint8_t i = 0; i < numNetworks; i++) {
|
||||
JsonObject network = networks.add<JsonObject>();
|
||||
network["rssi"] = WiFi.RSSI(i);
|
||||
network["ssid"] = WiFi.SSID(i);
|
||||
network["bssid"] = WiFi.BSSIDstr(i);
|
||||
network["channel"] = WiFi.channel(i);
|
||||
network["encryption_type"] = static_cast<uint8_t>(WiFi.encryptionType(i));
|
||||
}
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
} else if (numNetworks == -1) {
|
||||
request->send(202); // special code to indicate scan in progress
|
||||
} else {
|
||||
scanNetworks(request);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef WiFiScanner_h
|
||||
#define WiFiScanner_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
|
||||
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
|
||||
|
||||
class WiFiScanner {
|
||||
public:
|
||||
WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void scanNetworks(AsyncWebServerRequest * request);
|
||||
void listNetworks(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -26,13 +26,6 @@ SOFTWARE.
|
||||
#include <map>
|
||||
#include "semver200.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp" // for logging
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// disable symbol name too long warning
|
||||
#pragma warning(disable : 4503)
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace version {
|
||||
@@ -57,7 +50,7 @@ inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) {
|
||||
}
|
||||
|
||||
inline void Parse_error(const std::string & s) {
|
||||
emsesp::EMSESP::logger().err("parse error: %s", s.c_str());
|
||||
// emsesp::EMSESP::logger().err("parse error: %s", s.c_str());
|
||||
}
|
||||
|
||||
/// Advance parser state machine by a single step.
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
#include "uuid/syslog.h"
|
||||
|
||||
#include "../../../src/emsesp.h"
|
||||
|
||||
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
// time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
|
||||
@@ -233,8 +231,8 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
// Added for EMS-ESP
|
||||
// check for Ethernet too. This assumes the network has already started.
|
||||
if (time_good_ || emsesp::EMSESP::system_.network_connected()) {
|
||||
// if (time_good_ || emsesp::EMSESP::system_.network_connected()) {
|
||||
if (time_good_) {
|
||||
#if UUID_SYSLOG_HAVE_GETTIMEOFDAY
|
||||
if (gettimeofday(&time_, nullptr) != 0) {
|
||||
time_.tv_sec = (time_t)-1;
|
||||
@@ -364,9 +362,9 @@ bool SyslogService::can_transmit() {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!emsesp::EMSESP::system_.network_connected()) {
|
||||
return false;
|
||||
}
|
||||
// if (!emsesp::EMSESP::system_.network_connected()) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
const uint64_t now = uuid::get_uptime_ms();
|
||||
uint64_t message_delay = UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY;
|
||||
|
||||
Reference in New Issue
Block a user