Server Sent Events
From
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: