diff options
author | Merritt Boyd <mboyd@mit.edu> | 2011-02-20 18:27:52 -0500 |
---|---|---|
committer | Merritt Boyd <mboyd@mit.edu> | 2011-02-20 18:27:52 -0500 |
commit | 868c8206ac18e07f4e9d2eae32ec804344e31f2b (patch) | |
tree | 4d5b0f3c7076a5ddafdc25a8cfaa01aafbb6b6dd | |
parent | c2874a0cefa410aca3fdd33ed701b16bac520440 (diff) |
Websocket renderer and basic page server.
-rw-r--r-- | config/C5Sign-websocket.xml | 9 | ||||
-rw-r--r-- | renderers/Websocket.xml | 10 | ||||
-rw-r--r-- | renderers/WebsocketRenderer.py | 128 | ||||
-rw-r--r-- | web/smootlight.html | 107 |
4 files changed, 254 insertions, 0 deletions
diff --git a/config/C5Sign-websocket.xml b/config/C5Sign-websocket.xml new file mode 100644 index 0000000..549da07 --- /dev/null +++ b/config/C5Sign-websocket.xml @@ -0,0 +1,9 @@ +<LightInstallation> + <InheritsFrom>config/C5Sign.xml</InheritsFrom> + <RendererConfiguration> + <Renderer> + <InheritsFrom>renderers/Websocket.xml</InheritsFrom> + </Renderer> + </RendererConfiguration> + <InputConfiguration> </InputConfiguration> +</LightInstallation> diff --git a/renderers/Websocket.xml b/renderers/Websocket.xml new file mode 100644 index 0000000..6cf2e1f --- /dev/null +++ b/renderers/Websocket.xml @@ -0,0 +1,10 @@ +<Renderer> + <Class>renderers.WebsocketRenderer</Class> + <Args> + <Id>websocketrenderer</Id> + <Hostname>localhost</Hostname> + <Page>web/smootlight.html</Page> + <SourcePort>8081</SourcePort> + <Port>8000</Port> + </Args> +</Renderer> diff --git a/renderers/WebsocketRenderer.py b/renderers/WebsocketRenderer.py new file mode 100644 index 0000000..1a431fe --- /dev/null +++ b/renderers/WebsocketRenderer.py @@ -0,0 +1,128 @@ +from operationscore.Renderer import * +import util.TimeOps as timeops +import util.ComponentRegistry as compReg +import threading, socket, re, struct, hashlib, json, webbrowser + +class WebsocketRenderer(Renderer): + """Renders frame data over a websocket.""" + + def initRenderer(self): + self.hostname = self.argDict['Hostname'] + self.orig_port = int(self.argDict['SourcePort']) + self.port = int(self.argDict['Port']) + + self.clients = [] + self.clients_lock = threading.Lock() + + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.bind(('', self.port)) + self.sock.listen(1) + + self.connection_thread = threading.Thread(target=self.handle_connections) + self.connection_thread.daemon = True + self.connection_thread.start() + + self.serve_thread = threading.Thread(target=self.serve_page) + self.serve_thread.daemon = True + self.serve_thread.start() + + webbrowser.open('http://'+self.hostname+':'+str(self.orig_port)) + + def serve_page(self): + page = open(self.argDict['Page']).read() + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('', self.orig_port)) + sock.listen(1) + + while True: + client, addr = sock.accept() + req = client.recv(4096) + client.send('HTTP/1.0 200 Found\r\n') + client.send('Content-type: text/html\r\n\r\n') + client.send(page) + client.close() + + def handle_connections(self): + while True: + client, addr = self.sock.accept() + print 'Accepted websocket connection from %s' % str(addr) + header = '' + while not re.search("\r?\n\r?\n.{8}", header): # Receive headers + 8 bytes data + header += client.recv(1024) + + key1 = re.search("Sec-WebSocket-Key1: (.*)$", header, re.M).group(1) + key2 = re.search("Sec-WebSocket-Key2: (.*)$", header, re.M).group(1) + + data = header[-8:] + + key1n = int(re.sub("[^\d]", '', key1)) + key1ns = key1.count(' ') + n1 = key1n // key1ns + + key2n = int(re.sub("[^\d]", '', key2)) + key2ns = key2.count(' ') + n2 = key2n // key2ns + + s = struct.pack("!II", n1, n2) + data + respkey = hashlib.md5(s).digest() + + if self.orig_port == 80: + origin = 'http://'+self.hostname + else: + origin = 'http://'+self.hostname+':'+str(self.orig_port) + + resp = \ + "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + \ + "Upgrade: WebSocket\r\n" + \ + "Connection: Upgrade\r\n" + \ + "Sec-WebSocket-Origin:"+ origin + "\r\n" + \ + "Sec-WebSocket-Location: ws://"+self.hostname+":"+ \ + str(self.port)+"/\r\n" + \ + "Sec-WebSocket-Protocol: ledweb\r\n\r\n" + \ + respkey + "\r\n" + + client.send(resp) + self.clients_lock.acquire() + self.clients.append(client) + self.clients_lock.release() + + def render(self, lightSystem, currentTime=timeops.time()): + json_frame = [] + + for light in lightSystem: + loc = light.location + c = light.state(currentTime) + cs = 'rgb('+str(c[0])+','+str(c[1])+','+str(c[2])+')' + + json_frame.append((loc, cs)) + + size = compReg.getComponent('Screen').getSize() + + json_data = json.dumps(dict(status='ok', size=size, frame=json_frame)) + self.client_push(json_data) + + def client_push(self, data): + self.clients_lock.acquire() + dead_clients = [] + for i in range(len(self.clients)): + try: + self.clients[i].send("\x00") + self.clients[i].send(data) + self.clients[i].send("\xff") + except socket.error: + dead_clients.append(i) + + for i in range(len(dead_clients)): + self.close_sock(self.clients[dead_clients[i]-i]) + del self.clients[dead_clients[i]-i] + self.clients_lock.release() + + def close_sock(self, s): + try: + c.shutdown(socket.SHUT_RDWR) + c.close() + except Exception: + pass +
\ No newline at end of file diff --git a/web/smootlight.html b/web/smootlight.html new file mode 100644 index 0000000..5b64a28 --- /dev/null +++ b/web/smootlight.html @@ -0,0 +1,107 @@ +<!DOCTYPE html> +<head> +<title>SmootLight</title> +<script> +var websocket_address = "ws://localhost:8000"; +var circular_pixels = true; /* False for square */ + +var canvas; +var ctx; +var ws; + +var frameCount; +var lastTime; + +window.onload = function() { + canvas = document.getElementById('canvas'); + ctx = canvas.getContext('2d'); + blank(); + + frameCount = 0; + lastTime = (new Date).getTime(); + + connect(); +}; + +function connect() { + ws = new WebSocket(websocket_address); + + ws.onopen = function() { + document.getElementById('connection').innerHTML = 'Status: connected'; + } + + ws.onmessage = function(e) { + var data = JSON.parse(e.data); + + if (data['status'] == 'ok') { + blank(); + var size = data['size']; + var frame = data['frame']; + + var xo = size[0]; + var xf = canvas.width / (size[2]-size[0]); + var yo = size[1]; + var yf = canvas.height / (size[3]-size[1]); + + var pixelWidth = xf; + var pixelHeight = yf; + var pixelRadius = Math.min(xf, yf) / 2.0; + + for (var i = 0; i < frame.length; i++) { + var pos = frame[i][0]; + var clr = frame[i][1]; + + var x = (pos[0] - xo) * xf; + var y = (pos[1] - yo) * yf; + + ctx.fillStyle = clr; + ctx.strokeStyle = clr; + + if (circular_pixels) { + ctx.beginPath(); + ctx.arc(x, y, pixelRadius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + } else { + ctx.fillRect(x, y, pixelWidth, pixelHeight); + } + } + + if (frameCount == 30) { + frameCount = 0; + var t = (new Date).getTime(); + var dt = t - lastTime; + var fr = 30 / (dt / 1000.0); + document.getElementById('framerate').innerHTML = 'Framerate: ' + fr.toFixed(2) + ' fps'; + + lastTime = t; + } + + frameCount += 1; + + } else if (data['status'] == 'exiting') { + document.getElementById('framerate').innerHTML = ''; + ws.close(); + } + }; + + ws.onclose = function() { + document.getElementById('connection').innerHTML = 'Status: disconnected'; + blank(); + setTimeout(connect, 2000); + } +} + +function blank() { + ctx.fillStyle = 'rgb(0,0,0)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); +} +</script> +</head> + +<body> + <canvas id='canvas' style='margin: 50px 25px;' width='800px' height='500px'></canvas> + <div id='connection'></div> + <div id='framerate'></div> +</body> +</html>
\ No newline at end of file |