Server Sent Events

From

Jump to: navigation, search

Twisted is een event-driven python web-server framework dat uitermate geschikt is om een server-sent events server in te programmeren. Hieronder de code voor de 'SSeResource' class in de file sseresource.py en een voorbeeld hoe die te gebruiken

from twisted.internet import interfaces
from twisted.web.resource import Resource
from twisted.web import server
from zope.interface import implements
 
class Producer():
    implements(interfaces.IPushProducer)
 
    def __init__(self, request):
        self.request = request
        self.produce = True
        request.registerProducer(self, True)    
 
    def stopProducing(self):
        print "stopProducing"
        self.produce = False
        self.request.unregisterProducer()
        self.request.finish()
 
    def pauseProducing(self):
        print "pauseProducing"
        self.produce = False
        # kill immediately
        self.stopProducing()
 
    def resumeProducing(self):
        print "resumeProducing"
        self.produce = True
 
    def write(self, data = [], event = None):
        if self.produce:
            message = ""
            if event != None:
                message += "event: " + str(event) + "\n"
            for line in data:
                message += "data: " + str(line) + "\n"
#            print message
            self.request.write(message + "\n")
 
 
class SseResource(Resource):
 
    isLeaf = True
    def __init__(self):
        Resource.__init__(self)
        self.producers = []
 
    def connectionClosed(self, message, producer):
        print "Connection closed"
        print message
        self.producers.remove(producer)
        print self.producers
 
    def render_GET(self, request):
        request.setHeader("Content-Type", "text/event-stream")
        request.setHeader("Cache-Control", "no-cache")
        request.setHeader("Connection", "keep-alive")
        #request.setHeader("Access-Control-Allow-Origin", "http://localhost")
        request.setHeader("Access-Control-Allow-Origin", "*")
        # flush headers
        request.write("");
 
        print "Connection added"
        producer = Producer(request)
        self.producers.append(producer)
        print self.producers
        d = request.notifyFinish()
        d.addCallback(self.connectionClosed, producer)
        d.addErrback(self.connectionClosed, producer)
        return server.NOT_DONE_YET
 
    def write(self, data = [], event = None):
        for producer in self.producers:
            producer.write(data, event)

sseklokje.py is de webserver en implementeert een klok die elke seconde tikt.

from twisted.internet import reactor
from twisted.internet import task
from twisted.web.resource import Resource
from twisted.web.server import Site
from twisted.web.static import File
from datetime import datetime
from sseresource import SseResource
 
def runEverySecond():
    sse.write(data = [ datetime.now() ])
 
root = Resource()
sse = SseResource()
root.putChild("sse", sse)
root.putChild("page", File("sseklokje.html"))
l = task.LoopingCall(runEverySecond)
l.start(1.0) # call every second
factory = Site(root)
reactor.listenTCP(8881, factory)
reactor.run()

De html pagina sseklokje.html die wordt opgeroepen door sseklokje.py

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Server-Sent Events Demo</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      var button = document.getElementById("connect");
      var status = document.getElementById("status");
      var output = document.getElementById("output");
      var source;
 
      function connect() {
        source = new EventSource("/sse");
        source.addEventListener("message", function(event) {
          output.textContent = event.data;
        }, false);
 
        source.addEventListener("open", function(event) {
          button.value = "Disconnect";
          button.onclick = function(event) {
            source.close();
            button.value = "Connect";
            button.onclick = connect;
            status.textContent = "Connection closed!";
          };
          status.textContent = "Connection open!";
        }, false);
 
        source.addEventListener("error", function(event) {
          if (event.target.readyState === EventSource.CLOSED) {
            source.close();
            status.textContent = "Connection closed!";
          } else if (event.target.readyState === EventSource.CONNECTING) {
            status.textContent = "Connection closed. Attempting to reconnect!";
          } else {
            status.textContent = "Connection closed. Unknown error!";
          }
        }, false);
      }
 
      if (!!window.EventSource) {
        connect();
      } else {
        button.style.display = "none";
        status.textContent = "Sorry, your browser doesn't support server-sent events";
      }
    }, false);
  </script>
</head>
<body>
  <input type="button" id="connect" value="Connect" /><br />
  <span id="status">Connection closed!</span><br />
  <span id="output"></span>
</body>
</html>

De files sseresource.py, sseklokje.py en sseklokje.html in een willekeurige directory zetten. Start dan de twisted webserver met

 python sseklokje.py

en bezoek http://localhost:8881/page

bigred's potmeters

from twisted.internet import reactor
from twisted.internet.serialport import SerialPort
from twisted.protocols.basic import LineReceiver
from twisted.web.resource import Resource
from twisted.web.server import Site
from twisted.web.static import File
from sseresource import SseResource
 
class MySerialReceiver(LineReceiver):
    def lineReceived(self, line):
        sse.write(data = [ line ])
 
root = Resource()
sse = SseResource()
SerialPort(MySerialReceiver(), '/dev/ttyACM0', reactor, baudrate='19200')
root.putChild("sse", sse)
root.putChild("page", File("ssemarco.htm"))
factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Server-Sent Events Demo</title>
  <meta charset="UTF-8" />
  <script>
// <![CDATA
    window.addEventListener("load", function() {
      var button = document.getElementById("connect");
      var status = document.getElementById("status");
      var output = document.getElementById("output");
      var connectTime = document.getElementById("connecttime");
      var source;
 
      function connect() {
        source = new EventSource("/sse");
        source.addEventListener("message", function(event) {
          var value = event.data.substring(3);
          var canvas = document.getElementById("canvas");
          var context = canvas.getContext("2d");
          output.textContent = event.data;
 
          value /= 2;
          context.fillStyle = "white";
          context.fillRect(0, 0, 512, 30);
          context.fillStyle = "red";
          context.fillRect(0, 0, value, 30);
        }, false);
 
        source.addEventListener("open", function(event) {
          button.value = "Disconnect";
          button.onclick = function(event) {
            source.close();
            button.value = "Connect";
            button.onclick = connect;
            status.textContent = "Connection closed!";
          };
          status.textContent = "Connection open!";
        }, false);
 
        source.addEventListener("error", function(event) {
          if (event.target.readyState === EventSource.CLOSED) {
            source.close();
            status.textContent = "Connection closed!";
          } else if (event.target.readyState === EventSource.CONNECTING) {
            status.textContent = "Connection closed. Attempting to reconnect!";
          } else {
            status.textContent = "Connection closed. Unknown error!";
          }
        }, false);
      }
 
      if (!!window.EventSource) {
        connect();
      } else {
        button.style.display = "none";
        status.textContent = "Sorry, your browser doesn't support server-sent events";
      }
    }, false);
  // ]>
  </script>
</head>
<body>
  <input type="button" id="connect" value="Connect" /><br />
  <span id="status">Connection closed!</span><br />
  <span id="connecttime"></span><br />
  <span id="output"></span><br />
  <canvas id="canvas" width="512" height="30"></canvas>
</body>
</html>

Update by Ylebre and bigred

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Server-Sent Events Demo</title>
  <meta charset="UTF-8" />
  <script type="text/javascript">
// <![CDATA
	function setValue(barid, value) {
		barelm = document.getElementById(barid);
		if (barelm) {
			var newheight = parseInt(100*value/1024);
			document.getElementById(barid).style.height = newheight + "%";
			if (newheight > 75) {	
				document.getElementById	(barid).style.backgroundColor = "red";
			} else {
				document.getElementById(barid).style.backgroundColor = "black";
			}
		}
	}
 
    window.addEventListener("load", function() {
      var button = document.getElementById("connect");
      var status = document.getElementById("status");
      var output = document.getElementById("output");
      var connectTime = document.getElementById("connecttime");
      var source;
 
      function connect() {
        source = new EventSource("/sse");
        source.addEventListener("message", function(event) {
          var port = event.data.substring(1, 2);
	  var value = event.data.substring(3);
 
	  var barid = "bar" + port;
          setValue(barid, value); 
        }, false);
 
        source.addEventListener("open", function(event) {
          button.value = "Disconnect";
          button.onclick = function(event) {
            source.close();
            button.value = "Connect";
            button.onclick = connect;
            status.textContent = "Connection closed!";
          };
          status.textContent = "Connection open!";
        }, false);
 
        source.addEventListener("error", function(event) {
          if (event.target.readyState === EventSource.CLOSED) {
            source.close();
            status.textContent = "Connection closed!";
          } else if (event.target.readyState === EventSource.CONNECTING) {
            status.textContent = "Connection closed. Attempting to reconnect!";
          } else {
            status.textContent = "Connection closed. Unknown error!";
          }
        }, false);
      }
 
      if (!!window.EventSource) {
        connect();
      } else {
        button.style.display = "none";
        status.textContent = "Sorry, your browser doesn't support server-sent events";
      }
    }, false);
  // ]>
  </script>
  <style type="text/css">
	#bars {
		height: 400px;
		xborder: 1px solid black;
		position: relative;
	}
 
	#bar0 {
		left: 0px;
	}
	#bar1 {
		left: 40px;
	}
	#bar2 {
		left: 80px;
	}
	#bar3 {
		left: 120px;
	}
	#bar4 {
		left: 160px;
	}
	#bar5 {
		left: 200px;
	}
	.bar {
		width: 30px;
		border: 1px solid black;
		position: absolute;
		background-color: #c0c0c0;
		bottom: 0px;
	}	
 
	#bar0text {
		left: 0px;
	}
	#bar1text {
		left: 40px;
	}
	#bar2text {
		left: 80px;
	}
	#bar3text {
		left: 120px;
	}
	#bar4text {
		left: 160px;
	}
	#bar5text {
		left: 200px;
	}
	.bartext {
		height: 30px;
		width: 30px;
		border: 0px solid black;
		position: absolute;
		//background-color: #c0c0c0;
		bottom: -35px;
		vertical-align: middle;
		line-height: 30px;
		text-align: center;
		-moz-transform: rotate(+90deg);	
		-webkit-transform: rotate(-90deg);
	}	
	#side1text {
		 bottom: -15px;
	}
	#side2text {
		bottom: 285px;
	}
	#side3text {
		bottom: 383px;
	}
 
	.sidetext {
		left: 240px;
		height: 30px;
		width: 30px;
		border: 0px solid black;
		position: absolute;
		//background-color: #c0c0c0;
		bottom: -0px;
		vertical-align: middle;
		line-height: 30px;
		text-align: center;
	}			
 
  </style>
</head>
<body>
Analog input readout
<br><br><br>
 
  <input type="button" id="connect" value="Connect" /><br />
  <span id="status">Connection closed!</span><br />
  <span id="connecttime"></span><br />
  <span id="output"></span><br />
<div id="bars">
  <div id="bar0" class="bar"></div><div id="bar0text" class="bartext">A0</div>
  <div id="bar1" class="bar"></div><div id="bar1text" class="bartext">A1</div>
  <div id="bar2" class="bar"></div><div id="bar2text" class="bartext">A2</div>
  <div id="bar3" class="bar"></div><div id="bar3text" class="bartext">A3</div>
  <div id="bar4" class="bar"></div><div id="bar4text" class="bartext">A4</div>
  <div id="bar5" class="bar"></div><div id="bar5text" class="bartext">A5</div>
  <div id="side1text" class="sidetext">0%</div>
  <div id="side2text" class="sidetext">100%</div>
  <div id="side3text" class="sidetext">125%</div>
<div>
</body>
</html>

with this simple arduino code:

void setup()  
{
  Serial.begin(19200);
}
 
void loop()                     // run over and over again
{
int a0 = analogRead(0);
int a1 = analogRead(1);
int a2 = analogRead(2);
int a3 = analogRead(3);
int a4 = analogRead(4);
int a5 = analogRead(5);
 
  Serial.print("a0 ");
  Serial.println(a0);
  Serial.print("a1 ");
  Serial.println(a1);
  Serial.print("a2 ");
  Serial.println(a2);
  Serial.print("a2 ");
  Serial.println(a2);
  Serial.print("a3 ");
  Serial.println(a3);
  Serial.print("a4 ");
  Serial.println(a4);
  Serial.print("a5 ");
  Serial.println(a5);
}

results real-time view of your analog arduino ports on your web page without reloading:

Screeenshot.jpg