This commit is contained in:
40
.gitea/workflows/build.yaml
Normal file
40
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Build
|
||||
run-name: Build bin
|
||||
on: [ push ]
|
||||
|
||||
env:
|
||||
name: ${{ github.ref_name }}@${{ GITHUB_RUN_NUMBER }}
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}"
|
||||
- name: Get code
|
||||
uses: actions/checkout@v4
|
||||
# uses: http://server.jiang1446.i234.me:3000/jiang/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
- name: Build
|
||||
run: |
|
||||
cd ${{ gitea.workspace }} && make
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
- run: export name=6666
|
||||
- run: echo "🍏 ${{ github.ref_name }}@V${{ github.RUN_NUMBER }}."
|
||||
- name: Use Go Action
|
||||
id: use-go-action
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
# uses: http://server.jiang1446.i234.me:3000/jiang/gitea-release-action@v1
|
||||
env:
|
||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||
with:
|
||||
name: ${{ github.ref_name }}@V${{ github.RUN_NUMBER }}
|
||||
tag_name: ${{ github.ref_name }}@V${{ github.RUN_NUMBER }}
|
||||
# body: ${{ github.head_ref }}
|
||||
# prerelease: true
|
||||
md5sum: true
|
||||
sha256sum: true
|
||||
files: |-
|
||||
.pio/build/esp32solo1/firmware.bin
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"string": "cpp",
|
||||
"array": "cpp",
|
||||
"memory": "cpp",
|
||||
"functional": "cpp",
|
||||
"tuple": "cpp",
|
||||
"utility": "cpp",
|
||||
"deque": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"string_view": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"thread": "cpp",
|
||||
"compare": "cpp",
|
||||
"ratio": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"istream": "cpp"
|
||||
}
|
||||
}
|
||||
17
Makefile
Normal file
17
Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
datasdir := $(HOME)/.make/
|
||||
pio := $(HOME)/.platformio/penv/bin/pio
|
||||
ttydown := /dev/ttyUSB0
|
||||
|
||||
all : $(datasdir)
|
||||
$(pio) run
|
||||
|
||||
$(datasdir) :
|
||||
sudo echo
|
||||
curl -#fL https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -o get-platformio.py
|
||||
sudo apt install -y python3-venv
|
||||
python3 get-platformio.py
|
||||
mkdir $(datasdir)
|
||||
|
||||
clean :
|
||||
$(pio) run --target clean
|
||||
|
||||
14
include/Http_Server.h
Normal file
14
include/Http_Server.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef _Http_Server_H_
|
||||
#define _Http_Server_H_
|
||||
|
||||
class Http_Server
|
||||
{
|
||||
public:
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
};
|
||||
|
||||
extern Http_Server httpserver;
|
||||
|
||||
#endif
|
||||
38
include/README
Normal file
38
include/README
Normal file
@@ -0,0 +1,38 @@
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
23
include/user.h
Normal file
23
include/user.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef _user_H_
|
||||
#define _user_H_
|
||||
|
||||
#include "HLW8012.h"
|
||||
|
||||
extern char reboot_;
|
||||
extern char user_;
|
||||
|
||||
extern HLW8012 hlw8012;
|
||||
extern unsigned int getVoltage;
|
||||
extern double getCurrent;
|
||||
extern unsigned int getActivePower;
|
||||
extern unsigned int getApparentPower;
|
||||
extern double getPowerFactor;
|
||||
|
||||
extern String host;
|
||||
|
||||
extern uint8_t if_mode;
|
||||
extern int fan_mode;
|
||||
extern int swing_mode;
|
||||
extern int temp_mode;
|
||||
|
||||
#endif
|
||||
16
include/user_mqtt.h
Normal file
16
include/user_mqtt.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef _USER_MQTT__H_
|
||||
#define _USER_MQTT__H_
|
||||
|
||||
class MQTT
|
||||
{
|
||||
|
||||
public:
|
||||
void setup();
|
||||
void loop();
|
||||
void pust();
|
||||
void irpost_();
|
||||
};
|
||||
|
||||
extern MQTT mqtt;
|
||||
|
||||
#endif
|
||||
45
lib/README
Normal file
45
lib/README
Normal file
@@ -0,0 +1,45 @@
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
38
lib/WebServer/keywords.txt
Normal file
38
lib/WebServer/keywords.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
WebServer KEYWORD1
|
||||
WebServerSecure KEYWORD1
|
||||
HTTPMethod KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
handleClient KEYWORD2
|
||||
on KEYWORD2
|
||||
addHandler KEYWORD2
|
||||
uri KEYWORD2
|
||||
method KEYWORD2
|
||||
client KEYWORD2
|
||||
send KEYWORD2
|
||||
arg KEYWORD2
|
||||
argName KEYWORD2
|
||||
args KEYWORD2
|
||||
hasArg KEYWORD2
|
||||
onNotFound KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
HTTP_GET LITERAL1
|
||||
HTTP_POST LITERAL1
|
||||
HTTP_ANY LITERAL1
|
||||
CONTENT_LENGTH_UNKNOWN LITERAL1
|
||||
9
lib/WebServer/library.properties
Normal file
9
lib/WebServer/library.properties
Normal file
@@ -0,0 +1,9 @@
|
||||
name=WebServer
|
||||
version=2.0.0
|
||||
author=Ivan Grokhotkov
|
||||
maintainer=Ivan Grokhtkov <ivan@esp8266.com>
|
||||
sentence=Simple web server library
|
||||
paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time.
|
||||
category=Communication
|
||||
url=
|
||||
architectures=esp32
|
||||
9
lib/WebServer/src/HTTP_Method.h
Normal file
9
lib/WebServer/src/HTTP_Method.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef _HTTP_Method_H_
|
||||
#define _HTTP_Method_H_
|
||||
|
||||
#include "http_parser.h"
|
||||
|
||||
typedef enum http_method HTTPMethod;
|
||||
#define HTTP_ANY (HTTPMethod)(255)
|
||||
|
||||
#endif /* _HTTP_Method_H_ */
|
||||
605
lib/WebServer/src/Parsing.cpp
Normal file
605
lib/WebServer/src/Parsing.cpp
Normal file
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
Parsing.cpp - HTTP request parsing.
|
||||
|
||||
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp32-hal-log.h>
|
||||
#include "NetworkServer.h"
|
||||
#include "NetworkClient.h"
|
||||
#include "WebServer.h"
|
||||
#include "detail/mimetable.h"
|
||||
|
||||
#ifndef WEBSERVER_MAX_POST_ARGS
|
||||
#define WEBSERVER_MAX_POST_ARGS 32
|
||||
#endif
|
||||
|
||||
#define __STR(a) #a
|
||||
#define _STR(a) __STR(a)
|
||||
static const char *_http_method_str[] = {
|
||||
#define XX(num, name, string) _STR(name),
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
static const char Content_Type[] PROGMEM = "Content-Type";
|
||||
static const char filename[] PROGMEM = "filename";
|
||||
|
||||
static char *readBytesWithTimeout(NetworkClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
|
||||
char *buf = nullptr;
|
||||
dataLength = 0;
|
||||
while (dataLength < maxLength) {
|
||||
int tries = timeout_ms;
|
||||
size_t newLength;
|
||||
while (!(newLength = client.available()) && tries--) {
|
||||
delay(1);
|
||||
}
|
||||
if (!newLength) {
|
||||
break;
|
||||
}
|
||||
if (!buf) {
|
||||
buf = (char *)malloc(newLength + 1);
|
||||
if (!buf) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
|
||||
if (!newBuf) {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
buf = newBuf;
|
||||
}
|
||||
client.readBytes(buf + dataLength, newLength);
|
||||
dataLength += newLength;
|
||||
buf[dataLength] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool WebServer::_parseRequest(NetworkClient &client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
//reset header value
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
_currentHeaders[i].value = String();
|
||||
}
|
||||
|
||||
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||
// Retrieve the "/path" part by finding the spaces
|
||||
int addr_start = req.indexOf(' ');
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
log_e("Invalid request: %s", req.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
String url = req.substring(addr_start + 1, addr_end);
|
||||
String versionEnd = req.substring(addr_end + 8);
|
||||
_currentVersion = atoi(versionEnd.c_str());
|
||||
String searchStr = "";
|
||||
int hasSearch = url.indexOf('?');
|
||||
if (hasSearch != -1) {
|
||||
searchStr = url.substring(hasSearch + 1);
|
||||
url = url.substring(0, hasSearch);
|
||||
}
|
||||
_currentUri = url;
|
||||
_chunked = false;
|
||||
_clientContentLength = 0; // not known yet, or invalid
|
||||
|
||||
HTTPMethod method = HTTP_ANY;
|
||||
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||
for (size_t i = 0; i < num_methods; i++) {
|
||||
if (methodStr == _http_method_str[i]) {
|
||||
method = (HTTPMethod)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (method == HTTP_ANY) {
|
||||
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||
return false;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
||||
|
||||
//attach handler
|
||||
RequestHandler *handler;
|
||||
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||
if (handler->canHandle(*this, _currentMethod, _currentUri)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_currentHandler = handler;
|
||||
|
||||
String formData;
|
||||
// below is needed only when POST type request
|
||||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
|
||||
String boundaryStr;
|
||||
String headerName;
|
||||
String headerValue;
|
||||
bool isForm = false;
|
||||
bool isEncoded = false;
|
||||
//parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") {
|
||||
break; //no moar headers
|
||||
}
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 1);
|
||||
headerValue.trim();
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
using namespace mime;
|
||||
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
|
||||
isForm = false;
|
||||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
|
||||
isForm = false;
|
||||
isEncoded = true;
|
||||
} else if (headerValue.startsWith(F("multipart/"))) {
|
||||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
||||
boundaryStr.replace("\"", "");
|
||||
isForm = true;
|
||||
}
|
||||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
|
||||
_clientContentLength = headerValue.toInt();
|
||||
} else if (headerName.equalsIgnoreCase(F("Host"))) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isForm && _currentHandler && _currentHandler->canRaw(*this, _currentUri)) {
|
||||
log_v("Parse raw");
|
||||
_currentRaw.reset(new HTTPRaw());
|
||||
_currentRaw->status = RAW_START;
|
||||
_currentRaw->totalSize = 0;
|
||||
_currentRaw->currentSize = 0;
|
||||
log_v("Start Raw");
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
_currentRaw->status = RAW_WRITE;
|
||||
|
||||
while (_currentRaw->totalSize < _clientContentLength) {
|
||||
_currentRaw->currentSize = client.readBytes(_currentRaw->buf, HTTP_RAW_BUFLEN);
|
||||
_currentRaw->totalSize += _currentRaw->currentSize;
|
||||
if (_currentRaw->currentSize == 0) {
|
||||
_currentRaw->status = RAW_ABORTED;
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
return false;
|
||||
}
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
}
|
||||
_currentRaw->status = RAW_END;
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
log_v("Finish Raw");
|
||||
} else if (!isForm) {
|
||||
size_t plainLength;
|
||||
char *plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if (plainLength < _clientContentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
}
|
||||
if (_clientContentLength > 0) {
|
||||
if (isEncoded) {
|
||||
//url encoded form
|
||||
if (searchStr != "") {
|
||||
searchStr += '&';
|
||||
}
|
||||
searchStr += plainBuf;
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
if (!isEncoded) {
|
||||
//plain post json or other data
|
||||
RequestArgument &arg = _currentArgs[_currentArgCount++];
|
||||
arg.key = F("plain");
|
||||
arg.value = String(plainBuf);
|
||||
}
|
||||
|
||||
log_v("Plain: %s", plainBuf);
|
||||
free(plainBuf);
|
||||
} else {
|
||||
// No content - but we can still have arguments in the URL.
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
} else {
|
||||
// it IS a form
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, _clientContentLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String headerName;
|
||||
String headerValue;
|
||||
//parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") {
|
||||
break; //no moar headers
|
||||
}
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 2);
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase("Host")) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
client.flush();
|
||||
|
||||
log_v("Request: %s", url.c_str());
|
||||
log_v(" Arguments: %s", searchStr.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
|
||||
for (int i = 0; i < _headerKeysCount; i++) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||
_currentHeaders[i].value = headerValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::_parseArguments(String data) {
|
||||
log_v("args: %s", data.c_str());
|
||||
if (_currentArgs) {
|
||||
delete[] _currentArgs;
|
||||
}
|
||||
_currentArgs = 0;
|
||||
if (data.length() == 0) {
|
||||
_currentArgCount = 0;
|
||||
_currentArgs = new RequestArgument[1];
|
||||
return;
|
||||
}
|
||||
_currentArgCount = 1;
|
||||
|
||||
for (int i = 0; i < (int)data.length();) {
|
||||
i = data.indexOf('&', i);
|
||||
if (i == -1) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
++_currentArgCount;
|
||||
}
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
|
||||
_currentArgs = new RequestArgument[_currentArgCount + 1];
|
||||
int pos = 0;
|
||||
int iarg;
|
||||
for (iarg = 0; iarg < _currentArgCount;) {
|
||||
int equal_sign_index = data.indexOf('=', pos);
|
||||
int next_arg_index = data.indexOf('&', pos);
|
||||
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||
log_e("arg missing value: %d", iarg);
|
||||
if (next_arg_index == -1) {
|
||||
break;
|
||||
}
|
||||
pos = next_arg_index + 1;
|
||||
continue;
|
||||
}
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
||||
++iarg;
|
||||
if (next_arg_index == -1) {
|
||||
break;
|
||||
}
|
||||
pos = next_arg_index + 1;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
}
|
||||
|
||||
void WebServer::_uploadWriteByte(uint8_t b) {
|
||||
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->currentSize = 0;
|
||||
}
|
||||
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||
}
|
||||
|
||||
int WebServer::_uploadReadByte(NetworkClient &client) {
|
||||
int res = client.read();
|
||||
|
||||
if (res < 0) {
|
||||
// keep trying until you either read a valid byte or timeout
|
||||
const unsigned long startMillis = millis();
|
||||
const long timeoutIntervalMillis = client.getTimeout();
|
||||
bool timedOut = false;
|
||||
for (;;) {
|
||||
if (!client.connected()) {
|
||||
return -1;
|
||||
}
|
||||
// loosely modeled after blinkWithoutDelay pattern
|
||||
while (!timedOut && !client.available() && client.connected()) {
|
||||
delay(2);
|
||||
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
||||
}
|
||||
|
||||
res = client.read();
|
||||
if (res >= 0) {
|
||||
return res; // exit on a valid read
|
||||
}
|
||||
// NOTE: it is possible to get here and have all of the following
|
||||
// assertions hold true
|
||||
//
|
||||
// -- client.available() > 0
|
||||
// -- client.connected == true
|
||||
// -- res == -1
|
||||
//
|
||||
// a simple retry strategy overcomes this which is to say the
|
||||
// assertion is not permanent, but the reason that this works
|
||||
// is elusive, and possibly indicative of a more subtle underlying
|
||||
// issue
|
||||
|
||||
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
||||
if (timedOut) {
|
||||
return res; // exit on a timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool WebServer::_parseForm(NetworkClient &client, String boundary, uint32_t len) {
|
||||
(void)len;
|
||||
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
|
||||
String line;
|
||||
int retry = 0;
|
||||
do {
|
||||
line = client.readStringUntil('\r');
|
||||
++retry;
|
||||
} while (line.length() == 0 && retry < 3);
|
||||
|
||||
client.readStringUntil('\n');
|
||||
//start reading the form
|
||||
if (line == ("--" + boundary)) {
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
}
|
||||
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
||||
_postArgsLen = 0;
|
||||
while (1) {
|
||||
String argName;
|
||||
String argValue;
|
||||
String argType;
|
||||
String argFilename;
|
||||
bool argIsFile = false;
|
||||
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
|
||||
int nameStart = line.indexOf('=');
|
||||
if (nameStart != -1) {
|
||||
argName = line.substring(nameStart + 2);
|
||||
nameStart = argName.indexOf('=');
|
||||
if (nameStart == -1) {
|
||||
argName = argName.substring(0, argName.length() - 1);
|
||||
} else {
|
||||
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
|
||||
argName = argName.substring(0, argName.indexOf('"'));
|
||||
argIsFile = true;
|
||||
log_v("PostArg FileName: %s", argFilename.c_str());
|
||||
//use GET to set the filename if uploading using blob
|
||||
if (argFilename == F("blob") && hasArg(FPSTR(filename))) {
|
||||
argFilename = arg(FPSTR(filename));
|
||||
}
|
||||
}
|
||||
log_v("PostArg Name: %s", argName.c_str());
|
||||
using namespace mime;
|
||||
argType = FPSTR(mimeTable[txt].mimeType);
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
while (line.length() > 0) {
|
||||
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
argType = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
//skip over any other headers
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
}
|
||||
log_v("PostArg Type: %s", argType.c_str());
|
||||
if (!argIsFile) {
|
||||
while (1) {
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.startsWith("--" + boundary)) {
|
||||
break;
|
||||
}
|
||||
if (argValue.length() > 0) {
|
||||
argValue += "\n";
|
||||
}
|
||||
argValue += line;
|
||||
}
|
||||
log_v("PostArg Value: %s", argValue.c_str());
|
||||
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = argName;
|
||||
arg.value = argValue;
|
||||
|
||||
if (line == ("--" + boundary + "--")) {
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
||||
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_currentUpload.reset(new HTTPUpload());
|
||||
_currentUpload->status = UPLOAD_FILE_START;
|
||||
_currentUpload->name = argName;
|
||||
_currentUpload->filename = argFilename;
|
||||
_currentUpload->type = argType;
|
||||
_currentUpload->totalSize = 0;
|
||||
_currentUpload->len = len;
|
||||
_currentUpload->currentSize = 0;
|
||||
log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str());
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||
|
||||
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
|
||||
char fastBoundary[fastBoundaryLen];
|
||||
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
|
||||
int boundaryPtr = 0;
|
||||
while (true) {
|
||||
int ret = _uploadReadByte(client);
|
||||
if (ret < 0) {
|
||||
// Unexpected, we should have had data available per above
|
||||
return _parseFormUploadAborted();
|
||||
}
|
||||
char in = (char)ret;
|
||||
if (in == fastBoundary[boundaryPtr]) {
|
||||
// The input matched the current expected character, advance and possibly exit this file
|
||||
boundaryPtr++;
|
||||
if (boundaryPtr == fastBoundaryLen - 1) {
|
||||
// We read the whole boundary line, we're done here!
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
|
||||
for (int i = 0; i < boundaryPtr; i++) {
|
||||
_uploadWriteByte(fastBoundary[i]);
|
||||
}
|
||||
if (in == fastBoundary[0]) {
|
||||
// This could be the start of the real end, mark it so and don't emit/skip it
|
||||
boundaryPtr = 1;
|
||||
} else {
|
||||
// Not the 1st char of our pattern, so emit and ignore
|
||||
_uploadWriteByte(in);
|
||||
boundaryPtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Found the boundary string, finish processing this file upload
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), (int)_currentUpload->totalSize);
|
||||
if (!client.connected()) {
|
||||
return _parseFormUploadAborted();
|
||||
}
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line == "--") { // extra two dashes mean we reached the end of all form fields
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int iarg;
|
||||
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount) ? (WEBSERVER_MAX_POST_ARGS - _postArgsLen) : _currentArgCount;
|
||||
for (iarg = 0; iarg < totalArgs; iarg++) {
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = _currentArgs[iarg].key;
|
||||
arg.value = _currentArgs[iarg].value;
|
||||
}
|
||||
if (_currentArgs) {
|
||||
delete[] _currentArgs;
|
||||
}
|
||||
_currentArgs = new RequestArgument[_postArgsLen];
|
||||
for (iarg = 0; iarg < _postArgsLen; iarg++) {
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = _postArgs[iarg].key;
|
||||
arg.value = _postArgs[iarg].value;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
_postArgs = nullptr;
|
||||
_postArgsLen = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
log_e("Error: line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::urlDecode(const String &text) {
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len) {
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
} else {
|
||||
if (encodedChar == '+') {
|
||||
decodedChar = ' ';
|
||||
} else {
|
||||
decodedChar = encodedChar; // normal ascii char
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool WebServer::_parseFormUploadAborted() {
|
||||
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
29
lib/WebServer/src/Uri.h
Normal file
29
lib/WebServer/src/Uri.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef URI_H
|
||||
#define URI_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
class Uri {
|
||||
|
||||
protected:
|
||||
const String _uri;
|
||||
|
||||
public:
|
||||
Uri(const char *uri) : _uri(uri) {}
|
||||
Uri(const String &uri) : _uri(uri) {}
|
||||
Uri(const __FlashStringHelper *uri) : _uri((const char *)uri) {}
|
||||
virtual ~Uri() {}
|
||||
|
||||
virtual Uri *clone() const {
|
||||
return new Uri(_uri);
|
||||
};
|
||||
|
||||
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
|
||||
|
||||
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
|
||||
return _uri == requestUri;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
865
lib/WebServer/src/WebServer.cpp
Normal file
865
lib/WebServer/src/WebServer.cpp
Normal file
@@ -0,0 +1,865 @@
|
||||
/*
|
||||
WebServer.cpp - Dead simple web-server.
|
||||
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp32-hal-log.h>
|
||||
#include <libb64/cdecode.h>
|
||||
#include <libb64/cencode.h>
|
||||
#include "esp_random.h"
|
||||
#include "NetworkServer.h"
|
||||
#include "NetworkClient.h"
|
||||
#include "WebServer.h"
|
||||
#include "FS.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
#include "MD5Builder.h"
|
||||
#include "SHA1Builder.h"
|
||||
#include "base64.h"
|
||||
|
||||
static const char AUTHORIZATION_HEADER[] = "Authorization";
|
||||
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||
static const char WWW_Authenticate[] = "WWW-Authenticate";
|
||||
static const char Content_Length[] = "Content-Length";
|
||||
static const char ETAG_HEADER[] = "If-None-Match";
|
||||
|
||||
WebServer::WebServer(IPAddress addr, int port)
|
||||
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE), _statusChange(0), _nullDelay(true),
|
||||
_currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr), _currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr),
|
||||
_headerKeysCount(0), _currentHeaders(nullptr), _contentLength(0), _clientContentLength(0), _chunked(false) {
|
||||
log_v("WebServer::Webserver(addr=%s, port=%d)", addr.toString().c_str(), port);
|
||||
}
|
||||
|
||||
WebServer::WebServer(int port)
|
||||
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE), _statusChange(0), _nullDelay(true),
|
||||
_currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr), _currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr),
|
||||
_headerKeysCount(0), _currentHeaders(nullptr), _contentLength(0), _clientContentLength(0), _chunked(false) {
|
||||
log_v("WebServer::Webserver(port=%d)", port);
|
||||
}
|
||||
|
||||
WebServer::~WebServer() {
|
||||
_server.close();
|
||||
if (_currentHeaders) {
|
||||
delete[] _currentHeaders;
|
||||
}
|
||||
RequestHandler *handler = _firstHandler;
|
||||
while (handler) {
|
||||
RequestHandler *next = handler->next();
|
||||
delete handler;
|
||||
handler = next;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::begin() {
|
||||
close();
|
||||
_server.begin();
|
||||
_server.setNoDelay(true);
|
||||
}
|
||||
|
||||
void WebServer::begin(uint16_t port) {
|
||||
close();
|
||||
_server.begin(port);
|
||||
_server.setNoDelay(true);
|
||||
}
|
||||
|
||||
String WebServer::_extractParam(String &authReq, const String ¶m, const char delimit) {
|
||||
int _begin = authReq.indexOf(param);
|
||||
if (_begin == -1) {
|
||||
return "";
|
||||
}
|
||||
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||
}
|
||||
|
||||
static String md5str(String &in) {
|
||||
MD5Builder md5 = MD5Builder();
|
||||
md5.begin();
|
||||
md5.add(in);
|
||||
md5.calculate();
|
||||
return md5.toString();
|
||||
}
|
||||
|
||||
bool WebServer::authenticateBasicSHA1(const char *_username, const char *_sha1Base64orHex) {
|
||||
return WebServer::authenticate([_username, _sha1Base64orHex](HTTPAuthMethod mode, String username, String params[]) -> String * {
|
||||
// rather than work on a password to compare with; we take the sha1 of the
|
||||
// password received over the wire and compare that to the base64 encoded
|
||||
// sha1 passed as _sha1base64. That way there is no need to have a
|
||||
// plaintext password in the code/binary (though note that SHA1 is well
|
||||
// past its retirement age). When that matches - we `cheat' by returning
|
||||
// the password we got in the first place; so the normal BasicAuth
|
||||
// can be completed. Note that this cannot work for a digest auth -
|
||||
// as there the password in the clear is part of the calculation.
|
||||
|
||||
if (params == nullptr) {
|
||||
log_e("Something went wrong. params is NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t sha1[20];
|
||||
char sha1calc[48]; // large enough for base64 and Hex representation
|
||||
String ret;
|
||||
SHA1Builder sha_builder;
|
||||
base64 b64;
|
||||
|
||||
log_v("Trying to authenticate user %s using SHA1.", username.c_str());
|
||||
sha_builder.begin();
|
||||
sha_builder.add((uint8_t *)params[0].c_str(), params[0].length());
|
||||
sha_builder.calculate();
|
||||
sha_builder.getBytes(sha1);
|
||||
|
||||
// we can either decode _sha1base64orHex and then compare the 20 bytes;
|
||||
// or encode the sha we calculated. We pick the latter as encoding of a
|
||||
// fixed array of 20 bytes is safer than operating on something external.
|
||||
if (strlen(_sha1Base64orHex) == 20 * 2) { // 2 chars per byte
|
||||
sha_builder.bytes2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1));
|
||||
log_v("Calculated SHA1 in hex: %s", sha1calc);
|
||||
} else {
|
||||
ret = b64.encode(sha1, sizeof(sha1));
|
||||
ret.toCharArray(sha1calc, sizeof(sha1calc));
|
||||
log_v("Calculated SHA1 in base64: %s", sha1calc);
|
||||
}
|
||||
|
||||
return ((username.equalsConstantTime(_username)) && (String((char *)sha1calc).equalsConstantTime(_sha1Base64orHex))
|
||||
&& (mode == BASIC_AUTH) /* to keep things somewhat time constant. */
|
||||
)
|
||||
? new String(params[0])
|
||||
: NULL;
|
||||
});
|
||||
}
|
||||
|
||||
bool WebServer::authenticate(const char *_username, const char *_password) {
|
||||
return WebServer::authenticate([_username, _password](HTTPAuthMethod mode, String username, String params[]) -> String * {
|
||||
return username.equalsConstantTime(_username) ? new String(_password) : NULL;
|
||||
});
|
||||
}
|
||||
|
||||
bool WebServer::authenticate(THandlerFunctionAuthCheck fn) {
|
||||
if (!hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
|
||||
if (authReq.startsWith(AuthTypeBasic)) {
|
||||
log_v("Trying to authenticate using Basic Auth");
|
||||
bool ret = false;
|
||||
|
||||
authReq = authReq.substring(6); // length of AuthTypeBasic including the space at the end.
|
||||
authReq.trim();
|
||||
|
||||
/* base64 encoded string is always shorter (or equal) in length */
|
||||
char *decoded = (authReq.length() < HTTP_MAX_BASIC_AUTH_LEN) ? new char[authReq.length()] : NULL;
|
||||
if (decoded) {
|
||||
char *p;
|
||||
if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded, ':')) && p) {
|
||||
authReq = "";
|
||||
/* Note: rfc7617 guarantees that there will not be an escaped colon in the username itself.
|
||||
* Note: base64_decode_chars() guarantees a terminating \0
|
||||
*/
|
||||
*p = '\0';
|
||||
char *_username = decoded, *_password = p + 1;
|
||||
String params[] = {_password, _srealm};
|
||||
String *password = fn(BASIC_AUTH, _username, params);
|
||||
|
||||
if (password) {
|
||||
ret = password->equalsConstantTime(_password);
|
||||
// we're more concerned about the password; as the attacker already
|
||||
// knows the _pasword. Arduino's string handling is simple; it reallocs
|
||||
// even when smaller; so a memset is enough (no capacity/size).
|
||||
memset((void *)password->c_str(), 0, password->length());
|
||||
delete password;
|
||||
}
|
||||
}
|
||||
delete[] decoded;
|
||||
}
|
||||
authReq = "";
|
||||
log_v("Authentication %s", ret ? "Success" : "Failed");
|
||||
return ret;
|
||||
} else if (authReq.startsWith(AuthTypeDigest)) {
|
||||
log_v("Trying to authenticate using Digest Auth");
|
||||
authReq = authReq.substring(7);
|
||||
log_v("%s", authReq.c_str());
|
||||
|
||||
// extracting required parameters for RFC 2069 simpler Digest
|
||||
String _username = _extractParam(authReq, F("username=\""), '\"');
|
||||
String _realm = _extractParam(authReq, F("realm=\""), '\"');
|
||||
String _uri = _extractParam(authReq, F("uri=\""), '\"');
|
||||
if (!_username.length()) {
|
||||
goto exf;
|
||||
}
|
||||
|
||||
String params[] = {_realm, _uri};
|
||||
String *password = fn(DIGEST_AUTH, _username, params);
|
||||
if (!password) {
|
||||
goto exf;
|
||||
}
|
||||
|
||||
String _H1 = md5str(String(_username) + ':' + _realm + ':' + *password);
|
||||
// we're extra concerned; as digest request us to know the password
|
||||
// in the clear.
|
||||
memset((void *)password->c_str(), 0, password->length());
|
||||
delete password;
|
||||
_username = "";
|
||||
|
||||
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
|
||||
String _response = _extractParam(authReq, F("response=\""), '\"');
|
||||
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
|
||||
|
||||
if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) {
|
||||
goto exf;
|
||||
}
|
||||
|
||||
if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
|
||||
goto exf;
|
||||
}
|
||||
|
||||
// parameters for the RFC 2617 newer Digest
|
||||
String _nc, _cnonce;
|
||||
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_nc = _extractParam(authReq, F("nc="), ',');
|
||||
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
|
||||
}
|
||||
|
||||
log_v("Hash of user:realm:pass=%s", _H1.c_str());
|
||||
String _H2 = "";
|
||||
if (_currentMethod == HTTP_GET) {
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_POST) {
|
||||
_H2 = md5str(String(F("POST:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_PUT) {
|
||||
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_DELETE) {
|
||||
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||
} else {
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}
|
||||
log_v("Hash of GET:uri=%s", _H2.c_str());
|
||||
String _responsecheck = "";
|
||||
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||
} else {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||
}
|
||||
authReq = "";
|
||||
|
||||
log_v("The Proper response=%s", _responsecheck.c_str());
|
||||
bool ret = _response == _responsecheck;
|
||||
log_v("Authentication %s", ret ? "Success" : "Failed");
|
||||
return ret;
|
||||
} else if (authReq.length()) {
|
||||
// OTHER_AUTH
|
||||
log_v("Trying to authenticate using Other Auth, authReq=%s", authReq.c_str());
|
||||
String *ret = fn(OTHER_AUTH, authReq, {});
|
||||
if (ret) {
|
||||
log_v("Authentication Success");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
exf:
|
||||
authReq = "";
|
||||
log_v("Authentication Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::_getRandomHexString() {
|
||||
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
sprintf(buffer + (i * 8), "%08lx", esp_random());
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
void WebServer::requestAuthentication(HTTPAuthMethod mode, const char *realm, const String &authFailMsg) {
|
||||
if (realm == NULL) {
|
||||
_srealm = String(F("Login Required"));
|
||||
} else {
|
||||
_srealm = String(realm);
|
||||
}
|
||||
if (mode == BASIC_AUTH) {
|
||||
sendHeader(String(FPSTR(WWW_Authenticate)), AuthTypeBasic + String(F(" realm=\"")) + _srealm + String(F("\"")));
|
||||
} else {
|
||||
_snonce = _getRandomHexString();
|
||||
_sopaque = _getRandomHexString();
|
||||
sendHeader(
|
||||
String(FPSTR(WWW_Authenticate)), AuthTypeDigest + String(F(" realm=\"")) + _srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce
|
||||
+ String(F("\", opaque=\"")) + _sopaque + String(F("\""))
|
||||
);
|
||||
}
|
||||
using namespace mime;
|
||||
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
|
||||
}
|
||||
|
||||
RequestHandler &WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
|
||||
return on(uri, HTTP_ANY, handler);
|
||||
}
|
||||
|
||||
RequestHandler &WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
|
||||
return on(uri, method, fn, _fileUploadHandler);
|
||||
}
|
||||
|
||||
RequestHandler &WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
|
||||
FunctionRequestHandler *handler = new FunctionRequestHandler(fn, ufn, uri, method);
|
||||
_addRequestHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
bool WebServer::removeRoute(const char *uri) {
|
||||
return removeRoute(String(uri), HTTP_ANY);
|
||||
}
|
||||
|
||||
bool WebServer::removeRoute(const char *uri, HTTPMethod method) {
|
||||
return removeRoute(String(uri), method);
|
||||
}
|
||||
|
||||
bool WebServer::removeRoute(const String &uri) {
|
||||
return removeRoute(uri, HTTP_ANY);
|
||||
}
|
||||
|
||||
bool WebServer::removeRoute(const String &uri, HTTPMethod method) {
|
||||
bool anyHandlerRemoved = false;
|
||||
RequestHandler *handler = _firstHandler;
|
||||
RequestHandler *previousHandler = nullptr;
|
||||
|
||||
while (handler) {
|
||||
if (handler->canHandle(method, uri)) {
|
||||
if (_removeRequestHandler(handler)) {
|
||||
anyHandlerRemoved = true;
|
||||
// Move to the next handler
|
||||
if (previousHandler) {
|
||||
handler = previousHandler->next();
|
||||
} else {
|
||||
handler = _firstHandler;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
previousHandler = handler;
|
||||
handler = handler->next();
|
||||
}
|
||||
|
||||
return anyHandlerRemoved;
|
||||
}
|
||||
|
||||
void WebServer::addHandler(RequestHandler *handler) {
|
||||
_addRequestHandler(handler);
|
||||
}
|
||||
|
||||
bool WebServer::removeHandler(RequestHandler *handler) {
|
||||
return _removeRequestHandler(handler);
|
||||
}
|
||||
|
||||
void WebServer::_addRequestHandler(RequestHandler *handler) {
|
||||
if (!_lastHandler) {
|
||||
_firstHandler = handler;
|
||||
_lastHandler = handler;
|
||||
} else {
|
||||
_lastHandler->next(handler);
|
||||
_lastHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
bool WebServer::_removeRequestHandler(RequestHandler *handler) {
|
||||
RequestHandler *current = _firstHandler;
|
||||
RequestHandler *previous = nullptr;
|
||||
|
||||
while (current != nullptr) {
|
||||
if (current == handler) {
|
||||
if (previous == nullptr) {
|
||||
_firstHandler = current->next();
|
||||
} else {
|
||||
previous->next(current->next());
|
||||
}
|
||||
|
||||
if (current == _lastHandler) {
|
||||
_lastHandler = previous;
|
||||
}
|
||||
|
||||
// Delete 'matching' handler
|
||||
delete current;
|
||||
return true;
|
||||
}
|
||||
previous = current;
|
||||
current = current->next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::serveStatic(const char *uri, FS &fs, const char *path, const char *cache_header) {
|
||||
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||
}
|
||||
|
||||
void WebServer::handleClient() {
|
||||
if (_currentStatus == HC_NONE) {
|
||||
_currentClient = _server.accept();
|
||||
if (!_currentClient) {
|
||||
if (_nullDelay) {
|
||||
delay(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log_v("New client: client.localIP()=%s", _currentClient.localIP().toString().c_str());
|
||||
|
||||
_currentStatus = HC_WAIT_READ;
|
||||
_statusChange = millis();
|
||||
}
|
||||
|
||||
bool keepCurrentClient = false;
|
||||
bool callYield = false;
|
||||
|
||||
if (_currentClient.connected()) {
|
||||
switch (_currentStatus) {
|
||||
case HC_NONE:
|
||||
// No-op to avoid C++ compiler warning
|
||||
break;
|
||||
case HC_WAIT_READ:
|
||||
// Wait for data from client to become available
|
||||
if (_currentClient.available()) {
|
||||
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT); /* / 1000 removed, WifiClient setTimeout changed to ms */
|
||||
if (_parseRequest(_currentClient)) {
|
||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||
_handleRequest();
|
||||
|
||||
if (_currentClient.isSSE()) {
|
||||
_currentStatus = HC_WAIT_CLOSE;
|
||||
_statusChange = millis();
|
||||
keepCurrentClient = true;
|
||||
}
|
||||
// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
|
||||
// if (_currentClient.connected()) {
|
||||
// _currentStatus = HC_WAIT_CLOSE;
|
||||
// _statusChange = millis();
|
||||
// keepCurrentClient = true;
|
||||
// }
|
||||
}
|
||||
} else { // !_currentClient.available()
|
||||
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
}
|
||||
callYield = true;
|
||||
}
|
||||
break;
|
||||
case HC_WAIT_CLOSE:
|
||||
if (_currentClient.isSSE()) {
|
||||
// Never close connection
|
||||
_statusChange = millis();
|
||||
}
|
||||
// Wait for client to close the connection
|
||||
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
callYield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepCurrentClient) {
|
||||
_currentClient = NetworkClient();
|
||||
_currentStatus = HC_NONE;
|
||||
_currentUpload.reset();
|
||||
_currentRaw.reset();
|
||||
}
|
||||
|
||||
if (callYield) {
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::close() {
|
||||
_server.close();
|
||||
_currentStatus = HC_NONE;
|
||||
if (!_headerKeysCount) {
|
||||
collectHeaders(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::stop() {
|
||||
close();
|
||||
}
|
||||
|
||||
void WebServer::sendHeader(const String &name, const String &value, bool first) {
|
||||
String headerLine = name;
|
||||
headerLine += F(": ");
|
||||
headerLine += value;
|
||||
headerLine += "\r\n";
|
||||
|
||||
if (first) {
|
||||
_responseHeaders = headerLine + _responseHeaders;
|
||||
} else {
|
||||
_responseHeaders += headerLine;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::setContentLength(const size_t contentLength) {
|
||||
_contentLength = contentLength;
|
||||
}
|
||||
|
||||
void WebServer::enableDelay(boolean value) {
|
||||
_nullDelay = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCORS(boolean value) {
|
||||
_corsEnabled = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCrossOrigin(boolean value) {
|
||||
enableCORS(value);
|
||||
}
|
||||
|
||||
void WebServer::enableETag(bool enable, ETagFunction fn) {
|
||||
_eTagEnabled = enable;
|
||||
_eTagFunction = fn;
|
||||
}
|
||||
|
||||
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
|
||||
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
|
||||
response += String(code);
|
||||
response += ' ';
|
||||
response += _responseCodeToString(code);
|
||||
response += "\r\n";
|
||||
|
||||
using namespace mime;
|
||||
if (!content_type) {
|
||||
content_type = mimeTable[html].mimeType;
|
||||
}
|
||||
|
||||
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
|
||||
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
|
||||
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
|
||||
} else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion) { //HTTP/1.1 or above client
|
||||
//let's do chunked
|
||||
_chunked = true;
|
||||
sendHeader(String(F("Accept-Ranges")), String(F("none")));
|
||||
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
|
||||
}
|
||||
if (_corsEnabled) {
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
|
||||
}
|
||||
sendHeader(String(F("Connection")), String(F("close")));
|
||||
|
||||
response += _responseHeaders;
|
||||
response += "\r\n";
|
||||
_responseHeaders = "";
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const char *content_type, const String &content) {
|
||||
String header;
|
||||
// Can we assume the following?
|
||||
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
if (content.length() == 0) {
|
||||
log_w("content length is zero");
|
||||
}
|
||||
_prepareHeader(header, code, content_type, content.length());
|
||||
_currentClientWrite(header.c_str(), header.length());
|
||||
if (content.length()) {
|
||||
sendContent(content);
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::send(int code, char *content_type, const String &content) {
|
||||
send(code, (const char *)content_type, content);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const String &content_type, const String &content) {
|
||||
send(code, (const char *)content_type.c_str(), content);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const char *content_type, const char *content) {
|
||||
const String passStr = (String)content;
|
||||
if (strlen(content) != passStr.length()) {
|
||||
log_e("String cast failed. Use send_P for long arrays");
|
||||
}
|
||||
send(code, content_type, passStr);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
size_t contentLength = 0;
|
||||
|
||||
if (content != NULL) {
|
||||
contentLength = strlen_P(content);
|
||||
}
|
||||
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void *)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char *)type, contentLength);
|
||||
_currentClientWrite(header.c_str(), header.length());
|
||||
sendContent_P(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void *)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char *)type, contentLength);
|
||||
sendContent(header);
|
||||
sendContent_P(content, contentLength);
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const String &content) {
|
||||
sendContent(content.c_str(), content.length());
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const char *content, size_t contentLength) {
|
||||
const char *footer = "\r\n";
|
||||
if (_chunked) {
|
||||
char *chunkSize = (char *)malloc(11);
|
||||
if (chunkSize) {
|
||||
sprintf(chunkSize, "%x%s", contentLength, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite(content, contentLength);
|
||||
if (_chunked) {
|
||||
_currentClient.write(footer, 2);
|
||||
if (contentLength == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content) {
|
||||
sendContent_P(content, strlen_P(content));
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content, size_t size) {
|
||||
const char *footer = "\r\n";
|
||||
if (_chunked) {
|
||||
char *chunkSize = (char *)malloc(11);
|
||||
if (chunkSize) {
|
||||
sprintf(chunkSize, "%x%s", size, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite_P(content, size);
|
||||
if (_chunked) {
|
||||
_currentClient.write(footer, 2);
|
||||
if (size == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType, const int code) {
|
||||
using namespace mime;
|
||||
setContentLength(fileSize);
|
||||
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) && contentType != String(FPSTR(mimeTable[gz].mimeType))
|
||||
&& contentType != String(FPSTR(mimeTable[none].mimeType))) {
|
||||
sendHeader(F("Content-Encoding"), F("gzip"));
|
||||
}
|
||||
send(code, contentType, "");
|
||||
}
|
||||
|
||||
String WebServer::pathArg(unsigned int i) {
|
||||
if (_currentHandler != nullptr) {
|
||||
return _currentHandler->pathArg(i);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if (_postArgs[j].key == name) {
|
||||
return _postArgs[j].value;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name) {
|
||||
return _currentArgs[i].value;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(int i) {
|
||||
if (i < _currentArgCount) {
|
||||
return _currentArgs[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::argName(int i) {
|
||||
if (i < _currentArgCount) {
|
||||
return _currentArgs[i].key;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::args() {
|
||||
return _currentArgCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasArg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if (_postArgs[j].key == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::header(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(name)) {
|
||||
return _currentHeaders[i].value;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
|
||||
_headerKeysCount = headerKeysCount + 2;
|
||||
if (_currentHeaders) {
|
||||
delete[] _currentHeaders;
|
||||
}
|
||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
|
||||
for (int i = 2; i < _headerKeysCount; i++) {
|
||||
_currentHeaders[i].key = headerKeys[i - 2];
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::header(int i) {
|
||||
if (i < _headerKeysCount) {
|
||||
return _currentHeaders[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::headerName(int i) {
|
||||
if (i < _headerKeysCount) {
|
||||
return _currentHeaders[i].key;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::headers() {
|
||||
return _headerKeysCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasHeader(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::hostHeader() {
|
||||
return _hostHeader;
|
||||
}
|
||||
|
||||
void WebServer::onFileUpload(THandlerFunction fn) {
|
||||
_fileUploadHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::onNotFound(THandlerFunction fn) {
|
||||
_notFoundHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::_handleRequest() {
|
||||
bool handled = false;
|
||||
if (!_currentHandler) {
|
||||
log_e("request handler not found");
|
||||
} else {
|
||||
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||
if (!handled) {
|
||||
log_e("request handler failed to handle request");
|
||||
}
|
||||
}
|
||||
if (!handled && _notFoundHandler) {
|
||||
_notFoundHandler();
|
||||
handled = true;
|
||||
}
|
||||
if (!handled) {
|
||||
using namespace mime;
|
||||
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
_finalizeResponse();
|
||||
}
|
||||
_currentUri = "";
|
||||
}
|
||||
|
||||
void WebServer::_finalizeResponse() {
|
||||
if (_chunked) {
|
||||
sendContent("");
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::_responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100: return F("Continue");
|
||||
case 101: return F("Switching Protocols");
|
||||
case 200: return F("OK");
|
||||
case 201: return F("Created");
|
||||
case 202: return F("Accepted");
|
||||
case 203: return F("Non-Authoritative Information");
|
||||
case 204: return F("No Content");
|
||||
case 205: return F("Reset Content");
|
||||
case 206: return F("Partial Content");
|
||||
case 300: return F("Multiple Choices");
|
||||
case 301: return F("Moved Permanently");
|
||||
case 302: return F("Found");
|
||||
case 303: return F("See Other");
|
||||
case 304: return F("Not Modified");
|
||||
case 305: return F("Use Proxy");
|
||||
case 307: return F("Temporary Redirect");
|
||||
case 400: return F("Bad Request");
|
||||
case 401: return F("Unauthorized");
|
||||
case 402: return F("Payment Required");
|
||||
case 403: return F("Forbidden");
|
||||
case 404: return F("Not Found");
|
||||
case 405: return F("Method Not Allowed");
|
||||
case 406: return F("Not Acceptable");
|
||||
case 407: return F("Proxy Authentication Required");
|
||||
case 408: return F("Request Time-out");
|
||||
case 409: return F("Conflict");
|
||||
case 410: return F("Gone");
|
||||
case 411: return F("Length Required");
|
||||
case 412: return F("Precondition Failed");
|
||||
case 413: return F("Request Entity Too Large");
|
||||
case 414: return F("Request-URI Too Large");
|
||||
case 415: return F("Unsupported Media Type");
|
||||
case 416: return F("Requested range not satisfiable");
|
||||
case 417: return F("Expectation Failed");
|
||||
case 500: return F("Internal Server Error");
|
||||
case 501: return F("Not Implemented");
|
||||
case 502: return F("Bad Gateway");
|
||||
case 503: return F("Service Unavailable");
|
||||
case 504: return F("Gateway Time-out");
|
||||
case 505: return F("HTTP Version not supported");
|
||||
default: return F("");
|
||||
}
|
||||
}
|
||||
303
lib/WebServer/src/WebServer.h
Normal file
303
lib/WebServer/src/WebServer.h
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
WebServer.h - Dead simple web-server.
|
||||
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#ifndef WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "FS.h"
|
||||
#include "Network.h"
|
||||
#include "HTTP_Method.h"
|
||||
#include "Uri.h"
|
||||
|
||||
enum HTTPUploadStatus {
|
||||
UPLOAD_FILE_START,
|
||||
UPLOAD_FILE_WRITE,
|
||||
UPLOAD_FILE_END,
|
||||
UPLOAD_FILE_ABORTED
|
||||
};
|
||||
enum HTTPRawStatus {
|
||||
RAW_START,
|
||||
RAW_WRITE,
|
||||
RAW_END,
|
||||
RAW_ABORTED
|
||||
};
|
||||
enum HTTPClientStatus {
|
||||
HC_NONE,
|
||||
HC_WAIT_READ,
|
||||
HC_WAIT_CLOSE
|
||||
};
|
||||
enum HTTPAuthMethod {
|
||||
BASIC_AUTH,
|
||||
DIGEST_AUTH,
|
||||
OTHER_AUTH
|
||||
};
|
||||
|
||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||
|
||||
#ifndef HTTP_UPLOAD_BUFLEN
|
||||
#define HTTP_UPLOAD_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_RAW_BUFLEN
|
||||
#define HTTP_RAW_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
|
||||
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
|
||||
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
|
||||
#define HTTP_MAX_CLOSE_WAIT 5000 //ms to wait for the client to close the connection
|
||||
#define HTTP_MAX_BASIC_AUTH_LEN 256 // maximum length of a basic Auth base64 encoded username:password string
|
||||
|
||||
#define CONTENT_LENGTH_UNKNOWN ((size_t) - 1)
|
||||
#define CONTENT_LENGTH_NOT_SET ((size_t) - 2)
|
||||
|
||||
class WebServer;
|
||||
|
||||
typedef struct {
|
||||
HTTPUploadStatus status;
|
||||
String filename;
|
||||
String name;
|
||||
String type;
|
||||
size_t totalSize; // file size
|
||||
size_t len;
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||
} HTTPUpload;
|
||||
|
||||
typedef struct {
|
||||
HTTPRawStatus status;
|
||||
size_t totalSize; // content size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_RAW_BUFLEN];
|
||||
void *data; // additional data
|
||||
} HTTPRaw;
|
||||
|
||||
#include "detail/RequestHandler.h"
|
||||
|
||||
namespace fs {
|
||||
class FS;
|
||||
}
|
||||
|
||||
class WebServer {
|
||||
public:
|
||||
WebServer(IPAddress addr, int port = 80);
|
||||
WebServer(int port = 80);
|
||||
virtual ~WebServer();
|
||||
|
||||
virtual void begin();
|
||||
virtual void begin(uint16_t port);
|
||||
virtual void handleClient();
|
||||
|
||||
virtual void close();
|
||||
void stop();
|
||||
|
||||
const String AuthTypeDigest = F("Digest");
|
||||
const String AuthTypeBasic = F("Basic");
|
||||
|
||||
/* Callbackhandler for authentication. The extra parameters depend on the
|
||||
* HTTPAuthMethod mode:
|
||||
*
|
||||
* BASIC_AUTH enteredUsernameOrReq contains the username entered by the user
|
||||
* param[0] password entered (in the clear)
|
||||
* param[1] authentication realm.
|
||||
*
|
||||
* To return - the password the user entered password is compared to. Or Null on fail.
|
||||
*
|
||||
* DIGEST_AUTH enteredUsernameOrReq contains the username entered by the user
|
||||
* param[0] autenticaiton realm
|
||||
* param[1] authentication URI
|
||||
*
|
||||
* To return - the password of which the digest will be based on for comparison. Or NULL
|
||||
* to fail.
|
||||
*
|
||||
* OTHER_AUTH enteredUsernameOrReq rest of the auth line.
|
||||
* params empty array
|
||||
*
|
||||
* To return - NULL to fail; or any string.
|
||||
*/
|
||||
typedef std::function<String *(HTTPAuthMethod mode, String enteredUsernameOrReq, String extraParams[])> THandlerFunctionAuthCheck;
|
||||
|
||||
bool authenticate(THandlerFunctionAuthCheck fn);
|
||||
bool authenticate(const char *username, const char *password);
|
||||
bool authenticateBasicSHA1(const char *_username, const char *_sha1AsBase64orHex);
|
||||
|
||||
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String(""));
|
||||
|
||||
typedef std::function<void(void)> THandlerFunction;
|
||||
typedef std::function<bool(WebServer &server)> FilterFunction;
|
||||
RequestHandler &on(const Uri &uri, THandlerFunction fn);
|
||||
RequestHandler &on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||
RequestHandler &on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); //ufn handles file uploads
|
||||
bool removeRoute(const char *uri);
|
||||
bool removeRoute(const char *uri, HTTPMethod method);
|
||||
bool removeRoute(const String &uri);
|
||||
bool removeRoute(const String &uri, HTTPMethod method);
|
||||
void addHandler(RequestHandler *handler);
|
||||
bool removeHandler(RequestHandler *handler);
|
||||
void serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_header = NULL);
|
||||
void onNotFound(THandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(THandlerFunction ufn); //handle file uploads
|
||||
|
||||
String uri() {
|
||||
return _currentUri;
|
||||
}
|
||||
HTTPMethod method() {
|
||||
return _currentMethod;
|
||||
}
|
||||
virtual NetworkClient &client() {
|
||||
return _currentClient;
|
||||
}
|
||||
HTTPUpload &upload() {
|
||||
return *_currentUpload;
|
||||
}
|
||||
HTTPRaw &raw() {
|
||||
return *_currentRaw;
|
||||
}
|
||||
|
||||
String pathArg(unsigned int i); // get request path argument by number
|
||||
String arg(String name); // get request argument value by name
|
||||
String arg(int i); // get request argument value by number
|
||||
String argName(int i); // get request argument name by number
|
||||
int args(); // get arguments count
|
||||
bool hasArg(String name); // check if argument exists
|
||||
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||
String header(String name); // get request header value by name
|
||||
String header(int i); // get request header value by number
|
||||
String headerName(int i); // get request header name by number
|
||||
int headers(); // get header count
|
||||
bool hasHeader(String name); // check if header exists
|
||||
|
||||
int clientContentLength() {
|
||||
return _clientContentLength;
|
||||
} // return "content-length" of incoming HTTP header from "_currentClient"
|
||||
|
||||
String hostHeader(); // get request host header if available or empty String if not
|
||||
|
||||
// send response to the client
|
||||
// code - HTTP response code, can be 200 or 404
|
||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||
// content - actual content body
|
||||
void send(int code, const char *content_type = NULL, const String &content = String(""));
|
||||
void send(int code, char *content_type, const String &content);
|
||||
void send(int code, const String &content_type, const String &content);
|
||||
void send(int code, const char *content_type, const char *content);
|
||||
|
||||
void send_P(int code, PGM_P content_type, PGM_P content);
|
||||
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
||||
|
||||
void enableDelay(boolean value);
|
||||
void enableCORS(boolean value = true);
|
||||
void enableCrossOrigin(boolean value = true);
|
||||
typedef std::function<String(FS &fs, const String &fName)> ETagFunction;
|
||||
void enableETag(bool enable, ETagFunction fn = nullptr);
|
||||
|
||||
void setContentLength(const size_t contentLength);
|
||||
void sendHeader(const String &name, const String &value, bool first = false);
|
||||
void sendContent(const String &content);
|
||||
void sendContent(const char *content, size_t contentLength);
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
|
||||
static String urlDecode(const String &text);
|
||||
|
||||
template<typename T> size_t streamFile(T &file, const String &contentType, const int code = 200) {
|
||||
_streamFileCore(file.size(), file.name(), contentType, code);
|
||||
return _currentClient.write(file);
|
||||
}
|
||||
|
||||
bool _eTagEnabled = false;
|
||||
ETagFunction _eTagFunction = nullptr;
|
||||
|
||||
protected:
|
||||
virtual size_t _currentClientWrite(const char *b, size_t l) {
|
||||
return _currentClient.write(b, l);
|
||||
}
|
||||
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
|
||||
return _currentClient.write_P(b, l);
|
||||
}
|
||||
void _addRequestHandler(RequestHandler *handler);
|
||||
bool _removeRequestHandler(RequestHandler *handler);
|
||||
void _handleRequest();
|
||||
void _finalizeResponse();
|
||||
bool _parseRequest(NetworkClient &client);
|
||||
void _parseArguments(String data);
|
||||
static String _responseCodeToString(int code);
|
||||
bool _parseForm(NetworkClient &client, String boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
int _uploadReadByte(NetworkClient &client);
|
||||
void _prepareHeader(String &response, int code, const char *content_type, size_t contentLength);
|
||||
bool _collectHeader(const char *headerName, const char *headerValue);
|
||||
|
||||
void _streamFileCore(const size_t fileSize, const String &fileName, const String &contentType, const int code = 200);
|
||||
|
||||
String _getRandomHexString();
|
||||
// for extracting Auth parameters
|
||||
String _extractParam(String &authReq, const String ¶m, const char delimit = '"');
|
||||
|
||||
struct RequestArgument {
|
||||
String key;
|
||||
String value;
|
||||
};
|
||||
|
||||
boolean _corsEnabled;
|
||||
NetworkServer _server;
|
||||
|
||||
NetworkClient _currentClient;
|
||||
HTTPMethod _currentMethod;
|
||||
String _currentUri;
|
||||
uint8_t _currentVersion;
|
||||
HTTPClientStatus _currentStatus;
|
||||
unsigned long _statusChange;
|
||||
boolean _nullDelay;
|
||||
|
||||
RequestHandler *_currentHandler;
|
||||
RequestHandler *_firstHandler;
|
||||
RequestHandler *_lastHandler;
|
||||
THandlerFunction _notFoundHandler;
|
||||
THandlerFunction _fileUploadHandler;
|
||||
|
||||
int _currentArgCount;
|
||||
RequestArgument *_currentArgs;
|
||||
int _postArgsLen;
|
||||
RequestArgument *_postArgs;
|
||||
|
||||
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||
std::unique_ptr<HTTPRaw> _currentRaw;
|
||||
|
||||
int _headerKeysCount;
|
||||
RequestArgument *_currentHeaders;
|
||||
size_t _contentLength;
|
||||
int _clientContentLength; // "Content-Length" from header of incoming POST or GET request
|
||||
String _responseHeaders;
|
||||
|
||||
String _hostHeader;
|
||||
bool _chunked;
|
||||
|
||||
String _snonce; // Store noance and opaque for future comparison
|
||||
String _sopaque;
|
||||
String _srealm; // Store the Auth realm between Calls
|
||||
};
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
||||
91
lib/WebServer/src/detail/RequestHandler.h
Normal file
91
lib/WebServer/src/detail/RequestHandler.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef REQUESTHANDLER_H
|
||||
#define REQUESTHANDLER_H
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
virtual ~RequestHandler() {}
|
||||
|
||||
/*
|
||||
note: old handler API for backward compatibility
|
||||
*/
|
||||
|
||||
virtual bool canHandle(HTTPMethod method, String uri) {
|
||||
(void)method;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canUpload(String uri) {
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canRaw(String uri) {
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
note: new handler API with support for filters etc.
|
||||
*/
|
||||
|
||||
virtual bool canHandle(WebServer &server, HTTPMethod method, String uri) {
|
||||
(void)server;
|
||||
(void)method;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canUpload(WebServer &server, String uri) {
|
||||
(void)server;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canRaw(WebServer &server, String uri) {
|
||||
(void)server;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) {
|
||||
(void)server;
|
||||
(void)requestMethod;
|
||||
(void)requestUri;
|
||||
return false;
|
||||
}
|
||||
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload) {
|
||||
(void)server;
|
||||
(void)requestUri;
|
||||
(void)upload;
|
||||
}
|
||||
virtual void raw(WebServer &server, String requestUri, HTTPRaw &raw) {
|
||||
(void)server;
|
||||
(void)requestUri;
|
||||
(void)raw;
|
||||
}
|
||||
|
||||
virtual RequestHandler &setFilter(std::function<bool(WebServer &)> filter) {
|
||||
(void)filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestHandler *next() {
|
||||
return _next;
|
||||
}
|
||||
void next(RequestHandler *r) {
|
||||
_next = r;
|
||||
}
|
||||
|
||||
private:
|
||||
RequestHandler *_next = nullptr;
|
||||
|
||||
protected:
|
||||
std::vector<String> pathArgs;
|
||||
|
||||
public:
|
||||
const String &pathArg(unsigned int i) {
|
||||
assert(i < pathArgs.size());
|
||||
return pathArgs[i];
|
||||
}
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLER_H
|
||||
263
lib/WebServer/src/detail/RequestHandlersImpl.h
Normal file
263
lib/WebServer/src/detail/RequestHandlersImpl.h
Normal file
@@ -0,0 +1,263 @@
|
||||
#ifndef REQUESTHANDLERSIMPL_H
|
||||
#define REQUESTHANDLERSIMPL_H
|
||||
|
||||
#include "RequestHandler.h"
|
||||
#include "mimetable.h"
|
||||
#include "WString.h"
|
||||
#include "Uri.h"
|
||||
#include <MD5Builder.h>
|
||||
#include <base64.h>
|
||||
|
||||
using namespace mime;
|
||||
|
||||
class FunctionRequestHandler : public RequestHandler {
|
||||
public:
|
||||
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
|
||||
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
|
||||
_uri->initPathArgs(pathArgs);
|
||||
}
|
||||
|
||||
~FunctionRequestHandler() {
|
||||
delete _uri;
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs);
|
||||
}
|
||||
|
||||
bool canUpload(String requestUri) override {
|
||||
if (!_ufn || !canHandle(HTTP_POST, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canRaw(String requestUri) override {
|
||||
if (!_ufn || _method == HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canHandle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs) && (_filter != NULL ? _filter(server) : true);
|
||||
}
|
||||
|
||||
bool canUpload(WebServer &server, String requestUri) override {
|
||||
if (!_ufn || !canHandle(server, HTTP_POST, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canRaw(WebServer &server, String requestUri) override {
|
||||
if (!_ufn || _method == HTTP_GET || (_filter != NULL ? _filter(server) == false : false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (!canHandle(server, requestMethod, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
void upload(WebServer &server, String requestUri, HTTPUpload &upload) override {
|
||||
(void)upload;
|
||||
if (canUpload(server, requestUri)) {
|
||||
_ufn();
|
||||
}
|
||||
}
|
||||
|
||||
void raw(WebServer &server, String requestUri, HTTPRaw &raw) override {
|
||||
(void)raw;
|
||||
if (canRaw(server, requestUri)) {
|
||||
_ufn();
|
||||
}
|
||||
}
|
||||
|
||||
FunctionRequestHandler &setFilter(WebServer::FilterFunction filter) {
|
||||
_filter = filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
WebServer::THandlerFunction _fn;
|
||||
WebServer::THandlerFunction _ufn;
|
||||
// _filter should return 'true' when the request should be handled
|
||||
// and 'false' when the request should be ignored
|
||||
WebServer::FilterFunction _filter;
|
||||
Uri *_uri;
|
||||
HTTPMethod _method;
|
||||
};
|
||||
|
||||
class StaticRequestHandler : public RequestHandler {
|
||||
public:
|
||||
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header) : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
|
||||
File f = fs.open(path);
|
||||
_isFile = (f && (!f.isDirectory()));
|
||||
log_v(
|
||||
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header ? cache_header : ""
|
||||
); // issue 5506 - cache_header can be nullptr
|
||||
_baseUriLength = _uri.length();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (requestMethod != HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canHandle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (requestMethod != HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_filter != NULL ? _filter(server) == false : false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (!canHandle(server, requestMethod, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
|
||||
String path(_path);
|
||||
String eTagCode;
|
||||
|
||||
if (!_isFile) {
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (requestUri.endsWith("/")) {
|
||||
requestUri += "index.htm";
|
||||
}
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
}
|
||||
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
|
||||
String contentType = getContentType(path);
|
||||
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if (_fs.exists(pathWithGz)) {
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f || !f.available()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server._eTagEnabled) {
|
||||
if (server._eTagFunction) {
|
||||
eTagCode = (server._eTagFunction)(_fs, path);
|
||||
} else {
|
||||
eTagCode = calcETag(_fs, path);
|
||||
}
|
||||
|
||||
if (server.header("If-None-Match") == eTagCode) {
|
||||
server.send(304);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cache_header.length() != 0) {
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
}
|
||||
|
||||
if ((server._eTagEnabled) && (eTagCode.length() > 0)) {
|
||||
server.sendHeader("ETag", eTagCode);
|
||||
}
|
||||
|
||||
server.streamFile(f, contentType);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String getContentType(const String &path) {
|
||||
char buff[sizeof(mimeTable[0].mimeType)];
|
||||
// Check all entries but last one for match, return if found
|
||||
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
|
||||
strcpy_P(buff, mimeTable[i].endsWith);
|
||||
if (path.endsWith(buff)) {
|
||||
strcpy_P(buff, mimeTable[i].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
}
|
||||
// Fall-through and just return default type
|
||||
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
// calculate an ETag for a file in filesystem based on md5 checksum
|
||||
// that can be used in the http headers - include quotes.
|
||||
static String calcETag(FS &fs, const String &path) {
|
||||
String result;
|
||||
|
||||
// calculate eTag using md5 checksum
|
||||
uint8_t md5_buf[16];
|
||||
File f = fs.open(path, "r");
|
||||
MD5Builder calcMD5;
|
||||
calcMD5.begin();
|
||||
calcMD5.addStream(f, f.size());
|
||||
calcMD5.calculate();
|
||||
calcMD5.getBytes(md5_buf);
|
||||
f.close();
|
||||
// create a minimal-length eTag using base64 byte[]->text encoding.
|
||||
result = "\"" + base64::encode(md5_buf, 16) + "\"";
|
||||
return (result);
|
||||
} // calcETag
|
||||
|
||||
StaticRequestHandler &setFilter(WebServer::FilterFunction filter) {
|
||||
_filter = filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
// _filter should return 'true' when the request should be handled
|
||||
// and 'false' when the request should be ignored
|
||||
WebServer::FilterFunction _filter;
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
||||
33
lib/WebServer/src/detail/mimetable.cpp
Normal file
33
lib/WebServer/src/detail/mimetable.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "mimetable.h"
|
||||
#include "pgmspace.h"
|
||||
|
||||
namespace mime {
|
||||
|
||||
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
|
||||
const Entry mimeTable[maxType] = {
|
||||
{".html", "text/html"},
|
||||
{".htm", "text/html"},
|
||||
{".css", "text/css"},
|
||||
{".txt", "text/plain"},
|
||||
{".js", "application/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".png", "image/png"},
|
||||
{".gif", "image/gif"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".ttf", "application/x-font-ttf"},
|
||||
{".otf", "application/x-font-opentype"},
|
||||
{".woff", "application/font-woff"},
|
||||
{".woff2", "application/font-woff2"},
|
||||
{".eot", "application/vnd.ms-fontobject"},
|
||||
{".sfnt", "application/font-sfnt"},
|
||||
{".xml", "text/xml"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".zip", "application/zip"},
|
||||
{".gz", "application/x-gzip"},
|
||||
{".appcache", "text/cache-manifest"},
|
||||
{"", "application/octet-stream"}
|
||||
};
|
||||
|
||||
} // namespace mime
|
||||
41
lib/WebServer/src/detail/mimetable.h
Normal file
41
lib/WebServer/src/detail/mimetable.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef __MIMETABLE_H__
|
||||
#define __MIMETABLE_H__
|
||||
|
||||
namespace mime {
|
||||
|
||||
enum type {
|
||||
html,
|
||||
htm,
|
||||
css,
|
||||
txt,
|
||||
js,
|
||||
json,
|
||||
png,
|
||||
gif,
|
||||
jpg,
|
||||
ico,
|
||||
svg,
|
||||
ttf,
|
||||
otf,
|
||||
woff,
|
||||
woff2,
|
||||
eot,
|
||||
sfnt,
|
||||
xml,
|
||||
pdf,
|
||||
zip,
|
||||
gz,
|
||||
appcache,
|
||||
none,
|
||||
maxType
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
const char endsWith[16];
|
||||
const char mimeType[32];
|
||||
};
|
||||
|
||||
extern const Entry mimeTable[maxType];
|
||||
} // namespace mime
|
||||
|
||||
#endif
|
||||
68
lib/WebServer/src/uri/UriBraces.h
Normal file
68
lib/WebServer/src/uri/UriBraces.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef URI_BRACES_H
|
||||
#define URI_BRACES_H
|
||||
|
||||
#include "Uri.h"
|
||||
|
||||
class UriBraces : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriBraces(const char *uri) : Uri(uri){};
|
||||
explicit UriBraces(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriBraces(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
int numParams = 0, start = 0;
|
||||
do {
|
||||
start = _uri.indexOf("{}", start);
|
||||
if (start > 0) {
|
||||
numParams++;
|
||||
start += 2;
|
||||
}
|
||||
} while (start > 0);
|
||||
pathArgs.resize(numParams);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t uriLength = _uri.length();
|
||||
unsigned int pathArgIndex = 0;
|
||||
unsigned int requestUriIndex = 0;
|
||||
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
|
||||
char uriChar = _uri[i];
|
||||
char requestUriChar = requestUri[requestUriIndex];
|
||||
|
||||
if (uriChar == requestUriChar) {
|
||||
continue;
|
||||
}
|
||||
if (uriChar != '{') {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 2; // index of char after '}'
|
||||
if (i >= uriLength) {
|
||||
// there is no char after '}'
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
|
||||
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
|
||||
} else {
|
||||
char charEnd = _uri[i];
|
||||
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
|
||||
if (uriIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
|
||||
requestUriIndex = (unsigned int)uriIndex;
|
||||
}
|
||||
pathArgIndex++;
|
||||
}
|
||||
|
||||
return requestUriIndex >= requestUri.length();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
22
lib/WebServer/src/uri/UriGlob.h
Normal file
22
lib/WebServer/src/uri/UriGlob.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef URI_GLOB_H
|
||||
#define URI_GLOB_H
|
||||
|
||||
#include "Uri.h"
|
||||
#include <fnmatch.h>
|
||||
|
||||
class UriGlob : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriGlob(const char *uri) : Uri(uri){};
|
||||
explicit UriGlob(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriGlob(_uri);
|
||||
};
|
||||
|
||||
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
|
||||
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
45
lib/WebServer/src/uri/UriRegex.h
Normal file
45
lib/WebServer/src/uri/UriRegex.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef URI_REGEX_H
|
||||
#define URI_REGEX_H
|
||||
|
||||
#include "Uri.h"
|
||||
#include <regex>
|
||||
|
||||
class UriRegex : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriRegex(const char *uri) : Uri(uri){};
|
||||
explicit UriRegex(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriRegex(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
std::regex rgx((_uri + "|").c_str());
|
||||
std::smatch matches;
|
||||
std::string s{""};
|
||||
std::regex_search(s, matches, rgx);
|
||||
pathArgs.resize(matches.size() - 1);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int pathArgIndex = 0;
|
||||
std::regex rgx(_uri.c_str());
|
||||
std::smatch matches;
|
||||
std::string s(requestUri.c_str());
|
||||
if (std::regex_search(s, matches, rgx)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // skip first
|
||||
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
|
||||
pathArgIndex++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
19
platformio.ini
Normal file
19
platformio.ini
Normal file
@@ -0,0 +1,19 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32solo1]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.07.11/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = esp32-solo1
|
||||
build_flags = -DFRAMEWORK_ARDUINO_SOLO1
|
||||
lib_deps =
|
||||
xoseperez/HLW8012@^1.1.2
|
||||
plerup/EspSoftwareSerial@^8.2.0
|
||||
knolleary/PubSubClient@^2.8
|
||||
765
src/http_server.cpp
Normal file
765
src/http_server.cpp
Normal file
@@ -0,0 +1,765 @@
|
||||
#include "Http_Server.h"
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <Update.h>
|
||||
#include <Arduino.h>
|
||||
#include <DNSServer.h>
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "esp_wifi.h"
|
||||
|
||||
#include "user.h"
|
||||
#include "user_mqtt.h"
|
||||
|
||||
//Http_Server httpserver;
|
||||
DNSServer dnsServer;
|
||||
|
||||
MQTT mqtt;
|
||||
|
||||
WebServer httpserver_(80);
|
||||
|
||||
bool linkwifi = false;
|
||||
unsigned long WFilinkMillis;
|
||||
|
||||
#define devdev "N2-IR"
|
||||
String host;
|
||||
String htmltou = "<html lang=\"zh\"><head><meta name=\"format-detection\" content=\"telephone=no\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"><title>";
|
||||
String htmltou2 = "</title>";
|
||||
String htmltou3 = "<style>.c,body{text-align:center;font-family:verdana}div,input{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box;}input,button,.msg{border-radius:.3rem;width: 100%}button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}input[type='file']{border:1px solid #1fa3ec}.wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}.q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;long:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{long:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');background-size: 95px 16px;}}.msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}td{vertical-align: top;}.h{display:none}button.D{background-color:#dc3630}button.F{background-color:#777}body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}input:disabled {opacity: 0.5;}</style>";
|
||||
String htmltou4 = "</head><body class=\"invert\"><div class=\"wrap\">";
|
||||
String htmltou5 = "</div></body></html>";
|
||||
|
||||
void h_index(){
|
||||
//httpserver_.sendHeader("Connection", "close");
|
||||
String html;
|
||||
html = htmltou + devdev + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
html += "<meta charset='UTF-8'>";
|
||||
html += devdev;
|
||||
html += "</meta>";
|
||||
html += "<button onclick='location.href=(\"wifi#p\")' type=\"button\">WiFi设置</button><br><br>";
|
||||
|
||||
html += "<form method=\"\" action=\"update\"><button type=\"submit\">OTA更新</button></form>";
|
||||
html += "<form method=\"\" action=\"fsca\"><button class=\"D\" type=\"submit\">恢复出厂设置</button></form>";
|
||||
html += htmltou5;
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html); //返回保存成功页面
|
||||
}
|
||||
void h_refresh(){
|
||||
//httpserver_.sendHeader("Connection", "close");
|
||||
//WiFi.setAutoReconnect(false);
|
||||
httpserver_.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0; URL=http://192.168.4.1/\" />");
|
||||
}
|
||||
void h_config(){
|
||||
String message = "{";
|
||||
String mac = WiFi.macAddress();
|
||||
message += "\"mac\":\"";
|
||||
message += mac;
|
||||
message += "\",\"name\":\"";
|
||||
message += host;
|
||||
message += "\",\"aps\":[{}";
|
||||
int n = WiFi.scanNetworks();
|
||||
Serial.println("scan done");
|
||||
if (n == 0) {
|
||||
Serial.println("no networks found");
|
||||
}else{
|
||||
Serial.print(n);
|
||||
Serial.println("\tnetworks found");
|
||||
for (int i = 0; i < n; ++i) {
|
||||
Serial.print(i + 1);
|
||||
Serial.print(":\t");
|
||||
Serial.print(WiFi.SSID(i));
|
||||
Serial.print("\t(");
|
||||
Serial.print(WiFi.RSSI(i));
|
||||
Serial.print(")\t");
|
||||
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
|
||||
if(WiFi.SSID(i) != ""){
|
||||
message += ", {\"ssid\":\"";
|
||||
message += WiFi.SSID(i);
|
||||
message += "\",\"rssi\":";
|
||||
message += WiFi.RSSI(i);
|
||||
message += ",\"lock\":";
|
||||
if(WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {message += "1";}else{message += "0";}
|
||||
message += "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
message += "]}";
|
||||
httpserver_.send(200, "application/json", message);
|
||||
}
|
||||
void h_wifi__get(){
|
||||
String html;
|
||||
//WiFi.setAutoReconnect(false);
|
||||
//httpserver_.sendHeader("Connection", "close");
|
||||
// html = htmltou + "Set WiFi" + htmltou2;
|
||||
// html += "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;p = l.nextElementSibling.classList.contains('l');document.getElementById('p').disabled = !p;if(p)document.getElementById('p').focus();}</script>";
|
||||
// html += htmltou3 + htmltou4;
|
||||
// int n = WiFi.scanNetworks();
|
||||
// //WiFi.setAutoReconnect(true);
|
||||
// Serial.println("scan done");
|
||||
// if (n == 0) {
|
||||
// Serial.println("no networks found");
|
||||
// }else{
|
||||
// Serial.print(n);
|
||||
// Serial.println("\tnetworks found");
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// // Print SSID and RSSI for each network found
|
||||
// Serial.print(i + 1);
|
||||
// Serial.print(":\t");
|
||||
// Serial.print(WiFi.SSID(i));
|
||||
// Serial.print("\t(");
|
||||
// Serial.print(WiFi.RSSI(i));
|
||||
// Serial.print(")\t");
|
||||
// Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
|
||||
// if(WiFi.SSID(i) != ""){
|
||||
// html += "<div><a href=\"wifi#p\" onclick=\"c(this)\">";
|
||||
// html += WiFi.SSID(i);
|
||||
// html += "</a><div role=\"img\" aria-label=\"";
|
||||
// html += WiFi.RSSI(i);
|
||||
// html += "%\" title=\"";
|
||||
// html += "\" class=\"q ";
|
||||
// if(WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {html += "l";}
|
||||
// html += " ";
|
||||
// if(WiFi.RSSI(i) > -60){
|
||||
// html += " q-4";
|
||||
// }else if(WiFi.RSSI(i) > -80){
|
||||
// html += " q-3";
|
||||
// }else if(WiFi.RSSI(i) > -85){
|
||||
// html += " q-2";
|
||||
// }else{
|
||||
// html += " q-1";
|
||||
// }
|
||||
// html += " ";
|
||||
// html += "\"></div><div class=\"q h\">";
|
||||
// html += WiFi.RSSI(i);
|
||||
// html += "%</div></div>";
|
||||
// html += "";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// WiFi.scanDelete();
|
||||
// html += "<form method=\"POST\" action=\"\"><label for=\"s\">SSID</label><input id=\"s\" name=\"s\" maxlength=\"32\" autocorrect=\"off\" autocapitalize=\"none\" placeholder=\"\"><br><label for=\"p\">Password</label><input id=\"p\" name=\"p\" maxlength=\"64\" type=\"password\" placeholder=\"********\"><hr><p></p>";
|
||||
// //html += "<label for=\"CityCode\">blinker KEY</label><br><input id=\"key\" name=\"key\" maxlength=\"12\" value=\"";
|
||||
// //html += "";
|
||||
// html += "<br><br><button type=\"submit\">Save</button></form>";
|
||||
// html += "<form method=\"\" action=\"/\"><button type=\"submit\">返回</button></form>";
|
||||
|
||||
// html += htmltou5;
|
||||
html = "<!doctype html>\
|
||||
<html lang=en>\
|
||||
<head>\
|
||||
<meta charset=UTF-8/><link rel=icon href=data:image/>\
|
||||
<meta name=viewport content=\"width=device-width,initial-scale=1,user-scalable=no\"/>\
|
||||
<script type=module>\
|
||||
//assets/index.633c2a7f.js \r\n\
|
||||
document.location.search === \"?save\" && (document.getElementsByTagName(\"aside\")[0].style.display = \"block\");\
|
||||
function s(e) {\
|
||||
let t = Math.max(Math.min(2 * (e + 100), 100), 0) / 100;\
|
||||
return a(`<path d=\"m12.008 19.25-11.3-15c7-5 14-5 22.5 0z\" fill=\"#000\" stroke=\"currentColor\"/>\
|
||||
<path d=\"m12.008 19.25-11.3-15c7-5 14-5 22.5 0z\" fill=\"#FFF\" transform=\"scale(${t} ${t})\" transform-origin=\"12 18\"/>`)\
|
||||
}\
|
||||
function a(e) {\
|
||||
return o([`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">${e}</svg>`])\
|
||||
}\
|
||||
function i(e) {\
|
||||
return e ? a(`<path d='M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 \
|
||||
2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2\
|
||||
2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 \
|
||||
5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z'/>`) : \"\"\
|
||||
}\
|
||||
function o(e) {\
|
||||
return e.join(\"\")\
|
||||
}\
|
||||
fetch(\"/config.json\").then(function(e) {\
|
||||
e.json().then(function(t) {\
|
||||
document.title = t.name,\
|
||||
document.body.getElementsByTagName(\"h1\")[0].innerText = \"MAC Address: \" + t.mac,\
|
||||
document.body.getElementsByTagName(\"h1\")[1].innerText = \"WiFi Networks: \" + t.name;\
|
||||
let r = t.aps.slice(1).map(function(n) {\
|
||||
return `<div class=\"network\" \
|
||||
onclick=\"document.getElementById('ssid').value = this.innerText;document.getElementById('psk').focus()\">\
|
||||
<a href=\"#\" class=\"network-left\">\
|
||||
${s(n.rssi)}\
|
||||
<span class=\"network-ssid\">${n.ssid}</span>\
|
||||
</a>\
|
||||
${i(n.lock)}\
|
||||
</div>`\
|
||||
});\
|
||||
document.querySelector(\"#net\").innerHTML = o(r),\
|
||||
document.querySelector(\"link[rel~='icon']\").href = `data:image/svg+xml,${s(-65)}`\
|
||||
})\
|
||||
});\
|
||||
</script>\
|
||||
<style>\
|
||||
* {\
|
||||
box-sizing: inherit\
|
||||
}\
|
||||
\
|
||||
div,input {\
|
||||
padding: 5px;\
|
||||
font-size: 1em\
|
||||
}\
|
||||
\
|
||||
input {\
|
||||
width: 95%\
|
||||
}\
|
||||
\
|
||||
body {\
|
||||
text-align: center;\
|
||||
font-family: sans-serif\
|
||||
}\
|
||||
\
|
||||
button {\
|
||||
border: 0;\
|
||||
border-radius: .3rem;\
|
||||
background-color: #1fa3ec;\
|
||||
color: #fff;\
|
||||
line-height: 2.4rem;\
|
||||
font-size: 1.2rem;\
|
||||
width: 100%;\
|
||||
padding: 0;\
|
||||
cursor: pointer\
|
||||
}\
|
||||
\
|
||||
main {\
|
||||
text-align: left;\
|
||||
display: inline-block;\
|
||||
min-width: 260px\
|
||||
}\
|
||||
\
|
||||
.network {\
|
||||
display: flex;\
|
||||
justify-content: space-between;\
|
||||
align-items: center\
|
||||
}\
|
||||
\
|
||||
.network-left {\
|
||||
display: flex;\
|
||||
align-items: center\
|
||||
}\
|
||||
\
|
||||
.network-ssid {\
|
||||
margin-bottom: -7px;\
|
||||
margin-left: 10px\
|
||||
}\
|
||||
\
|
||||
aside {\
|
||||
border: 1px solid;\
|
||||
margin: 10px 0;\
|
||||
padding: 15px 10px;\
|
||||
color: #4f8a10;\
|
||||
background-color: #dff2bf;\
|
||||
display: none\
|
||||
}\
|
||||
\
|
||||
i {\
|
||||
width: 24px;\
|
||||
height: 24px\
|
||||
}\
|
||||
\
|
||||
i.lock {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
i.sig1 {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
i.sig2 {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
i.sig3 {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.4A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
i.sig4 {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
i.sig {\
|
||||
background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z'/%3E%3C/svg%3E\")\
|
||||
}\
|
||||
\
|
||||
body.invert {\
|
||||
background-color: #060606;\
|
||||
color: #fff;\
|
||||
}\
|
||||
\
|
||||
a {color:#fff;}\
|
||||
</style>\
|
||||
</head>\
|
||||
<body class=\"invert\">\
|
||||
<main>\
|
||||
<h1 id=mac>MAC Address</h1>\
|
||||
<h1 id=h1>WiFi Networks</h1>\
|
||||
<aside>\
|
||||
The ESP will now try to connect to the network...<br>\
|
||||
Please give it some time to connect.<br>\
|
||||
</aside>\
|
||||
<span id=net></span>\
|
||||
<h3>WiFi Settings</h3>\
|
||||
<form method=\"POST\" action=>\
|
||||
<input id=ssid name=s length=32 placeholder=SSID/>\
|
||||
<br>\
|
||||
<input id=psk name=p length=64 type=password placeholder=Password/>\
|
||||
<br>\
|
||||
<br>\
|
||||
<button type=submit>Save</button>\
|
||||
</form>\
|
||||
<br>\
|
||||
</main>\
|
||||
</body>\
|
||||
</html>\
|
||||
";
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
//WiFi.reconnect();
|
||||
return;
|
||||
}
|
||||
void h_wifi__post(){
|
||||
String wifi_ssid;
|
||||
String wifi_pass;
|
||||
|
||||
// WiFi.setAutoReconnect(false);
|
||||
|
||||
if(httpserver_.hasArg("s")){
|
||||
Serial.print("got ssid:");
|
||||
wifi_ssid = httpserver_.arg("s");
|
||||
Serial.println(wifi_ssid);
|
||||
}else{
|
||||
Serial.println("error, not found ssid");
|
||||
httpserver_.send(200, "text/html", "<meta charset='UTF-8'>error, not found ssid");//返回错误页面
|
||||
return;
|
||||
}
|
||||
if (httpserver_.hasArg("p")) {
|
||||
Serial.print("got password:");
|
||||
wifi_pass = httpserver_.arg("p"); //获取html表单输入框name名为"pwd"的内容
|
||||
//strcpy(sta_pass, httpserver_.arg("pass").c_str());
|
||||
Serial.println(wifi_pass);
|
||||
} else {
|
||||
Serial.println("error, not found password");
|
||||
httpserver_.send(200, "text/html", "<meta charset='UTF-8'>error, not found password");
|
||||
return;
|
||||
}
|
||||
if(wifi_ssid != ""){
|
||||
//settingMode = 0;
|
||||
//menuPage = 0;
|
||||
//setMenuPage = 0;
|
||||
Serial.println("WiFi Connect SSID:" + wifi_ssid + " PASS:" + wifi_pass);
|
||||
//wifi_set_tmp = 252;
|
||||
}
|
||||
if(wifi_ssid != ""){
|
||||
String html;
|
||||
html = htmltou + "Set WiFi" + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
html += "<meta charset='UTF-8'>SSID:" + wifi_ssid + "<br />password:" + wifi_pass + "<br />已取得WiFi信息,正在尝试连接,请手动关闭此页面。";
|
||||
html += "<hr><form method=\"\" action=\"\\\"><br><br><button type=\"submit\">返回</button></form>";
|
||||
html += htmltou5;
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html); //返回保存成功页面
|
||||
WiFi.begin(wifi_ssid,wifi_pass);
|
||||
Serial2.write("\x4D\x00\x01\x03\x0D\x00\x00\x5E\x5A",9);
|
||||
}else{
|
||||
httpserver_.send(200, "text/html", "<meta charset='UTF-8'>啥也没有,你个吊毛干嘛呢"); //返回保存成功页面
|
||||
}
|
||||
if(user_ > 199){
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
}
|
||||
}
|
||||
void handleNotFound(){
|
||||
//digitalWrite(led, 1);
|
||||
String message = "File Not Found\r\n\r\n";
|
||||
message += "URI: ";
|
||||
message += httpserver_.uri();
|
||||
message += "\r\nMethod: ";
|
||||
message += (httpserver_.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\r\nArguments: ";
|
||||
message += httpserver_.args();
|
||||
message += "\r\n";
|
||||
|
||||
for (uint8_t i = 0; i < httpserver_.args(); i++) {
|
||||
message += " " + httpserver_.argName(i) + ": " + httpserver_.arg(i) + "\r\n";
|
||||
}
|
||||
|
||||
//Serial.print(message);
|
||||
|
||||
httpserver_.send(200, "text/plain", message);
|
||||
//digitalWrite(led, 0);
|
||||
}
|
||||
void h_update__get(){
|
||||
String html;
|
||||
httpserver_.sendHeader("Connection", "close");
|
||||
html = htmltou + devdev + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
html += "<meta charset='UTF-8'>OTA</meta>";
|
||||
html += "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
|
||||
html += htmltou5;
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
}
|
||||
void h_update__post(){
|
||||
String html;
|
||||
// Serial.printf("hostHeader()");
|
||||
// Serial.printf(httpserver_.hasArg("curl").c_str());
|
||||
httpserver_.sendHeader("Connection", "close");
|
||||
if(httpserver_.hasArg("curl")){
|
||||
// Serial.printf("hostHeader()");
|
||||
if(Update.hasError()){
|
||||
html += "FAIL";
|
||||
}else{
|
||||
html += "OK";
|
||||
}
|
||||
}else{
|
||||
html = htmltou + "OTA" + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
if(Update.hasError()){
|
||||
html += "<meta charset='UTF-8'>FAIL</meta>";
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>OK</meta>";
|
||||
}
|
||||
|
||||
html += "<form method='' action='/reboot/set'><input type='submit' value='REBOOT'></form>";
|
||||
html += htmltou5;
|
||||
}
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
}
|
||||
size_t upload_len = 0;
|
||||
void uplood(HTTPUpload upload){
|
||||
upload_len += upload.currentSize;
|
||||
Serial.printf("%u\r",(upload.totalSize / (upload.len / 100)));
|
||||
// Serial.printf("%u\r\n",(upload.totalSize));
|
||||
// Serial.printf("%u\r\n",(upload_len ));
|
||||
}
|
||||
void upend(HTTPUpload upload){
|
||||
Serial.printf("Update Success: %u\r\n", upload.totalSize);
|
||||
reboot_ = 1;
|
||||
}
|
||||
void upstart(HTTPUpload upload){
|
||||
upload_len = 0;
|
||||
Serial.printf(" Update: %s\r\n", upload.filename.c_str());
|
||||
}
|
||||
void h_update__post_(){
|
||||
HTTPUpload& upload = httpserver_.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
upstart(upload);
|
||||
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}else if (upload.status == UPLOAD_FILE_WRITE){
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
uplood(upload);
|
||||
}else if (upload.status == UPLOAD_FILE_END){
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
upend(upload);
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
void h_reboot(){
|
||||
|
||||
}
|
||||
void h_reboot_set(){
|
||||
ESP.restart();
|
||||
}
|
||||
void h_fsca(){
|
||||
String html;
|
||||
html = htmltou + devdev + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
html += "reinit";
|
||||
html += htmltou5;
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html); //返回保存成功页面
|
||||
|
||||
LittleFS.end();
|
||||
LittleFS.format();
|
||||
esp_wifi_restore();
|
||||
reboot_ = 1;
|
||||
}
|
||||
void h_8012(){
|
||||
String html;
|
||||
html = htmltou + devdev + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
html += "HLW8012 \
|
||||
<div id=\"info\">正在加载</div> \
|
||||
<form method=\"\" action=\"8012/setv?\"> \
|
||||
<label for=\"s\" >设置实际测量电压0.1V :如 230V 设置 2300</label> \
|
||||
<input id=\"id\" name=\"V\" type=\"number\" value=\"2300\" maxlength=\"\" placeholder=\"\"> \
|
||||
<button type=\"submit\">校准电压</button> \
|
||||
</form> \
|
||||
<form method=\"\" action=\"8012/setc?\"> \
|
||||
<label for=\"s\" >设置实际测量电流0.1A :如 3A 设置 30</label> \
|
||||
<input id=\"id\" name=\"C\" type=\"number\" value=\"30\" maxlength=\"\" placeholder=\"\"> \
|
||||
<button type=\"submit\">校准电压</button> \
|
||||
</form> ";
|
||||
html += htmltou5;
|
||||
html += "<script type=\"text/javascript\"> \
|
||||
window.setInterval(infoupdata, 1000); \
|
||||
function infoupdata(){ \
|
||||
const cpuHttp = new XMLHttpRequest(); \
|
||||
cpuHttp.open(\"GET\", '8012/get'); \
|
||||
cpuHttp.send(); \
|
||||
cpuHttp.onreadystatechange = function() { \
|
||||
if(cpuHttp.readyState == 4 && cpuHttp.status == 200){ \
|
||||
var data = JSON.parse(cpuHttp.responseText); \
|
||||
var res; \
|
||||
res = '电压:' + data.V + \"V\"; \
|
||||
res += '<br>电流:' + data.C + \"A\"; \
|
||||
res += '<br>功率:' + data.P + \"W\"; \
|
||||
res += '<br>视在功率:' + data.VA + \"VA\"; \
|
||||
res += '<br>功率因数:' + data.F + \"%\"; \
|
||||
document.getElementById('info').innerHTML = res; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
</script>";
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
}
|
||||
void h_8012_get(){
|
||||
String message = "{";
|
||||
|
||||
message += "\"V\":\"" + String(hlw8012.getVoltage()) + "\",";
|
||||
message += "\"C\":\"" + String(hlw8012.getCurrent()) + "\",";
|
||||
message += "\"P\":\"" + String(hlw8012.getActivePower()) + "\",";
|
||||
message += "\"VA\":\"" + String(hlw8012.getApparentPower()) + "\",";
|
||||
message += "\"F\":\"" + String((int) (100 * hlw8012.getPowerFactor())) + "\"";
|
||||
message += "}";
|
||||
httpserver_.send(200, "application/json", message);
|
||||
Serial.print("[HLW] Active Power (W) : "); Serial.println(hlw8012.getActivePower());
|
||||
Serial.print("[HLW] Voltage (V) : "); Serial.println(hlw8012.getVoltage());
|
||||
Serial.print("[HLW] Current (A) : "); Serial.println(hlw8012.getCurrent());
|
||||
Serial.print("[HLW] Apparent Power (VA) : "); Serial.println(hlw8012.getApparentPower());
|
||||
Serial.print("[HLW] Power Factor (%) : "); Serial.println((int) (100 * hlw8012.getPowerFactor()));
|
||||
|
||||
}
|
||||
void h_8012_setv(){
|
||||
String v;
|
||||
String html;
|
||||
html = htmltou + "Setup" + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
|
||||
|
||||
if(httpserver_.hasArg("V")){
|
||||
v = httpserver_.arg("V");
|
||||
unsigned int vv = v.toInt();
|
||||
double vvv = vv * 0.1;
|
||||
Serial.print("[SET] Voltage (V) : "); Serial.println(vvv);
|
||||
hlw8012.expectedVoltage(vvv);
|
||||
|
||||
vvv = hlw8012.getVoltageMultiplier();
|
||||
if(LittleFS.exists("/8012v")){
|
||||
LittleFS.remove("/8012v");
|
||||
}
|
||||
File file = LittleFS.open("/8012v",FILE_WRITE);
|
||||
if(file){
|
||||
if(file.print(String(vvv).c_str())){
|
||||
html += "<meta charset='UTF-8'>成功";
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>失败,不知道为什么";
|
||||
}
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>失败,请重置设备";
|
||||
}
|
||||
file.close();
|
||||
|
||||
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>获取参数失败<br />请更换浏览器后重试";
|
||||
}
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
}
|
||||
void h_8012_setc(){
|
||||
String c;
|
||||
String html;
|
||||
html = htmltou + "Setup" + htmltou2;
|
||||
html += htmltou3 + htmltou4;
|
||||
|
||||
|
||||
if(httpserver_.hasArg("C")){
|
||||
c = httpserver_.arg("C");
|
||||
unsigned int cc = c.toInt();
|
||||
double ccc = cc * 0.1;
|
||||
Serial.print("[SET] Current (A) : "); Serial.println(ccc);
|
||||
hlw8012.expectedCurrent(ccc);
|
||||
|
||||
ccc = hlw8012.getCurrentMultiplier();
|
||||
if(LittleFS.exists("/8012c")){
|
||||
LittleFS.remove("/8012c");
|
||||
}
|
||||
File file = LittleFS.open("/8012c",FILE_WRITE);
|
||||
if(file){
|
||||
if(file.print(String(ccc).c_str())){
|
||||
ccc = cc * 0.1;
|
||||
unsigned int ppp = (unsigned int)ccc * hlw8012.getVoltage();
|
||||
hlw8012.expectedActivePower(ppp);
|
||||
ppp = hlw8012.getPowerMultiplier();
|
||||
file.close();
|
||||
if(LittleFS.exists("/8012p")){
|
||||
LittleFS.remove("/8012p");
|
||||
}
|
||||
file = LittleFS.open("/8012p",FILE_WRITE);
|
||||
file.print(String(ppp).c_str());
|
||||
|
||||
html += "<meta charset='UTF-8'>成功";
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>失败,不知道为什么";
|
||||
}
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>失败,请重置设备";
|
||||
}
|
||||
file.close();
|
||||
|
||||
|
||||
}else{
|
||||
html += "<meta charset='UTF-8'>获取参数失败<br />请更换浏览器后重试";
|
||||
}
|
||||
httpserver_.send(200, "text/html;charset=UTF-8", html);
|
||||
}
|
||||
|
||||
void Http_Server::setup(){
|
||||
Serial.println("Booting");
|
||||
WiFi.mode(WIFI_STA);
|
||||
delay(100);
|
||||
|
||||
String mac = WiFi.macAddress();
|
||||
host = (String)devdev + (String)"_" + mac.substring(9,11) + mac.substring(12,14) + mac.substring(15,17);
|
||||
WiFi.setHostname(host.c_str());
|
||||
WiFi.softAP(host.c_str());
|
||||
Serial.print("> Host:\t");
|
||||
Serial.println(host);
|
||||
Serial.print("> SETAP:\t");
|
||||
Serial.println(host.c_str());
|
||||
IPAddress apIP(192, 168, 4, 1); // The default android DNS
|
||||
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); //设置AP热点IP和子网掩码
|
||||
dnsServer.start(53, "*", apIP);
|
||||
|
||||
Serial.print("> Set Web Server\r\n");
|
||||
httpserver_.onNotFound(handleNotFound);
|
||||
httpserver_.on("/",h_index);
|
||||
httpserver_.on("/generate_204",h_refresh);
|
||||
httpserver_.on("/reboot",h_reboot);
|
||||
httpserver_.on("/reboot/set",h_reboot);
|
||||
httpserver_.on("/wifi",HTTP_GET,h_wifi__get);
|
||||
httpserver_.on("/wifi",HTTP_POST,h_wifi__post);
|
||||
httpserver_.on("/config.json",h_config);
|
||||
httpserver_.on("/update", HTTP_GET,h_update__get);
|
||||
httpserver_.on("/update", HTTP_POST,h_update__post,h_update__post_);
|
||||
httpserver_.on("/fsca",h_fsca);
|
||||
httpserver_.on("/8012",h_8012);
|
||||
httpserver_.on("/8012/get",h_8012_get);
|
||||
httpserver_.on("/8012/setv",h_8012_setv);
|
||||
httpserver_.on("/8012/setc",h_8012_setc);
|
||||
|
||||
httpserver_.begin();
|
||||
WFilinkMillis = millis();
|
||||
|
||||
mqtt.setup();
|
||||
|
||||
if(user_ < 200){
|
||||
WiFi.mode(WIFI_OFF);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin();
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
// Serial.println("Connection Failed! Rebooting...");
|
||||
// delay(5000);
|
||||
// ESP.restart();
|
||||
WiFi.mode(WIFI_AP);
|
||||
Serial2.write("\x4D\x00\x01\x03\x0c\x00\x00\x5d\x5A",9);
|
||||
delay(1800);
|
||||
digitalWrite(25, HIGH);
|
||||
delay(1000);
|
||||
digitalWrite(25, LOW);
|
||||
|
||||
}else{
|
||||
Serial.print("> LINK:\t");
|
||||
Serial.println(WiFi.SSID().c_str());
|
||||
Serial.print("> IP:\t");
|
||||
Serial.println(WiFi.localIP());
|
||||
linkwifi = true;
|
||||
digitalWrite(19, LOW);
|
||||
Serial2.write("\x4D\x00\x01\x03\x0E\x00\x00\x5F\x5A",9);
|
||||
}
|
||||
}else{
|
||||
Serial.println("> No Link Mode");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Http_Server::loop(){
|
||||
if(user_ < 200){
|
||||
if(millis() > WFilinkMillis){
|
||||
WFilinkMillis = millis() + 1000;
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED){
|
||||
digitalWrite(19, !digitalRead(19));
|
||||
if(linkwifi){
|
||||
linkwifi = false;
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
Serial.println("> AP: ON");
|
||||
// httpserver_.stop();
|
||||
// httpserver_.begin();
|
||||
|
||||
//WiFi.begin();
|
||||
|
||||
}
|
||||
}else{
|
||||
if(!linkwifi){
|
||||
linkwifi = true;
|
||||
WiFi.mode(WIFI_STA);
|
||||
Serial.println("> AP: OFF");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
// httpserver_.stop();
|
||||
// httpserver_.begin();
|
||||
// WiFi.begin();
|
||||
digitalWrite(19, LOW);
|
||||
Serial2.write("\x4D\x00\x01\x03\x0E\x00\x00\x5F\x5A",9);
|
||||
}
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
}else{
|
||||
if(millis() > WFilinkMillis){
|
||||
WFilinkMillis = millis() + 1000;
|
||||
if(user_ == 200){
|
||||
user_++;
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
}else if(user_ == 201){
|
||||
user_++;
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, LOW);
|
||||
}else if(user_ == 202){
|
||||
user_++;
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, HIGH);
|
||||
}else if(user_ == 203){
|
||||
user_++;
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, HIGH);
|
||||
}else if(user_ == 204){
|
||||
user_++;
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, HIGH);
|
||||
}else if(user_ == 205){
|
||||
user_++;
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, LOW);
|
||||
}else if(user_ == 206){
|
||||
user_ = 200;
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
httpserver_.handleClient();
|
||||
dnsServer.processNextRequest();
|
||||
|
||||
}
|
||||
692
src/main.cpp
Normal file
692
src/main.cpp
Normal file
@@ -0,0 +1,692 @@
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "SoftwareSerial.h"
|
||||
|
||||
#include "Http_Server.h"
|
||||
#include "user_mqtt.h"
|
||||
#include "user.h"
|
||||
|
||||
//MQTT mqtt;
|
||||
Http_Server httpserver;
|
||||
EspSoftwareSerial::UART mySerial;
|
||||
|
||||
char reboot_ = 0;
|
||||
/*重启标识符
|
||||
*/
|
||||
char user_ = 0;
|
||||
/*
|
||||
*/
|
||||
|
||||
uint8_t if_mode = 0x11;
|
||||
/*
|
||||
0x1X 关
|
||||
1 自动
|
||||
2 制冷
|
||||
3 除湿
|
||||
4 送风
|
||||
5 制热
|
||||
*/
|
||||
int fan_mode = 0;
|
||||
/*
|
||||
0 自
|
||||
1 低
|
||||
2 中
|
||||
3 高
|
||||
*/
|
||||
int swing_mode = 1;
|
||||
/*
|
||||
0 off
|
||||
1 左右
|
||||
2 上下
|
||||
3 on
|
||||
*/
|
||||
int temp_mode = 24 - 16;
|
||||
/*
|
||||
+16
|
||||
*/
|
||||
|
||||
// GPIOs
|
||||
#define SEL_PIN 23
|
||||
#define CF1_PIN 13
|
||||
#define CF_PIN 15
|
||||
HLW8012 hlw8012;
|
||||
// These are the nominal values for the resistors in the circuit
|
||||
#define CURRENT_RESISTOR 0.001
|
||||
#define VOLTAGE_RESISTOR_UPSTREAM ( 5 * 470000 ) // Real: 2280k
|
||||
#define VOLTAGE_RESISTOR_DOWNSTREAM ( 1000 ) // Real 1.009k
|
||||
|
||||
|
||||
bool ifchar(unsigned char* ifdata,unsigned char* dataif,char iflen){
|
||||
// Serial.println("\n\rifchar");
|
||||
// Serial.println("len ");
|
||||
// Serial.println("cat ");
|
||||
// Serial.println("ifd ");
|
||||
for(int i = 0;iflen > i;i++){
|
||||
// Serial.print("\x1B\x5B\x41");
|
||||
// Serial.print("\x1B\x5B\x41");
|
||||
// Serial.print(i,DEC);
|
||||
// if(i > 9) {Serial.print(" ");}else{Serial.print(" ");}
|
||||
// Serial.print("\x1B\x5B\x44");Serial.print("\x1B\x5B\x44");Serial.print("\x1B\x5B\x44");
|
||||
// Serial.print("\x1B\x5B\x42");
|
||||
// Serial.print(ifdata[i],HEX);
|
||||
// if(ifdata[i] > 0x0F){Serial.print(" ");}else{Serial.print(" ");}
|
||||
// Serial.print("\x1B\x5B\x44");Serial.print("\x1B\x5B\x44");Serial.print("\x1B\x5B\x44");
|
||||
// Serial.print("\x1B\x5B\x42");
|
||||
// Serial.print(dataif[i],HEX);
|
||||
// if(dataif[i] > 0x0F){Serial.print(" ");}else{Serial.print(" ");}
|
||||
if(ifdata[i] != dataif[i]){
|
||||
// Serial.println("false\r\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Serial.println("true\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void hlw8012_cf1_interrupt(){
|
||||
hlw8012.cf1_interrupt();
|
||||
}
|
||||
void hlw8012_cf_interrupt(){
|
||||
hlw8012.cf_interrupt();
|
||||
}
|
||||
void hlw8012_init(){
|
||||
// Initialize HLW8012
|
||||
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
|
||||
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
|
||||
// * currentWhen is the value in sel_pin to select current sampling
|
||||
// * set use_interrupts to false, we will have to call handle() in the main loop to do the sampling
|
||||
// * set pulse_timeout to 500ms for a fast response but losing precision (that's ~24W precision :( )
|
||||
hlw8012.begin(CF_PIN, CF1_PIN, SEL_PIN, HIGH, true);
|
||||
// These values are used to calculate current, voltage and power factors as per datasheet formula
|
||||
// These are the nominal values for the Sonoff POW resistors:
|
||||
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
|
||||
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
|
||||
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
|
||||
hlw8012.setResistors(CURRENT_RESISTOR, VOLTAGE_RESISTOR_UPSTREAM, VOLTAGE_RESISTOR_DOWNSTREAM);
|
||||
|
||||
// Show default (as per datasheet) multipliers
|
||||
Serial.print("[HLW] Default current multiplier : "); Serial.println(hlw8012.getCurrentMultiplier());
|
||||
Serial.print("[HLW] Default voltage multiplier : "); Serial.println(hlw8012.getVoltageMultiplier());
|
||||
Serial.print("[HLW] Default power multiplier : "); Serial.println(hlw8012.getPowerMultiplier());
|
||||
Serial.println();
|
||||
|
||||
attachInterrupt(CF1_PIN,hlw8012_cf1_interrupt,CHANGE);
|
||||
attachInterrupt(CF_PIN ,hlw8012_cf_interrupt ,CHANGE);
|
||||
|
||||
//打印当前功率
|
||||
//hlw8012.getActivePower();
|
||||
// Serial.print("[TEM] Active Power (W) : "); Serial.println(hlw8012.getActivePower());
|
||||
// hlw8012.setMode(MODE_CURRENT);
|
||||
// delay(2000);
|
||||
// //hlw8012.getCurrent();
|
||||
// Serial.print("[TEM] Current (A) : "); Serial.println(hlw8012.getCurrent());
|
||||
|
||||
// hlw8012.setMode(MODE_VOLTAGE);
|
||||
// delay(2000);
|
||||
// //hlw8012.getVoltage();
|
||||
// Serial.print("[TEM] Voltage (V) : "); Serial.println(hlw8012.getVoltage());
|
||||
delay(2000);
|
||||
if(LittleFS.exists("/8012c")){
|
||||
File file = LittleFS.open("/8012c");
|
||||
if(!file || file.isDirectory()){
|
||||
Serial.println("ERROR NO 8012 C DATA");
|
||||
}else{
|
||||
String data;
|
||||
data = "";
|
||||
while (file.available())
|
||||
{
|
||||
data += file.readString();
|
||||
}
|
||||
file.close();
|
||||
Serial.print("8012 C DATA ");
|
||||
Serial.println(data);
|
||||
hlw8012.setCurrentMultiplier(data.toDouble());
|
||||
}
|
||||
}
|
||||
if(LittleFS.exists("/8012v")){
|
||||
File file = LittleFS.open("/8012v");
|
||||
if(!file || file.isDirectory()){
|
||||
Serial.println("ERROR NO 8012 V DATA");
|
||||
}else{
|
||||
String data;
|
||||
data = "";
|
||||
while (file.available())
|
||||
{
|
||||
data += file.readString();
|
||||
}
|
||||
file.close();
|
||||
Serial.print("8012 V DATA ");
|
||||
Serial.println(data);
|
||||
hlw8012.setVoltageMultiplier(data.toDouble());
|
||||
}
|
||||
}
|
||||
if(LittleFS.exists("/8012p")){
|
||||
File file = LittleFS.open("/8012p");
|
||||
if(!file || file.isDirectory()){
|
||||
Serial.println("ERROR NO 8012 P DATA");
|
||||
}else{
|
||||
String data;
|
||||
data = "";
|
||||
while (file.available())
|
||||
{
|
||||
data += file.readString();
|
||||
}
|
||||
file.close();
|
||||
Serial.print("8012 P DATA ");
|
||||
Serial.println(data);
|
||||
hlw8012.setPowerMultiplier(data.toDouble());
|
||||
}
|
||||
}
|
||||
delay(2000);
|
||||
//calibrate();
|
||||
/*
|
||||
hlw8012.expectedActivePower(60.0);
|
||||
hlw8012.expectedVoltage(218.0);
|
||||
hlw8012.expectedCurrent(60.0 / 230.0);
|
||||
|
||||
// Show corrected factors
|
||||
Serial.print("[HLW] New current multiplier : "); Serial.println(hlw8012.getCurrentMultiplier());
|
||||
Serial.print("[HLW] New voltage multiplier : "); Serial.println(hlw8012.getVoltageMultiplier());
|
||||
Serial.print("[HLW] New power multiplier : "); Serial.println(hlw8012.getPowerMultiplier());
|
||||
Serial.println();
|
||||
|
||||
|
||||
*/
|
||||
|
||||
Serial.print("[HLW] Active Power (W) : "); Serial.println(hlw8012.getActivePower());
|
||||
Serial.print("[HLW] Voltage (V) : "); Serial.println(hlw8012.getVoltage());
|
||||
Serial.print("[HLW] Current (A) : "); Serial.println(hlw8012.getCurrent());
|
||||
Serial.print("[HLW] Apparent Power (VA) : "); Serial.println(hlw8012.getApparentPower());
|
||||
Serial.print("[HLW] Power Factor (%) : "); Serial.println((int) (100 * hlw8012.getPowerFactor()));
|
||||
|
||||
}
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200); //设置串口波特率
|
||||
Serial.println(""); //启动消息
|
||||
|
||||
|
||||
|
||||
Serial2.begin(115200,SERIAL_8N1,16,17);
|
||||
pinMode(26,INPUT);
|
||||
pinMode(27,INPUT);
|
||||
//digitalWrite(26, LOW);
|
||||
//delay(10);
|
||||
//digitalWrite(26, HIGH);
|
||||
int i = 6000;
|
||||
while (i>0)
|
||||
{
|
||||
delay(1);
|
||||
i--;
|
||||
if(!digitalRead(26)){
|
||||
Serial.println("> IR Init OK");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Serial1.begin(19200); //(27,26)
|
||||
Serial1.write("\x00",1);
|
||||
delay(50);
|
||||
Serial1.write("\x16\x08\x00\x1E\x08",5);
|
||||
//mySerial.begin(19200,EspSoftwareSerial::SWSERIAL_8N1, 26, 27, false, 95, 11);
|
||||
|
||||
pinMode(4,INPUT);
|
||||
pinMode(5,OUTPUT);
|
||||
pinMode(18,OUTPUT);
|
||||
pinMode(19,OUTPUT);
|
||||
|
||||
pinMode(23,OUTPUT);
|
||||
pinMode(13,INPUT);
|
||||
pinMode(15,INPUT);
|
||||
|
||||
pinMode(25,OUTPUT);
|
||||
//pinMode(14,OUTPUT);
|
||||
if(!digitalRead(4)){
|
||||
user_ = 200;
|
||||
}
|
||||
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, HIGH);
|
||||
|
||||
//digitalWrite(14, HIGH);
|
||||
digitalWrite(25, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(25, LOW);
|
||||
delay(2000);
|
||||
Serial2.write("\x4D\x00\x01\x03\x0D\x00\x00\x5E\x5A",9);
|
||||
|
||||
// Serial2.write("\x4D\x00\x01\x03\x0E\x00\x00\x5F\x5A",9);
|
||||
// Serial2.write((u8_t)0x4d);
|
||||
// Serial2.write((u8_t)0x00);
|
||||
// Serial2.write(0x01);
|
||||
// Serial2.write(0x03);
|
||||
// Serial2.write(0x0C);
|
||||
// Serial2.write(0x00);
|
||||
// Serial2.write(0x00);
|
||||
// Serial2.write(0x5D);
|
||||
// Serial2.write(0x5A);
|
||||
// Serial.print(Serial2.readString());
|
||||
// delay(2000);
|
||||
|
||||
hlw8012.toggleMode();
|
||||
delay(1000);
|
||||
hlw8012.toggleMode();
|
||||
|
||||
|
||||
Serial.println("> FS Init...");
|
||||
if(!LittleFS.begin(false)){
|
||||
Serial.println("FS Mount Failed");
|
||||
delay(10000);
|
||||
// blinker_key = "";
|
||||
}else{
|
||||
|
||||
}
|
||||
|
||||
hlw8012_init();
|
||||
|
||||
httpserver.setup();
|
||||
}
|
||||
|
||||
// unsigned char u0data[5];
|
||||
// char u0len = 0;
|
||||
unsigned char u1data[220];
|
||||
char u1len = 0;
|
||||
unsigned char u2data[20];
|
||||
char u2len = 0;
|
||||
unsigned long U1dataMillis;
|
||||
|
||||
|
||||
void loop(){
|
||||
httpserver.loop();
|
||||
if(Serial.available()){
|
||||
unsigned char get = Serial.read();
|
||||
Serial.print(get,HEX);
|
||||
Serial.print(" ");
|
||||
if(get == 0x30){
|
||||
Serial1.write("\x00",1);
|
||||
delay(50);
|
||||
//mySerial.write("\x16\x82\x00\x98\x08\x16\x8A\x0E\x01\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\xDB\x08",24);
|
||||
Serial1.write("\x16\x02\x10\xFF\x00\x01\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\x54\x08",21);
|
||||
}else if(get == 0x31){
|
||||
//xTaskCreate(TaskmySerial,"Task mySerial",2048,NULL,2,NULL);
|
||||
}else if(get == 0x39){
|
||||
Serial1.write("\x00",1);
|
||||
delay(50);
|
||||
Serial1.write("\x16\x0C\x00\x22\x08",5);
|
||||
Serial.println("U1 RELINK");
|
||||
}else{
|
||||
//mySerial.write("\x16\x88\x02\x55\x01\xF6\x08",7);
|
||||
Serial1.write("\x16\x08\x00\x1E\x08",5);
|
||||
//mySerial.available();
|
||||
}
|
||||
}
|
||||
if(Serial1.available()){
|
||||
unsigned char get = Serial1.read();
|
||||
u1data[u1len] = get;
|
||||
u1len++;
|
||||
if(u1len > 220){u1len = 0;Serial.println("> u1data len error");}
|
||||
U1dataMillis = millis();
|
||||
Serial.println("U1 ++++++++++");
|
||||
// Serial.println("> u1data ");
|
||||
// Serial.print(u2len,DEC);
|
||||
// Serial.print(" ");
|
||||
// Serial.print(get,HEX);
|
||||
// Serial.print(" ");
|
||||
}
|
||||
if(u1len != 0){
|
||||
if(U1dataMillis + 100 < millis()){
|
||||
if(u1data[u1len -1] == 0x08){
|
||||
Serial.println("U1 U1U1U1U1U1U1");
|
||||
if(ifchar(u1data,(uint8_t*)"\x16\x82\x00\x98\x08\x16\x8A\x0E",8)){
|
||||
Serial.println("U1 OK DATA:");
|
||||
Serial.println("if_mode_ir");
|
||||
Serial.println(if_mode,HEX);
|
||||
if(u1data[0x12] == 0x20){
|
||||
if_mode = 0x10 + u1data[0x08] + 1;
|
||||
}else{
|
||||
if_mode = u1data[0x08] + 1;
|
||||
}
|
||||
Serial.println("if_mode_ir");
|
||||
Serial.println(if_mode,HEX);
|
||||
fan_mode = u1data[10];
|
||||
if((u1data[12] == 0)&&(u1data[13] == 0)){
|
||||
swing_mode = 0;
|
||||
}else if((u1data[12] == 1)&&(u1data[13] == 0)){
|
||||
swing_mode = 1;
|
||||
}else if((u1data[12] == 0)&&(u1data[13] == 1)){
|
||||
swing_mode = 2;
|
||||
}else if((u1data[12] == 1)&&(u1data[13] == 1)){
|
||||
swing_mode = 3;
|
||||
}
|
||||
temp_mode = u1data[9];
|
||||
|
||||
mqtt.pust();
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(18, LOW);
|
||||
}else{
|
||||
if(ifchar(u1data,(uint8_t*)"\x16\x8C\xD0",3)){
|
||||
delay(1000);
|
||||
Serial1.write("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",10);
|
||||
}
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, HIGH);
|
||||
|
||||
digitalWrite(5, LOW);
|
||||
delay(500);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
Serial.println("U1 ERROR DATA:");
|
||||
// char i = 0;
|
||||
// Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ");
|
||||
// for(;u1len > i;){
|
||||
// for(char x = 0;x < 17;x++){
|
||||
// //Serial.print(i,DEC);
|
||||
// //Serial.print("\t");
|
||||
// //Serial.println(u2data[u2len],HEX);
|
||||
// if(u1len > i){
|
||||
// if(u1data[i] < 0x10){
|
||||
// Serial.print("0");
|
||||
// }
|
||||
// Serial.print(u1data[i],HEX);
|
||||
// Serial.print(" ");
|
||||
// i++;
|
||||
// }
|
||||
// }
|
||||
// Serial.print("\r\n");
|
||||
// }
|
||||
}
|
||||
char i = 0;
|
||||
Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ");
|
||||
for(;u1len > i;){
|
||||
for(char x = 0;x < 16;x++){
|
||||
//Serial.print(i,DEC);
|
||||
//Serial.print("\t");
|
||||
//Serial.println(u2data[u2len],HEX);
|
||||
if(u1len > i){
|
||||
if(u1data[i] < 0x10){
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.print(u1data[i],HEX);
|
||||
Serial.print(" ");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
Serial.print("\r\n");
|
||||
}
|
||||
u1len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Serial2.available()){
|
||||
unsigned char get = Serial2.read();
|
||||
// Serial.print(u2len,DEC);
|
||||
// Serial.print(" ");
|
||||
// Serial.print(get,HEX);
|
||||
// Serial.print(" ");
|
||||
u2data[u2len] = get;
|
||||
u2len++;
|
||||
if(u2len > 20){u2len = 0;Serial.println("> u2data len error");}
|
||||
if(get == 0x5a){
|
||||
Serial.println("if_mode");
|
||||
Serial.println(if_mode,HEX);
|
||||
if(ifchar(u2data,(uint8_t*)"\x4C\x00\x00\x00\x4C\x5A",6)){
|
||||
digitalWrite(5, HIGH);
|
||||
digitalWrite(18, HIGH);
|
||||
digitalWrite(19, HIGH);
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x16\x02\x00\x00\x64\x5A",7)){
|
||||
digitalWrite(5, LOW);
|
||||
digitalWrite(18, LOW);
|
||||
delay(500);
|
||||
digitalWrite(19, LOW);
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x01\x00\x4D\x5A",6)){//打开空调
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
//if_mode = 0x13;
|
||||
if(if_mode >> 4 == 1){
|
||||
if_mode = if_mode - 0x10;
|
||||
}
|
||||
Serial.println("if_mode");
|
||||
Serial.println(if_mode,HEX);
|
||||
mqtt.irpost_();
|
||||
Serial.println("if_mode");
|
||||
Serial.println(if_mode,HEX);
|
||||
//mySerial.write("\x16\x82\x00\x98\x08\x16\x8A\x0E\x01\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\xDB\x08",24);
|
||||
//mySerial.write("\x16\x02\x10\xFF\x00\x01\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\x54\x08",21);
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x02\x00\x4E\x5A",6)){//关闭空调
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
Serial.println("if_mode_sat");
|
||||
Serial.println(if_mode,HEX);
|
||||
if(if_mode >> 4 != 1){
|
||||
if_mode = if_mode + 0x10;
|
||||
}
|
||||
Serial.println("if_mode_if");
|
||||
Serial.println(if_mode,HEX);
|
||||
mqtt.irpost_();
|
||||
Serial.println("if_mode_ir");
|
||||
Serial.println(if_mode,HEX);
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x03\x00\x4F\x5A",6)){//制热模式
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
//if(if_mode >> 4 == 0){
|
||||
if_mode = 5;
|
||||
//}else{
|
||||
// if_mode = 0x10 + 5;
|
||||
//}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x04\x00\x50\x5A",6)){//制冷空调
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
if(if_mode >> 4 == 0){
|
||||
if_mode = 0 + 2;
|
||||
}else{
|
||||
if_mode = 0x10 + 2;
|
||||
}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x05\x00\x51\x5A",6)){//风大
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
if(fan_mode != 3){
|
||||
fan_mode++;
|
||||
}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x06\x00\x52\x5A",6)){//风小
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
if(fan_mode != 0){
|
||||
fan_mode--;
|
||||
}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x08\x00\x54\x5A",6)){//开扫风
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
swing_mode = 3;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x09\x00\x55\x5A",6)){//关扫风
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
swing_mode = 0;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x12\x00\x5E\x5A",6)){//温度调高
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
if(temp_mode != (32-16)){
|
||||
temp_mode++;
|
||||
}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x13\x00\x5F\x5A",6)){//温度调低
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
if(temp_mode != 0){
|
||||
temp_mode--;
|
||||
}
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x1E\x76\x5A",6)){//30
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 30 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x1D\x75\x5A",6)){//29
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 29 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x1C\x74\x5A",6)){//28
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 28 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x1B\x73\x5A",6)){//27
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 27 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x1A\x72\x5A",6)){//26
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 26 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x19\x71\x5A",6)){//25
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 25 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x18\x70\x5A",6)){//24
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 24 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x17\x6F\x5A",6)){//23
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 23 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x16\x6E\x5A",6)){//22
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 28 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x15\x6D\x5A",6)){//21
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 21 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x14\x6C\x5A",6)){//20
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 20 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x13\x6B\x5A",6)){//19
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 19 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x12\x6A\x5A",6)){//18
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 18 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x11\x69\x5A",6)){//17
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 17 - 16;
|
||||
mqtt.irpost_();
|
||||
}else if(ifchar(u2data,(uint8_t*)"\x4C\x00\x0B\x01\x10\x68\x5A",6)){//16
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
delay(500);
|
||||
digitalWrite(5, LOW);
|
||||
temp_mode = 16 - 16;
|
||||
mqtt.irpost_();
|
||||
}else{
|
||||
Serial.println("U2 ERROR DATA:");
|
||||
char i = 0;
|
||||
Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ");
|
||||
for(;u2len > i;){
|
||||
for(char x = 0;x < 17;x++){
|
||||
//Serial.print(i,DEC);
|
||||
//Serial.print("\t");
|
||||
//Serial.println(u2data[u2len],HEX);
|
||||
if(u2len > i){
|
||||
if(u2data[i] < 0x10){
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.print(u2data[i],HEX);
|
||||
Serial.print(" ");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
Serial.print("\r\n");
|
||||
}
|
||||
digitalWrite(5, LOW);
|
||||
delay(500);
|
||||
digitalWrite(18, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
}
|
||||
u2len = 0;
|
||||
Serial.println("if_mode");
|
||||
Serial.println(if_mode,HEX);
|
||||
}
|
||||
}
|
||||
|
||||
if(reboot_ > 0){
|
||||
if(reboot_ == 100){
|
||||
ESP.restart();
|
||||
}
|
||||
reboot_++;
|
||||
}
|
||||
}
|
||||
350
src/user_mqtt.cpp
Normal file
350
src/user_mqtt.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
#include "user_mqtt.h"
|
||||
|
||||
#include "user.h"
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
String mqtt_server;
|
||||
String mqtt_user;
|
||||
String mqtt_pass;
|
||||
String mqtt_uid;
|
||||
|
||||
WiFiClient esp_mqtt_Client;
|
||||
PubSubClient mqtt_client(esp_mqtt_Client);
|
||||
|
||||
unsigned long mqttrelinkMillis;
|
||||
|
||||
uint8_t ir_dev[] = "\005\x0F\xFF\x02\x54\007";
|
||||
/*
|
||||
0 mode
|
||||
1 off
|
||||
2 id1
|
||||
3 id2
|
||||
4 jy
|
||||
5 fan
|
||||
*/
|
||||
|
||||
void irpost(){
|
||||
digitalWrite(18, HIGH);
|
||||
Serial1.write("\x00",1);
|
||||
delay(49);
|
||||
char data[] = "\x16\x02\x10\xFF\x00\x01\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x02\x54\x08";
|
||||
//char data[] = "\x16\x02\x10\x00\x00\x00\x08\x03\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\x54\x08";
|
||||
data[3] = ir_dev[2];
|
||||
data[18] = ir_dev[3];
|
||||
data[19] = ir_dev[4];
|
||||
Serial.print("mode ");Serial.print(if_mode,HEX);
|
||||
Serial.print(" fan ");Serial.print(fan_mode,HEX);
|
||||
Serial.print(" swing ");Serial.print(swing_mode,HEX);
|
||||
Serial.print(" temp ");Serial.println(temp_mode + 16,DEC);
|
||||
if(if_mode < 0x10){
|
||||
//data[19]++;
|
||||
data[ir_dev[0]] = if_mode - 1;
|
||||
data[ir_dev[1]] = 0x21;
|
||||
data[19] = data[19] + data[ir_dev[0]];
|
||||
data[ir_dev[5]] = fan_mode;
|
||||
data[19] = data[19] + fan_mode;
|
||||
if(swing_mode == 1){
|
||||
data[9] = 1;
|
||||
data[19] = data[19] + 1;
|
||||
}else if(swing_mode == 2){
|
||||
data[10] = 1;
|
||||
data[19] = data[19] + 1;
|
||||
}else if(swing_mode == 3){
|
||||
data[9] = 1;
|
||||
data[10] = 1;
|
||||
data[19] = data[19] + 2;
|
||||
}
|
||||
data[6] = data[6] - 0x0A + temp_mode;
|
||||
data[19] = data[19] - 0x0A + temp_mode;
|
||||
}
|
||||
|
||||
Serial.println("ifok");
|
||||
char i = 0;
|
||||
Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ");
|
||||
for(;21 > i;){
|
||||
for(char x = 0;x < 17;x++){
|
||||
//Serial.print(i,DEC);
|
||||
//Serial.print("\t");
|
||||
//Serial.println(u2data[u2len],HEX);
|
||||
if(21 > i){
|
||||
if(data[i] < 0x10){
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.print(data[i],HEX);
|
||||
Serial.print(" ");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
Serial.print("\r\n");
|
||||
}
|
||||
Serial1.write(data,21);
|
||||
}
|
||||
|
||||
void post(String id,String data){
|
||||
mqtt_client.beginPublish(id.c_str(), data.length(), false);
|
||||
mqtt_client.print(data);
|
||||
mqtt_client.endPublish();
|
||||
}
|
||||
|
||||
void mqtt_pust(){
|
||||
String id;
|
||||
String data;
|
||||
data = "";
|
||||
|
||||
id = "jcmdev/" + mqtt_uid + "/rx/fan";
|
||||
switch (fan_mode)
|
||||
{
|
||||
case 0:
|
||||
data = "auto";
|
||||
break;
|
||||
case 1:
|
||||
data = "low";
|
||||
break;
|
||||
case 2:
|
||||
data = "medium";
|
||||
break;
|
||||
case 3:
|
||||
data = "high";
|
||||
break;
|
||||
|
||||
default:
|
||||
data = String(fan_mode);
|
||||
break;
|
||||
}
|
||||
post(id,data);
|
||||
|
||||
id = "jcmdev/" + mqtt_uid + "/rx/mode";
|
||||
switch (if_mode)
|
||||
{
|
||||
case 0:
|
||||
data = "off";
|
||||
break;
|
||||
case 1:
|
||||
data = "auto";
|
||||
break;
|
||||
case 2:
|
||||
data = "cool";
|
||||
break;
|
||||
case 3:
|
||||
data = "dry";
|
||||
break;
|
||||
case 4:
|
||||
data = "fan_only";
|
||||
break;
|
||||
case 5:
|
||||
data = "heat";
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.println(if_mode,HEX);
|
||||
if(if_mode >> 4 == 1){
|
||||
data = "off";
|
||||
}else{
|
||||
data = String(if_mode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
post(id,data);
|
||||
|
||||
id = "jcmdev/" + mqtt_uid + "/rx/fan/sw";
|
||||
switch (swing_mode)
|
||||
{
|
||||
case 0:
|
||||
data = "off";
|
||||
break;
|
||||
case 1:
|
||||
data = "左右";
|
||||
break;
|
||||
case 2:
|
||||
data = "上下";
|
||||
break;
|
||||
case 3:
|
||||
data = "on";
|
||||
break;
|
||||
|
||||
default:
|
||||
data = String(swing_mode);
|
||||
break;
|
||||
}
|
||||
post(id,data);
|
||||
|
||||
post("jcmdev/" + mqtt_uid + "/rx/t",String(temp_mode + 16));
|
||||
}
|
||||
|
||||
void MQTT::pust(){
|
||||
mqtt_pust();
|
||||
}
|
||||
void MQTT::irpost_(){
|
||||
//Serial.print("mode ");Serial.print(if_mode,HEX);
|
||||
irpost();
|
||||
}
|
||||
|
||||
void addstart(){
|
||||
String id;
|
||||
String json;
|
||||
id = "homeassistant/climate/" + mqtt_uid + "/IR/config";
|
||||
json = "{";
|
||||
json += "\"availability\":[{";
|
||||
json += "\"topic\":\"jcmdev/" + mqtt_uid + "/state\",";
|
||||
json += "\"value_template\":\"{{ value_json.state }}\"}],";
|
||||
json += "\"device\":{";
|
||||
json += "\"identifiers\":[\"" + mqtt_uid + "\"],";
|
||||
json += "\"manufacturer\":\"ms__jiang\",";
|
||||
json += "\"model\":\"" + mqtt_uid + "\",";
|
||||
json += "\"name\":\"" + mqtt_uid + "\",";
|
||||
json += "\"configuration_url\":\"http://" + WiFi.localIP().toString() + "\"},";
|
||||
json += "\"json_attributes_topic\":\"jcmdev/" + mqtt_uid + "\",";
|
||||
json += "\"object_id\":\"" + mqtt_uid + "_" + "IR" + "\",";
|
||||
json += "\"unique_id\":\"" + mqtt_uid + "_" + "IR" + "\",";
|
||||
json += "\"max_temp\":\"32\",";
|
||||
json += "\"min_temp\":\"16\",";
|
||||
json += "\"mode_command_topic\":\"jcmdev/" + mqtt_uid + "/tx/mode\",";
|
||||
json += "\"mode_state_topic\":\"jcmdev/" + mqtt_uid + "/rx/mode\",";
|
||||
|
||||
json += "\"fan_mode_command_topic\":\"jcmdev/" + mqtt_uid + "/tx/fan\",";
|
||||
json += "\"fan_mode_state_topic\":\"jcmdev/" + mqtt_uid + "/rx/fan\",";
|
||||
|
||||
json += "\"swing_modes\":[\"on\",\"左右\",\"上下\",\"off\"],";
|
||||
json += "\"swing_mode_command_topic\":\"jcmdev/" + mqtt_uid + "/tx/fan/sw\",";
|
||||
json += "\"swing_mode_state_topic\":\"jcmdev/" + mqtt_uid + "/rx/fan/sw\",";
|
||||
|
||||
json += "\"temperature_command_topic\":\"jcmdev/" + mqtt_uid + "/tx/t\",";
|
||||
json += "\"temperature_state_topic\":\"jcmdev/" + mqtt_uid + "/rx/t\",";
|
||||
json += "\"temperature_unit\":\"C\",";
|
||||
json += "\"current_temperature_topic\":\"jcmdev/" + mqtt_uid + "/rx/temp\"";
|
||||
json += "}";
|
||||
post(id,json);
|
||||
|
||||
id = "jcmdev/" + mqtt_uid + "/tx/mode";
|
||||
mqtt_client.subscribe(id.c_str());
|
||||
id = "jcmdev/" + mqtt_uid + "/tx/fan";
|
||||
mqtt_client.subscribe(id.c_str());
|
||||
id = "jcmdev/" + mqtt_uid + "/tx/fan/sw";
|
||||
mqtt_client.subscribe(id.c_str());
|
||||
id = "jcmdev/" + mqtt_uid + "/tx/t";
|
||||
mqtt_client.subscribe(id.c_str());
|
||||
|
||||
delay(500);
|
||||
post("jcmdev/" + mqtt_uid + "/state","online");
|
||||
}
|
||||
|
||||
void reconnect(){
|
||||
if(!mqtt_client.connected()){
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
if (mqtt_client.connect(mqtt_uid.c_str(),mqtt_user.c_str(),mqtt_pass.c_str())) {
|
||||
Serial.println("connected");
|
||||
addstart();
|
||||
delay(100);
|
||||
// mqtt_client.subscribe("#");
|
||||
mqtt_client.subscribe("homeassistant/status");
|
||||
}else{
|
||||
Serial.print("failed, rc=");
|
||||
Serial.print(mqtt_client.state());
|
||||
Serial.println(" try again in 10 seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
String data = "";
|
||||
String id = "";
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i = 0; i < length; i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
data += (char)payload[i];
|
||||
}
|
||||
id = topic;
|
||||
Serial.println();
|
||||
//Serial.println(id);
|
||||
|
||||
|
||||
if (id == "homeassistant/status"){
|
||||
if(data == "online"){
|
||||
addstart();
|
||||
}
|
||||
}else if(id == "jcmdev/" + mqtt_uid + "/tx/mode"){
|
||||
if(data == "off"){
|
||||
if(if_mode < 0x10){if_mode += 0x10;}
|
||||
irpost();
|
||||
}else if(data == "auto"){
|
||||
if_mode = 1;
|
||||
irpost();
|
||||
}else if(data == "cool"){
|
||||
if_mode = 2;
|
||||
irpost();
|
||||
}else if(data == "dry"){
|
||||
if_mode = 3;
|
||||
irpost();
|
||||
}else if(data == "fan_only"){
|
||||
if_mode = 4;
|
||||
irpost();
|
||||
}else if(data == "heat"){
|
||||
if_mode = 5;
|
||||
irpost();
|
||||
}
|
||||
}else if(id == "jcmdev/" + mqtt_uid + "/tx/fan"){
|
||||
if(data == "auto"){
|
||||
fan_mode = 0;
|
||||
irpost();
|
||||
}else if(data == "low"){
|
||||
fan_mode = 1;
|
||||
irpost();
|
||||
}else if(data == "medium"){
|
||||
fan_mode = 2;
|
||||
irpost();
|
||||
}else if(data == "high"){
|
||||
fan_mode = 3;
|
||||
irpost();
|
||||
}
|
||||
}else if(id == "jcmdev/" + mqtt_uid + "/tx/fan/sw"){
|
||||
if(data == "off"){
|
||||
swing_mode = 0;
|
||||
irpost();
|
||||
}else if(data == "左右"){
|
||||
swing_mode = 1;
|
||||
irpost();
|
||||
}else if(data == "上下"){
|
||||
swing_mode = 2;
|
||||
irpost();
|
||||
}else if(data == "on"){
|
||||
swing_mode = 3;
|
||||
irpost();
|
||||
}
|
||||
}else if(id == "jcmdev/" + mqtt_uid + "/tx/t"){
|
||||
temp_mode = data.toInt() - 16;
|
||||
irpost();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MQTT::setup(){
|
||||
mqtt_server = "homeassistant.lan";
|
||||
mqtt_user = "user";
|
||||
mqtt_pass = "passwd";
|
||||
mqtt_uid = host;
|
||||
mqtt_server = "192.168.2.168";
|
||||
mqtt_user = "mqtt";
|
||||
mqtt_pass = "mqtt";
|
||||
mqtt_uid = host;
|
||||
|
||||
if(mqtt_server != ""){
|
||||
mqtt_client.setServer(mqtt_server.c_str(), 1883);
|
||||
mqtt_client.setCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::loop(){
|
||||
mqtt_client.loop();
|
||||
if (WiFi.waitForConnectResult() == WL_CONNECTED){
|
||||
if(mqttrelinkMillis < millis()){
|
||||
mqttrelinkMillis = millis() + 10000;
|
||||
if(!mqtt_client.connected()){
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
test/README
Normal file
10
test/README
Normal file
@@ -0,0 +1,10 @@
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
1
update.bat
Normal file
1
update.bat
Normal file
@@ -0,0 +1 @@
|
||||
curl http://N2-IR_48B858.lan/update?curl= -F "file=@.pio\build\esp32solo1\firmware.bin"
|
||||
96
web/index.html
Normal file
96
web/index.html
Normal file
@@ -0,0 +1,96 @@
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>N2-IR</title>
|
||||
<style>
|
||||
.c,body{text-align:center;font-family:verdana}
|
||||
div,input{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box;}
|
||||
input,button,.msg{border-radius:.3rem;width: 100%}
|
||||
button,input[type='button'],input[type='submit']
|
||||
{
|
||||
cursor:pointer;
|
||||
border:0;
|
||||
background-color:#1fa3ec;
|
||||
color:#fff;
|
||||
line-height:2.4rem;
|
||||
font-size:1.2rem;
|
||||
width:100%
|
||||
}
|
||||
input[type='file']{border:1px solid #1fa3ec}
|
||||
.wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}
|
||||
a{color:#000;font-weight:700;text-decoration:none}
|
||||
a:hover{color:#1fa3ec;text-decoration:underline}
|
||||
.q
|
||||
{
|
||||
height:16px;
|
||||
margin:0;
|
||||
padding:0 5px;
|
||||
text-align:right;
|
||||
min-width:38px;
|
||||
long:right
|
||||
}
|
||||
.q.q-0:after{background-position-x:0}
|
||||
.q.q-1:after{background-position-x:-16px}
|
||||
.q.q-2:after{background-position-x:-32px}
|
||||
.q.q-3:after{background-position-x:-48px}
|
||||
.q.q-4:after{background-position-x:-64px}
|
||||
.q.l:before{background-position-x:-80px;padding-right:5px}
|
||||
.ql .q{long:left}
|
||||
.q:after,
|
||||
.q:before{
|
||||
content:'';
|
||||
width:16px;
|
||||
height:16px;
|
||||
display:inline-block;
|
||||
background-repeat:no-repeat;
|
||||
background-position: 16px 0;
|
||||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');
|
||||
}@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){
|
||||
.q:before,
|
||||
.q:after {
|
||||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');
|
||||
background-size: 95px 16px;
|
||||
}
|
||||
}.msg{
|
||||
padding:20px;
|
||||
margin:20px 0;
|
||||
border:1px solid #eee;
|
||||
border-left-width:5px;
|
||||
border-left-color:#777
|
||||
}.msg h4{margin-top:0;margin-bottom:5px}
|
||||
.msg.P{border-left-color:#1fa3ec}
|
||||
.msg.P h4{color:#1fa3ec}
|
||||
.msg.D{border-left-color:#dc3630}
|
||||
.msg.D h4{color:#dc3630}
|
||||
.msg.S{border-left-color: #5cb85c}
|
||||
.msg.S h4{color: #5cb85c}
|
||||
dt{font-weight:bold}
|
||||
dd{margin:0;padding:0 0 0.5em 0;min-height:12px}
|
||||
td{vertical-align: top;}
|
||||
.h{display:none}
|
||||
button.D{background-color:#dc3630}
|
||||
button.F{background-color:#777}
|
||||
body.invert,body.invert a,body.invert h1 {
|
||||
background-color:#060606;color:#fff;
|
||||
}body.invert
|
||||
.msg{
|
||||
color:#fff;
|
||||
background-color:#282828;
|
||||
border-top:1px solid #555;
|
||||
border-right:1px solid #555;
|
||||
border-bottom:1px solid #555;
|
||||
}body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}
|
||||
input:disabled {opacity: 0.5;}
|
||||
</style>
|
||||
</head>
|
||||
<body class="invert">
|
||||
<div class="wrap">
|
||||
|
||||
<meta charset='UTF-8'>N2-IR</meta>
|
||||
<button onclick='location.href=("wifi#p")' type="button">WiFi设置</button>
|
||||
<br><br>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
150
web/wifi/index.html
Normal file
150
web/wifi/index.html
Normal file
@@ -0,0 +1,150 @@
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>Set WiFi</title>
|
||||
<style>
|
||||
.c,body{text-align:center;font-family:verdana}
|
||||
div,input{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box;}
|
||||
input,button,.msg{border-radius:.3rem;width: 100%}
|
||||
button,input[type='button'],input[type='submit']
|
||||
{
|
||||
cursor:pointer;
|
||||
border:0;
|
||||
background-color:#1fa3ec;
|
||||
color:#fff;
|
||||
line-height:2.4rem;
|
||||
font-size:1.2rem;
|
||||
width:100%
|
||||
}
|
||||
input[type='file']{border:1px solid #1fa3ec}
|
||||
.wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}
|
||||
a{color:#000;font-weight:700;text-decoration:none}
|
||||
a:hover{color:#1fa3ec;text-decoration:underline}
|
||||
.q
|
||||
{
|
||||
height:16px;
|
||||
margin:0;
|
||||
padding:0 5px;
|
||||
text-align:right;
|
||||
min-width:38px;
|
||||
long:right
|
||||
}
|
||||
.q.q-0:after{background-position-x:0}
|
||||
.q.q-1:after{background-position-x:-16px}
|
||||
.q.q-2:after{background-position-x:-32px}
|
||||
.q.q-3:after{background-position-x:-48px}
|
||||
.q.q-4:after{background-position-x:-64px}
|
||||
.q.l:before{background-position-x:-80px;padding-right:5px}
|
||||
.ql .q{long:left}
|
||||
.q:after,
|
||||
.q:before{
|
||||
content:'';
|
||||
width:16px;
|
||||
height:16px;
|
||||
display:inline-block;
|
||||
background-repeat:no-repeat;
|
||||
background-position: 16px 0;
|
||||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');
|
||||
}@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){
|
||||
.q:before,
|
||||
.q:after {
|
||||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');
|
||||
background-size: 95px 16px;
|
||||
}
|
||||
}.msg{
|
||||
padding:20px;
|
||||
margin:20px 0;
|
||||
border:1px solid #eee;
|
||||
border-left-width:5px;
|
||||
border-left-color:#777
|
||||
}.msg h4{margin-top:0;margin-bottom:5px}
|
||||
.msg.P{border-left-color:#1fa3ec}
|
||||
.msg.P h4{color:#1fa3ec}
|
||||
.msg.D{border-left-color:#dc3630}
|
||||
.msg.D h4{color:#dc3630}
|
||||
.msg.S{border-left-color: #5cb85c}
|
||||
.msg.S h4{color: #5cb85c}
|
||||
dt{font-weight:bold}
|
||||
dd{margin:0;padding:0 0 0.5em 0;min-height:12px}
|
||||
td{vertical-align: top;}
|
||||
.h{display:none}
|
||||
button.D{background-color:#dc3630}
|
||||
button.F{background-color:#777}
|
||||
body.invert,body.invert a,body.invert h1 {
|
||||
background-color:#060606;color:#fff;
|
||||
}body.invert
|
||||
.msg{
|
||||
color:#fff;
|
||||
background-color:#282828;
|
||||
border-top:1px solid #555;
|
||||
border-right:1px solid #555;
|
||||
border-bottom:1px solid #555;
|
||||
}body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}
|
||||
input:disabled {opacity: 0.5;}
|
||||
</style>
|
||||
<script>
|
||||
function c(l){
|
||||
document.getElementById('s').value=l.innerText||l.textContent;
|
||||
p = l.nextElementSibling.classList.contains('l');
|
||||
document.getElementById('p').disabled = !p;
|
||||
if(p)document.getElementById('p').focus();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="invert">
|
||||
<div class="wrap" id="main">
|
||||
|
||||
<meta charset='UTF-8'>正在加载</meta>
|
||||
<br><br>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
window.onload = function (){
|
||||
const Http = new XMLHttpRequest();
|
||||
Http.open("GET",'wifi/scan');
|
||||
Http.send();
|
||||
Http.onreadystatechange = (e) => {
|
||||
var readyState = Http.readyState;
|
||||
var status = Http.status;
|
||||
if(readyState == 4){
|
||||
var html = '';
|
||||
cathttp = Http.responseText;
|
||||
var data = JSON.parse(cathttp);
|
||||
for(i = 0;i < data.length;i++){
|
||||
w = data[i];
|
||||
if((!w[5])&&(w[0] != "")){
|
||||
html += "<div><a href=\"wifi#p\" onclick=\"c(this)\">";
|
||||
html += w[0];
|
||||
html += "</a><div role=\"img\" aria-label=\"";
|
||||
html += w[3];
|
||||
html += "%\" title=\"";
|
||||
html += "\" class=\"q ";
|
||||
if(w[4] != 0) {html += "l";}
|
||||
html += " ";
|
||||
if(w[3] > -60){
|
||||
html += " q-4";
|
||||
}else if(w[3] > -80){
|
||||
html += " q-3";
|
||||
}else if(w[3] > -85){
|
||||
html += " q-2";
|
||||
}else{
|
||||
html += " q-1";
|
||||
}
|
||||
html += " ";
|
||||
html += "\"></div><div class=\"q h\">";
|
||||
html += w[3];
|
||||
html += "%</div></div>";
|
||||
html += "";
|
||||
}
|
||||
}
|
||||
html += "<form method=\"GET\" action=\"wifi/set\"><label for=\"s\">SSID</label><input id=\"s\" name=\"s\" maxlength=\"32\" autocorrect=\"off\" autocapitalize=\"none\" placeholder=\"\"><br><label for=\"p\">Password</label><input id=\"p\" name=\"p\" maxlength=\"64\" type=\"password\" placeholder=\"********\"><hr><p></p>";
|
||||
html += "<br><br><button type=\"submit\">Save</button></form>";
|
||||
html += "<form method=\"\" action=\"/\"><button type=\"submit\">返回</button></form>";
|
||||
document.getElementById('main').innerHTML = html;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user