From bc099437c80182aeb8cb6c6c9db4dae931b763ca Mon Sep 17 00:00:00 2001 From: ewaldc Date: Sun, 12 Jan 2020 12:13:25 +0100 Subject: [PATCH 01/11] Server Sent Events example - issue #7008 Illustrates the use of SSE using ESP8266WebServer --- .../ServerSentEvents/ServerSentEvents.ino | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino new file mode 100644 index 0000000000..4b78c4e5da --- /dev/null +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -0,0 +1,191 @@ +/* Simple Server Sent Event (aka EventSource) demo + Run demo as follows: + 1. set SSID, password and ports, compile and run program + you should see (random) updates of sensors A and B + + 2. on the client, register it for the event bus using a REST API call: curl -sS "http://:/rest/events/subscribe" + on both server and client, you should now see that your client is registered + the server sends back the location of the event bus (channel) to the client: + subscription for client IP : event bus location: http://:/rest/events + + you will also see that the sensors are ready to broadcast state changes, but the client is not yet listening: + SSEBroadcastState - client > registered but not listening + + 3. on the client, start listening for events with: curl -sS "http://:/rest/events" + if all is well, the following is being displayed on the ESP console + SSEHandler - registered client with IP is listening... + broadcast status change to client IP > for sensor[A|B] with new state > + every minute you will see on the ESP: SSEKeepAlive - client is still connected + + on the client, you should see the SSE messages coming in: + event: event + data: { "TYPE":"KEEP-ALIVE" } + event: event + data: { "TYPE":"STATE", "sensorB": {"state" : 12408, "prevState": 13502} } + event: event + data: { "TYPE":"STATE", "sensorA": {"state" : 17664, "prevState": 49362} } + + 4. on the client, stop listening by hitting control-C + on the ESP, after maximum one minute, the following message is displayed: SSEKeepAlive - client no longer connected, remove subscription + if you start listening again after the time expired, the "/rest/events" handle becomes stale and "Handle not found" is returned + you can also try to start listening again before the KeepAliver timer expires or simply register your client again +*/ + +extern "C" { +#include "c_types.h" +} +#include +#include +#include +#include +#include + +#ifndef STASSID +//#define STASSID "your-ssid" +//#define STAPSK "your-password" +#define STASSID "EThomeZX" +#define STAPSK "superd0me;0rca" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; +const unsigned int port = 80; + +ESP8266WebServer server(port); +ESP8266WebServer SSEserver(port + 1); + +struct SSESubscription { + uint32_t clientIP; + WiFiClient client; + Ticker keepAliveTimer; +} subscription; // in this simplified example, only one SSE client subscription allowed + +unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors +Ticker update, updateA, updateB; + +void notFound(ESP8266WebServer &server) { + Serial.println(F("Handle not found")); + String message = "Handle Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} +void handleNotFound() { notFound(server); } +void handleSSENotFound() { notFound(SSEserver); } + +void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, unsigned short sensorValue) { + if (!subscription.clientIP) return; + WiFiClient client = subscription.client; + if (client.connected()) { + Serial.printf_P(PSTR("broadcast status change to client IP %s for %s with new state %d\n"), + IPAddress(subscription.clientIP).toString().c_str(), sensorName, sensorValue); + client.printf_P(PSTR("event: event\ndata: { \"TYPE\":\"STATE\", \"%s\": {\"state\" : %d, \"prevState\": %d} }\n"), + sensorName, sensorValue, prevSensorValue); + } else + Serial.printf_P(PSTR("SSEBroadcastState - client %s registered but not listening\n"), IPAddress(subscription.clientIP).toString().c_str()); +} + +void SSEKeepAlive(SSESubscription *s) { + SSESubscription &subscription = *s; + if (!subscription.clientIP) return; + WiFiClient client = subscription.client; + if (client.connected()) { + Serial.println(F("SSEKeepAlive - client is still connected")); + client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }")); + } else { + Serial.println(F("SSEKeepAlive - client no longer connected, remove subscription")); + subscription.keepAliveTimer.detach(); + client.flush(); + client.stop(); + subscription.clientIP = 0; + } +} + +// SSEHandler handles the client connection to the event bus (client event listener) +// every 60 seconds it sends a keep alive event via Ticker +void SSEHandler(SSESubscription *s) { + WiFiClient client = SSEserver.client(); + SSESubscription &subscription = *s; + if (subscription.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client + Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), SSEserver.client().remoteIP().toString().c_str()); + return notFound(SSEserver); + } + //client.setNoDelay(true); // Any of these will crash the ESP (Soft WDT reset) + //client.setSync(true); + Serial.printf_P(PSTR("SSEHandler - registered client with IP %s is listening...\n"), IPAddress(subscription.clientIP).toString().c_str()); + subscription.client = client; // capture SSE server client connection + SSEserver.setContentLength(CONTENT_LENGTH_UNKNOWN); // the payload can go on forever + subscription.keepAliveTimer.attach(30.0, std::bind(SSEKeepAlive, s)); // Refresh time every 30s for demo +} + +// Simulate sensors +void updateSensor(const char* name, unsigned short *value) { + unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor + unsigned short val = *value; + Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); + if (val != newVal) SSEBroadcastState(name, newVal, val); // only broadcast if state is different + *value = newVal; + update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A +} + +void handleSubscribe() { + IPAddress clientIP = server.client().remoteIP(); // get IP address of client + String SSEurl = F("http://"); + SSEurl += WiFi.localIP().toString(); + SSEurl += F(":"); + SSEurl += port + 1; + size_t offset = SSEurl.length(); + SSEurl += F("/rest/events"); + + if (subscription.clientIP != (uint32_t) clientIP) { // Allocate new subscription + subscription = {(uint32_t) clientIP, SSEserver.client(), Ticker()}; + SSEserver.on(SSEurl.substring(offset), std::bind(SSEHandler, &subscription)); + } else + Serial.print(F("reusing ")); + Serial.printf_P(PSTR("subscription for client IP %s: event bus location: %s\n"), clientIP.toString().c_str(), SSEurl.c_str()); + server.send_P(200, "text/plain", SSEurl.c_str()); +} + +void startServers() { + server.on(F("/rest/events/subscribe"), handleSubscribe); + server.onNotFound(handleNotFound); + server.begin(); + Serial.println("HTTP server started"); + SSEserver.onNotFound(handleSSENotFound); + //SSEserver.keepCurrentClient(true); // Looks like it is not needed + SSEserver.begin(); + Serial.println("HTTP SSE EventSource server started"); +} + +void setup(void) { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + while (WiFi.status() != WL_CONNECTED) { // Wait for connection + delay(500); + Serial.print("."); + } + Serial.printf_P(PSTR("\nConnected to %s with IP address: %s\n"), ssid, WiFi.localIP().toString().c_str()); + if (MDNS.begin("esp8266")) + Serial.println("MDNS responder started"); + + startServers(); // start web and SSE servers + updateSensor("sensorA", &sensorA); + updateSensor("sensorB", &sensorB); +} + +void loop(void) { + server.handleClient(); + SSEserver.handleClient(); + MDNS.update(); + yield(); +} From 6d453d806cb81df8bb2cfc8cadf5953fa18565dd Mon Sep 17 00:00:00 2001 From: ewaldc Date: Sun, 12 Jan 2020 12:18:03 +0100 Subject: [PATCH 02/11] Update ServerSentEvents.ino --- .../examples/ServerSentEvents/ServerSentEvents.ino | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 4b78c4e5da..60b11781b2 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -41,10 +41,8 @@ extern "C" { #include #ifndef STASSID -//#define STASSID "your-ssid" -//#define STAPSK "your-password" -#define STASSID "EThomeZX" -#define STAPSK "superd0me;0rca" +#define STASSID "your-ssid" +#define STAPSK "your-password" #endif const char* ssid = STASSID; From 6f926512728b162b85fe0c395c17b850818e4985 Mon Sep 17 00:00:00 2001 From: ewaldc Date: Mon, 13 Jan 2020 12:48:16 +0100 Subject: [PATCH 03/11] Create ServerSentEventsMultiClient.ino --- .../ServerSentEventsMultiClient.ino | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino diff --git a/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino b/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino new file mode 100644 index 0000000000..b019829734 --- /dev/null +++ b/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino @@ -0,0 +1,213 @@ +/* Multi-client Server Sent Event (aka EventSource) demo + Run demo as follows: + 1. set SSID, password and ports, compile and run program + you should see (random) updates of sensors A and B + + 2. on the client(s), register it for the event bus using a REST API call: curl -sS "http://:/rest/events/subscribe" + on both server and client, you should now see that your client is registered + the server sends back the location of the event bus (channel) to the client: + subscription for client IP : event bus location: http://:/rest/events + + you will also see that the sensors are ready to broadcast state changes, but the client is not yet listening: + SSEBroadcastState - client > registered but not listening + + 3. on the client(s), start listening for events with: curl -sS "http://:/rest/events" + if all is well, the following is being displayed on the ESP console + SSEHandler - registered client with IP is listening... + broadcast status change to client IP > for sensor[A|B] with new state > + every minute you will see on the ESP: SSEKeepAlive - client is still connected + + on the client, you should see the SSE messages coming in: + event: event + data: { "TYPE":"KEEP-ALIVE" } + event: event + data: { "TYPE":"STATE", "sensorB": {"state" : 12408, "prevState": 13502} } + event: event + data: { "TYPE":"STATE", "sensorA": {"state" : 17664, "prevState": 49362} } + + 4. on the client, stop listening by hitting control-C + on the ESP, after maximum one minute, the following message is displayed: SSEKeepAlive - client no longer connected, remove subscription + if you start listening again after the time expired, the "/rest/events" handle becomes stale and "Handle not found" is returned + you can also try to start listening again before the KeepAliver timer expires or simply register your client again +*/ + +extern "C" { +#include "c_types.h" +} +#include +#include +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; +const unsigned int port = 80; + +ESP8266WebServer server(port); +ESP8266WebServer SSEserver(port + 1); + +#define MAX_CHANNELS 2 // in this simplified example, only two SSE clients subscription allowed +struct SSESubscription { + uint32_t clientIP; + WiFiClient client; + Ticker keepAliveTimer; +} subscription[MAX_CHANNELS]; + +unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors +Ticker update, updateA, updateB; + +void notFound(ESP8266WebServer &server) { + Serial.println(F("Handle not found")); + String message = "Handle Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} +void handleNotFound() { notFound(server); } + +void SSEKeepAlive(SSESubscription *s) { + SSESubscription &subscription = *s; + if (!subscription.clientIP) return; + WiFiClient client = subscription.client; + if (client.connected()) { + Serial.println(F("SSEKeepAlive - client is still connected")); + client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }")); + } else { + Serial.println(F("SSEKeepAlive - client no longer connected, remove subscription")); + subscription.keepAliveTimer.detach(); + client.flush(); + client.stop(); + subscription.clientIP = 0; + } +} + +// SSEHandler handles the client connection to the event bus (client event listener) +// every 60 seconds it sends a keep alive event via Ticker +void SSEHandler(SSESubscription *s) { + WiFiClient client = SSEserver.client(); + SSESubscription &subscription = *s; + if (subscription.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client + Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), SSEserver.client().remoteIP().toString().c_str()); + return notFound(SSEserver); + } + //client.setNoDelay(true); // Any of these will crash the ESP + //client.setSync(true); + Serial.printf_P(PSTR("SSEHandler - registered client with IP %s is listening...\n"), IPAddress(subscription.clientIP).toString().c_str()); + subscription.client = client; // capture SSE server client connection + SSEserver.setContentLength(CONTENT_LENGTH_UNKNOWN); // the payload can go on forever + subscription.keepAliveTimer.attach(30.0, std::bind(SSEKeepAlive, s)); // Refresh time every 30s for demo +} + +void handleSSENotFound() { + const char *uri = SSEserver.uri().c_str(); + if (strncmp_P(uri, PSTR("/rest/events/"), sizeof("/rest/events"))) return notFound(SSEserver); + uri += sizeof("/rest/events"); + Serial.printf_P(PSTR("WebServer missed .on registration for channel %d\n"), atoi(uri)); + SSEHandler(&subscription[atoi(uri)]); +}; + +void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, unsigned short sensorValue) { + for (uint8_t i = 0; i < MAX_CHANNELS; i++) { + if (!(subscription[i].clientIP)) continue; + WiFiClient client = subscription[i].client; + String IPaddrstr = IPAddress(subscription[i].clientIP).toString(); + if (client.connected()) { + Serial.printf_P(PSTR("broadcast status change to client IP %s on channel %d for %s with new state %d\n"), + IPaddrstr.c_str(), i, sensorName, sensorValue); + client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n"), + sensorName, sensorValue, prevSensorValue); + } else + Serial.printf_P(PSTR("SSEBroadcastState - client %s registered on channel %d but not listening\n"), IPaddrstr.c_str(), i); + } +} + +// Simulate sensors +void updateSensor(const char* name, unsigned short *value) { + unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor + unsigned short val = *value; + Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); + if (val != newVal) SSEBroadcastState(name, newVal, val); // only broadcast if state is different + *value = newVal; + update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A +} + +void handleSubscribe() { + IPAddress clientIP = server.client().remoteIP(); // get IP address of client + String SSEurl = F("http://"); + SSEurl += WiFi.localIP().toString(); + SSEurl += F(":"); + SSEurl += port + 1; + size_t offset = SSEurl.length(); + SSEurl += F("/rest/events/"); + + uint8_t channel = -1; + for (uint8_t i = 0; i < MAX_CHANNELS; i++) { + if (subscription[i].clientIP == (uint32_t) clientIP) { + Serial.println(F("ERROR: only one channel per IP address")); + if (channel >= 0) + subscription[channel].clientIP = 0; // invalide previously allocated channel + return notFound(SSEserver); + } else if (!subscription[i].clientIP) { // Free slot, allocate new subscription + channel = i; + subscription[i] = {(uint32_t) clientIP, SSEserver.client(), Ticker()}; + SSEurl += channel; + SSEurl += F("\n"); + Serial.printf_P(PSTR("Allocated channel %d, on uri %s\n"), channel, SSEurl.substring(offset).c_str()); + SSEserver.on(SSEurl.substring(offset), std::bind(SSEHandler, &(subscription[i]))); + break; + } + } + Serial.printf_P(PSTR("subscription for client IP %s: event bus location: %s\n"), clientIP.toString().c_str(), SSEurl.c_str()); + server.send_P(200, "text/plain", SSEurl.c_str()); +} + +void startServers() { + server.on(F("/rest/events/subscribe"), handleSubscribe); + server.onNotFound(handleNotFound); + server.begin(); + Serial.println("HTTP server started"); + SSEserver.onNotFound(handleSSENotFound); + //SSEserver.keepCurrentClient(true); // Looks like it is not needed + SSEserver.begin(); + Serial.println("HTTP SSE EventSource server started"); +} + +void setup(void) { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + while (WiFi.status() != WL_CONNECTED) { // Wait for connection + delay(500); + Serial.print("."); + } + Serial.printf_P(PSTR("\nConnected to %s with IP address: %s\n"), ssid, WiFi.localIP().toString().c_str()); + if (MDNS.begin("esp8266")) + Serial.println("MDNS responder started"); + + //for (uint8_t i = 0; i Date: Mon, 20 Jan 2020 15:21:49 +0100 Subject: [PATCH 04/11] sync --- libraries/ESP8266SdFat | 2 +- .../ServerSentEvents/ServerSentEvents.ino | 165 ++++++++------ .../ServerSentEventsMultiClient.ino | 213 ------------------ libraries/LittleFS/lib/littlefs | 2 +- libraries/SoftwareSerial | 2 +- tools/esptool | 2 +- tools/sdk/lwip2/builder | 2 +- tools/sdk/ssl/bearssl | 2 +- 8 files changed, 96 insertions(+), 294 deletions(-) delete mode 100644 libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino diff --git a/libraries/ESP8266SdFat b/libraries/ESP8266SdFat index b240d2231a..af4ed0c5ec 160000 --- a/libraries/ESP8266SdFat +++ b/libraries/ESP8266SdFat @@ -1 +1 @@ -Subproject commit b240d2231a117bbd89b79902eb54cae948ee2f42 +Subproject commit af4ed0c5ec3084cb3883df51ec2052791ca2bff2 diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 60b11781b2..4d8e67bbc4 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -1,17 +1,17 @@ -/* Simple Server Sent Event (aka EventSource) demo +/* Multi-client Server Sent Event (aka EventSource) demo Run demo as follows: 1. set SSID, password and ports, compile and run program you should see (random) updates of sensors A and B - 2. on the client, register it for the event bus using a REST API call: curl -sS "http://:/rest/events/subscribe" + 2. on the client(s), register it for the event bus using a REST API call: curl -sS "http://:/rest/events/subscribe" on both server and client, you should now see that your client is registered the server sends back the location of the event bus (channel) to the client: - subscription for client IP : event bus location: http://:/rest/events + subscription for client IP : event bus location: http://:/rest/events/ you will also see that the sensors are ready to broadcast state changes, but the client is not yet listening: SSEBroadcastState - client > registered but not listening - 3. on the client, start listening for events with: curl -sS "http://:/rest/events" + 3. on the client(s), start listening for events with: curl -sS "http://:/rest/events/" if all is well, the following is being displayed on the ESP console SSEHandler - registered client with IP is listening... broadcast status change to client IP > for sensor[A|B] with new state > @@ -41,8 +41,10 @@ extern "C" { #include #ifndef STASSID -#define STASSID "your-ssid" -#define STAPSK "your-password" +//#define STASSID "your-ssid" +//#define STAPSK "your-password" +#define STASSID "EThomeZX" +#define STAPSK "superd0me;0rca" #endif const char* ssid = STASSID; @@ -50,18 +52,19 @@ const char* password = STAPSK; const unsigned int port = 80; ESP8266WebServer server(port); -ESP8266WebServer SSEserver(port + 1); +#define SSE_MAX_CHANNELS 8 // in this simplified example, only eight SSE clients subscription allowed struct SSESubscription { uint32_t clientIP; - WiFiClient client; + WiFiClient client; Ticker keepAliveTimer; -} subscription; // in this simplified example, only one SSE client subscription allowed +} subscription[SSE_MAX_CHANNELS]; +uint8_t subscriptionCount = 0; unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors -Ticker update, updateA, updateB; +Ticker update; -void notFound(ESP8266WebServer &server) { +void handleNotFound() { Serial.println(F("Handle not found")); String message = "Handle Not Found\n\n"; message += "URI: "; @@ -76,91 +79,104 @@ void notFound(ESP8266WebServer &server) { } server.send(404, "text/plain", message); } -void handleNotFound() { notFound(server); } -void handleSSENotFound() { notFound(SSEserver); } -void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, unsigned short sensorValue) { - if (!subscription.clientIP) return; - WiFiClient client = subscription.client; - if (client.connected()) { - Serial.printf_P(PSTR("broadcast status change to client IP %s for %s with new state %d\n"), - IPAddress(subscription.clientIP).toString().c_str(), sensorName, sensorValue); - client.printf_P(PSTR("event: event\ndata: { \"TYPE\":\"STATE\", \"%s\": {\"state\" : %d, \"prevState\": %d} }\n"), - sensorName, sensorValue, prevSensorValue); - } else - Serial.printf_P(PSTR("SSEBroadcastState - client %s registered but not listening\n"), IPAddress(subscription.clientIP).toString().c_str()); -} - -void SSEKeepAlive(SSESubscription *s) { - SSESubscription &subscription = *s; - if (!subscription.clientIP) return; - WiFiClient client = subscription.client; - if (client.connected()) { - Serial.println(F("SSEKeepAlive - client is still connected")); - client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }")); - } else { - Serial.println(F("SSEKeepAlive - client no longer connected, remove subscription")); - subscription.keepAliveTimer.detach(); - client.flush(); - client.stop(); - subscription.clientIP = 0; +void SSEKeepAlive() { + for (uint8_t i = 0; i < SSE_MAX_CHANNELS; i++) { + if (!(subscription[i].clientIP)) continue; + WiFiClient client = subscription[i].client; + if (client.connected()) { + Serial.printf_P(PSTR("SSEKeepAlive - client is still listening on channel %d\n")); + client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }\n")); // Extra newline required by SSE standard + } else { + Serial.printf_P(PSTR("SSEKeepAlive - client not listening on channel %d, remove subscription\n")); + subscription[i].keepAliveTimer.detach(); + client.flush(); + client.stop(); + subscription[i].clientIP = 0; + } } } // SSEHandler handles the client connection to the event bus (client event listener) // every 60 seconds it sends a keep alive event via Ticker -void SSEHandler(SSESubscription *s) { - WiFiClient client = SSEserver.client(); - SSESubscription &subscription = *s; - if (subscription.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client - Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), SSEserver.client().remoteIP().toString().c_str()); - return notFound(SSEserver); +void SSEHandler(uint8_t channel) { + WiFiClient client = server.client(); + SSESubscription &s = subscription[channel]; + if (s.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client + Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), server.client().remoteIP().toString().c_str()); + return handleNotFound(); } - //client.setNoDelay(true); // Any of these will crash the ESP (Soft WDT reset) - //client.setSync(true); - Serial.printf_P(PSTR("SSEHandler - registered client with IP %s is listening...\n"), IPAddress(subscription.clientIP).toString().c_str()); - subscription.client = client; // capture SSE server client connection - SSEserver.setContentLength(CONTENT_LENGTH_UNKNOWN); // the payload can go on forever - subscription.keepAliveTimer.attach(30.0, std::bind(SSEKeepAlive, s)); // Refresh time every 30s for demo + client.setNoDelay(true); + client.setSync(true); + Serial.printf_P(PSTR("SSEHandler - registered client with IP %s is listening\n"), IPAddress(s.clientIP).toString().c_str()); + s.client = client; // capture SSE server client connection + server.setContentLength(CONTENT_LENGTH_UNKNOWN); // the payload can go on forever + server.sendContent_P(PSTR("HTTP/1.1 200 OK\nContent-Type: text/event-stream;\nConnection: keep-alive\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n\n")); + s.keepAliveTimer.attach_scheduled(30.0, SSEKeepAlive); // Refresh time every 30s for demo +} + +void handleAll() { + const char *uri = server.uri().c_str(); + if (strncmp_P(uri, PSTR("/rest/events/"), sizeof("/rest/events"))) return handleNotFound(); + uri += sizeof("/rest/events"); + unsigned int channel = atoi(uri); + if (channel < SSE_MAX_CHANNELS) + return SSEHandler(channel); + handleNotFound(); +}; + +void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, unsigned short sensorValue) { + for (uint8_t i = 0; i < SSE_MAX_CHANNELS; i++) { + if (!(subscription[i].clientIP)) continue; + WiFiClient client = subscription[i].client; + String IPaddrstr = IPAddress(subscription[i].clientIP).toString(); + if (client.connected()) { + Serial.printf_P(PSTR("broadcast status change to client IP %s on channel %d for %s with new state %d\n"), + IPaddrstr.c_str(), i, sensorName, sensorValue); + client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n\n"), + sensorName, sensorValue, prevSensorValue); + } else + Serial.printf_P(PSTR("SSEBroadcastState - client %s registered on channel %d but not listening\n"), IPaddrstr.c_str(), i); + } } // Simulate sensors void updateSensor(const char* name, unsigned short *value) { unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor unsigned short val = *value; - Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); + Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); if (val != newVal) SSEBroadcastState(name, newVal, val); // only broadcast if state is different *value = newVal; update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A } void handleSubscribe() { - IPAddress clientIP = server.client().remoteIP(); // get IP address of client - String SSEurl = F("http://"); - SSEurl += WiFi.localIP().toString(); - SSEurl += F(":"); - SSEurl += port + 1; - size_t offset = SSEurl.length(); - SSEurl += F("/rest/events"); - - if (subscription.clientIP != (uint32_t) clientIP) { // Allocate new subscription - subscription = {(uint32_t) clientIP, SSEserver.client(), Ticker()}; - SSEserver.on(SSEurl.substring(offset), std::bind(SSEHandler, &subscription)); - } else - Serial.print(F("reusing ")); - Serial.printf_P(PSTR("subscription for client IP %s: event bus location: %s\n"), clientIP.toString().c_str(), SSEurl.c_str()); + uint8_t channel; + IPAddress clientIP = server.client().remoteIP(); // get IP address of client + String SSEurl = F("http://"); + SSEurl += WiFi.localIP().toString(); + SSEurl += F(":"); + SSEurl += port; + size_t offset = SSEurl.length(); + SSEurl += F("/rest/events/"); + + if (subscriptionCount == SSE_MAX_CHANNELS - 1) return handleNotFound(); // We ran out of channels + ++subscriptionCount; + for (channel = 0; channel < SSE_MAX_CHANNELS; channel++) // Find first free slot + if (!subscription[channel].clientIP) break; + subscription[channel] = {(uint32_t) clientIP, server.client()}; + SSEurl += channel; + Serial.printf_P(PSTR("Allocated channel %d, on uri %s\n"), channel, SSEurl.substring(offset).c_str()); + //server.on(SSEurl.substring(offset), std::bind(SSEHandler, &(subscription[channel]))); + Serial.printf_P(PSTR("subscription for client IP %s: event bus location: %s\n"), clientIP.toString().c_str(), SSEurl.c_str()); server.send_P(200, "text/plain", SSEurl.c_str()); } void startServers() { - server.on(F("/rest/events/subscribe"), handleSubscribe); - server.onNotFound(handleNotFound); - server.begin(); - Serial.println("HTTP server started"); - SSEserver.onNotFound(handleSSENotFound); - //SSEserver.keepCurrentClient(true); // Looks like it is not needed - SSEserver.begin(); - Serial.println("HTTP SSE EventSource server started"); + server.on(F("/rest/events/subscribe"), handleSubscribe); + server.onNotFound(handleAll); + server.begin(); + Serial.println("HTTP server and SSE EventSource started"); } void setup(void) { @@ -183,7 +199,6 @@ void setup(void) { void loop(void) { server.handleClient(); - SSEserver.handleClient(); MDNS.update(); yield(); -} +} \ No newline at end of file diff --git a/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino b/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino deleted file mode 100644 index b019829734..0000000000 --- a/libraries/ESP8266WebServer/examples/ServerSentEventsMultiClient/ServerSentEventsMultiClient.ino +++ /dev/null @@ -1,213 +0,0 @@ -/* Multi-client Server Sent Event (aka EventSource) demo - Run demo as follows: - 1. set SSID, password and ports, compile and run program - you should see (random) updates of sensors A and B - - 2. on the client(s), register it for the event bus using a REST API call: curl -sS "http://:/rest/events/subscribe" - on both server and client, you should now see that your client is registered - the server sends back the location of the event bus (channel) to the client: - subscription for client IP : event bus location: http://:/rest/events - - you will also see that the sensors are ready to broadcast state changes, but the client is not yet listening: - SSEBroadcastState - client > registered but not listening - - 3. on the client(s), start listening for events with: curl -sS "http://:/rest/events" - if all is well, the following is being displayed on the ESP console - SSEHandler - registered client with IP is listening... - broadcast status change to client IP > for sensor[A|B] with new state > - every minute you will see on the ESP: SSEKeepAlive - client is still connected - - on the client, you should see the SSE messages coming in: - event: event - data: { "TYPE":"KEEP-ALIVE" } - event: event - data: { "TYPE":"STATE", "sensorB": {"state" : 12408, "prevState": 13502} } - event: event - data: { "TYPE":"STATE", "sensorA": {"state" : 17664, "prevState": 49362} } - - 4. on the client, stop listening by hitting control-C - on the ESP, after maximum one minute, the following message is displayed: SSEKeepAlive - client no longer connected, remove subscription - if you start listening again after the time expired, the "/rest/events" handle becomes stale and "Handle not found" is returned - you can also try to start listening again before the KeepAliver timer expires or simply register your client again -*/ - -extern "C" { -#include "c_types.h" -} -#include -#include -#include -#include -#include - -#ifndef STASSID -#define STASSID "your-ssid" -#define STAPSK "your-password" -#endif - -const char* ssid = STASSID; -const char* password = STAPSK; -const unsigned int port = 80; - -ESP8266WebServer server(port); -ESP8266WebServer SSEserver(port + 1); - -#define MAX_CHANNELS 2 // in this simplified example, only two SSE clients subscription allowed -struct SSESubscription { - uint32_t clientIP; - WiFiClient client; - Ticker keepAliveTimer; -} subscription[MAX_CHANNELS]; - -unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors -Ticker update, updateA, updateB; - -void notFound(ESP8266WebServer &server) { - Serial.println(F("Handle not found")); - String message = "Handle Not Found\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for (uint8_t i = 0; i < server.args(); i++) { - message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; - } - server.send(404, "text/plain", message); -} -void handleNotFound() { notFound(server); } - -void SSEKeepAlive(SSESubscription *s) { - SSESubscription &subscription = *s; - if (!subscription.clientIP) return; - WiFiClient client = subscription.client; - if (client.connected()) { - Serial.println(F("SSEKeepAlive - client is still connected")); - client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }")); - } else { - Serial.println(F("SSEKeepAlive - client no longer connected, remove subscription")); - subscription.keepAliveTimer.detach(); - client.flush(); - client.stop(); - subscription.clientIP = 0; - } -} - -// SSEHandler handles the client connection to the event bus (client event listener) -// every 60 seconds it sends a keep alive event via Ticker -void SSEHandler(SSESubscription *s) { - WiFiClient client = SSEserver.client(); - SSESubscription &subscription = *s; - if (subscription.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client - Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), SSEserver.client().remoteIP().toString().c_str()); - return notFound(SSEserver); - } - //client.setNoDelay(true); // Any of these will crash the ESP - //client.setSync(true); - Serial.printf_P(PSTR("SSEHandler - registered client with IP %s is listening...\n"), IPAddress(subscription.clientIP).toString().c_str()); - subscription.client = client; // capture SSE server client connection - SSEserver.setContentLength(CONTENT_LENGTH_UNKNOWN); // the payload can go on forever - subscription.keepAliveTimer.attach(30.0, std::bind(SSEKeepAlive, s)); // Refresh time every 30s for demo -} - -void handleSSENotFound() { - const char *uri = SSEserver.uri().c_str(); - if (strncmp_P(uri, PSTR("/rest/events/"), sizeof("/rest/events"))) return notFound(SSEserver); - uri += sizeof("/rest/events"); - Serial.printf_P(PSTR("WebServer missed .on registration for channel %d\n"), atoi(uri)); - SSEHandler(&subscription[atoi(uri)]); -}; - -void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, unsigned short sensorValue) { - for (uint8_t i = 0; i < MAX_CHANNELS; i++) { - if (!(subscription[i].clientIP)) continue; - WiFiClient client = subscription[i].client; - String IPaddrstr = IPAddress(subscription[i].clientIP).toString(); - if (client.connected()) { - Serial.printf_P(PSTR("broadcast status change to client IP %s on channel %d for %s with new state %d\n"), - IPaddrstr.c_str(), i, sensorName, sensorValue); - client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n"), - sensorName, sensorValue, prevSensorValue); - } else - Serial.printf_P(PSTR("SSEBroadcastState - client %s registered on channel %d but not listening\n"), IPaddrstr.c_str(), i); - } -} - -// Simulate sensors -void updateSensor(const char* name, unsigned short *value) { - unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor - unsigned short val = *value; - Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); - if (val != newVal) SSEBroadcastState(name, newVal, val); // only broadcast if state is different - *value = newVal; - update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A -} - -void handleSubscribe() { - IPAddress clientIP = server.client().remoteIP(); // get IP address of client - String SSEurl = F("http://"); - SSEurl += WiFi.localIP().toString(); - SSEurl += F(":"); - SSEurl += port + 1; - size_t offset = SSEurl.length(); - SSEurl += F("/rest/events/"); - - uint8_t channel = -1; - for (uint8_t i = 0; i < MAX_CHANNELS; i++) { - if (subscription[i].clientIP == (uint32_t) clientIP) { - Serial.println(F("ERROR: only one channel per IP address")); - if (channel >= 0) - subscription[channel].clientIP = 0; // invalide previously allocated channel - return notFound(SSEserver); - } else if (!subscription[i].clientIP) { // Free slot, allocate new subscription - channel = i; - subscription[i] = {(uint32_t) clientIP, SSEserver.client(), Ticker()}; - SSEurl += channel; - SSEurl += F("\n"); - Serial.printf_P(PSTR("Allocated channel %d, on uri %s\n"), channel, SSEurl.substring(offset).c_str()); - SSEserver.on(SSEurl.substring(offset), std::bind(SSEHandler, &(subscription[i]))); - break; - } - } - Serial.printf_P(PSTR("subscription for client IP %s: event bus location: %s\n"), clientIP.toString().c_str(), SSEurl.c_str()); - server.send_P(200, "text/plain", SSEurl.c_str()); -} - -void startServers() { - server.on(F("/rest/events/subscribe"), handleSubscribe); - server.onNotFound(handleNotFound); - server.begin(); - Serial.println("HTTP server started"); - SSEserver.onNotFound(handleSSENotFound); - //SSEserver.keepCurrentClient(true); // Looks like it is not needed - SSEserver.begin(); - Serial.println("HTTP SSE EventSource server started"); -} - -void setup(void) { - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - while (WiFi.status() != WL_CONNECTED) { // Wait for connection - delay(500); - Serial.print("."); - } - Serial.printf_P(PSTR("\nConnected to %s with IP address: %s\n"), ssid, WiFi.localIP().toString().c_str()); - if (MDNS.begin("esp8266")) - Serial.println("MDNS responder started"); - - //for (uint8_t i = 0; i Date: Mon, 20 Jan 2020 15:25:18 +0100 Subject: [PATCH 05/11] Update ServerSentEvents.ino --- .../examples/ServerSentEvents/ServerSentEvents.ino | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 4d8e67bbc4..cc28009824 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -41,10 +41,8 @@ extern "C" { #include #ifndef STASSID -//#define STASSID "your-ssid" -//#define STAPSK "your-password" -#define STASSID "EThomeZX" -#define STAPSK "superd0me;0rca" +#define STASSID "your-ssid" +#define STAPSK "your-password" #endif const char* ssid = STASSID; From be66b017556815aa9147afad585fa88f3ed26541 Mon Sep 17 00:00:00 2001 From: ewaldc Date: Tue, 4 Feb 2020 12:54:41 +0100 Subject: [PATCH 06/11] Update ServerSentEvents.ino Fix missing variables in printf statments Fix subscriptioncount not decreasing Fix SSEBroadcastState (argument sequence wrong) --- .../examples/ServerSentEvents/ServerSentEvents.ino | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index cc28009824..6effb0b899 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -83,14 +83,15 @@ void SSEKeepAlive() { if (!(subscription[i].clientIP)) continue; WiFiClient client = subscription[i].client; if (client.connected()) { - Serial.printf_P(PSTR("SSEKeepAlive - client is still listening on channel %d\n")); + Serial.printf_P(PSTR("SSEKeepAlive - client is still listening on channel %d\n"), i); client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }\n")); // Extra newline required by SSE standard } else { - Serial.printf_P(PSTR("SSEKeepAlive - client not listening on channel %d, remove subscription\n")); + Serial.printf_P(PSTR("SSEKeepAlive - client not listening on channel %d, remove subscription\n"), i); subscription[i].keepAliveTimer.detach(); client.flush(); client.stop(); subscription[i].clientIP = 0; + subscriptionCount--; } } } @@ -143,7 +144,7 @@ void updateSensor(const char* name, unsigned short *value) { unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor unsigned short val = *value; Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); - if (val != newVal) SSEBroadcastState(name, newVal, val); // only broadcast if state is different + if (val != newVal) SSEBroadcastState(name, val, newVal); // only broadcast if state is different *value = newVal; update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A } From 6a6bd7c7c0112dc09aab62b58098e5c6d76141ef Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 15 May 2020 16:32:56 -0700 Subject: [PATCH 07/11] Undo the library additions, move to current master --- libraries/LittleFS/lib/littlefs | 2 +- libraries/SoftwareSerial | 2 +- tools/esptool | 2 +- tools/sdk/lwip2/builder | 2 +- tools/sdk/ssl/bearssl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs index abd90cb84c..a049f1318e 160000 --- a/libraries/LittleFS/lib/littlefs +++ b/libraries/LittleFS/lib/littlefs @@ -1 +1 @@ -Subproject commit abd90cb84c818a663b584575b019258d01d0065e +Subproject commit a049f1318eecbe502549f9d74a41951985fb956f diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index dd9d2326f8..91ea6b1b1c 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit dd9d2326f85a22ca96ef5922df044884a2f3b529 +Subproject commit 91ea6b1b1c34601565b23c96c4441f2d399a4f99 diff --git a/tools/esptool b/tools/esptool index 9ad444a6e0..de30f21a22 160000 --- a/tools/esptool +++ b/tools/esptool @@ -1 +1 @@ -Subproject commit 9ad444a6e06e58833d5e6044c1d5f3eb3dd56023 +Subproject commit de30f21a222ec62f5a023dd955439b4f57702768 diff --git a/tools/sdk/lwip2/builder b/tools/sdk/lwip2/builder index ffa962483c..92add5010f 160000 --- a/tools/sdk/lwip2/builder +++ b/tools/sdk/lwip2/builder @@ -1 +1 @@ -Subproject commit ffa962483cc1c5d874b11bec13080359619c4cb2 +Subproject commit 92add5010fc329cbfbbcb1ce713108451cf9fc8c diff --git a/tools/sdk/ssl/bearssl b/tools/sdk/ssl/bearssl index 89454af34e..5c771bed8b 160000 --- a/tools/sdk/ssl/bearssl +++ b/tools/sdk/ssl/bearssl @@ -1 +1 @@ -Subproject commit 89454af34e3e61ddfc9837f3da5a0bc8ed44c3aa +Subproject commit 5c771bed8b11b968e2f75f26b9b00de51f2e9333 From 05b239dadc81d0fe59185381b5f291932727e5b8 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 15 May 2020 16:43:18 -0700 Subject: [PATCH 08/11] Fix compiler warning --- .../examples/ServerSentEvents/ServerSentEvents.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 80d01243b1..471846696b 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -177,7 +177,7 @@ void handleSubscribe() { if (!subscription[channel].clientIP) { break; } - subscription[channel] = {(uint32_t) clientIP, server.client()}; + subscription[channel] = {(uint32_t) clientIP, server.client(), Ticker()}; SSEurl += channel; Serial.printf_P(PSTR("Allocated channel %d, on uri %s\n"), channel, SSEurl.substring(offset).c_str()); //server.on(SSEurl.substring(offset), std::bind(SSEHandler, &(subscription[channel]))); @@ -215,4 +215,4 @@ void loop(void) { server.handleClient(); MDNS.update(); yield(); -} \ No newline at end of file +} From e94deafaa4ce20b2f6ae23a723154f04d3907672 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 15 May 2020 21:34:16 -0700 Subject: [PATCH 09/11] Address review and fix multi-sensor updates Address points of @devyte's code review: * Use IPAddress vs. uint32_t * Refactor the URL parsing logic to use strlen vs. sizeof, since there was some confusion in the original (correct) version * Minimize copies of WiFiClients while in use * Use byref access for sensor updates Fix multi-sensor updates * Create an update Ticker for each sensor, because the original code only had one whose callback was overridden by sensorB, meaning sensorA never changed --- .../ServerSentEvents/ServerSentEvents.ino | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 471846696b..50197a6c6c 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -53,14 +53,18 @@ ESP8266WebServer server(port); #define SSE_MAX_CHANNELS 8 // in this simplified example, only eight SSE clients subscription allowed struct SSESubscription { - uint32_t clientIP; + IPAddress clientIP; WiFiClient client; Ticker keepAliveTimer; } subscription[SSE_MAX_CHANNELS]; uint8_t subscriptionCount = 0; -unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors -Ticker update; +typedef struct { + const char *name; + unsigned short value; + Ticker update; +} sensorType; +sensorType sensor[2]; void handleNotFound() { Serial.println(F("Handle not found")); @@ -92,7 +96,7 @@ void SSEKeepAlive() { subscription[i].keepAliveTimer.detach(); client.flush(); client.stop(); - subscription[i].clientIP = 0; + subscription[i].clientIP = {0,0,0,0}; subscriptionCount--; } } @@ -103,7 +107,7 @@ void SSEKeepAlive() { void SSEHandler(uint8_t channel) { WiFiClient client = server.client(); SSESubscription &s = subscription[channel]; - if (s.clientIP != uint32_t(client.remoteIP())) { // IP addresses don't match, reject this client + if (s.clientIP != client.remoteIP()) { // IP addresses don't match, reject this client Serial.printf_P(PSTR("SSEHandler - unregistered client with IP %s tries to listen\n"), server.client().remoteIP().toString().c_str()); return handleNotFound(); } @@ -118,10 +122,11 @@ void SSEHandler(uint8_t channel) { void handleAll() { const char *uri = server.uri().c_str(); - if (strncmp_P(uri, PSTR("/rest/events/"), sizeof("/rest/events"))) { + const char *restEvents = PSTR("/rest/events/"); + if (strncmp_P(uri, restEvents, strlen_P(restEvents))) { return handleNotFound(); } - uri += sizeof("/rest/events"); + uri += strlen_P(restEvents); // Skip the "/rest/events/" and get to the channel number unsigned int channel = atoi(uri); if (channel < SSE_MAX_CHANNELS) { return SSEHandler(channel); @@ -134,12 +139,11 @@ void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, u if (!(subscription[i].clientIP)) { continue; } - WiFiClient client = subscription[i].client; String IPaddrstr = IPAddress(subscription[i].clientIP).toString(); - if (client.connected()) { + if (subscription[i].client.connected()) { Serial.printf_P(PSTR("broadcast status change to client IP %s on channel %d for %s with new state %d\n"), IPaddrstr.c_str(), i, sensorName, sensorValue); - client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n\n"), + subscription[i].client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n\n"), sensorName, sensorValue, prevSensorValue); } else { Serial.printf_P(PSTR("SSEBroadcastState - client %s registered on channel %d but not listening\n"), IPaddrstr.c_str(), i); @@ -148,18 +152,21 @@ void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, u } // Simulate sensors -void updateSensor(const char* name, unsigned short *value) { +void updateSensor(sensorType &sensor) { unsigned short newVal = (unsigned short)RANDOM_REG32; // (not so good) random value for the sensor - unsigned short val = *value; - Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), name, val, newVal); - if (val != newVal) { - SSEBroadcastState(name, val, newVal); // only broadcast if state is different + Serial.printf_P(PSTR("update sensor %s - previous state: %d, new state: %d\n"), sensor.name, sensor.value, newVal); + if (sensor.value != newVal) { + SSEBroadcastState(sensor.name, sensor.value, newVal); // only broadcast if state is different } - *value = newVal; - update.once(rand() % 20 + 10, std::bind(updateSensor, name, value)); // randomly update sensor A + sensor.value = newVal; + sensor.update.once(rand() % 20 + 10, std::bind(updateSensor, sensor)); // randomly update sensor } void handleSubscribe() { + if (subscriptionCount == SSE_MAX_CHANNELS - 1) { + return handleNotFound(); // We ran out of channels + } + uint8_t channel; IPAddress clientIP = server.client().remoteIP(); // get IP address of client String SSEurl = F("http://"); @@ -169,15 +176,12 @@ void handleSubscribe() { size_t offset = SSEurl.length(); SSEurl += F("/rest/events/"); - if (subscriptionCount == SSE_MAX_CHANNELS - 1) { - return handleNotFound(); // We ran out of channels - } ++subscriptionCount; for (channel = 0; channel < SSE_MAX_CHANNELS; channel++) // Find first free slot if (!subscription[channel].clientIP) { break; } - subscription[channel] = {(uint32_t) clientIP, server.client(), Ticker()}; + subscription[channel] = {clientIP, server.client(), Ticker()}; SSEurl += channel; Serial.printf_P(PSTR("Allocated channel %d, on uri %s\n"), channel, SSEurl.substring(offset).c_str()); //server.on(SSEurl.substring(offset), std::bind(SSEHandler, &(subscription[channel]))); @@ -207,8 +211,10 @@ void setup(void) { } startServers(); // start web and SSE servers - updateSensor("sensorA", &sensorA); - updateSensor("sensorB", &sensorB); + sensor[0].name = "sensorA"; + sensor[1].name = "sensorB"; + updateSensor(sensor[0]); + updateSensor(sensor[1]); } void loop(void) { From e82539e173bc909e2074cd3e075b94d60ee482cb Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 16 May 2020 06:43:06 -0700 Subject: [PATCH 10/11] Fix IPv6 build errors --- .../examples/ServerSentEvents/ServerSentEvents.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 50197a6c6c..76a1f41f01 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -96,7 +96,7 @@ void SSEKeepAlive() { subscription[i].keepAliveTimer.detach(); client.flush(); client.stop(); - subscription[i].clientIP = {0,0,0,0}; + subscription[i].clientIP = INADDR_NONE; subscriptionCount--; } } @@ -144,7 +144,7 @@ void SSEBroadcastState(const char *sensorName, unsigned short prevSensorValue, u Serial.printf_P(PSTR("broadcast status change to client IP %s on channel %d for %s with new state %d\n"), IPaddrstr.c_str(), i, sensorName, sensorValue); subscription[i].client.printf_P(PSTR("event: event\ndata: {\"TYPE\":\"STATE\", \"%s\":{\"state\":%d, \"prevState\":%d}}\n\n"), - sensorName, sensorValue, prevSensorValue); + sensorName, sensorValue, prevSensorValue); } else { Serial.printf_P(PSTR("SSEBroadcastState - client %s registered on channel %d but not listening\n"), IPaddrstr.c_str(), i); } From b9db02481a7545797c6446c8fd20c76bfbb1bc86 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 16 May 2020 11:38:43 -0700 Subject: [PATCH 11/11] Remove WiFiClient extraneous copy Avoid duplicating WiFiClient by using the WiFiClient object embedded in the subscriber[] array instead. --- .../examples/ServerSentEvents/ServerSentEvents.ino | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino index 76a1f41f01..77ae1e958e 100644 --- a/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino +++ b/libraries/ESP8266WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -87,15 +87,14 @@ void SSEKeepAlive() { if (!(subscription[i].clientIP)) { continue; } - WiFiClient client = subscription[i].client; - if (client.connected()) { + if (subscription[i].client.connected()) { Serial.printf_P(PSTR("SSEKeepAlive - client is still listening on channel %d\n"), i); - client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }\n")); // Extra newline required by SSE standard + subscription[i].client.println(F("event: event\ndata: { \"TYPE\":\"KEEP-ALIVE\" }\n")); // Extra newline required by SSE standard } else { Serial.printf_P(PSTR("SSEKeepAlive - client not listening on channel %d, remove subscription\n"), i); subscription[i].keepAliveTimer.detach(); - client.flush(); - client.stop(); + subscription[i].client.flush(); + subscription[i].client.stop(); subscription[i].clientIP = INADDR_NONE; subscriptionCount--; }