aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LightInstallation.py77
-rw-r--r--Util.py67
-rw-r--r--behaviors/BehaviorChain.py10
-rw-r--r--behaviors/ColorChangerBehavior.py12
-rw-r--r--behaviors/DecayBehavior.py15
-rw-r--r--config/.LightInstallationConfig.xml.swpbin12288 -> 20480 bytes
-rw-r--r--config/Behavior.params2
-rw-r--r--config/Input.params3
-rw-r--r--config/LightInstallationConfig.xml54
-rw-r--r--docs/designDocs.tex4
-rw-r--r--inputs/PygameInput.py2
-rw-r--r--inputs/__init__.py0
-rw-r--r--operationscore/Behavior.py12
-rw-r--r--operationscore/Input.py12
-rw-r--r--pixelcore/Pixel.py3
-rw-r--r--pixelcore/PixelStrip.py3
-rw-r--r--pixelevents/DecayEvent.py9
17 files changed, 230 insertions, 55 deletions
diff --git a/LightInstallation.py b/LightInstallation.py
index 3fcdcd5..2891e08 100644
--- a/LightInstallation.py
+++ b/LightInstallation.py
@@ -9,19 +9,33 @@ class LightInstallation:
def __init__(self, configFileName):
self.inputs = {} #dict of inputs and their bound behaviors, keyed by InputId
self.behaviors = {}
+ self.behaviorOutputs = {} #key: [list of output destinations]
+ self.behaviorInputs = {}
+ self.componentDict = {}
+ self.inputBehaviorRegistry = {} #inputid -> behaviors listening to that
+ #input
+ #give Util a pointer to our componentRegistry so that everyone can use
+ #it
+ Util.setComponentDict(self.componentDict)
self.screen = Screen()
config = Util.loadConfigFile(configFileName)
+ #read configs from xml
rendererConfig = config.find('RendererConfiguration')
layoutConfig = config.find('LayoutConfiguration')
inputConfig = config.find('InputConfiguration')
behaviorConfig = config.find('BehaviorConfiguration')
- self.initializeLights(layoutConfig)
+ #inits
+ self.initializeScreen(layoutConfig)
self.initializeRenderers(rendererConfig)
self.initializeInputs(inputConfig)
self.initializeBehaviors(behaviorConfig)
-
+ #registration in dict
+ self.registerComponents(self.renderers)
+ self.registerComponents(self.inputs)
+ self.registerComponents(self.behaviors)
+ #Done initializing. Lets start this thing!
self.mainLoop()
- def initializeLights(self, layoutConfig):
+ def initializeScreen(self, layoutConfig):
layoutEngines = self.initializeComponent(layoutConfig)
[self.addPixelStrip(l) for l in layoutEngines]
def addPixelStrip(self, layoutEngine):
@@ -29,12 +43,20 @@ class LightInstallation:
self.screen.addStrip(pixelStrip)
def initializeInputs(self, inputConfig):
inputs = self.initializeComponent(inputConfig)
+ self.inputs = inputs
for inputClass in inputs:
inputClass.start()
- self.inputs[inputClass.argDict['InputId']] = (inputClass, [])
+ self.inputBehaviorRegistry[inputClass['Id']] = []
+ #empty list is list of bound behaviors
def initializeRenderers(self, rendererConfig):
self.renderers = self.initializeComponent(rendererConfig)
- print self.renderers
+ def registerComponents(self, components):
+ for component in components:
+ try:
+ cid = component['Id']
+ except:
+ raise Exception('Components must have Ids!')
+ self.componentDict[cid] = component
def initializeComponent(self, config):
components = []
if config != None:
@@ -42,7 +64,8 @@ class LightInstallation:
[module,className] = configItem.find('Class').text.split('.')
exec('from ' + module+'.'+className + ' import *')
args = Util.generateArgDict(configItem.find('Args'))
- args['parentScope'] = self
+ args['parentScope'] = self #TODO: we shouldn't give away scope
+ #like this, find another way.
components.append(eval(className+'(args)')) #TODO: doesn't error
#right
return components
@@ -52,32 +75,42 @@ class LightInstallation:
#self.screen.allOn()
while 1:
time.sleep(.1)
- responses = []
- for behaviorId in self.behaviors:
- [responses.append(b) for b in \
- self.behaviors[behaviorId].timeStep()] #processes all queued inputs
+ responses = self.evaluateBehaviors() #inputs are all queued when they
+ #happen, so we only need to run the behaviors
[self.screen.respond(response) for response in responses if
response != []]
self.screen.timeStep()
- if responses != []:
- print responses
[r.render(self.screen) for r in self.renderers]
+ #evaluates all the behaviors (including inter-dependencies) and returns a
+ #list of responses to go to the screen.
+ def evaluateBehaviors(self):
+ responses = {}
+ responses['Screen'] = [] #responses to the screen
+ for behavior in self.behaviors:
+ responses[behavior['Id']] = behavior.timeStep()
+ if behavior['RenderToScreen'] == True: #TODO: this uses extra space,
+ #we can use less in the future if needbe.
+ responses['Screen'] += responses[behavior['Id']]
+ return responses['Screen']
+
def initializeBehaviors(self, behaviorConfig):
- behaviors = self.initializeComponent(behaviorConfig)
- for behavior in behaviors:
- print behavior.argDict
+ self.behaviors = self.initializeComponent(behaviorConfig)
+ for behavior in self.behaviors:
self.addBehavior(behavior)
- print self.inputs
- print self.behaviors
+ #TODO: we probably don't need this anymore :(
+ def topologicalBehaviorSort(self):
+ return Util.topologicalSort(self.behaviorDependencies)
+ #Does work needed to add a behavior: currently -- maps behavior inputs into
+ #the input behavior registry.
def addBehavior(self, behavior):
- self.behaviors[behavior.argDict['behaviorId']] = behavior
for inputId in behavior.argDict['Inputs']:
- self.inputs[inputId][1].append(behavior.argDict['behaviorId'])
+ if inputId in self.inputBehaviorRegistry: #it could be a behavior
+ self.inputBehaviorRegistry[inputId].append(behavior['Id'])
def processResponse(self,inputDict, responseDict):
#pdb.set_trace()
- inputId = inputDict['InputId']
- boundBehaviors = self.inputs[inputId][1]
- [self.behaviors[b].addInput(responseDict) for b in boundBehaviors]
+ inputId = inputDict['Id']
+ boundBehaviorIds = self.inputBehaviorRegistry[inputId]
+ [self.componentDict[b].addInput(responseDict) for b in boundBehaviorIds]
def main(argv):
print argv
diff --git a/Util.py b/Util.py
index cfea4f5..533f0d9 100644
--- a/Util.py
+++ b/Util.py
@@ -3,21 +3,32 @@ from xml.etree.ElementTree import ElementTree
import math,struct
#import json # json.loads() to decode string; json.dumps() to encode data
import socket
+import random
from pygame.locals import *
import time as clock
-KINET_VERSION = 0x0001
-KINET_MAGIC = 0x4adc0104
-KINET_MOREMAGIC = 0xdeadbeef
-KINET_DEEPMAGIC = 0xc001d00d
-KINET_MAGICHASH = 0x69000420
-KINET_PORTOUT = 0x0108
-KINET_UNI = 0
+VERSION = 0x0001
+MAGIC = 0x4adc0104
+MOREMAGIC = 0xdeadbeef
+DEEPMAGIC = 0xc001d00d
+MAGICHASH = 0x69000420
+PORTOUT = 0x0108
+UNI = 0
CONFIG_PATH = 'config/'
kinetDict = {'flags': 0, 'startcode': 0, 'pad':0}
+componentDict = {}
+def setComponentDict(componentDictRef):
+ globals()["componentDict"] = componentDictRef
+def getComponentById(cid):
+ if cid in componentDict:
+ return componentDict[cid]
+ else:
+ return None
def dist(l1, l2):
return math.sqrt(sum([(l1[i]-l2[i])**2 for i in range(len(l1))]))
def time():
return clock.time()*1000
+def randomColor():
+ return [random.randint(0,255) for i in range(3)]
def loadParamRequirementDict(className):
return fileToDict(CONFIG_PATH + className)
def loadConfigFile(fileName):
@@ -36,10 +47,12 @@ def fileToDict(fileName):
if fileText == '':
return {}
return eval(fileText)
+def safeColor(c):
+ return [min(channel,255) for channel in c]
def combineColors(c1,c2):
- return [c1[i]+c2[i] for i in range(min(len(c1),len(c2)))]
+ return safeColor([c1[i]+c2[i] for i in range(min(len(c1),len(c2)))])
def multiplyColor(color, percent):
- return tuple([channel*(percent) for channel in color])
+ return safeColor(tuple([channel*(percent) for channel in color]))
#parses arguments into python objects if possible, otherwise leaves as strings
def generateArgDict(parentNode, recurse=False):
args = {}
@@ -63,6 +76,34 @@ def generateArgDict(parentNode, recurse=False):
if len(args.keys()) == 1 and recurse:
return args[args.keys()[0]]
return args
+#Given a dictionary of connections, returns their topological ordering -- (the
+#order in which they can be visited such that all parents have been visited
+#before their children. Returns the order or None if no such ordering exists
+#(the graph contains a cycle).
+def topologicalSort(adjacencyDict):
+ def dfsVisit(vertex):
+ gray[vertex] = 1
+ for child in adjacencyDict[vertex]:
+ if not child in visited:
+ if child in gray: #We have a cycle. No topological ordering
+ #exists!
+ raise Exception('Cycle!')
+ dfsVisit(child)
+ orderedList.insert(0, vertex)
+ visited[vertex] = 1
+ orderedList = []
+ visited = {}
+ gray = {}
+ for vertex in adjacencyDict:
+ try:
+ if not vertex in visited:
+ dfsVisit(vertex)
+ except:
+ return None #cycle
+ return orderedList
+def topoTest():
+ adj = {'a':['d','c'], 'b':['c'], 'c':['e'], 'd':['e'], 'e':[]}
+ print topologicalOrdering(adj)
def getConnectedSocket(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print (ip, port)
@@ -90,14 +131,14 @@ def composePixelStripPacket(pixelStrip,port):
return packet
def kinetHeader():
header = bytearray()
- header.extend(struct.pack('L', KINET_MAGIC))
- header.extend(struct.pack('H', KINET_VERSION))
- header.extend(struct.pack('H', KINET_PORTOUT))
+ header.extend(struct.pack('L', MAGIC))
+ header.extend(struct.pack('H', VERSION))
+ header.extend(struct.pack('H', PORTOUT))
header.extend(struct.pack('L', 0))
return header
def kinetPortOut():
header = kinetHeader()
- header.extend(struct.pack('L', KINET_UNI))
+ header.extend(struct.pack('L', UNI))
return header
def kinetPortOutPayload(argDict):
payload = bytearray()
diff --git a/behaviors/BehaviorChain.py b/behaviors/BehaviorChain.py
new file mode 100644
index 0000000..15f7d74
--- /dev/null
+++ b/behaviors/BehaviorChain.py
@@ -0,0 +1,10 @@
+from operationscore.Behavior import *
+import Util
+import pdb
+class BehaviorChain(Behavior):
+ def processResponse(self, sensorInputs, recursiveInputs):
+ response = sensorInputs
+ for behaviorId in self['ChainedBehaviors']:
+ behavior = Util.getComponentById(behaviorId)
+ response = behavior.immediateProcessInput(response)
+ return response
diff --git a/behaviors/ColorChangerBehavior.py b/behaviors/ColorChangerBehavior.py
new file mode 100644
index 0000000..8ecc5f2
--- /dev/null
+++ b/behaviors/ColorChangerBehavior.py
@@ -0,0 +1,12 @@
+from operationscore.Behavior import *
+import Util
+import pdb
+class ColorChangerBehavior(Behavior):
+ def processResponse(self, sensorInputs, recursiveInputs):
+ ret = []
+ for sensory in sensorInputs:
+ newDict = dict(sensory) #don't run into shallow copy issues
+ #TODO: support for PixelEvents
+ newDict['Color'] = Util.randomColor()
+ ret.append(newDict)
+ return ret
diff --git a/behaviors/DecayBehavior.py b/behaviors/DecayBehavior.py
new file mode 100644
index 0000000..f9efa3b
--- /dev/null
+++ b/behaviors/DecayBehavior.py
@@ -0,0 +1,15 @@
+from operationscore.Behavior import *
+from pixelevents.DecayEvent import *
+import Util
+import pdb
+class DecayBehavior(Behavior):
+ def processResponse(self, sensorInputs, recursiveInputs):
+ ret = []
+ #TODO: move into params
+ for sensory in sensorInputs:
+ outDict = {}
+ outDict[Util.location] = sensory[Util.location]
+ outDict['PixelEvent'] = \
+ DecayEvent.generate('Proportional',100, sensory['Color'])
+ ret.append(outDict)
+ return ret
diff --git a/config/.LightInstallationConfig.xml.swp b/config/.LightInstallationConfig.xml.swp
index cfcec1b..e3dbfd3 100644
--- a/config/.LightInstallationConfig.xml.swp
+++ b/config/.LightInstallationConfig.xml.swp
Binary files differ
diff --git a/config/Behavior.params b/config/Behavior.params
index 532fa03..0967ef4 100644
--- a/config/Behavior.params
+++ b/config/Behavior.params
@@ -1 +1 @@
-{'Inputs':'Inputs must be defined!'}
+{}
diff --git a/config/Input.params b/config/Input.params
index f051cb3..0967ef4 100644
--- a/config/Input.params
+++ b/config/Input.params
@@ -1,2 +1 @@
-{'InputId': 'InputId must be defined in config XML', 'RefreshInterval':
-'RefreshInterval must be defined in config XML'}
+{}
diff --git a/config/LightInstallationConfig.xml b/config/LightInstallationConfig.xml
index a5f4a08..a4722db 100644
--- a/config/LightInstallationConfig.xml
+++ b/config/LightInstallationConfig.xml
@@ -56,14 +56,22 @@
<InputElement>
<Class>inputs.PygameInput</Class>
<Args><!--Passed as a dictionary-->
- <InputId>pygame</InputId>
+ <Id>pygame</Id>
+ <RefreshInterval>100</RefreshInterval>
+ </Args>
+ </InputElement>
+ <InputElement>
+ <Class>inputs.PygameInput</Class>
+ <Args><!--Passed as a dictionary-->
+ <Id>followmouse</Id>
+ <FollowMouse>True</FollowMouse>
<RefreshInterval>100</RefreshInterval>
</Args>
</InputElement>
<InputElement>
<Class>inputs.UDPInput</Class>
<Args>
- <InputId>UDP</InputId>
+ <Id>UDP</Id>
<Port>6038</Port>
<RefreshInterval>100</RefreshInterval>
</Args>
@@ -73,21 +81,55 @@
<Behavior>
<Class>behaviors.EchoBehavior</Class>
<Args>
- <behaviorId>echo</behaviorId>
+ <Id>echo</Id>
+ <z-index>0</z-index>
+ <RenderToScreen>False</RenderToScreen>
+ <Inputs>
+ </Inputs>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.ColorChangerBehavior</Class>
+ <Args>
+ <Id>color</Id>
<z-index>0</z-index>
+ <RenderToScreen>False</RenderToScreen>
+ <Inputs>
+ </Inputs>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.DecayBehavior</Class>
+ <Args>
+ <Id>decay</Id>
+ <z-index>0</z-index>
+ <RenderToScreen>False</RenderToScreen>
<Inputs>
- <InputId>pygame</InputId>
</Inputs>
</Args>
</Behavior>
<Behavior>
<Class>behaviors.DebugBehavior</Class>
<Args>
- <behaviorId>debug</behaviorId>
+ <Id>debug</Id>
<z-index>0</z-index>
<Inputs>
- <InputId>UDP</InputId>
+ <Id>UDP</Id>
+ </Inputs>
+ </Args>
+ </Behavior>
+ <Behavior>
+ <Class>behaviors.BehaviorChain</Class>
+ <Args>
+ <Inputs>
+ <Id>followmouse</Id>
</Inputs>
+ <ChainedBehaviors>
+ <Id>echo</Id>
+ <Id>color</Id>
+ <Id>decay</Id>
+ </ChainedBehaviors>
+ <RenderToScreen>True</RenderToScreen>
</Args>
</Behavior>
</BehaviorConfiguration>
diff --git a/docs/designDocs.tex b/docs/designDocs.tex
index 2a9fd97..327c280 100644
--- a/docs/designDocs.tex
+++ b/docs/designDocs.tex
@@ -164,6 +164,10 @@ argument. This will be passed in via recursive inputs.}
\subsection{Time}
For time, use the \texttt{Util.time()} method to return the current
time in ms.
+ \subsection{Acessing a Component Given an Id}
+ Use \texttt{Util.getComponentById(id)}. This provides any
+ component access to any other components. We may consider a way to
+ make this read-only.
\subsection{Acessing Pixels}
The ideal method for acessing Pixels in a screen is to use its
iterator. Iterating over the individual PixelStrips is also an
diff --git a/inputs/PygameInput.py b/inputs/PygameInput.py
index 6c84664..e07592b 100644
--- a/inputs/PygameInput.py
+++ b/inputs/PygameInput.py
@@ -7,6 +7,8 @@ from pygame.locals import *
class PygameInput(Input):
def sensingLoop(self):
#try:
+ if self['FollowMouse']:
+ self.respond({Util.location: pygame.mouse.get_pos()})
for event in pygame.event.get():
if event.type is KEYDOWN:
self.respond({Util.location: (5,5),'Key': event.key})
diff --git a/inputs/__init__.py b/inputs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/inputs/__init__.py
diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py
index f29430f..198c4b2 100644
--- a/operationscore/Behavior.py
+++ b/operationscore/Behavior.py
@@ -21,8 +21,16 @@ class Behavior(SmootCoreObject):
self.outGoingQueue = []
def processResponse(self, sensorInputs, recursiveInputs):
pass
- def addInput(self, sensorInputs):
- self.sensorResponseQueue.append(sensorInputs)
+ def addInput(self, sensorInput):
+ self.sensorResponseQueue.append(sensorInput)
+ #used for behavior chaining
+ def immediateProcessInput(self, sensorInputs):
+ return self.processResponse(sensorInputs, [])
+ def addInputs(self, sensorInputs):
+ if type(sensorInputs) == type([]):
+ [self.addInput(sensorInput) for sensorInput in sensorInputs]
+ else:
+ self.addInput(sensorInputs)
def recursiveReponse(self, args):
self.responseQueue.append(args)
def timeStep(self):
diff --git a/operationscore/Input.py b/operationscore/Input.py
index 1ba4528..67a7bb0 100644
--- a/operationscore/Input.py
+++ b/operationscore/Input.py
@@ -13,14 +13,22 @@ class Input(threading.Thread):
self.eventQueue = []
self.parentScope = argDict['parentScope']
self.argDict = argDict
- if not 'InputId' in argDict:
- raise Exception('InputId must be defined in config xml')
if not 'RefreshInterval' in argDict:
print 'RefreshInterval not defined. Defaulting to .5s.'
self.argDict['RefreshInterval'] = 500
self.inputInit()
threading.Thread.__init__(self)
self.daemon = True #This kills this thread when the main thread stops
+ #CHEATING until I can get multiple inheritence working
+ def __setitem__(self,k, item):
+ self.argDict[k] = item
+ def __getitem__(self, item):
+ if item in self.argDict:
+ return self.argDict[item]
+ else:
+ return None
+ def __getiter__(self):
+ return self.argDict.__getiter__()
def respond(self, eventDict):
#if eventDict != []:
#pdb.set_trace()
diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py
index 6784c63..a71dba5 100644
--- a/pixelcore/Pixel.py
+++ b/pixelcore/Pixel.py
@@ -32,12 +32,9 @@ class Pixel:
eventResult = event.state(currentTime-eventTime)
if eventResult != None:
resultingColor = Util.combineColors(eventResult, resultingColor)
- print resultingColor
else:
deadEvents.append(eventTime)
[self.events.pop(event) for event in deadEvents]
- if sum(resultingColor) > 0:
- print resultingColor
return tuple(resultingColor)
def __str__(self):
return 'Loc: ' + str(self.location)
diff --git a/pixelcore/PixelStrip.py b/pixelcore/PixelStrip.py
index 14c87d9..45d2c8b 100644
--- a/pixelcore/PixelStrip.py
+++ b/pixelcore/PixelStrip.py
@@ -21,14 +21,13 @@ class PixelStrip:
[l.turnOnFor(time) for l in self.pixels] #TODO: add test-on method to
#pixels
def respond(self, responseInfo):
- print 'PixelEvent', responseInfo
location = responseInfo[Util.location]
if not 'PixelEvent' in responseInfo:
if 'Color' in responseInfo:
color = responseInfo['Color']
else:
raise Exception('Need Color. Probably')
- responseInfo['PixelEvent'] = StepEvent.generate(300, color)
+ responseInfo['PixelEvent'] = StepEvent.generate(300, color)
(dist, pixel) = self.getPixelNearest(location)
pixel.processInput(responseInfo['PixelEvent'], 0) #TODO: z-index
diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py
index c9fc226..01255be 100644
--- a/pixelevents/DecayEvent.py
+++ b/pixelevents/DecayEvent.py
@@ -1,4 +1,4 @@
-from pixelcore import PixelEvent
+from operationscore.PixelEvent import *
import Util, math
class DecayEvent(PixelEvent):
def initEvent(self):
@@ -9,4 +9,9 @@ class DecayEvent(PixelEvent):
decay = math.exp(timeDelay*-1*self['Coefficient'])
if self['DecayType'] == 'Proportional':
decay = float(self['Coefficient']) / timeDelay
- return Util.multiplyColor(self['Color'], decay)
+ color = Util.multiplyColor(self['Color'], decay)
+ return color if sum(color) > 5 else None
+ @staticmethod
+ def generate(decayType, coefficient, color):
+ args = {'DecayType': decayType, 'Coefficient':coefficient, 'Color':color}
+ return DecayEvent(args)