Switch HTTP Server
6
lib/PsychicHttp/examples/arduino/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.pio
|
||||
.vscode/
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
497
lib/PsychicHttp/examples/arduino/arduino.ino
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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)**
|
||||

|
||||
|
||||
**Station (web page is shown whatever url for Station IP, eg 192.168.1.50/abcdefg**
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
138
lib/PsychicHttp/examples/arduino/arduino_ota/README.md
Normal 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)**
|
||||

|
||||
|
||||
\
|
||||
```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)**
|
||||
|
||||

|
||||
```
|
||||
[ 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**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
```
|
||||
[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
|
||||
|
||||
```
|
||||
|
||||
|
||||
219
lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino
Normal 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
|
||||
BIN
lib/PsychicHttp/examples/arduino/arduino_ota/code.bin
Normal file
166
lib/PsychicHttp/examples/arduino/arduino_ota/data/update.html
Normal 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>
|
||||
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 59 KiB |
BIN
lib/PsychicHttp/examples/arduino/arduino_ota/littlefs.bin
Normal file
1
lib/PsychicHttp/examples/arduino/data/custom.txt
Normal file
@@ -0,0 +1 @@
|
||||
Custom text file.
|
||||
BIN
lib/PsychicHttp/examples/arduino/data/img/request_flow.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
19
lib/PsychicHttp/examples/arduino/data/server.crt
Normal 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-----
|
||||
28
lib/PsychicHttp/examples/arduino/data/server.key
Normal 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-----
|
||||
15
lib/PsychicHttp/examples/arduino/data/www-ap/index.html
Normal 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>
|
||||
BIN
lib/PsychicHttp/examples/arduino/data/www/alien.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
lib/PsychicHttp/examples/arduino/data/www/favicon.ico
Normal file
|
After Width: | Height: | Size: 66 KiB |
236
lib/PsychicHttp/examples/arduino/data/www/index.html
Normal 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>
|
||||
1
lib/PsychicHttp/examples/arduino/data/www/text.txt
Normal file
@@ -0,0 +1 @@
|
||||
Test File.
|
||||
2
lib/PsychicHttp/examples/arduino/secret.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#define WIFI_SSID "Your_SSID"
|
||||
#define WIFI_PASS "Your_PASS"
|
||||