diff options
author | dxiao <dxiao@mit.edu> | 2011-02-20 19:31:04 -0500 |
---|---|---|
committer | dxiao <dxiao@mit.edu> | 2011-02-20 19:31:04 -0500 |
commit | 8660df89beb0869204adc270710b3d9efe70dde1 (patch) | |
tree | d35295eca82b64221406393399a79abfeed7e110 | |
parent | 42a3112b7cd7518ab69ba8d69c636a6278cfb288 (diff) | |
parent | fd13edeeb2f277ee78af6d8ced1fba81a67ed1ba (diff) |
Merge branch 'conner5' of github.com:dxiao/SmootLight into conner5
-rw-r--r-- | config/C5Sign-Leah.xml | 301 | ||||
-rw-r--r-- | config/C5Sign-pygame.xml | 2 | ||||
-rw-r--r-- | config/C5Sign-websocket.xml | 9 | ||||
-rw-r--r-- | inputs/ParametricLocationInput.py | 1 | ||||
-rw-r--r-- | renderers/Websocket.xml | 10 | ||||
-rw-r--r-- | renderers/WebsocketRenderer.py | 128 | ||||
-rw-r--r-- | web/smootlight.html | 107 |
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}<2 or {x}>48</LocationRestriction> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RestrictLocation</Class> + <Args> + <Id>ybounce</Id> + <Action>{val}*-1</Action> + <ParamName>YStep</ParamName> + <LocationRestriction>{y}<2 or {y}>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 |