2019/04/20

First Experiment with SSE (Server-Sent Events)

This post is a record of my first experiment with SSE (Server-Sent Events).

Schematic

Connect 3 wires to A0, GPIO4, and GPIO5 of NodeMCU as shown below.


Testing The Arduino Code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
/*
 Source: https://github.com/IU5HKU/ESP8266-ServerSentEvents/blob/master/ESP8266_ServerSentEvents/ESP8266_ServerSentEvents.ino
 
 Server-Sent Events / EventSource DEMO
 forked from Claudius Coenen repository
 based on Web Server example by David A. Mellis and Tom Igoe
 Adapted to the new ESP8266 SDK 2.4.2 by Marco Campinoti
 Circuit:
 * Analog input attached to pins A0 (optional)
 * Digital input attached to pins 5 or 6 (optional)
 This is free software. Use, modify and tinker with it however you like!
 LICENSED UNDER CC-BY-4.0 http://creativecommons.org/licenses/by/4.0/
 */

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

//declare the webserver
ESP8266WebServer server(80);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print(F("Connecting"));
        
    WiFi.persistent(false);       // WiFi config isn't saved in flash
    WiFi.mode(WIFI_STA);          // use WIFI_AP_STA if you want an AP
    WiFi.hostname("ESP8266");     // must be called before wifi.begin()
    WiFi.begin("SSID", "SSID_Password");
   
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(F("."));
    }
  }
     
  Serial.println();
  Serial.print(F("IP address: "));
  Serial.println(WiFi.localIP());  
  
  //start the webserver
  server.begin();
  //Server Sent Events will be handled from this URI
  server.on("/ssedata", handleSSEdata);
}

void loop() {
  // listen for incoming clients
  server.handleClient();
}

void handleSSEdata(){
  WiFiClient client = server.client();
  
  if (client) {
    Serial.println("new client");
    serverSentEventHeader(client);
    while (client.connected()) {
      serverSentEvent(client);
      delay(16); // round about 60 messages per second
    }

    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void serverSentEventHeader(WiFiClient client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/event-stream;charset=UTF-8");
  client.println("Connection: close");               // the connection will be closed after completion of the response
  client.println("Access-Control-Allow-Origin: *");  // allow any connection. We don't want Arduino to host all of the website ;-)
  client.println("Cache-Control: no-cache");         // refresh the page automatically every 5 sec
  client.println();
  client.flush();
}

void serverSentEvent(WiFiClient client) {
  client.println("event: esp8266");                 // this name could be anything, really.
  client.print("data: {");
  client.print("\"A0\": ");
  client.print(1.0 * analogRead(0) / 1024.0);
  client.print(", \"in5\": ");
  client.print(digitalRead(5));
  client.print(", \"in4\": ");
  client.print(digitalRead(4));
  //client.print(", \"text\": ESP8266");           // added just to show how you can add your own parameters
  client.print(", \"text\": \"ESP8266\"");         // Be sure to add " around ESP8266; otherwise, it won't be correctly parsed in the web UI!!!
  client.println("}");
  client.println();
  client.flush();
}

Assuming the IP address assigned to the NodeMCU is 192.168.31.148, launch a web browser that's running on a computer which is on the same network segment as the NodeMCU and put http://192.168.31.148/ssedata in the URL field of the web browser..

Below is the output on the web browser.


Testing The PHP Code (source.php)

With the above Arduino code still running on the NodeMCU module, put the code below on a computer that already has PHP and Apache Web Server installed. The code is to be placed under "/var/www/html".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
/*
 Server-Sent Events / EventSource DEMO
 Claudius Coenen
 This is free software. Use, modify and tinker with it however you like!
 LICENSED UNDER CC-BY-4.0 http://creativecommons.org/licenses/by/4.0/
 */
error_log('script starting');
header('Content-Type: text/event-stream');
header('Access-Control-Allow-Origin: *');
header('Cache-Control: no-cache');
echo "retry: 3000\n";
$seed = rand();
$count = 0;
function sendMsg($msg) {
 global $count, $seed;
 error_log('message sent: ' . $msg);
 echo "event: testeventcc\n";
 echo "data: $msg $count $seed\n\n";
 ob_flush();
 flush();
}
while (!connection_aborted()) {
 $count++;
 sendMsg('server time: ' . date("H:i:s"));
 usleep(16000); // 16ms per message -> 60msg/s
}
error_log("connection closed");

Assuming the IP of the computer that hosts the PHP code is 192.168.31.77, launch a web browser and put http://192.168.31.77/source.php in the URL field.

Below is the output on the web browser.


Testing The index.html Code - with SSE data source from source.php

Here is source of data is from source.php (highlighted in yellow below).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<html>
    <head>

        <style>
            body {
                font-size: 3em;
                font-family: sans-serif;
            }
        </style>
    </head>
    
<body>
 <p id="output">Nothing received so far...</p>
    
 <script>
        // change this line to match the IP address of your Arduino
        // var source = new EventSource('http://192.168.31.148/ssedata');
        
        // or, in case you're using the PHP Script, use this line instead.
        var source = new EventSource('source.php');
        
        var outputElement = document.getElementById('output');
        var eventCounter = 0;
        
        source.addEventListener('testeventcc', function(e) {
        //source.addEventListener('arduino', function(e) {
            eventCounter++;
            outputElement.innerText = e.data + " (" + eventCounter + " Events)";
        }, false);
 
        //source.addEventListener('arduino', function(e) {
        source.addEventListener('esp8266', function(e) {
            eventCounter++;
            outputElement.innerText = e.data + " (" + eventCounter + " Events)";
            var inputs = JSON.parse(e.data);
            document.body.style.backgroundColor = inputs.in5 > 0 ? '#ff0000' : '#ffffff';
            document.body.style.color = inputs.in6 > 0 ? 'fuchsia' : 'black';
            outputElement.style.opacity = inputs.A0;
        }, false);
 
        source.addEventListener('open', function(e) {
            console.log("connected");
        }, false);
 
        source.addEventListener('error', function(e) {
            console.error(e);
            if (e.readyState == EventSource.CLOSED) {
            }
        }, false);
 </script>
</body>
</html>

Below is the output from the web browser.


I use Wireshark to capture and analyze the traffic. In the pic below, 192.168.31.77 is the IP address of the Raspberry Pi that has index.html and source.php running on it, 192.168.31.178 is the IP address of the computer browsing the output of source.php.


Testing The index.html Code - with SSE data source from ESP8266

Replace the previous 'index.html" in "/var/www/html" with the one below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<html>
    <head>

        <style>
            body {
                font-size: 3em;
                font-family: sans-serif;
            }
        </style>
    </head>
    
<body>
 <p id="output">Nothing received so far...</p>
    
 <script>
        // change this line to match the IP address of your Arduino
        var source = new EventSource('http://192.168.31.148/ssedata');
        
        // or, in case you're using the PHP Script, use this line instead.
        // var source = new EventSource('source.php');
        
        var outputElement = document.getElementById('output');
        var eventCounter = 0;
        
        source.addEventListener('testeventcc', function(e) {
            eventCounter++;
            outputElement.innerText = e.data + " (" + eventCounter + " Events_1)";
        }, false);
 
        source.addEventListener('esp8266', function(e) {
            eventCounter++;
            outputElement.innerText = e.data + " (" + eventCounter + " Events_2)";
            //console.log(e);
            var inputs = JSON.parse(e.data);
            document.body.style.backgroundColor = inputs.in5 > 0 ? '#ff0000' : '#ffffff';
            document.body.style.color = inputs.in4 > 0 ? 'fuchsia' : 'black';
            outputElement.style.opacity = inputs.A0;
        }, false);
 
        source.addEventListener('open', function(e) {
            console.log("connected");
        }, false);
 
        source.addEventListener('error', function(e) {
            console.error(e);
            if (e.readyState == EventSource.CLOSED) {
            }
        }, false);
 </script>
</body>
</html>

***IMPORTANT***

Be sure to close all other browser / browser tab that is accessing the web UI running on ESP8266 (in this case "http://192.168.31.148/ssedata"); otherwise, you will see "Nothing received so far" when accessing the URL that gets its SSE data from ESP8266.

For example, in the pic below, when the browser on the left is accessing "http://192.168.31.148/ssedata", the one on the left is receiving nothing.


In the pic below, when the browser on the left stops accessing "http://192.168.31.148/ssedata", the one on the left is receiving and showing the SSE data.

I think this issue could be solved by using async web server...


Overall Architecture

There are 2 data sources, one is generated by the Arduino code running on NodeMCU. The other is from the PHP code running on the Raspberry Pi.


The index.html file is running on the Raspberry Pi.



Reference:

How to use Server-Sent Events / EventSource from PHP and ESP8266
https://github.com/IU5HKU/ESP8266-ServerSentEvents

How to use Server-Sent Events / EventSource from PHP and Arduino
https://github.com/ccoenen/server-sent-events-demo/

Generating Server-Sent Events on Arduino
https://www.claudiuscoenen.de/2015/09/generating-server-sent-events-on-arduino/

HTML5 Server-Sent Events
https://www.w3schools.com/html/html5_serversentevents.asp

No comments:

Post a Comment