aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Russell Cohen <rcoh@mit.edu>2010-11-25 21:44:00 -0500
committerGravatar Russell Cohen <rcoh@mit.edu>2010-11-25 21:44:00 -0500
commit5783d6336f014c05e0e46d7bc35533e70b280582 (patch)
treeb4e0b0b2787b8fe464baadbd375ad6dc6c1dbf76
parentb042647b68abdc82490ca6e059993b8eba28904c (diff)
Added BehaviorChain to support chains of behaviors. Added FollowMouse
parameter to PygameInput to support following of mice. Added component registry to Util which allows any component to access any other component. Some changes to the structure of LightInstallation.
-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)