1451 lines
46 KiB
C++
1451 lines
46 KiB
C++
|
|
/*
|
|
wifi is started in STATION mode and tries to connected to AP saved
|
|
in the creds.json file. It will keep trying to connect forever. If
|
|
it connects then the board LEDS will toggle, the buzzer will play a tune
|
|
and the IP address will be sent to the serial port. Also the device will
|
|
be discoverable as "http://atadev.local/"
|
|
|
|
If credentials need to be changed then double reset can be done to
|
|
trigger the AP mode and a wifi config page will be available on address
|
|
192.168.4.1. Credentials can be entered and applied then you can reset
|
|
the device so it connects with the correct credentials.
|
|
|
|
*/
|
|
#include "my_wifi.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
#include <FS.h>
|
|
#include <LittleFS.h>
|
|
#include <SD.h>
|
|
|
|
#include <Wifi.h>
|
|
#include <ESPmDNS.h>
|
|
#include <DNSServer.h>
|
|
#include <esp_task_wdt.h>
|
|
|
|
#include <Update.h>
|
|
|
|
#include "global.h"
|
|
#include <esp_wifi.h>
|
|
#include "common/my_buzzer.h"
|
|
#include "my_board.h"
|
|
#include "common/led_animation.h"
|
|
#include "led_strip.h"
|
|
#include "common/fileSystem.h"
|
|
#include "JsonConstrain.h"
|
|
#include "led_strip.h"
|
|
#include "my_oled.h"
|
|
#include "BTSerial.h"
|
|
#include "esp_log.h"
|
|
|
|
#include "firmware_html.h"
|
|
|
|
int RebootSystem = 0;
|
|
#define WIFI_TIMEOUT_MS 10000
|
|
|
|
static const char* tag = "wifi";
|
|
|
|
bool WifiClientConnected = false;
|
|
AsyncWebServer webServer(80);
|
|
DNSServer *dnsServer;
|
|
#define DNS_PORT 53
|
|
|
|
#define StartDelayedRebooth RebootSystem = 1000/BUTTON_UPDATE_PERIOD; // start reboot countdown for 1sec
|
|
|
|
String ssid;
|
|
String passPhrase;
|
|
|
|
String AP_SSID;
|
|
String AP_Pass;
|
|
String mDnsName;
|
|
String HostName;
|
|
|
|
IPAddress local_IP(192,168,10,1);
|
|
IPAddress gateway(192,168,10,200);
|
|
IPAddress subnet(255,255,255,0);
|
|
|
|
// for file manager page
|
|
String filesDropdownOptions((char*)0);
|
|
String dirDropdownOptions((char*)0);
|
|
String savePath((char*)0); // needed for storing file when editing a file
|
|
String savePathInput((char*)0);
|
|
const char* http_username = "admin";
|
|
const char* http_password = "admin";
|
|
const char* param_delete_path = "delete-path";
|
|
const char* param_edit_path = "edit-path";
|
|
const char* param_dir_pad = "dir-path";
|
|
const char* param_edit_textarea = "edit-textarea";
|
|
const char* param_save_path = "save-path";
|
|
String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg";
|
|
//const char* jquery = "/jquery-3.6.3.min.js";
|
|
|
|
void Init_Wifi_Task(void)
|
|
{
|
|
File file = LittleFS.open("/cfg/wifi.json", "r");
|
|
if(!file){
|
|
ESP_LOGE(tag, "Failed to open wifi.json!");
|
|
file.close();
|
|
return;
|
|
}
|
|
|
|
// Parse the JSON file
|
|
StaticJsonDocument<1024> doc;
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
if(!error){
|
|
JsonObject wifiJson = doc["wifi"];
|
|
file.close();
|
|
mDnsName = jsonConstrainString(tag, wifiJson,"mdns-name", "atadev");
|
|
ESP_LOGV(tag, "mDnsName: %s", mDnsName.c_str());
|
|
|
|
if(jsonConstrainBool(tag, wifiJson, "en", false)){
|
|
JsonObject j = doc["cred1"];
|
|
Wifi_Read_Credentials( j );
|
|
xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*10, NULL, 1, NULL, CONFIG_ARDUINO_RUNNING_CORE);
|
|
ESP_LOGV(tag, "Initialized WiFi (task created)...");
|
|
}
|
|
|
|
JsonObject ap = doc["ap"];
|
|
AP_SSID = jsonConstrainString(tag, ap, "ssid", "ATA_AP");
|
|
if(jsonConstrainBool(tag, ap, "append-id", true)){
|
|
String macHex = String(chipInfo.macByte[1], HEX) + String(chipInfo.macByte[0], HEX);
|
|
AP_SSID += "_";
|
|
macHex.toUpperCase();
|
|
AP_SSID += macHex;
|
|
}
|
|
AP_Pass = jsonConstrainString(tag, ap, "pass", "12345678");
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for wifi.json");
|
|
}
|
|
}
|
|
|
|
void onWiFiEvent(WiFiEvent_t event)
|
|
{
|
|
//Serial.printf("[WiFi-event] event: %d\n", event);
|
|
switch (event) {
|
|
case ARDUINO_EVENT_WIFI_READY:
|
|
//Serial.println("WiFi interface ready");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
|
//Serial.println("Completed scan for access points");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_START:
|
|
//Serial.println("WiFi client started");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_STOP:
|
|
//Serial.println("WiFi clients stopped");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
|
WifiClientConnected = true;
|
|
//Serial.println("Connected to access point");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
|
WifiClientConnected = false;
|
|
ESP_LOGV(tag,"WiFi Disconnected");
|
|
//Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
|
|
//Serial.println("Authentication mode of access point has changed");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
|
ESP_LOGV(tag,"My IP: %s", WiFi.localIP().toString());
|
|
Wifi_Start_MDNS();
|
|
Buzzer_Play_Tune(TUNE_WIFI_CONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
|
|
//Serial.println("Lost IP address and IP address is reset to 0");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_SUCCESS:
|
|
//Serial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_FAILED:
|
|
//Serial.println("WiFi Protected Setup (WPS): failed in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_TIMEOUT:
|
|
// Serial.println("WiFi Protected Setup (WPS): timeout in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_PIN:
|
|
//Serial.println("WiFi Protected Setup (WPS): pin code in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_START:
|
|
//Serial.println("WiFi access point started");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STOP:
|
|
//Serial.println("WiFi access point stopped");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
|
//Serial.println("Client connected");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
|
ESP_LOGV(tag,"SoftAP Client Disconnected");
|
|
//Buzzer_Play_Tune(TUNE_WIFI_DISCONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
|
ESP_LOGV(tag,"SoftAP Client Connected");
|
|
Buzzer_Play_Tune(TUNE_WIFI_CONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
|
|
//Serial.println("Received probe request");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
|
|
//Serial.println("AP IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
|
//Serial.println("STA IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP6:
|
|
//Serial.println("Ethernet IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_START:
|
|
//Serial.println("Ethernet started");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_STOP:
|
|
//Serial.println("Ethernet stopped");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
//Serial.println("Ethernet connected");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
|
//Serial.println("Ethernet disconnected");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
|
// Serial.println("Obtained IP address");
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void Wifi_Task(void *parameters)
|
|
{
|
|
// Extend watchdog timer
|
|
esp_task_wdt_init(2, false);
|
|
|
|
Setup_WebServer_Handlers(&webServer);
|
|
WiFi.onEvent(onWiFiEvent);
|
|
WiFi.setHostname(mDnsName.c_str());
|
|
WiFi.softAPConfig(local_IP, gateway, subnet);
|
|
WiFi.softAP(AP_SSID.c_str(), AP_Pass.c_str());
|
|
Wifi_Start_MDNS();
|
|
vTaskDelay(100);
|
|
webServer.begin();
|
|
|
|
// Choose WIFI Mode
|
|
if(commMode == COMM_WIFI_AP_BLE){ // AP Only
|
|
WiFi.mode(WIFI_AP);
|
|
while(1){ vTaskDelay(500 / portTICK_PERIOD_MS); }
|
|
}else{ // AP & Client (dslrbooth)
|
|
esp_wifi_set_ps(WIFI_PS_NONE);
|
|
WiFi.mode(WIFI_AP_STA);
|
|
Wifi_Client_Loop();
|
|
}
|
|
}
|
|
|
|
void Wifi_Client_Loop(){
|
|
while(true){
|
|
// Wifi status check loop
|
|
if(WiFi.status() == WL_CONNECTED){
|
|
vTaskDelay(250);
|
|
continue;
|
|
}
|
|
|
|
// Start WiFi Client
|
|
ESP_LOGD(tag, "Wifi trying stored creds: %s..%s",ssid.c_str(), passPhrase.c_str());
|
|
WiFi.begin(ssid.c_str(), passPhrase.c_str());
|
|
|
|
//Wait until connected or timeout
|
|
ESP_LOGV(tag, "WiFi Connecting...");
|
|
unsigned long startAttemptTime = millis();
|
|
while(WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS){
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
// Delay if wifi connection fails and continue
|
|
if(WiFi.status() != WL_CONNECTED){
|
|
ESP_LOGW(tag, "WIFI Connection Failed!");
|
|
for(int i = 0; i < WIFI_TIMEOUT_MS/100; i++){
|
|
vTaskDelay(100);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Wifi_Start_MDNS(void)
|
|
{
|
|
ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str());
|
|
if (!MDNS.begin(mDnsName.c_str())) {
|
|
ESP_LOGE(tag, "Error setting up MDNS responder!");
|
|
}else{
|
|
ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName);
|
|
}
|
|
}
|
|
|
|
void Wifi_Read_Credentials(const JsonObject &credJson){
|
|
if(credJson.isNull()){ // set generic defaults on error
|
|
ESP_LOGE(tag, "Failed to find wifi key");
|
|
|
|
ssid = "Generic";
|
|
passPhrase = "password";
|
|
//strncpy(ssid, "Generic", sizeof(ssid)-1);
|
|
//strncpy(passPhrase, "password", sizeof(passPhrase)-1);
|
|
return;
|
|
}
|
|
else{
|
|
// TODO Restore String
|
|
ssid = jsonConstrainString(tag, credJson, "ssid", "default");
|
|
passPhrase = jsonConstrainString(tag, credJson, "pass", "password");
|
|
//strncpy(ssid, jsonConstrainString(tag, credJson, "ssid", "default").c_str(), sizeof(ssid)-1);
|
|
//strncpy(passPhrase, jsonConstrainString(tag, credJson, "pass", "password").c_str(), sizeof(passPhrase)-1);
|
|
}
|
|
}
|
|
|
|
void Wifi_Save_Credentials(String newSSID, String newPass)
|
|
{
|
|
// Open the credentials file writing
|
|
File credsFile = LittleFS.open("/cfg/wifi.json", "r+");
|
|
if(!credsFile){
|
|
ESP_LOGE(tag, "Failed to open wifi.json\n");
|
|
return;
|
|
}
|
|
|
|
// Parse the JSON file
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, credsFile);
|
|
if(!error){
|
|
JsonObject cred1Json = doc.createNestedObject("cred1");
|
|
|
|
if(cred1Json){
|
|
cred1Json["ssid"] = newSSID;
|
|
cred1Json["pass"] = newPass;
|
|
|
|
// Clear file contents before saving the modified JSON
|
|
credsFile.seek(0);
|
|
|
|
int written = serializeJson(doc, credsFile); // save to file
|
|
ESP_LOGD(tag, "Credentials saved... ssid: %s, pass: %s, size written: %d", newSSID, newPass, written);
|
|
}
|
|
credsFile.close();
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for wifi.json");
|
|
}
|
|
}
|
|
|
|
void Setup_WebServer_Handlers(AsyncWebServer* serv)
|
|
{
|
|
serv->on("/dslrbooth", HTTP_GET, handleGET_DSLRBooth);
|
|
|
|
serv->on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(WiFi.getMode() == WIFI_AP && !WifiClientConnected){
|
|
request->redirect("/wifi");
|
|
}else{
|
|
request->redirect("/home");
|
|
}
|
|
});
|
|
serv->on("/home", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
sendHtmlFile("/www/home.html", request, HomeHtmlProcessor);
|
|
});
|
|
serv->on("/lights", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
sendHtmlFile("/www/lights.html", request, htmlProcessor);
|
|
});
|
|
serv->on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
sendHtmlFile("/www/setup.html", request, htmlProcessor);
|
|
});
|
|
serv->on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(WiFi.getMode() == WIFI_MODE_APSTA){
|
|
// TODO Disable navigation bar
|
|
}
|
|
sendHtmlFile("/www/wifi.html", request, htmlProcessor);
|
|
});
|
|
serv->on("/files", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
sendHtmlFile("/www/files.html", request, htmlProcessor);
|
|
});
|
|
serv->on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handlePOST_Upload);
|
|
|
|
serv->on("/download", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if (request->hasParam("file")) {
|
|
String filename = request->getParam("file")->value();
|
|
if (LittleFS.exists(filename)) {
|
|
fs::File file = LittleFS.open(filename, "r");
|
|
if (file) {
|
|
request->send(LittleFS, filename, "application/octet-stream");
|
|
file.close();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
request->send(404, "text/plain", "File not found!");
|
|
});
|
|
serv->on("/delete", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
|
|
String filename = request->getParam(param_delete_path)->value();
|
|
if(filename !="choose"){
|
|
LittleFS.remove(filename.c_str());
|
|
ESP_LOGD(tag, "Deleted file: %s", filename.c_str());
|
|
}
|
|
|
|
request->redirect("/files");
|
|
});
|
|
serv->on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
|
|
String fileName = request->getParam(param_edit_path)->value();
|
|
if(fileName =="new"){
|
|
savePath = "/new.txt";
|
|
}
|
|
else{
|
|
savePath = fileName;
|
|
}
|
|
|
|
ESP_LOGD(tag, "Edit file: %s", savePath.c_str());
|
|
sendHtmlFile("/www/edit.html", request, htmlProcessor);
|
|
});
|
|
serv->on("/save", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
String inputMessage((char*)0);
|
|
if (request->hasParam(param_edit_textarea)) {
|
|
inputMessage = request->getParam(param_edit_textarea)->value();
|
|
}
|
|
if (request->hasParam(param_save_path)) {
|
|
savePath = request->getParam(param_save_path)->value();
|
|
}
|
|
writeFile(LittleFS, savePath.c_str(), inputMessage.c_str());
|
|
|
|
request->redirect("/files");
|
|
});
|
|
serv->on("/update", HTTP_POST, handlePOST_Update, updateCallback);
|
|
|
|
// Request Data
|
|
serv->on("/get", HTTP_GET, handleGET_Get);
|
|
// Post Data
|
|
serv->on("/post", HTTP_POST, handlePOST_Post, postFileUpload, postBody);
|
|
|
|
serv->on("/generate_204", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
ESP_LOGD(tag, "/generate_204 ... redirect to /wifi");
|
|
request->redirect("/wifi");
|
|
});
|
|
|
|
serv->on("/hotspot-detect.html", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
ESP_LOGD(tag, "/hotspot-detect.html ... redirect to /wifi");
|
|
request->redirect("/wifi");
|
|
});
|
|
|
|
serv->on("/msftconnecttest.txt", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
ESP_LOGD(tag, "/msftconnecttest.txt ... redirect to /wifi");
|
|
request->redirect("/wifi");
|
|
});
|
|
|
|
serv->on("/reg-stick", HTTP_POST, handlePOST_RegStick);
|
|
|
|
serv->on("/firmware", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
request->send_P(200, "text/html", firmware_html_page);
|
|
});
|
|
|
|
serv->onNotFound([](AsyncWebServerRequest *request){
|
|
if(WiFi.getMode() == WIFI_MODE_APSTA){
|
|
ESP_LOGD(tag, "OnNotFound Redirect to /wifi");
|
|
request->redirect("/wifi");
|
|
}else if(WiFi.getMode() == WIFI_MODE_AP){
|
|
ESP_LOGD(tag, "OnNotFound Redirect to /home");
|
|
request->redirect("/home");
|
|
}else{
|
|
request->send(404);
|
|
}
|
|
});
|
|
|
|
serv->on("/*", HTTP_GET, handleGET_SendFile); // send any file
|
|
|
|
}
|
|
|
|
void handlePOST_RegStick(AsyncWebServerRequest *request){
|
|
|
|
}
|
|
|
|
bool getpostSuccess = false;
|
|
//
|
|
void handleGET_Get(AsyncWebServerRequest *request){
|
|
if(request->hasParam("type", false, false)) {
|
|
const char* dataType = request->getParam("type", false, false)->value().c_str();
|
|
|
|
if(!strcmp(dataType, "app-events")){ // send json file
|
|
request->send(LittleFS, "/cfg/app-events.json", "application/json");
|
|
return;
|
|
}
|
|
|
|
if(!strcmp(dataType, "anim-profiles")){ // send json file
|
|
request->send(LittleFS, "/cfg/anim-profiles.json", "application/json");
|
|
return;
|
|
}
|
|
|
|
if(!strcmp(dataType, "anim-list")){
|
|
request->send(LittleFS, "/cfg/anim-list.json", "application/json");
|
|
return;
|
|
}
|
|
|
|
if(!strcmp(dataType, "sys-summary")){
|
|
JsonDocument doc;
|
|
CreateSysSummmaryPacket(doc);
|
|
String summary;
|
|
serializeJson(doc, summary);
|
|
request->send(200, "application/json", summary);
|
|
return;
|
|
}
|
|
|
|
if(!strcmp(dataType, "wifi")){
|
|
JsonDocument jsdoc;
|
|
jsdoc["ssid"] = ssid;
|
|
//jsdoc["pass"] = (int)strlen(passPhrase);
|
|
jsdoc["pass"] = passPhrase;
|
|
|
|
String jsStr;
|
|
serializeJson(jsdoc, jsStr);
|
|
|
|
request->send(200, "application/json", jsStr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
request->send(400, "text/plain", "Failed");
|
|
}
|
|
|
|
void CreateSysSummmaryPacket(JsonDocument &doc){
|
|
JsonObject booth = doc.createNestedObject("booth");
|
|
booth["mode"] = "";
|
|
booth["app"] = sysProps.appName;
|
|
booth["strip1"] = (strip1) ? true : false,
|
|
booth["strip2"] = (strip2) ? true : false,
|
|
booth["front-light"] = true;
|
|
booth["rear-light"] = false;
|
|
booth["oled"] = (oled) ? true : false;
|
|
|
|
//ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getHeapSize()-ESP.getFreeHeap());
|
|
|
|
JsonObject Temperature = doc.createNestedObject("temperature");
|
|
Temperature["temp"] = sysProps.t_sensor.temperature;
|
|
Temperature["sp1"] = sysProps.t_sensor.Setpoint1;
|
|
doc["system"]["firm-ver"] = FIRMWARE_VER;
|
|
|
|
JsonObject chip = doc.createNestedObject("chip");
|
|
chip["flash-size"] = "";
|
|
chip["flash-free"] = "";
|
|
chip["ram-size"] = ESP.getHeapSize();
|
|
chip["ram-free"] = ESP.getFreeHeap();
|
|
|
|
|
|
|
|
// Get the Wi-Fi RSSI
|
|
wifi_ap_record_t wifi_ap_info;
|
|
esp_err_t rssi_result = esp_wifi_sta_get_ap_info(&wifi_ap_info);
|
|
int rssi = 0; int wifi_ch = 0;
|
|
String encryp = "";
|
|
if (rssi_result == ESP_OK) {
|
|
rssi = wifi_ap_info.rssi;
|
|
wifi_ch = wifi_ap_info.primary;
|
|
|
|
wifi_auth_mode_t auth_mode = wifi_ap_info.authmode;
|
|
switch (auth_mode) {
|
|
case WIFI_AUTH_OPEN:
|
|
encryp = "Open";
|
|
break;
|
|
case WIFI_AUTH_WEP:
|
|
encryp = "WEP";
|
|
break;
|
|
case WIFI_AUTH_WPA_PSK:
|
|
encryp = "WPA_PSK";
|
|
break;
|
|
case WIFI_AUTH_WPA2_PSK:
|
|
encryp = "WPA2_PSK";
|
|
break;
|
|
case WIFI_AUTH_WPA_WPA2_PSK:
|
|
encryp = "WPA_WPA2_PSK";
|
|
break;
|
|
case WIFI_AUTH_WPA2_ENTERPRISE:
|
|
encryp = "WPA2_ENT";
|
|
break;
|
|
default:
|
|
encryp = "UNKNOWN";
|
|
break;
|
|
}
|
|
}
|
|
|
|
JsonObject wifi = doc.createNestedObject("wifi");
|
|
wifi["client-ip"] = "XXX.XXX.XXX.XXX";
|
|
wifi["mac-addr"] = chipInfo.macStr;
|
|
wifi["ch"] = wifi_ch;
|
|
wifi["rssi"] = rssi;
|
|
wifi["encryp"] = encryp;
|
|
|
|
JsonObject ble = doc.createNestedObject("ble");
|
|
ble["en"] = (sysProps.appIndex == DSLRBOOTH_INDEX)? false : true;
|
|
ble["clients"] = (BTDeviceConnected)? 1 : 0;
|
|
ble["ssid"] = BLEDeviceName;
|
|
|
|
}
|
|
|
|
void handlePOST_Post(AsyncWebServerRequest *request){
|
|
ESP_LOGD(tag, "posts request..");
|
|
if(!getpostSuccess) { request->send(400, "text/plain", "Error"); }
|
|
|
|
request->send(200, "text/plain", "Ok");
|
|
}
|
|
|
|
void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
|
|
getpostSuccess = false;
|
|
Serial.println("post file");
|
|
}
|
|
|
|
|
|
// POST Requests
|
|
int packets, postTotal;
|
|
char postType[24];
|
|
char *postData;
|
|
void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
|
|
if (!index){
|
|
packets = 0;
|
|
int sectors = (total + 1023 + 64) / 1024;
|
|
postData = new char[1024 * sectors];
|
|
}
|
|
|
|
// Accumulate Data Chunks
|
|
memcpy(postData + index, data, len);
|
|
packets++;
|
|
|
|
if((index + len) >= total){
|
|
ESP_LOGD(tag, "index: %d, len: %d, total: %d", index, len, total);
|
|
ESP_LOGD(tag, "packets: %d", packets);
|
|
getpostSuccess = false;
|
|
|
|
// Check if the request contains the necessary parameters
|
|
if (request->hasParam("type", false, false)) {
|
|
const char* dataType = request->getParam("type", false, false)->value().c_str();
|
|
ESP_LOGD(tag, "data type: %s", dataType);
|
|
|
|
if(!strcmp(dataType, "anim-profile-common") || !strcmp(dataType, "set-countdown")){
|
|
JsonDocument profJson(1 * 1024);
|
|
DeserializationError error = deserializeJson(profJson, postData, total);
|
|
|
|
if(!error){
|
|
animProps.profileIndex = profJson["profile-index"].as<int>();
|
|
|
|
JsonObject countdownJson = profJson["countdown"];
|
|
animProps.frontLight.minDuty = countdownJson["min"].as<float>();
|
|
animProps.frontLight.maxDuty = countdownJson["max"].as<float>();
|
|
animProps.frontLight.holdTime = countdownJson["hold"].as<int>();
|
|
animProps.frontLight.rampTime = countdownJson["ramp"].as<int>();
|
|
|
|
int indexChanged = animProps.profileIndex - countdownJson["profile-index"].as<int>();
|
|
animProps.profileIndex = countdownJson["profile-index"].as<int>();
|
|
if(indexChanged){
|
|
load_animation_profileByIndex(animProps.profileIndex);
|
|
}
|
|
|
|
// Save to disk/flash
|
|
if(dataType[0] == 'a'){
|
|
if(updateJsonDocument(profJson, "/cfg/anim-profile-common.json")){
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
}
|
|
else{
|
|
ESP_LOGD(tag, "Post:set-countdown, index: %d, min: %.1f, max: %.1f, hold: %d, ramp: %d", animProps.profileIndex, animProps.frontLight.minDuty, animProps.frontLight.maxDuty, animProps.frontLight.holdTime, animProps.frontLight.rampTime);
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for wifi.json");
|
|
}
|
|
|
|
getpostSuccess = true;
|
|
//goto done;
|
|
}
|
|
else if(!strncmp(dataType, "anim-profile", 12)){
|
|
if (strlen(dataType) > 12) {
|
|
int profile_index = (dataType[12] - '0' - 1) % 8; // extract index, assuming dataType has enough characters
|
|
//DynamicJsonDocument profJson(4 * 1024);
|
|
JsonDocument profJson;
|
|
DeserializationError error = deserializeJson(profJson, postData, total);
|
|
|
|
if(!error){
|
|
JsonArray eventsArray = profJson["events"];
|
|
for(int i = 0; i < eventsArray.size(); i++ ){
|
|
animProps.event[i].selfIndex = i;
|
|
animProps.event[i].animIndex = eventsArray[i]["anim"].as<int>();
|
|
animProps.event[i].hue = eventsArray[i]["hue"].as<int>();
|
|
animProps.event[i].hueRange = eventsArray[i]["hue-range"].as<int>();
|
|
animProps.event[i].speed = eventsArray[i]["speed"].as<int>();
|
|
animProps.event[i].param1 = eventsArray[i]["param1"].as<int>();
|
|
animProps.event[i].param2 = eventsArray[i]["param2"].as<int>();
|
|
animProps.event[i].check1 = eventsArray[i]["check1"].as<bool>();
|
|
animProps.event[i].check2 = eventsArray[i]["check2"].as<bool>();
|
|
animProps.event[i].check3 = eventsArray[i]["check3"].as<bool>();
|
|
animProps.event[i].check4 = eventsArray[i]["check4"].as<bool>();
|
|
animProps.event[i].countDown = 0;
|
|
}
|
|
|
|
ESP_LOGD(tag, "Extracted/Updated active profile to animProps.");
|
|
|
|
// Save to disk/flash
|
|
String filePath = "/cfg/anim-profile" + String(profile_index + 1) + ".json";
|
|
if(updateJsonDocument(profJson, filePath.c_str())){
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for wifi.json");
|
|
}
|
|
|
|
getpostSuccess = true;
|
|
}
|
|
}
|
|
else if(!strcmp(dataType, "play-anim")){
|
|
JsonDocument animData;
|
|
DeserializationError error = deserializeJson(animData, postData, total);
|
|
if(!error){
|
|
ANIMATION_EVENT testEvent;
|
|
testEvent.selfIndex = animData["index"].as<int>();
|
|
testEvent.animIndex = animData["anim"].as<int>();
|
|
testEvent.hue = animData["hue"].as<int>();
|
|
testEvent.hueRange = animData["hue-range"].as<int>();
|
|
testEvent.speed = animData["speed"].as<int>();
|
|
testEvent.param1 = animData["param1"].as<int>();
|
|
testEvent.param2 = animData["param2"].as<int>();
|
|
testEvent.check1 = animData["check1"].as<int>();
|
|
testEvent.check2 = animData["check2"].as<int>();
|
|
testEvent.check3 = animData["check3"].as<int>();
|
|
testEvent.check4 = animData["check4"].as<int>();
|
|
|
|
if(testEvent.selfIndex != 0){
|
|
testEvent.selfIndex = 1;
|
|
}
|
|
ESP_LOGI(tag, "Post:play-anim, index: %d, anim: %d, hue: %d, rng: %d, speed: %d, par1: %d, par2: %d, chk1: %d, chk2: %d, chk3: %d, chk4: %d", testEvent.selfIndex, testEvent.animIndex, testEvent.hue, testEvent.hueRange, testEvent.speed, testEvent.param1, testEvent.param2, testEvent.check1, testEvent.check2, testEvent.check3, testEvent.check4);
|
|
testEvent.countDown = 0; // set to zero so param1 determines countdown time
|
|
testEvent.type = EV_TEST;
|
|
PostNewEvent(testEvent); // eventIndex always = 1
|
|
Buzzer_Beep(150, 3000);
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for play-anim");
|
|
}
|
|
|
|
getpostSuccess = true;
|
|
}
|
|
else if(!strcmp(dataType, "wifi")){
|
|
JsonDocument wifiCreds;
|
|
DeserializationError error = deserializeJson(wifiCreds, postData, total);
|
|
if(!error){
|
|
//read credentials
|
|
//const char* new_ssid = wifiCreds["ssid"];
|
|
//const char* new_pass = wifiCreds["pass"];
|
|
|
|
String new_ssid = wifiCreds["ssid"];
|
|
String new_pass = wifiCreds["pass"];
|
|
ESP_LOGV(tag, "SSID: %s PASS: %s", new_ssid, new_pass);
|
|
// Save Credentials if different
|
|
//if(strcmp(new_ssid, ssid)!=0 || strcmp(new_pass, passPhrase)!=0){
|
|
if(new_ssid != ssid || new_pass != passPhrase)
|
|
Wifi_Save_Credentials(new_ssid, new_pass);
|
|
StartDelayedRebooth;
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for wifi");
|
|
}
|
|
|
|
getpostSuccess = true;
|
|
}
|
|
else if(!strcmp(dataType, "set-pixel")){
|
|
JsonDocument js;
|
|
DeserializationError error = deserializeJson(js, postData, total);
|
|
if(!error){
|
|
ANIMATION_EVENT testEvent;
|
|
testEvent.selfIndex = 1;
|
|
testEvent.animIndex = 81;
|
|
testEvent.hue = js["hue"].as<int>();
|
|
testEvent.param1 = js["index"].as<int>();
|
|
testEvent.type = EV_TEST;
|
|
PostNewEvent(testEvent); // eventIndex always = 1
|
|
ESP_LOGD(tag, "Post: set-pixel, hue: %d, pixel: %d", testEvent.hue, testEvent.param1);
|
|
Buzzer_Beep(150, 3000);
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for set-pixel");
|
|
}
|
|
|
|
Buzzer_Beep(50, 3000);
|
|
getpostSuccess = true;
|
|
}
|
|
else if(!strcmp(dataType, "clear-strip")){
|
|
ANIMATION_EVENT testEvent;
|
|
testEvent.selfIndex = 1;
|
|
testEvent.animIndex = 80;
|
|
ESP_LOGD(tag, "Post: clear-strip");
|
|
testEvent.type = EV_TEST;
|
|
PostNewEvent(testEvent); // eventIndex always = 1
|
|
|
|
getpostSuccess = true;
|
|
Buzzer_Beep(50, 3000);
|
|
}
|
|
else if(!strcmp(dataType, "setup-save")){
|
|
JsonDocument js;
|
|
DeserializationError error = deserializeJson(js, postData, total);
|
|
if(!error){
|
|
// If app index is different open app-events.json and update
|
|
if(sysProps.appIndex != js["appindex"].as<int>()){
|
|
File file = LittleFS.open("/cfg/app-events.json", "r+");
|
|
if(!file){
|
|
ESP_LOGE(tag, "Failed to open app-events.json");
|
|
file.close();
|
|
return;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
file.close();
|
|
|
|
if(!error){
|
|
// Update index value
|
|
doc["index"] = js["appindex"].as<int>();
|
|
ESP_LOGD(tag, "New App Index = %d", doc["index"].as<int>());
|
|
|
|
if(updateJsonDocument(doc, "/cfg/app-events.json")){
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for app-events.json");
|
|
}
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for seteup-save");
|
|
}
|
|
|
|
// if any of the values are diiferent then open led-devices.json and update
|
|
//if app index is different open app-events.json and update
|
|
bool editJson = false;
|
|
//if(js["en1"].as<int>() != strip1->size) { editJson = true; }
|
|
if(js["count1"].as<int>() != strip1->size) { editJson = true; }
|
|
else if(js["shift1"].as<int>() != strip1->shift) { editJson = true; }
|
|
else if(js["offset1"].as<int>() != strip1->offset) { editJson = true; }
|
|
else if(js["rgb1"].as<int>() != strip1->ledOrder) { editJson = true; }
|
|
else if(js["power1"].as<int>() != strip1->powerDiv) { editJson = true; }
|
|
|
|
if(editJson){
|
|
File file = LittleFS.open("/cfg/led-devices.json", "r+");
|
|
if(!file){
|
|
ESP_LOGE(tag, "Failed to open led-devices.json");
|
|
file.close();
|
|
return;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
file.close();
|
|
|
|
if(!error){
|
|
JsonObject jsStrip1 = doc.createNestedObject("strip1"); // nested so it doesn't create a copy
|
|
|
|
jsStrip1["en"] = true;
|
|
jsStrip1["size"] = js["count1"].as<int>();
|
|
jsStrip1["shift"] = js["shift1"].as<int>();
|
|
jsStrip1["offset"] = js["offset1"].as<int>();
|
|
jsStrip1["power-div"] = js["power1"].as<int>();
|
|
jsStrip1["rgb-order"] = js["rgb1"];
|
|
|
|
// Save Json File
|
|
if(updateJsonDocument(doc, "/cfg/led-devices.json")){
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "Deserialization error for led-devices.json");
|
|
}
|
|
}
|
|
|
|
ESP_LOGD(tag, "Post: setup-save");
|
|
getpostSuccess = true;
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
else if(!strcmp(dataType, "restart")){
|
|
StartDelayedRebooth;
|
|
|
|
ESP_LOGD(tag, "Post: restart");
|
|
getpostSuccess = true;
|
|
Buzzer_Beep(150, 3000);
|
|
}
|
|
}
|
|
|
|
// Delete the allocated buffer (owned by the request->_tempObject)
|
|
delete[] postData;
|
|
}
|
|
}
|
|
|
|
bool isInternetConnected()
|
|
{
|
|
if (WiFi.status() == WL_CONNECTED) { // check if WiFi is connected
|
|
WiFiClient client;
|
|
const int httpPort = 80;
|
|
if (client.connect("www.google.com", httpPort)) { // try to connect to Google
|
|
client.stop();
|
|
return true; // Internet access available
|
|
}
|
|
}
|
|
return false; // Internet access not available
|
|
}
|
|
|
|
void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
|
|
{
|
|
if (!index && request->hasParam("dir-path", true, false)) {
|
|
AsyncWebParameter* p = request->getParam("dir-path",true, false);
|
|
String path = p->value() + "/" + filename;
|
|
ESP_LOGD(tag, "upload path: %s", path.c_str());
|
|
|
|
request->_tempFile= LittleFS.open(path, "w");
|
|
//fs::File file = LittleFS.open(path, "w");
|
|
if (!request->_tempFile) {
|
|
ESP_LOGE(tag, "Failed to create file!");
|
|
return;
|
|
}
|
|
}else{
|
|
ESP_LOGE(tag, "dir-path Param not found..");
|
|
}
|
|
|
|
if(len && request->_tempFile){
|
|
request->_tempFile.write(data, len);
|
|
}
|
|
|
|
if(final){
|
|
request->_tempFile.close();
|
|
request->redirect("/files");
|
|
ESP_LOGD(tag, "UploadEnd: %s, %u B", filename.c_str(), index+len);
|
|
}
|
|
}
|
|
|
|
void handleGET_DSLRBooth(AsyncWebServerRequest *request)
|
|
{
|
|
static int lastCountdown = 1000;
|
|
request->send(200, "text/plain"); // send this right away to avoid comm delays
|
|
|
|
if(request->args() >= 1){
|
|
int x = 0;
|
|
const char* event_type = request->arg( x ).c_str();
|
|
|
|
if(strcmp("countdown", event_type) == 0){
|
|
int p = request->arg(1).toInt();
|
|
if(p > 0 && p <= 100){
|
|
animStatus.countStatus = p;
|
|
}
|
|
|
|
// in case "coundown_start" was missed
|
|
if(animStatus.EventsIndex != ANIM_WHITEFILL_INDEX){
|
|
animProps.event[0].countDown = lastCountdown;
|
|
animProps.event[0].type = EV_NORMAL;
|
|
PostNewEvent(animProps.event[0]);
|
|
}
|
|
}
|
|
else if(strcmp("countdown_start", event_type) == 0){ // index=0
|
|
int count = request->arg(1).toInt(); // retrieve countdown value
|
|
//Log.traceln("<dslr> countdown = %d", count);
|
|
if(count < 1){count = 1;}
|
|
count *= 1000;
|
|
lastCountdown = count - 500;
|
|
animProps.event[0].countDown = lastCountdown;
|
|
animProps.event[0].type = EV_NORMAL;
|
|
PostNewEvent(animProps.event[0]);
|
|
}
|
|
else if(strcmp("sharing_screen", event_type) == 0){
|
|
animProps.event[2].type = EV_NORMAL;
|
|
PostNewEvent(animProps.event[2]);
|
|
}
|
|
else if(strcmp("session_end", event_type) == 0){ //index=1
|
|
animProps.event[1].type = EV_NORMAL;
|
|
PostNewEvent(animProps.event[1]);
|
|
}
|
|
else{
|
|
// else if(strcmp_P("session_start", event_type) == 0){
|
|
// TODO determine if ramp down is active depending on session type
|
|
|
|
/*
|
|
const char* param1 = request->arg(1).c_str();
|
|
if(strcmp("PrintOnly", param1)){
|
|
animStatus.Animation = ANIM_START_PRINTONLY;
|
|
xTaskNotifyGive( Strip1_Task_Handle );
|
|
//Serial.println("ses_print..\n\r");
|
|
}else if(strcmp("PrintAndGIF", param1)){
|
|
animStatus.Animation = ANIM_START_PRINTGIF;
|
|
xTaskNotifyGive( Strip1_Task_Handle );
|
|
//Serial.println("ses_prnt_gif..\n\r");
|
|
}else if(strcmp("OnlyGIF", param1)){
|
|
animStatus.Animation = ANIM_START_GIFONLY;
|
|
xTaskNotifyGive( Strip1_Task_Handle );
|
|
//Serial.println("ses_gif..\n\r");
|
|
}else if(strcmp("Boomerang", param1)){
|
|
animStatus.Animation = ANIM_START_BOOM;
|
|
xTaskNotifyGive( Strip1_Task_Handle );
|
|
//Serial.println("ses_boom..\n\r");
|
|
}else if(strcmp("Video", param1)){
|
|
animStatus.Animation = ANIM_START_VIDEO;
|
|
xTaskNotifyGive( Strip1_Task_Handle );
|
|
//Serial.println("ses_vid..\n\r");
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isFirmware = true;
|
|
bool updateSuccessful = false;
|
|
void handlePOST_Update(AsyncWebServerRequest *request)
|
|
{
|
|
bool hasError = Update.hasError();
|
|
String responseContent = hasError ? "failed" : "success";
|
|
String responseType = hasError ? "text/plain" : "text/html";
|
|
int responseCode = hasError ? 500 : 200;
|
|
|
|
AsyncWebServerResponse *response = request->beginResponse(responseCode, responseType, responseContent);
|
|
response->addHeader("status", responseContent);
|
|
request->send(response);
|
|
}
|
|
|
|
// handlePOST_Update Callback function to install the firmware or file system bin files
|
|
void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
|
|
{
|
|
// Do Once Code
|
|
if (!index) {
|
|
size_t fileSize = UPDATE_SIZE_UNKNOWN;
|
|
// Retrieve file-size parameter
|
|
if(request->hasParam("file-size", true, false)) { // make sure param is from POST request or it will not find it
|
|
fileSize = request->getParam("file-size", true, false)->value().toInt();
|
|
}
|
|
|
|
// register progress callback function
|
|
Update.onProgress(updateFirmwareProgress);
|
|
updateSuccessful = false;
|
|
|
|
//file name checks
|
|
if (filename.startsWith("ata_fw")) {
|
|
isFirmware = true;
|
|
if (!Update.begin(fileSize, U_FLASH, BoardLED2)) {
|
|
ESP_LOGD(tag, "Firmware update failed to start");
|
|
return;
|
|
}
|
|
ESP_LOGD(tag, "Updating Firmware with: %s Size:%d ", filename.c_str(), fileSize);
|
|
}
|
|
else if (filename.startsWith("ata_fs")) {
|
|
isFirmware = false;
|
|
if (!Update.begin(fileSize, U_SPIFFS, BoardLED2)) {
|
|
ESP_LOGD(tag, "LittleFS update failed to start");
|
|
return;
|
|
}
|
|
ESP_LOGD(tag, "Updating File System with: %s Size:%d ", filename.c_str(), fileSize);
|
|
}
|
|
else {
|
|
ESP_LOGD(tag, "Unsupported file type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Writing...
|
|
if (!Update.hasError()) {
|
|
if (Update.write(data, len) != len) {
|
|
Update.printError(Serial);
|
|
}
|
|
}
|
|
|
|
// All Done...
|
|
if (final) {
|
|
vTaskDelay(100);
|
|
if(isFirmware) { // firmware update
|
|
if (Update.end(true)) {
|
|
RebootSystem = true;
|
|
updateSuccessful = true;
|
|
ESP_LOGD(tag, "firmware update successful: %d", index + len);
|
|
}
|
|
else {
|
|
Update.printError(Serial);
|
|
}
|
|
}
|
|
else{ // file system update
|
|
if(Update.end(true)) {
|
|
updateSuccessful = true;
|
|
ESP_LOGD(tag, "file system update successful!");
|
|
}
|
|
else {
|
|
Update.printError(Serial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Server requested files that aren't template processed
|
|
void handleGET_SendFile(AsyncWebServerRequest *request)// try this later "^/(img|favicon).*"
|
|
{
|
|
String filePath = request->url();
|
|
const char* ext = getFileExtension(filePath.c_str());
|
|
const char* contentType = getFileType(ext);
|
|
|
|
if( contentType == NULL ){
|
|
request->send(404);
|
|
return;
|
|
}
|
|
|
|
if(filePath.c_str()[0] != '/'){
|
|
filePath = '/' + filePath;
|
|
}
|
|
|
|
if (!strcmp(ext,"html") || !strcmp(ext, "htm") || !strcmp(ext, "css") || !strcmp(ext, "js")) {
|
|
if(!filePath.startsWith("/www/")){
|
|
filePath = "/www" + filePath;
|
|
}
|
|
}
|
|
|
|
ESP_LOGD(tag, "Sent: %s", filePath.c_str());
|
|
request->send(LittleFS, filePath, contentType);
|
|
}
|
|
|
|
String listDir(String directoryList[], int count)
|
|
{
|
|
String listedFiles;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
// directory html
|
|
listedFiles += "<tr id=\"file-row\"><td>Dir: ";
|
|
listedFiles += directoryList[i];
|
|
listedFiles += "/</td><td>-</td></tr>\n";
|
|
|
|
filesDropdownOptions += "<option value=\"";
|
|
filesDropdownOptions += directoryList[i];
|
|
filesDropdownOptions += "\">";
|
|
filesDropdownOptions += directoryList[i];
|
|
filesDropdownOptions += "/</option>\n";
|
|
|
|
dirDropdownOptions += "<option value=\"";
|
|
dirDropdownOptions += directoryList[i];
|
|
dirDropdownOptions += "\">";
|
|
dirDropdownOptions += directoryList[i];
|
|
dirDropdownOptions += "/</option>\n";
|
|
|
|
File dir = LittleFS.open(directoryList[i]);
|
|
File file = dir.openNextFile();
|
|
while (file) {
|
|
String fileName = file.name();
|
|
if (!file.isDirectory()) {
|
|
//Serial.println(" File: " + String(file.name()));
|
|
listedFiles += "<tr id=\"file-row\"><td> <button onclick=\"window.location.href='/download?file=";
|
|
listedFiles += file.path();
|
|
listedFiles += "\" \"\">⇩</button> ";
|
|
listedFiles += fileName;
|
|
listedFiles += "</td><td>";
|
|
listedFiles += convertFileSize(file.size());
|
|
listedFiles += "</td></tr>\n";
|
|
|
|
filesDropdownOptions += "<option value=\"";
|
|
filesDropdownOptions += file.path();
|
|
filesDropdownOptions += "\">  ";
|
|
filesDropdownOptions += fileName;
|
|
filesDropdownOptions += "</option>\n";
|
|
}
|
|
file = dir.openNextFile();
|
|
}
|
|
dir.close();
|
|
}
|
|
|
|
return listedFiles;
|
|
}
|
|
|
|
char* readFile(fs::FS &fs, const char* path) {
|
|
File file = fs.open(path, "r");
|
|
if (!file || file.isDirectory()) {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t fileSize = file.size();
|
|
char* fileContent = new char[fileSize + 1]; // +1 for null-terminator
|
|
|
|
size_t bytesRead = file.readBytes(fileContent, fileSize);
|
|
file.close();
|
|
|
|
fileContent[bytesRead] = '\0'; // Set null-terminating character
|
|
|
|
if (bytesRead != fileSize) {
|
|
delete[] fileContent;
|
|
return nullptr;
|
|
}
|
|
|
|
return fileContent;
|
|
}
|
|
|
|
void writeFile(fs::FS &fs, const char * path, const char * message)
|
|
{
|
|
File file = fs.open(path, "w");
|
|
if(!file){
|
|
return;
|
|
}
|
|
file.print(message);
|
|
file.close();
|
|
}
|
|
|
|
// Send html file with template processing {{VAR}}
|
|
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&))
|
|
{
|
|
ESP_LOGD(tag, "Sent file: %s", filePath);
|
|
File file = LittleFS.open(filePath, "r");
|
|
const char* htmlFile = readFile(LittleFS, filePath);
|
|
|
|
//String processedData = varReplace(htmlFile, htmlProcessor);
|
|
String processedData = varReplace(htmlFile, callback);
|
|
request->send(200, "text/html", processedData);
|
|
}
|
|
|
|
const char* convertFileSize(const size_t bytes) {
|
|
static char fileSizeBuffer[16]; // PreAllocated buffer for the file size
|
|
|
|
if (bytes < 1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes);
|
|
} else if (bytes < 1024*1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", static_cast<double>(bytes) / 1024.0);
|
|
} else if (bytes < 1024*1024*1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", static_cast<double>(bytes) / 1048576.0);
|
|
} else {
|
|
fileSizeBuffer[0] = '\0'; // Empty string if the file size is too large
|
|
}
|
|
|
|
return fileSizeBuffer;
|
|
}
|
|
|
|
// Callback template processor
|
|
String htmlProcessor(const String& var)
|
|
{
|
|
if(var == "NAVBAR"){ return String("<iframe src=\"/www/navbar.html\" style=\"display: block; width: 100%; max-height: 4vh; border: none;\" scrolling=\"no\"></iframe>\n"); }
|
|
|
|
if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; }
|
|
|
|
if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); }
|
|
|
|
if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); }
|
|
|
|
if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
|
|
|
|
if(var == "LISTED_FILES"){
|
|
filesDropdownOptions = ""; // clear out
|
|
dirDropdownOptions = ""; // clear out
|
|
String directories[8];
|
|
int dirCount = 0;
|
|
getAllDirectories(directories, dirCount);
|
|
|
|
return listDir(directories, dirCount);
|
|
}
|
|
|
|
if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; }
|
|
|
|
if(var == "DIR_LIST"){ return dirDropdownOptions; }
|
|
|
|
if(var == "SAVE_PATH_INPUT"){
|
|
if(savePath == "/new.txt"){
|
|
return String("<input type=\"text\" id=\"save-path\" name=\"save-path\" value=\"") + savePath + "\">";
|
|
}
|
|
else{
|
|
return String("<input type=\"text\" id=\"save-path\" name=\"save-path\" value=\"") + savePath + "\"disabled>";
|
|
}
|
|
}
|
|
|
|
if(var == "FIRM_VER"){ return FIRMWARE_VER; }
|
|
|
|
return var;
|
|
}
|
|
|
|
String HomeHtmlProcessor(const String& var) {
|
|
if(var == "NAVBAR"){ return String("<iframe src=\"/www/navbar.html\" style=\"display: block; width: 100%; max-height: 3vh; border: none;\" scrolling=\"no\"></iframe>\n"); }
|
|
|
|
if (var == "APP_NAME") {
|
|
return sysProps.appName;
|
|
}
|
|
if (var == "OLED") {
|
|
return "No";
|
|
}
|
|
if (var == "STRIP1") {
|
|
return (strip1) ? "Yes" : "No";
|
|
}
|
|
if (var == "STRIP2") {
|
|
return (strip2) ? "Yes" : "No";
|
|
}
|
|
if (var == "FRONT_LIGHT") {
|
|
return (animProps.frontLight.enabled) ? "Yes" : "No";
|
|
}
|
|
if (var == "REAR_LIGHT") {
|
|
return (animProps.rearLight.enabled) ? "Yes" : "No";
|
|
}
|
|
if (var == "FIRMWARE") {
|
|
return FIRMWARE_VER;
|
|
}
|
|
if (var == "BOOTH_T") {
|
|
return String(sysProps.t_sensor.temperature) + "F";
|
|
}
|
|
if (var == "SETPOINT") {
|
|
return String(sysProps.t_sensor.Setpoint1) + "F";
|
|
}
|
|
if (var == "FLASH_SIZE") {
|
|
return convertFileSize(ESP.getSketchSize());
|
|
}
|
|
if (var == "FLASH_FREE") {
|
|
return convertFileSize(ESP.getFreeSketchSpace());
|
|
}
|
|
if (var == "HEAP_SIZE") {
|
|
return convertFileSize(ESP.getHeapSize());
|
|
}
|
|
if (var == "HEAP_FREE") {
|
|
return convertFileSize(ESP.getFreeHeap());
|
|
}
|
|
if (var == "CPU_FREQ") {
|
|
return String(ESP.getCpuFreqMHz()) + "Mhz";
|
|
}
|
|
if (var == "IP") {
|
|
return WiFi.localIP().toString();
|
|
}
|
|
if (var == "MAC") {
|
|
return chipInfo.macStr;
|
|
}
|
|
if (var == "SSID") {
|
|
return WiFi.SSID();
|
|
}
|
|
if (var == "RSSI") {
|
|
return String(WiFi.RSSI());
|
|
}
|
|
if (var == "WIFI_CH") {
|
|
return String(WiFi.channel());
|
|
}
|
|
if (var == "ENCRYP") {
|
|
return String(WiFi.encryptionType(0));
|
|
}
|
|
if (var == "AP_SSID") {
|
|
return WiFi.softAPSSID();
|
|
}
|
|
if (var == "AP_CLIENTS") {
|
|
return String(WiFi.softAPgetStationNum());
|
|
}
|
|
if (var == "BLE") {
|
|
return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No";
|
|
}
|
|
if (var == "BLE_SSID") {
|
|
return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : "";
|
|
}
|
|
if (var == "BLE_CLIENTS") {
|
|
return (BTDeviceConnected) ? "1" : "0";
|
|
}
|
|
if (var == "AP_MAC") {
|
|
return getSoftAPMacAddress();
|
|
}
|
|
|
|
|
|
// Return an empty string if the variable is not recognized
|
|
return var;
|
|
}
|
|
|
|
String getSoftAPMacAddress() {
|
|
uint8_t mac[6];
|
|
WiFi.softAPmacAddress(mac);
|
|
|
|
String macString = "";
|
|
for (int i = 0; i < 6; i++) {
|
|
macString += String(mac[i], HEX);
|
|
if (i < 5) macString += ":";
|
|
}
|
|
return macString;
|
|
}
|
|
|
|
// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content
|
|
String varReplace(const String& input, String (*callback)(const String&)) {
|
|
if (input.isEmpty()) {
|
|
return input;
|
|
}
|
|
|
|
String result;
|
|
int startPos = 0;
|
|
int start = input.indexOf("{{", startPos);
|
|
|
|
while (start != -1) {
|
|
result += input.substring(startPos, start);
|
|
|
|
int end = input.indexOf("}}", start + 2);
|
|
if (end == -1) {
|
|
break;
|
|
}
|
|
|
|
String segment = input.substring(start + 2, end);
|
|
int segmentLength = segment.length();
|
|
if (segmentLength <= 32) {
|
|
String replacement = callback(segment);
|
|
result += replacement;
|
|
} else {
|
|
result += input.substring(start, end + 2); // Include the original segment if it exceeds the limit
|
|
}
|
|
|
|
startPos = end + 2;
|
|
start = input.indexOf("{{", startPos);
|
|
}
|
|
|
|
result += input.substring(startPos);
|
|
return result;
|
|
}
|
|
|
|
const char* getFileExtension(const char* filename) {
|
|
size_t dotPos = strlen(filename);
|
|
while (dotPos > 0 && filename[dotPos - 1] != '.') {
|
|
dotPos--;
|
|
}
|
|
|
|
if (dotPos != 0 && dotPos < strlen(filename) - 1) {
|
|
return &filename[dotPos];
|
|
}
|
|
|
|
return ""; // Empty string if no extension found
|
|
}
|
|
|
|
const char* getFileType(const char* ext) {
|
|
if (strcmp(ext, "png") == 0) {
|
|
return "image/png";
|
|
} else if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) {
|
|
return "image/jpeg";
|
|
} else if (strcmp(ext, "gif") == 0) {
|
|
return "image/gif";
|
|
} else if (strcmp(ext, "ico") == 0) {
|
|
return "image/x-icon";
|
|
} else if (strcmp(ext, "txt") == 0) {
|
|
return "text/plain";
|
|
} else if (strcmp(ext, "css") == 0) {
|
|
return "text/css";
|
|
} else if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) {
|
|
return "text/html";
|
|
} else if (strcmp(ext, "js") == 0) {
|
|
return "text/javascript";
|
|
} else if (strcmp(ext, "json") == 0) {
|
|
return "application/json";
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Serial print firmware or file system update progress
|
|
void updateFirmwareProgress(size_t progress, size_t total)
|
|
{
|
|
static int lastProg = -1;
|
|
int firmwareProgress = (progress * 100)/ total;
|
|
|
|
if(firmwareProgress != lastProg){
|
|
Serial.printf("Progress: %u%%\r", firmwareProgress);
|
|
lastProg = firmwareProgress;
|
|
//TODO Add buzzer tune while uploading firmware
|
|
//Buzzer_Play_Tune(TUNE_DOWNLOADING,1,true,false);
|
|
}
|
|
}
|
|
|
|
|