Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
#ifndef ASYNC_FS_WEBSERVER_H
2
#define ASYNC_FS_WEBSERVER_H
3
 
4
#include <FS.h>
5
#include <DNSServer.h>
6
 
7
#include "ESPAsyncWebServer.h"
8
#include "Json.h"
9
#include "WiFiService.h"
10
#include "SerialLog.h"
11
#include "Version.h"
12
#include <type_traits>
13
 
14
class Print;
15
 
16
#ifdef ESP32
17
  // Arduino-ESP32 v3 splits networking primitives into a dedicated core library.
18
  // PlatformIO's dependency finder doesn't always pull it in via transitive includes,
19
  // so include it explicitly to ensure it gets compiled and linked.
20
  #if defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3)
21
    #include <Network.h>
22
  #endif
23
  #include <WiFi.h>
24
  #include <WiFiAP.h>
25
  #include <Update.h>
26
  #include <ESPmDNS.h>
27
  #include "esp_wifi.h"
28
  #include "esp_task_wdt.h"
29
  #include "sys/stat.h"
30
#elif defined(ESP8266)
31
  #include <ESP8266mDNS.h>
32
  #include <Updater.h>
33
#else
34
  #error Platform not supported
35
#endif
36
 
37
#ifndef ESP_FS_WS_EDIT
38
    #define ESP_FS_WS_EDIT              1   // Library has edit methods
39
    #ifndef ESP_FS_WS_EDIT_HTM
40
                                            // Disable if you provide your own edit page
41
        #define ESP_FS_WS_EDIT_HTM      1   // Library serve /edit webpage from progmem                                            
42
    #endif
43
#endif
44
 
45
#ifndef ESP_FS_WS_SETUP
46
    #define ESP_FS_WS_SETUP             1   // Library has setup methods
47
    #ifndef ESP_FS_WS_SETUP_HTM
48
                                            // Disable if you provide your own setup page
49
        #define ESP_FS_WS_SETUP_HTM     1   // Library serve /setup webpage from progmem                                            
50
    #endif
51
#endif
52
 
53
#if ESP_FS_WS_EDIT_HTM
54
    #include "assets/edit_htm.h"
55
#endif
56
 
57
#if ESP_FS_WS_SETUP_HTM
58
    #define ESP_FS_WS_CONFIG_FOLDER "/config"
59
    #define ESP_FS_WS_CONFIG_FILE ESP_FS_WS_CONFIG_FOLDER "/config.json"
60
    #include "assets/setup_htm.h"
61
    #include "assets/creds_js.h"
62
    #include "assets/logo_svg.h"
63
    #include "CredentialManager.h"    
64
    #include "SetupConfig.hpp" 
65
#endif
66
 
67
#include "CaptivePortal.hpp"
68
#ifndef ESP_FS_WS_MDNS
69
  #define ESP_FS_WS_MDNS 1
70
#endif
71
 
72
 
73
#define LIB_URL "https://github.com/cotestatnt/async-esp-fs-webserver/"
74
#define MIN_F -3.4028235E+38
75
#define MAX_F 3.4028235E+38
76
 
77
// Watchdog timeout utility
78
#if defined(ESP32)
79
    #define AWS_WDT_TIMEOUT (CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000)
80
    #define AWS_LONG_WDT_TIMEOUT (AWS_WDT_TIMEOUT * 4)
81
#else
82
  #define AWS_WDT_TIMEOUT 5000
83
  #define AWS_LONG_WDT_TIMEOUT 15000
84
#endif
85
 
86
typedef struct {
87
  size_t totalBytes;
88
  size_t usedBytes;
89
  String fsName;
90
} fsInfo_t;
91
 
92
using FsInfoCallbackF = std::function<void(fsInfo_t*)>;
93
using CallbackF = std::function<void(void)>;
94
using ConfigSavedCallbackF = std::function<void(const char*)>;  // Callback for config file saves
95
 
96
class AsyncFsWebServer : public AsyncWebServer
97
{
98
  protected:
99
    AsyncWebSocket* m_ws = nullptr;
100
    AsyncWebHandler *m_captive = nullptr;
101
    DNSServer* m_dnsServer = nullptr;
102
 
103
    char m_apSSID[33] = {0};
104
    char m_apPassword[65] = {0};
105
    bool m_isApMode = false;
106
    bool m_authAll = false;
107
 
108
    void notFound(AsyncWebServerRequest *request);
109
    void handleFileName(AsyncWebServerRequest *request);
110
 
111
#if ESP_FS_WS_SETUP    
112
    void handleSetup(AsyncWebServerRequest *request);
113
    void getStatus(AsyncWebServerRequest *request);
114
    void clearConfig(AsyncWebServerRequest *request);        
115
    void doWifiConnection(AsyncWebServerRequest *request);
116
    void handleScanNetworks(AsyncWebServerRequest *request);
117
    void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);    
118
    void onUpdate();
119
#endif
120
 
121
    // edit page, in useful in some situation, but if you need to provide only a web interface, you can disable
122
#if ESP_FS_WS_EDIT_HTM
123
    void deleteContent(String& path) ;
124
    void handleFileDelete(AsyncWebServerRequest *request);
125
    void handleFileCreate(AsyncWebServerRequest *request);
126
    void handleFsStatus(AsyncWebServerRequest *request);
127
    void handleFileList(AsyncWebServerRequest *request);
128
    void handleFileEdit(AsyncWebServerRequest *request);
129
#endif
130
 
131
  /*
132
    Create a dir if not exist on uploading files
133
  */
134
  bool createDirFromPath( const String& path) ;
135
 
136
  private:
137
    char* m_pageUser = nullptr;
138
    char* m_pagePswd = nullptr;
139
    String m_host = "esphost";
140
    String m_captiveUrl = "/setup";
141
    String typeName = "FileSystem";
142
 
143
    uint16_t m_port;
144
    uint32_t m_timeout = AWS_LONG_WDT_TIMEOUT;
145
 
146
    // Firmware version buffer (expanded to accommodate custom version strings)
147
    String m_version;
148
    bool m_filesystem_ok = false;
149
 
150
    fs::FS* m_filesystem = nullptr;
151
    FsInfoCallbackF getFsInfo = nullptr;
152
    ConfigSavedCallbackF m_configSavedCallback = nullptr;  // Callback for config file saves
153
 
154
    IPAddress m_serverIp = IPAddress(192, 168, 4, 1);
155
    CredentialManager *m_credentialManager = nullptr;
156
 
157
#if ESP_FS_WS_SETUP
158
    SetupConfigurator* setup = nullptr;
159
 
160
    // Lazy initialization: create setup object only when first needed
161
    SetupConfigurator* getSetupConfigurator() {
162
      if (!setup) {
163
        setup = new SetupConfigurator(m_filesystem, m_port, m_host);
164
      }
165
      return setup;
166
    }
167
#endif
168
 
169
  public:
170
 
171
    // Template Constructor for derived filesystem classes (LittleFS, SPIFFS, etc)
172
    template <typename T>
173
    AsyncFsWebServer(T &fs, uint16_t port, const char* hostname = "") : AsyncWebServer(port), m_filesystem(&fs)
174
    {
175
      m_port = port;      
176
 
177
      if (!m_credentialManager) {
178
        log_error("Credential manager not initialized");        
179
        m_credentialManager = new CredentialManager();
180
        m_credentialManager->begin();
181
        #ifdef ESP8266        
182
        m_credentialManager->setFilesystem(m_filesystem);  // Set FS for persistence
183
        #endif
184
      }     
185
 
186
      // Set hostname if provided from constructor
187
      if (strlen(hostname))
188
        m_host = hostname;
189
 
190
      // Sync hostname into shared CredentialManager
191
      if (m_credentialManager) {
192
        m_credentialManager->setHostname(m_host.c_str());
193
      }
194
 
195
#ifdef ESP32
196
        // Auto-configure getFsInfo for ESP32 filesystems
197
        // Try to infer filesystem name from template type
198
        String pretty = __PRETTY_FUNCTION__;
199
        int start = pretty.indexOf("T = ");
200
        if (start != -1)
201
        {
202
            start += 4;
203
            int end = pretty.indexOf("]", start);
204
            int semi = pretty.indexOf(";", start);
205
            if (semi != -1 && semi < end)
206
            {
207
                end = semi;
208
            }
209
            if (end != -1)
210
            {
211
                typeName = pretty.substring(start, end);
212
                // Clean up common prefixes/suffixes
213
                typeName.replace("fs::", "");
214
                if (typeName.endsWith("FS"))
215
                {
216
                    typeName = typeName.substring(0, typeName.length() - 2);
217
                }
218
            }
219
        }
220
 
221
        getFsInfo = [&fs, this](fsInfo_t *info)
222
        {
223
            info->totalBytes = fs.totalBytes();
224
            info->usedBytes = fs.usedBytes();
225
            info->fsName = this->typeName;
226
        };
227
#endif    
228
    }
229
 
230
    // Class destructor
231
    ~AsyncFsWebServer() {
232
      reset();
233
      end();
234
      if(_catchAllHandler) delete _catchAllHandler;
235
 
236
      // Deallocate all dynamically allocated resources
237
      if(m_pageUser) delete[] m_pageUser;
238
      if(m_pagePswd) delete[] m_pagePswd;
239
      if(m_ws) delete m_ws;
240
      if(m_captive) delete m_captive;
241
      if(m_dnsServer) delete m_dnsServer;
242
      if (m_credentialManager) delete m_credentialManager;
243
#if ESP_FS_WS_SETUP
244
      if(setup) delete setup;  // Only delete if it was lazily initialized
245
#endif
246
    }
247
 
248
  #ifdef ESP32
249
    inline TaskHandle_t getTaskHandler() {
250
      return xTaskGetCurrentTaskHandle();
251
    }
252
  #endif
253
 
254
    /*
255
      Get the webserver IP address
256
    */
257
    inline IPAddress getServerIP() {
258
      return m_serverIp;
259
    }
260
    /*
261
      Return true if the device is currently running in Access Point mode
262
    */
263
    inline bool isAccessPointMode() const { return m_isApMode; }
264
    /*
265
      Start webserver and bind a websocket event handler (optional)
266
    */
267
    bool init(AwsEventHandler wsHandle = nullptr);
268
 
269
#if ESP_FS_WS_EDIT
270
 
271
    /*
272
      Enable the built-in ACE web file editor
273
    */
274
    void enableFsCodeEditor();
275
 
276
    // Backward compatibility method
277
    [[deprecated("Use enableFsCodeEditor() instead (use built-in callback to provide FS info).")]]
278
    void enableFsCodeEditor(FsInfoCallbackF fsCallback) {
279
        if (fsCallback)
280
            getFsInfo = fsCallback;
281
        enableFsCodeEditor();
282
    }
283
 
284
    /*
285
     * Set callback function to provide updated FS info to library
286
     * This it is necessary due to the different implementation of
287
     * libraries for the filesystem (LittleFS, FFat, SPIFFS etc etc)
288
     */
289
    [[deprecated("Use enableFsCodeEditor() instead (use built-in callback to provide FS info)")]]
290
    inline void setFsInfoCallback(FsInfoCallbackF fsCallback)
291
    {
292
        getFsInfo = fsCallback;
293
    }
294
#endif
295
 
296
    /*
297
      Enable authenticate for /setup webpage
298
    */
299
    void setAuthentication(const char* user, const char* pswd);
300
    /*
301
    Enable the flag which turns on basic authentication for all pages
302
    */
303
    inline void requireAuthentication(bool require){
304
      m_authAll = require;
305
    }
306
 
307
    /*
308
      List FS content
309
    */
310
    void printFileList(fs::FS &fs, const char * dirname, uint8_t levels);
311
 
312
    /*
313
      List FS content to a destination stream (e.g. Serial, WiFiClient)
314
    */
315
    void printFileList(fs::FS &fs, const char * dirname, uint8_t levels, Print& out);
316
 
317
    /*
318
      Send a default "OK" reply to client
319
    */
320
    void sendOK(AsyncWebServerRequest *request);
321
 
322
    /*
323
      Start WiFi connection, callback function is called when trying to connect
324
    */
325
    bool startWiFi(uint32_t timeout, CallbackF fn=nullptr) ;
326
 
327
    /**
328
     * @brief Access shared CredentialManager instance (for advanced configuration)
329
     */
330
    inline CredentialManager* getCredentialManager() { return m_credentialManager; }
331
 
332
 
333
    [[deprecated("Use startWiFi(timeout) and if it fails, use startCaptivePortal(ssid, pswd) instead.")]]
334
    IPAddress startWiFi(uint32_t timeout, const char* ssid, const char* pswd, CallbackF fn = nullptr, const char* redirectTargetURL = nullptr) {
335
      if (!startWiFi(timeout, fn)) {
336
        delay(100);
337
        startCaptivePortal(ssid, pswd, redirectTargetURL == nullptr ? "/setup" : redirectTargetURL);
338
      }
339
      return m_serverIp;
340
    }
341
 
342
    /*
343
     * Redirect to captive portal if we got a request for another domain.
344
    */
345
    bool startCaptivePortal(const char* ssid, const char* pass, const char* redirectTargetURL = "/setup");
346
    bool startCaptivePortal(WiFiConnectParams& params, const char *redirectTargetURL = "/setup");
347
 
348
    /*
349
      Set AP SSID and Password (backward compatibility)
350
    */
351
    void setAP(const char *ssid, const char *pass) {
352
      strlcpy(m_apSSID, ssid, sizeof(m_apSSID));
353
      strlcpy(m_apPassword, pass, sizeof(m_apPassword));
354
    }
355
 
356
    /*
357
    * Setup and start mDNS responder
358
    */
359
    bool startMDNSResponder();
360
 
361
    /*
362
    * Need to be run in loop to handle DNS requests
363
    */
364
    inline void updateDNS() {
365
      m_dnsServer->processNextRequest();
366
    }
367
 
368
    /*
369
     * get instance of current websocket handler (enabled at runtime)
370
    */
371
    AsyncWebSocket* getWebSocket() { return m_ws; }
372
 
373
    /*
374
     * Enable WebSocket at runtime. Creates WS on `path` and registers handler.
375
    */
376
    void enableWebSocket(const char* path, AwsEventHandler handler);
377
 
378
        /*
379
     * Broadcast a websocket message to all clients connected
380
    */
381
    void wsBroadcast(const char * buffer) {
382
      if (m_ws != nullptr)
383
        m_ws->textAll(buffer);
384
    }
385
 
386
    /*
387
    * Broadcast a binary websocket message to all clients connected
388
    */
389
    void wsBroadcastBinary(uint8_t * message, size_t len) {
390
      if (m_ws != nullptr)
391
        m_ws->binaryAll(message, len);
392
    }
393
 
394
    /*
395
    * Set current firmware version (shown in /setup webpage)
396
    */
397
    inline void setFirmwareVersion(const char* version) {
398
      m_version = String(version);
399
    }
400
 
401
    inline void setFirmwareVersion(const String& version) {
402
      m_version = version;
403
    }
404
 
405
    /*
406
    * Set hostmane
407
    */
408
    inline void setHostname(const char * host) {
409
      m_host = host;
410
      if (m_credentialManager) {
411
        m_credentialManager->setHostname(host);
412
      }
413
    }
414
 
415
    /////////////////////////////////////////////////////////////////////////////////////////////////
416
    ////////////////////////////   SETUP PAGE CONFIGURATION /////////////////////////////////////////
417
    /////////////////////////////////////////////////////////////////////////////////////////////////
418
#if ESP_FS_WS_SETUP    
419
 
420
    // Public alias to dropdown definition type (available only when /setup is enabled)
421
    using DropdownList = AsyncFSWebServer::DropdownList;
422
    // Public alias to slider definition type
423
    using Slider = AsyncFSWebServer::Slider;
424
 
425
    /*
426
    * Set callback function to be called when configuration file is saved via /edit POST
427
    * The callback receives the filename path as parameter
428
    */
429
    inline void setConfigSavedCallback(ConfigSavedCallbackF callback) {
430
      m_configSavedCallback = callback;
431
    }
432
 
433
    /*
434
    * Get reference to current config.json file
435
    */
436
    inline File getConfigFile(const char* mode) {
437
      File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, mode);
438
      return file;
439
    }
440
 
441
    /*
442
    * Clear the saved configuration options by removing config.json.
443
    * Returns true if the file was removed or did not exist.
444
    */
445
    inline bool clearConfigFile() {
446
      if (m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {
447
        return m_filesystem->remove(ESP_FS_WS_CONFIG_FILE);
448
      }
449
      return true;
450
    }
451
 
452
    /*
453
    * Get complete path of config.json file
454
    */
455
    inline const char* getConfiFileName() {
456
      return ESP_FS_WS_CONFIG_FILE;
457
    }
458
 
459
    void setSetupPageTitle(const char* title) { getSetupConfigurator()->setSetupPageTitle(title); }
460
    void addHTML(const char* html, const char* id, bool ow = false) {getSetupConfigurator()->addHTML(html, id, ow);}
461
    void addCSS(const char* css, const char* id, bool ow = false){getSetupConfigurator()->addCSS(css, id, ow);}
462
    void addJavascript(const char* script, const char* id, bool ow = false) {getSetupConfigurator()->addJavascript(script, id, ow);}
463
    void addDropdownList(const char *lbl, const char** a, size_t size){getSetupConfigurator()->addDropdownList(lbl, a, size);}    
464
    void addDropdownList(DropdownList &def){ getSetupConfigurator()->addDropdownList(def); }
465
    void addSlider(Slider &def){ getSetupConfigurator()->addSlider(def); }
466
    void addOptionBox(const char* title) { getSetupConfigurator()->addOptionBox(title); }
467
    void setSetupPageLogo(const uint8_t* imageData, size_t imageSize, const char* mimeType = "image/png", bool ow = false) {
468
      getSetupConfigurator()->setSetupPageLogo(imageData, imageSize, mimeType, ow);
469
    }
470
    void setSetupPageLogo(const char* svgText, bool ow = false) {
471
      getSetupConfigurator()->setSetupPageLogo(svgText, ow);
472
    }
473
    // boolean option overload with per-option grouping control
474
    void addOption(const char *lbl, bool val, bool hidden = false, bool grouped = true) {
475
      getSetupConfigurator()->addOption(lbl, val, hidden, grouped);
476
    }
477
 
478
    // specialized bool overload that also accepts a comment string
479
    void addOption(const char *lbl, bool val, const char *comment,
480
                   bool hidden = false, bool grouped = false) {
481
      getSetupConfigurator()->addOption(lbl, val, hidden, grouped);
482
      addComment(lbl, comment);
483
    }
484
 
485
    template <typename T>
486
    void addOption(const char *lbl, T val, double min, double max, double st){
487
      getSetupConfigurator()->addOption(lbl, val, false, min, max, st);
488
    }
489
 
490
    template <typename T>
491
    void addOption(const char *lbl, T val, bool hidden = false,  double min = MIN_F,
492
      double max = MAX_F, double st = 1.0) {
493
      getSetupConfigurator()->addOption(lbl, val, hidden, min, max, st);
494
    }
495
 
496
    // Overload for options with comments (added as metadata to be displayed under the input in the UI)
497
    // disable for bool so the bool-specific overload is used instead
498
    template <typename T, typename std::enable_if<!std::is_same<T, bool>::value, int>::type = 0>
499
    void addOption(const char *lbl, T val, const char* comment) {
500
      getSetupConfigurator()->addOption(lbl, val, false, MIN_F, MAX_F, 1.0);
501
      addComment(lbl, comment);
502
    }
503
 
504
    // Associate a short comment with a configuration element (displayed under the input)
505
    void addComment(const char *lbl, const char *comment) { getSetupConfigurator()->addComment(lbl, comment); }
506
 
507
    template <typename T>
508
    bool getOptionValue(const char *lbl, T &var) { return getSetupConfigurator()->getOptionValue(lbl, var);}
509
    template <typename T>
510
    bool saveOptionValue(const char *lbl, T val) { return getSetupConfigurator()->saveOptionValue(lbl, val);}
511
 
512
    // Update a dropdown definition's selectedIndex from persisted config
513
    bool getDropdownSelection(DropdownList &def) { return getSetupConfigurator()->getDropdownSelection(def); }
514
    // Read slider value back into struct
515
    bool getSliderValue(Slider &def) { return getSetupConfigurator()->getSliderValue(def); }
516
 
517
    void closeSetupConfiguration() {
518
      getSetupConfigurator()->closeConfiguration();
519
    }
520
    /////////////////////////////////////////////////////////////////////////////////////////////////
521
#endif
522
 
523
};
524
 
525
 
526
 
527
 
528
#endif