diff options
-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) |