Switch HTTP Server

This commit is contained in:
iranl
2024-08-26 21:47:10 +02:00
parent d3c3589233
commit ca9c2feebc
234 changed files with 20090 additions and 8061 deletions

View File

@@ -0,0 +1,6 @@
.pio
.vscode/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -0,0 +1,497 @@
/*
PsychicHTTP Server Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/**********************************************************************************************
* Note: this demo relies on the following libraries (Install via Library Manager)
* ArduinoJson UrlEncode
**********************************************************************************************/
/**********************************************************************************************
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
**********************************************************************************************/
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include "_secret.h"
#include <PsychicHttp.h>
#include <PsychicHttpsServer.h> //uncomment this to enable HTTPS / SSL
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Set your SoftAP credentials
const char *softap_ssid = "PsychicHttp";
const char *softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
//credentials for the /auth-basic and /auth-digest examples
const char *app_user = "admin";
const char *app_pass = "admin";
const char *app_name = "Your App";
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
//#define PSY_ENABLE_SSL to enable ssl
#ifdef PSY_ENABLE_SSL
bool app_enable_ssl = true;
String server_cert;
String server_key;
#endif
//our main server object
#ifdef PSY_ENABLE_SSL
PsychicHttpsServer server;
#else
PsychicHttpServer server;
#endif
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
bool connectToWifi()
{
//dual client and AP mode
WiFi.mode(WIFI_AP_STA);
// Configure SoftAP
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
WiFi.softAP(softap_ssid, softap_password);
IPAddress myIP = WiFi.softAPIP();
Serial.print("SoftAP IP Address: ");
Serial.println(myIP);
Serial.println();
Serial.print("[WiFi] Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Auto reconnect is set true as default
// To set auto connect off, use the following function
// WiFi.setAutoReconnect(false);
// Will try for about 10 seconds (20x 500ms)
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
case WL_CONNECT_FAILED:
Serial.print("[WiFi] Failed - WiFi not connected! Reason: ");
return false;
break;
case WL_CONNECTION_LOST:
Serial.println("[WiFi] Connection was lost");
break;
case WL_SCAN_COMPLETED:
Serial.println("[WiFi] Scan is completed");
break;
case WL_DISCONNECTED:
Serial.println("[WiFi] WiFi is disconnected");
break;
case WL_CONNECTED:
Serial.println("[WiFi] WiFi is connected!");
Serial.print("[WiFi] IP address: ");
Serial.println(WiFi.localIP());
return true;
break;
default:
Serial.print("[WiFi] WiFi Status: ");
Serial.println(WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0)
{
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
numberOfTries--;
}
}
return false;
}
void setup()
{
Serial.begin(115200);
delay(10);
// We start by connecting to a WiFi network
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
//set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin())
{
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//look up our keys?
#ifdef PSY_ENABLE_SSL
if (app_enable_ssl)
{
File fp = LittleFS.open("/server.crt");
if (fp)
{
server_cert = fp.readString();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
}
else
{
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
fp.close();
File fp2 = LittleFS.open("/server.key");
if (fp2)
{
server_key = fp2.readString();
// Serial.println("Server Key:");
// Serial.println(server_key);
}
else
{
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
fp2.close();
}
#endif
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
#ifdef PSY_ENABLE_SSL
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
//do we want secure or not?
if (app_enable_ssl)
{
server.listen(443, server_cert.c_str(), server_key.c_str());
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer *redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->listen(80);
redirectServer->onNotFound([](PsychicRequest *request) {
String url = "https://" + request->host() + request->url();
return request->redirect(url.c_str());
});
}
else
server.listen(80);
#else
server.listen(80);
#endif
//serve static files from LittleFS/www on / only to clients on same wifi network
//this is where our /index.html file lives
server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER);
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
//this is where our /index.html file lives
server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER);
//serve static files from LittleFS/img on /img
//it's more efficient to serve everything from a single www directory, but this is also possible.
server.serveStatic("/img", LittleFS, "/img/");
//you can also serve single files
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
//example callback everytime a connection is opened
server.onOpen([](PsychicClient *client) {
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
});
//example callback everytime a connection is closed
server.onClose([](PsychicClient *client) {
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
});
//api - json message passed in as post body
server.on("/api", HTTP_POST, [](PsychicRequest *request)
{
//load our JSON request
StaticJsonDocument<1024> json;
String body = request->body();
DeserializationError err = deserializeJson(json, body);
//create our response json
StaticJsonDocument<128> output;
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
//work with some params
if (json.containsKey("foo"))
{
String foo = json["foo"];
output["foo"] = foo;
}
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
{
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
});
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
//create a response object
StaticJsonDocument<128> output;
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
//work with some params
if (request->hasParam("foo"))
{
String foo = request->getParam("foo")->name();
output["foo"] = foo;
}
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
//how to redirect a request
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
{
return request->redirect("/alien.png");
});
//how to do basic auth
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
return request->reply("Auth Basic Success!");
});
//how to do digest auth
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
return request->reply("Auth Digest Success!");
});
//example of getting / setting cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
{
PsychicResponse response(request);
int counter = 0;
if (request->hasCookie("counter"))
{
counter = std::stoi(request->getCookie("counter").c_str());
counter++;
}
char cookie[10];
sprintf(cookie, "%i", counter);
response.setCookie("counter", cookie);
response.setContent(cookie);
return response.send();
});
//example of getting POST variables
server.on("/post", HTTP_POST, [](PsychicRequest *request)
{
String output;
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
});
//you can set up a custom 404 handler.
server.onNotFound([](PsychicRequest *request)
{
return request->reply(404, "text/html", "Custom 404 Handler");
});
//handle a very basic upload as post body
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler();
uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
File file;
String path = "/www/" + filename;
Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str());
if (last)
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
//our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
return ESP_OK;
});
//gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest *request)
{
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
server.on("/upload/*", HTTP_POST, uploadHandler);
//a little bit more complicated multipart form
PsychicUploadHandler *multipartHandler = new PsychicUploadHandler();
multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
File file;
String path = "/www/" + filename;
//some progress over serial.
Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str());
if (last)
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
//our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
return ESP_OK;
});
//gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest *request)
{
PsychicWebParameter *file = request->getParam("file_upload");
String url = "/" + file->value();
String output;
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
output += "Bytes: " + String(file->size()) + "<br/>\n";
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
server.on("/multipart", HTTP_POST, multipartHandler);
//a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
return request->reply(frame);
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
});
server.on("/ws", &websocketHandler);
//EventSource server
eventSource.onOpen([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
client->send("Hello user!", NULL, millis(), 1000);
});
eventSource.onClose([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
});
server.on("/events", &eventSource);
}
}
unsigned long lastUpdate = 0;
char output[60];
void loop()
{
if (millis() - lastUpdate > 2000)
{
sprintf(output, "Millis: %d\n", millis());
websocketHandler.sendAll(output);
sprintf(output, "%d", millis());
eventSource.send(output, "millis", millis(), 0);
lastUpdate = millis();
}
}

View File

@@ -0,0 +1,60 @@
**1) SUMMARY**
This example implements a **captive portal** with library DNSServer, ie web page which opens automatically when user connects Wifi network (eg "PsychitHttp").
Captiveportal is implemented in **ESPAsyncWebServer** [https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino](url) and in **arduino-esp32 examples** [https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino](url)
This feature can be implemented with Psychichttp with a **dedicated handler**, as shown in code below.
Code highlights are added below for reference.
**2) CODE**
**Definitions**
```
// captiveportal
// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino
//https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino
#include <DNSServer.h>
DNSServer dnsServer;
class CaptiveRequestHandler : public PsychicWebHandler { // handler
public:
CaptiveRequestHandler() {};
virtual ~CaptiveRequestHandler() {};
bool canHandle(PsychicRequest*request){
// ... if needed some tests ... return(false);
return true; // activate captive portal
}
esp_err_t handleRequest(PsychicRequest *request) {
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
//return response.send(); // uncomment : return captive portal page
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
}
};
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
```
**setup()**
```
// captive portal
dnsServer.start(53, "*", WiFi.softAPIP()); // DNS requests are executed over port 53 (standard)
captivehandler= new CaptiveRequestHandler(); // create captive portal handler, important : after server.on since handlers are triggered on a first created/first trigerred basis
server.addHandler(captivehandler); // captive portal handler (last handler)
```
**loop()**
```
dnsServer.processNextRequest(); // captive portal
```
**3) RESULT**
**Access Point (web page is opened automatically when connecting to PsychicHttp AP)**
![captive portal access point](images/accesspoint.png)
**Station (web page is shown whatever url for Station IP, eg 192.168.1.50/abcdefg**
![captive portal station point](images/station.png)

View File

@@ -0,0 +1,144 @@
/*
PsychicHTTP Server Captive Portal Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ESPmDNS.h>
#include <PsychicHttp.h>
char* TAG = "CAPTPORT";
// captiveportal
// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino
//https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino
#include <DNSServer.h>
DNSServer dnsServer;
class CaptiveRequestHandler : public PsychicWebHandler { // handler
public:
CaptiveRequestHandler() {};
virtual ~CaptiveRequestHandler() {};
bool canHandle(PsychicRequest*request){
// ... if needed some tests ... return(false);
return true; // activate captive portal
}
esp_err_t handleRequest(PsychicRequest *request) {
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
//return response.send(); // uncomment : return captive portal page
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
}
};
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
const char* ssid = "mySSID"; // replace with your SSID (mode STATION)
const char* password = "myPassword"; // replace with you password (mode STATION)
// Set your SoftAP credentials
const char *softap_ssid = "PsychicHttp";
const char *softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
//our main server object
PsychicHttpServer server;
bool connectToWifi() {
//dual client and AP mode
WiFi.mode(WIFI_AP_STA);
// Configure SoftAP
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
WiFi.softAP(softap_ssid, softap_password);
IPAddress myIP = WiFi.softAPIP();
ESP_LOGI(TAG,"SoftAP IP Address: %s", myIP.toString().c_str());
ESP_LOGI(TAG,"[WiFi] Connecting to %s", ssid);
WiFi.begin(ssid, password);
// Will try for about 10 seconds (20x 500ms)
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
ESP_LOGE(TAG,"[WiFi] SSID not found");
break;
case WL_CONNECT_FAILED:
ESP_LOGI(TAG,"[WiFi] Failed - WiFi not connected! Reason: ");
return false;
break;
case WL_CONNECTION_LOST:
ESP_LOGI(TAG,"[WiFi] Connection was lost");
break;
case WL_SCAN_COMPLETED:
ESP_LOGI(TAG,"[WiFi] Scan is completed");
break;
case WL_DISCONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is disconnected");
break;
case WL_CONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is connected, IP address %s",WiFi.localIP().toString().c_str());
return true;
break;
default:
ESP_LOGI(TAG,"[WiFi] WiFi Status: %d",WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
ESP_LOGI(TAG,"[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else numberOfTries--;
}
return false;
} // end connectToWifi
void setup() {
Serial.begin(115200);
delay(10);
// Wifi
if (connectToWifi()) { // set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
ESP_LOGE(TAG,"Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin()) {
ESP_LOGI(TAG,"ERROR : LittleFS Mount Failed.");
return;
}
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
server.listen(80);
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
// captive portal
dnsServer.start(53, "*", WiFi.softAPIP()); // DNS requests are executed over port 53 (standard)
captivehandler= new CaptiveRequestHandler(); // create captive portal handler, important : after server.on since handlers are triggered on a first created/first trigerred basis
server.addHandler(captivehandler); // captive portal handler (last handler)
} // end set up our esp32 to listen on the local_hostname.local domain
} // end setup
void loop() {
dnsServer.processNextRequest(); // captive portal
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -0,0 +1,138 @@
**OTA update example for PsychicHttp**
Example of OTA (Over The Air) update implementation for PsychicHttp, using Arduino IDE.
**Requirements**
Requirements for project are :
- OTA update for code (firmware)
- OTA update for data (littlefs)
- manual restart of ESP32, triggered by User (no automatic restart after code or data file upload)
**Implementation**
OTA update relies on handler PsychicUploadHandler.
Screenshots and Code are shown below.
**Credits**
https://github.com/hoeken/PsychicHttp/blob/master/src/PsychicUploadHandler.cpp
https://github.com/hoeken/PsychicHttp/issues/30
**Configuration**
Example has been implemented with following configuration :\
Arduino IDE 1.8.19\
arduino-32 v2.0.15\
PsychicHttp 1.1.1\
ESP32S3
**Example Files Structure**
```
arduino_ota
data
| update.html
arduino_ota.ino
code.bin
littlefs.bin
README
```
"code.bin" and "littlefs.bin" are example update files which can be used to update respectily code (firmware) or data (littlefs).
"Real" update files can be generated on Arduino IDE 1.x :
- for code, menu "Sketch -> Export bin"
- for data, using plugin arduino-esp32fs-plugin https://github.com/lorol/arduino-esp32fs-plugin/releases
**SCREENSHOTS**
**Update code (firmware)**
![otaupdate1](images/otaupdate1.png)
![otaupdate2](images/otaupdate2.png)\
```ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x2b (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0
[332885][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[332895][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 867306
[332908][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 862016
[332919][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[332929][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename code.bin
[333082][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 856272
[333095][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[snip]
[339557][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 416
[339566][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1
[339718][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 867072 written
[339726][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error
[339738][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[339747][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
```
**Update data (littlefs)**
![otaupdate3](images/otaupdate3.png)
```
[ 48216][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[ 48226][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1573100
[ 48239][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1567810
[ 48250][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 48261][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename littlefs.bin
[ 48376][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1562066
[ 48389][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 48408][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1556322
[ 48421][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1550578
[ 48432][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[snip]
[ 54317][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 16930
[ 54327][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54340][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 11186
[ 54351][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54363][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 5442
[ 54375][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54386][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1
[ 54396][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 1572864 written
[ 54404][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error
[ 54415][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[ 54424][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
```
**Restart**
![otaupdate4](images/otaupdate4.png)
![otaupdate5](images/otaupdate5.png)
```
[110318][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[110327][I][arduino_ota.ino:205] operator()(): [OTA] <b style='color:green'>Restarting ...</b>
[110338][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[111317][W][WiFiGeneric.cpp:1062] _eventCallback(): Reason: 8 - ASSOC_LEAVE
[111319][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 51
[111332][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0xc (RTC_SW_CPU_RST),boot:0x8 (SPI_FAST_FLASH_BOOT)
Saved PC:0x420984ae
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0
[ 283][I][arduino_ota.ino:57] connectToWifi(): [OTA] [WiFi] WiFi is disconnected
[ 791][I][arduino_ota.ino:60] connectToWifi(): [OTA] [WiFi] WiFi is connected, IP address 192.168.1.50
```

View File

@@ -0,0 +1,219 @@
/*
Over The Air (OTA) update example for PsychicHttp web server
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
char *TAG = "OTA"; // ESP_LOG tag
// PsychicHttp
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ESPmDNS.h>
#include <PsychicHttp.h>
PsychicHttpServer server; // main server object
const char *local_hostname = "psychichttp"; // hostname for mdns
// OTA
#include <Update.h>
bool esprestart=false; // true if/when ESP should be restarted, after OTA update
// Wifi
const char *ssid = "SSID"; // your SSID
const char *password = "PASSWORD"; // your PASSWORD
bool connectToWifi() { // Wifi
//client in STA mode
WiFi.mode(WIFI_AP_STA);
WiFi.begin(ssid,password);
WiFi.begin(ssid, password);
// Will try for about 10 seconds (20x 500ms)
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
ESP_LOGE(TAG,"[WiFi] SSID not found");
break;
case WL_CONNECT_FAILED:
ESP_LOGI(TAG,"[WiFi] Failed - WiFi not connected! Reason: ");
return false;
break;
case WL_CONNECTION_LOST:
ESP_LOGI(TAG,"[WiFi] Connection was lost");
break;
case WL_SCAN_COMPLETED:
ESP_LOGI(TAG,"[WiFi] Scan is completed");
break;
case WL_DISCONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is disconnected");
break;
case WL_CONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is connected, IP address %s",WiFi.localIP().toString().c_str());
return true;
break;
default:
ESP_LOGI(TAG,"[WiFi] WiFi Status: %d",WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
ESP_LOGI(TAG,"[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else numberOfTries--;
}
return false;
}
// =======================================================================
// setup
// =======================================================================
void setup()
{ Serial.begin(115200);
delay(10);
// Wifi
if (connectToWifi()) { //set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
ESP_LOGE(TAG,"Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin()) {
ESP_LOGI(TAG,"ERROR : LittleFS Mount Failed.");
return;
}
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
server.listen(80);
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
//server.maxRequestBodySize=2*1024*1024; // 2Mb, change default value if needed
//server.maxUploadSize=64*1024*1024; // 64Mb, change default value if needed
//you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest *request) {
return request->reply(404, "text/html", "Custom 404 Handler");
});
// OTA
PsychicUploadHandler *updateHandler = new PsychicUploadHandler(); // create handler for OTA update
updateHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { // onUpload
/* callback to upload code (firmware) or data (littlefs)
* callback is triggered for each file chunk, from first chunk (index is 0) to last chunk (last is true)
* callback is triggered by handler in handleRequest(), after _multipartUploadHandler() *
* filename : name of file to upload, with naming convention below
* "*code.bin" for code (firmware) update, eg "v1_code.bin"
* "*littlefs.bin" for data (little fs) update, eg "v1_littlefs.bin" *
*/
int command; //command : firmware and filesystem update type, ie code (U_FLASH 0) or data (U_SPIFFS 100)
ESP_LOGI(TAG,"updateHandler->onUpload _error %d Update.hasError() %d last %d", Update.getError(), Update.hasError(), last);
// Update.abort() replaces 1st error (eg "UPDATE_ERROR_ERASE") with abort error ("UPDATE_ERROR_ABORT") so root cause is lost
if (!Update.hasError()) { // no error encountered so far during update, process current chunk
if (!index){ // index is 0, begin update (first chunk)
ESP_LOGI(TAG,"update begin, filename %s", filename.c_str());
Update.clearError(); // first chunk, clear Update error if any
// check if update file is code, data or sd card one
if (!filename.endsWith("code.bin") && !filename.endsWith("littlefs.bin")) { // incorrect file name
ESP_LOGE(TAG,"ERROR : filename %s format is incorrect", filename.c_str());
if (!Update.hasError()) Update.abort();
return(ESP_FAIL);
} // end incorrect file name
else { // file name is correct
// check update type : code or data
if (filename.endsWith("code.bin")) command=U_FLASH; // update code
else command=U_SPIFFS; // update data
if (!Update.begin(UPDATE_SIZE_UNKNOWN, command)) { // start update with max available size
// error, begin is KO
if (!Update.hasError()) Update.abort(); // abort
ESP_LOGE(TAG,"ERROR : update.begin error Update.errorString() %s",Update.errorString());
return(ESP_FAIL);
}
} // end file name is correct
} // end begin update
if ((len) && (!Update.hasError())) { // ongoing update if no error encountered
if (Update.write(data, len) != len) {
// error, write is KO
if (!Update.hasError()) Update.abort();
ESP_LOGE(TAG,"ERROR : update.write len %d Update.errorString() %s",len, Update.errorString()) ;
return(ESP_FAIL);
}
} // end ongoing update
if ((last) && (!Update.hasError())) { // last update if no error encountered
if (Update.end(true)) { // update end is OKTEST
ESP_LOGI(TAG, "Update Success: %u written", index+len);
}
else { // update end is KO
if (!Update.hasError()) Update.abort(); // abort
ESP_LOGE(TAG,"ERROR : update end error Update.errorString() %s", Update.errorString());
return(ESP_FAIL);
}
} // last update if no error encountered
return(ESP_OK);
} // end no error encountered so far during update, process current chunk
else { // error encountered so far during update
return(ESP_FAIL);
}
}); // end onUpload
updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important)
String result; // request result
// code below is executed when update is finished
if (!Update.hasError()) { // update is OK
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
result = "<b style='color:green'>Update done for file.</b>";
return request->reply(200,"text/html",result.c_str());
// ESP.restart(); // restart ESP if needed
} // end update is OK
else { // update is KO, send request with pretty print error
result = " Update.errorString() " + String(Update.errorString());
ESP_LOGE(TAG,"ERROR : error %s",result.c_str());
return request->reply(500, "text/html", result.c_str());
} // end update is KO
});
server.on("/update", HTTP_GET, [](PsychicRequest*request){
PsychicFileResponse response(request, LittleFS, "/update.html");
return response.send();
});
server.on("/update", HTTP_POST, updateHandler);
server.on("/restart", HTTP_POST, [](PsychicRequest *request) {
String output = "<b style='color:green'>Restarting ...</b>";
ESP_LOGI(TAG,"%s",output.c_str());
esprestart=true;
return request->reply(output.c_str());
});
} // end onRequest
} // end setup
// =======================================================================
// loop
// =======================================================================
void loop() {
delay(2000);
if (esprestart) ESP.restart(); // restart ESP
} // end loop

Binary file not shown.

View File

@@ -0,0 +1,166 @@
<html>
<head>
<title>PSYCHICHTTP</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
input[type=file]::file-selector-button {
margin-right: 20px;
border: none;
background: #3498db; /* blue */
padding: 10px 20px;
border-radius: 30px;
color: #fff;
cursor: pointer;
transition: background .2s ease-in-out;
}
input[type=file]::file-selector-button:hover {
background: green ;
}
.drop-container {
position: relative;
display: flex;
gap: 10px;
flex-direction: column;
justify-content: center;
align-items: center;
height: 150px;
padding: 20px;
border-radius: 10px;
border: 2px dashed #555;
color: #444;
cursor: pointer;
transition: background .2s ease-in-out, border .2s ease-in-out;
}
.drop-container:hover {
background: #eee;
border-color: #111;
}
.drop-container:hover .drop-title {
color: #222;
}
.drop-title {
color: #444;
font-size: 20px;
font-weight: bold;
text-align: center;
transition: color .2s ease-in-out;
}
.drop-container.drag-active {
background: #eee;
border-color: #111;
}
.drop-container.drag-active .drop-title {
color: #222;
}
.restart-button {
margin-right: 20px;
border: none;
background: #3498db; /* blue */
padding: 10px 20px;
border-radius: 30px;
color: #fff;
cursor: pointer;
transition: background .2s ease-in-out;
}
.restart-button:hover {
background: green ;
}
</style>
</head>
<body>
<div class="row">
<h1>PsychicHttp OTA Update</h1>
</div>
<div><p style="text-align: left">This page allows to test OTA update with PsychicHttp, and file naming convention below :
<ul>
<li><b>"*code.bin"</b> for code (firmware) update, eg "v1_code.bin"</li>
<li><b>"*littlefs.bin"</b> for data (littlefs) update, eg "v1_littlefs.bin" </li>
</ul>
</div>
<div>
<form id="upload_form" enctype="multipart/form-data" method="post">
<label for="images" class="drop-container" id="dropcontainer">
<input type="file" class="inputfile" name="updatefile" id="updatefile" value="updatefile" accept=".bin" onchange="uploadFile()" required>
<span class="drop-title">Or drop file here</span>
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
<font color='black' id="status"></font>
<p id="loaded_n_total"></p>
</label>
</form>
</div>
<div>
<p style="text-align: left">Update must be done for each of the files provided (code, littlefs). Once updates are made, the ESP32 can be restarted.
<button class="restart-button" onclick="esprestartButton()">Restart</button>
</p>
</div>
</body>
<script>
function uploadFile() {
var urltocall = "/update";
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", progressHandler, false);
xhr.addEventListener("error", errorHandler, false);
var fileupdate = document.getElementById('updatefile'); // get file structure in DOM
var file = fileupdate.files[0]; // get file element
//alert(file.name+" | "+file.size+" | "+file.type);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("status").innerHTML = xhr.responseText; // success, show response on page
}
else {
document.getElementById("status").innerHTML = xhr.responseText; // error, show response on page
}
};
var formdata = new FormData();
formdata.append("file", file);
xhr.open("POST", urltocall); // trigger upload request with file parameter
xhr.send(formdata);
}
function progressHandler(event) {
// document.getElementById("loaded_n_total").innerHTML = "Chargé " + event.loaded + " octets"; // do not display bytes loaded
var percent = (event.loaded / event.total) * 100;
document.getElementById("progressBar").value = Math.round(percent);
document.getElementById("status").innerHTML = Math.round(percent) + "% uploaded...";
if (percent >= 100) {
document.getElementById("status").innerHTML = "Writing file ...";
}
}
function errorHandler(event) {
document.getElementById("status").innerHTML = "<b style='color:red'>Error event catched by errorHandler !</b>";
}
function esprestartButton() {
/* restart ESP */
var urltocall = "/restart";
xhr=new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4) document.getElementById("status").innerHTML = xhr.responseText;
};
xhr.open("POST", urltocall, false); // trigger restart request with file parameter
xhr.send();
}
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1 @@
Custom text file.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx
MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3
emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY
W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx
bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN
ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl
hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
v/t+MeGJP/0Zw8v/X2CFll96
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP SoftAP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<main>
<h1>SoftAP Demo</h1>
<p>You are connected to the ESP in SoftAP mode.</p>
</main>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,236 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<main>
<h1>Basic Request Examples</h1>
<ul>
<li><a href="/api?foo=bar">API Call</a></li>
<li><a href="/redirect">Redirection</a></li>
<li><a href="/auth-digest">Authentication (Digest)</a></li>
<li><a href="/auth-basic">Authentication (Basic)</a></li>
<li><a href="/cookies">Cookies Demo</a></li>
<li><a href="/404">404</a></li>
</ul>
<h1>Static Serving</h1>
<p>
<a href="/alien.png"><img width="60" src="/alien.png"></a>
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
</p>
<p><a href="/myfile.txt">Text File</a></p>
<h1>Simple POST Form</h1>
<form action="/post" method="post">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<input type="submit" value="Submit">
</form>
<h1>Basic File Upload</h1>
<table border="0">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
</tr>
<tr>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
<script>
function setpath() {
var default_path = document.getElementById("newfile").files[0].name;
document.getElementById("filepath").value = default_path;
}
function upload() {
var filePath = document.getElementById("filepath").value;
var upload_path = "/upload/" + filePath;
var fileInput = document.getElementById("newfile").files;
/* Max size of an individual file. Make sure this
* value is same as that set in file_server.c */
var MAX_FILE_SIZE = 2048*1024;
var MAX_FILE_SIZE_STR = "2MB";
if (fileInput.length == 0) {
alert("No file selected!");
} else if (filePath.length == 0) {
alert("File path on server is not set!");
} else if (filePath.indexOf(' ') >= 0) {
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/') {
alert("File name not specified after path!");
} else if (fileInput[0].size > 200*1024) {
alert("File size must be less than 200KB!");
} else {
document.getElementById("newfile").disabled = true;
document.getElementById("filepath").disabled = true;
document.getElementById("upload").disabled = true;
var file = fileInput[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.open();
document.write(xhttp.responseText);
document.close();
} else if (xhttp.status == 0) {
alert("Server closed the connection abruptly!");
location.reload()
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
}
}
};
xhttp.open("POST", upload_path, true);
xhttp.send(file);
}
}
</script>
<h1>Multipart POST Form</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<label for="file-upload">File Upload:</label>
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif" required>
<br>
<input type="submit" value="Upload File">
</form>
<h1>Websocket Demo</h1>
<input type="text" id="message_input" placeholder="Type your message">
<button onclick="sendMessage()">Send Message</button>
<button onclick="websocketConnect()">Connect</button>
<div>
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
let socket;
const outputText = document.getElementById('websocket_output');
const messageInput = document.getElementById('message_input');
function websocketConnect()
{
// Create a WebSocket connection
socket = new WebSocket('ws://' + window.location.host + '/ws');
// Event handler for when the WebSocket connection is open
socket.addEventListener('open', (event) => {
outputText.value += `[socket] connection opened!\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when a message is received from the WebSocket server
socket.addEventListener('message', (event) => {
// Echo the received message into the output div
let data = event.data.trim();
outputText.value += `[received] ${data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when an error occurs with the WebSocket connection
socket.addEventListener('error', (event) => {
let data = event.data.trim();
outputText.value += `[error] ${event.data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when the WebSocket connection is closed
socket.addEventListener('close', (event) => {
outputText.value += `[socket] connection opened!\n`;
});
}
// Function to send a message to the WebSocket server
function sendMessage() {
if (socket.readyState == WebSocket.OPEN) {
const message = messageInput.value.trim();
if (message) {
socket.send(message);
messageInput.value = ''; // Clear the input field after sending the message
outputText.value += `[sent] ${message}\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
else
{
outputText.value += `[error] Not Connected\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
</script>
<h1>EventSource Demo</h1>
<button onclick="eventSourceConnect()">Connect</button>
<div>
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
const dataElement = document.getElementById('eventsource_output');
function eventSourceConnect()
{
const eventSource = new EventSource('/events');
eventSource.onopen = () => {
dataElement.value += `[connected]\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.addEventListener('millis', (event) => {
let data = event.data.trim()
dataElement.value += `[millis] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
});
eventSource.onmessage = (event) => {
let data = event.data.trim()
dataElement.value += `[message] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.onerror = (error) => {
dataElement.value += `[error] ${error}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
eventSource.close();
};
}
</script>
</main>
</body>
</html>

View File

@@ -0,0 +1 @@
Test File.

View File

@@ -0,0 +1,2 @@
#define WIFI_SSID "Your_SSID"
#define WIFI_PASS "Your_PASS"