diff options
author | Russell Cohen <rcoh@mit.edu> | 2010-11-25 21:44:00 -0500 |
---|---|---|
committer | Russell Cohen <rcoh@mit.edu> | 2010-11-25 21:44:00 -0500 |
commit | 5783d6336f014c05e0e46d7bc35533e70b280582 (patch) | |
tree | b4e0b0b2787b8fe464baadbd375ad6dc6c1dbf76 | |
parent | b042647b68abdc82490ca6e059993b8eba28904c (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.py | 77 | ||||
-rw-r--r-- | Util.py | 67 | ||||
-rw-r--r-- | behaviors/BehaviorChain.py | 10 | ||||
-rw-r--r-- | behaviors/ColorChangerBehavior.py | 12 | ||||
-rw-r--r-- | behaviors/DecayBehavior.py | 15 | ||||
-rw-r--r-- | config/.LightInstallationConfig.xml.swp | bin | 12288 -> 20480 bytes | |||
-rw-r--r-- | config/Behavior.params | 2 | ||||
-rw-r--r-- | config/Input.params | 3 | ||||
-rw-r--r-- | config/LightInstallationConfig.xml | 54 | ||||
-rw-r--r-- | docs/designDocs.tex | 4 | ||||
-rw-r--r-- | inputs/PygameInput.py | 2 | ||||
-rw-r--r-- | inputs/__init__.py | 0 | ||||
-rw-r--r-- | operationscore/Behavior.py | 12 | ||||
-rw-r--r-- | operationscore/Input.py | 12 | ||||
-rw-r--r-- | pixelcore/Pixel.py | 3 | ||||
-rw-r--r-- | pixelcore/PixelStrip.py | 3 | ||||
-rw-r--r-- | pixelevents/DecayEvent.py | 9 |
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 @@ -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 Binary files differindex cfcec1b..e3dbfd3 100644 --- a/config/.LightInstallationConfig.xml.swp +++ b/config/.LightInstallationConfig.xml.swp 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) |