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--; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy