Skip to content

Commit 3ca1665

Browse files
committed
Add driver for ESPAsyncWebServer, which allows the library to be used on ESP32 without the current connection quirks.
In order to support this, a few more things have been moved behind the output driver abstraction layer. This results in yet fewer lines of code to write. TODO: More testing, before merging.
1 parent 741d22e commit 3ca1665

9 files changed

+183
-81
lines changed

EmbAJAX.h

+35-14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
class EmbAJAXOutputDriverBase;
2929
class EmbAJAXElement;
3030
class EmbAJAXContainerBase;
31+
class EmbAJAXPageBase;
3132

3233
/** @brief Abstract base class for anything shown on an EmbAJAXPage
3334
*
@@ -117,6 +118,13 @@ class EmbAJAXOutputDriverBase {
117118
virtual void printHeader(bool html) = 0;
118119
virtual void printContent(const char *content) = 0;
119120
virtual const char* getArg(const char* name, char* buf, int buflen) = 0;
121+
/** Set up the given page to be served on the given path.
122+
*
123+
* @param change_callback See EmbAJAXPage::handleRequest() for details.
124+
*/
125+
virtual void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) = 0;
126+
/** Insert this hook into loop(). Takes care of the appropriate server calls, if needed. */
127+
virtual void loopHook() = 0;
120128

121129
uint16_t revision() const {
122130
return _revision;
@@ -163,17 +171,6 @@ class EmbAJAXOutputDriverBase {
163171
uint16_t next_revision;
164172
};
165173

166-
// If the user has not #includ'ed a specific output driver implementation, make a good guess, here
167-
#if not defined (EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION)
168-
#if defined (ESP8266)
169-
#include <EmbAJAXOutputDriverESP8266.h>
170-
#elif defined (ESP32)
171-
#include <EmbAJAXOutputDriverESP32.h>
172-
#else
173-
#error No output driver available for this hardware (yet). Please implement your own (it is easy!) and submit a patch.
174-
#endif
175-
#endif
176-
177174
/** Convenience macro to set up an EmbAJAXPage, without counting the number of elements for the template. See EmbAJAXPage::EmbAJAXPage()
178175
* @param name Variable name of the page instance
179176
* @param title HTML Title
@@ -546,7 +543,7 @@ template<size_t NUM> class EmbAJAXHideableContainer : public EmbAJAXElement {
546543

547544
/** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc.
548545
*
549-
* You can insert either the whole group into an ArudJAXPage at once, or - for more flexbile
546+
* You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile
550547
* layouting - retrieve the individual buttons using() button, and insert them into the page
551548
* as independent elements. */
552549
template<size_t NUM> class EmbAJAXRadioGroup : public EmbAJAXContainer<NUM>, public EmbAJAXRadioGroupBase {
@@ -643,13 +640,22 @@ template<size_t NUM> class EmbAJAXOptionSelect : public EmbAJAXOptionSelectBase
643640
const char* _labels[NUM];
644641
};
645642

643+
/** @brief Absrract internal helper class
644+
*
645+
* Needed for internal reasons. Refer to EmbAJAXPage, instead. */
646+
class EmbAJAXPageBase {
647+
public:
648+
virtual void handleRequest(void (*change_callback)()=0) = 0;
649+
virtual void printPage() = 0;
650+
};
651+
646652
/** @brief The main interface class
647653
*
648654
* This is the main interface class. Create a web-page with a list of elements on it, and arrange for
649655
* print() (for page loads) adn handleRequest() (for AJAX calls) to be called on requests. By default,
650656
* both page loads, and AJAX are handled on the same URL, but the first via GET, and the second
651657
* via POST. */
652-
template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM> {
658+
template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM>, public EmbAJAXPageBase {
653659
public:
654660
/** Create a web page.
655661
* @param children list of elements on the page
@@ -659,6 +665,10 @@ template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM> {
659665
_title = title;
660666
_header_add = header_add;
661667
}
668+
/** Duplication of print(), needed for internal reasons. Use print(), instead! */
669+
void printPage() override {
670+
print();
671+
}
662672
/** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever
663673
* there is a GET request to the desired URL. */
664674
void print() const override {
@@ -672,12 +682,23 @@ template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM> {
672682
* response to the change, you should specify this function, and handle the change inside it.
673683
* This way, an update can be sent back to the client, immediately, for a smooth UI experience.
674684
* (Otherwise the client will be updated on the next poll). */
675-
void handleRequest(void (*change_callback)()=0) {
685+
void handleRequest(void (*change_callback)()=0) override {
676686
EmbAJAXBase::handleRequest(EmbAJAXContainer<NUM>::_children, NUM, change_callback);
677687
}
678688
protected:
679689
const char* _title;
680690
const char* _header_add;
681691
};
682692

693+
// If the user has not #includ'ed a specific output driver implementation, make a good guess, here
694+
#if not defined (EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION)
695+
#if defined (ESP8266)
696+
#include <EmbAJAXOutputDriverESP8266.h>
697+
#elif defined (ESP32)
698+
#include <EmbAJAXOutputDriverESP32.h>
699+
#else
700+
#error No output driver available for this hardware (yet). Please implement your own (it is easy!) and submit a patch.
701+
#endif
702+
#endif
703+
683704
#endif

EmbAJAXOutputDriverESP32.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#define EMBAJAXOUTPUTDRIVERESP32_H
2424

2525
#include <WebServer.h>
26-
#define EmbAJAXOutputDriverWebserverClass Webserver
26+
#define EmbAJAXOutputDriverWebServerClass WebServer
2727

2828
#include <WiFi.h> // Makes the examples work cross-platform; not strictly needed
2929

EmbAJAXOutputDriverESPAsync.h

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* EmbAJAX - Simplistic framework for creating and handling displays and controls on a WebPage served by an Arduino (or other small device).
3+
*
4+
* Copyright (C) 2018-2019 Thomas Friedrichsmeier
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Lesser General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or (at
9+
* your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
**/
20+
21+
#ifndef EMBAJAXOUTPUTDRIVERESPASYNC_H
22+
#define EMBAJAXOUTPUTDRIVERESPASYNC_H
23+
24+
#if defined (EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION)
25+
#error Duplicate definition of output driver. Fix your include-directives.
26+
#endif
27+
#define EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION
28+
29+
// For EmbAJAXPage. Important to include after defining EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION
30+
#include "EmbAJAX.h"
31+
32+
#include <ESPAsyncWebServer.h>
33+
34+
#if defined (ESP32)
35+
#define EmbAJAXOutputDriverWebServerClass AsyncWebServer
36+
#else
37+
#define EmbAJAXOutputDriverWebServerClass ESPAsyncWebServer
38+
#endif
39+
40+
/** @brief Output driver implementation. This implementation works with ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer).
41+
*
42+
* To use this class, you will have to include EmbAJAXOutputDriverESPAsync.h *before* EmbAJAX.h
43+
*/
44+
class EmbAJAXOutputDriverESPAsync : public EmbAJAXOutputDriverBase {
45+
public:
46+
/** To register an (ESP)AsyncWebServer with EmbAJAX, simply create a (global) instance of this class.
47+
@param server pointer to the server. The class of this is usually an auto-detected sensible default for the platform, AsyncWebServer on ESP32, ESPAsyncWebServer on ESP8266. */
48+
EmbAJAXOutputDriverESPAsync(EmbAJAXOutputDriverWebServerClass *server) {
49+
EmbAJAXBase::setDriver(this);
50+
_server = server;
51+
_request = 0;
52+
}
53+
void printHeader(bool html) override {
54+
_response = _request->beginResponseStream(html ? "text/html" : "text/json");
55+
}
56+
void printContent(const char *content) override {
57+
_response->print(content);
58+
}
59+
const char* getArg(const char* name, char* buf, int buflen) override {
60+
_request->arg(name).toCharArray (buf, buflen);
61+
return buf;
62+
}
63+
void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) override {
64+
_server->on(path, [=](AsyncWebServerRequest* request) {
65+
_request = request;
66+
_response = 0;
67+
if (_request->method() == HTTP_POST) { // AJAX request
68+
page->handleRequest(change_callback);
69+
} else { // Page load
70+
page->printPage();
71+
}
72+
_request->send(_response);
73+
_request = 0;
74+
});
75+
}
76+
void loopHook() override {};
77+
private:
78+
EmbAJAXOutputDriverWebServerClass *_server;
79+
AsyncWebServerRequest *_request;
80+
AsyncResponseStream *_response;
81+
};
82+
83+
typedef EmbAJAXOutputDriverESPAsync EmbAJAXOutputDriver;
84+
85+
#endif

EmbAJAXOutputDriverGeneric.h

+19-2
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@
3131
#error Please define EmbAJAXOutputDriverWebServerClass or #include appropriate hardware specific driver
3232
#endif
3333

34+
// For EmbAJAXPage. Important to include after defining EMBAJAX_OUTUPUTDRIVER_IMPLEMENTATION
35+
#include "EmbAJAX.h"
36+
3437
/** @brief Output driver implementation. This implementation should work for most arduino web servers with minimal adjustmnets. */
35-
class EmbAJAXOutputDriver : public EmbAJAXOutputDriverBase {
38+
class EmbAJAXOutputDriverGeneric : public EmbAJAXOutputDriverBase {
3639
public:
3740
/** To register an WebServer with EmbAJAX, simply create a (globaL) instance of this class.
3841
@param server pointer to the server. The class of this is usually an auto-detected sensible default for the platform, e.g. ESP8266WebServer on ESP8266. */
39-
EmbAJAXOutputDriver(EmbAJAXOutputDriverWebServerClass *server) {
42+
EmbAJAXOutputDriverGeneric(EmbAJAXOutputDriverWebServerClass *server) {
4043
EmbAJAXBase::setDriver(this);
4144
_server = server;
4245
}
@@ -54,9 +57,23 @@ class EmbAJAXOutputDriver : public EmbAJAXOutputDriverBase {
5457
const char* getArg(const char* name, char* buf, int buflen) override {
5558
_server->arg(name).toCharArray (buf, buflen);
5659
return buf;
60+
}
61+
void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) override {
62+
_server->on(path, [=]() {
63+
if (_server->method() == HTTP_POST) { // AJAX request
64+
page->handleRequest(change_callback);
65+
} else { // Page load
66+
page->printPage();
67+
}
68+
});
69+
}
70+
void loopHook() override {
71+
_server->handleClient();
5772
};
5873
private:
5974
EmbAJAXOutputDriverWebServerClass *_server;
6075
};
6176

77+
typedef EmbAJAXOutputDriverGeneric EmbAJAXOutputDriver;
78+
6279
#endif

README.md

+25-15
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,22 @@ The following additional features may be of interest (supported as of now):
5858
Currently there are output drivers for ESP8266 and ESP32. However, drivers are really easy to add. All that is needed is a very
5959
basic abstraction across some web server calls.
6060

61-
ESP8266 support is solid. ESP32-support is currently plagued by a bug in the ESP32's networking code, that causes incoming connections
62-
to fail under some circumstances (https://github.com/espressif/arduino-esp32/issues/1921).
61+
#### ESP32 quirks and workaround
6362

63+
Unfortunately, ESP32-support is currently plagued by a bug in the ESP32's networking code, that causes incoming connections
64+
to fail under some circumstances (https://github.com/espressif/arduino-esp32/issues/1921). If you are using ESP32, and EmbAJAX
65+
feels sluggish, displays do not update, or the status indicator sometimes turns red, despite good network strength, you are probably
66+
being hit by this bug.
67+
68+
You can work around this by using the ESPAsyncWebServer library (https://github.com/me-no-dev/ESPAsyncWebServer). To do so,
69+
(after installing the lib), you will need to add:
70+
71+
```cpp
72+
#include <AsyncTCP.h>
73+
#include <EmbAJAXOutputDriverESPAsync.h>
74+
```
75+
76+
**above** ```#include <EmbAJAX.h>``` in your EmbAJAX sketches. No further adjustments are needed in the examples, provided, here.
6477

6578
## Example sketch
6679

@@ -89,23 +102,15 @@ MAKE_EmbAJAXPage(page, "EmbAJAXTest", "",
89102
new EmbAJAXStatic("</b></p>")
90103
)
91104

92-
// This is all you need to write for the page handler
93-
void handlePage() {
94-
if(server.method() == HTTP_POST) { // AJAX request
95-
page.handleRequest(updateUI);
96-
} else { // Page load
97-
page.print();
98-
}
99-
}
100-
101105
void setup() {
102106
// Example WIFI setup as an access point. Change this to whatever suits you, best.
103107
WiFi.mode(WIFI_AP);
104108
WiFi.softAPConfig (IPAddress (192,168,4,1), IPAddress (0,0,0,0), IPAddress (255,255,255,0));
105109
WiFi.softAP("EmbAJAXTest", "12345678");
106110

107111
// Tell the server to serve our EmbAJAX test page on root
108-
server.on("/", handlePage);
112+
// installPage() abstracts over the (trivial but not uniform) WebServer-specific instructions to do so
113+
driver.installPage(&page, "/", updateUI);
109114
server.begin();
110115

111116
updateUI(); // initialize display
@@ -123,8 +128,8 @@ void updateUI() {
123128
}
124129

125130
void loop() {
126-
// handle network
127-
server.handleClient();
131+
// handle network. loopHook() simply calls server.handleClient(), in most but not all server implementations.
132+
driver.loopHook();
128133
}
129134

130135
```
@@ -157,13 +162,18 @@ added to the ESP8266 arduino core. So check back soon (or submit your pull reque
157162
## Some implementation notes
158163

159164
Currently, the web servers for embeddables I have dealt with so far, are limited to one client at a time. Therefore, if using
160-
a permanent AJAX connection, all further access would be blocked. Even separate page loads from the same browser. So, instead,
165+
a permanent connection, all further access would be blocked. Even separate page loads from the same browser. So, instead,
161166
we resort to regular polling for updates. An update poll is always included, automatically, when the client sends control
162167
changes to the server, so in most cases, the client would still appear to be refreshed, immediately.
163168

164169
To avoid sending all states of all controls on each request from each client, the framework keeps track of the latest "revision number"
165170
sent to any client. The client pings back its current revision number on each request, so only real changes have to be forwarded.
166171

172+
Concurrent access by an arbitrary number of separate clients is the main reason behind going with AJAX, instead of WebSockets, even if the
173+
latter are often described as more "modern". Note that the purpoted drawback to AJAX - latency - can easily be circumventented for most use
174+
cases, as desribed, above. Still, it would be relatively easy to generalize the framework to also allow a WebSocket-connection. I'm not
175+
doing this, ATM, for fear of adding unneccessary complexity for little or no practical gain.
176+
167177
You may have noted that the framework avoids the use of the String class, even though that would make some things easier. The reason
168178
for this design choice is that the overhead of using char*, here, in a sketch that may be using String, already, is low. However, if this
169179
framework were to rely on String, while nothing else in the sketch uses String, that would incur a significant overhead. Further, it should

examples/Blink/Blink.ino

+4-12
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,15 @@ MAKE_EmbAJAXPage(page, "EmbAJAX example - Blink", "",
3232
new EmbAJAXStatic("<i>FAST</i></p>")
3333
)
3434

35-
// This is all you need to write for the page handler
36-
void handlePage() {
37-
if(server.method() == HTTP_POST) { // AJAX request
38-
page.handleRequest(updateUI);
39-
} else { // Page load
40-
page.print();
41-
}
42-
}
43-
4435
void setup() {
4536
// Example WIFI setup as an access point. Change this to whatever suits you, best.
4637
WiFi.mode(WIFI_AP);
4738
WiFi.softAPConfig (IPAddress (192,168,4,1), IPAddress (0,0,0,0), IPAddress (255,255,255,0));
4839
WiFi.softAP("EmbAJAXTest", "12345678");
4940

5041
// Tell the server to serve our EmbAJAX test page on root
51-
server.on("/", handlePage);
42+
// installPage() abstracts over the (trivial but not uniform) WebServer-specific instructions to do so
43+
driver.installPage(&page, "/", updateUI);
5244
server.begin();
5345

5446
pinMode(LEDPIN, OUTPUT);
@@ -61,8 +53,8 @@ void updateUI() {
6153
}
6254

6355
void loop() {
64-
// handle network
65-
server.handleClient();
56+
// handle network. loopHook() simply calls server.handleClient(), in most but not all server implementations.
57+
driver.loopHook();
6658

6759
// And these lines are all you have to write for the logic: Access the elements as if they were plain
6860
// local controls

examples/Inputs/Inputs.ino

+4-12
Original file line numberDiff line numberDiff line change
@@ -109,23 +109,15 @@ MAKE_EmbAJAXPage(page, "EmbAJAX example - Inputs", "",
109109
new EmbAJAXStatic("</b></td></tr></table>")
110110
)
111111

112-
// This is all you need to write for the page handler
113-
void handlePage() {
114-
if(server.method() == HTTP_POST) { // AJAX request
115-
page.handleRequest(updateUI);
116-
} else { // Page load
117-
page.print();
118-
}
119-
}
120-
121112
void setup() {
122113
// Example WIFI setup as an access point. Change this to whatever suits you, best.
123114
WiFi.mode(WIFI_AP);
124115
WiFi.softAPConfig (IPAddress (192,168,4,1), IPAddress (0,0,0,0), IPAddress (255,255,255,0));
125116
WiFi.softAP("EmbAJAXTest", "12345678");
126117

127118
// Tell the server to serve our EmbAJAX test page on root
128-
server.on("/", handlePage);
119+
// installPage() abstracts over the (trivial but not uniform) WebServer-specific instructions to do so
120+
driver.installPage(&page, "/", updateUI);
129121
server.begin();
130122

131123
valtext.setPlaceholder("192.168.1.1");
@@ -149,6 +141,6 @@ void updateUI() {
149141
}
150142

151143
void loop() {
152-
// handle network
153-
server.handleClient();
144+
// handle network. loopHook() simply calls server.handleClient(), in most but not all server implementations.
145+
driver.loopHook();
154146
}

0 commit comments

Comments
 (0)