aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar dxiao <dxiao@mit.edu>2011-02-20 19:31:04 -0500
committerGravatar dxiao <dxiao@mit.edu>2011-02-20 19:31:04 -0500
commit8660df89beb0869204adc270710b3d9efe70dde1 (patch)
treed35295eca82b64221406393399a79abfeed7e110
parent42a3112b7cd7518ab69ba8d69c636a6278cfb288 (diff)
parentfd13edeeb2f277ee78af6d8ced1fba81a67ed1ba (diff)
Merge branch 'conner5' of github.com:dxiao/SmootLight into conner5
-rw-r--r--config/C5Sign-Leah.xml301
-rw-r--r--config/C5Sign-pygame.xml2
-rw-r--r--config/C5Sign-websocket.xml9
-rw-r--r--inputs/ParametricLocationInput.py1
-rw-r--r--renderers/Websocket.xml10
-rw-r--r--renderers/WebsocketRenderer.py128
-rw-r--r--web/smootlight.html107
7 files changed, 557 insertions, 1 deletions
diff --git a/config/C5Sign-Leah.xml b/config/C5Sign-Leah.xml
new file mode 100644
index 0000000..94fda27
--- /dev/null
+++ b/config/C5Sign-Leah.xml
@@ -0,0 +1,301 @@
+<!---All configuration items contain a "Class" tag specifying the python class they represent, and an "Args" tag specifying the args to be passed in.-->
+<LightInstallation>
+ <InstallationConfiguration>
+ <Defaults>
+ <PixelMapper>gaussmap</PixelMapper>
+ </Defaults>
+ </InstallationConfiguration>
+ <PixelConfiguration>
+ <InheritsFrom>layouts/C5SignLayout.xml</InheritsFrom>
+ </PixelConfiguration>
+ <PixelMapperConfiguration>
+ <PixelMapper>
+ <Class>pixelmappers.C5SignMapper</Class>
+ <Args>
+ <Id>simplemap</Id>
+ <CutoffDist>20</CutoffDist>
+ </Args>
+ </PixelMapper>
+ <PixelMapper>
+ <Class>pixelmappers.GaussianMapper</Class>
+ <Args>
+ <Id>gaussmap</Id>
+ <CutoffDist>4</CutoffDist>
+ <MinWeight>0.1</MinWeight>
+ <Width>1</Width>
+ <Height>1</Height>
+ </Args>
+ </PixelMapper>
+ <PixelMapper>
+ <Class>pixelmappers.C5SignMapper</Class>
+ <Args>
+ <Id>c5signmapper</Id>
+ <CutoffDist>20</CutoffDist>
+ </Args>
+ </PixelMapper>
+ </PixelMapperConfiguration>
+ <RendererConfiguration>
+ <APPEND><Renderer>
+ <InheritsFrom>renderers/C5Renderer.xml</InheritsFrom>
+ </Renderer></APPEND>
+ </RendererConfiguration>
+ <InputConfiguration>
+ <APPEND><InputElement>
+ <Class>inputs.ParametricLocationInput</Class>
+ <Args>
+ <Id>random_top</Id>
+ <xloc>center</xloc>
+ <yloc>top</yloc>
+ <xEquation>random.random()</xEquation>
+ <yEquation>0</yEquation>
+ <RefreshInterval>100</RefreshInterval>
+ </Args>
+ </InputElement>
+ <InputElement>
+ <Class>inputs.OSCInput</Class>
+ <Args>
+ <Id>osc</Id>
+ <Port>1234</Port>
+ <RefreshInterval>10</RefreshInterval>
+ </Args>
+ </InputElement>
+ <InputElement>
+ <Class>inputs.UDPInput</Class>
+ <Args>
+ <Id>udp</Id>
+ <Port>3344</Port>
+ <RefreshInterval>50</RefreshInterval>
+ </Args>
+ </InputElement>
+ <InputElement>
+ <Class>inputs.ContinuousCenterInput</Class>
+ <Args>
+ <Id>center</Id>
+ <RefreshInterval>700</RefreshInterval>
+ </Args>
+ </InputElement></APPEND>
+ </InputConfiguration>
+ <BehaviorConfiguration>
+ <Behavior>
+ <Args>
+ <Id>touchosc</Id>
+ </Args>
+ <Class>behaviors.TouchOSC</Class>
+ </Behavior>
+ <Behavior Id="slowdecay">
+ <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom>
+ <Args>
+ <Coefficient>.001</Coefficient>
+ </Args>
+ </Behavior>
+
+ <Behavior>
+ <Class>behaviors.XYMove</Class>
+ <Args>
+ <Id>xymove</Id>
+ <XStep>0</XStep>
+ <YStep>.5</YStep>
+ </Args>
+ </Behavior>
+
+ <Behavior>
+ <Class>behaviors.ModifyParam</Class>
+ <Args>
+ <Id>modifyY</Id>
+ <ParamType>Sensor</ParamType>
+ <ParamName>YStep</ParamName>
+ <ParamOp>{val}+.1</ParamOp>
+ </Args>
+ </Behavior>
+
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>point_move</Id>
+ <ChainedBehaviors>
+ <Id>xymove</Id>
+ <Id>modifyY</Id>
+ <Id>recursivedecay</Id>
+ </ChainedBehaviors>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>falling_points</Id>
+ <Inputs>
+ <Id>random_top</Id>
+ </Inputs>
+ <ChainedBehaviors>
+ <Id>colorchange</Id>
+ <Id>mover</Id>
+ <Id>slowdecay</Id>
+ </ChainedBehaviors>
+ <RecursiveHooks>{'mover':'point_move'}</RecursiveHooks>
+ <RenderToScreen>True</RenderToScreen>
+ </Args>
+ </Behavior>
+
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>OSCTouchChase</Id>
+ <Inputs>
+ <Id>osc</Id>
+ </Inputs>
+ <ChainedBehaviors>
+ <Id>touchosc</Id>
+ <Id>decay</Id>
+ </ChainedBehaviors>
+ <Mapper>gaussmap</Mapper>
+ <RenderToScreen>False</RenderToScreen>
+ </Args>
+ </Behavior>
+ <Behavior Id="decay">
+ <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom>
+ </Behavior>
+ <Behavior Id="singleframe">
+ <InheritsFrom>behaviors/SingleFrame.xml</InheritsFrom>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.RestrictLocation</Class>
+ <Args>
+ <Id>xbounce</Id>
+ <Action>{val}*-1</Action>
+ <ParamName>XStep</ParamName>
+ <LocationRestriction>{x}&lt;2 or {x}&gt;48</LocationRestriction>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.RestrictLocation</Class>
+ <Args>
+ <Id>ybounce</Id>
+ <Action>{val}*-1</Action>
+ <ParamName>YStep</ParamName>
+ <LocationRestriction>{y}&lt;2 or {y}&gt;24</LocationRestriction>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>movebounce</Id>
+ <ChainedBehaviors>
+ <Id>xymove</Id>
+ <Id>ybounce</Id>
+ <Id>xbounce</Id>
+ <Id>longrecursivedecay</Id>
+ </ChainedBehaviors>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.TimeSwitch</Class>
+ <Args>
+ <Id>main</Id>
+ <Inputs>
+ <Id>centerleft</Id>
+ <Id>center</Id>
+ </Inputs>
+ <TimeMap>{'scanningbars':10}</TimeMap>
+ <InputMap>{'scanningbars':'centerleft'}
+ </InputMap>
+ <RenderToScreen>False</RenderToScreen>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.DebugBehavior</Class>
+ <Args>
+ <Id>debug</Id>
+ <z-index>0</z-index>
+ <Inputs>
+ <Id>pygamekey</Id>
+ <Id>udp</Id>
+ </Inputs>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.AllPixels</Class>
+ <Args>
+ <Id>square</Id>
+ <Width>20</Width>
+ </Args>
+ </Behavior>
+ <Behavior Id="recursivedecay" InitialResponseCount="50">
+ <InheritsFrom>behaviors/LoopAndDie.xml</InheritsFrom>
+ </Behavior>
+ <Behavior Id="longrecursivedecay" InitialResponseCount="80">
+ <InheritsFrom>behaviors/LoopAndDie.xml</InheritsFrom>
+ </Behavior>
+ <Behavior Id="colorchange">
+ <InheritsFrom>behaviors/RandomColor.xml</InheritsFrom>
+ <Args>
+ <ColorList>
+ <Val>(0,0,255)</Val>
+ </ColorList>
+ </Args>
+ </Behavior>
+
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>runcolordecay</Id>
+ <ChainedBehaviors>
+ <Id>colorchanger</Id>
+ <Id>mover</Id>
+ <!--<Id>square</Id>-->
+ <Id>decay</Id>
+ </ChainedBehaviors>
+ <RecursiveHooks>{'mover':'movebounce'}</RecursiveHooks>
+ <RenderToScreen>False</RenderToScreen>
+ <Mapper>gaussmap</Mapper>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.ResponseMover</Class>
+ <Args>
+ <Id>mover</Id>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.ModifyParam</Class>
+ <Args>
+ <Id>incrinner</Id>
+ <ParamOp>{val}+.6</ParamOp>
+ <ParamName>innercircleRadius</ParamName>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.ModifyParam</Class>
+ <Args>
+ <Id>incrouter</Id>
+ <ParamOp>{val}+.6</ParamOp>
+ <ParamName>outercircleRadius</ParamName>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>circle_expand</Id>
+ <ChainedBehaviors>
+ <Id>innercircle</Id>
+ <Id>outercircle</Id>
+ <Id>incrinner</Id>
+ <Id>incrouter</Id>
+ <Id>recursivedecay</Id>
+ </ChainedBehaviors>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Id>expandingcircles</Id>
+ <ChainedBehaviors>
+ <Id>colorchange</Id>
+ <Id>mover</Id>
+ <Id>decay</Id>
+ <!--Id>singleframe</Id-->
+ </ChainedBehaviors>
+ <RecursiveHooks>{'mover':'circle_expand'}</RecursiveHooks>
+ </Args>
+ </Behavior>
+ </BehaviorConfiguration>
+</LightInstallation>
diff --git a/config/C5Sign-pygame.xml b/config/C5Sign-pygame.xml
index 2040642..d08e14c 100644
--- a/config/C5Sign-pygame.xml
+++ b/config/C5Sign-pygame.xml
@@ -14,5 +14,5 @@
</Args>
</InputElement>
</InputConfiguration>
- <InheritsFrom>config/C5Sign.xml</InheritsFrom>
+ <InheritsFrom>config/C5Sign-Leah.xml</InheritsFrom>
</LightInstallation>
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/inputs/ParametricLocationInput.py b/inputs/ParametricLocationInput.py
index e817a9d..7c7e968 100644
--- a/inputs/ParametricLocationInput.py
+++ b/inputs/ParametricLocationInput.py
@@ -1,6 +1,7 @@
import util.TimeOps as clock
import util.ComponentRegistry as compReg
import util.Strings as Strings
+import random
from operationscore.Input import *
class ParametricLocationInput(Input):
"""Takes three arguments: xEquation, yEquation, and useClock where
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