Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libraries/ESP8266SdFat
Submodule ESP8266SdFat updated 1 files
+7 −1 src/SysCall.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/* 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://<your ESP IP>:<your port>/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 <your client's IP address>: event bus location: http://<your ESP IP>:<your port>/rest/events/<channel>

you will also see that the sensors are ready to broadcast state changes, but the client is not yet listening:
SSEBroadcastState - client <your client IP>> registered but not listening

3. on the client(s), start listening for events with: curl -sS "http://<your ESP IP>:<your port>/rest/events/<channel>"
if all is well, the following is being displayed on the ESP console
SSEHandler - registered client with IP <your client IP address> is listening...
broadcast status change to client IP <your client IP>> for sensor[A|B] with new state <some number>>
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 <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Ticker.h>

#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);

#define SSE_MAX_CHANNELS 8 // in this simplified example, only eight SSE clients subscription allowed
struct SSESubscription {
uint32_t clientIP;
WiFiClient client;
Ticker keepAliveTimer;
} subscription[SSE_MAX_CHANNELS];
uint8_t subscriptionCount = 0;

unsigned short sensorA = 0, sensorB = 0; //Simulate two sensors
Ticker update;

void handleNotFound() {
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 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"), 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].clientIP = 0;
subscriptionCount--;
}
}
}

// 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(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);
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);
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
}

void handleSubscribe() {
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(handleAll);
server.begin();
Serial.println("HTTP server and SSE EventSource 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();
MDNS.update();
yield();
}
2 changes: 1 addition & 1 deletion libraries/LittleFS/lib/littlefs
2 changes: 1 addition & 1 deletion tools/esptool
Submodule esptool updated 45 files
+12 −7 .travis.yml
+17 −1 .travis_setup_build_env.sh
+0 −14 README.md
+99 −179 espefuse.py
+23 −68 espsecure.py
+154 −396 esptool.py
+11 −5 flasher_stub/Makefile
+1 −1 flasher_stub/compare_stubs.py
+0 −130 flasher_stub/include/soc_support.h
+0 −44 flasher_stub/include/stub_commands.h
+0 −43 flasher_stub/include/stub_write_flash.h
+0 −26 flasher_stub/miniz.h
+0 −3 flasher_stub/rom_32.ld
+0 −0 flasher_stub/rom_8266.ld
+22 −11 flasher_stub/rom_functions.h
+1 −1 flasher_stub/run_tests_with_stub.sh
+5 −1 flasher_stub/slip.h
+47 −0 flasher_stub/soc_support.h
+0 −0 flasher_stub/stub_32.ld
+1 −1 flasher_stub/stub_8266.ld
+1 −2 flasher_stub/stub_commands.c
+25 −0 flasher_stub/stub_commands.h
+32 −27 flasher_stub/stub_flasher.c
+5 −5 flasher_stub/stub_flasher.h
+18 −63 flasher_stub/stub_write_flash.c
+20 −0 flasher_stub/stub_write_flash.h
+1 −1 flasher_stub/wrap_stub.py
+1 −1 setup.cfg
+ test/elf2image/esp32-app-template.elf
+ test/images/aes_key.bin
+ test/images/esp8266_deepsleep.bin
+ test/images/helloworld-esp32_edit.bin
+ test/images/image_header_only.bin
+0 −1 test/images/one_kb_all_ef.bin
+ test/secure_images/bootloader-encrypted-conf0.bin
+ test/secure_images/bootloader-encrypted-conf3.bin
+ test/secure_images/bootloader-encrypted-conf9.bin
+ test/secure_images/bootloader-encrypted-confc.bin
+ test/secure_images/bootloader-encrypted.bin
+0 −1 test/secure_images/ef-flashencryption-key.bin
+ test/secure_images/hello-world-signed-encrypted.bin
+ test/secure_images/hello-world-signed.bin
+11 −34 test/test_espsecure.py
+21 −162 test/test_esptool.py
+11 −17 test/test_imagegen.py
2 changes: 1 addition & 1 deletion tools/sdk/lwip2/builder