aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Merritt Boyd <mboyd@mit.edu>2011-02-20 18:27:52 -0500
committerGravatar Merritt Boyd <mboyd@mit.edu>2011-02-20 18:27:52 -0500
commit868c8206ac18e07f4e9d2eae32ec804344e31f2b (patch)
tree4d5b0f3c7076a5ddafdc25a8cfaa01aafbb6b6dd
parentc2874a0cefa410aca3fdd33ed701b16bac520440 (diff)
Websocket renderer and basic page server.
-rw-r--r--config/C5Sign-websocket.xml9
-rw-r--r--renderers/Websocket.xml10
-rw-r--r--renderers/WebsocketRenderer.py128
-rw-r--r--web/smootlight.html107
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