Merge pull request #1 from technyon/w5500
Add support for W5x00 LAN modules
This commit is contained in:
@@ -4,6 +4,11 @@ set(ARDUINO_BOARD "ESP32 Dev Module [esp32.esp32]")
|
||||
|
||||
project(nuki_hub CXX)
|
||||
|
||||
# ARDUHAL_LOG_LEVEL_NONE, define ARDUHAL_LOG_LEVEL_ERROR, define ARDUHAL_LOG_LEVEL_WARN, define ARDUHAL_LOG_LEVEL_INFO,
|
||||
# define ARDUHAL_LOG_LEVEL_DEBUG, define ARDUHAL_LOG_LEVEL_VERBOSE
|
||||
|
||||
set(LOG_LEVEL ARDUHAL_LOG_LEVEL_NONE)
|
||||
|
||||
include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
lib/Crc16
|
||||
@@ -11,13 +16,19 @@ include_directories(${PROJECT_NAME}
|
||||
lib/nuki_ble/src
|
||||
lib/ESP32_BLE_Arduino-1.0.1/src
|
||||
lib/WiFiManager
|
||||
lib/pubsubclient/src/
|
||||
lib/pubsubclient/src
|
||||
lib/WebServer/src
|
||||
include
|
||||
)
|
||||
|
||||
file(GLOB SRCFILES
|
||||
# "Lib/FreeRTOS/src/*.c"
|
||||
# "Lib/FreeRTOS/src/*.cpp"
|
||||
Pins.h
|
||||
Network.cpp
|
||||
networkDevices/NetworkDevice.h
|
||||
networkDevices/WifiDevice.cpp
|
||||
networkDevices/W5500Device.cpp
|
||||
NukiWrapper.cpp
|
||||
MqttTopics.h
|
||||
WebCfgServer.cpp
|
||||
@@ -25,6 +36,7 @@ file(GLOB SRCFILES
|
||||
PreferencesKeys.h
|
||||
SpiffsCookie.cpp
|
||||
Version.h
|
||||
include/RTOS.h
|
||||
lib/ESP32_BLE_Arduino-1.0.1/src/*.cpp
|
||||
lib/ESP32_BLE_Arduino-1.0.1/src/*.h
|
||||
lib/WiFiManager/WiFiManager.cpp
|
||||
@@ -41,6 +53,8 @@ file(GLOB_RECURSE SRCFILESREC
|
||||
lib/NimBLE-Arduino/src/*.c
|
||||
lib/NimBLE-Arduino/src/*.cpp
|
||||
lib/NimBLE-Arduino/src/*.h
|
||||
lib/WebServer/src/*.cpp
|
||||
lib/WebServer/src/*.h
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
@@ -49,16 +63,23 @@ add_executable(${PROJECT_NAME}
|
||||
${SRCFILESREC}
|
||||
)
|
||||
|
||||
# Arduino.h is included in hello_world.cpp, so link with Arduino core
|
||||
target_compile_definitions(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
ARDUHAL_LOG_LEVEL=${LOG_LEVEL}
|
||||
CORE_DEBUG_LEVEL=${LOG_LEVEL}
|
||||
CONFIG_NIMBLE_CPP_LOG_LEVEL=0
|
||||
)
|
||||
|
||||
target_link_arduino_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
core
|
||||
WiFi
|
||||
Update
|
||||
WebServer
|
||||
# WebServer
|
||||
DNSServer
|
||||
Preferences
|
||||
SPIFFS
|
||||
Ethernet
|
||||
# esp32
|
||||
# Wire
|
||||
# FS
|
||||
|
||||
144
Network.cpp
144
Network.cpp
@@ -1,18 +1,21 @@
|
||||
#include "Network.h"
|
||||
#include "WiFi.h"
|
||||
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
|
||||
#include "Arduino.h"
|
||||
#include "MqttTopics.h"
|
||||
#include "PreferencesKeys.h"
|
||||
#include "Pins.h"
|
||||
|
||||
Network* nwInst;
|
||||
|
||||
Network::Network(Preferences* preferences)
|
||||
: _mqttClient(_wifiClient),
|
||||
_preferences(preferences)
|
||||
Network::Network(const NetworkDeviceType networkDevice, Preferences* preferences)
|
||||
: _preferences(preferences),
|
||||
_networkDeviceType(networkDevice)
|
||||
{
|
||||
nwInst = this;
|
||||
|
||||
_hostname = _preferences->getString(preference_hostname);
|
||||
setupDevice(networkDevice);
|
||||
|
||||
_configTopics.reserve(5);
|
||||
_configTopics.push_back(mqtt_topic_config_button_enabled);
|
||||
_configTopics.push_back(mqtt_topic_config_led_enabled);
|
||||
@@ -21,53 +24,46 @@ Network::Network(Preferences* preferences)
|
||||
_configTopics.push_back(mqtt_topic_config_auto_lock);
|
||||
}
|
||||
|
||||
Network::~Network()
|
||||
{
|
||||
if(_device != nullptr)
|
||||
{
|
||||
delete _device;
|
||||
_device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Network::setupDevice(const NetworkDeviceType hardware)
|
||||
{
|
||||
switch(hardware)
|
||||
{
|
||||
case NetworkDeviceType::W5500:
|
||||
Serial.println(F("Network device: W5500"));
|
||||
_device = new W5500Device(_hostname, _preferences);
|
||||
break;
|
||||
case NetworkDeviceType::WiFi:
|
||||
Serial.println(F("Network device: Builtin WiFi"));
|
||||
_device = new WifiDevice(_hostname);
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown network device type, defaulting to WiFi"));
|
||||
_device = new WifiDevice(_hostname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Network::initialize()
|
||||
{
|
||||
String hostname = _preferences->getString(preference_hostname);
|
||||
if(hostname == "")
|
||||
if(_hostname == "")
|
||||
{
|
||||
hostname = "nukihub";
|
||||
_preferences->putString(preference_hostname, hostname);
|
||||
_hostname = "nukihub";
|
||||
_preferences->putString(preference_hostname, _hostname);
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
|
||||
// it is a good practice to make sure your code sets wifi mode how you want it.
|
||||
|
||||
//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wm;
|
||||
|
||||
std::vector<const char *> wm_menu;
|
||||
wm_menu.push_back("wifi");
|
||||
wm_menu.push_back("exit");
|
||||
wm.setShowInfoUpdate(false);
|
||||
wm.setMenu(wm_menu);
|
||||
wm.setHostname(hostname);
|
||||
|
||||
bool res = false;
|
||||
|
||||
if(_cookie.isSet())
|
||||
{
|
||||
Serial.println(F("Opening WiFi configuration portal."));
|
||||
_cookie.clear();
|
||||
res = wm.startConfigPortal();
|
||||
}
|
||||
else
|
||||
{
|
||||
res = wm.autoConnect(); // password protected ap
|
||||
}
|
||||
|
||||
if(!res) {
|
||||
Serial.println(F("Failed to connect. Wait for ESP restart."));
|
||||
delay(10000);
|
||||
ESP.restart();
|
||||
}
|
||||
else {
|
||||
Serial.print(F("WiFi connected."));
|
||||
Serial.println(WiFi.localIP().toString());
|
||||
}
|
||||
_device->initialize();
|
||||
|
||||
Serial.print(F("Host name: "));
|
||||
Serial.println(hostname);
|
||||
Serial.println(_hostname);
|
||||
|
||||
const char* brokerAddr = _preferences->getString(preference_mqtt_broker).c_str();
|
||||
strcpy(_mqttBrokerAddr, brokerAddr);
|
||||
@@ -119,14 +115,15 @@ void Network::initialize()
|
||||
Serial.print(F(":"));
|
||||
Serial.println(port);
|
||||
|
||||
_mqttClient.setServer(_mqttBrokerAddr, port);
|
||||
_mqttClient.setCallback(Network::onMqttDataReceivedCallback);
|
||||
_device->mqttClient()->setServer(_mqttBrokerAddr, port);
|
||||
_device->mqttClient()->setCallback(Network::onMqttDataReceivedCallback);
|
||||
}
|
||||
|
||||
|
||||
bool Network::reconnect()
|
||||
{
|
||||
while (!_mqttClient.connected() && millis() > _nextReconnect)
|
||||
_mqttConnected = false;
|
||||
|
||||
while (!_device->mqttClient()->connected() && millis() > _nextReconnect)
|
||||
{
|
||||
Serial.println(F("Attempting MQTT connection"));
|
||||
bool success = false;
|
||||
@@ -134,19 +131,19 @@ bool Network::reconnect()
|
||||
if(strlen(_mqttUser) == 0)
|
||||
{
|
||||
Serial.println(F("MQTT: Connecting without credentials"));
|
||||
success = _mqttClient.connect(_preferences->getString(preference_hostname).c_str());
|
||||
success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(F("MQTT: Connecting with user: ")); Serial.println(_mqttUser);
|
||||
success = _mqttClient.connect(_preferences->getString(preference_hostname).c_str(), _mqttUser, _mqttPass);
|
||||
success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str(), _mqttUser, _mqttPass);
|
||||
}
|
||||
|
||||
|
||||
if (success) {
|
||||
if (success)
|
||||
{
|
||||
Serial.println(F("MQTT connected"));
|
||||
_mqttConnected = true;
|
||||
delay(200);
|
||||
delay(100);
|
||||
subscribe(mqtt_topic_lock_action);
|
||||
|
||||
for(auto topic : _configTopics)
|
||||
@@ -157,7 +154,7 @@ bool Network::reconnect()
|
||||
else
|
||||
{
|
||||
Serial.print(F("MQTT connect failed, rc="));
|
||||
Serial.println(_mqttClient.state());
|
||||
Serial.println(_device->mqttClient()->state());
|
||||
_mqttConnected = false;
|
||||
_nextReconnect = millis() + 5000;
|
||||
}
|
||||
@@ -167,13 +164,26 @@ bool Network::reconnect()
|
||||
|
||||
void Network::update()
|
||||
{
|
||||
if(!WiFi.isConnected())
|
||||
long ts = millis();
|
||||
|
||||
if((ts - _lastMaintain) > 1000)
|
||||
{
|
||||
Serial.println(F("WiFi not connected"));
|
||||
vTaskDelay( 1000 / portTICK_PERIOD_MS);
|
||||
_device->update();
|
||||
|
||||
if(!_device->isConnected())
|
||||
{
|
||||
Serial.println(F("Network not connected. Trying reconnect."));
|
||||
bool success = _device->reconnect();
|
||||
Serial.println(success ? F("Reconnect successful") : F("Reconnect failed"));
|
||||
}
|
||||
}
|
||||
|
||||
if(!_mqttClient.connected())
|
||||
if(!_device->isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_device->mqttClient()->connected())
|
||||
{
|
||||
bool success = reconnect();
|
||||
if(!success)
|
||||
@@ -188,7 +198,7 @@ void Network::update()
|
||||
_presenceCsv = nullptr;
|
||||
}
|
||||
|
||||
_mqttClient.loop();
|
||||
_device->mqttClient()->loop();
|
||||
}
|
||||
|
||||
void Network::onMqttDataReceivedCallback(char *topic, byte *payload, unsigned int length)
|
||||
@@ -235,7 +245,7 @@ void Network::publishKeyTurnerState(const Nuki::KeyTurnerState& keyTurnerState,
|
||||
{
|
||||
char str[50];
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.lockState != lastKeyTurnerState.lockState)
|
||||
if((_firstTunerStatePublish || keyTurnerState.lockState != lastKeyTurnerState.lockState) && keyTurnerState.lockState != Nuki::LockState::Undefined)
|
||||
{
|
||||
memset(&str, 0, sizeof(str));
|
||||
lockstateToString(keyTurnerState.lockState, str);
|
||||
@@ -323,7 +333,7 @@ void Network::publishFloat(const char* topic, const float value, const uint8_t p
|
||||
dtostrf(value, 0, precision, str);
|
||||
char path[200] = {0};
|
||||
buildMqttPath(topic, path);
|
||||
_mqttClient.publish(path, str);
|
||||
_device->mqttClient()->publish(path, str);
|
||||
}
|
||||
|
||||
void Network::publishInt(const char *topic, const int value)
|
||||
@@ -333,7 +343,7 @@ void Network::publishInt(const char *topic, const int value)
|
||||
itoa(value, str, 10);
|
||||
char path[200] = {0};
|
||||
buildMqttPath(topic, path);
|
||||
_mqttClient.publish(path, str);
|
||||
_device->mqttClient()->publish(path, str);
|
||||
}
|
||||
|
||||
void Network::publishBool(const char *topic, const bool value)
|
||||
@@ -342,14 +352,14 @@ void Network::publishBool(const char *topic, const bool value)
|
||||
str[0] = value ? '1' : '0';
|
||||
char path[200] = {0};
|
||||
buildMqttPath(topic, path);
|
||||
_mqttClient.publish(path, str);
|
||||
_device->mqttClient()->publish(path, str);
|
||||
}
|
||||
|
||||
void Network::publishString(const char *topic, const char *value)
|
||||
{
|
||||
char path[200] = {0};
|
||||
buildMqttPath(topic, path);
|
||||
_mqttClient.publish(path, value);
|
||||
_device->mqttClient()->publish(path, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -384,14 +394,12 @@ void Network::subscribe(const char *path)
|
||||
{
|
||||
char prefixedPath[500];
|
||||
buildMqttPath(path, prefixedPath);
|
||||
_mqttClient.subscribe(prefixedPath);
|
||||
_device->mqttClient()->subscribe(prefixedPath);
|
||||
}
|
||||
|
||||
void Network::restartAndConfigureWifi()
|
||||
{
|
||||
_cookie.set();
|
||||
delay(200);
|
||||
ESP.restart();
|
||||
_device->reconfigure();
|
||||
}
|
||||
|
||||
bool Network::comparePrefixedPath(const char *fullPath, const char *subPath)
|
||||
|
||||
24
Network.h
24
Network.h
@@ -1,20 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClient.h>
|
||||
#include "networkDevices/NetworkDevice.h"
|
||||
#include "networkDevices/WifiDevice.h"
|
||||
#include "networkDevices/W5500Device.h"
|
||||
#include <Preferences.h>
|
||||
#include <vector>
|
||||
#include "NukiConstants.h"
|
||||
#include "SpiffsCookie.h"
|
||||
|
||||
enum class NetworkDeviceType
|
||||
{
|
||||
WiFi,
|
||||
W5500
|
||||
};
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
explicit Network(Preferences* preferences);
|
||||
virtual ~Network() = default;
|
||||
explicit Network(const NetworkDeviceType networkDevice, Preferences* preferences);
|
||||
virtual ~Network();
|
||||
|
||||
void initialize();
|
||||
void update();
|
||||
void setupDevice(const NetworkDeviceType hardware);
|
||||
void initializeW5500();
|
||||
|
||||
bool isMqttConnected();
|
||||
|
||||
@@ -45,10 +55,10 @@ private:
|
||||
|
||||
bool reconnect();
|
||||
|
||||
PubSubClient _mqttClient;
|
||||
WiFiClient _wifiClient;
|
||||
NetworkDevice* _device = nullptr;
|
||||
Preferences* _preferences;
|
||||
SpiffsCookie _cookie;
|
||||
String _hostname;
|
||||
NetworkDeviceType _networkDeviceType;
|
||||
|
||||
bool _mqttConnected = false;
|
||||
|
||||
@@ -64,6 +74,8 @@ private:
|
||||
|
||||
bool _firstTunerStatePublish = true;
|
||||
|
||||
long _lastMaintain = 0;
|
||||
|
||||
void (*_lockActionReceivedCallback)(const char* value) = nullptr;
|
||||
void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
|
||||
};
|
||||
|
||||
5
Pins.h
Normal file
5
Pins.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define NETWORK_SELECT 26
|
||||
#define ETHERNET_CS_PIN 5
|
||||
#define ETHERNET_RESET_PIN 33
|
||||
@@ -12,6 +12,10 @@
|
||||
#define preference_cred_user "crdusr"
|
||||
#define preference_cred_password "crdpass"
|
||||
#define preference_presence_detection_timeout "prdtimeout"
|
||||
#define preference_has_mac_saved "hasmac"
|
||||
#define preference_has_mac_byte_0 "macb0"
|
||||
#define preference_has_mac_byte_1 "macb1"
|
||||
#define preference_has_mac_byte_2 "macb2"
|
||||
|
||||
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -43,6 +43,22 @@ Just enable pairing mode on the NUKI lock and power on the ESP32. Pairing should
|
||||
|
||||
- presence/devices: List of detected bluetooth devices as CSV. Can be used for presence detection
|
||||
|
||||
# Connecting via LAN (Optional)
|
||||
|
||||
If you prefer to connect to the MQTT Broker via LAN instead of WiFi, you can use a Wizent W5x00 Module (W5100, W5200, W5500 are supported).
|
||||
To connect, just wire the module and connect the LAN cable:
|
||||
|
||||
- Connect W5x00 to ESP32 SPI0:<br>
|
||||
W5x00 SCK to GPIO18<br>
|
||||
W5x00 MISO to GPIOGPIO19<br>
|
||||
W5x00 MOSI to GPIO23<br>
|
||||
W5x00 CS/SS to GPIO5
|
||||
- Additionally connect:<br>
|
||||
W5x00 reset to GPIO33
|
||||
- Last but not least, on the ESP32 bridge GPIO26 and GND. This let's the firmware know that a LAN Module is connected
|
||||
|
||||
Wifi is now disabled, and the module doesn't boot into WifiManager anymore.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
This is a third party software for NUKI smart door locks. This project or any of it's authors aren't associated with Nuki Home Solutions GmbH. Please refer for official products and offical support to their website:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define nuki_hub_version "1.11"
|
||||
#define nuki_hub_version "2.0"
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "WebCfgServer.h"
|
||||
#include <WiFiClient.h>
|
||||
#include "PreferencesKeys.h"
|
||||
#include "Version.h"
|
||||
#include "hardware/WifiEthServer.h"
|
||||
|
||||
WebCfgServer::WebCfgServer(NukiWrapper* nuki, Network* network, Preferences* preferences)
|
||||
: server(80),
|
||||
WebCfgServer::WebCfgServer(NukiWrapper* nuki, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal)
|
||||
: _server(ethServer),
|
||||
_nuki(nuki),
|
||||
_network(network),
|
||||
_preferences(preferences)
|
||||
_preferences(preferences),
|
||||
_allowRestartToPortal(allowRestartToPortal)
|
||||
{
|
||||
String str = _preferences->getString(preference_cred_user);
|
||||
|
||||
@@ -26,43 +27,46 @@ WebCfgServer::WebCfgServer(NukiWrapper* nuki, Network* network, Preferences* pre
|
||||
|
||||
void WebCfgServer::initialize()
|
||||
{
|
||||
server.on("/", [&]() {
|
||||
if (_hasCredentials && !server.authenticate(_credUser, _credPassword)) {
|
||||
return server.requestAuthentication();
|
||||
_server.on("/", [&]() {
|
||||
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
|
||||
return _server.requestAuthentication();
|
||||
}
|
||||
String response = "";
|
||||
buildHtml(response);
|
||||
server.send(200, "text/html", response);
|
||||
_server.send(200, "text/html", response);
|
||||
});
|
||||
server.on("/cred", [&]() {
|
||||
if (_hasCredentials && !server.authenticate(_credUser, _credPassword)) {
|
||||
return server.requestAuthentication();
|
||||
_server.on("/cred", [&]() {
|
||||
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
|
||||
return _server.requestAuthentication();
|
||||
}
|
||||
String response = "";
|
||||
buildCredHtml(response);
|
||||
server.send(200, "text/html", response);
|
||||
_server.send(200, "text/html", response);
|
||||
});
|
||||
server.on("/wifi", [&]() {
|
||||
if (_hasCredentials && !server.authenticate(_credUser, _credPassword)) {
|
||||
return server.requestAuthentication();
|
||||
_server.on("/wifi", [&]() {
|
||||
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
|
||||
return _server.requestAuthentication();
|
||||
}
|
||||
String response = "";
|
||||
buildConfigureWifiHtml(response);
|
||||
server.send(200, "text/html", response);
|
||||
_server.send(200, "text/html", response);
|
||||
});
|
||||
server.on("/wifimanager", [&]() {
|
||||
if (_hasCredentials && !server.authenticate(_credUser, _credPassword)) {
|
||||
return server.requestAuthentication();
|
||||
_server.on("/wifimanager", [&]() {
|
||||
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
|
||||
return _server.requestAuthentication();
|
||||
}
|
||||
if(_allowRestartToPortal)
|
||||
{
|
||||
String response = "";
|
||||
buildConfirmHtml(response, "Restarting. Connect to ESP access point to reconfigure WiFi.", 0);
|
||||
_server.send(200, "text/html", response);
|
||||
waitAndProcess(true, 2000);
|
||||
_network->restartAndConfigureWifi();
|
||||
}
|
||||
String response = "";
|
||||
buildConfirmHtml(response, "Restarting. Connect to ESP access point to reconfigure WiFi.", 0);
|
||||
server.send(200, "text/html", response);
|
||||
waitAndProcess(true, 2000);
|
||||
_network->restartAndConfigureWifi();
|
||||
});
|
||||
server.on("/method=get", [&]() {
|
||||
if (_hasCredentials && !server.authenticate(_credUser, _credPassword)) {
|
||||
return server.requestAuthentication();
|
||||
_server.on("/method=get", [&]() {
|
||||
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
|
||||
return _server.requestAuthentication();
|
||||
}
|
||||
String message = "";
|
||||
bool restartEsp = processArgs(message);
|
||||
@@ -70,7 +74,7 @@ void WebCfgServer::initialize()
|
||||
{
|
||||
String response = "";
|
||||
buildConfirmHtml(response, message);
|
||||
server.send(200, "text/html", response);
|
||||
_server.send(200, "text/html", response);
|
||||
Serial.println(F("Restarting"));
|
||||
|
||||
waitAndProcess(true, 1000);
|
||||
@@ -80,12 +84,12 @@ void WebCfgServer::initialize()
|
||||
{
|
||||
String response = "";
|
||||
buildConfirmHtml(response, message, 3);
|
||||
server.send(200, "text/html", response);
|
||||
_server.send(200, "text/html", response);
|
||||
waitAndProcess(false, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
server.begin();
|
||||
_server.begin();
|
||||
}
|
||||
|
||||
bool WebCfgServer::processArgs(String& message)
|
||||
@@ -94,11 +98,11 @@ bool WebCfgServer::processArgs(String& message)
|
||||
bool clearMqttCredentials = false;
|
||||
bool clearCredentials = false;
|
||||
|
||||
int count = server.args();
|
||||
int count = _server.args();
|
||||
for(int index = 0; index < count; index++)
|
||||
{
|
||||
String key = server.argName(index);
|
||||
String value = server.arg(index);
|
||||
String key = _server.argName(index);
|
||||
String value = _server.arg(index);
|
||||
|
||||
if(key == "MQTTSERVER")
|
||||
{
|
||||
@@ -215,7 +219,7 @@ void WebCfgServer::update()
|
||||
{
|
||||
if(!_enabled) return;
|
||||
|
||||
server.handleClient();
|
||||
_server.handleClient();
|
||||
}
|
||||
|
||||
void WebCfgServer::buildHtml(String& response)
|
||||
@@ -263,9 +267,12 @@ void WebCfgServer::buildHtml(String& response)
|
||||
response.concat("<button type=\"submit\">Edit</button>");
|
||||
response.concat("</form>");
|
||||
|
||||
response.concat("<br><br><h3>WiFi</h3>");
|
||||
response.concat("<form method=\"get\" action=\"/wifi\">");
|
||||
response.concat("<button type=\"submit\">Restart and configure wifi</button>");
|
||||
if(_allowRestartToPortal)
|
||||
{
|
||||
response.concat("<br><br><h3>WiFi</h3>");
|
||||
response.concat("<form method=\"get\" action=\"/wifi\">");
|
||||
response.concat("<button type=\"submit\">Restart and configure wifi</button>");
|
||||
}
|
||||
response.concat("</form>");
|
||||
|
||||
response.concat("</BODY>\n");
|
||||
@@ -399,7 +406,7 @@ void WebCfgServer::waitAndProcess(const bool blocking, const uint32_t duration)
|
||||
unsigned long timeout = millis() + duration;
|
||||
while(millis() < timeout)
|
||||
{
|
||||
server.handleClient();
|
||||
_server.handleClient();
|
||||
if(blocking)
|
||||
{
|
||||
delay(10);
|
||||
|
||||
@@ -20,7 +20,7 @@ enum class TokenType
|
||||
class WebCfgServer
|
||||
{
|
||||
public:
|
||||
WebCfgServer(NukiWrapper* nuki, Network* network, Preferences* preferences);
|
||||
WebCfgServer(NukiWrapper* nuki, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal);
|
||||
~WebCfgServer() = default;
|
||||
|
||||
void initialize();
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
|
||||
void waitAndProcess(const bool blocking, const uint32_t duration);
|
||||
|
||||
WebServer server;
|
||||
WebServer _server;
|
||||
NukiWrapper* _nuki;
|
||||
Network* _network;
|
||||
Preferences* _preferences;
|
||||
@@ -50,6 +50,7 @@ private:
|
||||
bool _hasCredentials = false;
|
||||
char _credUser[20] = {0};
|
||||
char _credPassword[20] = {0};
|
||||
bool _allowRestartToPortal = false;
|
||||
|
||||
bool _enabled = true;
|
||||
};
|
||||
1
include/RTOS.h
Normal file
1
include/RTOS.h
Normal file
@@ -0,0 +1 @@
|
||||
#include <FreeRTOS.h>
|
||||
147
lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino
Normal file
147
lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright (c) 2015, Majenko Technologies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* * Neither the name of Majenko Technologies nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char *ssid = "YourSSIDHere";
|
||||
const char *password = "YourPSKHere";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = 13;
|
||||
|
||||
void handleRoot() {
|
||||
digitalWrite(led, 1);
|
||||
char temp[400];
|
||||
int sec = millis() / 1000;
|
||||
int min = sec / 60;
|
||||
int hr = min / 60;
|
||||
|
||||
snprintf(temp, 400,
|
||||
|
||||
"<html>\
|
||||
<head>\
|
||||
<meta http-equiv='refresh' content='5'/>\
|
||||
<title>ESP32 Demo</title>\
|
||||
<style>\
|
||||
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
||||
</style>\
|
||||
</head>\
|
||||
<body>\
|
||||
<h1>Hello from ESP32!</h1>\
|
||||
<p>Uptime: %02d:%02d:%02d</p>\
|
||||
<img src=\"/test.svg\" />\
|
||||
</body>\
|
||||
</html>",
|
||||
|
||||
hr, min % 60, sec % 60
|
||||
);
|
||||
server.send(200, "text/html", temp);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
server.on("/test.svg", drawGraph);
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
server.onNotFound(handleNotFound);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
|
||||
void drawGraph() {
|
||||
String out = "";
|
||||
char temp[100];
|
||||
out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
|
||||
out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
|
||||
out += "<g stroke=\"black\">\n";
|
||||
int y = rand() % 130;
|
||||
for (int x = 10; x < 390; x += 10) {
|
||||
int y2 = rand() % 130;
|
||||
sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
|
||||
out += temp;
|
||||
y = y2;
|
||||
}
|
||||
out += "</g>\n</svg>\n";
|
||||
|
||||
server.send(200, "image/svg+xml", out);
|
||||
}
|
||||
304
lib/WebServer/examples/FSBrowser/FSBrowser.ino
Normal file
304
lib/WebServer/examples/FSBrowser/FSBrowser.ino
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
FSWebServer - Example WebServer with FS backend for esp8266/esp32
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
upload the contents of the data folder with MkSPIFFS Tool ("ESP32 Sketch Data Upload" in Tools menu in Arduino IDE)
|
||||
or you can upload the contents of a folder if you CD in that folder and run the following command:
|
||||
for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp32fs.local/edit; done
|
||||
|
||||
access the sample web page at http://esp32fs.local
|
||||
edit the page by going to http://esp32fs.local/edit
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
#define FILESYSTEM SPIFFS
|
||||
// You only need to format the filesystem once
|
||||
#define FORMAT_FILESYSTEM false
|
||||
#define DBG_OUTPUT_PORT Serial
|
||||
|
||||
#if FILESYSTEM == FFat
|
||||
#include <FFat.h>
|
||||
#endif
|
||||
#if FILESYSTEM == SPIFFS
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
const char* ssid = "wifi-ssid";
|
||||
const char* password = "wifi-password";
|
||||
const char* host = "esp32fs";
|
||||
WebServer server(80);
|
||||
//holds the current upload
|
||||
File fsUploadFile;
|
||||
|
||||
//format bytes
|
||||
String formatBytes(size_t bytes) {
|
||||
if (bytes < 1024) {
|
||||
return String(bytes) + "B";
|
||||
} else if (bytes < (1024 * 1024)) {
|
||||
return String(bytes / 1024.0) + "KB";
|
||||
} else if (bytes < (1024 * 1024 * 1024)) {
|
||||
return String(bytes / 1024.0 / 1024.0) + "MB";
|
||||
} else {
|
||||
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
|
||||
}
|
||||
}
|
||||
|
||||
String getContentType(String filename) {
|
||||
if (server.hasArg("download")) {
|
||||
return "application/octet-stream";
|
||||
} else if (filename.endsWith(".htm")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".html")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (filename.endsWith(".js")) {
|
||||
return "application/javascript";
|
||||
} else if (filename.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (filename.endsWith(".gif")) {
|
||||
return "image/gif";
|
||||
} else if (filename.endsWith(".jpg")) {
|
||||
return "image/jpeg";
|
||||
} else if (filename.endsWith(".ico")) {
|
||||
return "image/x-icon";
|
||||
} else if (filename.endsWith(".xml")) {
|
||||
return "text/xml";
|
||||
} else if (filename.endsWith(".pdf")) {
|
||||
return "application/x-pdf";
|
||||
} else if (filename.endsWith(".zip")) {
|
||||
return "application/x-zip";
|
||||
} else if (filename.endsWith(".gz")) {
|
||||
return "application/x-gzip";
|
||||
}
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool exists(String path){
|
||||
bool yes = false;
|
||||
File file = FILESYSTEM.open(path, "r");
|
||||
if(!file.isDirectory()){
|
||||
yes = true;
|
||||
}
|
||||
file.close();
|
||||
return yes;
|
||||
}
|
||||
|
||||
bool handleFileRead(String path) {
|
||||
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
|
||||
if (path.endsWith("/")) {
|
||||
path += "index.htm";
|
||||
}
|
||||
String contentType = getContentType(path);
|
||||
String pathWithGz = path + ".gz";
|
||||
if (exists(pathWithGz) || exists(path)) {
|
||||
if (exists(pathWithGz)) {
|
||||
path += ".gz";
|
||||
}
|
||||
File file = FILESYSTEM.open(path, "r");
|
||||
server.streamFile(file, contentType);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleFileUpload() {
|
||||
if (server.uri() != "/edit") {
|
||||
return;
|
||||
}
|
||||
HTTPUpload& upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
if (!filename.startsWith("/")) {
|
||||
filename = "/" + filename;
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
|
||||
fsUploadFile = FILESYSTEM.open(filename, "w");
|
||||
filename = String();
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
|
||||
if (fsUploadFile) {
|
||||
fsUploadFile.write(upload.buf, upload.currentSize);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (fsUploadFile) {
|
||||
fsUploadFile.close();
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void handleFileDelete() {
|
||||
if (server.args() == 0) {
|
||||
return server.send(500, "text/plain", "BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
|
||||
if (path == "/") {
|
||||
return server.send(500, "text/plain", "BAD PATH");
|
||||
}
|
||||
if (!exists(path)) {
|
||||
return server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
FILESYSTEM.remove(path);
|
||||
server.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileCreate() {
|
||||
if (server.args() == 0) {
|
||||
return server.send(500, "text/plain", "BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
|
||||
if (path == "/") {
|
||||
return server.send(500, "text/plain", "BAD PATH");
|
||||
}
|
||||
if (exists(path)) {
|
||||
return server.send(500, "text/plain", "FILE EXISTS");
|
||||
}
|
||||
File file = FILESYSTEM.open(path, "w");
|
||||
if (file) {
|
||||
file.close();
|
||||
} else {
|
||||
return server.send(500, "text/plain", "CREATE FAILED");
|
||||
}
|
||||
server.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileList() {
|
||||
if (!server.hasArg("dir")) {
|
||||
server.send(500, "text/plain", "BAD ARGS");
|
||||
return;
|
||||
}
|
||||
|
||||
String path = server.arg("dir");
|
||||
DBG_OUTPUT_PORT.println("handleFileList: " + path);
|
||||
|
||||
|
||||
File root = FILESYSTEM.open(path);
|
||||
path = String();
|
||||
|
||||
String output = "[";
|
||||
if(root.isDirectory()){
|
||||
File file = root.openNextFile();
|
||||
while(file){
|
||||
if (output != "[") {
|
||||
output += ',';
|
||||
}
|
||||
output += "{\"type\":\"";
|
||||
output += (file.isDirectory()) ? "dir" : "file";
|
||||
output += "\",\"name\":\"";
|
||||
output += String(file.name()).substring(1);
|
||||
output += "\"}";
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
output += "]";
|
||||
server.send(200, "text/json", output);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
DBG_OUTPUT_PORT.begin(115200);
|
||||
DBG_OUTPUT_PORT.print("\n");
|
||||
DBG_OUTPUT_PORT.setDebugOutput(true);
|
||||
if (FORMAT_FILESYSTEM) FILESYSTEM.format();
|
||||
FILESYSTEM.begin();
|
||||
{
|
||||
File root = FILESYSTEM.open("/");
|
||||
File file = root.openNextFile();
|
||||
while(file){
|
||||
String fileName = file.name();
|
||||
size_t fileSize = file.size();
|
||||
DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
|
||||
file = root.openNextFile();
|
||||
}
|
||||
DBG_OUTPUT_PORT.printf("\n");
|
||||
}
|
||||
|
||||
|
||||
//WIFI INIT
|
||||
DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
|
||||
if (String(WiFi.SSID()) != String(ssid)) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
}
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
DBG_OUTPUT_PORT.print(".");
|
||||
}
|
||||
DBG_OUTPUT_PORT.println("");
|
||||
DBG_OUTPUT_PORT.print("Connected! IP address: ");
|
||||
DBG_OUTPUT_PORT.println(WiFi.localIP());
|
||||
|
||||
MDNS.begin(host);
|
||||
DBG_OUTPUT_PORT.print("Open http://");
|
||||
DBG_OUTPUT_PORT.print(host);
|
||||
DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
|
||||
|
||||
|
||||
//SERVER INIT
|
||||
//list directory
|
||||
server.on("/list", HTTP_GET, handleFileList);
|
||||
//load editor
|
||||
server.on("/edit", HTTP_GET, []() {
|
||||
if (!handleFileRead("/edit.htm")) {
|
||||
server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
});
|
||||
//create file
|
||||
server.on("/edit", HTTP_PUT, handleFileCreate);
|
||||
//delete file
|
||||
server.on("/edit", HTTP_DELETE, handleFileDelete);
|
||||
//first callback is called after the request has ended with all parsed arguments
|
||||
//second callback handles file uploads at that location
|
||||
server.on("/edit", HTTP_POST, []() {
|
||||
server.send(200, "text/plain", "");
|
||||
}, handleFileUpload);
|
||||
|
||||
//called when the url is not defined here
|
||||
//use it to load content from FILESYSTEM
|
||||
server.onNotFound([]() {
|
||||
if (!handleFileRead(server.uri())) {
|
||||
server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
});
|
||||
|
||||
//get heap status, analog input value and all GPIO statuses in one json call
|
||||
server.on("/all", HTTP_GET, []() {
|
||||
String json = "{";
|
||||
json += "\"heap\":" + String(ESP.getFreeHeap());
|
||||
json += ", \"analog\":" + String(analogRead(A0));
|
||||
json += ", \"gpio\":" + String((uint32_t)(0));
|
||||
json += "}";
|
||||
server.send(200, "text/json", json);
|
||||
json = String();
|
||||
});
|
||||
server.begin();
|
||||
DBG_OUTPUT_PORT.println("HTTP server started");
|
||||
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
BIN
lib/WebServer/examples/FSBrowser/data/edit.htm.gz
Normal file
BIN
lib/WebServer/examples/FSBrowser/data/edit.htm.gz
Normal file
Binary file not shown.
BIN
lib/WebServer/examples/FSBrowser/data/favicon.ico
Normal file
BIN
lib/WebServer/examples/FSBrowser/data/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lib/WebServer/examples/FSBrowser/data/graphs.js.gz
Normal file
BIN
lib/WebServer/examples/FSBrowser/data/graphs.js.gz
Normal file
Binary file not shown.
97
lib/WebServer/examples/FSBrowser/data/index.htm
Normal file
97
lib/WebServer/examples/FSBrowser/data/index.htm
Normal file
@@ -0,0 +1,97 @@
|
||||
<!--
|
||||
FSWebServer - Example Index Page
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>ESP Monitor</title>
|
||||
<script type="text/javascript" src="graphs.js"></script>
|
||||
<script type="text/javascript">
|
||||
var heap,temp,digi;
|
||||
var reloadPeriod = 1000;
|
||||
var running = false;
|
||||
|
||||
function loadValues(){
|
||||
if(!running) return;
|
||||
var xh = new XMLHttpRequest();
|
||||
xh.onreadystatechange = function(){
|
||||
if (xh.readyState == 4){
|
||||
if(xh.status == 200) {
|
||||
var res = JSON.parse(xh.responseText);
|
||||
heap.add(res.heap);
|
||||
temp.add(res.analog);
|
||||
digi.add(res.gpio);
|
||||
if(running) setTimeout(loadValues, reloadPeriod);
|
||||
} else running = false;
|
||||
}
|
||||
};
|
||||
xh.open("GET", "/all", true);
|
||||
xh.send(null);
|
||||
};
|
||||
|
||||
function run(){
|
||||
if(!running){
|
||||
running = true;
|
||||
loadValues();
|
||||
}
|
||||
}
|
||||
|
||||
function onBodyLoad(){
|
||||
var refreshInput = document.getElementById("refresh-rate");
|
||||
refreshInput.value = reloadPeriod;
|
||||
refreshInput.onchange = function(e){
|
||||
var value = parseInt(e.target.value);
|
||||
reloadPeriod = (value > 0)?value:0;
|
||||
e.target.value = reloadPeriod;
|
||||
}
|
||||
var stopButton = document.getElementById("stop-button");
|
||||
stopButton.onclick = function(e){
|
||||
running = false;
|
||||
}
|
||||
var startButton = document.getElementById("start-button");
|
||||
startButton.onclick = function(e){
|
||||
run();
|
||||
}
|
||||
|
||||
// Example with 10K thermistor
|
||||
//function calcThermistor(v) {
|
||||
// var t = Math.log(((10230000 / v) - 10000));
|
||||
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
|
||||
// return (t>120)?0:Math.round(t*10)/10;
|
||||
//}
|
||||
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
|
||||
|
||||
temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
|
||||
heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
|
||||
digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
|
||||
run();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
|
||||
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
|
||||
<label>Period (ms):</label>
|
||||
<input type="number" id="refresh-rate"/>
|
||||
<input type="button" id="start-button" value="Start"/>
|
||||
<input type="button" id="stop-button" value="Stop"/>
|
||||
</div>
|
||||
<div id="heap"></div>
|
||||
<div id="analog"></div>
|
||||
<div id="digital"></div>
|
||||
</body>
|
||||
</html>
|
||||
74
lib/WebServer/examples/HelloServer/HelloServer.ino
Normal file
74
lib/WebServer/examples/HelloServer/HelloServer.ino
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = 13;
|
||||
|
||||
void handleRoot() {
|
||||
digitalWrite(led, 1);
|
||||
server.send(200, "text/plain", "hello from esp32!");
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
60
lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino
Normal file
60
lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
HTTP Advanced Authentication example
|
||||
Created Mar 16, 2017 by Ahmed El-Sharnoby.
|
||||
This example code is in the public domain.
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const char* www_username = "admin";
|
||||
const char* www_password = "esp32";
|
||||
// allows you to set the realm of authentication Default:"Login Required"
|
||||
const char* www_realm = "Custom Auth Realm";
|
||||
// the Content of the HTML response in case of Unautherized Access Default:empty
|
||||
String authFailResponse = "Authentication Failed";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(www_username, www_password))
|
||||
//Basic Auth Method with Custom realm and Failure Response
|
||||
//return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse);
|
||||
//Digest Auth Method with realm="Login Required" and empty Failure Response
|
||||
//return server.requestAuthentication(DIGEST_AUTH);
|
||||
//Digest Auth Method with Custom realm and empty Failure Response
|
||||
//return server.requestAuthentication(DIGEST_AUTH, www_realm);
|
||||
//Digest Auth Method with Custom realm and Failure Response
|
||||
{
|
||||
return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse);
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
42
lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
Normal file
42
lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const char* www_username = "admin";
|
||||
const char* www_password = "esp32";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(www_username, www_password)) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
57
lib/WebServer/examples/PathArgServer/PathArgServer.ino
Normal file
57
lib/WebServer/examples/PathArgServer/PathArgServer.ino
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
#include <uri/UriBraces.h>
|
||||
#include <uri/UriRegex.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(9600);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on(F("/"), []() {
|
||||
server.send(200, "text/plain", "hello from esp32!");
|
||||
});
|
||||
|
||||
server.on(UriBraces("/users/{}"), []() {
|
||||
String user = server.pathArg(0);
|
||||
server.send(200, "text/plain", "User: '" + user + "'");
|
||||
});
|
||||
|
||||
server.on(UriRegex("^\\/users\\/([0-9]+)\\/devices\\/([0-9]+)$"), []() {
|
||||
String user = server.pathArg(0);
|
||||
String device = server.pathArg(1);
|
||||
server.send(200, "text/plain", "User: '" + user + "' and Device: '" + device + "'");
|
||||
});
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
314
lib/WebServer/examples/SDWebServer/SDWebServer.ino
Normal file
314
lib/WebServer/examples/SDWebServer/SDWebServer.ino
Normal file
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
SDWebServer - Example WebServer with SD Card backend for esp8266
|
||||
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Have a FAT Formatted SD Card connected to the SPI port of the ESP8266
|
||||
The web root is the SD Card root folder
|
||||
File extensions with more than 3 charecters are not supported by the SD Library
|
||||
File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter
|
||||
index.htm is the default index (works on subfolders as well)
|
||||
|
||||
upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit
|
||||
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
#define DBG_OUTPUT_PORT Serial
|
||||
|
||||
const char* ssid = "**********";
|
||||
const char* password = "**********";
|
||||
const char* host = "esp32sd";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
static bool hasSD = false;
|
||||
File uploadFile;
|
||||
|
||||
|
||||
void returnOK() {
|
||||
server.send(200, "text/plain", "");
|
||||
}
|
||||
|
||||
void returnFail(String msg) {
|
||||
server.send(500, "text/plain", msg + "\r\n");
|
||||
}
|
||||
|
||||
bool loadFromSdCard(String path) {
|
||||
String dataType = "text/plain";
|
||||
if (path.endsWith("/")) {
|
||||
path += "index.htm";
|
||||
}
|
||||
|
||||
if (path.endsWith(".src")) {
|
||||
path = path.substring(0, path.lastIndexOf("."));
|
||||
} else if (path.endsWith(".htm")) {
|
||||
dataType = "text/html";
|
||||
} else if (path.endsWith(".css")) {
|
||||
dataType = "text/css";
|
||||
} else if (path.endsWith(".js")) {
|
||||
dataType = "application/javascript";
|
||||
} else if (path.endsWith(".png")) {
|
||||
dataType = "image/png";
|
||||
} else if (path.endsWith(".gif")) {
|
||||
dataType = "image/gif";
|
||||
} else if (path.endsWith(".jpg")) {
|
||||
dataType = "image/jpeg";
|
||||
} else if (path.endsWith(".ico")) {
|
||||
dataType = "image/x-icon";
|
||||
} else if (path.endsWith(".xml")) {
|
||||
dataType = "text/xml";
|
||||
} else if (path.endsWith(".pdf")) {
|
||||
dataType = "application/pdf";
|
||||
} else if (path.endsWith(".zip")) {
|
||||
dataType = "application/zip";
|
||||
}
|
||||
|
||||
File dataFile = SD.open(path.c_str());
|
||||
if (dataFile.isDirectory()) {
|
||||
path += "/index.htm";
|
||||
dataType = "text/html";
|
||||
dataFile = SD.open(path.c_str());
|
||||
}
|
||||
|
||||
if (!dataFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server.hasArg("download")) {
|
||||
dataType = "application/octet-stream";
|
||||
}
|
||||
|
||||
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
|
||||
DBG_OUTPUT_PORT.println("Sent less data than expected!");
|
||||
}
|
||||
|
||||
dataFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleFileUpload() {
|
||||
if (server.uri() != "/edit") {
|
||||
return;
|
||||
}
|
||||
HTTPUpload& upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
if (SD.exists((char *)upload.filename.c_str())) {
|
||||
SD.remove((char *)upload.filename.c_str());
|
||||
}
|
||||
uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
|
||||
DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename);
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
if (uploadFile) {
|
||||
uploadFile.write(upload.buf, upload.currentSize);
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize);
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (uploadFile) {
|
||||
uploadFile.close();
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteRecursive(String path) {
|
||||
File file = SD.open((char *)path.c_str());
|
||||
if (!file.isDirectory()) {
|
||||
file.close();
|
||||
SD.remove((char *)path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
file.rewindDirectory();
|
||||
while (true) {
|
||||
File entry = file.openNextFile();
|
||||
if (!entry) {
|
||||
break;
|
||||
}
|
||||
String entryPath = path + "/" + entry.name();
|
||||
if (entry.isDirectory()) {
|
||||
entry.close();
|
||||
deleteRecursive(entryPath);
|
||||
} else {
|
||||
entry.close();
|
||||
SD.remove((char *)entryPath.c_str());
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
SD.rmdir((char *)path.c_str());
|
||||
file.close();
|
||||
}
|
||||
|
||||
void handleDelete() {
|
||||
if (server.args() == 0) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
if (path == "/" || !SD.exists((char *)path.c_str())) {
|
||||
returnFail("BAD PATH");
|
||||
return;
|
||||
}
|
||||
deleteRecursive(path);
|
||||
returnOK();
|
||||
}
|
||||
|
||||
void handleCreate() {
|
||||
if (server.args() == 0) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
if (path == "/" || SD.exists((char *)path.c_str())) {
|
||||
returnFail("BAD PATH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.indexOf('.') > 0) {
|
||||
File file = SD.open((char *)path.c_str(), FILE_WRITE);
|
||||
if (file) {
|
||||
file.write(0);
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
SD.mkdir((char *)path.c_str());
|
||||
}
|
||||
returnOK();
|
||||
}
|
||||
|
||||
void printDirectory() {
|
||||
if (!server.hasArg("dir")) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg("dir");
|
||||
if (path != "/" && !SD.exists((char *)path.c_str())) {
|
||||
return returnFail("BAD PATH");
|
||||
}
|
||||
File dir = SD.open((char *)path.c_str());
|
||||
path = String();
|
||||
if (!dir.isDirectory()) {
|
||||
dir.close();
|
||||
return returnFail("NOT DIR");
|
||||
}
|
||||
dir.rewindDirectory();
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
server.send(200, "text/json", "");
|
||||
WiFiClient client = server.client();
|
||||
|
||||
server.sendContent("[");
|
||||
for (int cnt = 0; true; ++cnt) {
|
||||
File entry = dir.openNextFile();
|
||||
if (!entry) {
|
||||
break;
|
||||
}
|
||||
|
||||
String output;
|
||||
if (cnt > 0) {
|
||||
output = ',';
|
||||
}
|
||||
|
||||
output += "{\"type\":\"";
|
||||
output += (entry.isDirectory()) ? "dir" : "file";
|
||||
output += "\",\"name\":\"";
|
||||
output += entry.name();
|
||||
output += "\"";
|
||||
output += "}";
|
||||
server.sendContent(output);
|
||||
entry.close();
|
||||
}
|
||||
server.sendContent("]");
|
||||
dir.close();
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
if (hasSD && loadFromSdCard(server.uri())) {
|
||||
return;
|
||||
}
|
||||
String message = "SDCARD Not Detected\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
DBG_OUTPUT_PORT.print(message);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
DBG_OUTPUT_PORT.begin(115200);
|
||||
DBG_OUTPUT_PORT.setDebugOutput(true);
|
||||
DBG_OUTPUT_PORT.print("\n");
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
DBG_OUTPUT_PORT.print("Connecting to ");
|
||||
DBG_OUTPUT_PORT.println(ssid);
|
||||
|
||||
// Wait for connection
|
||||
uint8_t i = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds
|
||||
delay(500);
|
||||
}
|
||||
if (i == 21) {
|
||||
DBG_OUTPUT_PORT.print("Could not connect to");
|
||||
DBG_OUTPUT_PORT.println(ssid);
|
||||
while (1) {
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Connected! IP address: ");
|
||||
DBG_OUTPUT_PORT.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin(host)) {
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
DBG_OUTPUT_PORT.println("MDNS responder started");
|
||||
DBG_OUTPUT_PORT.print("You can now connect to http://");
|
||||
DBG_OUTPUT_PORT.print(host);
|
||||
DBG_OUTPUT_PORT.println(".local");
|
||||
}
|
||||
|
||||
|
||||
server.on("/list", HTTP_GET, printDirectory);
|
||||
server.on("/edit", HTTP_DELETE, handleDelete);
|
||||
server.on("/edit", HTTP_PUT, handleCreate);
|
||||
server.on("/edit", HTTP_POST, []() {
|
||||
returnOK();
|
||||
}, handleFileUpload);
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
DBG_OUTPUT_PORT.println("HTTP server started");
|
||||
|
||||
if (SD.begin(SS)) {
|
||||
DBG_OUTPUT_PORT.println("SD Card initialized.");
|
||||
hasSD = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
674
lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm
Normal file
674
lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm
Normal file
@@ -0,0 +1,674 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>SD Editor</title>
|
||||
<style type="text/css" media="screen">
|
||||
.contextMenu {
|
||||
z-index: 300;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
border: 1px solid #444;
|
||||
background-color: #F5F5F5;
|
||||
display: none;
|
||||
box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-weight:bold;
|
||||
}
|
||||
.contextMenu ul {
|
||||
list-style: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.contextMenu li {
|
||||
position: relative;
|
||||
min-width: 60px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contextMenu span {
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
}
|
||||
.contextMenu li:hover { background: #444; }
|
||||
.contextMenu li:hover span { color: #EEE; }
|
||||
|
||||
.css-treeview ul, .css-treeview li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.css-treeview input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.css-treeview {
|
||||
font: normal 11px Verdana, Arial, Sans-serif;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.css-treeview span {
|
||||
color: #00f;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.css-treeview span:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.css-treeview input + label + ul {
|
||||
margin: 0 0 0 22px;
|
||||
}
|
||||
|
||||
.css-treeview input ~ ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview label::before {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.css-treeview input:disabled + label {
|
||||
cursor: default;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.css-treeview input:checked:not(:disabled) ~ ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview label::before {
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAACgCAYAAAAFOewUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApxJREFUeNrslM1u00AQgGdthyalFFOK+ClIIKQKyqUVQvTEE3DmAhLwAhU8QZoH4A2Q2gMSFace4MCtJ8SPBFwAkRuiHKpA6sRN/Lu7zG5i14kctaUqRGhGXnu9O/Pt7MzsMiklvF+9t2kWTDvyIrAsA0aKRRi1T0C/hJ4LUbt5/8rNpWVlp8RSr9J40b48fxFaTQ9+ft8EZ6MJYb0Ok+dnYGpmPgXwKIAvLx8vYXc5GdMAQJgQEkpjRTh36TS2U+DWW/D17WuYgm8pwJyY1npZsZKOxImOV1I/h4+O6vEg5GCZBpgmA6hX8wHKUHDRBXQYicQ4rlc3Tf0VMs8DHBS864F2YFspjgUYjKX/Az3gsdQd2eeBHwmdGWXHcgBGSkZXOXohcEXebRoQcAgjqediNY+AVyu3Z3sAKqfKoGMsewBeEIOPgQxxPJIjcGH6qtL/0AdADzKGnuuD+2tLK7Q8DhHHbOBW+KEzcHLuYc82MkEUekLiwuvVH+guQBQzOG4XdAb8EOcRcqQvDkY2iCLuxECJ43JobMXoutqGgDa2T7UqLKwt9KRyuxKVByqVXXqIoCCUCAqhUOioTWC7G4TQEOD0APy2/7G2Xpu1J4+lxeQ4TXBbITDpoVelRN/BVFbwu5oMMJUBhoXy5tmdRcMwymP2OLQaLjx9/vnBo6V3K6izATmSnMa0Dq7ferIohJhr1p01zrlz49rZF4OMs8JkX23vVQzYp+wbYGV/KpXKjvspl8tsIKCrMNAYFxj2GKS5ZWxg4ewKsJfaGMIY5KXqPz8LBBj6+yDvVP79+yDp/9F9oIx3OisHWwe7Oal0HxCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgwD8E/BZgAP0qhKj3rXO7AAAAAElFTkSuQmCC") no-repeat;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview span, .css-treeview label::before {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.css-treeview label {
|
||||
background-position: 18px 0;
|
||||
}
|
||||
|
||||
.css-treeview label::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
margin: 0 22px 0 0;
|
||||
vertical-align: middle;
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
|
||||
.css-treeview input:checked + label::before {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
|
||||
/* webkit adjacent element selector bugfix */
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0)
|
||||
{
|
||||
.css-treeview{
|
||||
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes webkit-adjacent-element-selector-bugfix
|
||||
{
|
||||
from {
|
||||
padding: 0;
|
||||
}
|
||||
to {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#uploader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height:28px;
|
||||
line-height: 24px;
|
||||
padding-left: 10px;
|
||||
background-color: #444;
|
||||
color:#EEE;
|
||||
}
|
||||
#tree {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width:200px;
|
||||
padding: 8px;
|
||||
}
|
||||
#editor, #preview {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 200px;
|
||||
}
|
||||
#preview {
|
||||
background-color: #EEE;
|
||||
padding:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function createFileUploader(element, tree, editor){
|
||||
var xmlHttp;
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = false;
|
||||
input.name = "data";
|
||||
document.getElementById(element).appendChild(input);
|
||||
var path = document.createElement("input");
|
||||
path.id = "upload-path";
|
||||
path.type = "text";
|
||||
path.name = "path";
|
||||
path.defaultValue = "/";
|
||||
document.getElementById(element).appendChild(path);
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = 'Upload';
|
||||
document.getElementById(element).appendChild(button);
|
||||
var mkdir = document.createElement("button");
|
||||
mkdir.innerHTML = 'MkDir';
|
||||
document.getElementById(element).appendChild(mkdir);
|
||||
var mkfile = document.createElement("button");
|
||||
mkfile.innerHTML = 'MkFile';
|
||||
document.getElementById(element).appendChild(mkfile);
|
||||
|
||||
function httpPostProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
else {
|
||||
tree.refreshPath(path.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
function createPath(p){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("path", p);
|
||||
xmlHttp.open("PUT", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
|
||||
mkfile.onclick = function(e){
|
||||
if(path.value.indexOf(".") === -1) return;
|
||||
createPath(path.value);
|
||||
editor.loadUrl(path.value);
|
||||
};
|
||||
mkdir.onclick = function(e){
|
||||
if(path.value.length < 2) return;
|
||||
var dir = path.value
|
||||
if(dir.indexOf(".") !== -1){
|
||||
if(dir.lastIndexOf("/") === 0) return;
|
||||
dir = dir.substring(0, dir.lastIndexOf("/"));
|
||||
}
|
||||
createPath(dir);
|
||||
};
|
||||
button.onclick = function(e){
|
||||
if(input.files.length === 0){
|
||||
return;
|
||||
}
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("data", input.files[0], path.value);
|
||||
xmlHttp.open("POST", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
input.onchange = function(e){
|
||||
if(input.files.length === 0) return;
|
||||
var filename = input.files[0].name;
|
||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
|
||||
var name = /(.*)\.[^.]+$/.exec(filename)[1];
|
||||
if(typeof name !== undefined){
|
||||
if(name.length > 8) name = name.substring(0, 8);
|
||||
filename = name;
|
||||
}
|
||||
if(typeof ext !== undefined){
|
||||
if(ext === "html") ext = "htm";
|
||||
else if(ext === "jpeg") ext = "jpg";
|
||||
filename = filename + "." + ext;
|
||||
}
|
||||
if(path.value === "/" || path.value.lastIndexOf("/") === 0){
|
||||
path.value = "/"+filename;
|
||||
} else {
|
||||
path.value = path.value.substring(0, path.value.lastIndexOf("/")+1)+filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTree(element, editor){
|
||||
var preview = document.getElementById("preview");
|
||||
var treeRoot = document.createElement("div");
|
||||
treeRoot.className = "css-treeview";
|
||||
document.getElementById(element).appendChild(treeRoot);
|
||||
|
||||
function loadDownload(path){
|
||||
document.getElementById('download-frame').src = path+"?download=true";
|
||||
}
|
||||
|
||||
function loadPreview(path){
|
||||
document.getElementById("editor").style.display = "none";
|
||||
preview.style.display = "block";
|
||||
preview.innerHTML = '<img src="'+path+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
|
||||
}
|
||||
|
||||
function fillFolderMenu(el, path){
|
||||
var list = document.createElement("ul");
|
||||
el.appendChild(list);
|
||||
var action = document.createElement("li");
|
||||
list.appendChild(action);
|
||||
var isChecked = document.getElementById(path).checked;
|
||||
var expnd = document.createElement("li");
|
||||
list.appendChild(expnd);
|
||||
if(isChecked){
|
||||
expnd.innerHTML = "<span>Collapse</span>";
|
||||
expnd.onclick = function(e){
|
||||
document.getElementById(path).checked = false;
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var refrsh = document.createElement("li");
|
||||
list.appendChild(refrsh);
|
||||
refrsh.innerHTML = "<span>Refresh</span>";
|
||||
refrsh.onclick = function(e){
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
} else {
|
||||
expnd.innerHTML = "<span>Expand</span>";
|
||||
expnd.onclick = function(e){
|
||||
document.getElementById(path).checked = true;
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
var upload = document.createElement("li");
|
||||
list.appendChild(upload);
|
||||
upload.innerHTML = "<span>Upload</span>";
|
||||
upload.onclick = function(e){
|
||||
var pathEl = document.getElementById("upload-path");
|
||||
if(pathEl){
|
||||
var subPath = pathEl.value;
|
||||
if(subPath.lastIndexOf("/") < 1) pathEl.value = path+subPath;
|
||||
else pathEl.value = path.substring(subPath.lastIndexOf("/"))+subPath;
|
||||
}
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var delFile = document.createElement("li");
|
||||
list.appendChild(delFile);
|
||||
delFile.innerHTML = "<span>Delete</span>";
|
||||
delFile.onclick = function(e){
|
||||
httpDelete(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
|
||||
function fillFileMenu(el, path){
|
||||
var list = document.createElement("ul");
|
||||
el.appendChild(list);
|
||||
var action = document.createElement("li");
|
||||
list.appendChild(action);
|
||||
if(isTextFile(path)){
|
||||
action.innerHTML = "<span>Edit</span>";
|
||||
action.onclick = function(e){
|
||||
editor.loadUrl(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
} else if(isImageFile(path)){
|
||||
action.innerHTML = "<span>Preview</span>";
|
||||
action.onclick = function(e){
|
||||
loadPreview(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
var download = document.createElement("li");
|
||||
list.appendChild(download);
|
||||
download.innerHTML = "<span>Download</span>";
|
||||
download.onclick = function(e){
|
||||
loadDownload(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var delFile = document.createElement("li");
|
||||
list.appendChild(delFile);
|
||||
delFile.innerHTML = "<span>Delete</span>";
|
||||
delFile.onclick = function(e){
|
||||
httpDelete(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
|
||||
function showContextMenu(e, path, isfile){
|
||||
var divContext = document.createElement("div");
|
||||
var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
|
||||
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
|
||||
var left = e.clientX + scrollLeft;
|
||||
var top = e.clientY + scrollTop;
|
||||
divContext.className = 'contextMenu';
|
||||
divContext.style.display = 'block';
|
||||
divContext.style.left = left + 'px';
|
||||
divContext.style.top = top + 'px';
|
||||
if(isfile) fillFileMenu(divContext, path);
|
||||
else fillFolderMenu(divContext, path);
|
||||
document.body.appendChild(divContext);
|
||||
var width = divContext.offsetWidth;
|
||||
var height = divContext.offsetHeight;
|
||||
divContext.onmouseout = function(e){
|
||||
if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(divContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createTreeLeaf(path, name, size){
|
||||
var leaf = document.createElement("li");
|
||||
leaf.id = (((path == "/")?"":path)+"/"+name).toLowerCase();
|
||||
var label = document.createElement("span");
|
||||
label.textContent = name.toLowerCase();
|
||||
leaf.appendChild(label);
|
||||
leaf.onclick = function(e){
|
||||
if(isTextFile(leaf.id)){
|
||||
editor.loadUrl(leaf.id);
|
||||
} else if(isImageFile(leaf.id)){
|
||||
loadPreview(leaf.id);
|
||||
}
|
||||
};
|
||||
leaf.oncontextmenu = function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showContextMenu(e, leaf.id, true);
|
||||
};
|
||||
return leaf;
|
||||
}
|
||||
|
||||
function createTreeBranch(path, name, disabled){
|
||||
var leaf = document.createElement("li");
|
||||
var check = document.createElement("input");
|
||||
check.type = "checkbox";
|
||||
check.id = (((path == "/")?"":path)+"/"+name).toLowerCase();
|
||||
if(typeof disabled !== "undefined" && disabled) check.disabled = "disabled";
|
||||
leaf.appendChild(check);
|
||||
var label = document.createElement("label");
|
||||
label.for = check.id;
|
||||
label.textContent = name.toLowerCase();
|
||||
leaf.appendChild(label);
|
||||
check.onchange = function(e){
|
||||
if(check.checked){
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, check.id);
|
||||
}
|
||||
};
|
||||
label.onclick = function(e){
|
||||
if(!check.checked){
|
||||
check.checked = true;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, check.id);
|
||||
} else {
|
||||
check.checked = false;
|
||||
}
|
||||
};
|
||||
leaf.oncontextmenu = function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showContextMenu(e, check.id, false);
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
function addList(parent, path, items){
|
||||
var list = document.createElement("ul");
|
||||
parent.appendChild(list);
|
||||
var ll = items.length;
|
||||
for(var i = 0; i < ll; i++){
|
||||
var item = items[i];
|
||||
var itemEl;
|
||||
if(item.type === "file"){
|
||||
itemEl = createTreeLeaf(path, item.name, item.size);
|
||||
} else {
|
||||
itemEl = createTreeBranch(path, item.name);
|
||||
}
|
||||
list.appendChild(itemEl);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isTextFile(path){
|
||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "txt":
|
||||
case "htm":
|
||||
case "html":
|
||||
case "js":
|
||||
case "json":
|
||||
case "c":
|
||||
case "h":
|
||||
case "cpp":
|
||||
case "css":
|
||||
case "xml":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isImageFile(path){
|
||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "png":
|
||||
case "jpg":
|
||||
case "gif":
|
||||
case "ico":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.refreshPath = function(path){
|
||||
if(path.lastIndexOf('/') < 1){
|
||||
path = '/';
|
||||
treeRoot.removeChild(treeRoot.childNodes[0]);
|
||||
httpGet(treeRoot, "/");
|
||||
} else {
|
||||
path = path.substring(0, path.lastIndexOf('/'));
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
}
|
||||
};
|
||||
|
||||
function delCb(path){
|
||||
return function(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200){
|
||||
alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
} else {
|
||||
if(path.lastIndexOf('/') < 1){
|
||||
path = '/';
|
||||
treeRoot.removeChild(treeRoot.childNodes[0]);
|
||||
httpGet(treeRoot, "/");
|
||||
} else {
|
||||
path = path.substring(0, path.lastIndexOf('/'));
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function httpDelete(filename){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = delCb(filename);
|
||||
var formData = new FormData();
|
||||
formData.append("path", filename);
|
||||
xmlHttp.open("DELETE", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
|
||||
function getCb(parent, path){
|
||||
return function(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
//clear loading
|
||||
if(xmlHttp.status == 200) addList(parent, path, JSON.parse(xmlHttp.responseText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function httpGet(parent, path){
|
||||
xmlHttp = new XMLHttpRequest(parent, path);
|
||||
xmlHttp.onreadystatechange = getCb(parent, path);
|
||||
xmlHttp.open("GET", "/list?dir="+path, true);
|
||||
xmlHttp.send(null);
|
||||
//start loading
|
||||
}
|
||||
|
||||
httpGet(treeRoot, "/");
|
||||
return this;
|
||||
}
|
||||
|
||||
function createEditor(element, file, lang, theme, type){
|
||||
function getLangFromFilename(filename){
|
||||
var lang = "plain";
|
||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "txt": lang = "plain"; break;
|
||||
case "htm": lang = "html"; break;
|
||||
case "js": lang = "javascript"; break;
|
||||
case "c": lang = "c_cpp"; break;
|
||||
case "cpp": lang = "c_cpp"; break;
|
||||
case "css":
|
||||
case "scss":
|
||||
case "php":
|
||||
case "html":
|
||||
case "json":
|
||||
case "xml":
|
||||
lang = ext;
|
||||
}
|
||||
}
|
||||
return lang;
|
||||
}
|
||||
|
||||
if(typeof file === "undefined") file = "/index.htm";
|
||||
|
||||
if(typeof lang === "undefined"){
|
||||
lang = getLangFromFilename(file);
|
||||
}
|
||||
|
||||
if(typeof theme === "undefined") theme = "textmate";
|
||||
|
||||
if(typeof type === "undefined"){
|
||||
type = "text/"+lang;
|
||||
if(lang === "c_cpp") type = "text/plain";
|
||||
}
|
||||
|
||||
var xmlHttp = null;
|
||||
var editor = ace.edit(element);
|
||||
|
||||
//post
|
||||
function httpPostProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
}
|
||||
}
|
||||
function httpPost(filename, data, type){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("data", new Blob([data], { type: type }), filename);
|
||||
xmlHttp.open("POST", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
//get
|
||||
function httpGetProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
document.getElementById("preview").style.display = "none";
|
||||
document.getElementById("editor").style.display = "block";
|
||||
if(xmlHttp.status == 200) editor.setValue(xmlHttp.responseText);
|
||||
else editor.setValue("");
|
||||
editor.clearSelection();
|
||||
}
|
||||
}
|
||||
function httpGet(theUrl){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpGetProcessRequest;
|
||||
xmlHttp.open("GET", theUrl, true);
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
|
||||
editor.setTheme("ace/theme/"+theme);
|
||||
editor.$blockScrolling = Infinity;
|
||||
editor.getSession().setUseSoftTabs(true);
|
||||
editor.getSession().setTabSize(2);
|
||||
editor.setHighlightActiveLine(true);
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.commands.addCommand({
|
||||
name: 'saveCommand',
|
||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
|
||||
exec: function(editor) {
|
||||
httpPost(file, editor.getValue()+"", type);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
editor.commands.addCommand({
|
||||
name: 'undoCommand',
|
||||
bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'},
|
||||
exec: function(editor) {
|
||||
editor.getSession().getUndoManager().undo(false);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
editor.commands.addCommand({
|
||||
name: 'redoCommand',
|
||||
bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'},
|
||||
exec: function(editor) {
|
||||
editor.getSession().getUndoManager().redo(false);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
httpGet(file);
|
||||
editor.loadUrl = function(filename){
|
||||
file = filename;
|
||||
lang = getLangFromFilename(file);
|
||||
type = "text/"+lang;
|
||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
|
||||
httpGet(file);
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
function onBodyLoad(){
|
||||
var vars = {};
|
||||
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
|
||||
var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
|
||||
var tree = createTree("tree", editor);
|
||||
createFileUploader("uploader", tree, editor);
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.9/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
</head>
|
||||
<body onload="onBodyLoad();">
|
||||
<div id="uploader"></div>
|
||||
<div id="tree"></div>
|
||||
<div id="editor"></div>
|
||||
<div id="preview" style="display:none;"></div>
|
||||
<iframe id=download-frame style='display:none;'></iframe>
|
||||
</body>
|
||||
</html>
|
||||
22
lib/WebServer/examples/SDWebServer/SdRoot/index.htm
Normal file
22
lib/WebServer/examples/SDWebServer/SdRoot/index.htm
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>ESP Index</title>
|
||||
<style>
|
||||
body {
|
||||
background-color:black;
|
||||
color:white;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function onBodyLoad(){
|
||||
console.log("we are loaded!!");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="index" onload="onBodyLoad()">
|
||||
<h1>ESP8266 Pin Functions</h1>
|
||||
<img src="pins.png" />
|
||||
</body>
|
||||
</html>
|
||||
BIN
lib/WebServer/examples/SDWebServer/SdRoot/pins.png
Normal file
BIN
lib/WebServer/examples/SDWebServer/SdRoot/pins.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
@@ -0,0 +1,133 @@
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
//Check if header is present and correct
|
||||
bool is_authentified() {
|
||||
Serial.println("Enter is_authentified");
|
||||
if (server.hasHeader("Cookie")) {
|
||||
Serial.print("Found cookie: ");
|
||||
String cookie = server.header("Cookie");
|
||||
Serial.println(cookie);
|
||||
if (cookie.indexOf("ESPSESSIONID=1") != -1) {
|
||||
Serial.println("Authentification Successful");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Serial.println("Authentification Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
//login page, also called for disconnect
|
||||
void handleLogin() {
|
||||
String msg;
|
||||
if (server.hasHeader("Cookie")) {
|
||||
Serial.print("Found cookie: ");
|
||||
String cookie = server.header("Cookie");
|
||||
Serial.println(cookie);
|
||||
}
|
||||
if (server.hasArg("DISCONNECT")) {
|
||||
Serial.println("Disconnection");
|
||||
server.sendHeader("Location", "/login");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
|
||||
server.send(301);
|
||||
return;
|
||||
}
|
||||
if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) {
|
||||
if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") {
|
||||
server.sendHeader("Location", "/");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.sendHeader("Set-Cookie", "ESPSESSIONID=1");
|
||||
server.send(301);
|
||||
Serial.println("Log in Successful");
|
||||
return;
|
||||
}
|
||||
msg = "Wrong username/password! try again.";
|
||||
Serial.println("Log in Failed");
|
||||
}
|
||||
String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
|
||||
content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
|
||||
content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
|
||||
content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
|
||||
content += "You also can go <a href='/inline'>here</a></body></html>";
|
||||
server.send(200, "text/html", content);
|
||||
}
|
||||
|
||||
//root page can be accessed only if authentification is ok
|
||||
void handleRoot() {
|
||||
Serial.println("Enter handleRoot");
|
||||
String header;
|
||||
if (!is_authentified()) {
|
||||
server.sendHeader("Location", "/login");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.send(301);
|
||||
return;
|
||||
}
|
||||
String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>";
|
||||
if (server.hasHeader("User-Agent")) {
|
||||
content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
|
||||
}
|
||||
content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
|
||||
server.send(200, "text/html", content);
|
||||
}
|
||||
|
||||
//no need authentification
|
||||
void handleNotFound() {
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
|
||||
server.on("/", handleRoot);
|
||||
server.on("/login", handleLogin);
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works without need of authentification");
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
//here the list of headers to be recorded
|
||||
const char * headerkeys[] = {"User-Agent", "Cookie"} ;
|
||||
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
|
||||
//ask server to track these headers
|
||||
server.collectHeaders(headerkeys, headerkeyssize);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
69
lib/WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
69
lib/WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <Update.h>
|
||||
|
||||
const char* host = "esp32-webupdate";
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.println("Booting Sketch...");
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
|
||||
MDNS.begin(host);
|
||||
server.on("/", HTTP_GET, []() {
|
||||
server.sendHeader("Connection", "close");
|
||||
server.send(200, "text/html", serverIndex);
|
||||
});
|
||||
server.on("/update", HTTP_POST, []() {
|
||||
server.sendHeader("Connection", "close");
|
||||
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
|
||||
ESP.restart();
|
||||
}, []() {
|
||||
HTTPUpload& upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
Serial.setDebugOutput(true);
|
||||
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||
if (!Update.begin()) { //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);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
Serial.setDebugOutput(false);
|
||||
} else {
|
||||
Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status);
|
||||
}
|
||||
});
|
||||
server.begin();
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
Serial.printf("Ready! Open http://%s.local in your browser\n", host);
|
||||
} else {
|
||||
Serial.println("WiFi Failed");
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2);//allow the cpu to switch to other tasks
|
||||
}
|
||||
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=1.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
|
||||
22
lib/WebServer/src/EthClient.h
Normal file
22
lib/WebServer/src/EthClient.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstddef>
|
||||
|
||||
class EthClient
|
||||
{
|
||||
public:
|
||||
virtual uint8_t connected() = 0;
|
||||
virtual int available() = 0;
|
||||
virtual unsigned long getTimeout(void) = 0;
|
||||
virtual int setTimeout(uint32_t seconds) = 0;
|
||||
virtual int read() = 0;
|
||||
virtual size_t write(const char *buffer, size_t size) = 0;
|
||||
virtual size_t write_P(PGM_P buf, size_t size) = 0;
|
||||
virtual size_t write(Stream &stream) = 0;
|
||||
virtual String readStringUntil(char terminator) = 0;
|
||||
virtual size_t readBytes(char *buffer, size_t length) = 0;
|
||||
virtual IPAddress localIP() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void flush() = 0;
|
||||
};
|
||||
20
lib/WebServer/src/EthServer.h
Normal file
20
lib/WebServer/src/EthServer.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <IPAddress.h>
|
||||
#include <WiFiClient.h>
|
||||
#include "EthClient.h"
|
||||
|
||||
class EthServer
|
||||
{
|
||||
public:
|
||||
EthServer(IPAddress address, int port) { }
|
||||
|
||||
EthServer(int port) { }
|
||||
|
||||
virtual EthClient* available() = 0;
|
||||
virtual void discardClient() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
virtual void begin(const int port = 80) = 0;
|
||||
virtual void setNoDelay(const bool value) = 0;
|
||||
};
|
||||
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_ */
|
||||
589
lib/WebServer/src/Parsing.cpp
Normal file
589
lib/WebServer/src/Parsing.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
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 "WiFiServer.h"
|
||||
#include "WiFiClient.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)
|
||||
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(EthClient* 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(EthClient* client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
//reset header value
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
_currentHeaders[i].value =String();
|
||||
}
|
||||
|
||||
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||
// Retrieve the "/path" part by finding the spaces
|
||||
int addr_start = req.indexOf(' ');
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
log_e("Invalid request: %s", req.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
String url = req.substring(addr_start + 1, addr_end);
|
||||
String versionEnd = req.substring(addr_end + 8);
|
||||
_currentVersion = atoi(versionEnd.c_str());
|
||||
String searchStr = "";
|
||||
int hasSearch = url.indexOf('?');
|
||||
if (hasSearch != -1){
|
||||
searchStr = url.substring(hasSearch + 1);
|
||||
url = url.substring(0, hasSearch);
|
||||
}
|
||||
_currentUri = url;
|
||||
_chunked = false;
|
||||
|
||||
HTTPMethod method = HTTP_ANY;
|
||||
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||
for (size_t i=0; i<num_methods; i++) {
|
||||
if (methodStr == _http_method_str[i]) {
|
||||
method = (HTTPMethod)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (method == HTTP_ANY) {
|
||||
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||
return false;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
||||
|
||||
//attach handler
|
||||
RequestHandler* handler;
|
||||
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||
if (handler->canHandle(_currentMethod, _currentUri))
|
||||
break;
|
||||
}
|
||||
_currentHandler = handler;
|
||||
|
||||
String formData;
|
||||
// below is needed only when POST type request
|
||||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
|
||||
String boundaryStr;
|
||||
String headerName;
|
||||
String headerValue;
|
||||
bool isForm = false;
|
||||
bool isEncoded = false;
|
||||
uint32_t contentLength = 0;
|
||||
//parse headers
|
||||
while(1){
|
||||
req = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
if (req == "") break;//no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1){
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 1);
|
||||
headerValue.trim();
|
||||
_collectHeader(headerName.c_str(),headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){
|
||||
using namespace mime;
|
||||
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){
|
||||
isForm = false;
|
||||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){
|
||||
isForm = false;
|
||||
isEncoded = true;
|
||||
} else if (headerValue.startsWith(F("multipart/"))){
|
||||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
||||
boundaryStr.replace("\"","");
|
||||
isForm = true;
|
||||
}
|
||||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))){
|
||||
contentLength = headerValue.toInt();
|
||||
} else if (headerName.equalsIgnoreCase(F("Host"))){
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isForm){
|
||||
size_t plainLength;
|
||||
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if (plainLength < contentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
}
|
||||
if (contentLength > 0) {
|
||||
if(isEncoded){
|
||||
//url encoded form
|
||||
if (searchStr != "") searchStr += '&';
|
||||
searchStr += plainBuf;
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
if(!isEncoded){
|
||||
//plain post json or other data
|
||||
RequestArgument& arg = _currentArgs[_currentArgCount++];
|
||||
arg.key = F("plain");
|
||||
arg.value = String(plainBuf);
|
||||
}
|
||||
|
||||
log_v("Plain: %s", plainBuf);
|
||||
free(plainBuf);
|
||||
} else {
|
||||
// No content - but we can still have arguments in the URL.
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (isForm){
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, contentLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String headerName;
|
||||
String headerValue;
|
||||
//parse headers
|
||||
while(1){
|
||||
req = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
if (req == "") break;//no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1){
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 2);
|
||||
_collectHeader(headerName.c_str(),headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase("Host")){
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
client->flush();
|
||||
|
||||
log_v("Request: %s", url.c_str());
|
||||
log_v(" Arguments: %s", searchStr.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
|
||||
for (int i = 0; i < _headerKeysCount; i++) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||
_currentHeaders[i].value=headerValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::_parseArguments(String data) {
|
||||
log_v("args: %s", data.c_str());
|
||||
if (_currentArgs)
|
||||
delete[] _currentArgs;
|
||||
_currentArgs = 0;
|
||||
if (data.length() == 0) {
|
||||
_currentArgCount = 0;
|
||||
_currentArgs = new RequestArgument[1];
|
||||
return;
|
||||
}
|
||||
_currentArgCount = 1;
|
||||
|
||||
for (int i = 0; i < (int)data.length(); ) {
|
||||
i = data.indexOf('&', i);
|
||||
if (i == -1)
|
||||
break;
|
||||
++i;
|
||||
++_currentArgCount;
|
||||
}
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
|
||||
_currentArgs = new RequestArgument[_currentArgCount+1];
|
||||
int pos = 0;
|
||||
int iarg;
|
||||
for (iarg = 0; iarg < _currentArgCount;) {
|
||||
int equal_sign_index = data.indexOf('=', pos);
|
||||
int next_arg_index = data.indexOf('&', pos);
|
||||
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||
log_e("arg missing value: %d", iarg);
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
continue;
|
||||
}
|
||||
RequestArgument& arg = _currentArgs[iarg];
|
||||
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
||||
++iarg;
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
|
||||
}
|
||||
|
||||
void WebServer::_uploadWriteByte(uint8_t b){
|
||||
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->currentSize = 0;
|
||||
}
|
||||
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||
}
|
||||
|
||||
int WebServer::_uploadReadByte(EthClient* client){
|
||||
int res = client->read();
|
||||
if(res < 0) {
|
||||
// keep trying until you either read a valid byte or timeout
|
||||
unsigned long startMillis = millis();
|
||||
long timeoutIntervalMillis = client->getTimeout();
|
||||
boolean timedOut = false;
|
||||
for(;;) {
|
||||
if (!client->connected()) return -1;
|
||||
// loosely modeled after blinkWithoutDelay pattern
|
||||
while(!timedOut && !client->available() && client->connected()){
|
||||
delay(2);
|
||||
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||
}
|
||||
|
||||
res = client->read();
|
||||
if(res >= 0) {
|
||||
return res; // exit on a valid read
|
||||
}
|
||||
// NOTE: it is possible to get here and have all of the following
|
||||
// assertions hold true
|
||||
//
|
||||
// -- client->available() > 0
|
||||
// -- client->connected == true
|
||||
// -- res == -1
|
||||
//
|
||||
// a simple retry strategy overcomes this which is to say the
|
||||
// assertion is not permanent, but the reason that this works
|
||||
// is elusive, and possibly indicative of a more subtle underlying
|
||||
// issue
|
||||
|
||||
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||
if(timedOut) {
|
||||
return res; // exit on a timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool WebServer::_parseForm(EthClient* client, String boundary, uint32_t len){
|
||||
(void) len;
|
||||
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
|
||||
String line;
|
||||
int retry = 0;
|
||||
do {
|
||||
line = client->readStringUntil('\r');
|
||||
++retry;
|
||||
} while (line.length() == 0 && retry < 3);
|
||||
|
||||
client->readStringUntil('\n');
|
||||
//start reading the form
|
||||
if (line == ("--"+boundary)){
|
||||
if(_postArgs) delete[] _postArgs;
|
||||
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
||||
_postArgsLen = 0;
|
||||
while(1){
|
||||
String argName;
|
||||
String argValue;
|
||||
String argType;
|
||||
String argFilename;
|
||||
bool argIsFile = false;
|
||||
|
||||
line = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){
|
||||
int nameStart = line.indexOf('=');
|
||||
if (nameStart != -1){
|
||||
argName = line.substring(nameStart+2);
|
||||
nameStart = argName.indexOf('=');
|
||||
if (nameStart == -1){
|
||||
argName = argName.substring(0, argName.length() - 1);
|
||||
} else {
|
||||
argFilename = argName.substring(nameStart+2, argName.length() - 1);
|
||||
argName = argName.substring(0, argName.indexOf('"'));
|
||||
argIsFile = true;
|
||||
log_v("PostArg FileName: %s",argFilename.c_str());
|
||||
//use GET to set the filename if uploading using blob
|
||||
if (argFilename == F("blob") && hasArg(FPSTR(filename)))
|
||||
argFilename = arg(FPSTR(filename));
|
||||
}
|
||||
log_v("PostArg Name: %s", argName.c_str());
|
||||
using namespace mime;
|
||||
argType = FPSTR(mimeTable[txt].mimeType);
|
||||
line = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){
|
||||
argType = line.substring(line.indexOf(':')+2);
|
||||
//skip next line
|
||||
client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
}
|
||||
log_v("PostArg Type: %s", argType.c_str());
|
||||
if (!argIsFile){
|
||||
while(1){
|
||||
line = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
if (line.startsWith("--"+boundary)) break;
|
||||
if (argValue.length() > 0) argValue += "\n";
|
||||
argValue += line;
|
||||
}
|
||||
log_v("PostArg Value: %s", argValue.c_str());
|
||||
|
||||
RequestArgument& arg = _postArgs[_postArgsLen++];
|
||||
arg.key = argName;
|
||||
arg.value = argValue;
|
||||
|
||||
if (line == ("--"+boundary+"--")){
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
||||
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_currentUpload.reset(new HTTPUpload());
|
||||
_currentUpload->status = UPLOAD_FILE_START;
|
||||
_currentUpload->name = argName;
|
||||
_currentUpload->filename = argFilename;
|
||||
_currentUpload->type = argType;
|
||||
_currentUpload->totalSize = 0;
|
||||
_currentUpload->currentSize = 0;
|
||||
log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str());
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||
int argByte = _uploadReadByte(client);
|
||||
readfile:
|
||||
|
||||
while(argByte != 0x0D){
|
||||
if(argByte < 0) return _parseFormUploadAborted();
|
||||
_uploadWriteByte(argByte);
|
||||
argByte = _uploadReadByte(client);
|
||||
}
|
||||
|
||||
argByte = _uploadReadByte(client);
|
||||
if(argByte < 0) return _parseFormUploadAborted();
|
||||
if (argByte == 0x0A){
|
||||
argByte = _uploadReadByte(client);
|
||||
if(argByte < 0) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
goto readfile;
|
||||
} else {
|
||||
argByte = _uploadReadByte(client);
|
||||
if(argByte < 0) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
goto readfile;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t endBuf[boundary.length()];
|
||||
uint32_t i = 0;
|
||||
while(i < boundary.length()){
|
||||
argByte = _uploadReadByte(client);
|
||||
if(argByte < 0) return _parseFormUploadAborted();
|
||||
if ((char)argByte == 0x0D){
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t j = 0;
|
||||
while(j < i){
|
||||
_uploadWriteByte(endBuf[j++]);
|
||||
}
|
||||
goto readfile;
|
||||
}
|
||||
endBuf[i++] = (uint8_t)argByte;
|
||||
}
|
||||
|
||||
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize);
|
||||
line = client->readStringUntil(0x0D);
|
||||
client->readStringUntil(0x0A);
|
||||
if (line == "--"){
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t i = 0;
|
||||
while(i < boundary.length()){
|
||||
_uploadWriteByte(endBuf[i++]);
|
||||
}
|
||||
argByte = _uploadReadByte(client);
|
||||
goto readfile;
|
||||
}
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
goto readfile;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int iarg;
|
||||
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)?(WEBSERVER_MAX_POST_ARGS - _postArgsLen):_currentArgCount;
|
||||
for (iarg = 0; iarg < totalArgs; iarg++){
|
||||
RequestArgument& arg = _postArgs[_postArgsLen++];
|
||||
arg.key = _currentArgs[iarg].key;
|
||||
arg.value = _currentArgs[iarg].value;
|
||||
}
|
||||
if (_currentArgs) delete[] _currentArgs;
|
||||
_currentArgs = new RequestArgument[_postArgsLen];
|
||||
for (iarg = 0; iarg < _postArgsLen; iarg++){
|
||||
RequestArgument& arg = _currentArgs[iarg];
|
||||
arg.key = _postArgs[iarg].key;
|
||||
arg.value = _postArgs[iarg].value;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
_postArgs=nullptr;
|
||||
_postArgsLen = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
log_e("Error: line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::urlDecode(const String& text)
|
||||
{
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len)
|
||||
{
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len))
|
||||
{
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
}
|
||||
else {
|
||||
if (encodedChar == '+')
|
||||
{
|
||||
decodedChar = ' ';
|
||||
}
|
||||
else {
|
||||
decodedChar = encodedChar; // normal ascii char
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool WebServer::_parseFormUploadAborted(){
|
||||
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
return false;
|
||||
}
|
||||
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(String(uri)) {}
|
||||
virtual ~Uri() {}
|
||||
|
||||
virtual Uri* clone() const {
|
||||
return new Uri(_uri);
|
||||
};
|
||||
|
||||
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
|
||||
|
||||
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
|
||||
return _uri == requestUri;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
680
lib/WebServer/src/WebServer.cpp
Normal file
680
lib/WebServer/src/WebServer.cpp
Normal file
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
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/cencode.h>
|
||||
#include "WiFiClient.h"
|
||||
#include "WebServer.h"
|
||||
#include "FS.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
#include "mbedtls/md5.h"
|
||||
|
||||
|
||||
static const char AUTHORIZATION_HEADER[] = "Authorization";
|
||||
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||
static const char WWW_Authenticate[] = "WWW-Authenticate";
|
||||
static const char Content_Length[] = "Content-Length";
|
||||
|
||||
|
||||
//WebServer::WebServer(IPAddress addr, int port)
|
||||
WebServer::WebServer(EthServer* server)
|
||||
: _corsEnabled(false)
|
||||
, _server(server)
|
||||
, _currentMethod(HTTP_ANY)
|
||||
, _currentVersion(0)
|
||||
, _currentStatus(HC_NONE)
|
||||
, _statusChange(0)
|
||||
, _nullDelay(true)
|
||||
, _currentHandler(nullptr)
|
||||
, _firstHandler(nullptr)
|
||||
, _lastHandler(nullptr)
|
||||
, _currentArgCount(0)
|
||||
, _currentArgs(nullptr)
|
||||
, _postArgsLen(0)
|
||||
, _postArgs(nullptr)
|
||||
, _headerKeysCount(0)
|
||||
, _currentHeaders(nullptr)
|
||||
, _contentLength(0)
|
||||
, _chunked(false)
|
||||
{
|
||||
}
|
||||
|
||||
WebServer::~WebServer() {
|
||||
_server->close();
|
||||
if (_currentHeaders)
|
||||
delete[]_currentHeaders;
|
||||
RequestHandler* handler = _firstHandler;
|
||||
while (handler) {
|
||||
RequestHandler* next = handler->next();
|
||||
delete handler;
|
||||
handler = next;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::begin() {
|
||||
close();
|
||||
_server->begin();
|
||||
_server->setNoDelay(true);
|
||||
}
|
||||
|
||||
void WebServer::begin(uint16_t port) {
|
||||
close();
|
||||
_server->begin(port);
|
||||
_server->setNoDelay(true);
|
||||
}
|
||||
|
||||
String WebServer::_extractParam(String& authReq,const String& param,const char delimit){
|
||||
int _begin = authReq.indexOf(param);
|
||||
if (_begin == -1)
|
||||
return "";
|
||||
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
|
||||
}
|
||||
|
||||
static String md5str(String &in){
|
||||
char out[33] = {0};
|
||||
mbedtls_md5_context _ctx;
|
||||
uint8_t i;
|
||||
uint8_t * _buf = (uint8_t*)malloc(16);
|
||||
if(_buf == NULL)
|
||||
return String(out);
|
||||
memset(_buf, 0x00, 16);
|
||||
mbedtls_md5_init(&_ctx);
|
||||
mbedtls_md5_starts(&_ctx);
|
||||
mbedtls_md5_update(&_ctx, (const uint8_t *)in.c_str(), in.length());
|
||||
mbedtls_md5_finish(&_ctx, _buf);
|
||||
for(i = 0; i < 16; i++) {
|
||||
sprintf(out + (i * 2), "%02x", _buf[i]);
|
||||
}
|
||||
out[32] = 0;
|
||||
free(_buf);
|
||||
return String(out);
|
||||
}
|
||||
|
||||
bool WebServer::authenticate(const char * username, const char * password){
|
||||
if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
|
||||
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
|
||||
if(authReq.startsWith(F("Basic"))){
|
||||
authReq = authReq.substring(6);
|
||||
authReq.trim();
|
||||
char toencodeLen = strlen(username)+strlen(password)+1;
|
||||
char *toencode = new char[toencodeLen + 1];
|
||||
if(toencode == NULL){
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||
if(encoded == NULL){
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
} else if(authReq.startsWith(F("Digest"))) {
|
||||
authReq = authReq.substring(7);
|
||||
log_v("%s", authReq.c_str());
|
||||
String _username = _extractParam(authReq,F("username=\""),'\"');
|
||||
if(!_username.length() || _username != String(username)) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
// extracting required parameters for RFC 2069 simpler Digest
|
||||
String _realm = _extractParam(authReq, F("realm=\""),'\"');
|
||||
String _nonce = _extractParam(authReq, F("nonce=\""),'\"');
|
||||
String _uri = _extractParam(authReq, F("uri=\""),'\"');
|
||||
String _response = _extractParam(authReq, F("response=\""),'\"');
|
||||
String _opaque = _extractParam(authReq, F("opaque=\""),'\"');
|
||||
|
||||
if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
// parameters for the RFC 2617 newer Digest
|
||||
String _nc,_cnonce;
|
||||
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_nc = _extractParam(authReq, F("nc="), ',');
|
||||
_cnonce = _extractParam(authReq, F("cnonce=\""),'\"');
|
||||
}
|
||||
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||
log_v("Hash of user:realm:pass=%s", _H1.c_str());
|
||||
String _H2 = "";
|
||||
if(_currentMethod == HTTP_GET){
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}else if(_currentMethod == HTTP_POST){
|
||||
_H2 = md5str(String(F("POST:")) + _uri);
|
||||
}else if(_currentMethod == HTTP_PUT){
|
||||
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||
}else if(_currentMethod == HTTP_DELETE){
|
||||
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||
}else{
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}
|
||||
log_v("Hash of GET:uri=%s", _H2.c_str());
|
||||
String _responsecheck = "";
|
||||
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||
} else {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||
}
|
||||
log_v("The Proper response=%s", _responsecheck.c_str());
|
||||
if(_response == _responsecheck){
|
||||
authReq = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
authReq = "";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::_getRandomHexString() {
|
||||
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||
int i;
|
||||
for(i = 0; i < 4; i++) {
|
||||
sprintf (buffer + (i*8), "%08x", 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)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
|
||||
} else {
|
||||
_snonce=_getRandomHexString();
|
||||
_sopaque=_getRandomHexString();
|
||||
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\"")));
|
||||
}
|
||||
using namespace mime;
|
||||
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
|
||||
on(uri, HTTP_ANY, handler);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
|
||||
on(uri, method, fn, _fileUploadHandler);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
|
||||
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
||||
}
|
||||
|
||||
void WebServer::addHandler(RequestHandler* handler) {
|
||||
_addRequestHandler(handler);
|
||||
}
|
||||
|
||||
void WebServer::_addRequestHandler(RequestHandler* handler) {
|
||||
if (!_lastHandler) {
|
||||
_firstHandler = handler;
|
||||
_lastHandler = handler;
|
||||
}
|
||||
else {
|
||||
_lastHandler->next(handler);
|
||||
_lastHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
||||
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||
}
|
||||
|
||||
void WebServer::handleClient() {
|
||||
if (_currentStatus == HC_NONE) {
|
||||
EthClient* client = _server->available();
|
||||
if (!client->connected()) {
|
||||
if (_nullDelay) {
|
||||
delay(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
log_v("New client");
|
||||
|
||||
_currentClient = client;
|
||||
_currentStatus = HC_WAIT_READ;
|
||||
_statusChange = millis();
|
||||
}
|
||||
|
||||
bool keepCurrentClient = false;
|
||||
bool callYield = false;
|
||||
|
||||
if (_currentClient->connected()) {
|
||||
switch (_currentStatus) {
|
||||
case HC_NONE:
|
||||
// No-op to avoid C++ compiler warning
|
||||
break;
|
||||
case HC_WAIT_READ:
|
||||
// Wait for data from client to become available
|
||||
if (_currentClient->available()) {
|
||||
if (_parseRequest(_currentClient)) {
|
||||
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
|
||||
// it must be divided by 1000
|
||||
_currentClient->setTimeout(HTTP_MAX_SEND_WAIT / 1000);
|
||||
|
||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||
_handleRequest();
|
||||
}
|
||||
} else { // !_currentClient->available()
|
||||
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
}
|
||||
callYield = true;
|
||||
}
|
||||
break;
|
||||
case HC_WAIT_CLOSE:
|
||||
// Wait for client to close the connection
|
||||
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
callYield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
if (!keepCurrentClient) {
|
||||
_server->discardClient();
|
||||
_currentClient = _server->available();
|
||||
_currentStatus = HC_NONE;
|
||||
_currentUpload.reset();
|
||||
}
|
||||
|
||||
if (callYield) {
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::close() {
|
||||
_server->close();
|
||||
_currentStatus = HC_NONE;
|
||||
if(!_headerKeysCount)
|
||||
collectHeaders(0, 0);
|
||||
}
|
||||
|
||||
void WebServer::stop() {
|
||||
close();
|
||||
}
|
||||
|
||||
void WebServer::sendHeader(const String& name, const String& value, bool first) {
|
||||
String headerLine = name;
|
||||
headerLine += F(": ");
|
||||
headerLine += value;
|
||||
headerLine += "\r\n";
|
||||
|
||||
if (first) {
|
||||
_responseHeaders = headerLine + _responseHeaders;
|
||||
}
|
||||
else {
|
||||
_responseHeaders += headerLine;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::setContentLength(const size_t contentLength) {
|
||||
_contentLength = contentLength;
|
||||
}
|
||||
|
||||
void WebServer::enableDelay(boolean value) {
|
||||
_nullDelay = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCORS(boolean value) {
|
||||
_corsEnabled = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCrossOrigin(boolean value) {
|
||||
enableCORS(value);
|
||||
}
|
||||
|
||||
void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
||||
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
|
||||
response += String(code);
|
||||
response += ' ';
|
||||
response += _responseCodeToString(code);
|
||||
response += "\r\n";
|
||||
|
||||
using namespace mime;
|
||||
if (!content_type)
|
||||
content_type = mimeTable[html].mimeType;
|
||||
|
||||
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
|
||||
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
|
||||
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
|
||||
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
|
||||
//let's do chunked
|
||||
_chunked = true;
|
||||
sendHeader(String(F("Accept-Ranges")),String(F("none")));
|
||||
sendHeader(String(F("Transfer-Encoding")),String(F("chunked")));
|
||||
}
|
||||
if (_corsEnabled) {
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
|
||||
}
|
||||
sendHeader(String(F("Connection")), String(F("close")));
|
||||
|
||||
response += _responseHeaders;
|
||||
response += "\r\n";
|
||||
_responseHeaders = "";
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const char* content_type, const String& content) {
|
||||
String header;
|
||||
// Can we asume the following?
|
||||
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
_prepareHeader(header, code, content_type, content.length());
|
||||
_currentClientWrite(header.c_str(), header.length());
|
||||
if(content.length())
|
||||
sendContent(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
size_t contentLength = 0;
|
||||
|
||||
if (content != NULL) {
|
||||
contentLength = strlen_P(content);
|
||||
}
|
||||
|
||||
String header;
|
||||
char type[64];
|
||||
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::send(int code, char* content_type, const String& content) {
|
||||
send(code, (const char*)content_type, content);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const String& content_type, const String& content) {
|
||||
send(code, (const char*)content_type.c_str(), content);
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const String& content) {
|
||||
sendContent(content.c_str(), content.length());
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const char* content, size_t contentLength) {
|
||||
const char * footer = "\r\n";
|
||||
if(_chunked) {
|
||||
char * chunkSize = (char *)malloc(11);
|
||||
if(chunkSize){
|
||||
sprintf(chunkSize, "%x%s", contentLength, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite(content, contentLength);
|
||||
if(_chunked){
|
||||
_currentClient->write(footer, 2);
|
||||
if (contentLength == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content) {
|
||||
sendContent_P(content, strlen_P(content));
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content, size_t size) {
|
||||
const char * footer = "\r\n";
|
||||
if(_chunked) {
|
||||
char * chunkSize = (char *)malloc(11);
|
||||
if(chunkSize){
|
||||
sprintf(chunkSize, "%x%s", size, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite_P(content, size);
|
||||
if(_chunked){
|
||||
_currentClient->write(footer, 2);
|
||||
if (size == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType)
|
||||
{
|
||||
using namespace mime;
|
||||
setContentLength(fileSize);
|
||||
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
|
||||
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
|
||||
contentType != String(FPSTR(mimeTable[none].mimeType))) {
|
||||
sendHeader(F("Content-Encoding"), F("gzip"));
|
||||
}
|
||||
send(200, contentType, "");
|
||||
}
|
||||
|
||||
String WebServer::pathArg(unsigned int i) {
|
||||
if (_currentHandler != nullptr)
|
||||
return _currentHandler->pathArg(i);
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if ( _postArgs[j].key == name )
|
||||
return _postArgs[j].value;
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if ( _currentArgs[i].key == name )
|
||||
return _currentArgs[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].value;
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::argName(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].key;
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::args() {
|
||||
return _currentArgCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasArg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if (_postArgs[j].key == name)
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String WebServer::header(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(name))
|
||||
return _currentHeaders[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||
_headerKeysCount = headerKeysCount + 1;
|
||||
if (_currentHeaders)
|
||||
delete[]_currentHeaders;
|
||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||
for (int i = 1; i < _headerKeysCount; i++){
|
||||
_currentHeaders[i].key = headerKeys[i-1];
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::header(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].value;
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::headerName(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].key;
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::headers() {
|
||||
return _headerKeysCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasHeader(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::hostHeader() {
|
||||
return _hostHeader;
|
||||
}
|
||||
|
||||
void WebServer::onFileUpload(THandlerFunction fn) {
|
||||
_fileUploadHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::onNotFound(THandlerFunction fn) {
|
||||
_notFoundHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::_handleRequest() {
|
||||
bool handled = false;
|
||||
if (!_currentHandler){
|
||||
log_e("request handler not found");
|
||||
}
|
||||
else {
|
||||
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||
if (!handled) {
|
||||
log_e("request handler failed to handle request");
|
||||
}
|
||||
}
|
||||
if (!handled && _notFoundHandler) {
|
||||
_notFoundHandler();
|
||||
handled = true;
|
||||
}
|
||||
if (!handled) {
|
||||
using namespace mime;
|
||||
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
_finalizeResponse();
|
||||
}
|
||||
_currentUri = "";
|
||||
}
|
||||
|
||||
|
||||
void WebServer::_finalizeResponse() {
|
||||
if (_chunked) {
|
||||
sendContent("");
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::_responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100: return F("Continue");
|
||||
case 101: return F("Switching Protocols");
|
||||
case 200: return F("OK");
|
||||
case 201: return F("Created");
|
||||
case 202: return F("Accepted");
|
||||
case 203: return F("Non-Authoritative Information");
|
||||
case 204: return F("No Content");
|
||||
case 205: return F("Reset Content");
|
||||
case 206: return F("Partial Content");
|
||||
case 300: return F("Multiple Choices");
|
||||
case 301: return F("Moved Permanently");
|
||||
case 302: return F("Found");
|
||||
case 303: return F("See Other");
|
||||
case 304: return F("Not Modified");
|
||||
case 305: return F("Use Proxy");
|
||||
case 307: return F("Temporary Redirect");
|
||||
case 400: return F("Bad Request");
|
||||
case 401: return F("Unauthorized");
|
||||
case 402: return F("Payment Required");
|
||||
case 403: return F("Forbidden");
|
||||
case 404: return F("Not Found");
|
||||
case 405: return F("Method Not Allowed");
|
||||
case 406: return F("Not Acceptable");
|
||||
case 407: return F("Proxy Authentication Required");
|
||||
case 408: return F("Request Time-out");
|
||||
case 409: return F("Conflict");
|
||||
case 410: return F("Gone");
|
||||
case 411: return F("Length Required");
|
||||
case 412: return F("Precondition Failed");
|
||||
case 413: return F("Request Entity Too Large");
|
||||
case 414: return F("Request-URI Too Large");
|
||||
case 415: return F("Unsupported Media Type");
|
||||
case 416: return F("Requested range not satisfiable");
|
||||
case 417: return F("Expectation Failed");
|
||||
case 500: return F("Internal Server Error");
|
||||
case 501: return F("Not Implemented");
|
||||
case 502: return F("Bad Gateway");
|
||||
case 503: return F("Service Unavailable");
|
||||
case 504: return F("Gateway Time-out");
|
||||
case 505: return F("HTTP Version not supported");
|
||||
default: return F("");
|
||||
}
|
||||
}
|
||||
212
lib/WebServer/src/WebServer.h
Normal file
212
lib/WebServer/src/WebServer.h
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
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 <WiFi.h>
|
||||
#include "HTTP_Method.h"
|
||||
#include "Uri.h"
|
||||
|
||||
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
|
||||
UPLOAD_FILE_ABORTED };
|
||||
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
|
||||
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||
|
||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||
|
||||
#ifndef HTTP_UPLOAD_BUFLEN
|
||||
#define HTTP_UPLOAD_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
|
||||
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
|
||||
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
|
||||
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
|
||||
|
||||
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
|
||||
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
|
||||
|
||||
class WebServer;
|
||||
|
||||
typedef struct {
|
||||
HTTPUploadStatus status;
|
||||
String filename;
|
||||
String name;
|
||||
String type;
|
||||
size_t totalSize; // file size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||
} HTTPUpload;
|
||||
|
||||
#include "detail/RequestHandler.h"
|
||||
#include "EthServer.h"
|
||||
#include "EthClient.h"
|
||||
|
||||
namespace fs {
|
||||
class FS;
|
||||
}
|
||||
|
||||
class WebServer
|
||||
{
|
||||
public:
|
||||
WebServer(EthServer* server);
|
||||
virtual ~WebServer();
|
||||
|
||||
virtual void begin();
|
||||
virtual void begin(uint16_t port);
|
||||
virtual void handleClient();
|
||||
|
||||
virtual void close();
|
||||
void stop();
|
||||
|
||||
bool authenticate(const char * username, const char * password);
|
||||
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
|
||||
|
||||
typedef std::function<void(void)> THandlerFunction;
|
||||
void on(const Uri &uri, THandlerFunction handler);
|
||||
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
|
||||
void addHandler(RequestHandler* handler);
|
||||
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
|
||||
void onNotFound(THandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(THandlerFunction fn); //handle file uploads
|
||||
|
||||
String uri() { return _currentUri; }
|
||||
HTTPMethod method() { return _currentMethod; }
|
||||
virtual EthClient* client() { return _currentClient; }
|
||||
HTTPUpload& upload() { return *_currentUpload; }
|
||||
|
||||
String pathArg(unsigned int i); // get request path argument by number
|
||||
String arg(String name); // get request argument value by name
|
||||
String arg(int i); // get request argument value by number
|
||||
String argName(int i); // get request argument name by number
|
||||
int args(); // get arguments count
|
||||
bool hasArg(String name); // check if argument exists
|
||||
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||
String header(String name); // get request header value by name
|
||||
String header(int i); // get request header value by number
|
||||
String headerName(int i); // get request header name by number
|
||||
int headers(); // get header count
|
||||
bool hasHeader(String name); // check if header exists
|
||||
|
||||
String hostHeader(); // get request host header if available or empty String if not
|
||||
|
||||
// send response to the client
|
||||
// code - HTTP response code, can be 200 or 404
|
||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||
// content - actual content body
|
||||
void send(int code, const char* content_type = NULL, const String& content = String(""));
|
||||
void send(int code, char* content_type, const String& content);
|
||||
void send(int code, const String& content_type, const String& content);
|
||||
void send_P(int code, PGM_P content_type, PGM_P content);
|
||||
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
||||
|
||||
void enableDelay(boolean value);
|
||||
void enableCORS(boolean value = true);
|
||||
void enableCrossOrigin(boolean value = true);
|
||||
|
||||
void setContentLength(const size_t contentLength);
|
||||
void sendHeader(const String& name, const String& value, bool first = false);
|
||||
void sendContent(const String& content);
|
||||
void sendContent(const char* content, size_t contentLength);
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
|
||||
static String urlDecode(const String& text);
|
||||
|
||||
template<typename T>
|
||||
size_t streamFile(T &file, const String& contentType) {
|
||||
_streamFileCore(file.size(), file.name(), contentType);
|
||||
return _currentClient->write(file);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient->write( b, l ); }
|
||||
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient->write_P( b, l ); }
|
||||
void _addRequestHandler(RequestHandler* handler);
|
||||
void _handleRequest();
|
||||
void _finalizeResponse();
|
||||
bool _parseRequest(EthClient* client);
|
||||
void _parseArguments(String data);
|
||||
static String _responseCodeToString(int code);
|
||||
bool _parseForm(EthClient* client, String boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
int _uploadReadByte(EthClient* client);
|
||||
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
|
||||
bool _collectHeader(const char* headerName, const char* headerValue);
|
||||
|
||||
void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType);
|
||||
|
||||
String _getRandomHexString();
|
||||
// for extracting Auth parameters
|
||||
String _extractParam(String& authReq,const String& param,const char delimit = '"');
|
||||
|
||||
struct RequestArgument {
|
||||
String key;
|
||||
String value;
|
||||
};
|
||||
|
||||
boolean _corsEnabled;
|
||||
EthServer* _server;
|
||||
|
||||
EthClient* _currentClient = nullptr;
|
||||
HTTPMethod _currentMethod;
|
||||
String _currentUri;
|
||||
uint8_t _currentVersion;
|
||||
HTTPClientStatus _currentStatus;
|
||||
unsigned long _statusChange;
|
||||
boolean _nullDelay;
|
||||
|
||||
RequestHandler* _currentHandler;
|
||||
RequestHandler* _firstHandler;
|
||||
RequestHandler* _lastHandler;
|
||||
THandlerFunction _notFoundHandler;
|
||||
THandlerFunction _fileUploadHandler;
|
||||
|
||||
int _currentArgCount;
|
||||
RequestArgument* _currentArgs;
|
||||
int _postArgsLen;
|
||||
RequestArgument* _postArgs;
|
||||
|
||||
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||
|
||||
int _headerKeysCount;
|
||||
RequestArgument* _currentHeaders;
|
||||
size_t _contentLength;
|
||||
String _responseHeaders;
|
||||
|
||||
String _hostHeader;
|
||||
bool _chunked;
|
||||
|
||||
String _snonce; // Store noance and opaque for future comparison
|
||||
String _sopaque;
|
||||
String _srealm; // Store the Auth realm between Calls
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
||||
31
lib/WebServer/src/detail/RequestHandler.h
Normal file
31
lib/WebServer/src/detail/RequestHandler.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef REQUESTHANDLER_H
|
||||
#define REQUESTHANDLER_H
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
virtual ~RequestHandler() { }
|
||||
virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; }
|
||||
virtual bool canUpload(String uri) { (void) uri; return false; }
|
||||
virtual bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }
|
||||
virtual void upload(WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; }
|
||||
|
||||
RequestHandler* next() { return _next; }
|
||||
void next(RequestHandler* r) { _next = r; }
|
||||
|
||||
private:
|
||||
RequestHandler* _next = nullptr;
|
||||
|
||||
protected:
|
||||
std::vector<String> pathArgs;
|
||||
|
||||
public:
|
||||
const String& pathArg(unsigned int i) {
|
||||
assert(i < pathArgs.size());
|
||||
return pathArgs[i];
|
||||
}
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLER_H
|
||||
151
lib/WebServer/src/detail/RequestHandlersImpl.h
Normal file
151
lib/WebServer/src/detail/RequestHandlersImpl.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#ifndef REQUESTHANDLERSIMPL_H
|
||||
#define REQUESTHANDLERSIMPL_H
|
||||
|
||||
#include "RequestHandler.h"
|
||||
#include "mimetable.h"
|
||||
#include "WString.h"
|
||||
#include "Uri.h"
|
||||
|
||||
using namespace mime;
|
||||
|
||||
class FunctionRequestHandler : public RequestHandler {
|
||||
public:
|
||||
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
|
||||
: _fn(fn)
|
||||
, _ufn(ufn)
|
||||
, _uri(uri.clone())
|
||||
, _method(method)
|
||||
{
|
||||
_uri->initPathArgs(pathArgs);
|
||||
}
|
||||
|
||||
~FunctionRequestHandler() {
|
||||
delete _uri;
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod)
|
||||
return false;
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs);
|
||||
}
|
||||
|
||||
bool canUpload(String requestUri) override {
|
||||
if (!_ufn || !canHandle(HTTP_POST, requestUri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||
(void) server;
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
_fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
void upload(WebServer& server, String requestUri, HTTPUpload& upload) override {
|
||||
(void) server;
|
||||
(void) upload;
|
||||
if (canUpload(requestUri))
|
||||
_ufn();
|
||||
}
|
||||
|
||||
protected:
|
||||
WebServer::THandlerFunction _fn;
|
||||
WebServer::THandlerFunction _ufn;
|
||||
Uri *_uri;
|
||||
HTTPMethod _method;
|
||||
};
|
||||
|
||||
class StaticRequestHandler : public RequestHandler {
|
||||
public:
|
||||
StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
|
||||
: _fs(fs)
|
||||
, _uri(uri)
|
||||
, _path(path)
|
||||
, _cache_header(cache_header)
|
||||
{
|
||||
_isFile = fs.exists(path);
|
||||
log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
||||
_baseUriLength = _uri.length();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (requestMethod != HTTP_GET)
|
||||
return false;
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
|
||||
String path(_path);
|
||||
|
||||
if (!_isFile) {
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (requestUri.endsWith("/"))
|
||||
requestUri += "index.htm";
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
}
|
||||
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
|
||||
String contentType = getContentType(path);
|
||||
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if(_fs.exists(pathWithGz))
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f || !f.available())
|
||||
return false;
|
||||
|
||||
if (_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
|
||||
server.streamFile(f, contentType);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String getContentType(const String& path) {
|
||||
char buff[sizeof(mimeTable[0].mimeType)];
|
||||
// Check all entries but last one for match, return if found
|
||||
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
|
||||
strcpy_P(buff, mimeTable[i].endsWith);
|
||||
if (path.endsWith(buff)) {
|
||||
strcpy_P(buff, mimeTable[i].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
}
|
||||
// Fall-through and just return default type
|
||||
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
||||
35
lib/WebServer/src/detail/mimetable.cpp
Normal file
35
lib/WebServer/src/detail/mimetable.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#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" }
|
||||
};
|
||||
|
||||
}
|
||||
47
lib/WebServer/src/detail/mimetable.h
Normal file
47
lib/WebServer/src/detail/mimetable.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#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];
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
90
lib/WebServer/src/hardware/W5500EthClient.cpp
Normal file
90
lib/WebServer/src/hardware/W5500EthClient.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "W5500EthClient.h"
|
||||
|
||||
W5500EthClient::W5500EthClient(EthernetClient *wifiClient)
|
||||
: _ethClient(wifiClient)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
W5500EthClient::~W5500EthClient()
|
||||
{
|
||||
_ethClient = nullptr;
|
||||
}
|
||||
|
||||
uint8_t W5500EthClient::connected()
|
||||
{
|
||||
return _ethClient->connected();
|
||||
}
|
||||
|
||||
int W5500EthClient::setTimeout(uint32_t seconds)
|
||||
{
|
||||
// return _ethClient->setTimeout(seconds);
|
||||
}
|
||||
|
||||
size_t W5500EthClient::write(const char *buffer, size_t size)
|
||||
{
|
||||
return _ethClient->write(buffer, size);
|
||||
}
|
||||
|
||||
IPAddress W5500EthClient::localIP()
|
||||
{
|
||||
return Ethernet.localIP();
|
||||
}
|
||||
|
||||
void W5500EthClient::stop()
|
||||
{
|
||||
_ethClient->stop();
|
||||
}
|
||||
|
||||
size_t W5500EthClient::write_P(const char *buf, size_t size)
|
||||
{
|
||||
return _ethClient->write(buf, size);
|
||||
}
|
||||
|
||||
int W5500EthClient::available()
|
||||
{
|
||||
return _ethClient->available();
|
||||
}
|
||||
|
||||
String W5500EthClient::readStringUntil(char terminator)
|
||||
{
|
||||
return _ethClient->readStringUntil(terminator);
|
||||
}
|
||||
|
||||
size_t W5500EthClient::readBytes(char *buffer, size_t length)
|
||||
{
|
||||
return _ethClient->readBytes(buffer, length);
|
||||
}
|
||||
|
||||
void W5500EthClient::flush()
|
||||
{
|
||||
_ethClient->flush();
|
||||
}
|
||||
|
||||
int W5500EthClient::read()
|
||||
{
|
||||
return _ethClient->read();
|
||||
}
|
||||
|
||||
unsigned long W5500EthClient::getTimeout(void)
|
||||
{
|
||||
return _ethClient->getTimeout();
|
||||
}
|
||||
|
||||
size_t W5500EthClient::write(Stream &stream)
|
||||
{
|
||||
uint8_t * buf = (uint8_t *)malloc(1360);
|
||||
if(!buf){
|
||||
return 0;
|
||||
}
|
||||
size_t toRead = 0, toWrite = 0, written = 0;
|
||||
size_t available = stream.available();
|
||||
while(available){
|
||||
toRead = (available > 1360)?1360:available;
|
||||
toWrite = stream.readBytes(buf, toRead);
|
||||
written += _ethClient->write(buf, toWrite);
|
||||
available = stream.available();
|
||||
}
|
||||
free(buf);
|
||||
return written;
|
||||
}
|
||||
29
lib/WebServer/src/hardware/W5500EthClient.h
Normal file
29
lib/WebServer/src/hardware/W5500EthClient.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <EthernetClient.h>
|
||||
#include "EthClient.h"
|
||||
|
||||
class W5500EthClient : public EthClient
|
||||
{
|
||||
public:
|
||||
explicit W5500EthClient(EthernetClient* wifiClient);
|
||||
virtual ~W5500EthClient();
|
||||
|
||||
uint8_t connected() override;
|
||||
int available() override;
|
||||
unsigned long getTimeout(void) override;
|
||||
int setTimeout(uint32_t seconds) override;
|
||||
int read() override;
|
||||
size_t write(const char *buffer, size_t size) override;
|
||||
size_t write(Stream &stream) override;
|
||||
size_t write_P(const char *buf, size_t size) override;
|
||||
String readStringUntil(char terminator) override;
|
||||
size_t readBytes(char *buffer, size_t length) override;
|
||||
IPAddress localIP() override;
|
||||
void stop() override;
|
||||
void flush() override;
|
||||
|
||||
private:
|
||||
EthernetClient* _ethClient;
|
||||
};
|
||||
75
lib/WebServer/src/hardware/W5500EthServer.cpp
Normal file
75
lib/WebServer/src/hardware/W5500EthServer.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "W5500EthServer.h"
|
||||
|
||||
|
||||
W5500EthServer::W5500EthServer(IPAddress address, int port)
|
||||
: EthServer(address, port),
|
||||
_ethServer(address, port)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
W5500EthServer::W5500EthServer(int port)
|
||||
: EthServer(port),
|
||||
_ethServer(port)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void W5500EthServer::close()
|
||||
{
|
||||
// _ethServer.close();
|
||||
}
|
||||
|
||||
void W5500EthServer::begin(const int port)
|
||||
{
|
||||
_ethServer.begin(port);
|
||||
}
|
||||
|
||||
void W5500EthServer::setNoDelay(const bool value)
|
||||
{
|
||||
// _ethServer.setNoDelay(value);
|
||||
}
|
||||
|
||||
EthClient* W5500EthServer::available()
|
||||
{
|
||||
if(_W5500EthClient != nullptr)
|
||||
{
|
||||
delete _W5500EthClient;
|
||||
_W5500EthClient = nullptr;
|
||||
}
|
||||
|
||||
_ethClient = _ethServer.available();
|
||||
_W5500EthClient = new W5500EthClient(&_ethClient);
|
||||
return _W5500EthClient;
|
||||
}
|
||||
|
||||
|
||||
void W5500EthServer::discardClient()
|
||||
{
|
||||
if(_W5500EthClient != nullptr)
|
||||
{
|
||||
delete _W5500EthClient;
|
||||
_W5500EthClient = nullptr;
|
||||
}
|
||||
|
||||
_ethClient = EthernetClient();
|
||||
}
|
||||
|
||||
|
||||
// EthernetServerImpl
|
||||
void EthernetServerImpl::begin(uint16_t port)
|
||||
{
|
||||
EthernetServer::begin();
|
||||
}
|
||||
|
||||
EthernetServerImpl::EthernetServerImpl(int address, int port)
|
||||
: EthernetServer(port)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
EthernetServerImpl::EthernetServerImpl(int port)
|
||||
: EthernetServer(port)
|
||||
{
|
||||
|
||||
}
|
||||
35
lib/WebServer/src/hardware/W5500EthServer.h
Normal file
35
lib/WebServer/src/hardware/W5500EthServer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "EthServer.h"
|
||||
#include "W5500EthClient.h"
|
||||
#include <WiFiServer.h>
|
||||
#include <EthernetServer.h>
|
||||
|
||||
class EthernetServerImpl : public EthernetServer
|
||||
{
|
||||
public:
|
||||
EthernetServerImpl(int address, int port);
|
||||
explicit EthernetServerImpl(int port);
|
||||
|
||||
virtual void begin(uint16_t port);
|
||||
};
|
||||
|
||||
class W5500EthServer : public EthServer
|
||||
{
|
||||
public:
|
||||
W5500EthServer(IPAddress address, int port);
|
||||
explicit W5500EthServer(int port);
|
||||
|
||||
virtual EthClient* available();
|
||||
virtual void discardClient();
|
||||
|
||||
virtual void begin(const int port = 80);
|
||||
virtual void close();
|
||||
|
||||
virtual void setNoDelay(const bool value);
|
||||
|
||||
private:
|
||||
EthernetServerImpl _ethServer;
|
||||
EthernetClient _ethClient;
|
||||
W5500EthClient* _W5500EthClient = nullptr;
|
||||
};
|
||||
77
lib/WebServer/src/hardware/WifiEthClient.cpp
Normal file
77
lib/WebServer/src/hardware/WifiEthClient.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "WifiEthClient.h"
|
||||
|
||||
WifiEthClient::WifiEthClient(WiFiClient *wifiClient)
|
||||
: _wifiClient(wifiClient)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WifiEthClient::~WifiEthClient()
|
||||
{
|
||||
_wifiClient = nullptr;
|
||||
}
|
||||
|
||||
uint8_t WifiEthClient::connected()
|
||||
{
|
||||
return _wifiClient->connected();
|
||||
}
|
||||
|
||||
int WifiEthClient::setTimeout(uint32_t seconds)
|
||||
{
|
||||
return _wifiClient->setTimeout(seconds);
|
||||
}
|
||||
|
||||
size_t WifiEthClient::write(const char *buffer, size_t size)
|
||||
{
|
||||
return _wifiClient->write(buffer, size);
|
||||
}
|
||||
|
||||
IPAddress WifiEthClient::localIP()
|
||||
{
|
||||
return _wifiClient->localIP();
|
||||
}
|
||||
|
||||
void WifiEthClient::stop()
|
||||
{
|
||||
_wifiClient->stop();
|
||||
}
|
||||
|
||||
size_t WifiEthClient::write_P(const char *buf, size_t size)
|
||||
{
|
||||
return _wifiClient->write_P(buf, size);
|
||||
}
|
||||
|
||||
int WifiEthClient::available()
|
||||
{
|
||||
return _wifiClient->available();
|
||||
}
|
||||
|
||||
String WifiEthClient::readStringUntil(char terminator)
|
||||
{
|
||||
return _wifiClient->readStringUntil(terminator);
|
||||
}
|
||||
|
||||
size_t WifiEthClient::readBytes(char *buffer, size_t length)
|
||||
{
|
||||
return _wifiClient->readBytes(buffer, length);
|
||||
}
|
||||
|
||||
void WifiEthClient::flush()
|
||||
{
|
||||
_wifiClient->flush();
|
||||
}
|
||||
|
||||
int WifiEthClient::read()
|
||||
{
|
||||
return _wifiClient->read();
|
||||
}
|
||||
|
||||
unsigned long WifiEthClient::getTimeout(void)
|
||||
{
|
||||
return _wifiClient->getTimeout();
|
||||
}
|
||||
|
||||
size_t WifiEthClient::write(Stream &stream)
|
||||
{
|
||||
return _wifiClient->write(stream);
|
||||
}
|
||||
28
lib/WebServer/src/hardware/WifiEthClient.h
Normal file
28
lib/WebServer/src/hardware/WifiEthClient.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include "EthClient.h"
|
||||
|
||||
class WifiEthClient : public EthClient
|
||||
{
|
||||
public:
|
||||
explicit WifiEthClient(WiFiClient* wifiClient);
|
||||
virtual ~WifiEthClient();
|
||||
|
||||
uint8_t connected() override;
|
||||
int available() override;
|
||||
unsigned long getTimeout(void) override;
|
||||
int setTimeout(uint32_t seconds) override;
|
||||
int read() override;
|
||||
size_t write(const char *buffer, size_t size) override;
|
||||
size_t write(Stream &stream) override;
|
||||
size_t write_P(const char *buf, size_t size) override;
|
||||
String readStringUntil(char terminator) override;
|
||||
size_t readBytes(char *buffer, size_t length) override;
|
||||
IPAddress localIP() override;
|
||||
void stop() override;
|
||||
void flush() override;
|
||||
|
||||
private:
|
||||
WiFiClient* _wifiClient;
|
||||
};
|
||||
56
lib/WebServer/src/hardware/WifiEthServer.cpp
Normal file
56
lib/WebServer/src/hardware/WifiEthServer.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "WifiEthServer.h"
|
||||
|
||||
|
||||
WifiEthServer::WifiEthServer(IPAddress address, int port)
|
||||
: EthServer(address, port),
|
||||
_wifiServer(address, port)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WifiEthServer::WifiEthServer(int port)
|
||||
: EthServer(port),
|
||||
_wifiServer(port)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WifiEthServer::close()
|
||||
{
|
||||
_wifiServer.close();
|
||||
}
|
||||
|
||||
void WifiEthServer::begin(const int port)
|
||||
{
|
||||
_wifiServer.begin(port);
|
||||
}
|
||||
|
||||
void WifiEthServer::setNoDelay(const bool value)
|
||||
{
|
||||
_wifiServer.setNoDelay(value);
|
||||
}
|
||||
|
||||
EthClient* WifiEthServer::available()
|
||||
{
|
||||
if(_wifiEthClient != nullptr)
|
||||
{
|
||||
delete _wifiEthClient;
|
||||
_wifiEthClient = nullptr;
|
||||
}
|
||||
|
||||
_wifiClient = _wifiServer.available();
|
||||
_wifiEthClient = new WifiEthClient(&_wifiClient);
|
||||
return _wifiEthClient;
|
||||
}
|
||||
|
||||
|
||||
void WifiEthServer::discardClient()
|
||||
{
|
||||
if(_wifiEthClient != nullptr)
|
||||
{
|
||||
delete _wifiEthClient;
|
||||
_wifiEthClient = nullptr;
|
||||
}
|
||||
|
||||
_wifiClient = WiFiClient();
|
||||
}
|
||||
25
lib/WebServer/src/hardware/WifiEthServer.h
Normal file
25
lib/WebServer/src/hardware/WifiEthServer.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "EthServer.h"
|
||||
#include "WifiEthClient.h"
|
||||
#include <WiFiServer.h>
|
||||
|
||||
class WifiEthServer : public EthServer
|
||||
{
|
||||
public:
|
||||
WifiEthServer(IPAddress address, int port);
|
||||
explicit WifiEthServer(int port);
|
||||
|
||||
virtual EthClient* available();
|
||||
virtual void discardClient();
|
||||
|
||||
virtual void begin(const int port = 80);
|
||||
virtual void close();
|
||||
|
||||
virtual void setNoDelay(const bool value);
|
||||
|
||||
private:
|
||||
WiFiServer _wifiServer;
|
||||
WiFiClient _wifiClient;
|
||||
WifiEthClient* _wifiEthClient = nullptr;
|
||||
};
|
||||
66
lib/WebServer/src/uri/UriBraces.h
Normal file
66
lib/WebServer/src/uri/UriBraces.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#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
|
||||
44
lib/WebServer/src/uri/UriRegex.h
Normal file
44
lib/WebServer/src/uri/UriRegex.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#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
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include "WiFiManager.h"
|
||||
#include "hardware/WifiEthServer.h"
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
@@ -586,7 +587,7 @@ void WiFiManager::setupHTTPServer(){
|
||||
#endif
|
||||
}
|
||||
|
||||
server.reset(new WM_WebServer(_httpPort));
|
||||
server.reset(new WM_WebServer(new WifiEthServer(80)));
|
||||
// This is not the safest way to reset the webserver, it can cause crashes on callbacks initilized before this and since its a shared pointer...
|
||||
|
||||
if ( _webservercallback != NULL) {
|
||||
@@ -2290,7 +2291,7 @@ boolean WiFiManager::captivePortal() {
|
||||
|
||||
if(!_enableCaptivePortal) return false; // skip redirections, @todo maybe allow redirection even when no cp ? might be useful
|
||||
|
||||
String serverLoc = toStringIp(server->client().localIP());
|
||||
String serverLoc = toStringIp(server->client()->localIP());
|
||||
if(_httpPort != 80) serverLoc += ":" + (String)_httpPort; // add port if not default
|
||||
bool doredirect = serverLoc != server->hostHeader(); // redirect if hostheader not server ip, prevent redirect loops
|
||||
// doredirect = !isIp(server->hostHeader()) // old check
|
||||
@@ -2301,7 +2302,7 @@ boolean WiFiManager::captivePortal() {
|
||||
#endif
|
||||
server->sendHeader(F("Location"), (String)F("http://") + serverLoc, true); // @HTTPHEAD send redirect
|
||||
server->send ( 302, FPSTR(HTTP_HEAD_CT2), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server->client().stop(); // Stop is needed because we sent no content length
|
||||
server->client()->stop(); // Stop is needed because we sent no content length
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
Submodule lib/nuki_ble updated: 7bb5d57d9e...75d63e4d7a
@@ -20,7 +20,7 @@ void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
EthernetClient ethClient;
|
||||
server-> ethClient;
|
||||
PubSubClient client(server, 1883, callback, ethClient);
|
||||
|
||||
void setup()
|
||||
|
||||
43
main.cpp
43
main.cpp
@@ -1,18 +1,20 @@
|
||||
#include "Arduino.h"
|
||||
#include "Pins.h"
|
||||
#include "NukiWrapper.h"
|
||||
#include "Network.h"
|
||||
#include "WebCfgServer.h"
|
||||
#include <FreeRTOS.h>
|
||||
#include "PreferencesKeys.h"
|
||||
#include "PresenceDetection.h"
|
||||
#include "hardware/W5500EthServer.h"
|
||||
#include "hardware/WifiEthServer.h"
|
||||
|
||||
#define ESP32
|
||||
|
||||
Network* network;
|
||||
WebCfgServer* webCfgServer;
|
||||
NukiWrapper* nuki;
|
||||
PresenceDetection* presenceDetection;
|
||||
Preferences* preferences;
|
||||
Network* network = nullptr;
|
||||
WebCfgServer* webCfgServer = nullptr;
|
||||
NukiWrapper* nuki = nullptr;
|
||||
PresenceDetection* presenceDetection = nullptr;
|
||||
Preferences* preferences = nullptr;
|
||||
EthServer* ethServer = nullptr;
|
||||
|
||||
void networkTask(void *pvParameters)
|
||||
{
|
||||
@@ -78,13 +80,34 @@ uint32_t getRandomId()
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
void initEthServer(const NetworkDeviceType device)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case NetworkDeviceType::W5500:
|
||||
ethServer = new W5500EthServer(80);
|
||||
break;
|
||||
case NetworkDeviceType::WiFi:
|
||||
ethServer = new WifiEthServer(80);
|
||||
break;
|
||||
default:
|
||||
ethServer = new WifiEthServer(80);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
pinMode(NETWORK_SELECT, INPUT_PULLUP);
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
// const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi;
|
||||
const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
|
||||
|
||||
preferences = new Preferences();
|
||||
preferences->begin("nukihub", false);
|
||||
network = new Network(preferences);
|
||||
network = new Network(networkDevice, preferences);
|
||||
network->initialize();
|
||||
|
||||
uint32_t deviceId = preferences->getUInt(preference_deviceId);
|
||||
@@ -94,8 +117,10 @@ void setup()
|
||||
preferences->putUInt(preference_deviceId, deviceId);
|
||||
}
|
||||
|
||||
initEthServer(networkDevice);
|
||||
|
||||
nuki = new NukiWrapper("NukiHub", deviceId, network, preferences);
|
||||
webCfgServer = new WebCfgServer(nuki, network, preferences);
|
||||
webCfgServer = new WebCfgServer(nuki, network, ethServer, preferences, networkDevice == NetworkDeviceType::WiFi);
|
||||
webCfgServer->initialize();
|
||||
nuki->initialize();
|
||||
|
||||
|
||||
24
networkDevices/NetworkDevice.h
Normal file
24
networkDevices/NetworkDevice.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "PubSubClient.h"
|
||||
|
||||
class NetworkDevice
|
||||
{
|
||||
public:
|
||||
explicit NetworkDevice(const String& hostname)
|
||||
: _hostname(hostname)
|
||||
{}
|
||||
|
||||
virtual PubSubClient* mqttClient() = 0;
|
||||
|
||||
virtual void initialize() = 0;
|
||||
virtual bool reconnect() = 0;
|
||||
virtual void reconfigure() = 0;
|
||||
|
||||
virtual void update() = 0;
|
||||
|
||||
virtual bool isConnected() = 0;
|
||||
|
||||
protected:
|
||||
const String _hostname;
|
||||
};
|
||||
167
networkDevices/W5500Device.cpp
Normal file
167
networkDevices/W5500Device.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "W5500Device.h"
|
||||
#include "../Pins.h"
|
||||
#include "../PreferencesKeys.h"
|
||||
|
||||
W5500Device::W5500Device(const String &hostname, Preferences* preferences)
|
||||
: NetworkDevice(hostname),
|
||||
_preferences(preferences)
|
||||
{
|
||||
initializeMacAddress(_mac);
|
||||
|
||||
Serial.print("MAC Adress: ");
|
||||
for(int i=0; i < 6; i++)
|
||||
{
|
||||
if(_mac[i] < 10)
|
||||
{
|
||||
Serial.print(F("0"));
|
||||
}
|
||||
Serial.print(_mac[i], 16);
|
||||
if(i < 5)
|
||||
{
|
||||
Serial.print(F(":"));
|
||||
}
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
W5500Device::~W5500Device()
|
||||
{}
|
||||
|
||||
void W5500Device::initialize()
|
||||
{
|
||||
resetDevice();
|
||||
|
||||
Ethernet.init(ETHERNET_CS_PIN);
|
||||
_ethClient = new EthernetClient();
|
||||
_mqttClient = new PubSubClient(*_ethClient);
|
||||
|
||||
reconnect();
|
||||
|
||||
_fromTask = true;
|
||||
}
|
||||
|
||||
|
||||
bool W5500Device::reconnect()
|
||||
{
|
||||
_hasDHCPAddress = false;
|
||||
|
||||
// start the Ethernet connection:
|
||||
Serial.println(F("Initialize Ethernet with DHCP:"));
|
||||
|
||||
int dhcpRetryCnt = 0;
|
||||
|
||||
while(dhcpRetryCnt < 3)
|
||||
{
|
||||
Serial.print(F("DHCP connect try #"));
|
||||
Serial.print(dhcpRetryCnt);
|
||||
Serial.println();
|
||||
dhcpRetryCnt++;
|
||||
|
||||
if (Ethernet.begin(_mac, 1000, 1000) == 0)
|
||||
{
|
||||
Serial.println(F("Failed to configure Ethernet using DHCP"));
|
||||
// Check for Ethernet hardware present
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
{
|
||||
Serial.println(F("Ethernet module not found"));
|
||||
}
|
||||
if (Ethernet.linkStatus() == LinkOFF)
|
||||
{
|
||||
Serial.println(F("Ethernet cable is not connected."));
|
||||
}
|
||||
|
||||
IPAddress ip;
|
||||
ip.fromString("192.168.4.1");
|
||||
|
||||
IPAddress subnet;
|
||||
subnet.fromString("255.255.255.0");
|
||||
|
||||
// try to congifure using IP address instead of DHCP:
|
||||
Ethernet.begin(_mac, ip);
|
||||
Ethernet.setSubnetMask(subnet);
|
||||
|
||||
nwDelay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasDHCPAddress = true;
|
||||
dhcpRetryCnt = 1000;
|
||||
Serial.print(F(" DHCP assigned IP "));
|
||||
Serial.println(Ethernet.localIP());
|
||||
}
|
||||
}
|
||||
|
||||
return _hasDHCPAddress;
|
||||
}
|
||||
|
||||
|
||||
void W5500Device::reconfigure()
|
||||
{
|
||||
Serial.println(F("Reconfigure W5500 not implemented."));
|
||||
}
|
||||
|
||||
void W5500Device::resetDevice()
|
||||
{
|
||||
Serial.println(F("Resetting network hardware."));
|
||||
pinMode(ETHERNET_RESET_PIN, OUTPUT);
|
||||
digitalWrite(ETHERNET_RESET_PIN, HIGH);
|
||||
nwDelay(250);
|
||||
digitalWrite(ETHERNET_RESET_PIN, LOW);
|
||||
nwDelay(50);
|
||||
digitalWrite(ETHERNET_RESET_PIN, HIGH);
|
||||
nwDelay(1500);
|
||||
}
|
||||
|
||||
PubSubClient *W5500Device::mqttClient()
|
||||
{
|
||||
return _mqttClient;
|
||||
}
|
||||
|
||||
bool W5500Device::isConnected()
|
||||
{
|
||||
return Ethernet.linkStatus() == EthernetLinkStatus::LinkON && _maintainResult == 0 && _hasDHCPAddress;
|
||||
}
|
||||
|
||||
void W5500Device::initializeMacAddress(byte *mac)
|
||||
{
|
||||
memset(mac, 0, 6);
|
||||
|
||||
mac[0] = 0x00; // wiznet prefix
|
||||
mac[1] = 0x08; // wiznet prefix
|
||||
mac[2] = 0xDC; // wiznet prefix
|
||||
|
||||
if(_preferences->getBool(preference_has_mac_saved))
|
||||
{
|
||||
mac[3] = _preferences->getChar(preference_has_mac_byte_0);
|
||||
mac[4] = _preferences->getChar(preference_has_mac_byte_1);
|
||||
mac[5] = _preferences->getChar(preference_has_mac_byte_2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mac[3] = random(0,255);
|
||||
mac[4] = random(0,255);
|
||||
mac[5] = random(0,255);
|
||||
|
||||
_preferences->putChar(preference_has_mac_byte_0, mac[3]);
|
||||
_preferences->putChar(preference_has_mac_byte_1, mac[4]);
|
||||
_preferences->putChar(preference_has_mac_byte_2, mac[5]);
|
||||
_preferences->putBool(preference_has_mac_saved, true);
|
||||
}
|
||||
}
|
||||
|
||||
void W5500Device::nwDelay(unsigned long ms)
|
||||
{
|
||||
if(_fromTask)
|
||||
{
|
||||
vTaskDelay( ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
else
|
||||
{
|
||||
delay(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void W5500Device::update()
|
||||
{
|
||||
_maintainResult = Ethernet.maintain();
|
||||
}
|
||||
38
networkDevices/W5500Device.h
Normal file
38
networkDevices/W5500Device.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "NetworkDevice.h"
|
||||
#include <Ethernet.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
class W5500Device : public NetworkDevice
|
||||
{
|
||||
public:
|
||||
explicit W5500Device(const String& hostname, Preferences* _preferences);
|
||||
~W5500Device();
|
||||
|
||||
virtual void initialize();
|
||||
virtual bool reconnect();
|
||||
virtual void reconfigure();
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isConnected();
|
||||
|
||||
virtual PubSubClient *mqttClient();
|
||||
|
||||
private:
|
||||
void resetDevice();
|
||||
void initializeMacAddress(byte* mac);
|
||||
void nwDelay(unsigned long ms);
|
||||
|
||||
bool _fromTask = false;
|
||||
|
||||
EthernetClient* _ethClient = nullptr;
|
||||
PubSubClient* _mqttClient = nullptr;
|
||||
Preferences* _preferences = nullptr;
|
||||
|
||||
int _maintainResult = 0;
|
||||
bool _hasDHCPAddress = false;
|
||||
|
||||
byte _mac[6];
|
||||
};
|
||||
74
networkDevices/WifiDevice.cpp
Normal file
74
networkDevices/WifiDevice.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <WiFi.h>
|
||||
#include "WifiDevice.h"
|
||||
#include "WiFiManager.h"
|
||||
|
||||
WifiDevice::WifiDevice(const String& hostname)
|
||||
: NetworkDevice(hostname),
|
||||
_mqttClient(_wifiClient)
|
||||
{}
|
||||
|
||||
PubSubClient *WifiDevice::mqttClient()
|
||||
{
|
||||
return &_mqttClient;
|
||||
}
|
||||
|
||||
void WifiDevice::initialize()
|
||||
{
|
||||
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
std::vector<const char *> wm_menu;
|
||||
wm_menu.push_back("wifi");
|
||||
wm_menu.push_back("exit");
|
||||
wm.setShowInfoUpdate(false);
|
||||
wm.setMenu(wm_menu);
|
||||
wm.setHostname(_hostname);
|
||||
|
||||
bool res = false;
|
||||
|
||||
if(_cookie.isSet())
|
||||
{
|
||||
Serial.println(F("Opening WiFi configuration portal."));
|
||||
_cookie.clear();
|
||||
res = wm.startConfigPortal();
|
||||
}
|
||||
else
|
||||
{
|
||||
res = wm.autoConnect(); // password protected ap
|
||||
}
|
||||
|
||||
if(!res) {
|
||||
Serial.println(F("Failed to connect. Wait for ESP restart."));
|
||||
delay(10000);
|
||||
ESP.restart();
|
||||
}
|
||||
else {
|
||||
Serial.print(F("WiFi connected: "));
|
||||
Serial.println(WiFi.localIP().toString());
|
||||
}
|
||||
}
|
||||
|
||||
void WifiDevice::reconfigure()
|
||||
{
|
||||
_cookie.set();
|
||||
delay(200);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
bool WifiDevice::isConnected()
|
||||
{
|
||||
return WiFi.isConnected();
|
||||
}
|
||||
|
||||
bool WifiDevice::reconnect()
|
||||
{
|
||||
vTaskDelay( 1000 / portTICK_PERIOD_MS);
|
||||
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
void WifiDevice::update()
|
||||
{
|
||||
|
||||
}
|
||||
26
networkDevices/WifiDevice.h
Normal file
26
networkDevices/WifiDevice.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include "NetworkDevice.h"
|
||||
#include "../SpiffsCookie.h"
|
||||
|
||||
class WifiDevice : public NetworkDevice
|
||||
{
|
||||
public:
|
||||
WifiDevice(const String& hostname);
|
||||
|
||||
virtual void initialize();
|
||||
virtual void reconfigure();
|
||||
virtual bool reconnect();
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isConnected();
|
||||
|
||||
virtual PubSubClient *mqttClient();
|
||||
|
||||
private:
|
||||
WiFiClient _wifiClient;
|
||||
PubSubClient _mqttClient;
|
||||
SpiffsCookie _cookie;
|
||||
};
|
||||
Binary file not shown.
Reference in New Issue
Block a user